chef-12.14.60/000077500000000000000000000000001276456504500126705ustar00rootroot00000000000000chef-12.14.60/.bundle/000077500000000000000000000000001276456504500142175ustar00rootroot00000000000000chef-12.14.60/.bundle/config000066400000000000000000000000271276456504500154060ustar00rootroot00000000000000--- BUNDLE_FROZEN: '1' chef-12.14.60/.gitattributes000066400000000000000000000003701276456504500155630ustar00rootroot00000000000000# git config merge.ignore.name 'ignore changes merge driver' # git config merge.ignore.driver 'touch %A' distro/common/html/* merge=ignore distro/common/man/man1/* merge=ignore distro/common/man/man8/* merge=ignore lib/chef/version.rb merge=ignore chef-12.14.60/.github/000077500000000000000000000000001276456504500142305ustar00rootroot00000000000000chef-12.14.60/.github/ISSUE_TEMPLATE.md000066400000000000000000000023761276456504500167450ustar00rootroot00000000000000## Description Briefly describe the issue ## Chef Version Tell us which version of chef-client you are using (see below for Server+ChefDK bugs). ## Platform Version Tell us which Operating System distribution and version chef-client is running on. ## Replication Case Tell us what steps to take to replicate your problem. See [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) for information on how to create a good replication case. ## Client Output The relevant output of the chef-client run or a link to a gist of the entire run, if there is one. The debug output (chef-client -l debug) may be useful, but please link to a gist, or truncate it. ## Stacktrace Please include the stacktrace.out output or link to a gist of it, if there is one. ### NOTE: CHEF CLIENT BUGS ONLY This issue tracker is for the code contained within this repo -- `chef-client`, base `knife` functionality (not plugins), `chef-apply`, `chef-solo`, `chef-client -z`, etc. * [Server issues](https://github.com/chef/chef-server/issues/new) * [ChefDK issues](https://github.com/chef/chef-dk/issues/new) * Cookbook Issues (see the https://github.com/chef-cookbooks repos or search [Supermarket](https://supermarket.chef.io) or GitHub/Google) chef-12.14.60/.gitignore000066400000000000000000000011621276456504500146600ustar00rootroot00000000000000.autotest coverage .DS_Store pkg/* tags */tags *~ .chef # You should check in your Gemfile.lock in applications, and not in gems external_tests/*.lock /Gemfile.local # ignore some common Bundler 'binstubs' directory names # http://gembundler.com/man/bundle-exec.1.html b/ binstubs/ **/.bundle # RVM and RBENV ruby version files .rbenv-version .rvmrc .ruby-version .ruby-gemset # IDE files .project # Documentation _site/* .yardoc/ doc/ # Test Kitchen .kitchen/ # Vagrant Vagrantfile .vagrant/ # Kitchen Tests Local Mode Data kitchen-tests/nodes/* # Temporary files present during spec runs spec/data/test-dir /config/ chef-12.14.60/.mailmap000066400000000000000000000157271276456504500143250ustar00rootroot00000000000000# Daniel DeLeo Daniel DeLeo Daniel DeLeo Daniel DeLeo danielsdeleo Daniel DeLeo Dan DeLeo Daniel DeLeo Dan DeLeo Daniel DeLeo danielsdeleo Daniel DeLeo danielsdeleo Daniel DeLeo Daniel DeLeo # Adam Jacob Adam Jacob Adam Jacob Adam Jacob Adam Jacob Adam Jacob Adam Jacob Adam Jacob Adam Jacob # Bryan McLellan Bryan McLellan Bryan McLellan Bryan McLellan Bryan McLellan Bryan McLellan Bryan McLellan Bryan McLellan Bryan McLellan Bryan McLellan Bryan McLellan # Lamont Granquist Lamont Granquist Lamont Granquist Lamont Granquist lamont-opscode Lamont Granquist Lamont Granquist Lamont Granquist Lamont Granquist Lamont Granquist lamont-granquist # Serdar Sutay Serdar Sutay Serdar Sutay Serdar Sutay sersut Serdar Sutay ssutay # Claire McQuin Claire McQuin Claire McQuin Claire McQuin Claire McQuin Claire McQuin Claire McQuin Claire McQuin Claire McQuin # John Keiser John Keiser John Keiser John Keiser jkeiser John Keiser John Keiser John Keiser John Keiser # Seth Chisamore Seth Chisamore Seth Chisamore Seth Chisamore Seth Chisamore # Joshua Timberman Joshua Timberman jtimberman Joshua Timberman Joshua Timberman Joshua Timberman Joshua Timberman Joshua Timberman Joshua Timberman Joshua Timberman jtimberman Joshua Timberman jtimberman Joshua Timberman jtimberman # Nuo Yan Nuo Yan Nuo Yan Nuo Yan Nuo Yan Nuo Yan # Thom May Thom May Thom May Thom May Thom May Thom May Thom May Thom May Thom May Thom May Thom May # Stephen Delano Stephen Delano Stephen Delano Stephen Delano Stephen Delano Stephen Delano Stephen Delano Stephen Delano sdelano # AJ Christensen AJ Christensen AJ Christensen AJ Christensen # Seth Falcon Seth Falcon Seth Falcon Seth Falcon Seth Falcon # Adam Edwards Adam Edwards Adam Edwards Adam Edwards adamedx Adam Edwards adamedx Adam Edwards adamedx # Prajakta Purohit Prajakta Purohit Prajakta Purohit Prajakta Purohit PrajaktaPurohit kaustubh-d kaustubh-d kaustubh kaustubh-d kaustubh-d # Xabier de Zuazo Xabier de Zuazo Xabier de Zuazo Xabier de Zuazo Xabier de Zuazo Xabier de Zuazo # Tim Hinderliter Tim Hinderliter Tim Hinderliter tim@opscode.com Tim Hinderliter timh # Mark Paradise Marc Paradise Marc Paradise Marc Paradise marc@opscode.com # Tyler Ball Tyler Ball tyler-ball # Steven Danna Steven Danna Steven Danna # Salim Alam Salim Alam chefsalim # Isa Farnik Isa Farnik curiositycasualty Isa Farnik curiositycasualty # Paul Mooring Paul Mooring Paul Mooring # Jeremiah Snapp Jeremiah Snapp Jeremiah Snapp Jeremiah Snapp Jeremiah Snapp Jeremiah Snapp Jeremiah Snapp # Mark Myzk Mark Mzyk Mark Mzyk Mark Mzyk mmzyk Mark Mzyk Mark Mzyk # Chris Doherty Chris Doherty Chris Doherty Chris Doherty Chris Doherty Chris Doherty Chris Doherty Chris Doherty unknown # Christopher Webber Christopher Webber Christopher Webber # Tyler Cloke Tyler Cloke tylercloke Tyler Cloke tylercloke Tyler Cloke Tyler Cloke # Julian Dunn Julian C. Dunn Julian C. Dunn Julian C. Dunn Julian C. Dunn Julian C. Dunn Julian C. Dunn # Tom Duffield Tom Duffield Tom Duffield Tom Duffield Tom Duffield # Scott Hain Scott Hain Scott Hain # Peter Burkholder Peter Burkholder Peter Burkholder # JJ Ashgar JJ Asghar JJ Asghar chef-12.14.60/.rspec000066400000000000000000000000141276456504500140000ustar00rootroot00000000000000--color -fd chef-12.14.60/.rubocop.yml000066400000000000000000000001551276456504500151430ustar00rootroot00000000000000AllCops: Exclude: - "spec/data/**/*" - "vendor/**/*" - "pkg/**/*" - "chef-config/pkg/**/*" chef-12.14.60/.travis.yml000066400000000000000000000256161276456504500150130ustar00rootroot00000000000000language: ruby sudo: false cache: bundler # Early warning system to catch if Rubygems breaks something 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) - rm -f .bundle/config bundler_args: --without changelog development docgen guard maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen before_script: # force all .rspec tests into progress display to reduce line count - echo --color > .rspec - echo -fp >> .rspec # necessary for sudo: true tests, ingore failures on tests invoked with sudo: false - sudo sed -i -e 's/^Defaults\tsecure_path.*$//' /etc/sudoers || true # do not run expensive spec tests on PRs, only on branches branches: only: - master - 10-stable - 11-stable env: global: - FORCE_FFI_YAJL=ext matrix: include: - rvm: 2.2.5 sudo: true script: sudo -E $(which bundle) exec rake spec; # also remove integration / external tests bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen - rvm: 2.3.1 sudo: true script: sudo -E $(which bundle) exec rake spec; # also remove integration / external tests bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen - rvm: rbx sudo: true script: sudo -E $(which bundle) exec rake spec; # also remove integration / external tests bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen - env: CHEFSTYLE: 1 rvm: 2.3.1 script: bundle exec rake style # also remove integration / external tests bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen - env: AUDIT_CHECK: 1 rvm: 2.3.1 script: bundle exec bundle-audit check --update # also remove integration / external tests bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen # # External tests # - env: TEST_GEM: chef-provisioning script: tasks/bin/run_external_test $TEST_GEM rake spec rvm: 2.3.1 - env: TEST_GEM: chef-provisioning-aws script: tasks/bin/run_external_test $TEST_GEM rake spec rvm: 2.3.1 - env: TEST_GEM: chef-sugar script: tasks/bin/run_external_test $TEST_GEM rake rvm: 2.3.1 - env: - TEST_GEM: chef-zero script: tasks/bin/run_external_test $TEST_GEM rake spec cheffs rvm: 2.3.1 - env: TEST_GEM: cheffish script: tasks/bin/run_external_test $TEST_GEM rake spec rvm: 2.3.1 - env: TEST_GEM: chefspec # The chefspec tests + bundler cache + "gem update --system" interact badly :/ # (Cucumber doesn't start.) before_install: - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) - bundle config --local without server:docgen:maintenance:omnibus_package:development:ruby_prof:pry script: tasks/bin/run_external_test $TEST_GEM rake rvm: 2.3.1 - env: TEST_GEM: foodcritic script: tasks/bin/run_external_test $TEST_GEM rake test rvm: 2.3.1 - env: TEST_GEM: halite script: tasks/bin/run_external_test $TEST_GEM rake spec rvm: 2.3.1 - env: TEST_GEM: knife-windows script: tasks/bin/run_external_test $TEST_GEM rake unit_spec rvm: 2.3.1 - env: TEST_GEM: poise script: tasks/bin/run_external_test $TEST_GEM rake spec rvm: 2.3.1 ### START TEST KITCHEN ONLY ### # - rvm: 2.3.1 services: docker sudo: required gemfile: kitchen-tests/Gemfile before_install: - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2) - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen before_script: - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) - cd kitchen-tests script: - bundle exec kitchen test ubuntu-1204 after_failure: - cat .kitchen/logs/kitchen.log env: - UBUNTU=12.04 - KITCHEN_YAML=.kitchen.travis.yml - rvm: 2.3.1 services: docker sudo: required gemfile: kitchen-tests/Gemfile before_install: - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2) - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen before_script: - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) - cd kitchen-tests script: - bundle exec kitchen test ubuntu-1404 after_failure: - cat .kitchen/logs/kitchen.log env: - UBUNTU=14.04 - KITCHEN_YAML=.kitchen.travis.yml - rvm: 2.3.1 services: docker sudo: required gemfile: kitchen-tests/Gemfile before_install: - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2) - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen before_script: - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) - cd kitchen-tests script: - bundle exec kitchen test ubuntu-1604 after_failure: - cat .kitchen/logs/kitchen.log env: - UBUNTU=16.04 - KITCHEN_YAML=.kitchen.travis.yml - rvm: 2.3.1 services: docker sudo: required gemfile: kitchen-tests/Gemfile before_install: - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2) - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen before_script: - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) - cd kitchen-tests script: - bundle exec kitchen test debian-7 after_failure: - cat .kitchen/logs/kitchen.log env: - DEBIAN=7 - KITCHEN_YAML=.kitchen.travis.yml - rvm: 2.3.1 services: docker sudo: required gemfile: kitchen-tests/Gemfile before_install: - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2) - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen before_script: - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) - cd kitchen-tests script: - bundle exec kitchen test debian-8 bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen after_failure: - cat .kitchen/logs/kitchen.log env: - DEBIAN=8 - KITCHEN_YAML=.kitchen.travis.yml - rvm: 2.3.1 services: docker sudo: required gemfile: kitchen-tests/Gemfile before_install: - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2) - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen before_script: - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) - cd kitchen-tests script: - bundle exec kitchen test centos-6 after_failure: - cat .kitchen/logs/kitchen.log env: - CENTOS=6 - KITCHEN_YAML=.kitchen.travis.yml - rvm: 2.3.1 services: docker sudo: required gemfile: kitchen-tests/Gemfile before_install: - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2) - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen before_script: - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) - cd kitchen-tests script: - bundle exec kitchen test centos-7 after_failure: - cat .kitchen/logs/kitchen.log env: - CENTOS=7 - KITCHEN_YAML=.kitchen.travis.yml - rvm: 2.3.1 services: docker sudo: required gemfile: kitchen-tests/Gemfile before_install: - gem update --system $(grep rubygems omnibus_overrides.rb | cut -d'"' -f2) - gem install bundler -v $(grep bundler omnibus_overrides.rb | cut -d'"' -f2) bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen before_script: - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) - cd kitchen-tests script: - bundle exec kitchen test fedora-23 after_failure: - cat .kitchen/logs/kitchen.log env: - FEDORA=23 - KITCHEN_YAML=.kitchen.travis.yml ### END TEST KITCHEN ONLY ### - rvm: 2.3.1 sudo: required dist: trusty 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) - sudo apt-get update - sudo apt-get -y install squid3 git curl bundler_args: --without changelog development docgen guard integration maintenance omnibus_package tools aix bsd mac_os_x solaris windows --frozen env: - PROXY_TESTS_DIR=proxy_tests/files/default/scripts - PROXY_TESTS_REPO=$PROXY_TESTS_DIR/repo script: - bundle exec chef-client --version - git clone https://github.com/chef/proxy_tests.git - rvmsudo -E bundle exec bash $PROXY_TESTS_DIR/run_tests.sh chef_client \* \* /tmp/out.txt after_script: - cat /tmp/out.txt - sudo cat /var/log/squid3/cache.log - sudo cat /var/log/squid3/access.log allow_failures: - rvm: rbx notifications: on_change: true on_failure: true on_success: change on_pull_requests: false irc: channels: - chat.freenode.net#chef-hacking chef-12.14.60/.yardopts000066400000000000000000000000141276456504500145310ustar00rootroot00000000000000-m markdown chef-12.14.60/CBGB.md000066400000000000000000000025761276456504500137210ustar00rootroot00000000000000 # Chef Board of Governance (CBGB) Chef was designed from the outset to have a very open structure, including open design, open contribution, and consistent use of tools across the project. Given the large numbers of contributors, users, and companies with a stake in the future of the project, Chef leadership has established an advisory board, as part of its long term commitment to open governance. The Chef Board of Governance (CBGB) shall advise the Leadership on matters related to supporting the long-term governance, structure, and roadmap of the Project. More information can be found in the [Chef Board of Governance RFC](Chef Board of Governance). # Board of Governors ## Project Lead * [Adam Jacob](https://github.com/adamhjk) ### Users/Contributors (4) * [Ranjib Dey](https://github.com/ranjib) * [Doug Ireton](https://github.com/dougireton) * [Noah Kantrowitz](https://github.com/coderanger) * [Charity Majors](https://github.com/charity) ### Corporate Contributors (4) * Etsy - Katherine Daniels * Facebook - Phil Dibowitz * Nordstrom - Mark Ayers * PagerDuty - Evan Gilman ### Lieutenants (3) * [Jon Cowie](https://github.com/jonlives) * [Joshua Timberman](https://github.com/jtimberman) * [Seth Vargo](https://github.com/sethvargo) chef-12.14.60/CBGB.toml000066400000000000000000000044021276456504500142620ustar00rootroot00000000000000# # This file is structured to be consumed by both humans and computers. # It is a TOML document containing Markdown # [Preamble] title = "Chef Board of Governance (CBGB)" text = """ Chef was designed from the outset to have a very open structure, including open design, open contribution, and consistent use of tools across the project. Given the large numbers of contributors, users, and companies with a stake in the future of the project, Chef leadership has established an advisory board, as part of its long term commitment to open governance. The Chef Board of Governance (CBGB) shall advise the Leadership on matters related to supporting the long-term governance, structure, and roadmap of the Project. More information can be found in the [Chef Board of Governance RFC](Chef Board of Governance). """ [Org] [Org.Lead] title = "Project Lead" person = "adamhjk" [Org.Contributors] title = "Users/Contributors (4)" governers = [ "ranjibdey", "dougireton", "coderanger", "charitymajors" ] [Org.Corporate-Contributors] title = "Corporate Contributors (4)" governers = [ "etsy", "facebook", "nordstrom", "pagerduty" ] [Org.Lieutenants] title = "Lieutenants (3)" governers = [ "jonlives", "jtimberman", "sethvargo" ] [people] [people.adamhjk] Name = "Adam Jacob" GitHub = "adamhjk" IRC = "holoway" [people.jonlives] Name = "Jon Cowie" GitHub = "jonlives" IRC = "jonlives" [people.coderanger] Name = "Noah Kantrowitz" GitHub = "coderanger" [people.jtimberman] Name = "Joshua Timberman" GitHub = "jtimberman" [people.ranjibdey] Name = "Ranjib Dey" GitHub = "ranjib" [people.sethvargo] Name = "Seth Vargo" GitHub = "sethvargo" [people.dougireton] Name = "Doug Ireton" GitHub = "dougireton" [people.charitymajors] Name = "Charity Majors" GitHub = "charity" [corporations] [corporations.etsy] Name = "Etsy" Person = "Katherine Daniels" [corporations.facebook] Name = "Facebook" Person = "Phil Dibowitz" [corporations.nordstrom] Name = "Nordstrom" Person = "Mark Ayers" [corporations.pagerduty] Name = "PagerDuty" Person = "Evan Gilman" chef-12.14.60/CHANGELOG.md000066400000000000000000002570671276456504500145220ustar00rootroot00000000000000# Change Log ## [v12.13.37](https://github.com/chef/chef/tree/v12.13.37) (2016-08-12) [Full Changelog](https://github.com/chef/chef/compare/v12.13.30...v12.13.37) **Enhancements:** - Bumping ohai and mixlib-log to fix regression [\#5197](https://github.com/chef/chef/pull/5197) ([mwrock](https://github.com/mwrock)) - Remove requires in Chef::Recipe that are no longer necessary [\#5189](https://github.com/chef/chef/pull/5189) ([lamont-granquist](https://github.com/lamont-granquist)) ## [v12.13.30](https://github.com/chef/chef/tree/v12.13.30) (2016-08-05) [Full Changelog](https://github.com/chef/chef/compare/v12.12.15...v12.13.30) **Enhancements:** - noop apt_update similar to apt_repository [\#5173](https://github.com/chef/chef/pull/5173) ([lamont-granquist](https://github.com/lamont-granquist)) - Bump dependencies to bring in Ohai 8.18 [\#5168](https://github.com/chef/chef/pull/5168) ([tas50](https://github.com/tas50)) - Make Chef work with Ruby 2.3, update Ruby to 2.1.9 [\#5165](https://github.com/chef/chef/pull/5165) ([jkeiser](https://github.com/jkeiser)) - Log cause chain for exceptions [\#3354](https://github.com/chef/chef/pull/3354) ([jaym](https://github.com/jaym)) - First pass on --config-option handling. [\#5045](https://github.com/chef/chef/pull/5045) ([coderanger](https://github.com/coderanger)) - Add bootstrap proxy authentication support. [\#4059](https://github.com/chef/chef/pull/4059) ([yossigo](https://github.com/yossigo)) - Support setting an empty string for cron attrs [\#5127](https://github.com/chef/chef/pull/5127) ([thommay](https://github.com/thommay)) - Also clear notifications when deleting a resource. [\#5146](https://github.com/chef/chef/pull/5146) ([coderanger](https://github.com/coderanger)) - Clean up subscribes internals and notification storage. [\#5145](https://github.com/chef/chef/pull/5145) ([coderanger](https://github.com/coderanger)) - Cache ChefFS children [\#5131](https://github.com/chef/chef/pull/5131) ([thommay](https://github.com/thommay)) - Update to rspec 3.5 [\#5126](https://github.com/chef/chef/pull/5126) ([thommay](https://github.com/thommay)) - Add `chef\_data\_bag\_item` to Cheffish DSL methods [\#5125](https://github.com/chef/chef/pull/5125) ([danielsdeleo](https://github.com/danielsdeleo)) - replace glibc resolver with ruby resolver [\#5123](https://github.com/chef/chef/pull/5123) ([lamont-granquist](https://github.com/lamont-granquist)) - The user must specify a category for a new cookbook [\#5091](https://github.com/chef/chef/pull/5091) ([thommay](https://github.com/thommay)) - Warn if not installing an individual bff fileset [\#5093](https://github.com/chef/chef/pull/5093) ([mwrock](https://github.com/mwrock)) - Use Mixlib::Archive to extract tarballs [\#5080](https://github.com/chef/chef/pull/5080) ([thommay](https://github.com/thommay)) - Data Collector server URL validation, and disable on host down [\#5076](https://github.com/chef/chef/pull/5076) ([adamleff](https://github.com/adamleff)) **Fixed Bugs:** - Don't log error for reporting audit data in when in chef-zero [\#5016](https://github.com/chef/chef/pull/5016) ([erichelgeson](https://github.com/erichelgeson)) - Invalidate the file system cache on deletion [\#5154](https://github.com/chef/chef/pull/5154) ([thommay](https://github.com/thommay)) - Root ACLs are a top level json file not a sub-directory [\#5155](https://github.com/chef/chef/pull/5155) ([thommay](https://github.com/thommay)) - Install nokogiri and pin mixlib-cli [\#5118](https://github.com/chef/chef/pull/5118) ([ksubrama](https://github.com/ksubrama)) - Ensure that the valid option is given back to the option parser [\#5114](https://github.com/chef/chef/pull/5114) ([dldinternet](https://github.com/dldinternet)) - Fixed regex for zypper version 1.13.\*. [\#5109](https://github.com/chef/chef/pull/5109) ([yeoldegrove](https://github.com/yeoldegrove)) - add back method\_missing support to set\_unless [\#5103](https://github.com/chef/chef/pull/5103) ([lamont-granquist](https://github.com/lamont-granquist)) - Fix \#5094 node.default\_unless issue in 12.12.13 [\#5097](https://github.com/chef/chef/pull/5097) ([lamont-granquist](https://github.com/lamont-granquist)) - Fix \#5078 using cwd parameter instead of Dir.pwd [\#5079](https://github.com/chef/chef/pull/5079) ([Tensibai](https://github.com/Tensibai)) ## [v12.12.15](https://github.com/chef/chef/tree/v12.12.15) (2016-07-08) [Full Changelog](https://github.com/chef/chef/compare/v12.12.13...v12.12.15) **Fixed Bugs:** - Fix for #5094 12.12.13 node.default_unless issue [\#5097](https://github.com/chef/chef/pull/5097) ([lamont-granquist](https://github.com/lamont-granquist)) ## [v12.12.13](https://github.com/chef/chef/tree/v12.12.13) (2016-07-01) [Full Changelog](https://github.com/chef/chef/compare/v12.11.18...v12.12.13) **Implemented Enhancements:** - Tweak 3694 warnings [\#5075](https://github.com/chef/chef/pull/5075) ([lamont-granquist](https://github.com/lamont-granquist)) - Adding node object to Data collector run\_converge message [\#5065](https://github.com/chef/chef/pull/5065) ([adamleff](https://github.com/adamleff)) - Attribute API improvements [\#5029](https://github.com/chef/chef/pull/5029) ([lamont-granquist](https://github.com/lamont-granquist)) - Remove deprecated Thread.exclusive around require call. [\#5068](https://github.com/chef/chef/pull/5068) ([maxlazio](https://github.com/maxlazio)) - Ensure that chef-solo uses the expected repo dir [\#5059](https://github.com/chef/chef/pull/5059) ([thommay](https://github.com/thommay)) - Expand data\_collector resource list to include all resources [\#5058](https://github.com/chef/chef/pull/5058) ([adamleff](https://github.com/adamleff)) - Turn off fips with an empty environment var [\#5048](https://github.com/chef/chef/pull/5048) ([mwrock](https://github.com/mwrock)) - Deprecate knife-supermarket gem [\#4896](https://github.com/chef/chef/pull/4896) ([thommay](https://github.com/thommay)) - Update Nokogiri [\#5042](https://github.com/chef/chef/pull/5042) ([mwrock](https://github.com/mwrock)) - Remote resource should respect sensitive flag [\#5025](https://github.com/chef/chef/pull/5025) ([PrajaktaPurohit](https://github.com/PrajaktaPurohit)) - Convert the 3694 warning to a deprecation so it will be subject to the usual deprecation formatting \(collected at the bottom, can be made an error, etc\). [\#5022](https://github.com/chef/chef/pull/5022) ([coderanger](https://github.com/coderanger)) - Deprecate `knife cookbook create` in favor of `chef generate cookbook`. [\#5021](https://github.com/chef/chef/pull/5021) ([tylercloke](https://github.com/tylercloke)) **Fixed Bugs:** - Fixes windows_package uninstall scenarios by calling uninstall string directly [\#5050](https://github.com/chef/chef/pull/5050) ([mwrock](https://github.com/mwrock)) - Fix gem_package idempotency [\#5046](https://github.com/chef/chef/pull/5046) ([thommay](https://github.com/thommay)) - Undefined local variable lookup in multiplexed_dir.rb [\#5027](https://github.com/chef/chef/issues/5027) ([robdimarco](https://github.com/robdimarco)) - Correctly write out data collector metadata file [\#5019](https://github.com/chef/chef/pull/5019) ([adamleff](https://github.com/adamleff)) - Eliminate missing constant errors for LWRP class [\#5000](https://github.com/chef/chef/pull/5000) ([PrajaktaPurohit](https://github.com/PrajaktaPurohit)) - Updated_resource_count to data collector should only include updated resources [\#5006](https://github.com/chef/chef/pull/5006) ([adamleff](https://github.com/adamleff)) - Don't mask directory deletion errors [\#4991](https://github.com/chef/chef/pull/4991) ([jaymzh](https://github.com/jaymzh)) ## [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) **Fixed Bugs:** - Removing non-existent members from group should not fail [\#4812](https://github.com/chef/chef/pull/4812) ([chefsalim](https://github.com/chefsalim)) - The easy\_install provider and resource are deprecated and will be removed in Chef 13 [\#4860](https://github.com/chef/chef/pull/4860) ([coderanger](https://github.com/coderanger)) **Tech cleanup:** - Refactor ChefFS files to be files [\#4837](https://github.com/chef/chef/pull/4837) ([thommay](https://github.com/thommay)) - Rename and add backcompat requires for ChefFS dirs [\#4830](https://github.com/chef/chef/pull/4830) ([thommay](https://github.com/thommay)) - Refactor ChefFS directories to be directories [\#4826](https://github.com/chef/chef/pull/4826) ([thommay](https://github.com/thommay)) - Move all ChefFS exceptions into a single file [\#4822](https://github.com/chef/chef/pull/4822) ([thommay](https://github.com/thommay)) **Enhancements:** - Add layout option support for device creation to mdadm resource provider [\#4855](https://github.com/chef/chef/pull/4855) ([kbruner](https://github.com/kbruner)) - add notifying\_block and subcontext\_block to chef [\#4818](https://github.com/chef/chef/pull/4818) ([lamont-granquist](https://github.com/lamont-granquist)) - modernize shell\_out method syntax [\#4865](https://github.com/chef/chef/pull/4865) ([lamont-granquist](https://github.com/lamont-granquist)) - Update rubygems provider to support local install of gems if so specified [\#4847](https://github.com/chef/chef/pull/4847) ([PrajaktaPurohit](https://github.com/PrajaktaPurohit)) - fix details in with\_run\_context [\#4839](https://github.com/chef/chef/pull/4839) ([lamont-granquist](https://github.com/lamont-granquist)) - Lock dependencies of chef through a `Gemfile.lock` [\#4820](https://github.com/chef/chef/pull/4820) ([jkeiser](https://github.com/jkeiser)) - add better resource manipulation API [\#4834](https://github.com/chef/chef/pull/4834) ([lamont-granquist](https://github.com/lamont-granquist)) - add nillable apt\_repository and nillable properties [\#4832](https://github.com/chef/chef/pull/4832) ([lamont-granquist](https://github.com/lamont-granquist)) ## [v12.9](https://github.com/chef/chef/tree/v12.9.38) (2016-04-09) [Full Changelog](https://github.com/chef/chef/compare/v12.8.2...v12.9.38) **Implemented enhancements:** - Sftp remote file support [\#4750](https://github.com/chef/chef/pull/4750) ([jkerry](https://github.com/jkerry)) - Setting init\_command should be accepted instead of specific command overrides [\#4709](https://github.com/chef/chef/pull/4709) ([coderanger](https://github.com/coderanger)) - Add a NoOp provider [\#4798](https://github.com/chef/chef/pull/4798) ([thommay](https://github.com/thommay)) - Add ability to notify from inside LWRP to wrapping resource\_collections [\#4017](https://github.com/chef/chef/issues/4017) - Notifications from LWRPS/sub-resources can trigger resources in outer run\_context scopes [\#4741](https://github.com/chef/chef/pull/4741) ([lamont-granquist](https://github.com/lamont-granquist)) - Improve the docs generated by knife cookbook create [\#4757](https://github.com/chef/chef/pull/4757) ([tas50](https://github.com/tas50)) - Need Config/CLI options to move interval+splay sleep to end of client loop [\#3305](https://github.com/chef/chef/issues/3305) - Add optional integer argument for --daemonize option [\#4759](https://github.com/chef/chef/pull/4759) ([jrunning](https://github.com/jrunning)) - Add shorthand :syslog and :win\_evt for log\_location config [\#4751](https://github.com/chef/chef/pull/4751) ([jrunning](https://github.com/jrunning)) **Fixed bugs:** - chef\_gem and gem metadata don't play well [\#4710](https://github.com/chef/chef/issues/4710) - Fix cookbook metadata 'gem' command to make it useful [\#4809](https://github.com/chef/chef/pull/4809) ([lamont-granquist](https://github.com/lamont-granquist)) - Convert timeout config to integer [\#4787](https://github.com/chef/chef/pull/4787) ([chefsalim](https://github.com/chefsalim)) - The mount resource is not idempotent on windows [\#3861](https://github.com/chef/chef/issues/3861) - fix for \#4715 - unset TMPDIR in homebrew package provider [\#4716](https://github.com/chef/chef/pull/4716) ([gips0n](https://github.com/gips0n)) - tons of "Deprecation class overwrites LWRP resource" WARNING SPAM with chefspec [\#4668](https://github.com/chef/chef/issues/4668) **Merged pull requests:** - Add apt\_repository resource [\#4782](https://github.com/chef/chef/pull/4782) ([thommay](https://github.com/thommay)) - Point to the right license file for chef. [\#4811](https://github.com/chef/chef/pull/4811) ([sersut](https://github.com/sersut)) - add omnibus license metadata [\#4805](https://github.com/chef/chef/pull/4805) ([patrick-wright](https://github.com/patrick-wright)) - Add default timeout [\#4804](https://github.com/chef/chef/pull/4804) ([chefsalim](https://github.com/chefsalim)) - Spec break on Windows due to temp dir and short path names [\#4776](https://github.com/chef/chef/pull/4776) ([adamedx](https://github.com/adamedx)) - Require chef/version since it's used here [\#4762](https://github.com/chef/chef/pull/4762) ([jkeiser](https://github.com/jkeiser)) - remove pry from rbx build [\#4761](https://github.com/chef/chef/pull/4761) ([lamont-granquist](https://github.com/lamont-granquist)) - ruby 2.0.0 is EOL [\#4752](https://github.com/chef/chef/pull/4752) ([lamont-granquist](https://github.com/lamont-granquist)) - supresses parser gem errors [\#4755](https://github.com/chef/chef/pull/4755) ([lamont-granquist](https://github.com/lamont-granquist)) - Set inherit=false on the fallback provider constant lookup. [\#4753](https://github.com/chef/chef/pull/4753) ([coderanger](https://github.com/coderanger)) **Closed issues:** - Uploading an encrypted data bag to Chef server fails [\#4815](https://github.com/chef/chef/issues/4815) - powershell\_script does not have PSCredential capability [\#4589](https://github.com/chef/chef/issues/4589) - Documentation don't include how to setup mail server during deployment of Chef server [\#4807](https://github.com/chef/chef/issues/4807) - Resource 'mount' and chef 12.5.1 [\#4056](https://github.com/chef/chef/issues/4056) - Incorrect $TMPDIR environment variable on OS X [\#4715](https://github.com/chef/chef/issues/4715) - group provider on suse Linux adds user multiple times [\#4689](https://github.com/chef/chef/issues/4689) - Unexpected error when using "knife cookbook show ...." [\#4659](https://github.com/chef/chef/issues/4659) ## [12.8.1](https://github.com/chef/chef/tree/12.8.1) (2016-03-07) [Full Changelog](https://github.com/chef/chef/compare/12.7.2...12.8.1) **Implemented enhancements:** - Clarify the probable cause of tempfile creation failure during cookbook sync [\#2171](https://github.com/chef/chef/issues/2171) - Remove static libraries from Chef package [\#4654](https://github.com/chef/chef/pull/4654) ([chefsalim](https://github.com/chefsalim)) - Have client.rb verify that FIPS mode can be enforced [\#4630](https://github.com/chef/chef/pull/4630) ([ksubrama](https://github.com/ksubrama)) - List all of the unignored files when loading a cookbook [\#4629](https://github.com/chef/chef/pull/4629) ([danielsdeleo](https://github.com/danielsdeleo)) - adding pry and pry-byebug to dev dependencies [\#4601](https://github.com/chef/chef/pull/4601) ([mwrock](https://github.com/mwrock)) - Split group members on commas [\#4583](https://github.com/chef/chef/pull/4583) ([thommay](https://github.com/thommay)) - Make tempfiles easier to read \(prepend chef to the name\) [\#4582](https://github.com/chef/chef/pull/4582) ([thommay](https://github.com/thommay)) - Extend cookbook shadowing deprecation warnings more broadly [\#4574](https://github.com/chef/chef/pull/4574) ([lamont-granquist](https://github.com/lamont-granquist)) - tell knife's edit\_data what the object is [\#4548](https://github.com/chef/chef/pull/4548) ([thommay](https://github.com/thommay)) - Implement knife bootstrap client.d RFC [\#4529](https://github.com/chef/chef/pull/4529) ([jaym](https://github.com/jaym)) - Update to Log Level when showing unencrypted databag [\#4524](https://github.com/chef/chef/pull/4524) ([PatrickWalker](https://github.com/PatrickWalker)) - RFC-060 gem metadata MVP [\#4478](https://github.com/chef/chef/pull/4478) ([lamont-granquist](https://github.com/lamont-granquist)) - chef-client: add --\[no\]skip-cookbook-sync option [\#4316](https://github.com/chef/chef/pull/4316) ([josb](https://github.com/josb)) - Extend service resource to support masking [\#4307](https://github.com/chef/chef/pull/4307) ([davide125](https://github.com/davide125)) - launchd for osx [\#4111](https://github.com/chef/chef/pull/4111) ([mikedodge04](https://github.com/mikedodge04)) **Fixed bugs:** - Chef::DataBagItem.to\_hash is modifying Chef::DataBagItem.raw\_data [\#4614](https://github.com/chef/chef/issues/4614) - Chef 12 seeing a ton of these in debug mode [\#2396](https://github.com/chef/chef/issues/2396) - Data bag item hash can have name key [\#4664](https://github.com/chef/chef/pull/4664) ([chefsalim](https://github.com/chefsalim)) - Clearer exception for loading non-existent data bag items in solo mode. [\#4655](https://github.com/chef/chef/pull/4655) ([coderanger](https://github.com/coderanger)) - Always rehash from gem source and not existing hash file [\#4651](https://github.com/chef/chef/pull/4651) ([tyler-ball](https://github.com/tyler-ball)) - Handle negative content length headers too. [\#4646](https://github.com/chef/chef/pull/4646) ([coderanger](https://github.com/coderanger)) - if no module name is found for a valid dsc resource default to PSDesiredStateConfiguration [\#4638](https://github.com/chef/chef/pull/4638) ([mwrock](https://github.com/mwrock)) - removing disabling of readline in chef-shell [\#4635](https://github.com/chef/chef/pull/4635) ([mwrock](https://github.com/mwrock)) - Fix a bug that was causing DataBagItem.to\_hash to mutate the data bag item [\#4631](https://github.com/chef/chef/pull/4631) ([itmustbejj](https://github.com/itmustbejj)) - ensure paths maintain utf-8ness in non ascii encodings [\#4626](https://github.com/chef/chef/pull/4626) ([mwrock](https://github.com/mwrock)) - Fix the Chocolatey-missing error again [\#4621](https://github.com/chef/chef/pull/4621) ([randomcamel](https://github.com/randomcamel)) - fixes exe package downloads [\#4612](https://github.com/chef/chef/pull/4612) ([mwrock](https://github.com/mwrock)) - fallback to netmsg.dll error table if error message is not found in system errors [\#4600](https://github.com/chef/chef/pull/4600) ([mwrock](https://github.com/mwrock)) - zypper multipackage performance fix [\#4591](https://github.com/chef/chef/pull/4591) ([lamont-granquist](https://github.com/lamont-granquist)) - bugfix \#2865 check for validation\_key [\#4581](https://github.com/chef/chef/pull/4581) ([thommay](https://github.com/thommay)) - remove bogus recalculation of cookbook upload failures [\#4580](https://github.com/chef/chef/pull/4580) ([thommay](https://github.com/thommay)) - Make sure we have a valid object before calling close! [\#4579](https://github.com/chef/chef/pull/4579) ([thommay](https://github.com/thommay)) - Fix policyfile\_zero provisioner in 12.7 [\#4571](https://github.com/chef/chef/pull/4571) ([andy-dufour](https://github.com/andy-dufour)) - do not include source parameter when removing a chocolatey package and ensure source is used on all functional tests [\#4570](https://github.com/chef/chef/pull/4570) ([mwrock](https://github.com/mwrock)) - Fix databag globbing issues for chef-solo on windows [\#4569](https://github.com/chef/chef/pull/4569) ([jaym](https://github.com/jaym)) - remove Chef::Mixin::Command use [\#4566](https://github.com/chef/chef/pull/4566) ([lamont-granquist](https://github.com/lamont-granquist)) ## 12.7.2 * [pr#4559](https://github.com/chef/chef/pull/4559) Remove learnchef acceptance tests until we make them more reliable * [pr#4545](https://github.com/chef/chef/pull/4545) Removing rm -rf in chef-solo recipe_url ## 12.7.1 * [**Daniel Steen**](https://github.com/dansteen) * [pr#3183](https://github.com/chef/chef/pull/3183) Provide more helpful error message when accidentally using --secret instead of --secret-file * [pr#4532](https://github.com/chef/chef/pull/4532) Bump Bundler + Rubygems * [pr#4550](https://github.com/chef/chef/pull/4550) Use a streaming request to download cookbook ## 12.7.0 * [**Nate Walck**](https://github.com/natewalck) * [pr#4078](https://github.com/chef/chef/pull/4078) Add `osx_profile` resource for OS X * [**Timothy Cyrus**](https://github.com/tcyrus) * [pr#4420](https://github.com/chef/chef/pull/4420) Update code climate badge and code climate blocks in README.md * [**Jordan Running**](https://github.com/jrunning) * [pr#4399](https://github.com/chef/chef/pull/4399) Correctly save policy_name and policy_group with `knife node edit` * [**Brian Goad**](https://github.com/bbbco) * [pr#4315](https://github.com/chef/chef/pull/4315) Add extra tests around whether to skip with multiple guards * [pr#4516](https://github.com/chef/chef/pull/4516) Return propper error messages when using windows based `mount`, `user` and `group` resources * [pr#4500](https://github.com/chef/chef/pull/4500) Explicitly declare directory permissions of chef install on windows to restrict rights on Windows client versions * [pr#4498](https://github.com/chef/chef/pull/4498) Correct major and minor OS versions for Windows 10 and add versions for Windows 2016 Server * [pr#4375](https://github.com/chef/chef/pull/4375) No longer try to auto discover package version of `exe` based windows packages * [pr#4369](https://github.com/chef/chef/pull/4396) Import omnibus-chef chef project definition and history * [pr#4399](https://github.com/chef/chef/pull/4399) Correctly save `policy_name` and `policy_group` with `knife node edit` * [pr#4278](https://github.com/chef/chef/pull/4278) make file resource use properties * [pr#4479](https://github.com/chef/chef/pull/4479) Remove incorrect cookbook artifact normalization * [pr#4470](https://github.com/chef/chef/pull/4470) Fix sh spacing issues * [pr#4434](https://github.com/chef/chef/pull/4434) adds EOFError message to handlers * [pr#4422](https://github.com/chef/chef/pull/4422) Add an apt_update resource * [pr#4287](https://github.com/chef/chef/pull/4287) Default Chef with FIPS OpenSSL to use sign v1.3 * [pr#4461](https://github.com/chef/chef/pull/4461) debian-6 is EOL next month * [pr#4460](https://github.com/chef/chef/pull/4460) Set range of system user/group id to max of 200 * [pr#4231](https://github.com/chef/chef/pull/4231) zypper multipackage patch * [pr#4459](https://github.com/chef/chef/pull/4459) use require_paths and not path so bundler grabs all paths from a git reference * [pr#4450](https://github.com/chef/chef/pull/4450) don't warn about ambiguous property usage * [pr#4445](https://github.com/chef/chef/pull/4445) Add CBGB to the repository * [pr#4423](https://github.com/chef/chef/pull/4423) Add deprecation warnings to Chef::REST and all json_creates * [pr#4439](https://github.com/chef/chef/pull/4439) Sometimes chocolately doesn't appear on the path * [pr#4432](https://github.com/chef/chef/pull/4432) add get_rest etc calls to ServerAPI * [pr#4435](https://github.com/chef/chef/pull/4435) add nokogiri to omnibus-chef * [pr#4419](https://github.com/chef/chef/pull/4419) explicitly adding .bat to service executable called by service in case users remove .bat from PATHEXT * [pr#4413](https://github.com/chef/chef/pull/4413) configure chef client windows service to the correct chef directory * [pr#4377](https://github.com/chef/chef/pull/4377) fixing candidate filtering and adding functional tests for chocolatey_package * [pr#4406](https://github.com/chef/chef/pull/4406) Updating to the latest release of net-ssh to consume https://github.com/net-ssh/net-ssh/pull/280 * [pr#4405](https://github.com/chef/chef/pull/4405) ServerAPI will return a raw hash, so do that * [pr#4400](https://github.com/chef/chef/pull/4400) inflate an environment after loading it * [pr#4396](https://github.com/chef/chef/pull/4396) Remove duplicate initialization of @password in user_v1 * [pr#4344](https://github.com/chef/chef/pull/4344) Warn (v. info) when reloading resources * [pr#4369](https://github.com/chef/chef/pull/4369) Migrate omnibus-chef project/software definitions for chef in here * [pr#4106](https://github.com/chef/chef/pull/4106) add chocolatey_package to core chef * [pr#4321](https://github.com/chef/chef/pull/4321) fix run_as_user of windows_service * [pr#4333](https://github.com/chef/chef/pull/4333) no longer wait on node search to refresh vault but pass created ApiCient instead * [pr#4325](https://github.com/chef/chef/pull/4325) Pin win32-eventlog to 0.6.3 to avoid clashing CreateEvent definition * [pr#4312](https://github.com/chef/chef/pull/4312) Updates the template to use omnitruck-direct.chef.io * [pr#4277](https://github.com/chef/chef/pull/4277) non msi packages must explicitly provide a source attribute on install * [pr#4309](https://github.com/chef/chef/pull/4309) tags always an array; fix set_unless * [pr#4278](https://github.com/chef/chef/pull/4278) make file resource use properties * [pr#4288](https://github.com/chef/chef/pull/4288) Fix no_proxy setting in chef-config * [pr#4273](https://github.com/chef/chef/pull/4273) Use signing protocol 1.1 by default * [pr#4520](https://github.com/chef/chef/pull/4520) Fix a few `dsc_resource` bugs ## 12.6.0 * [**Dave Eddy**](https://github.com/bahamas10) [pr#3187](https://github.com/chef/chef/pull/3187) overhaul solaris SMF service provider * [**Mikhail Zholobov**](https://github.com/legal90) - [pr#3192](https://github.com/chef/chef/pull/3192) provider/user/dscl: Set default gid to 20 - [pr#3193](https://github.com/chef/chef/pull/3193) provider/user/dscl: Set "comment" default value * [**Jordan Evans**](https://github.com/jordane) - [pr#3263](https://github.com/chef/chef/pull/3263) `value_for_platform` should use `Chef::VersionConstraint::Platform` - [pr#3633](https://github.com/chef/chef/pull/3633) add the word group to `converge_by` call for group provider * [**Scott McGillivray**](https://github.com/thechile) [pr#3450](https://github.com/chef/chef/pull/3450) Fix 'knife cookbook show' to work on root files * [**Aubrey Holland**](https://github.com/aub) [pr#3986](https://github.com/chef/chef/pull/3986) fix errors when files go away during chown * [**James Michael DuPont**](https://github.com/h4ck3rm1k3) [pr#3973](https://github.com/chef/chef/pull/3973) better error reporting * [**Michael Pereira**](https://github.com/MichaelPereira) [pr#3968](https://github.com/chef/chef/pull/3968) Fix cookbook installation from supermarket on windows * [**Yukihiko SAWANOBORI**](https://github.com/sawanoboly) - [pr#3941](https://github.com/chef/chef/pull/3941) allow reboot by reboot resource with chef-apply - [pr#3900](https://github.com/chef/chef/pull/3900) Add new option json attributes file to bootstraping * [**permyakovsv**](https://github.com/permyakovsv) [pr#3901](https://github.com/chef/chef/pull/3901) Add tmux-split parameter to knife ssh * [**Evan Gilman**](https://github.com/evan2645) [pr#3864](https://github.com/chef/chef/pull/3864) Knife `bootstrap_environment` should use Explicit config before Implicit * [**Ranjib Dey**](https://github.com/ranjib) [pr#3834](https://github.com/chef/chef/pull/3834) Dont spit out stdout and stderr for execute resource failure, if its declared sensitive * [**Jeff Blaine**](https://github.com/jblaine) - [pr#3776](https://github.com/chef/chef/pull/3776) Changes --hide-healthy to --hide-by-mins MINS - [pr#3848](https://github.com/chef/chef/pull/3848) Migrate to --ssh-identity-file instead of --identity-file * [**dbresson**](https://github.com/dbresson) [pr#3650](https://github.com/chef/chef/pull/3650) Define == for node objects * [**Patrick Connolly**](https://github.com/patcon) [pr#3529](https://github.com/chef/chef/pull/3529) Allow user@hostname format for knife-bootstrap * [**Justin Seubert**](https://github.com/dude051) [pr#4160](https://github.com/chef/chef/pull/4160) Correcting regex for upstart_state * [**Sarah Michaelson**](https://github.com/skmichaelson) [pr#3810](https://github.com/chef/chef/pull/3810) GH-1909 Add validation for chef_server_url * [**Maxime Brugidou**](https://github.com/brugidou) [pr#4052](https://github.com/chef/chef/pull/4052) Add make_child_entry in ChefFS CookbookSubdir * [**Nathan Williams**](https://github.com/nathwill) [pr#3836](https://github.com/chef/chef/pull/3836) simplify service helpers * [**Paul Welch**](https://github.com/pwelch) [pr#4066](https://github.com/chef/chef/pull/4066) Fix chef-apply usage banner * [**Mat Schaffer**](https://github.com/matschaffer) [pr#4153](https://github.com/chef/chef/pull/4153) Require ShellOut before Knife::SSH definition * [**Donald Guy**](https://github.com/donaldguy) [pr#4158](https://github.com/chef/chef/pull/4158) Allow named_run_list to be loaded from config * [**Jos Backus**](https://github.com/josb) [pr#4064](https://github.com/chef/chef/pull/4064) Ensure that tags are properly initialized * [**John Bellone**](https://github.com/johnbellone) [pr#4101](https://github.com/chef/chef/pull/4101) Adds alias method upgrade_package for solaris package * [**Nolan Davidson**](https://github.com/nsdavidson) [pr#4014](https://github.com/chef/chef/pull/4014) Adding ksh resource * [pr#4193](https://github.com/chef/chef/pull/4196) support for inno, nsis, wise and installshield installer types in windows_package resource * [pr#4196](https://github.com/chef/chef/pull/4196) multipackage dpkg_package and bonus fixes * [pr#4185](https://github.com/chef/chef/pull/4185) dpkg provider cleanup * [pr#4165](https://github.com/chef/chef/pull/4165) Multipackage internal API improvements * [pr#4081](https://github.com/chef/chef/pull/4081) RFC-037: add `chef_version` and `ohai_version` metadata * [pr#3530](https://github.com/chef/chef/pull/3530) Allow using --sudo option with user's home folder in knife bootstrap * [pr#3858](https://github.com/chef/chef/pull/3858) Remove duplicate 'Accept' header in spec * [pr#3911](https://github.com/chef/chef/pull/3911) Avoid subclassing Struct.new * [pr#3990](https://github.com/chef/chef/pull/3990) Use SHA256 instead of MD5 for `registry_key` when data is not displayable * [pr#4034](https://github.com/chef/chef/pull/4034) add optional ruby-profiling with --profile-ruby * [pr#3119](https://github.com/chef/chef/pull/3119) allow removing user, even if their GID isn't resolvable * [pr#4068](https://github.com/chef/chef/pull/4068) update messaging from LWRP to Custom Resource in logging and spec * [pr#4021](https://github.com/chef/chef/pull/4021) add missing requires for Chef::DSL::Recipe to LWRPBase * [pr#3597](https://github.com/chef/chef/pull/3597) print STDOUT from the powershell_script * [pr#4091](https://github.com/chef/chef/pull/4091) Allow downloading of root_files in a chef repository * [pr#4112](https://github.com/chef/chef/pull/4112) Update knife bootstrap command to honor --no-color flag in chef-client run that is part of the bootstrap process. * [pr#4090](https://github.com/chef/chef/pull/4090) Improve detection of ChefFS-based commands in `knife rehash` * [pr#3991](https://github.com/chef/chef/pull/3991) Modify remote_file cache_control_data to use sha256 for its name * [pr#4079](https://github.com/chef/chef/pull/4079) add logger to windows service shellout * [pr#3966](https://github.com/chef/chef/pull/3966) Report expanded run list json tree to reporting * [pr#4080](https://github.com/chef/chef/pull/4080) Make property modules possible * [pr#4069](https://github.com/chef/chef/pull/4069) Improvements to log messages * [pr#4049](https://github.com/chef/chef/pull/4049) Add gemspec files to allow bundler to run from the gem * [pr#4029](https://github.com/chef/chef/pull/4029) Fix search result pagination * [pr#4048](https://github.com/chef/chef/pull/4048) Accept coercion as a way to accept nil values * [pr#4046](https://github.com/chef/chef/pull/4046) ignore gid in the user resource on windows * [pr#4118](https://github.com/chef/chef/pull/4118) Make Property.derive create derived properties of the same type * [pr#4133](https://github.com/chef/chef/pull/4133) Add retries to `Chef::HTTP` for transient SSL errors * [pr#4135](https://github.com/chef/chef/pull/4135) Windows service uses log file location from config if none is given on commandline * [pr#4142](https://github.com/chef/chef/pull/4142) Use the proper python interpretor for yum-dump.py on Fedora 21 * [pr#4149](https://github.com/chef/chef/pull/4149) Handle nil run list option in knife bootstrap * [pr#4040](https://github.com/chef/chef/pull/4040) Implement live streaming for execute resources * [pr#4167](https://github.com/chef/chef/pull/4167) Add `reboot_action` to `dsc_resource` * [pr#4167](https://github.com/chef/chef/pull/4167) Allow `dsc_resource` to run with the LCM enabled * [pr#4188](https://github.com/chef/chef/pull/4188) Update `dsc_resource` to use verbose stream output * [pr#4200](https://github.com/chef/chef/pull/4200) Prevent inspect of PsCredential from printing out plain text password * [pr#4237](https://github.com/chef/chef/pull/4237) Enabling 'knife ssl check/fetch' commands to respect proxy environment variables and moving proxy environment variables export to Chef::Config ## 12.5.1 * [**Ranjib Dey**](https://github.com/ranjib): [pr#3588](https://github.com/chef/chef/pull/3588) Count skipped resources among total resources in doc formatter * [**John Kerry**](https://github.com/jkerry): [pr#3539](https://github.com/chef/chef/pull/3539) Fix issue: registry\_key resource is case sensitive in chef but not on windows * [**David Eddy**](https://github.com/bahamas10): - [pr#3443](https://github.com/chef/chef/pull/3443) remove extraneous space - [pr#3091](https://github.com/chef/chef/pull/3091) fix locking/unlocking users on SmartOS * [**margueritepd**](https://github.com/margueritepd): [pr#3693](https://github.com/chef/chef/pull/3693) Interpolate `%{path}` in verify command * [**Jeremy Fleischman**](https://github.com/jfly): [pr#3383](https://github.com/chef/chef/pull/3383) gem\_package should install to the systemwide Ruby when using ChefDK * [**Stefano Rivera**](https://github.com/stefanor): [pr#3657](https://github.com/chef/chef/pull/3657) fix upstart status\_commands * [**ABE Satoru**](https://github.com/polamjag): [pr#3764](https://github.com/chef/chef/pull/3764) uniquify chef\_repo\_path * [**Renan Vicente**](https://github.com/renanvicente): [pr#3771](https://github.com/chef/chef/pull/3771) add depth property for deploy resource * [**James Belchamber**](https://github.com/JamesBelchamber): [pr#1796](https://github.com/chef/chef/pull/1796): make mount options aware * [**Nate Walck**](https://github.com/natewalck): - [pr#3594](https://github.com/chef/chef/pull/3594): Update service provider for OSX 10.11 - [pr#3704](https://github.com/chef/chef/pull/3704): Add SIP (OS X 10.11) support * [**Phil Dibowitz**](https://github.com/jaymzh): [pr#3805](https://github.com/chef/chef/pull/3805) LWRP parameter validators should use truthiness * [**Igor Shpakov**](https://github.com/Igorshp): [pr#3743](https://github.com/chef/chef/pull/3743) speed improvement for `remote_directory` resource * [**James FitzGibbon**](https://github.com/jf647): [pr#3027](https://github.com/chef/chef/pull/3027) Add warnings to 'knife node run list remove ...' * [**Backslasher**](https://github.com/backslasher): [pr#3172](https://github.com/chef/chef/pull/3172) Migrated deploy resource to use shell\_out instead of run\_command * [**Sean Walberg**](https://github.com/swalberg): [pr#3190](https://github.com/chef/chef/pull/3190) Allow tags to be set on a node during bootstrap * [**ckaushik**](https://github.com/ckaushik) and [**Sam Dunne**](https://github.com/samdunne): [pr#3510](https://github.com/chef/chef/pull/3510) Fix broken rendering of partial templates. * [**Simon Detheridge**](https://github.com/gh2k): [pr#3806](https://github.com/chef/chef/pull/3806) Replace output\_of\_command with shell\_out! in subversion provider * [**Joel Handwell**](https://github.com/joelhandwell): [pr#3821](https://github.com/chef/chef/pull/3821) Human friendly elapsed time in log * [pr#3985](https://github.com/chef/chef/pull/3985) Simplify the regex which determines the rpm version to resolve issue #3671 * [pr#3928](https://github.com/chef/chef/pull/3928) Add named run list support when using policyfiles * [pr#3913](https://github.com/chef/chef/pull/3913) Add `policy_name`and `policy_group` fields to the node object * [pr#3875](https://github.com/chef/chef/pull/3875) Patch Win32::Registry#delete_key, #delete_value to use wide (W) APIs * [pr#3850](https://github.com/chef/chef/pull/3850) Patch Win32::Registry#write to fix encoding errors * [pr#3837](https://github.com/chef/chef/pull/3837) refactor remote_directory provider for mem+perf improvement * [pr#3799](https://github.com/chef/chef/pull/3799) fix supports hash issues in service providers * [pr#3797](https://github.com/chef/chef/pull/3797) Fix dsc_script spec failure on 64-bit Ruby * [pr#3817](https://github.com/chef/chef/pull/3817) Remove now-useless forcing of ruby Garbage Collector run * [pr#3775](https://github.com/chef/chef/pull/3775) Enable 64-bit support for Powershell and Batch scripts * [pr#3774](https://github.com/chef/chef/pull/3774) Add support for yum-deprecated in yum provider * [pr#3793](https://github.com/chef/chef/pull/3793) CHEF-5372: Support specific `run_levels` for RedHat service * [pr#2460](https://github.com/chef/chef/pull/2460) add privacy flag * [pr#1259](https://github.com/chef/chef/pull/1259) CHEF-5012: add methods for template breadcrumbs * [pr#3656](https://github.com/chef/chef/pull/3656) remove use of self.provides? * [pr#3455](https://github.com/chef/chef/pull/3455) powershell\_script: do not allow suppression of syntax errors * [pr#3519](https://github.com/chef/chef/pull/3519) The wording seemed odd. * [pr#3208](https://github.com/chef/chef/pull/3208) Missing require (require what you use). * [pr#3449](https://github.com/chef/chef/pull/3449) correcting minor typo in user\_edit knife action * [pr#3572](https://github.com/chef/chef/pull/3572) Use windows paths without case-sensitivity. * [pr#3666](https://github.com/chef/chef/pull/3666) Support SNI in `knife ssl check`. * [pr#3667](https://github.com/chef/chef/pull/3667) Change chef service to start as 'Automatic delayed start'. * [pr#3683](https://github.com/chef/chef/pull/3683) Correct Windows reboot command to delay in minutes, per the property. * [pr#3698](https://github.com/chef/chef/pull/3698) Add ability to specify dependencies in chef-service-manager. * [pr#3728](https://github.com/chef/chef/pull/3728) Rewrite NetLocalGroup things to use FFI * [pr#3754](https://github.com/chef/chef/pull/3754) Fix functional tests for group resource - fix #3728 * [pr#3498](https://github.com/chef/chef/pull/3498) Use dpkg-deb directly rather than regex * [pr#3759](https://github.com/chef/chef/pull/3759) Repair service convergence test on AIX * [pr#3329](https://github.com/chef/chef/pull/3329) Use ifconfig target property * [pr#3652](https://github.com/chef/chef/pull/3652) Fix explanation for configuring audit mode in client.rb * [pr#3687](https://github.com/chef/chef/pull/3687) Add formatter and force-logger/formatter options to chef-apply * [pr#3768](https://github.com/chef/chef/pull/3768) Make reboot\_pending? look for CBS RebootPending * [pr#3815](https://github.com/chef/chef/pull/3815) Fix `powershell_script` validation to use correct architecture * [pr#3772](https://github.com/chef/chef/pull/3772) Add `ps_credential` dsl method to `dsc_script` * [pr#3462](https://github.com/chef/chef/pull/3462) Fix issue where `ps_credential` does not work over winrm ## 12.4.1 * [**Noah Kantrowitz**](https://github.com/coderanger): [pr#3605](https://github.com/chef/chef/pull/3605) Rework `Resource#action` to match 12.3 API * [pr#3586](https://github.com/chef/chef/issues/3586) Fix bug preventing light weight resources from being used with heavy weight providers * [Issue #3593](https://github.com/chef/chef/issues/3593) Fix bug where provider priority map did not take into consideration a provided block * [pr#3630](https://github.com/chef/chef/pull/3630) Restore Chef::User and Chef::ApiClient namespace to API V0 functionality and move new functionality into Chef::UserV1 and Chef::ApiClientV1 until Chef 13. * [pr#3611](https://github.com/chef/chef/pull/3611) Call `provides?` even if `provides` is not called * [pr#3589](https://github.com/chef/chef/pull/3589) Fix errant bashisms * [pr#3620](https://github.com/chef/chef/pull/3620) Fix issue where recipe names in run list mutate when version constaints are present * [pr#3623](https://github.com/chef/chef/pull/3623) Allow LWRPs to access the real class when accessed through `Chef::Resource` and `Chef::Provider` * [pr#3627](https://github.com/chef/chef/pull/3627) Separate priority map and DSL handler map so that `provides` has veto power over priority * [pr#3638](https://github.com/chef/chef/pull/3638) Deprecate passing more than 1 argument to create a resource ## 12.4.0 * [**Phil Dibowitz**](https://github.com/jaymzh): Fix multipackage and architectures * [**Igor Shpakov**](https://github.com/Igorshp): Always run exception handlers Prioritise manual ssh attribute over automatic ones for knife * [**Noah Kantrowitz**](https://github.com/coderanger): Cache service\_resource\_providers for the duration of the run. #2953 * [**Slava Kardakov**](https://github.com/ojab): Fix installation of yum packages with version constraints #3155 * [**Dave Eddy**](https://github.com/bahamas10): fix smartos\_package for new "pkgin" output, fixes #3112 #3165 * [**Yukihiko SAWANOBORI**](https://github.com/sawanoboly): Show Chef version on chef shell prompt * [**Jacob Minshall**](https://github.com/minshallj): Ensure suid bit is preserved if group or owner changes * [**Tim Smith**](https://github.com/tas50): Convert wiki links to point to docs.chef.io * [**SAWANOBORI Yukihiko**](https://github.com/sawanoboly): Add Chef::Log::Syslog class for integrating sending logs to syslog * [**Pavel Yudin**](https://github.com/Kasen): Ensure LWRP and HWRP @action variable is consistent #3156 * [**Dan Bjorge**](https://github.com/dbjorge): Fix bad Windows securable\_resource functional spec assumptions for default file owners/groups #3266 * [**Yukihiko SAWANOBORI**](https://github.com/sawanoboly): Pass name by knife cil attribute [pr#3195](https://github.com/chef/chef/pull/3195) * [**Torben Knerr**](https://github.com/tknerr): Allow knife sub-command loader to match platform specific gems. [pr#3281](https://github.com/chef/chef/pull/3281) * [**Steve Lowe**](https://github.com/SteveLowe): Fix copying ntfs dacl and sacl when they are nil. [pr#3066](https://github.com/chef/chef/pull/3066) * [pr#3339](https://github.com/chef/chef/pull/3339): Powershell command wrappers to make argument passing to knife/chef-client etc. easier. * [pr#3720](https://github.com/chef/chef/pull/3270): Extract chef's configuration to a separate gem. Code stays in the Chef git repo. * [pr#3321](https://github.com/chef/chef/pull/3321): Add an integration test of chef-client with empty ENV. * [pr#3278](https://github.com/chef/chef/pull/3278): Switch over Windows builds to universal builds. * [pr#2877](https://github.com/chef/chef/pull/2877): Convert bootstrap template to use sh. * [Issue #3316](https://github.com/chef/chef/issues/3316): Fix idempotency issues with the `windows_package` resource * [pr#3295](https://github.com/chef/chef/pull/3295): Stop mutating `new_resource.checksum` in file providers. Fixes some ChecksumMismatch exceptions like [issue#3168](https://github.com/chef/chef/issues/3168) * [pr#3320](https://github.com/chef/chef/pull/3320): Sanitize non-UTF8 characters in the node data before doing node.save(). Works around many UTF8 exception issues reported on node.save(). * Implemented X-Ops-Server-API-Version with a API version of 0, as well as error handling when the Chef server does not support the API version that the client supports. * [pr#3327](https://github.com/chef/chef/pull/3327): Fix unreliable AIX service group parsing mechanism. * [pr#3333](https://github.com/chef/chef/pull/3333): Fix SSL errors when connecting to private Supermarkets * [pr#3340](https://github.com/chef/chef/pull/3340): Allow Event dispatch subscribers to be inspected. * [Issue #3055](https://github.com/chef/chef/issues/3055): Fix regex parsing for recipe failures on Windows * [pr#3345](https://github.com/chef/chef/pull/3345): Windows Event log logger * [pr#3336](https://github.com/chef/chef/pull/3336): Remote file understands UNC paths * [pr#3269](https://github.com/chef/chef/pull/3269): Deprecate automatic recipe DSL for classes in `Chef::Resource` * [pr#3360](https://github.com/chef/chef/pull/3360): Add check_resource_semantics! lifecycle method to provider * [pr#3344](https://github.com/chef/chef/pull/3344): Rewrite Windows user resouce code to use ffi instead of win32-api * [pr#3318](https://github.com/chef/chef/pull/3318): Modify Windows package provider to allow for url source * [pr#3381](https://github.com/chef/chef/pull/3381): warn on cookbook self-deps * [pr#2312](https://github.com/chef/chef/pull/2312): fix `node[:recipes]` duplication, add `node[:cookbooks]` and `node[:expanded_run_list]` * [pr#3325](https://github.com/chef/chef/pull/3325): enforce passing a node name with validatorless bootstrapping * [pr#3398](https://github.com/chef/chef/pull/3398): Allow spaces in files for the `remote_file` resource * [Issue #3010](https://github.com/chef/chef/issues/3010) Fixed `knife user` for use with current and future versions of Chef Server 12, with continued backwards compatible support for use with Open Source Server 11. * [pr#3438](https://github.com/chef/chef/pull/3438) Server API V1 support. Vast improvements to and testing expansion for Chef::User, Chef::ApiClient, and related knife commands. Deprecated Open Source Server 11 user support to the Chef::OscUser and knife osc_user namespace, but with backwards compatible support via knife user. * [Issue #2247](https://github.com/chef/chef/issues/2247): `powershell_script` returns 0 for scripts with syntax errors * [pr#3080](https://github.com/chef/chef/pull/3080): Issue 2247: `powershell_script` exit status should be nonzero for syntax errors * [pr#3441](https://github.com/chef/chef/pull/3441): Add `powershell_out` mixin to core chef * [pr#3448](https://github.com/chef/chef/pull/3448): Fix `dsc_resource` to work with wmf5 april preview * [pr#3392](https://github.com/chef/chef/pull/3392): Comment up `Chef::Client` and privatize/deprecate unused things * [pr#3419](https://github.com/chef/chef/pull/3419): Fix cli issue with `chef_repo_path` when ENV variable is unset * [pr#3358](https://github.com/chef/chef/pull/3358): Separate audit and converge failures * [pr#3431](https://github.com/chef/chef/pull/3431): Fix backups on windows for the file resource * [pr#3397](https://github.com/chef/chef/pull/3397): Validate owner exists in directory resources * [pr#3418](https://github.com/chef/chef/pull/3418): Add `shell_out` mixin to Chef::Resource class for use in `not_if`/`only_if` conditionals, etc. * [pr#3406](https://github.com/chef/chef/pull/3406): Add wide-char 'Environment' to `broadcast_env_change` mixin for setting windows environment variables * [pr#3442](https://github.com/chef/chef/pull/3442): Add `resource_name` to top-level Resource class to make defining resources easier. * [pr#3447](https://github.com/chef/chef/pull/3447): Add `allowed_actions` and `default_action` to top-level Resource class. * [pr#3475](https://github.com/chef/chef/pull/3475): Fix `shell_out` timeouts in all package providers to respect timeout property on the resource. * [pr#3477](https://github.com/chef/chef/pull/3477): Update `zypper_package` to look like the rest of our package classes. * [pr#3483](https://github.com/chef/chef/pull/3483): Allow `include_recipe` from LWRP providers. * [pr#3495](https://github.com/chef/chef/pull/3495): Make resource name automatically determined from class name, and provide DSL for it. * [pr#3497](https://github.com/chef/chef/pull/3497): Issue 3485: Corruption of node's run\_context when non-default guard\_interpreter is evaluated * [pr#3299](https://github.com/chef/chef/pull/3299): Remove experimental warning on audit mode ## 12.3.0 * [pr#3160](https://github.com/chef/chef/pull/3160): Use Chef Zero in socketless mode for local mode, add `--no-listen` flag to disable port binding * [**Nolan Davidson**](https://github.com/nsdavidson): Removed after_created and added test to recipe_spec * [**Tim Sogard**](https://github.com/drags): Reset $HOME to user running chef-client when running via sudo * [**Torben Knerr**](https://github.com/tknerr): Allow for the chef gem installation to succeed without elevated privileges #3126 * [**Mike Dodge**](https://github.com/mikedodge04) MacOSX services: Load LaunchAgents as console user, adding plist and session_type options. * [**Eric Herot**](https://github.com/eherot) Ensure knife ssh doesn't use a non-existant field for hostname #3131 * [**Tom Hughes**](https://github.com/tomhughes) Ensure searches progress in the face of incomplete responses #3135 * [pr#3162](https://github.com/chef/chef/pull/3162): Add `--minimal-ohai` flag to client/solo/apply; restricts ohai to only the bare minimum of plugins. * Ensure link's path attribute works with delayed #3130 * gem_package, chef_gem should not shell out to using https://rubygems.org #2867 * Add dynamic resource resolution similar to dynamic provider resolution * Add Chef class fascade to internal structures * Fix nil pointer for windows event logger #3200 * Use partial search for knife status * Ensure chef/knife properly honours proxy config ## 12.2.1 * [Issue 3153](https://github.com/chef/chef/issues/3153): Fix bug where unset HOME would cause chef to crash ## 12.2.0 * Update policyfile API usage to match forthcoming Chef Server release * `knife ssh` now has an --exit-on-error option that allows users to fail-fast rather than moving on to the next machine. * migrate macosx, windows, openbsd, and netbsd resources to dynamic resolution * migrate cron and mdadm resources to dynamic resolution * [Issue 3096](https://github.com/chef/chef/issues/3096) Fix OpenBSD package provider installation issues * New `dsc_resource` resource to invoke Powershell DSC resources ## 12.1.2 * [Issue 3022](https://github.com/chef/chef/issues/3022): Homebrew Cask install fails FIXME (remove on 12.2.0 release): 3022 was only merged to 12-stable and #3077 or its descendant should fix this * [Issue 3059](https://github.com/chef/chef/issues/3059): Chef 12.1.1 yum_package silently fails * [Issue 3078](https://github.com/chef/chef/issues/3078): Compat break in audit-mode changes ## 12.1.1 * [**Phil Dibowitz**](https://github.com/jaymzh): [Issue 3008](https://github.com/chef/chef/issues/3008) Allow people to pass in `source` to package * [Issue 3011](https://github.com/chef/chef/issues/3011) `package` provider base should include `Chef::Mixin::Command` as there are still providers that use it. * [**Ranjib Dey**](https://github.com/ranjib): [Issue 3019](https://github.com/chef/chef/issues/3019) Fix data fetching when explicit attributes are passed ## 12.1.0 * [**Andre Elizondo**](https://github.com/andrewelizondo) Typo fixes * [**Vasiliy Tolstov**](https://github.com/vtolstov): cleanup cookbook path from stale files (when using chef-solo with a tarball url) * [**Nathan Cerny**](https://github.com/ncerny): Fix rubygems provider to use https instead of http. * [**Anshul Sharma**](https://github.com/justanshulsharma) removed securerandom patch * [**Scott Bonds**](https://github.com/bonds) add package support for OpenBSD * [**Lucy Wyman**](https://github.com/lucywyman) Added support for handling empty version strings to rubygems provider. * [**Yulian Kuncheff**](https://github.com/Daegalus) Correctly set the pre-release identifier during knife bootstrap. * [**Anshul Sharma**](https://github.com/justanshulsharma) `knife node run_list remove` now accepts run_list options in the same form as add * [**Veres Lajos**](https://github.com/vlajos) Typo fixes * [**Tim Smith**](https://github.com/tas50) Typo fixes * [Pull 2505](https://github.com/opscode/chef/pull/2505) Make Chef handle URIs in a case-insensitive manner * [**Phil Dibowitz**](https://github.com/jaymzh): Drop SSL warnings now that we have a safe default * [Pull 2684](https://github.com/chef/chef/pull/2684) Remove ole_initialize/uninitialize which cause problems with Ruby >= 2 * [**BinaryBabel**](https://github.com/binarybabel) Make knife cookbook site share prefer gnutar when packaging * [**Dave Eddy**](https://github.com/bahamas10) Support arrays for not_if and only_if * [**Scott Bonds**](https://github.com/bonds) Add service provider for OpenBSD * [**Alex Slynko**](https://github.com/alex-slynko-wonga) Change env provider to preserve ordering * [**Rob Redpath**](https://github.com/robredpath) Add --lockfile opt for chef-client and chef-solo * [**Josh Murphy**](https://github.com/jdmurphy) Check cookbooks exist in path(s) before attempting to upload them with --all * [**Vasiliy Tolstov**](https://github.com/vtolstov) add ability to fetch recipes like in chef-solo when using local-mode * [**Jan**](https://github.com/habermann24) FIX data_bag_item.rb:161: warning: circular argument reference - data_bag * [**David Radcliffe**](https://github.com/dwradcliffe) add banner for knife serve command * [**Yukihiko Sawanobori**](https://github.com/sawanoboly) use Chef::JSONCompat.parse for file_contents * [**Xabier de Zuazo**] (https://github.com/zuazo) Remove some simple Ruby 1.8 and 1.9 code * [**Xabier de Zuazo**] (https://github.com/zuazo) Remove all RSpec test filters related to Ruby 1.8 and 1.9 * [**Xabier de Zuazo**] (https://github.com/zuazo) Fix knife cookbook upload messages * [**David Crowder**] (https://github.com/david-crowder) refactor to use shell_out in rpm provider * [**Phil Dibowitz**](https://github.com/jaymzh): Multi-package support * [**Naotoshi Seo**](https://github.com/sonots): Support HTTP/FTP source on rpm_package add json_attribs option for chef-apply command allow_downgrade in rpm_package * [**AJ Christensen**](https://github.com/fujin): Isolate/fix the no-fork fault. [Issue 2709](https://github.com/chef/chef/issues/2709) * [**Cory Stephenson**](https://github.com/Aevin1387): Remove comments of a service being enabled/disabled in FreeBSD. [Fixes #1791](https://github.com/chef/chef/issues/1791) * [**Will Albenzi**](https://github.com/walbenzi): CHEF-4591: Knife commands to manipulate env_run_list on nodes * [**Jon Cowie**](https://github.com/jonlives): CHEF-2911: Fix yum_package provider to respect version requirements in package name and version attribute * [**Anshul Sharma**](https://github.com/justanshulsharma): * Node::Attribute to_s should print merged attributes [Issue 1526](https://github.com/chef/chef/issues/1562) * Access keys attribute in `knife show` list incorrect information [Issue 1974](https://github.com/chef/chef/issues/1974) * Guard interpreter loading incorrect resource [Issue 2683](https://github.com/chef/chef/issues/2683) ### Chef Contributions * ruby 1.9.3 support is dropped * Update Chef to use RSpec 3.2 * Cleaned up script and execute provider + specs * Added deprecation warnings around the use of command attribute in script resources * Audit mode feature added - see the RELEASE_NOTES for details * shell_out now sets `LANGUAGE` and `LANG` to the `Chef::Config[:internal_locale]` in addition to `LC_ALL` forcing * chef_gem supports a compile_time flag and will warn if it is not set (behavior will change in the future) * suppress CHEF-3694 warnings on the most trivial resource cloning * fixed bugs in the deep_merge_cache logic introduced in 12.0.0 around `node['foo']` vs `node[:foo]` vs. `node.foo` * add `include_recipe "::recipe"` sugar to reference a recipe in the current cookbook * Add --proxy-auth option to `knife raw` * added Chef::Org model class for Chef Organizations in Chef 12 Server * `powershell_script` should now correctly get the exit code for scripts that it runs. See [Issue 2348](https://github.com/chef/chef/issues/2348) * Useradd functional tests fail randomly * Add comments to trusted_certs_content * fixes a bug where providers would not get defined if a top-level ruby constant with the same name was already defined (ark cookbook, chrome cookbook) * Fix a bug in `reboot`, `ips_package`, `paludis_package`, `windows_package` resources where `action :nothing` was not permitted * Use Chef::ApiClient#from_hash in `knife client create` to avoid json_class requirement. [Issue 2542](https://github.com/chef/chef/issues/2542) * Add support for policyfile native API (preview). These APIs are unstable, and you may be forced to delete data uploaded to them in a future release, so only use them for demonstration purposes. * Deprecation warning for 'knife cookbook test' * dsc_script should now correctly honor timeout. See [Issue 2831](https://github.com/chef/chef/issues/2831) * Added an `imports` attribute to dsc_script. This attribute allows you to specify DSC resources that need to be imported for your script. * Fixed error where guard resources (using :guard_interpreter) were not ran in `why_run` mode [Issue 2694](https://github.com/chef/chef/issues/2694) * Add `verify` method to File resource per RFC027 * Move supermarket.getchef.com to supermarket.chef.io * Check with AccessCheck for permission to write to directory on Windows * Add declare_resource/build_resource comments, fix faulty ||= * Knife bootstrap creates a client and ships it to the node to implement validatorless bootstraps * Knife bootstrap can use the client it creates to setup chef-vault items for the node * windows service now has a configurable timeout ## 12.0.3 * [**Phil Dibowitz**](https://github.com/jaymzh): [Issue 2594](https://github.com/opscode/chef/issues/2594) Restore missing require in `digester`. ## 12.0.2 * [Issue 2578](https://github.com/opscode/chef/issues/2578) Check that `installed` is not empty for `keg_only` formula in Homebrew provider * [Issue 2609](https://github.com/opscode/chef/issues/2609) Resolve the circular dependency between ProviderResolver and Resource. * [Issue 2596](https://github.com/opscode/chef/issues/2596) Fix nodes not writing to disk * [Issue 2580](https://github.com/opscode/chef/issues/2580) Make sure the relative paths are preserved when using link resource. * [Pull 2630](https://github.com/opscode/chef/pull/2630) Improve knife's SSL error messaging * [Issue 2606](https://github.com/opscode/chef/issues/2606) chef 12 ignores default_release for apt_package * [Issue 2602](https://github.com/opscode/chef/issues/2602) Fix `subscribes` resource notifications. * [Issue 2578](https://github.com/opscode/chef/issues/2578) Check that `installed` is not empty for `keg_only` formula in Homebrew provider. * [**gh2k**](https://github.com/gh2k): [Issue 2625](https://github.com/opscode/chef/issues/2625) Fix missing `shell_out!` for `windows_package` resource * [**BackSlasher**](https://github.com/BackSlasher): [Issue 2634](https://github.com/opscode/chef/issues/2634) Fix `option ':command' is not a valid option` error in subversion provider. * [**Seth Vargo**](https://github.com/sethvargo): [Issue 2345](https://github.com/opscode/chef/issues/2345) Allow knife to install cookbooks with metadata.json. ## 12.0.1 * [Issue 2552](https://github.com/opscode/chef/issues/2552) Create constant for LWRP before calling `provides` * [Issue 2545](https://github.com/opscode/chef/issues/2545) `path` attribute of `execute` resource is restored to provide backwards compatibility with Chef 11. * [Issue 2565](https://github.com/opscode/chef/issues/2565) Fix `Chef::Knife::Core::BootstrapContext` constructor for knife-windows compat. * [Issue 2566](https://github.com/opscode/chef/issues/2566) Make sure Client doesn't raise error when interval is set on Windows. * [Issue 2560](https://github.com/opscode/chef/issues/2560) Fix `uninitialized constant Windows::Constants` in `windows_eventlog`. * [Issue 2563](https://github.com/opscode/chef/issues/2563) Make sure the Chef Client rpm packages are signed with GPG keys correctly. ## 12.0.0 * [**Jesse Hu**](https://github.com/jessehu): retry on HTTP 50X Error when calling Chef REST API * [**Nolan Davidson**](https://github.com/nsdavidson): The chef-apply command now prints usage information when called without arguments * [**Kazuki Saito**](https://github.com/sakazuki): CHEF-4933: idempotency fixes for ifconfig provider * [**Kirill Shirinkin**](https://github.com/Fodoj): The knife bootstrap command expands the path of the secret-file * [**Malte Swart**](https://github.com/mswart): [CHEF-4101] DeepMerge - support overwriting hash values with nil * [**James Belchamber**](https://github.com/JamesBelchamber): Mount provider remount action now honours options * [**Mark Gibbons**](https://github.com/MarkGibbons): Fix noauto support in Solaris Mount Provider * [**Jordan Evans**](https://github.com/jordane): support version constraints in value_for_platform * [**Yukihiko Sawanobori**](https://github.com/sawanoboly): Add environment resource attribute to scm resources * [**Grzesiek Kolodziejczyk**](https://github.com/grk): Use thread-safe OpenSSL::Digest instead of Digest * [**Grzesiek Kolodziejczyk**](https://github.com/grk): Chef::Digester converted to thread-safe Singleton mixin. * [**Vasiliy Tolstov**](https://github.com/vtolstov): Reload systemd service only if it's running, otherwise start. * [**Chris Jerdonek**](https://github.com/cjerdonek): knife diagnostic messages sent to stdout instead of stderr * [**Xabier de Zuazo**](https://github.com/zuazo): Remove the unused StreamingCookbookUploader class (CHEF-4586) * [**Jacob Vosmaer**](https://github.com/jacobvosmaer): Fix creation of non-empty FreeBSD groups (#1698) * [**Nathan Huff**](https://github.com/nhuff): Check local repository for ips package installs (#1703) * [**Sean Clemmer**](https://github.com/sczizzo): Fix "cron" resource handling of special strings (e.g. @reboot, @yearly) (#1708) * [**Phil Dibowitz**](https://github.com/jaymzh): 'group' provider on OSX properly uses 'dscl' to determine existing groups * [**Hugo Lopes Tavares**](https://github.com/hltbra): Catch StandardError in Chef::ResourceReporter#post_reporting_data (Issue 1550). * [**Daniel O'Connor**](https://github.com/CloCkWeRX): Fix regex causing DuplicateRole error (Issue 1739). * [**Xeron**](https://github.com/xeron): Ability to specify an array for data_bag_path. (CHEF-3399, CHEF-4753) * [**Jordan**](https://github.com/jordane): Use Systemd for recent Fedora and RHEL 7. * [**Xabier de Zuazo**](https://github.com/zuazo): Encrypted data bags should use different HMAC key and include the IV in the HMAC (CHEF-5356). * [**Pierre Ynard**](https://github.com/linkfanel): Don't modify variable passed to env resource when updating. * [**Chris Aumann**](https://github.com/chr4): Add "force" attribute to resource/user, pass "-f" to userdel. (Issue 1601) * [**Brian Cobb**](https://github.com/bcobb): Chef::VersionConstraint#to_s should accurately reflect constraint's behavior. * [**Kevin Graham**](https://github.com/kgraham): Do not override ShellOut:live_stream if already set. * [**Mike Heijmans**](https://github.com/parabuzzle): Change knife option --force to --delete-validators. (Issue 1652) * [**Pavel Yudin**](https://github.com/Kasen): Add Parallels Cloud Server (PCS) platform support. * [**tbe**](https://github.com/tbe): Minor fixes for the Paludis package provider: * only search for non-masked packages, * increase command timeout length for package installation. * [**sawanoboly**](https://github.com/sawanoboly): Use shared_path for deploy resource. * [**Victor Hahn**](https://github.com/victorhahncastell): Add template syntax check to files in the templates/ dir only. * [**Jordan**](https://github.com/jordane): Allow git provider to checkout existing branch names. * [**Eric Herot**](https://github.com/eherot): Add whitespace boundaries to some mount point references in mount provider. * [**Dave Eddy**](https://github.com/bahamas10): Improve the regex for /etc/rc.conf for the FreeBSD service provider * [**Stanislav Bogatyrev**](https://github.com/realloc): Fetch recipe_url before loading json_attribs in chef-solo (CHEF-5075) * [**Mal Graty**](https://github.com/mal): Workaround for a breaking change in git's shallow-clone behavior. (Issue 1563) * [**Dave Eddy**](https://github.com/bahamas10): Fix version detection in FreeBSD pkgng provider. (PR 1980) * [**Dan Rathbone**](https://github.com/rathers): Fixed gem_package resource to be able to upgrade gems when version is not set. * [**Jean Mertz**](https://github.com/JeanMertz): Made Chef Client load library folder recursively. * [**Eric Saxby**](https://github.com/sax): Made Chef Client read the non-root crontab entries as the user specified in the resource. * [**sawanoboly**](https://github.com/sawanoboly): Added `--dry-run` option to `knife cookbook site share` which displays the files that are to be uploaded to Supermarket. * [**Sander van Harmelen**](https://github.com/svanharmelen): Fixed `Chef::HTTP` to be able to follow relative redirects. * [**Cory Stephenson**](https://github.com/Aevin1387): Fixed FreeBSD port package provider to interpret FreeBSD version 10 correctly. * [**Brett Chalupa**](https://github.com/brettchalupa): Added `source_url` and `issues_url` options to metadata to be used by Supermarket. * [**Anshul Sharma**](https://github.com/justanshulsharma): Fixed Chef Client to use the `:client_name` instead of `:node_name` during initial client registration. * [**tbe**](https://github.com/tbe): Fixed Paludis package provider to be able to interpret the package category. * [**David Workman**](https://github.com/workmad3): Added a more clear error message to chef-apply when no recipe is given. * [**Joe Nuspl**](https://github.com/nvwls): Added support for `sensitive` property to the execute resource. * [**Nolan Davidson**](https://github.com/nsdavidson): Added an error message to prevent unintentional running of `exec()` in recipes. * [**wacky612**](https://github.com/wacky612): Fixed a bug in pacman package provider that was preventing the installation of `bind` package. * [**IonuÈ› ArțăriÈ™i**](https://github.com/mapleoin): Changed the default service provider to systemd on SLES versions 12 and higher. * [**IonuÈ› ArțăriÈ™i**](https://github.com/mapleoin): Changed the default group provider to gpasswd on SLES versions 12 and higher. * [**Noah Kantrowitz**](https://github.com/coderanger): Implemented [RFC017 - File Specificity Overhaul](https://github.com/opscode/chef-rfc/blob/master/rfc017-file-specificity.md). * [**James Bence**](https://github.com/jbence): Improved the reliability of Git provider by making it to be more specific when selecting tags. * [**Jean Mertz**](https://github.com/JeanMertz): Changed knife upload not to validate the ruby files under files & templates directories. * [**Alex Pop**](https://github.com/alexpop): Made `knife cookbook create` to display the directory of the cookbook that is being created. * [**Alex Pop**](https://github.com/alexpop): Fixed the information debug output for the configuration file being used when running knife. * [**Martin Smith**](https://github.com/martinb3): Changed `knife cookbook site share` to make category an optional parameter when uploading cookbooks. It is still required when the cookbook is being uploaded for the first time but on the consequent uploads existing category of the cookbook will be used. * [**Nicolas DUPEUX**](https://github.com/vaxvms): Added JSON output to `knife status` command. `--medium` and `--long` output formatting parameters are now supported in knife status. * [**Trevor North**](https://github.com/trvrnrth): Removed dead code from `knife ssh`. * [**Nicolas Szalay**](https://github.com/rottenbytes): Fixed a bug preventing mounting of cgroup type devices in the mount provider. * [**Anshul Sharma**](https://github.com/justanshulsharma): Fixed inconsistent globbing in `knife from file` command. * [**Nicolas Szalay**](https://github.com/rottenbytes): Made user prompts in knife more beautiful by adding a space after Y/N prompts. * [**Ivan Larionov**](https://github.com/xeron): Made empty run_list to produce an empty array when using node.to_hash. * [**Siddheshwar More**](https://github.com/siddheshwar-more): Fixed a bug in knife bootstrap that caused config options to override command line options. * [**Thiago Oliveira**](https://github.com/chilicheech): Fixed a bug in Mac OSX group provider and made it idempotent. * [**liseki**](https://github.com/liseki): Fixed a bug in why-run mode for freebsd service resources without configured init scripts. * [**liseki**](https://github.com/liseki): Fixed a bug in freebsd service providers to load the status correctly. ### Chef Contributions * ruby 1.9.3 support is dropped * Added RFC-023 Chef 12 Attribute Changes (https://github.com/opscode/chef-rfc/blob/master/rfc023-chef-12-attributes-changes.md) * Added os/platform_family options to provides syntax on the Chef::Resource DSL * Added provides methods to the Chef::Provider DSL * Added supported?(resource, action) class method to all Providers for late-evaluation if a provider can handle a resource * Added ProviderResolver feature to handle late resolution of providers based on what kinds of support is in the base operating system. * Partial Deprecation of Chef::Platform provider mapping. The static mapping will be removed as Chef-12 progresses and the hooks will be completely dropped in Chef-13. * Default `guard_interpreter` for `powershell_script` resource set to `:powershell_script`, for `batch` to `:batch` * Recipe definition now returns the retval of the definition * Add support for Windows 10 to version helper. * `dsc_script` resource should honor configuration parameters when `configuration_data_script` is not set (Issue #2209) * Ruby has been updated to 2.1.3 along with rubygems update to 2.4.2 * Removed shelling out to erubis/ruby for syntax checks (>= 1.9 has been able to do this in the ruby vm itself for awhile now and we've dropped 1.8.7 which could not do this and had to shell_out) * Report the request and response when a non-200 error code happens * [FEATURE] Upgrade `knife upload` and `knife download` to download **everything** in an organization, now including the organization definition itself (`knife download /org.json`) and the invitations and member list (`knife download /invitations.json` and `knife download /members.json`). Should be compatible with knife-ec-backup. * Make default Windows paths more backslashy * `knife` now prefers to load `config.rb` in preference to `knife.rb`; `knife.rb` will be used if `config.rb` is not found. * Fixed Config[:cache_path] to use path_join() * Updated chef-zero to 3.0, so that client tests can be run against Enterprise Chef as well as Open Source. * knife cookbook site download/list/search/share/show/unshare now uses supermerket.getchef.com urls * added Chef::ResourceCollection#insert_at API to the ResourceCollection * http_proxy and related config vars no longer clobber already set ENV vars * all http_proxy configs now set lowercase + uppercase versions of ENV vars * https_proxy/ftp_proxy support setting `http://` URLs (and whatever mix and match makes sense) * End-to-end tests for Ubuntu 12.04 * Only run end-to-end tests when secure environment variables are present. * Remove recipe DSL from base provisioner (Issue 1446). * Enable client-side key generation by default. (Issue 1711) * CookbookSiteStreamingUploader now uses ssl_verify_mode config option (Issue 1518). * chef/json_compat now throws its own exceptions not JSON gem exceptions * Modify action for env raises Chef::Exceptions::Env exception on Windows (Chef Issues 1754) * Fix a bug in the experimental Policyfile mode that caused errors when using templates. * Disable JSON encoding of request body when non-JSON content type is specified. * Clean up FileVendor and CookbookUploader internal APIs * log resource now marks itself as supporting why-run * http_request no longer appends "?message=" query string to GET and HEAD requests * added shell_out commands directly to the recipe DSL * cookbook synchronizer deletes old files from cookbooks * do not clear file cache when override run list is set (CHEF-3684) * ruby 1.8.7/1.9.1/1.9.2 support is dropped * set no_lazy_load to true (CHEF-4961) * set file_stating_uses_destdir config option default to true (CHEF-5040) * remove dependency on rest-client gem * Add method shell_out_with_systems_locale to ShellOut. * chef-repo rake tasks are deprecated; print relevant information for each one. * Fix RPM package version detection (Issue 1554) * Don't override :default provider map if :default passed as platform (OC-11667). * Fix SuSE package removal failure (Issue 1732). * Enable Travis to run Test Kitchen with Kitchen EC2. * Fix a bug in reporting not to post negative duration values. * Add password setting support for Mac 10.7, 10.8 and 10.9 to the dscl user provider. * ChefSpec can find freebsd_package resource correctly when a package resource is declared on Freebsd. * Autodetect/decrypt encrypted data bag items with data_bag_item dsl method. (Issue 1837, Issue 1849) * windows_user: look up username instead of resource name (Issue #1705) * Remove the unused bootstrap templates that install chef from rubygems * Remove the Chef 10 functionality from bootstrap. * Deprecate --distro / --template_file options in favor of --boostrap-template * Add `:node_ssl_verify_mode` & `:node_verify_api_cert` options to bootstrap to be able to configure these settings on the bootstrapped node. * Add partial_search dsl method to Chef::Search::Query, add result filtering to search. * Transfer trusted certificates under :trusted_certs_dir during bootstrap. * Set :ssl_verify_mode to :verify_peer by default. * Add homebrew provider for package resource, use it by default on OS X (Issue #1709) * Add escape_glob method to PathHelper, update glob operations. * Verify x509 properties of certificates in the :trusted_certs_dir during knife ssl check. * Disable unforked interval chef-client runs. * Removed dependencies on the 'json' gem, replaced with ffi-yajl. Use Chef::JSONCompat library for parsing and printing. * Restore the deprecation logic of #valid_actions in LWRPs until Chef 13. * Now that we don't allow unforked chef-client interval runs, remove the reloading of previously defined LWRPs. * Use shell_out to determine Chef::Config[:internal_locale], fix CentOS locale detection bug. * `only_if` and `not_if` attributes of `execute` resource now inherits the parent resource's attributes when set to a `String`. * Retain the original value of `retries` for resources and display the original value when the run fails. * Added service provider for AIX. * The Windows env provider will delete elements even if they are only in ENV (and not in the registry) * Allow events to be logged to Windows Event Log * Fixed bug in env resource where a value containing the delimiter could never correctly match the existing values * More intelligent service check for systemd on Ubuntu 14.10. ## 11.16.4 * Windows omnibus installer security updates for redistributed bash.exe / sh.exe vulnerabilities ("Shellshock") CVE-2014-6271, CVE-2014-6271, CVE-2014-6278, CVE-2014-7186, CVE-2014-7187. * Fix bug on Windows where using the env resource on path could render the path unusable. * Chef Client now retries when it gets 50X from Chef Server. * Chef Client 11.16.4 can use the policyfiles generated with Chef DK 0.3.0. ## 11.16.2 * [**Phil Dibowitz**](https://github.com/jaymzh): Fix a regression in whyrun_safe_ruby_block. ## 11.16.0 * Fix a bug in user dscl provider to enable managing password and other properties at the same time. * Add `dsc_script` resource to Chef for PowerShell DSC support on Windows ## 11.14.6: * Modify action for env raises Chef::Exceptions::Env exception on Windows (Chef Issues 1754) * Fix RPM package version detection (Issue 1554) * Fix a bug in reporting not to post negative duration values. * Add password setting support for Mac 10.7, 10.8 and 10.9 to the dscl user provider. * ChefSpec can find freebsd_package resource correctly when a package resource is declared on Freebsd. * http_proxy and related config vars no longer clobber already set ENV vars * all http_proxy configs now set lowercase + uppercase versions of ENV vars * https_proxy/ftp_proxy support setting `http://` URLs (and whatever mix and match makes sense) ## 11.14.2 * [**Jess Mink**](https://github.com/jmink): Symlinks to directories should be swingable on windows (CHEF-3960) * [**Phil Dibowitz**](https://github.com/jaymzh): SIGTERM will once-more kill a non-daemonized chef-client (CHEF-5172) * [**Pierre Ynard**](https://github.com/linkfanel): chef-service-manager should run as a non-interactive service (CHEF-5150) * [**Tensibai Zhaoying**](https://github.com/Tensibai): Fix file:// URI support in remote\_file on windows (CHEF-4472) * [**John Dyer**](https://github.com/johntdyer): Catch HTTPServerException for 404 in remote_file retry (CHEF-5116) * [**Pavel Yudin**](https://github.com/Kasen): Providers are now set correctly on CloudLinux. (CHEF-5182) * [**Joe Richards**](https://github.com/viyh): Made -E option to work with single lettered environments. (CHEF-3075) * [**Jimmy McCrory**](https://github.com/JimmyMcCrory): Added a 'knife node environment set' command. (CHEF-1910) * [**Hongbin Lu**](https://github.com/hongbin): Made bootstrap report authentication exceptions. (CHEF-5161) * [**Richard Manyanza**](https://github.com/liseki): Made `freebsd_package` resource use the brand new "pkgng" package manager when available.(CHEF-4637) * [**Nikhil Benesch**](https://github.com/benesch): Implemented a threaded download queue for synchronizing cookbooks. (CHEF-4423) * [**Chulki Lee**](https://github.com/chulkilee): Raise an error when source is accidentally passed to apt_package (CHEF-5113) * [**Cam Cope**](https://github.com/ccope): Add an open_timeout when opening an http connection (CHEF-5152) * [**Sander van Harmelen**](https://github.com/svanharmelen): Allow environment variables set on Windows to be used immediately (CHEF-5174) * [**Luke Amdor**](https://github.com/rubbish): Add an option to configure the chef-zero port (CHEF-5228) * [**Ricardo Signes**](https://github.com/rjbs): Added support for the usermod provider on OmniOS * [**Anand Suresh**](https://github.com/anandsuresh): Only modify password when one has been specified. (CHEF-5327) * [**Stephan Renatus**](https://github.com/srenatus): Add exception when JSON parsing fails. (CHEF-5309) * [**Xabier de Zuazo**](https://github.com/zuazo): OK to exclude space in dependencies in metadata.rb. (CHEF-4298) * [**Åukasz Jagiełło**](https://github.com/ljagiello): Allow cookbook names with leading underscores. (CHEF-4562) * [**Michael Bernstein**](https://github.com/mrb): Add Code Climate badge to README. * [**Phil Sturgeon**](https://github.com/philsturgeon): Documentation that -E is not respected by knife ssh [search]. (CHEF-4778) * [**Stephan Renatus**](https://github.com/srenatus): Fix resource_spec.rb. * [**Sander van Harmelen**](https://github.com/svanharmelen): Ensure URI compliant urls. (CHEF-5261) * [**Robby Dyer**](https://github.com/robbydyer): Correctly detect when rpm_package does not exist in upgrade action. (CHEF-5273) * [**Sergey Sergeev**](https://github.com/zhirafovod): Hide sensitive data output on chef-client error (CHEF-5098) * [**Mark Vanderwiel**](https://github.com/kramvan1): Add config option :yum-lock-timeout for yum-dump.py * [**Peter Fern**](https://github.com/pdf): Convert APT package resource to use `provides :package`, add timeout parameter. * [**Xabier de Zuazo**](https://github.com/zuazo): Fix Chef::User#list API error when inflate=true. (CHEF-5328) * [**Raphaël Valyi**](https://github.com/rvalyi): Use git resource status checking to reduce shell_out system calls. * [**Eric Krupnik**](https://github.com/ekrupnik): Added .project to git ignore list. * [**Ryan Cragun**](https://github.com/ryancragun): Support override_runlist CLI option in shef/chef-shell. (CHEF-5314) * [**Cam Cope**](https://github.com/ccope): Fix updating user passwords on Solaris. (CHEF-5247) * [**Ben Somers**](https://github.com/bensomers): Enable storage of roles in subdirectories for chef-solo. (CHEF-4193) * [**Robert Tarrall**](https://github.com/tarrall): Fix Upstart provider with parameters. (CHEF-5265) * [**Klaas Jan Wierenga**](https://github.com/kjwierenga): Don't pass on default HTTP port(80) in Host header. (CHEF-5355) * [**MarkGibbons**](https://github.com/MarkGibbons): Allow for undefined solaris services in the service resource. (CHEF-5347) * [**Allan Espinosa**](https://github.com/aespinosa): Properly knife bootstrap on ArchLinux. (CHEF-5366) * [**Matt Hoyle**](https://github.com/deployable): Made windows service resource to handle transitory states. (CHEF-5319, CHEF-4791) * [**Brett cave**](https://github.com/brettcave): Add Dir.pwd as fallback for default user_home if home directory is not set. (CHEF-5365) * [**Caleb Tennis**](https://github.com/ctennis): Add support for automatically using the Systemd service provider when available. (CHEF-3637) * [**Matt Hoyle**](https://github.com/deployable): Add timeout for Chef::Provider::Service::Windows. (CHEF-1165) * [**Jesse Hu**](https://github.com/jessehu): knife[:attribute] in knife.rb should not override --attribute (CHEF-5158) * [**Vasiliy Tolstov**](https://github.com/vtolstov): Added the initial exherbo linux support for Chef providers. * Fix knife cookbook site share on windows (CHEF-4994) * YAJL Allows Invalid JSON File Sending To The Server (CHEF-4899) * YAJL Silently Ingesting Invalid JSON and "Normalizing" Incorrectly (CHEF-4565) * Update rpm provider checking regex to allow for special characters (CHEF-4893) * Allow for spaces in selinux controlled directories (CHEF-5095) * Windows batch resource run action fails: " TypeError: can't convert nil into String" (CHEF-5287) * Log resource always triggers notifications (CHEF-4028) * Prevent tracing? from throwing an exception when first starting chef-shell. * Use Upstart provider on Ubuntu 13.10+. (CHEF-5276) * Cleaned up mount provider superclass * Added "knife serve" to bring up local mode as a server * Print nested LWRPs with indentation in doc formatter output * Make local mode stable enough to run chef-pedant * Wrap code in block context when syntax checking so `return` is valid (CHEF-5199) * Quote git resource rev\_pattern to prevent glob matching files (CHEF-4940) * User resource now only prints the name during why-run runs. (CHEF-5180) * Set --run-lock-timeout to wait/bail if another client has the runlock (CHEF-5074) * remote\_file's source attribute does not support DelayedEvaluators (CHEF-5162) * `option` attribute of mount resource now supports lazy evaluation. (CHEF-5163) * `force_unlink` now only unlinks if the file already exists. (CHEF-5015) * `chef_gem` resource now uses omnibus gem binary. (CHEF-5092) * chef-full template gets knife options to override install script url, add wget/curl cli options, and custom install commands (CHEF-4697) * knife now bootstraps node with the latest current version of chef-client. (CHEF-4911) * Add config options for attribute whitelisting in node.save. (CHEF-3811) * Use user's .chef as a fallback cache path if /var/chef is not accessible. (CHEF-5259) * Fixed Ruby 2.0 Windows compatibility issues around ruby-wmi gem by replacing it with wmi-lite gem. * Set proxy environment variables if preset in config. (CHEF-4712) * Automatically enable verify_api_cert when running chef-client in local-mode. (Chef Issues 1464) * Add helper to warn for broken [windows] paths. (CHEF-5322) * Send md5 checksummed data for registry key if data type is binary, dword, or qword. (Chef-5323) * Add warning if host resembles winrm command and knife-windows is not present. * Use FFI binders to attach :SendMessageTimeout to avoid DL deprecation warning. (ChefDK Issues 69) * Use 'guest' user on AIX for RSpec tests. (OC-9954) * Added DelayedEvaluator support in LWRP using the `lazy {}` key * Fixed a bug where nested resources that inherited from Resource::LWRPBase would not share the same actions/default_action as their parent * Raise error if a guard_interpreter is specified and a block is passed to a guard (conditional) * Allow specifying a guard_interpreter after a conditional on a resource (Fixes #1943) * Windows package type should be a symbol (Fixes #1997) chef-12.14.60/CHEF_MVPS.md000066400000000000000000000223441276456504500145710ustar00rootroot00000000000000### Chef is proud of our community! Every release of Chef we pick someone from the community to name as the Most Valuable Player for that release. It could be someone who provided a big feature, reported a security vulnerability, or someone doing great things in the community that we want to highlight. In addition to the Hall of Fame and MVP recipients, three individuals are awarded the distinction of "Awesome Community Chef" each year at ChefConf. #### Hall of Fame After receiving three MVP awards, we add someone to the hall of fame. We want to express our gratitude to their continuing participation and give newer community members the opportunity to be recognized. * Matthew Kent * Doug MacEachern * Tollef Fog Heen * Thom May * Bryan Berry * Bryan McLellan * Jeff Blaine #### The MVP recipients | Release | Date | MVP | |---------|------|-----| | [Client 11.16.0](https://www.chef.io/blog/2014/09/08/release-chef-client-11-16-0-ohai-7-4-0/) | 2014-09-08 | Jesse Hu | | [Client 11.14.2](https://www.chef.io/blog/2014/08/01/release-chef-client-11-14-2/) | 2014-08-01 | Nikhil Benesch | | [Client 11.12.0](https://www.chef.io/blog/2014/04/08/release-chef-client-11-12-0-10-32-2/) | 2014-04-08 | Chris Bandy | | [Client 11.10.4](https://www.chef.io/blog/2014/02/20/chef-client-patch-release-11-10-4/) | 2014-02-20 | Jon Cowie | | [Client 11.10.2](https://www.chef.io/blog/2014/02/18/chef-client-release-11-10-2-10-30-4/) | 2014-02-18 | Eric Tucker | | [Client 11.10.0](https://www.chef.io/blog/2014/02/06/chef-client-11-10-0-release/) | 2014-02-06 | Nikhil Benesch | | [Client 11.8.2](https://www.chef.io/blog/2013/12/06/release-chef-client-10-30-2-11-8-2-mixlib-shellout-1-3-0/) | 2013-12-06 | James Ogden | | [Client 11.8.0](https://www.chef.io/blog/2013/10/31/release-chef-client-11-8-0-ohai-6-20-0/) | 2013-10-31 | Eric Saxby | | [Client 11.6.2](https://www.chef.io/blog/2013/10/08/release-chef-client-11-6-2-10-28-2/) | 2013-10-08 | Jeff Blaine | | [Client 11.6.0](https://www.chef.io/blog/2013/07/23/chef-client-11-6-0-ohai-6-18-0-and-more/) | 2013-07-23 | Jesse Campbell | | [Client 11.4.0](https://www.chef.io/blog/2013/02/13/chef-client-11-4-0-10-22-0-released/) | 2013-02-13 | Vaidas Jablonskis | | [Client 11.2.0](https://www.chef.io/blog/2013/02/07/chef-client-11-2-0-10-20-0-released/) | 2013-02-06 | Mike Javorski | | [Chef 11.0.0](https://www.chef.io/blog/2013/02/04/chef-11-released/) | 2013-02-04 | Andrea Campi, Bryan Berry | | [Chef 10.32.2](https://www.chef.io/blog/2014/04/08/release-chef-client-11-12-0-10-32-2/) | 2014-04-08 | Phil Dibowitz | | [Chef 10.30.4](https://www.chef.io/blog/2014/02/18/chef-client-release-11-10-2-10-30-4/) | 2014-02-18 | Christopher Laco | | [Chef 10.30.2](https://www.chef.io/blog/2013/12/06/release-chef-client-10-30-2-11-8-2-mixlib-shellout-1-3-0/) | 2013-12-06 | Phil Dibowitz | | [Chef 10.28.2](https://www.chef.io/blog/2013/10/08/release-chef-client-11-6-2-10-28-2/) | 2013-10-08 | Jeff Blaine | | [Chef 10.28.0](https://www.chef.io/blog/2013/09/03/chef-10-28-0-released/) | 2013-09-03 | Jeff Blaine | | [Chef 10.26.0](https://www.chef.io/blog/2013/05/08/chef-10-26-0-released/) | 2013-05-08 | Ranjib Dey | | [Chef 10.24.0](https://www.chef.io/blog/2013/02/15/chef-server-11-0-6-and-10-24-0-released/) | 2013-02-15 | Anthony Goddard | | [Chef 10.22.0](https://www.chef.io/blog/2013/02/13/chef-client-11-4-0-10-22-0-released/) | 2013-02-13 | Brian Bianco | | [Chef 10.20.0](https://www.chef.io/blog/2013/02/07/chef-client-11-2-0-10-20-0-released/) | 2013-02-06 | Chris Roberts | | [Chef 10.18.2](https://www.chef.io/blog/2013/01/18/chef-10-18-2-bugfix-release/) | 2013-01-18 | Fletcher Nichol | | [Chef 10.18.0](https://www.chef.io/blog/2013/01/16/chef-10-18-0-released/) | 2013-01-16 | Xabier de Zuazo | | [Chef 10.16.6](https://www.chef.io/blog/2013/01/11/chef-10-16-6-security-release/) | 2013-01-11 | Dan Kubb | | [Chef 10.16.4](https://www.chef.io/blog/2012/12/26/chef-10-16-4-released/) | 2012-12-26 | Avishai Ish-Shalom | | [Chef 10.16.2](https://www.chef.io/blog/2012/10/26/chef-10-16-2-released/) | 2012-10-26 | Jamie Winsor | | [Chef 10.16.0](https://www.chef.io/blog/2012/10/22/chef-10-16-0-released/) | 2012-10-22 | John Dewey | | [Chef 10.14.4](https://www.chef.io/blog/2012/09/28/chef-10-14-4-released/) | 2012-09-27 | Kendrick Martin | | [Chef 10.14.2](https://www.chef.io/blog/2012/09/11/chef-10-14-2-released/) | 2012-09-10 | Phil Dibowitz, Tim Smith | | [Chef 10.14.0](https://www.chef.io/blog/2012/09/07/chef-10-14-0-released/) | 2012-09-07 | Xabier de Zuazo | | [Chef 10.12.0](https://www.chef.io/blog/2012/06/19/chef-10-12-0-released/) | 2012-06-18 | Chris Roberts | | [Chef 0.10.10](https://www.chef.io/blog/2012/05/11/chef-0-10-10-released/) | 2012-05-11 | Juanje Ojeda, Igor Afonov | | [Chef 0.10.8](https://www.chef.io/blog/2011/12/15/chef-0-10-8-released/) | 2011-12-15 | Bryan Berry | | [Chef 0.10.6](https://www.chef.io/blog/2011/12/14/chef-0-10-6-released/) | 2011-12-13 | Andrea Campi | | [Chef 0.10.4](https://www.chef.io/blog/2011/08/11/chef-0-10-4-released/) | 2011-08-11 | Matthew Kent | | [Chef 0.10.2](https://www.chef.io/blog/2011/06/29/chef-0-10-2-and-0-9-18-released/) | 2011-06-29 | Daniel Oliver | | [Chef 0.10.0](https://www.chef.io/blog/2011/05/02/chef-0-10-0-released/) | 2011-05-02 | Grace Mollison, Darrin Eden | | [Chef 0.9.18](https://www.chef.io/blog/2011/06/29/chef-0-10-2-and-0-9-18-released/) | 2011-06-29 | Jesai Langenbach | | [Chef 0.9.16](https://www.chef.io/blog/2011/04/15/chef-0-9-16-released/) | 2011-04-15 | Michael Leinartas | | [Chef 0.9.14](https://www.chef.io/blog/2011/03/04/chef-0-9-14-released/) | 2011-03-04 | Gilles Devaux | | [Chef 0.9.12](https://www.chef.io/blog/2010/10/22/chef-0-9-12-released/) | 2010-10-22 | Laurent Désarmes | | [Chef 0.9.10](https://www.chef.io/blog/2010/10/19/chef-0-9-10-ohai-0-5-8-and-mixliblog-1-2-0-released/) | 2010-10-19 | Toomas Pelberg, Tommy Bishop | | [Chef 0.9.8](https://www.chef.io/blog/2010/08/05/chef-0-9-8-and-mixlib-authentication-1-1-4-released/) | 2010-08-05 | Joe Williams | | [Chef 0.9.6](https://www.chef.io/blog/2010/07/03/chef-0-9-6-released/) | 2010-07-03 | Caleb Tennis | | [Chef 0.9.4](https://www.chef.io/blog/2010/06/30/chef-0-9-4-released/) | 2010-06-30 | Ian Meyer | | [Chef 0.9.0](https://www.chef.io/blog/2010/06/21/chef-0-9-0-and-ohai-0-5-6-released/) | 2010-06-21 | Doug MacEachern | | [Chef 0.8.16](https://www.chef.io/blog/2010/05/11/chef-0-8-16-and-ohai-0-5-4-release/) | 2010-05-11 | Akzhan Abdulin | | [Chef 0.8.14](https://www.chef.io/blog/2010/05/07/chef-0-8-14-release/) | 2010-05-07 | Renaud Chaput | | [Chef 0.8.10](https://www.chef.io/blog/2010/04/02/chef-0-8-10-release/) | 2010-04-02 | Thom May, Tollef Fog Heen | | [Chef 0.8.8](https://www.chef.io/blog/2010/03/18/chef-0-8-8-release/) | 2010-03-18 | Eric Hankins | | [Chef 0.8.6](https://www.chef.io/blog/2010/03/05/chef-0-8-6-release/) | 2010-03-05 | Ian Meyer | | [Chef 0.8.4](https://www.chef.io/blog/2010/03/02/chef-0-8-4-release/) | 2010-03-02 | Tollef Fog Heen | | [Chef 0.8.2](https://www.chef.io/blog/2010/03/01/chef-0-8-2-release/) | 2010-03-01 | Scott M. Likens | | [Chef 0.7.16](https://www.chef.io/blog/2009/12/22/chef-0-7-16-release/) | 2009-12-22 | Bryan McLellan | | [Chef 0.7.14](https://www.chef.io/blog/2009/10/26/chef-0-7-14-ohai-0-3-6-releases/) | 2009-10-16 | Thom May | | [Chef 0.7.12](https://www.chef.io/blog/2009/10/06/chef-0-7-12rc0-ohai-0-3-4rc0-releases/) | 2009-10-06 | Diego Algorta | | [Chef 0.7.10](https://www.chef.io/blog/2009/09/04/chef-0-7-10-release/) | 2009-09-04 | Dan DeLeo | | [Chef 0.7.8](https://www.chef.io/blog/2009/08/13/chef-0-7-8-release/) | 2009-08-13 | Jeppe Nejsum Madsen | | [Chef 0.7.6](https://www.chef.io/blog/2009/08/08/chef-0-7-6-release/) | 2009-08-08 | Grant Zanetti | | [Chef 0.7.4](https://www.chef.io/blog/2009/06/26/back-to-back-chef-0-7-2-and-chef-0-7-4-released/) | 2009-06-26 | Hongli Lai | | [Chef 0.7.2](https://www.chef.io/blog/2009/06/26/back-to-back-chef-0-7-2-and-chef-0-7-4-released/) | 2009-06-26 | Joshua Sierles | | [Chef 0.7.0](https://www.chef.io/blog/2009/06/10/chef-0-7-0-release/) | 2009-06-10 | Matthew Kent | | [Chef 0.6.2](https://www.chef.io/blog/2009/04/29/chef-0-6-2-release/) | 2009-04-29 | David Balatero | | [Chef 0.6.0](https://www.chef.io/blog/2009/04/29/chef-0-6-0-release/) | 2009-04-29 | Matthew Kent | | [Chef 0.5.6](https://www.chef.io/blog/2009/03/06/chef-0-5-6/) | 2009-03-06 | Sean Cribbs | | [Chef 0.5.4](https://www.chef.io/blog/2009/02/13/chef-0-5-4/) | 2009-02-13 | Arjuna Christensen | | [Chef 0.5.2](https://www.chef.io/blog/2009/02/01/chef-0-5-2-and-ohai-0-1-4/) | 2009-02-01 | Bryan McLellan | #### Awesome Community Chefs Each year at ChefConf, three individuals are awarded the Awesome Community Chef award. The Awesome Community Chef awards are a way for the community to recognize a few of the individuals who have made a dramatic impact and have helped further the cause. * 2013 * [Bryan Berry](https://github.com/bryanwb) * [Fletcher Nichol](https://github.com/fnichol) * [Jamie Winsor](https://github.com/reset) * 2014 * [Ranjib Dey](https://github.com/ranjib) * [Miah Johnson](https://github.com/miah) * [Seth Vargo](https://github.com/sethvargo) * [Eric Wolfe](https://github.com/atomic-penguin) * 2015 * [Jon Cowie](https://github.com/jonlives) * [Noah Kantrowitz](https://github.com/coderanger) * [Matt Wrock](https://github.com/mwrock)chef-12.14.60/CLA_ARCHIVE.md000066400000000000000000002764741276456504500147760ustar00rootroot00000000000000 Corporate CLAs The list of Corporate CLAs allowed to contribute to Opscode projects. Only contributions from approved employees of these companies are acceptable. Employees get approved by being listed on the schedule A of the Corporate CLA. | **Number:** | **Company:** | **Date:** | |:------------|:---------------------------------------|:----------| | 1 | Opscode | | | 2 | Engine Yard | 1/7/09 | | 3 | Wikia | | | 4 | Aptana | 2/12/09 | | 5 | CleanOffer | 3/2/09 | | 6 | 37signals | 3/4/09 | | 7 | Nomitor | 3/9/09 | | 8 | We Go To 12 | 4/30/09 | | 9 | Plus2 Pty | 5/8/09 | | 10 | Phusion | 6/22/09 | | 11 | Rightscale | 6/30/09 | | 12 | Rubaidh | 7/27/09 | | 13 | Peritor GmbH | 8/12/09 | | 14 | Heroku | 8/13/09 | | 15 | Internet Exchange | 9/22/09 | | 16 | Betfair | 9/30/09 | | 17 | Sojern | 11/2/09 | | 18 | Runa | 12/20/09 | | 19 | MaxMedia | 1/11/10 | | 20 | Quantifind | 2/11/10 | | 21 | VMware | 2/11/10 | | 22 | Rackspace | 2/26/10 | | 23 | Leaway Enterprise | 3/16/10 | | 24 | Bueda | 3/30/10 | | 25 | Divergent Logic | 5/3/10 | | 26 | Basho Technologies | 5/4/10 | | 27 | Seven Scale | 5/13/10 | | 28 | IglooNET | 5/21/10 | | 29 | Freistil Consulting | 5/25/10 | | 30 | Promet Solutions | 5/25/10 | | 31 | Mint Digital | 6/16/10 | | 32 | Picklive | 6/16/10 | | 33 | 42 Lines | 6/27/10 | | 34 | Wildfire Interactiv | 7/9/10 | | 35 | Dynamic Network Services | 7/21/10 | | 36 | PeerPong | 8/4/10 | | 37 | domainfactory GmbH | 8/16/10 | | 38 | Tecnh | 8/17/10 | | 39 | 9Summer | 9/9/10 | | 40 | Wixpress | 9/13/10 | | 41 | Blue Box Group | 9/29/10 | | 42 | FindsYou Limited | 10/6/10 | | 43 | Highgroove Studios | 10/25/10 | | 44 | ZeStuff | 10/28/10 | | 45 | Worlize | 10/28/10 | | 46 | Automated Labs | 11/3/10 | | 47 | Estately | 11/4/10 | | 48 | Kapoq | 11/10/10 | | 49 | Openminds | 11/10/10 | | 50 | MobileCause | 11/10/10 | | 51 | Atalanta Systems | 11/14/10 | | 52 | Menue Americas | 11/17/10 | | 53 | Sociable Limited | 12/1/10 | | 54 | Nine Summer | 12/6/10 | | 55 | Neo Technology | 1/27/11 | | 56 | Moriz GmbH | 2/2/11 | | 57 | AegisCo | 2/14/11 | | 58 | SetJam | 2/15/11 | | 59 | Tippr | 2/18/11 | | 60 | Ning | 2/24/11 | | 61 | Workday | 3/12/11 | | 62 | 7digital | 3/3/11 | | 63 | PagerDuty | 3/17/11 | | 64 | Gnowsis | 3/25/11 | | 65 | Unboxed Consulting | 4/1/11 | | 66 | CustomInk | 4/8/11 | | 67 | TalentBox | 4/25/11 | | 68 | Wavii | 4/29/11 | | 69 | Datadog | 5/4/11 | | 70 | Viximo | 5/10/11 | | 71 | ZephirWorks | 5/11/11 | | 72 | Dell | 5/12/11 | | 73 | Newsweek/Daily Beast Company | 5/19/11 | | 74 | WordStream | 5/19/11 | | 75 | Flagbit | 6/14/11 | | 76 | Applications Online | 6/17/11 | | 77 | Versapay | 7/5/11 | | 78 | DigiTar | 7/19/11 | | 79 | DreamHost | 7/21/11 | | 80 | Edmunds.com | 7/22/11 | | 81 | Every Ware | 7/25/11 | | 82 | Ask.com | 8/1/11 | | 83 | bring.out doo Sarajevo | 8/11/11 | | 84 | Kos Media | 8/15/11 | | 85 | reallyenglish.com | 8/15/11 | | 86 | Fewbytes | 8/18/11 | | 87 | Business Intelligence Associates | 8/19/11 | | 88 | Tacit Knowledge | 8/22/11 | | 89 | Zenexity | 8/30/11 | | 90 | ClassDo | 8/30/11 | | 91 | Myplanet | 9/2/11 | | 92 | ihiji | 9/16/11 | | 93 | "Port 80 Productions, LLC" | 10/28/11 | | 94 | Green Alto | 11/2/11 | | 95 | "Heavy Water Software, Inc." | 11/4/11 | | 96 | Wealthfront Inc. | 11/15/11 | | 97 | "Kickstarter, Inc." | 11/18/11 | | 98 | Webtrends Inc | 11/22/11 | | 99 | "Infochimps, Inc." | 11/28/11 | | 100 | "Cycle Computing, LLC" | 11/29/11 | | 101 | "Ubalo, Inc" | 12/8/11 | | 102 | "SweetSpot Diabetes Care, Inc." | 12/12/11 | | 103 | "RideCharge, Inc." | 12/15/11 | | 104 | "Riot Games, Inc." | 12/15/11 | | 105 | Fiksu | 12/21/11 | | 106 | WhitePages Inc. | 1/3/12 | | 107 | "CX, Inc." | 1/12/12 | | 108 | Spoke Software | 1/15/12 | | 109 | Xforty Technologies | 1/25/12 | | 110 | "Democracy Works, Inc" | 1/30/12 | | 111 | "Pure Lake Software, Inc." | 2/10/12 | | 112 | Sveriges Television AB | 2/14/12 | | 113 | Reaktor Innovations | 2/14/12 | | 114 | "Oxygen Cloud, Inc." | 2/14/12 | | 115 | Robojar Pty Ltd | 2/17/12 | | 116 | Green and Secure IT Limited | 2/19/12 | | 117 | ModCloth | 2/23/12 | | 118 | Joyent | 2/29/12 | | 119 | "Wallrazor, Inc." | 3/4/12 | | 120 | "Cerner Innovation, Inc." | 3/8/12 | | 121 | "Numenta, Inc." | 3/27/12 | | 122 | Kotiri Software Ltd. | 4/3/12 | | 123 | "The Frontside Software, Inc." | 4/5/12 | | 124 | "Needle, Inc." | 4/5/12 | | 125 | "Gap, Inc." | 4/10/12 | | 126 | Youscribe | 4/11/12 | | 127 | Deutsche Telekom Laboratories | 4/17/12 | | 128 | "Relevance, Inc." | 4/20/12 | | 129 | Truer Sound LLC | 4/20/12 | | 130 | Websym Technologies Private Ltd. | 4/30/12 | | 131 | DreamBox Learning Inc | 5/3/12 | | 132 | Simple | 5/7/12 | | 133 | "Consumer Club, Inc" | 5/10/12 | | 134 | Onddo Labs | 5/18/12 | | 135 | CyberAgent Corp. | 5/22/12 | | 136 | SourceIndex IT-Services | 6/7/12 | | 137 | "Scribd, Inc." | 6/15/12 | | 138 | Civolution BV | 6/18/12 | | 139 | Drillinginfo | 6/18/12 | | 140 | NaviNet | 6/20/12 | | 141 | "Voxel Dot Net, Inc" | 6/26/12 | | 142 | Asbury Theological Seminary | 6/27/12 | | 143 | The Cloudscaling Group | 6/27/12 | | 144 | "Creationline, Inc." | 6/27/12 | | 145 | "Action Verb, LLC" | 7/10/12 | | 146 | Iugu Servicos na Internet LTDA | 7/11/12 | | 147 | OpeniT | 7/18/12 | | 148 | Cloudreach Limited | 7/24/12 | | 149 | Bonnier Corporation | 7/25/12 | | 150 | "OneHealth Solutions, Inc." | 7/25/12 | | 151 | Hewlett-Packard | 7/26/12 | | 152 | Paydici Corp. | 7/26/12 | | 153 | "Novell, Inc." | 8/1/12 | | 154 | Schuberg Phillis B.V. | 8/3/12 | | 155 | RelatelIQ Inc. | 8/3/12 | | 156 | HomeMade Digital Ltd | 8/7/12 | | 157 | "PrimeRevenue, Inc" | 8/10/12 | | 158 | Calxeda | 8/14/12 | | 159 | Big Cartel LLC | 8/17/12 | | 160 | Atlassian | 8/27/12 | | 161 | One Connect Limited | 9/7/12 | | 162 | Sonian Inc | 9/8/12 | | 163 | The App Business | 9/19/12 | | 164 | "Pat Deegan, PhD & Associates, LLC" | 9/26/12 | | 165 | OmniTI | 9/26/12 | | 166 | "Cloudant, Inc." | 10/5/12 | | 167 | ZestFinance | 10/8/12 | | 168 | Firebelly Design | 10/8/12 | | 169 | Nu Echo | 10/16/12 | | 170 | OpenConcept Consulting Inc. | 10/18/12 | | 171 | Apptentive | 10/20/12 | | 172 | "Document Swarm, LLC" | 10/20/12 | | 173 | "Tilting @, LLC" | 10/29/12 | | 174 | "Sift Science, Inc" | 10/31/12 | | 175 | FluxSauce | 11/1/12 | | 176 | Rocket Internet GmbH | 11/2/12 | | 177 | Coding-Knight LTD | 11/6/12 | | 178 | Tapp | 11/13/12 | | 179 | Taqtiqa LLC | 11/14/12 | | 180 | "Nordstrom, Inc" | 11/15/12 | | 181 | Daptiv Solutions LLC | 11/26/12 | | 182 | Lime Pepper Ltd | 11/28/12 | | 183 | "Straydog Software, Inc." | 11/29/12 | | 184 | "Fidelity Technology Group, LLC" | 12/6/12 | | 185 | "Angelweb, Unipessoal Lda." | 12/14/12 | | 186 | bcs kommunikationslosungen | 12/17/12 | | 187 | "North County Tech Center, LLC" | 12/22/12 | | 188 | Emergent One | 1/9/13 | | 189 | Ninefold Pty Limited | 1/9/13 | | 190 | DecisionDesk | 1/13/13 | | 191 | Belly Inc | 1/15/13 | | 192 | cloudbau Gmbh | 1/18/13 | | 193 | ActBlue Technical Services | 1/18/13 | | 194 | HiganWorks LLC | 1/22/13 | | 195 | Ontario Systems | 1/23/13 | | 196 | "Lytro, Inc." | 1/23/13 | | 197 | Grupa Allegro Sp. z o.o. | 1/31/13 | | 198 | Workday Inc. | 2/5/13 | | 199 | "Atlas Digital, LLC" | 2/6/13 | | 200 | Intoximeters | 2/15/13 | | 201 | Airbnb | 2/17/13 | | 202 | Valtech AB | 2/20/13 | | 203 | AWeber Communications | 2/25/13 | | 204 | adesso mobile solutions GmbH | 3/4/13 | | 205 | "Banno, LLC" | 3/5/13 | | 206 | AboutUs | 3/8/13 | | 207 | "Google, Inc" | 3/14/13 | | 208 | cloudControl GmbH | 3/21/13 | | 209 | Springest | 3/25/13 | | 210 | Criteo | 3/26/13 | | 211 | "Thinking Phone Networks, Inc." | 4/8/13 | | 212 | Evolving Web Inc | 4/11/13 | | 213 | BinaryBabel OSS | 4/17/13 | | 214 | Tout Industries | 4/18/13 | | 215 | "Lookout, Inc." | 4/22/13 | | 216 | Recorded Future Inc | 4/23/13 | | 217 | Irrational Industries | 5/1/13 | | 218 | "Socrata, Inc" | 5/1/13 | | 219 | "Aspera, Inc" | 5/1/13 | | 220 | "Hadapt, Inc" | 5/3/13 | | 221 | Moncai | 5/7/13 | | 222 | IBM | 5/14/13 | | 223 | Yahoo Inc. | 5/14/13 | | 224 | Texas A&M University College of Arch. | 5/20/13 | | 225 | MoPub | 5/21/13 | | 226 | "Onelogin, Inc" | 5/24/13 | | 227 | Yola | 5/28/13 | | 228 | CopperEgg | 5/28/13 | | 229 | "MeetMe, Inc" | 5/30/13 | | 230 | Boadree Innovations Kft. | 6/17/13 | | 231 | "Bitium, inc" | 6/21/13 | | 232 | Heart of Sales LLC DBA Ace of Sales | 7/4/13 | | 233 | NetSrv Consulting Ltd | 7/7/13 | | 234 | "AURIN Project" | 7/11/13 | | 235 | Onlife Health Inc | 7/17/13 | | 236 | Roblox Inc. | 7/17/13 | | 237 | "Taos Mountain, Inc" | 7/24/13 | | 238 | CoreMedia AG | 7/31/13 | | 239 | "PROS, Inc. a Delaware Corporation" | 8/14/13 | | 240 | Identive Group | 8/21/13 | | 241 | University of Derby | 8/22/13 | | 242 | TeamSnap | 8/29/13 | | 243 | Social Ally Pty Ltd | 8/29/13 | | 244 | Ecodev Sarl | 9/9/13 | | 245 | kreuzwerker GmbH | 9/18/13 | | 246 | Central Desktop | 9/18/13 | | 247 | Siili Solutions | 9/19/13 | | 248 | Twiket LTD | 9/23/13 | | 249 | Cloudsoft | 9/25/13 | | 250 | MYOB NZ Limited | 9/26/13 | | 251 | Mollie B.V. | 9/30/13 | | 252 | Unbounce | 10/1/13 | | 253 | Shutl Ltd. | 10/2/13 | | 254 | Rapid7 | 10/7/13 | | 255 | "Our Film Festival, Inc (dba Fandor)" | 10/7/13 | | 256 | "Ooyala, Inc." | 10/9/13 | | 257 | Squaremouth Inc | 10/10/13 | | 258 | Optiflows | 10/11/13 | | 259 | General Sensing LTD | 10/10/13 | | 260 | Deployable LTD | 10/22/13 | | 261 | Klarna | 10/23/13 | | 262 | "Nike, Inc." | 10/25/13 | | 263 | SoundCloud Ltd. | 11/5/13 | | 264 | Project Florida | 11/5/13 | | 265 | Intuit | 11/6/13 | | 266 | ComputeNext | 11/6/13 | | 267 | The Weather Companies | 11/8/13 | | 268 | PTC Inc | 11/13/13 | | 269 | RamTank Inc | 11/19/13 | | 270 | GoCardless | 11/24/13 | | 271 | ZANOX AG | 11/30/13 | | 272 | ARINC | 12/3/13 | | 273 | Lockheed Martin Corporation | 12/3/13 | | 274 | Brightcove | 12/16/13 | | 275 | "Sprint.ly, Inc" | 12/27/13 | | 276 | Cramer Development | 1/10/14 | | 277 | "BlackBerry, Inc." | 1/20/14 | | 278 | Cerner Innovation Inc | 2/6/14 | | 279 | Cerner Innovation Inc | 2/11/14 | | 280 | Engine Yard | 2/12/14 | | 281 | Crux Hosted Services | 2/18/14 | | 282 | Blue Spurs | 2/24/14 | | 283 | GitLab.com | 3/1/14 | | 284 | Yelp | 3/7/14 | | 285 | Workday | 3/10/14 | | 286 | BMC Software Inc | 3/13/14 | | 287 | Itison | 3/14/14 | | 288 | "OnBeep, Inc." | 3/19/14 | | 289 | Level 11 Consulting | 3/19/14 | | 290 | Linaro Limited | 4/4/14 | | 291 | Spanlink Communications | 4/17/14 | | 292 | "WESEEK, Inc" | 4/29/14 | | 293 | "Iniqa UK, Ltd" | 6/4/14 | | 294 | Jemstep | 6/13/1 | Allowed Contributors The list of allowed contributors to Opscode projects. Persons listed as associated with a company may also be individual contributors as well. To get on the list, check out our instructions on how to contribute. 1. Adam Jacob Opscode 1. Andy Delcambre Engineyard 1/7/09 1. Arjuna Christensen 1/7/09 1. Artur Bergman Wikia 1. Benjamin Black Opscode 1/10/09 1. Bryan McLellan 1/7/09 1. Dan Walters 3/7/09 1. Edward Muller Engineyard 1/28/09 1. Ezra Zygmuntowicz Engineyard 1/7/09 1. Jason Cook Wikia 1. Joe Williams 1/17/09 1. Kris Rasmussen Aptana 2/12/09 1. Lee Jensen Engineyard 1/24/09 1. Nick Sullivan Wikia 1. Paul Nasrat 1/19/09 1. Pawel Rein Wikia 1. Przemek Malkowski Wikia 1. Sean Cribbs 2/5/09 1. Steve Berryman 1/20/09 1. Steven Parkes Aptana 2/12/09 1. Thom May 1/21/09 1. Tim Dysinger 1/28/09 1. Michael Hale 2/16/09 1. Mathieu Sauve-Frankel 2/22/09 1. Matthew Landauer 2/25/09 1. John Hampton CleanOffer 3/2/09 1. Nadeem Bitar CleanOffer 3/2/09 1. James Gartrell 2/6/09 1. Joshua Sierles 37signals 3/4/09 1. Mark Imbriaco 37signals 3/4/09 1. Stephen Haynes Nomitor 3/9/09 1. Yun Huang Yong Nomitor 3/9/09 1. David Lee 3/9/09 1. Matthew Kent 3/24/09 1. Dave Myron 4/3/09 1. Miguel Cabeça 8/4/09 1. Jason Jackson 4/9/09 1. Caleb Tennis 4/10/09 1. Michael Lim 4/14/09 1. David Balatero 4/28/09 1. David Grandinetti We Go To 12 4/30/09 1. Lachlan Cox Plus2 Pty 5/8/09 1. Scott Likens 4/30/09 1. Andrew Willis 5/25/09 1. Hongli Lai Phusion 6/22/09 1. Ninh Bui Phusion 6/22/09 1. Edmund Haselwanter cloudbau Gmbh 6/25/09 1. Raphael Simon RightScale 6/30/09 1. Tony Spataro RightScale 6/30/09 1. Stéphane Crivisier 6/30/09 1. Matthew Todd Highgroove Studios 7/1/09 1. Grant Zanetti 2/1/09 1. Peter Woodman 6/22/09 1. Daniel DeLeo 7/10/09 1. Jeppe Madsen 9/13/09 1. Cary Penniman RightScale 7/20/09 1. J. Chris Anderson 8/7/09 1. Graeme Mathieson Rubaidh 8/10/09 1. Mark Connell Rubaidh 8/10/09 1. Jonathan Weiss Peritor GmbH 8/12/09 1. Mathias Meyer Peritor GmbH 8/12/09 1. Pedro Belo Heroku 8/13/09 1. Ricardo Chimal Jr. Heroku 8/13/09 1. Adam Wiggins Heroku 8/13/09 1. Ryan Tomayko Heroku 8/13/09 1. Blake Mizerany Heroku 8/13/09 1. Diego Algorta 1. Kevin Hunt 8/14/09 1. Sidney Burks 8/20/09 1. Joe Van Dyk 9/1/09 1. Sig Lange 9/1/09 1. Alexander van Zoest 9/1/09 1. Nathan Mueller 9/7/09 1. Roman Heinrich 9/11/09 1. Gábor Vészi 9/20/09 1. Kenneth Kalmer Internet Exchange 9/22/09 1. Luca Greco 9/22/09 1. Charles Cook Betfair 9/30/09 1. Mario Giammarco 10/6/09 1. Matthew King 10/14/09 1. James Golick 10/27/09 1. Jörn Berrisch 10/28/09 1. Peter Crossley 10/30/09 1. Eric Hankins Sojern 11/2/09 1. David McRae Sojern 11/2/09 1. Dan Fitch Sojern 11/2/09 1. Ian Meyer 11/8/09 1. John Alberts 11/9/09 1. Lee Marlow 11/11/09 1. Tollef Fog Heen 11/12/09 1. Cuong Chi Nghiem 11/13/09 1. Gordon Thiesfeld 11/18/09 1. Dreamcat4 11/21/09 1. Guy Bolton King 12/3/09 1. Robert Berger Runa 12/20/09 1. Siva Jagadeesan Runa 12/20/09 1. Ivan Pirlik 12/22/09 1. David Abdemoulaie 12/23/09 1. Alex Soto 12/29/09 1. Bryan Helmkamp 12/30/09 1. Jesse Nelson 1/6/10 1. Seth Chisamore Opscode 1/11/10 1. Alfredo Deza MaxMedia 1/11/10 1. N. Alan Johnson Jr. 1/15/10 1. Pavel Valodzka 2/8/10 1. Kyle Maxwell Quantifind 2/11/10 1. Doug MacEachern VMware 2/11/10 1. Jan Zimmek 2/14/10 1. Dan Prince Rackspace 2/26/10 1. Gabe Westmaas Rackspace 2/26/10 1. Tim Harper 3/8/10 1. Renaud Chaput 3/10/10 1. Daniel Peterson 3/11/10 1. Amit Cohen Leaway Enterprise 3/16/10 1. Avishai Ish-Shalom Leaway Enterprise 3/16/10 1. Or Cohen Leaway Enterprise 3/16/10 1. Jon Swope 3/19/10 1. Jonathan Tron 3/19/10 1. Christopher Peplin Bueda 3/30/10 1. Trotter Cashion 4/3/10 1. Benjamin Standefer 4/6/10 1. P. Barrett Little 4/7/10 1. John Nixon 4/13/10 1. Bruce Krysiak 4/13/10 1. Akzhan Abdulin 4/14/10 1. Grant Rodgers 4/17/10 1. Wesley Beary 4/22/10 1. Farzad Farid 4/23/10 1. Olivier Raginel 4/26/10 1. Jacques Crocker 5/3/10 1. Pierre Baillet 5/3/10 1. Joel Merrick 5/3/10 1. James Sanders 5/3/10 1. John Goulah 5/3/10 1. Toomas Pelberg 5/3/10 1. Ceaser Larry Divergent Logic 5/3/10 1. Justin Sheehy Basho Technologies 5/4/10 1. Andrew Gross Basho Technologies 5/4/10 1. Bryan Fink Basho Technologies 5/4/10 1. Ben Mabey 5/5/10 1. Christopher Durtschi Divergent Logic 5/7/10 1. Kevin Carter Divergent Logic 5/7/10 1. Saimon Moore 5/13/10 1. Troy Davis Seven Scale 5/13/10 1. Eric Lindvall Seven Scale 5/13/10 1. Alexey Ivanov 5/14/10 1. Pritesh Mehta 5/19/10 1. Ondrej Kudlik IglooNET 5/21/10 1. Marek Hulan IglooNET 5/21/10 1. Chad Woolley 5/22/10 1. Jochen Lillich Freistil Consulting 5/25/10 1. Marius Ducea Promet Solutions 5/25/10 1. Eric Butler 5/26/10 1. Sahil Cooner 6/6/10 1. Richard Nicholas Betfair 6/9/10 1. Dan Slimmon 6/10/10 1. Craig Webster Picklive 6/16/10 1. Dean Strelau Mint Digital 6/17/10 1. Kurt Yoder 6/25/10 1. Jim Browne 42 Lines 6/27/10 1. Andrey Sibiryov 7/7/10 1. Anthony Newman Betfair 7/8/10 1. Thomas Hoover 7/8/10 1. Dylan Egan Wildfire Interactive 7/9/10 1. Michael Carruthers Wildfire Interactive 7/11/10 1. Jon Seaberg RightScale 7/20/10 1. Sean O'Meara 7/20/10 1. Cory von Wallenstein Dynamic Network Services 7/21/10 1. Michael Leinartas 7/22/10 1. Thomas Bishop 7/23/10 1. Jon Wood 7/29/10 1. Dmitry Vyal 8/4/10 1. Gilles Devaux PeerPong 8/4/10 1. Chris Pepper 8/5/10 1. Dennis Klein 8/6/10 1. Warwick Poole 8/12/10 1. Ken Ming Ong 8/15/10 1. Ash Berlin 8/16/10 1. Jochen Tuchbreiter domainfactory GmbH 8/16/10 1. Mat Ellis Tecnh 8/17/10 1. Michael MacDonald 8/17/10 1. Jorge Luiz deBrito Falcão 8/18/10 1. Jamie Winsor 8/19/10 1. Darrin Eden 8/19/10 1. Jonathan Smith 8/19/10 1. Andrew Fulcher 8/23/10 1. Matthias Marschall 8/25/10 1. Peter Struijk 8/25/10 1. Robert Anthony Postill 8/28/10 1. Joshua Timberman Opscode 9/6/10 1. Benjamin Rockwood 9/6/10 1. Douglas Knight 9/9/10 1. Andrew Cole 9Summer 9/9/10 1. Dimitri Krassovski Wixpress 9/13/10 1. Gregory Man Wixpress 9/13/10 1. Allan Feid 9/17/10 1. Ringo De Smet 9/26/10 1. Tomasz Napierala 9/27/10 1. Jesse Proudman Blue Box Group 9/29/10 1. Ian Parades Blue Box Group 9/29/10 1. Lee Huffman Blue Box Group 9/29/10 1. Christopher Horton 10/1/10 1. Jude Sutton FindsYou Limited 10/6/10 1. James Le Cuirot FindsYou Limited 10/6/10 1. Richard Pelavin 10/7/10 1. Blake Irvin ModCloth 10/8/10 1. Jim Van Fleet 10/14/10 1. Laurent Désarmes 10/14/10 1. Jay T. McCanta 10/15/10 1. Eric G. Wolfe 10/20/10 1. Sami Haahtinen 10/21/10 1. Chris Kelly Highgroove Studios 10/25/10 1. Gerald L. Hevener Jr. 10/25/10 1. Charles Quinn Highgroove Studios 10/25/10 1. Jonathan Wallace Highgroove Studios 10/25/10 1. Jason Ardell 10/26/10 1. Sean Carey 10/27/10 1. Pierre-Luc Brunet ZeStuff 10/28/10 1. Sean Walbran 10/28/10 1. Brian McKelvey Worlize 10/28/10 1. Jeffrey Hulten Automated Labs 11/3/10 1. Doug Cole Estately 11/4/10 1. Ben Bleything Estately 11/4/10 1. Sebastian Boehm 11/6/10 1. Ches Martin 11/8/10 1. Eric C. Herot 11/8/10 1. Oliver Hankeln 11/10/10 1. David Nolan Kapoq 11/10/10 1. Frank Louwers Openminds 11/10/10 1. Bernard Grymonpon Openminds 11/10/10 1. Bram Gillemon Openminds 11/10/10 1. Paul Cortens MobileCause 11/10/10 1. Austin Schneider MobileCause 11/10/10 1. Kevin Ahrens 11/13/10 1. Stephen Nelson-Smith Atalanta Systems 11/14/10 1. Caleb Groom 11/16/10 1. Michael Ivey 11/17/10 1. Wojciech Wnetrzak 11/20/10 1. Dmitriy Tkachenko 11/22/10 1. Filip Tepper 11/24/10 1. Denis Barushev 11/27/10 1. Pedro F. <> Horrillo Guerra 11/28/10 1. James Harton Sociable Limited 12/1/10 1. Noah Kantrowitz 12/4/10 1. Anthony Burton 12/4/10 1. David Esposito Nine Summer 12/6/10 1. Mike Lecza Nine Summer 12/6/10 1. Andrew Cole Nine Summer 12/6/10 1. Michael Winser Nine Summer 12/6/10 1. Cory Burke Nine Summer 12/6/10 1. Charles Duffy Tippr 12/7/10 1. John Vincent 12/10/10 1. Dustin Currie 12/14/10 1. Mark Sonnabaum 12/14/10 1. Elliot Murphy 12/22/10 1. Laradji Nacer 12/30/10 1. Todd Nine 1/3/11 1. Elijah Wright 1/5/11 1. Anshul Khandelwal 1/13/11 1. Michael Carruthers 1/16/11 1. Vishvananda Ishaya 1/17/11 1. Scott Frazer 1/17/11 1. Eric Hodel 1/21/11 1. Eric Heydrick 1/25/11 1. Andreas Kollegger Neo Technology 1/27/11 1. Steve Lum 1/31/11 1. Anthony Goddard 2/1/11 1. Roland Moriz Moriz GmbH 2/2/11 1. Emil Sit Hadapt 2/2/11 1. Ranjib Dey 2/3/11 1. Ryan Davis 2/8/11 1. Eric Coleman 2/8/11 1. James Casey 2/9/11 1. Maciej Pasternacki 2/9/11 1. Grzegorz Marszalek 2/11/11 1. James Sulinski AegisCo 2/14/11 1. Erik Sabowski AegisCo 2/14/11 1. Maciej Pasternacki SetJam 2/15/11 1. Steven Dossett Ning 2/17/11 1. Dane Knecht Tippr 2/18/11 1. Mark Imbriaco Heroku 2/28/11 1. Jonathan Matthews 7digital 3/3/11 1. Patrick Collins 3/7/11 1. Jim Hopp Workday 3/12/11 1. Ken Dove Workday 3/12/11 1. Philip Reynolds Workday 3/12/11 1. Victor Zakharyev Workday 3/12/11 1. Greg Fuller Workday 3/12/11 1. Michael Callahan Workday 3/15/11 1. Don Norton Workday 3/15/11 1. Joe Nuspl Workday 3/15/11 1. Dan Thom Workday 3/15/11 1. Rick Cooper Workday 3/15/11 1. Patrick Debois 3/14/11 1. Michael Guterl 3/15/11 1. Andrew Miklas PagerDuty 3/17/11 1. Tristan Sloughter 3/22/11 1. Jonathon Ramsey 3/23/11 1. Jesai Langenbach 3/24/11 1. Maciej Pasternacki Gnowsis 3/25/11 1. Omri Cohen 3/28/11 1. Joseph Sokol-Margolis 3/31/11 1. Alex Tomlins Unboxed Consulting 4/1/11 1. Holger Just 4/5/11 1. Jake Vanderdray CustomInk 4/8/11 1. Nathen Harvey CustomInk 4/8/11 1. Padraig O'Sullivan 4/8/11 1. Christian Trabold 4/16/11 1. KC Braunschweig Edmunds.com 4/19/11 1. Josh Pasqualetto 4/21/11 1. Christopher C. Johnson 4/21/11 1. Christian Paredes 4/22/11 1. Viral Shah 4/24/11 1. Bradley Fritz 4/24/11 1. Nat Lownes 4/25/11 1. Jonathan Tron TalentBox 4/25/11 1. Joseph Halter TalentBox 4/25/11 1. Wilson Felipe Nunes Fernandes Pereira 4/27/11 1. Michael Grubb 4/27/11 1. Brandon Konkle 4/28/11 1. Matt Griffin 4/28/11 1. Anh K. Huynh 4/28/11 1. Erik Frey Wavii 4/29/11 1. Spike Gronim Wavii 4/29/11 1. Ian MacLeod Wavii 4/29/11 1. Guido Bartolucci Wavii 4/29/11 1. Fletcher Nichol 5/3/11 1. James Kane 7digital 5/4/11 1. Paul Richards 7digital 5/4/11 1. Alexis Le-Quoc Datadog 5/4/11 1. Matthew Singleton Datadog 5/4/11 1. Carlo Cabanilla Datadog 5/4/11 1. Olivier Pomel Datadog 5/4/11 1. Fabrice Ollivier Datadog 5/4/11 1. Matt Griffin Viximo 5/10/11 1. Chris Chiodo Viximo 5/10/11 1. Adam Bell Viximo 5/10/11 1. Sergio Rubio 5/10/11 1. Elmer Rivera 5/10/11 1. Andrea Campi ZephirWorks 5/11/11 1. Andrea Carlo Granata ZephirWorks 5/11/11 1. Marco Pierleoni ZephirWorks 5/11/11 1. Pietro Giorgianni ZephirWorks 5/11/11 1. Jesse Newland 5/11/11 1. Greg Swallow 5/12/11 1. Scott Jensen Dell 5/12/11 1. Greg Althaus Dell 5/12/11 1. Andi Abes Dell 5/12/11 1. Rob Hirschfeld Dell 5/12/11 1. Paul Webster Dell 5/12/11 1. Mitchell Hashimoto 5/12/11 1. Jamie van Dyke 5/17/11 1. Vladimir Kozhukalov 5/18/11 1. Nathan Butler Newsweek/Daily Beast Company 5/19/11 1. Ken Garland Newsweek/Daily Beast Company 5/19/11 1. Michael Yankovski WordStream 5/19/11 1. Augusto Becciu 5/19/11 1. Greg Albrecht 5/19/11 1. Eric James Buth 5/24/11 1. Dan Porter 5/24/11 1. Adrian Silva Atalanta Systems 5/25/11 1. Spike Morelli Atalanta Systems 5/25/11 1. Paul Nicholson 5/27/11 1. Mandi Walls 5/27/11 1. Carl Perry DreamHost 5/29/11 1. Greg Thornton 5/30/11 1. Joseph Heck 6/1/11 1. Charles Ray Johnson, Jr. 6/2/11 1. Joseph Anthony Pasquale Holsten 6/5/11 1. John Donagher 6/6/11 1. David Fuhr Flagbit 6/14/11 1. Jörg Weller Flagbit 6/14/11 1. Marcel Cary 6/15/11 1. Yedidya "Jay" Feldblum Applications Online 6/17/11 1. Michael Contento 6/20/11 1. Yogesh Pathade 6/22/11 1. Gavin Sandie 6/25/11 1. Bryan Horstmann-Allen 6/28/11 1. Glenn Pratt 7/5/11 1. Andrew Narkewicz Versapay 7/5/11 1. Zachary Tomas Stevens 7/11/11 1. Richard Gould 7/11/11 1. Philip Cohen 7/15/11 1. Christopher Michael McClimans 7/18/11 1. Jason J.W. Williams 7/19/11 1. Nuo Yan 7/21/11 1. Jaroslaw Åšmiejczak 7/25/11 1. Dimitri Aivaliotis Every Ware 7/25/11 1. Eric Rochester 7/27/11 1. Alex North-Keys Tippr 7/29/11 1. Adam Knight Tippr 7/29/11 1. Eugene Wood Ask.com 8/1/11 1. Aron Bartling Ask.com 8/1/11 1. Jack Francis Ask.com 8/1/11 1. Richard Marshall Ask.com 8/1/11 1. Mikola Kucharski Ask.com 8/1/11 1. Rory Mitchell Ask.com 8/3/11 1. Oskar Stolc Ask.com 8/3/11 1. Jack (John) Roehrig Ask.com 8/3/11 1. Paul Stahlke Ask.com 8/3/11 1. Jorge Mazzei Ask.com 8/3/11 1. Pakojo Samm Ask.com 8/3/11 1. David Smith Ask.com 8/3/11 1. Mike Adolphs 8/5/11 1. Ernad Husremović bring.out doo Sarajevo 8/11/11 1. Jasmin Beganović bring.out doo Sarajevo 8/11/11 1. SaÅ¡a Vranić bring.out doo Sarajevo 8/11/11 1. Å ator Emir bring.out doo Sarajevo 8/11/11 1. Jeremy Bingham Kos Media 8/15/11 1. Michael Taras Kos Media 8/15/11 1. Tomoyuki Sakurai reallyenglish.com 8/15/11 1. Mitsuru Yoshida reallyenglish.com 8/15/11 1. Avishai Ish-Shalom Fewbytes 8/18/11 1. Or Cohen Fewbytes 8/18/11 1. Domenico Delle Side 8/19/11 1. Paul Morton Business Intelligence Associates 8/19/11 1. Phil Austin Business Intelligence Associates 8/19/11 1. Andrian Jardan 8/22/11 1. Vladimir Girnet Tacit Knowledge 8/22/11 1. Scott Askew Tacit Knowledge 8/22/11 1. Emmett Finneran 8/26/11 1. Arthur Gautier Zenexity 8/30/11 1. Edward Middleton ClassDo 8/30/11 1. Michael Pearson 8/31/11 1. Nikolay Sturm 9/1/11 1. Nathan Lloyd Smith 9/1/11 1. Patrick Connolly Myplanet 9/2/11 1. Yashar Rassoulli Myplanet 9/2/11 1. James Walker Myplanet 9/2/11 1. Chris Read 9/6/11 1. Prashant Srivastava 9/7/11 1. Brad Knowles ihiji 9/16/11 1. Stuart Rench ihiji 9/16/11 1. Michael Maniscalco ihiji 9/16/11 1. Brian Cunnie 9/19/11 1. Joseph F. Reynolds 9/20/11 1. Gabriel McArthur 9/21/11 1. David Keith Hudgins 9/21/11 1. Paul MacDougall 9/23/11 1. Bulat Shakirzyanov 9/23/11 1. Jorge Eduardo Espada 9/28/11 1. Eric Dennis 9/28/11 1. Stuart Glenn 9/29/11 1. Bryan Wilson Berry 10/4/11 1. Christopher Sturm 10/5/11 1. John Sumsion 10/12/11 1. Steven Phung 10/13/11 1. Claudio Cesar Sanchez Tejeda 10/14/11 1. Igor Afonov 10/26/11 1. Dan Buettner Port 80 Production, LLC 10/28/11 1. Robby Grossman 10/31/11 1. Alan Harper 11/1/11 1. Juanje Ojeda 11/1/11 1. Stephane Jourdan Green Alto 11/2/11 1. Gregory Karekinian Green Alto 11/2/11 1. Samuel Maftoul Green Alto 11/2/11 1. Darrin Eden Heavy Water Software, Inc. 11/4/11 1. Sean Escriva Heavy Water Software, Inc. 11/4/11 1. Jake Davis Heavy Water Software, Inc. 11/4/11 1. AJ Christensen Heavy Water Software, Inc. 11/4/11 1. Michael Weinberg Heavy Water Software, Inc. 11/4/11 1. Aaron Baer Heavy Water Software, Inc. 11/4/11 1. Matthew Kanwisher 11/8/11 1. Joshua McKenty 11/9/11 1. Iulian-Corneliu Costan 11/11/11 1. Daniel Oliver 11/14/11 1. Adam Garside 11/15/11 1. Ian Wolfcat Atha Wealthfront Inc. 11/15/11 1. John Hitchings Wealthfront Inc. 11/15/11 1. David Fortunato Wealthfront Inc. 11/15/11 1. Julien Wetterwald Wealthfront Inc. 11/15/11 1. Kevin Peterson Wealthfront Inc. 11/15/11 1. Maksim Horbul 11/16/11 1. Roberto Gaiser 11/16/11 1. Robert Di Marco 11/17/11 1. Victor Lowther Dell 11/17/11 1. Jerry Chen 11/18/11 1. Murali Raju 11/18/11 1. Benjamin Smith 11/18/11 1. Aaron Suggs Kickstarter, Inc. 11/18/11 1. Lance Ivy Kickstarter, Inc. 11/18/11 1. Cedric Howe Kickstarter, Inc. 11/18/11 1. Tieg Zaharia Kickstarter, Inc. 11/18/11 1. Teemu Matilainen Reaktor Innovations 11/22/11 1. Dave Solbes Webtrends Inc 11/22/11 1. Grant Hutchins 11/25/11 1. Eric Saxby ModCloth 11/27/11 1. Gabriel Evans 11/27/11 1. Tim Smith Webtrends Inc 11/28/11 1. Nathaniel Eliot Infochimps, Inc 11/28/11 1. Adam Seever Infochimps, Inc 11/28/11 1. Travis Dempsey Infochimps, Inc 11/28/11 1. Dhruv Bansal Infochimps, Inc 11/28/11 1. Andrew Kaczorek Cycle Computing, LLC 11/29/11 1. Chris Chalfant Cycle Computing, LLC 11/29/11 1. Dan Harris Cycle Computing, LLC 11/29/11 1. Ian Alderman Cycle Computing, LLC 11/29/11 1. Stephen Balukoff 11/30/11 1. Kendrick Martin Webtrends Inc 12/1/11 1. Adnan Wahab 12/3/11 1. Alex Howells 12/4/11 1. Cameron Johnston Needle, Inc. 12/5/11 1. Justin Huff 12/8/11 1. Ian Downes Ubalo, Inc 12/8/11 1. Erik Hollensbe 12/9/11 1. Karel Minarik 12/11/11 1. Adam Greene SweetSpot Diabetes Care, Inc. 12/12/11 1. Justin Schumacher SweetSpot Diabetes Care, Inc. 12/12/11 1. Dan Root SweetSpot Diabetes Care, Inc. 12/12/11 1. Paul Dowman 12/12/11 1. Andrew Le 12/12/11 1. Paul Welch 12/13/11 1. Harlan Barnes 12/13/11 1. Philip Kates Rackspace US, Inc 12/13/11 1. Brandon Philips Rackspace US, Inc 12/13/11 1. Paul Querna Rackspace US, Inc 12/13/11 1. Arthur Pirogovski 12/13/11 1. John Scott Sanders, Jr RideCharge, Inc 12/15/11 1. Jamie Winsor Riot Games 12/15/11 1. Josiah Kiehl Riot Games 12/15/11 1. Jesse Howarth Riot Games 12/15/11 1. Michael Matsuuara Riot Games 12/15/11 1. Cliff Dickerson Riot Games 12/15/11 1. Philip Gollucci RideCharge, Inc 12/15/11 1. Radim Marek 12/17/11 1. Hugo Fichter 12/19/11 1. Chris Christensen 12/20/11 1. Chet Luther 12/20/11 1. Mark Luntzel 12/20/11 1. Kevin Karwaski Fiksu 12/21/11 1. David Calavera 12/22/11 1. Michael Stillwell 12/23/11 1. Aaron Bull Schaefer 12/28/11 1. Max Rabin 1/3/12 1. Michael Bradshaw WhitePages Inc. 1/3/12 1. Michael Cook WhitePages Inc. 1/3/12 1. Jack Foy WhitePages Inc. 1/3/12 1. Devin Ben-Hur WhitePages Inc. 1/3/12 1. Jeff Bellegarde WhitePages Inc. 1/3/12 1. John Dyer 1/4/12 1. Sam Marx 1/5/12 1. Praveen Arimbrathodiyil 1/6/12 1. Joshua Buysse 1/6/12 1. Dale Hui 1/6/12 1. Jesse Campbell 1/8/12 1. Roberto Carlos Morano 1/9/12 1. Miah Johnson Scribd, Inc. 1/10/12 1. Ian Delahorne 1/12/12 1. Mike Javorski Spoke Software 1/15/12 1. Michael A. Fiedler 1/16/12 1. Luis Bosque 1/16/12 1. Qiming He 1/18/12 1. Hector Castro 1/19/12 1. Wade Warren Wikia 1/23/12 1. Geoff Papilion Wikia 1/23/12 1. Justin Ryan Wikia 1/23/12 1. David King Xforty Technologies 1/25/12 1. Christian Pearce Xforty Technologies 1/25/12 1. Andrew Libby Xforty Technologies 1/25/12 1. Joshua Hou 1/27/12 1. Alice Kaerast 1/28/12 1. Brett Hoerner 1/28/12 1. Jon-Erik Schneiderhan 1/30/12 1. Wes Morgan Democracy Works, Inc 1/30/12 1. Ernie Brodeur 1/31/12 1. Stephen Figgins 1/31/12 1. Tal Rotbart 2/1/12 1. Benjamin Lindsey 2/1/12 1. Markus Schirp 2/1/12 1. Tryn Mirell 2/2/12 1. Hari Krishna Dara 2/7/12 1. Andrew Grangaard 2/8/12 1. Adam Mielke 2/8/12 1. Andrew Allan 2/9/12 1. Antonio Soares de Azevedo Terceiro 2/9/12 1. Istvan Szukacs 2/9/12 1. Brian Parker Pure Lake Software, Inc. 2/10/12 1. Sean Porter 2/10/12 1. William Carroll 2/12/12 1. Paul Diaconescu Sveriges Television AB 2/14/12 1. Jonas Eklof Sveriges Television AB 2/14/12 1. Per Bjorn Sveriges Television AB 2/14/12 1. Frank Hoffsumer Sveriges Television AB 2/14/12 1. Samppa Kytomaki Reaktor Innovations 2/14/12 1. Zuhaib Siddique Atlassian 2/14/12 1. Andrew Robson Oxygen Cloud, Inc. 2/14/12 1. Aaron Follette Oxygen Cloud, Inc. 2/14/12 1. Erik Bakker 2/16/12 1. David Golden 2/16/12 1. Jacques Chester Robojar Pty Ltd 2/17/12 1. Nicholas VINOT 2/18/12 1. Matthew MacDonald-Wallace 2/19/12 1. Andrew Gross 2/20/12 1. Andrew Fecheyr Lippins 2/21/12 1. Shoaib Kamil 2/21/12 1. Martin Vidner 2/23/12 1. Jake Ritorto ModCloth 2/23/12 1. Seth Kingry ModCloth 2/23/12 1. Manuel Gutierrez ModCloth 2/23/12 1. Graham McMillan World Wide Web Hosting, LLC 2/24/12 1. Nicholas Stielau 2/24/12 1. McClain Looney 2/24/12 1. Jim Meyer 2/29/12 1. Trevor Orsztynowicz Joyent 2/29/12 1. Kevin Chang Joyent 2/29/12 1. Geoffery Nix ModCloth 3/1/12 1. Roberto Sanchez ModCloth 3/1/12 1. Dan Buch ModCloth 3/1/12 1. Ziad Sawalha Rackspace 3/2/12 1. Benedikt Böhm 3/4/12 1. Steven Ivy Wallrazer, Inc. 3/4/12 1. Krzysztof Wilczynski 3/5/12 1. Elson Orlando Rodriguez 3/5/12 1. Michael Schubert 3/5/12 1. Douglas Thrift Rightscale 3/5/12 1. Andrew Benz 3/6/12 1. Yann Robin Youscribe 3/6/12 1. Javier Frias 3/6/12 1. Josh Miller Edmunds.com 3/6/12 1. Moritz Winter 3/6/12 1. Aaron Blythe Cerner Innovation, Inc. 3/8/12 1. Kevin Shekleton Cerner Innovation, Inc. 3/8/12 1. Josh Murphy Cerner Innovation, Inc. 3/8/12 1. Bryan Baugher Cerner Innovation, Inc. 3/8/12 1. Sachin Sagar Ra 3/8/12 1. Tarik Jabri 3/8/12 1. Michael W. Myers 3/10/12 1. Marcus Cobden 3/11/12 1. Coimbatore Sankarraman Shyam Sundar 3/12/13 1. Chris Roberts Heavy Water Software, Inc. 3/13/12 1. Justin Mazzi World Wide Web Hosting, LLC 3/13/12 1. Joshua Priddle World Wide Web Hosting, LLC 3/13/12 1. Paul Stengel World Wide Web Hosting, LLC 3/13/12 1. Vince Stratful World Wide Web Hosting, LLC 3/13/12 1. Artem Veremey 3/13/12 1. Jon Cowie 3/15/12 1. Philip Kromer Infochimps, Inc 3/17/12 1. Ben Dean 3/18/12 1. Zachary Cook 3/19/12 1. Welby McRoberts 3/20/12 1. Ian Coffey Voxel Dot Net, Inc 3/20/12 1. David Amian Valle 3/21/12 1. Lewis J. Goettner, III 3/21/12 1. Bernardo Gomez Palacio 3/23/12 1. Chris Gaffney 3/23/12 1. Igor Kurochkin 3/24/12 1. Oleksiy Kovyrin 3/24/12 1. Jordan Dea-Mattson Numenta, Inc. 3/26/12 1. Cody Ebberson Numenta, Inc. 3/27/12 1. Martin Hasan Bramwell 3/28/12 1. Mohammed Siddick 3/28/12 1. Ira Abramaov Fewbytes 3/29/12 1. Paul McCallick 3/29/12 1. Jon-Paul Sullivan Hewlett-Packard 3/30/12 1. Sascha Bates 3/30/12 1. Julian Cardona Edmunds.com 4/2/12 1. David Hudson Edmunds.com 4/2/12 1. Andrew Crump Kotiri Software Ltd. 4/3/12 1. Zach Dunn 4/5/12 1. Logan Lowell The Frontside Software, Inc. 4/5/12 1. Charles Lowell The Frontside Software, Inc. 4/5/12 1. Chris Buben 4/5/12 1. Joseph Brian Passavanti 4/5/12 1. Denis Barishev 4/6/12 1. Jonas Courteau 4/6/12 1. Eric Hankins 4/10/12 1. Chris Buben Gap, Inc. 4/10/12 1. Oliver Fross Gap, Inc. 4/10/12 1. Jeffery Padgett Gap, Inc. 4/10/12 1. Philip Vieira 4/10/12 1. Guilhem Lettron Youscribe 4/11/12 1. Sebastien Balant Youscribe 4/11/12 1. Robert E. Lewis 4/13/12 1. David Joos 4/16/12 1. Umang Chouhan 4/16/12 1. Sören Blom Deutsche Telekom Laboratories 4/17/12 1. Alex Redington Relevance, Inc. 4/20/12 1. Gabriel Horner Relevance, Inc. 4/20/12 1. Lake Denman Relevance, Inc. 4/20/12 1. Larry Karnowski Relevance, Inc./Truer Sound LLC 4/20/12 1. Sam Umbach Relevance, Inc./Truer Sound LLC 4/20/12 1. Jeremiah Snapp Asbury Theological Seminary 4/20/12 1. Brian Bianco 4/20/12 1. Brandon Martin 4/21/12 1. Alexander Gordeev 4/24/12 1. Joe Miller 4/25/12 1. Nick Peirson 4/27/12 1. Marc Morata Fite 4/27/12 1. Seth Thomas 4/27/12 1. Chris Griego 4/28/12 1. Dmytro Ilchenko 4/30/12 1. Morgan Nelson 4/30/12 1. Chirag Jog Websym Technologies Private Ltd. 4/30/12 1. Kalpak Shah Websym Technologies Private Ltd. 4/30/12 1. Mohit Sethi Websym Technologies Private Ltd. 4/30/12 1. Kyle VanderBeek 5/2/12 1. TANABE Ken-ichi 5/2/12 1. Brandon Adams DreamBox Learning, Inc. 5/3/12 1. Hui Hu 5/5/12 1. Will Maier Simple 5/7/12 1. Chris Brentano Simple 5/7/12 1. Cosmin Stejerean Simple 5/7/12 1. Brian Merritt Simple 5/7/12 1. Pascal Deschenes 5/8/12 1. Michael Glenn 5/8/12 1. Dan Crosta 5/9/12 1. Daniel Condomitti 5/9/12 1. Matthew Butcher 5/10/12 1. Ben Poweski Consumer Club, Inc. 5/10/12 1. Chris Griego Consumer Club, Inc. 5/10/12 1. Jim Hughes Consumer Club, Inc. 5/10/12 1. Morgan Nelson Consumer Club, Inc. 5/10/12 1. Kristina Rodgers Consumer Club, Inc. 5/10/12 1. Derek Schultz 5/11/12 1. Anay Nayak 5/15/12 1. Patrick Ting 5/17/12 1. Xabier de Zuazo Oteiza Onddo Labs 5/18/12 1. Raul Rodriguez Munoz Onddo Labs 5/18/12 1. Ramez Mourad 5/21/12 1. Jonathan Manton 5/22/12 1. Philipp Wollermann CyberAgent Corp. 5/22/12 1. Koji Hasebe CyberAgent Corp. 5/22/12 1. Jean-Daniel Bussy CyberAgent Corp. 5/22/12 1. Yoshihisa Sakamoto CyberAgent Corp. 5/22/12 1. Kohei Maeda CyberAgent Corp. 5/22/12 1. Eric Edgar 5/23/12 1. Rodolphe Blancho 5/23/12 1. Kevin Nuckolls 5/24/12 1. Michael Nygard Relevance, Inc. 5/29/12 1. Martin Fenner 5/30/12 1. Lukasz Kaniowski 5/31/12 1. Brian Flad 5/31/12 1. Justin Witrick Rackspace 6/1/12 1. Nickalaus Willever 6/1/12 1. Harold "Waldo" Grunenwald III 6/4/12 1. Bjorn Albers 6/4/12 1. Timothy Martin Potter 6/5/12 1. Greg Fitzgerald 6/6/12 1. Sebastian Wendel SourceIndex IT-Services 6/7/12 1. Leif Madsen 6/7/12 1. Jonathan del Strother 6/7/12 1. Bernd Roth 6/8/12 1. Madhurranjan Mohaan 6/11/12 1. Seth Vargo 6/11/12 1. Gregory Jones 6/12/12 1. Joshua Brand 6/15/12 1. David Stainton Scribd, Inc. 6/15/12 1. Sriram Devadas 6/17/12 1. Christopher Webber 6/17/12 1. Raf Geens Civolution BV 6/18/12 1. Greg Symons Drillinginfo 6/18/12 1. Clark Archer Drillinginfo 6/18/12 1. David Eddy 6/18/12 1. Jonathan Hartman Rackspace 6/21/12 1. Boyd Edward Hemphill 6/21/12 1. Martha Greenberg 6/24/12 1. Paul Meserve 6/25/12 1. Michael H. Oshita 6/25/12 1. James W. Brinkerhoff Voxel Dot Net, Inc 6/26/12 1. Evan Vetere Voxel Dot Net, Inc 6/26/12 1. Kris Beevers Voxel Dot Net, Inc 6/26/12 1. Patrick Dowell Voxel Dot Net, Inc 6/26/12 1. Zachary Voase 6/26/12 1. Paul Guth The Cloudscaling Group, Inc. 6/27/12 1. Rodolphe Pineau The Cloudscaling Group, Inc. 6/27/12 1. Jeremy Deininger The Cloudscaling Group, Inc. 6/27/12 1. Blake Barnett The Cloudscaling Group, Inc. 6/27/12 1. HIGUCHI Daisuke Creationline, Inc. 6/27/12 1. Jey Hotta Creationline, Inc. 6/27/12 1. Kent R. Spillner 7/6/12 1. Brian Dols 7/6/12 1. Frank Rosquin 7/10/12 1. Bill Moritz 7/10/12 1. Alfred Rossi Action Verb, LLC 7/10/12 1. Patrick Ribeiro Negri lugu Seervicos na Internet LTDA 7/11/12 1. Marcelo Paez lugu Seervicos na Internet LTDA 7/11/12 1. Alexandre Paez lugu Seervicos na Internet LTDA 7/11/12 1. Wong Liang Zan 7/11/12 1. Matthew Andersen 7/12/12 1. Jacob Atzen 7/13/12 1. Chris Parsons 7/13/12 1. Timothy Jones 7/14/12 1. Ameya Prakash Gangamwar 7/15/12 1. Adrien Brault 7/16/12 1. Michael T. Halligan 7/17/12 1. Andreas Boehrnsen OpeniT 7/18/12 1. Jay Levitt 7/20/12 1. Jose Luis Fernandez Perez 7/21/12 1. Aaron J. Peterson 7/22/12 1. Jim Croft Cloudreach Limited 7/24/12 1. Richard Bowden Cloudreach Limited 7/24/12 1. Joe Geldart Cloudreach Limited 7/24/12 1. Bryce Lynn Tacit Knowledge 7/24/12 1. Brian Smith Bonnier Corporation 7/25/12 1. Michael Linde Bonnier Corporation 7/25/12 1. Peter Lauda Bonnier Corporation 7/25/12 1. Rakesh Patel OneHealth Solutions, Inc. 7/25/12 1. Jay Perry 7/26/12 1. Mark Roddy 7/26/12 1. Andrew Regan 7/26/12 1. Takeshi Kondo 7/26/12 1. Paul Rossman 7/26/12 1. Bryan Stearns Paydici Corp. 7/26/12 1. Jim Harvey Paydici Corp. 7/26/12 1. Bill Burcham Paydici Corp. 7/26/12 1. Steve Rude 7/26/12 1. Richard Clamp 7/29/12 1. Christopher Kelly 7/30/12 1. Deepak Kannan 7/30/12 1. Roy Liu 7/31/12 1. Artiom Lunev 7/31/12 1. Anna Marseille D. Gabutero 7/31/12 1. Lucas Jandrew Riot Games 8/1/12 1. Dafydd Crosby 8/1/12 1. Christoph Thiel Novell, Inc. 8/1/12 1. Ralf Haferkamp Novell, Inc. 8/1/12 1. Adam Spiers Novell, Inc. 8/1/12 1. Tim Serong Novell, Inc. 8/1/12 1. Sascha Peilicke Novell, Inc. 8/1/12 1. Bernhard Wiedemann Novell, Inc. 8/1/12 1. Ionuts Artarisi Novell, Inc. 8/1/12 1. Vincent Untz Novell, Inc. 8/1/12 1. Martin Vidner Novell, Inc. 8/1/12 1. J. Daniel Schmidt Novell, Inc. 8/1/12 1. Stefan Fent Novell, Inc. 8/1/12 1. Danny Kukawka Novell, Inc. 8/1/12 1. Michal Vyskocil Novell, Inc. 8/1/12 1. Kristian Vlaardingerbroek Schuberg Philis B.V. 8/3/12 1. Jon Gretarsson RelatelIQ Inc. 8/3/12 1. Danial Pearce 8/5/12 1. Stathis Touloumis 8/5/12 1. Matthew Scott Moyer 8/7/12 1. Benedict Steele 8/7/12 1. James Tan Novell, Inc. 8/7/12 1. John Kip Larsen 8/8/12 1. Chris Buryta 8/9/12 1. Sahil Muthoo 8/10/12 1. Kyle Goodwin PrimeRevenue, Inc 8/10/12 1. Ben Rosenblum PrimeRevenue, Inc 8/10/12 1. Aaron Kalin 8/10/12 1. John Dewey 8/11/12 1. Abel Lopez 8/13/12 1. Lyndon Washington 8/14/12 1. Ripal Nathuji Calxeda 8/14/12 1. Gardner Bickford 8/15/12 1. Nick Heppner 8/16/12 1. Jeffrey Dutton 8/16/12 1. Taklon Wu 8/17/12 1. Craig Tracey 8/17/12 1. Lee Jensen Big Cartel LLC 8/17/12 1. Chris Cameron Big Cartel LLC 8/17/12 1. Kelley Reynolds Big Cartel LLC 8/17/12 1. Michael Wallman 8/17/12 1. Edward Sargisson 8/19/12 1. Winfield Peterson 8/20/12 1. Mathew Davies 8/21/12 1. Justin Shepherd Rackspace 8/21/12 1. Jason Cannavale Rackspace 8/21/12 1. Ron Pedde Rackspace 8/21/12 1. Joseph Breu Rackspace 8/21/12 1. William Kelly Rackspace 8/21/12 1. Darren Birkett Rackspace 8/21/12 1. Evan Callicoat Rackspace 8/21/12 1. Andrew Ferk 8/23/12 1. Shaun Hope 8/23/12 1. Matt Kynaston 8/24/12 1. Ben Marini 8/25/12 1. Garret Heaton Atlassian 8/27/12 1. Julian Dunn 8/27/12 1. Jordan Evans 8/28/12 1. Andrew Laski 8/31/12 1. Mat Schaffer 8/31/12 1. Elliot Pahl 9/3/12 1. Richard Shade Rightscale 9/5/12 1. Dmytro Kovalov 9/5/12 1. Shishir Das 9/6/12 1. Kimball Johnson One Connect Limited 9/7/12 1. Roy Crombleholme One Connect Limited 9/7/12 1. Martin Foster One Connect Limited 9/7/12 1. Alex Klepa 9/7/12 1. Brendan Hay 9/7/12 1. Paul Graydon 9/7/12 1. Steve Layland 9/7/12 1. Thomas Dudziak 9/7/12 1. Felix Sheng 9/8/12 1. Sean Porter Sonian Inc 9/8/12 1. Josh Pasqualetto Sonian Inc 9/8/12 1. TJ Vanderpoel Sonian Inc 9/8/12 1. Justin Kolberg Sonian Inc 9/8/12 1. Decklin Foster Sonian Inc 9/8/12 1. Randall Morse 9/10/12 1. Pete Cheslock Dyn 9/10/12 1. Max Stepanov 9/11/12 1. Sean Gallagher 9/11/12 1. Autif Khan 9/11/12 1. Jacques Marneweck 9/12/12 1. William Herry 9/16/12 1. Pawel Kozlowski 9/16/12 1. Lawrence Gilbert 9/18/12 1. Bob Walker 9/18/12 1. Nathan Schimke 9/18/12 1. Graham Christensen 9/19/12 1. Alessandro Dal Grande The App Business 9/19/12 1. Saager Suhas Mhatre 9/23/12 1. Charles J Blaine 9/23/12 1. Andres de Barbara 9/23/12 1. Adam Vinsh 9/25/12 1. Elliot Murphy Pat Deegan, PhD & Associates, LLC 9/26/12 1. John Nishinaga Pat Deegan, PhD & Associates, LLC 9/26/12 1. Farley Knight Pat Deegan, PhD & Associates, LLC 9/26/12 1. Jon Sime OmniTI 9/26/12 1. Clinton Wolfe OmniTI 9/26/12 1. Theo Schlossnagle OmniTI 9/26/12 1. Robert Treat OmniTI 9/26/12 1. Adam DePue 9/26/12 1. Martin Contento 9/27/12 1. Milos Gajdos 9/27/12 1. Brad Gignac Rackspace 9/28/12 1. Robert Allen 9/30/12 1. Chia-liang Kao 10/1/12 1. Ketan Padegaonkar 10/2/12 1. Antti Puranen Reaktor Innovations 10/5/12 1. Stephen Crawley 10/7/12 1. Brad Bennet ZestFinance 10/8/12 1. Alexander Tamoykin ZestFinance 10/8/12 1. Lloyd Philbrook Firebelly Design 10/8/12 1. Nate Beaty Firebelly Design 10/8/12 1. John Skopis 10/8/12 1. Susan Potter 10/8/12 1. Jatinder Giri 10/10/12 1. Joan Touzet Cloudant, Inc. 10/11/12 1. Kyle Allan Riot Games 10/11/12 1. Jay Pipes 10/12/12 1. John Austin Page 10/15/12 1. Chuck Ha 10/15/12 1. David Dvorak Webtrends 10/15/12 1. Dipen Lad 10/15/12 1. Pascal Deschenes Nu Echo 10/16/12 1. Matthieu Vachon Nu Echo 10/16/12 1. Raymond Menard Nu Echo 10/16/12 1. Jean-Francois Alix Nu Echo 10/16/12 1. Mariano Cortesi 10/16/12 1. Alexander Phan 10/16/12 1. Jeff Siegel 10/16/12 1. William Milton 10/17/12 1. Mark Pimentel 10/18/12 1. Mike Gifford OpenConcept 10/18/12 1. Mike Mallett OpenConcept 10/18/12 1. Brian Loomis 10/20/12 1. Michael Saffitz Apptentive 10/20/12 1. Andrew Wooster Apptentive 10/20/12 1. Sky Kelsey Apptentive 10/20/12 1. Benjamin Michael Atkin Document Swarm, LLC 10/20/12 1. Andreas Gerauer 10/22/12 1. Steven Deaton 10/22/12 1. Cassiano Bertol Leal 10/23/12 1. Matt Towers 10/23/12 1. Mark Ayers 10/23/12 1. Anthony Leto 10/23/12 1. Marc Soda 10/24/12 1. Jerome D Harrington, II 10/24/12 1. Russell Stewart Egan 10/24/12 1. Paul Thomas 10/24/12 1. Chris Lundquist 10/24/12 1. Anton Orel 10/25/12 1. Marius Sturm 10/25/12 1. Dimitri David Boelaert-Roche 10/25/12 1. Matthew Serafin Horan 10/25/12 1. Vojtech Hyza 10/25/12 1. Steve Houser 10/26/12 1. Dmitry Zamaruev 10/29/12 1. James Hu 10/29/12 1. Laust Rud Jacobsen 10/29/12 1. Zo Obradovic 10/29/12 1. Dale Kiefling 10/29/12 1. Todd Fleisher 10/29/12 1. Karl Freeman 10/30/12 1. Johannes Becker 10/30/12 1. Fred Sadaghiani Sift Science, Inc. 10/31/12 1. Jeff Thompson 10/31/12 1. Stanislav Bogatyrev 10/31/12 1. Jonathan Peck FluxSauce 11/1/12 1. Jeffrey Borg 11/1/12 1. Guido Serra Rocket Internet GmbH 11/2/12 1. Sebastian Grewe 11/3/12 1. Chaoran Xie 11/3/12 1. Julien Duponchelle 11/4/12 1. Trae Robrock 11/5/12 1. Nikita Borzykh 11/6/12 1. Ilya Sher 11/6/12 1. Michael Fischer 11/6/12 1. Nathan Baxter 11/6/12 1. Simon Belluzzo 11/6/12 1. Ben Hartshorne 11/9/12 1. Matt Whiteley Engine Yard 11/9/12 1. Raul Naveiras 11/13/12 1. Eugene Wood 11/13/12 1. Javier Perez-Griffo Tapp 11/13/12 1. Dang Nguyen 11/13/12 1. Pablo Banos Tapp 11/13/12 1. Christian Hofer Tapp 11/13/12 1. Matthew Rogers 11/13/12 1. Stephen Lauck 11/13/12 1. Mark Van de Vyver Taqtiqa LLC 11/14/12 1. Thomas Carroll 11/14/12 1. Jon DeCamp Nordstrom, Inc 11/15/12 1. Doug Ireton Nordstrom, Inc 11/15/12 1. Kevin Moser Nordstrom, Inc 11/15/12 1. Justin Schumacher Nordstrom, Inc 11/15/12 1. Rob Cummings Nordstrom, Inc 11/15/12 1. Brandon Burton 11/20/12 1. Christopher Ferry 11/20/12 1. Michael Hood 11/22/12 1. Gavin Montague 11/23/12 1. Michal Lomnicki 11/24/12 1. Doc Walker 11/24/12 1. Nicolas Szalay 11/26/12 1. Terry Carr 11/26/12 1. Michael Myers Daptiv Solutions LLC 11/26/12 1. Shawn Neal Daptiv Solutions LLC 11/26/12 1. Chris Bobo Daptiv Solutions LLC 11/26/12 1. Ian Gantt Daptiv Solutions LLC 11/26/12 1. Alan Gray Daptiv Solutions LLC 11/26/12 1. Kishore Kumar S 11/26/12 1. Vincent Leraitre 11/27/12 1. Samuel Gerstein 11/27/12 1. John T Skarbek 11/27/12 1. Paul A Jungwirth 11/28/12 1. Thomas Hodder Lime Pepper Ltd 11/28/12 1. Warren Vosper Straydog Software, Inc. 11/29/12 1. Joshua Reedy 11/30/12 1. Mehmet Ali Akmanalp 11/30/12 1. Panagiotis Papadomitsos 11/30/12 1. Allan Espinosa 11/30/12 1. Brian Pitts 12/1/12 1. Elliot Kendall 12/3/12 1. Nathan Mische 12/3/12 1. Matthew Turney 12/3/12 1. Jay Flowers 12/4/12 1. Loic Antoine-Gombeaud 12/5/12 1. Joe Rodriguez 12/5/12 1. Kyle Scarmardo Fidelity Technology Group, LLC 12/6/12 1. Jon Lenzer Fidelity Technology Group, LLC 12/6/12 1. Chaoran Xie Fidelity Technology Group, LLC 12/6/12 1. Shalon Wood Fidelity Technology Group, LLC 12/6/12 1. David Crane 12/6/12 1. Takumi IINO 12/9/12 1. Tolleiv Nietsch 12/9/12 1. Benoit Caron 12/9/12 1. Mathieu Martin 12/10/12 1. Yung Giang 12/11/12 1. Fabian Ruff 12/11/12 1. Takeshi KOMIYA 12/11/12 1. Rafael Fonseca 12/12/12 1. Justin Campbell 12/12/12 1. Andrey Subbota 12/13/12 1. Jacob Ritorto 12/13/12 1. Pierre Ozoux 12/14/12 1. Shoaib Mushtaq 12/16/12 1. Arnold Krille bcs kommunikationslosungen 12/17/12 1. Rainer Dietz bcs kommunikationslosungen 12/17/12 1. Paul Diaconescu 12/17/12 1. Jake Davis Simple 12/17/12 1. Mike Ehlert Simple 12/17/12 1. Kevin Bringard 12/19/12 1. David Whittington 12/20/12 1. Raphael Valyi 12/20/12 1. Michael Klapper 12/23/12 1. Eli Klein 12/24/12 1. Andrew Lawrence Burns 12/26/12 1. Kevin Keane North County Tech Center, LLC 12/26/12 1. David Petzel 12/28/12 1. Thomas Robison 12/28/12 1. Keenan Brock 12/28/12 1. Stefan Borsje 12/29/12 1. Jon Galentine 12/29/12 1. Kiesia Croucher 12/31/12 1. Deeba Siddiqi 1/2/13 1. Steven De Coeyer 1/2/13 1. Yoni Yalovitsky Fewbytes 1/2/13 1. Alex Kiernan 1/3/13 1. Gilles Cornu 1/4/13 1. Gavin Mogan 1/4/13 1. Steven Lehrburger 1/4/13 1. Jordi Llonch 1/6/13 1. Nicolas Rycar 1/7/13 1. Andrew McCloud 1/7/13 1. Gregoire Seux Criteo 1/8/13 1. Brian Scott Emergent One 1/9/13 1. Mike Taczak Emergent One 1/9/13 1. Javier Segura Martinez 1/9/13 1. Warren Bain Ninefold Pty Limited 1/9/13 1. Shaun Domingo Ninefold Pty Limited 1/9/13 1. Toby Hede Ninefold Pty Limited 1/9/13 1. Paul Handly DecisionDesk 1/13/13 1. Eric Neuman DecisionDesk 1/13/13 1. Will Olbrys DecisionDesk 1/13/13 1. Thomas Bouve 1/14/13 1. Kevin Reedy Belly Inc 1/15/13 1. Craig Ulliott Belly Inc 1/15/13 1. Jay OConnor Belly Inc 1/15/13 1. Courtenay Gasking 1/16/13 1. James Dabbs 1/16/13 1. Jerry Cattell 1/16/13 1. Jon Webb 1/17/13 1. Hendrik Volkmer cloudbau Gmbh 1/18/13 1. Thomas Kadauke cloudbau Gmbh 1/18/13 1. Martin Bosner cloudbau Gmbh 1/18/13 1. Christopher Laco 1/18/13 1. Haggai Philip Zagury 1/19/13 1. Eric Sigler 1/20/13 1. Yves Vogl 1/21/13 1. Yukihiko Sawanobori HiganWorks LLC 1/21/13 1. Seth Larson 1/21/13 1. Ben Langeld 1/21/13 1. Dan Midwood 1/22/13 1. Peter Pouliot 1/22/13 1. Alexander Bondarev 1/23/13 1. Ben Dean Ontario Systems 1/23/13 1. Keith Shook Ontario Systems 1/23/13 1. Lucas Heinlen Ontario Systems 1/23/13 1. Kyle Michel Ontario Systems 1/23/13 1. Brice Oliver Ontario Systems 1/23/13 1. David Rogers Lytro, Inc. 1/23/13 1. Alvin Lai Lytro, Inc. 1/23/13 1. Anuj Biyani Lytro, Inc. 1/23/13 1. Tiffany Russo Lytro, Inc. 1/23/13 1. Craig Brunner Lytro, Inc. 1/23/13 1. Mugur Marculescu Lytro, Inc. 1/23/13 1. Tom Hanley Lytro, Inc. 1/23/13 1. Thomas Massmann 1/25/13 1. Ankit Shah 1/25/13 1. Josh Mahowald 1/25/13 1. Christopher Bandy 1/25/13 1. Mal Graty 1/27/13 1. Vaidas Jablonskis 1/27/13 1. Simon McCartney 1/28/13 1. Jake Davis 1/29/13 1. Sean Kilgore 1/29/13 1. Scott Lampert 1/29/13 1. Michael Frick 1/29/13 1. Kevin Duane 1/30/13 1. Ryan Geyer 1/30/13 1. George Hafiz 1/30/13 1. Eric Pardee Atlas Digital, LLC 1/30/13 1. Jaroslaw Zmudzinski Grupa Allegro Sp. z o.o. 1/31/13 1. Alexey Polovinkin 2/1/13 1. Malte Swart 2/2/13 1. Jon Burgess 2/3/13 1. Daniel Hahn 2/4/13 1. Maxime Brugidou Criteo 2/4/13 1. Gareth David Rushgrove 2/4/13 1. Michael Conigliaro 2/4/13 1. James Kessler 2/4/13 1. Lukasz Jagiello 2/4/13 1. Mischa Taylor 2/4/13 1. Mervyn Hammer Workday Inc. 2/5/13 1. David Radcliffe 2/5/13 1. Alexander Titov 2/5/13 1. Buntaro OKADA 2/5/13 1. Alexey Kalinin 2/5/13 1. Adam Cownoble 2/6/13 1. Josh Behrends Webtrends 2/6/13 1. Mark Shlimovich 2/6/13 1. Jahn Bertsch 2/7/13 1. Sergio Rodriguez 2/7/13 1. Timur Batyrshin 2/8/13 1. Samuel Cooper 2/8/13 1. Ignacy Kasperowicz 2/8/13 1. Ranjib Dey 2/8/13 1. Kirill Kouznetsov 2/8/13 1. Jordan Hagan 2/8/13 1. Alexander Coles 2/9/13 1. Michael Grosser 2/10/13 1. Michael Goetz 2/11/13 1. Patrick Humpal 2/11/13 1. Nate Smith 2/13/13 1. Martin Eigenbrodt 2/13/13 1. Russell Cloran 2/13/13 1. John Gabriel McArthur 2/13/13 1. Jessica Bourne 2/13/13 1. Darren Haken 2/14/13 1. Rick Polk 2/14/13 1. Eric Berg 2/14/13 1. Andrew Williams Intoximeters 2/15/13 1. Matthew Follett Intoximeters 2/15/13 1. Brendan O'Donnell 2/16/13 1. Igor Serebryany Airbnb 2/17/13 1. Lukas Reinfurt 2/17/13 1. Adam Gross 2/17/13 1. Giorgio Valoti 2/19/13 1. Nathan Beyer Cerner Corporation 2/19/13 1. Patrik Stenmark Valtech AB 2/20/13 1. Evgeny Zislis 2/20/13 1. Luyi Wang 2/20/13 1. Jason Schadel AWeber Communications 2/20/13 1. David Kinzer 2/22/13 1. Achim Rosenhagen 2/23/13 1. Doug Cole 2/23/13 1. Matthew Wright 2/25/13 1. Jasper Lievisse Adriaanse 2/25/13 1. Julien Vehent AWeber Communications 2/25/13 1. Ryan Steele AWeber Communications 2/25/13 1. Brian K. Jones AWeber Communications 2/25/13 1. Benjamin Krein AWeber Communications 2/25/13 1. Cliff Erson 2/26/13 1. Booker Bense 2/27/13 1. Charity Majors 2/27/13 1. Jared Russell 2/27/13 1. Iiro Uusitalo 2/27/13 1. Ryan Walker Rackspace 2/28/13 1. Todd Bushnell 2/28/13 1. BK Box 2/28/13 1. Scott Stout 3/1/13 1. Steffen Gebert 3/3/13 1. John Cheng 3/3/13 1. Jeremy Olliver 3/3/13 1. Alexander Papaspyrou adesso mobile solutions GmbH 3/4/13 1. Stoyan Stoyanov adesso mobile solutions GmbH 3/4/13 1. Andreas Thielen adesso mobile solutions GmbH 3/4/13 1. Yves Vogl adesso mobile solutions GmbH 3/4/13 1. Brett Richardson 3/5/13 1. Kevin Nuckolls Banno, LLC 3/5/13 1. Nic Grayson Banno, LLC 3/5/13 1. Luke Amdor Banno, LLC 3/5/13 1. Danny Lockard Banno, LLC 3/5/13 1. Thomas Wallace 3/5/13 1. Ptah Dunbar 3/6/13 1. Jonathan Asghar 3/6/13 1. Brandon Sanders AboutUs 3/7/13 1. Aaron Brown 3/7/13 1. Paul Oliver 3/7/13 1. Alan Willis Riot Games 3/7/13 1. Dimitrios Verraros 3/8/13 1. Arangamanikkannan Manickam 3/8/13 1. David Bresnick 3/8/13 1. Brett Weaver 3/8/13 1. Drew Flower 3/8/13 1. Charles Gregory Willis 3/8/13 1. Owain Perry 3/9/13 1. Alexander Sakharchuk 3/9/13 1. Ameir Abdeldayem 3/9/13 1. Robert Choi 3/10/13 1. Gemini Agalo-os 3/10/13 1. Alexander Galato 3/11/13 1. Gabriel Klein 3/11/13 1. Eric Richardson 3/12/13 1. Steven Barre 3/13/13 1. Paul Rossman Google, Inc 3/14/13 1. Riccardo Carlesso Google, Inc 3/14/13 1. Benson Kalahar Google, Inc 3/14/13 1. Rick Wright Google, Inc 3/14/13 1. Eric Johnson Google, Inc 3/14/13 1. Aaron Rice 3/15/13 1. Radoslaw Gruchalski 3/15/13 1. Matt Gleeson Atlassian 3/18/13 1. Will DeHaan Atlassian 3/18/13 1. Gabor Nagy 3/18/13 1. Bryan Stearns 3/18/13 1. Jose Diaz-Gonzalez 3/19/13 1. Jean-Francois Theroux 3/19/13 1. Christopher Stolfi 3/19/13 1. Darrell Nash 3/19/13 1. Sean Kane 3/20/13 1. Patrick Leckey 3/20/13 1. Michael Rose 3/20/13 1. Pitr Vernigorov 3/20/13 1. Capen Brinkley 3/20/13 1. Tima Maslyuchenko 3/21/13 1. Yvo van Doorn 3/21/13 1. Tobias Wilken cloudControl GmbH 3/21/13 1. Mateusz Korszun cloudControl GmbH 3/21/13 1. Eric Chaves 3/21/13 1. Peter Donald 3/21/13 1. Remon Oldenbeuving 3/22/13 1. Philip Cristiano 3/22/13 1. Chris Streeter 3/24/13 1. Kenneth Vetergaard 3/25/13 1. Gert Kremer 3/25/13 1. Peter de Rujiter Springest 3/25/13 1. Maarten Hoogendoorn Springest 3/25/13 1. Daniel Ryan 3/25/13 1. Matthieu Launay Criteo 3/26/13 1. Jean-Baptiste Note Criteo 3/26/13 1. Daniel Koepke 3/26/13 1. Neil Schelly Dyn, Inc. 3/26/13 1. David Miller Dyn, Inc. 3/26/13 1. Bill Young Dyn, Inc. 3/26/13 1. Phillip Goldenburg 3/27/13 1. Faiz Kazi 3/31/13 1. James Tucker Google, Inc 4/1/13 1. Marco Delaurenti Google, Inc 4/1/13 1. Sebastien Roccaserra 4/4/13 1. Chendil Kumar Manoharan 4/4/13 1. Jeremy Mauro Criteo 4/5/13 1. Joshua Levine 4/5/13 1. Harley Alaniz Lookout, Inc. 4/6/13 1. Hiroaki Nakamura 4/8/13 1. Leif Madsen Thinking Phone Networks, Inc. 4/8/13 1. Chris Sibbitt Thinking Phone Networks, Inc. 4/8/13 1. Christian Brideau Thinking Phone Networks, Inc. 4/8/13 1. Travis Hein Thinking Phone Networks, Inc. 4/8/13 1. Ming Chan 4/8/13 1. Jamie Alessio 4/9/13 1. Daichi Kamemoto 4/10/13 1. David Groulx 4/10/13 1. TAKEUCHI Go 4/11/13 1. Alex Dergachev Evolving Web Inc 4/11/13 1. Suzanne Kennedy Evolving Web Inc 4/11/13 1. Sander Botman 4/15/13 1. Julio Arias 4/15/13 1. Alexander Wenzowski 4/16/13 1. Pete Bristow 4/16/13 1. Thorsten Klein 4/16/13 1. Qingkun Liu 4/17/13 1. Jonathan Cobb Tout Industries 4/18/13 1. Matt Lanier Tout Industries 4/18/13 1. Felix Roeser Tout Industries 4/18/13 1. Tom Hallett Tout Industries 4/18/13 1. Sam Gipe Tout Industries 4/18/13 1. Brandon Turner 4/20/13 1. Mathias Lafeldt 4/20/13 1. Matt Bower 4/21/13 1. Zachary Patten Lookout, Inc. 4/22/13 1. Jim Hopp Lookout, Inc. 4/22/13 1. Zsolt Dollenstein 4/23/13 1. Andrew Hollingsworth 4/24/13 1. Benjamin Krueger 4/24/13 1. Matt Thompson Rackspace 4/25/13 1. Hugh Saunders Rackspace 4/25/13 1. Harry Harrington Rackspace 4/25/13 1. Andy McCrae Rackspace 4/25/13 1. Chris Laco Rackspace 4/25/13 1. Bett Campbell Rackspace 4/25/13 1. Zack Feldstein Rackspace 4/25/13 1. Drew Rothstein 4/26/13 1. Gaetano Santonastaso 4/26/13 1. Tom Molin 4/26/13 1. James Thompson 4/26/13 1. Adam Stegman 4/26/13 1. Robert Rehberg 4/26/13 1. Amy Marco 4/27/13 1. Chris Fordham 4/28/13 1. Paolo Negri 4/29/13 1. Jeremy Katz 4/29/13 1. Troy Ready 4/30/13 1. Jameson Lee 4/30/13 1. Mehdi Lahmam 5/1/13 1. Chandrashekar Seenappa 5/1/13 1. Sander van Harmelen Schuberg Philis 5/1/13 1. Matthew Hooker Simple 5/1/13 1. Robert Roose 5/1/13 1. Peter Jihoon Kim Irrational Industries 5/1/13 1. Daniel Dao Quang Ming Irrational Industries 5/1/13 1. Arun K Thampi Irrational Industries 5/1/13 1. Paul Paradise Socrata, Inc 5/1/13 1. Chris Armstrong Socrata, Inc 5/1/13 1. David Chadwick Gibbons 5/1/13 1. Chulki Lee Aspera, Inc 5/1/13 1. Christopher Markle Aspera, Inc 5/1/13 1. Jason Rutherford 5/1/13 1. Peter Norton 5/2/13 1. Walter Dal Mut 5/2/13 1. Eric Sorenson 5/2/13 1. Derrick Bryant 5/3/13 1. Avrohom Katz 5/3/13 1. Robert Postill 5/3/13 1. Gabe Mulley Hadapt, Inc 5/3/13 1. Daniel Schauenberg 5/4/13 1. James Turnbull 5/6/13 1. Seren Thompson 5/7/13 1. Solvi Pall Asgeirsson 5/7/13 1. Dale Ragan Moncai 5/7/13 1. Eric Blevins Moncai 5/7/13 1. Kevin Landreth 5/8/13 1. Ka-Wing Tam 5/10/13 1. John Bellone Jr. 5/10/13 1. Paolo Agostinetto 5/11/13 1. Robert Coleman 5/11/13 1. Ahmad Jemai 5/13/13 1. Manuel Ryan 5/13/13 1. Ben Somers 5/13/13 1. Nate Fox 5/13/13 1. Simon Coffey 5/14/13 1. Andrea Bernardo Ciddio 5/14/13 1. Maxim Doucet 5/14/13 1. Martin Klein 5/14/13 1. Jeremiah Wuenschel Yahoo Inc. 5/14/13 1. Deven Panchal Yahoo Inc. 5/14/13 1. Jeff Parrish Yahoo Inc. 5/14/13 1. Venkat Venkataraju Yahoo Inc. 5/14/13 1. Chris Wing Yahoo Inc. 5/14/13 1. Ittai Shadmon Yahoo Inc. 5/14/13 1. Itsik Figenblat Yahoo Inc. 5/14/13 1. Matthew Mencel 5/14/13 1. Olaf Heydorn 5/16/13 1. Bryan Stenson 5/16/13 1. Holger Protzek 5/16/13 1. Nilesh Bairagi 5/16/13 1. Matt Clark 5/16/13 1. Jan Nikolai Trzeszkowski 5/17/13 1. Bernhard K. Weisshuhn 5/17/13 1. Chris Reid 5/17/13 1. Morgan Blackthorne 5/20/13 1. Ken Miles 5/20/13 1. James "Jim" Rosser, IV Texas A&M University College of Architecture 5/20/13 1. Derek Groh Texas A&M University College of Architecture 5/20/13 1. Benjamin Liles Texas A&M University College of Architecture 5/20/13 1. Kyle Morgan Rackspace 5/20/13 1. Wilfred Hughes 5/21/13 1. Jeff Anderson 5/21/13 1. Brian Hatfield 5/21/13 1. Guillermo Carrasco Hernandez 5/21/13 1. James Sulinksi MoPub 5/21/13 1. Haydn Dufrene MoPub 5/21/13 1. Rob McQueen MoPub 5/21/13 1. Chris Snook MoPub 5/21/13 1. Christophe Arguel 5/22/13 1. Sean Nolen 5/22/13 1. Chetan Sarva 5/24/13 1. Justin Ryan Onelogin, Inc 5/24/13 1. Stephen Touset Onelogin, Inc 5/24/13 1. Marcelo Serpa Onelogin, Inc 5/24/13 1. Nelson Enzo Onelogin, Inc 5/24/13 1. Elan Ruusamäe 5/24/13 1. Marco Betti 5/26/13 1. Jonathan Hitchcock Yola 5/27/13 1. Stefano Rivera Yola 5/27/13 1. Adrian Moisey Yola 5/28/13 1. Doug Beck Yola 5/28/13 1. John Tran 5/28/13 1. Jesse Ahrens CopperEgg 5/28/13 1. Ross Dickey CopperEgg 5/28/13 1. Scott Johnson CopperEgg 5/28/13 1. Eric Anderson CopperEgg 5/28/13 1. Benjamin Bytheway 5/28/13 1. Tehmasp Chaudhri 5/28/13 1. Russell Teabeault 5/28/13 1. Tim Ray 5/29/13 1. Gavin Roy MeetMe, Inc 5/30/13 1. Peter Eisentraut MeetMe, Inc 5/30/13 1. Jennifer Fountain MeetMe, Inc 5/30/13 1. Kenny Furguson MeetMe, Inc 5/30/13 1. Michael Glaesemann MeetMe, Inc 5/30/13 1. Edward Robinson 6/1/13 1. Baldur Gudbjornsson 6/1/13 1. Jeffrey Jones 6/1/13 1. Louis-Philippe Perron 6/5/13 1. Victor Sollerhed 6/5/13 1. Alvin Yik-ning Liang 6/5/13 1. Cassiano Morgado de Aquino 6/5/13 1. Brett Graves 6/6/13 1. Mattew Collinge 6/6/13 1. Nick Silkey Rackspace 6/6/13 1. Chris Stephan 6/6/13 1. Peter Fern 6/6/13 1. Kevin Bridges 6/6/13 1. Peter Halliday 6/7/13 1. Felix Bunemann 6/9/13 1. Nanuk Krinner 6/10/13 1. Robert Dyer 6/10/13 1. Anthony Scalisi 6/11/13 1. Ryan Hass 6/11/13 1. Brad Beam 6/12/13 1. Ean Rollings 6/13/13 1. Ken Robertson 6/13/13 1. Tony Chong 6/13/13 1. Oliver Nicolaas Ponder 6/14/13 1. Mikhail Kolesnik 6/16/13 1. Sergey Khaladzinksi 6/16/13 1. Tucker DeWitt 6/16/13 1. Thomas Meeus 6/17/13 1. Lin Lin 6/17/13 1. Omar Vargas 6/17/13 1. Domonkos Tomcsanyi Boadree Innovations Kft. 6/17/13 1. Prashant Nadarajan 6/18/13 1. Eohyung Lee 6/18/13 1. David Albrecht 6/18/13 1. Nicholas Downs 6/19/13 1. Mike Devine 6/19/13 1. Thomas Cate Rackspace 6/18/13 1. Ryan Richard Rackspace 6/18/13 1. Matthew Thode Rackspace 6/18/13 1. Chris Aumann 6/20/13 1. Eric Wunderlin 6/21/13 1. Georgi Markov 6/21/13 1. Sjoerd Mulder 6/21/13 1. Benjamin Knauss 6/21/13 1. Erik Gustavson Bitium, Inc 6/21/13 1. Prashant Nadarajan Bitium, Inc 6/21/13 1. Walter Schiessberg 6/24/13 1. Vasily Mikhaylichenko 6/24/13 1. William Anthony Rhodes Jr 6/24/13 1. Adam Wayne 6/24/13 1. Max Manders Cloudreach 6/24/13 1. Justin Stallard 6/25/13 1. Nelson Chen 6/26/13 1. Eric Sproul OmniTI 6/19/13 1. Andrew Macgregor 6/27/13 1. Jeffrey Damick 6/26/13 1. Daniel Williams 6/27/13 1. Bruce Li 6/27/13 1. Satoshi Akama 6/28/13 1. Dave Stern 6/28/13 1. David Andrew 6/28/13 1. Anthony Burns 6/29/13 1. Michael Ballantyne 6/28/13 1. Ewan McDougall 7/1/13 1. Matt Patterson 7/1/13 1. Ivan Puzyrevskiy 7/2/13 1. Stefano Tortarolo 7/3/13 1. Christopher MacNaughton 7/3/13 1. Skye Book 7/3/13 1. Mark Butcher 7/4/13 1. Nick Morgan Heart of Sales LLC DBA Ace of Sales 7/4/13 1. Ryan Schlesinger Heart of Sales LLC DBA Ace of Sales 7/4/13 1. Kevin Patrick Pullin II 7/5/13 1. Colin Woodcock NetSrv Consulting Ltd 7/7/13 1. Joshua Tobin 7/8/13 1. James Cuzella 7/8/13 1. Michael John Huot Jr. 7/9/13 1. William Albenzi 7/9/13 1. Matas Veitas 7/10/13 1. NagaLakshmi N 7/10/13 1. Evan Michael Kinney 7/11/13 1. Adam Lane 7/11/13 1. Rafael Colton 7/11/13 1. Julien Phalip 7/11/13 1. Matthew Savage 7/11/13 1. Koseki Kengo 7/11/13 1. Gregory Palmier 7/12/13 1. Alain O'Dea 7/12/13 1. Peter Hoellig PROS, Inc. a Delaware Corporation 7/12/13 1. Vladimir Skubriev 7/12/13 1. Ryan Stephens AURIN Project -Faculty of Architecture, Building and Planning 7/12/13 1. Martin Tomko AURIN Project -Faculty of Architecture, Building and Planning 7/12/13 1. Christopher Bayliss AURIN Project -Faculty of Architecture, Building and Planning 7/12/13 1. Peter Ellingsen AURIN Project -Faculty of Architecture, Building and Planning 7/12/13 1. Chris Pettit AURIN Project -Faculty of Architecture, Building and Planning 7/12/13 1. Jörg Thalheim 7/12/13 1. Zac Hallett 7/12/13 1. Emanuele Zattin 7/11/13 1. Daniel Steen 7/12/13 1. Ronnie Taylor 7/13/13 1. Danny Guinther 7/14/13 1. Michael Vitale 7/16/13 1. Nicholas Ethier 7/16/13 1. Steve Poe Onlife Health Inc 7/17/13 1. Craig Menning 7/17/13 1. Antoni Baranski Roblox Inc. 7/17/13 1. John Landahl 7/18/13 1. Rudy Grigar 7/19/13 1. Katsuma Ito 7/19/13 1. Andrew Wyatt Onlife Health Inc 7/19/13 1. Naoki AINOYA 7/21/13 1. David Giesberg 7/21/13 1. Luke Hoschke 7/21/13 1. Myles Steinhauser 7/22/13 1. Kyle Rames Rackspace 7/23/13 1. Chris Snell Rackspace 7/23/13 1. Jason Roelofs 7/23/13 1. Hugo Trippaers 7/24/13 1. Mark Friedgan 7/24/13 1. Matthew Hopkins 7/24/13 1. Eddie Zaneski 7/24/13 1. Maxwell Robett Dietz 7/24/13 1. Simon Robson 7/25/13 1. Dan Bachelder 7/26/13 1. Matthew Farmer 7/26/13 1. Thomas Neal Cravey 7/26/13 1. Ross Timson 7/29/13 1. Donald Stufft 7/28/13 1. Gilles Cornu 7/28/13 1. Kenichi Saita 7/28/13 1. Ivan Tanev 7/27/13 1. Chris Gallimore 7/26/13 1. Sonny Garcia 7/26/13 1. Alexis Midon 7/26/13 1. Brandon Henry 7/29/13 1. Jordan Wesolowski 7/29/13 1. Christopher Brinley 7/29/13 1. Nimesh Subramanian Cerner 7/29/13 1. Eric Hartmann 7/29/13 1. Kevin Rochford 7/30/13 1. Jon San Miguel 7/30/13 1. Tommy Fotak 7/31/13 1. Nicholas Hatch 8/1/13 1. Raf Geens 8/6/13 1. Thomas Bell 8/6/13 1. Braden Wright 8/6/13 1. Johnny Tan 8/6/13 1. Yvonne Beumer Cloudreach 8/12/13 1. Ben House 8/9/13 1. Joe Fitzgerald 8/12/13 1. Peter Hessler 8/13/13 1. Nicholas Russell 8/13/13 1. Brian Golf 8/13/13 1. Adam Kunk 8/13/13 1. Sandy Vanderbleek 8/13/13 1. Lance French 8/13/13 1. Jeff Hagadom 8/14/13 1. George Miranda 8/16/13 1. Evan Gilman 8/19/13 1. Nenad Petronijevic 8/19/13 1. Daniel Spilker CoreMedia AG 7/31/13 1. Felix Simmendinger CoreMedia AG 7/31/13 1. Eike Thienemann-Dehde CoreMedia AG 7/31/13 1. Christopher Hass CoreMedia AG 7/31/13 1. Daniel Zabel CoreMedia AG 7/31/13 1. Ryan Munson Taos Mountain, Inc. 7/31/13 1. Tim Fischbach 8/20/13 1. Chance Zibolski 8/20/13 1. Kazuki Akamine 8/20/13 1. David Wittman 8/20/13 1. Brian Whipple PROS, Inc. a Delaware Corporation 8/14/13 1. Michael Jensen PROS, Inc. a Delaware Corporation 8/14/13 1. Asanka Samaraweera PROS, Inc. a Delaware Corporation 8/14/13 1. Christian Vozar Belly Inc 8/20/13 1. Darby Frey Belly Inc 8/20/13 1. Matthew Herscovitch Identive Group 8/20/13 1. Mark Butcher Identive Group 8/20/13 1. Luke Bradbury University of Derby 8/28/13 1. Dan Webb University of Derby 8/28/13 1. Alastair Firth 8/23/13 1. Jesse Adams 8/28/13 1. Matt Alexander 8/28/13 1. Jason Vanderhoof 8/26/13 1. Lianping Chen 8/26/13 1. Christoph Bunte 8/26/13 1. Yvonne Lam 8/28/13 1. Mark Cornick TeamSnap 8/29/13 1. Justin Clarke Social Ally Pty Ltd 8/29/13 1. H Wade Minter TeamSnap 8/29/13 1. Kyle Ries TeamSnap 8/29/13 1. Phillip Hutchins 8/30/13 1. Artem Kornienko 8/30/13 1. Ulf Mansson Recorded Future 9/2/13 1. Sam Crang 8/31/13 1. Jorge Acosta Goszczynski 9/3/13 1. Lysenko Kostiantyn 9/4/13 1. Phil Sturgeon 9/4/13 1. Kamil Bednarz 9/4/13 1. Travis Petticrew 9/4/13 1. Peter Walz 8/29/13 1. Jeffrey Utter 9/4/13 1. Martin Cozzi 9/4/13 1. Andrew Thompson 9/4/13 1. Thomas von Schwerdtner 9/4/13 1. William Dierkes 9/4/13 1. Robin Ricard 9/5/13 1. Ben Longden 9/5/13 1. Alex Denvir Protec Innovations Ltd. 9/5/13 1. Martin Meredith Protec Innovations Ltd. 9/5/13 1. Phil Thompson Protec Innovations Ltd. 9/5/13 1. Alejandro Blanco 9/5/13 1. Adrian Moisey 9/5/13 1. Alex Zorin 9/5/13 1. Robert Schulze 9/5/13 1. Wei Liang 9/5/13 1. Jon Torresdal 9/5/13 1. Sergii Golovatiuk 9/6/13 1. Mark O'Keefe 9/6/13 1. Brint O'Heam 9/6/13 1. Jim Myhrberg 9/7/13 1. James FitzGibbon 9/8/13 1. Isbaran Akcayir 9/9/13 1. Sylvain Tisso Ecodev Sarl 9/9/13 1. Fabien Udriot Ecodev Sarl 9/9/13 1. Adrien Crivelli Ecodev Sarl 9/9/13 1. Yoshanda Shin 9/10/13 1. Christo De Lange 9/10/13 1. Arthur Freyman 9/10/13 1. Martin Walton 9/10/13 1. Matthew Brennan 9/10/13 1. Luis Ricardo Malheiros 9/11/13 1. Amir Kadivar 9/11/13 1. Davanum Srinivas 9/12/13 1. Jesse Pretorius 9/12/13 1. Denis Corol 9/12/13 1. Yevgen Kovalienia 9/12/13 1. Ben Hines 9/13/13 1. Muneyuki Noguchi 9/14/13 1. Myers Carpenter 9/16/13 1. Mathieu Allaire 9/16/13 1. Michael Stucki 9/17/13 1. Kevin Webster 9/17/13 1. Conor McDermottroe 9/17/13 1. Naoya Nakazawa 9/17/13 1. Mark Gibbons Nordstrom 9/17/13 1. Matthew Moore 9/17/13 1. Ingo Kampe kreuzwerker GmbH 9/18/13 1. Robert Conrad kreuzwerker GmbH 9/18/13 1. Daniel Meisen kreuzwerker GmbH 9/18/13 1. Alexander Dall kreuzwerker GmbH 9/18/13 1. Jan Nabbefeld kreuzwerker GmbH 9/18/13 1. Joern Barthel kreuzwerker GmbH 9/18/13 1. Harvey Bandana Nordstrom 9/18/13 1. Ben Holley 9/18/13 1. Yuji Takaesu 9/18/13 1. Colin Burn-Murdoch 9/20/13 1. Andrius Marcinkevicius 9/20/13 1. Alan Bryan Central Desktop 9/18/13 1. Craig Lewis Central Desktop 9/18/13 1. Saku Laitinen Siili Solutions 9/19/13 1. Denis Barishev Twiket LTD 9/23/13 1. Sam Orlov Twiket LTD 9/23/13 1. Jarek Gawor IBM 9/23/13 1. Anthony Elder IBM 9/23/13 1. Michael C Thompson IBM 9/23/13 1. Jeremy Hughes IBM 9/23/13 1. Igor Rodionov 9/23/13 1. Gabor Garami 9/24/13 1. Iulia Banghea 9/24/13 1. Paul Dunnavant 9/24/13 1. Alex Heneveld Cloudsoft Corp. 9/25/13 1. Jordi Massaguer Pla 9/25/13 1. Richard Downer Cloudsoft Corp. 9/25/13 1. Roger Hu 9/25/13 1. Dominic St-Jacques 9/25/13 1. Mahmoud Abdelkader 9/26/13 1. Takahiro Himura 9/26/13 1. Aled Sage Cloudsoft Corp. 9/25/13 1. Andrew Kennedy Cloudsoft Corp. 9/25/13 1. Sam Corbett Cloudsoft Corp. 9/25/13 1. Trevor Leybourne MYOB NZ Limited 9/26/13 1. Greg Zapp MYOB NZ Limited 9/26/13 1. Bo Ma MYOB NZ Limited 9/26/13 1. Dmitry Lavrinenko 9/27/13 1. Vitaly Shishlyannikov 9/27/13 1. Jonathan Mickle 9/27/13 1. Scott Hain Jr 9/27/13 1. Ethan Fremen 9/29/13 1. Daniel O'Conner 9/29/13 1. Bill Wiens 9/30/13 1. Jeroen Grusebroek Mollie B.V. 9/30/13 1. Morton Jonuschat 9/30/13 1. Bentrand Paquet 10/1/13 1. Christoph Hartmann 10/1/13 1. Okezie Eze 10/1/13 1. Christopher Dwan 10/1/13 1. Carl Schmidt Unbounce 10/1/13 1. Aaron Oman Unbounce 10/1/13 1. Chris Spicer Unbounce 10/1/13 1. Jimmy Zheng Unbounce 10/1/13 1. Josh Blancett 10/1/13 1. Stephen Romney Shutl Ltd. 10/2/13 1. James Wilford Shutl Ltd. 10/2/13 1. Sam Phillips Shutl Ltd. 10/2/13 1. Yomi Colledge Shutl Ltd. 10/2/13 1. Marco Nenciarini 10/2/13 1. Jake Herbst 10/2/13 1. Kawahara Masashi 10/2/13 1. Ilan Rabinovitch Ooyala, Inc. 10/3/13 1. Garry Polley 10/3/13 1. Akshay Karle 10/3/13 1. Pascal Gelinas Nu Echo 10/3/13 1. Salim Semaoune 10/3/13 1. August Schwer 10/4/13 1. Sam Adams 10/7/13 1. Niels Kristensen 10/7/13 1. Aliaksei Kliuchnikau 10/7/13 1. Theofilos Papapanagiotou 10/7/13 1. Mike Rossetti Our Film Festival (dba Fandor) 10/7/13 1. Victor Lin 10/8/13 1. Tyler Kellen 10/8/13 1. Baba Buehler 10/8/13 1. Matt Clifton 10/8/13 1. Daniel Babel Ooyala, Inc. 10/9/13 1. Jurgen Philippaerts Ooyala, Inc. 10/9/13 1. Josh Toft Ooyala, Inc. 10/9/13 1. Tobias Maier BauCloud GmbH 10/9/13 1. Ryota Arai 10/9/13 1. Kyle Kelley 10/10/13 1. Jaroslav Barton 10/10/13 1. Sam Pointer 10/10/13 1. Sebastian Guevara 10/10/13 1. Paul Welch Squaremouth Inc 10/10/13 1. Peter Georgantas 10/10/13 1. Karla Jacobsen 10/10/13 1. Ryan S Brown 10/10/13 1. Richard Manyanza 10/11/13 1. Guilhem Lettron Optiflows 10/11/13 1. Ludovic Havel Optiflows 10/11/13 1. Jean Rouge 10/11/13 1. Devon Jones 10/11/13 1. Matthew Kasa 10/11/13 1. James Moorhouse 10/12/13 1. Christopher Grim 10/12/13 1. Ryan Frantz 10/14/13 1. Shrikant Patnaik General Sensing LTD 10/10/13 1. Aaron Valade General Sensing LTD 10/10/13 1. Chris Jerdonek 10/13/13 1. EJ Ciramella Rapid7 10/14/13 1. Brandon Turner Rapid7 10/14/13 1. Chris Smtih Rapid7 10/14/13 1. Ben Tomasini 10/14/13 1. Evan Todd 10/15/13 1. Alex Shadrin 10/15/13 1. Mart Karu 10/15/13 1. Russell Cardullo 10/15/13 1. John Tran 10/15/13 1. Alex Koch 10/15/13 1. Jonathan Regeimbal 10/15/13 1. James Walker 10/16/13 1. Justin Dugger 10/16/13 1. Dustin Collins 10/16/13 1. Matthew Boedicker 10/17/13 1. John Deatherage 10/17/13 1. Aaron Jensen 10/17/13 1. Olksandr Slynko 10/17/13 1. Emanuele Rocca 10/18/13 1. Antonio Fernandez Vara 10/18/13 1. Mickhail Zholobov 10/18/13 1. Daniel Wallace Rackspace 10/18/13 1. Christian Fischer 10/18/13 1. Silviu Dicu 10/18/13 1. William Pietri 10/19/13 1. Daniel Tracy 10/21/13 1. Ashish Shinde 10/22/13 1. Mathew Hoyle Deployable LTD 10/22/13 1. Leonardo Leite 10/22/13 1. Michael Dore 10/22/13 1. Hannes Van De Vreken 10/22/13 1. Mevan Samaratunga 10/22/13 1. Adam Enger 10/22/13 1. Peter Jönsson Klarna 10/23/13 1. Carl Loa Odin Klarna 10/23/13 1. Olle Lundberg Klarna 10/23/13 1. Pat Downey 10/24/13 1. Max Lincoln 10/24/13 1. Pavel Brylov 10/24/13 1. Sam Clements 10/25/13 1. Gabriel Mazetto 10/25/13 1. Cory Gunterman Nike, Inc. 10/25/13 1. Justin Redd Nike, Inc. 10/25/13 1. Dave Palomino Nike, Inc. 10/25/13 1. Tom Luce Nike, Inc. 10/25/13 1. Shawn Turpin Nike, Inc. 10/25/13 1. Ed Tretyakov 10/27/13 1. Frank Breedijk Schuberg Phillis B.V. 10/28/13 1. Henry Finucane 10/30/13 1. Thomas de Grenier de Latour 10/30/13 1. Andrey Chernih 10/30/13 1. Matt Wormley 10/30/13 1. Maciej Galkiewicz 10/31/13 1. Makiko Nomura 11/1/13 1. Cheah Chu Yeow Irrational Industries, Inc. 11/1/13 1. Christian Paredes Irrational Industries, Inc. 11/1/13 1. Rafael Kolless 11/2/13 1. Zsolt Takacs 11/2/13 1. Tobias Schmidt SoundCloud Ltd. 11/5/13 1. Frederick Jaeckel SoundCloud Ltd. 11/5/13 1. Matthias Rampke SoundCloud Ltd. 11/5/13 1. Daman Yang SoundCloud Ltd. 11/5/13 1. Ben Kochie SoundCloud Ltd. 11/5/13 1. Alexander Grosse SoundCloud Ltd. 11/5/13 1. Allan Beaufour Project Florida 11/5/13 1. Jow Crobak Project Florida 11/5/13 1. Derek Groh 11/5/13 1. David Shawley 11/5/13 1. Jason Faulkner 11/5/13 1. Cheryl Ainoa Intuit 11/6/13 1. Thomas Bishop Intuit 11/6/13 1. Jeffrey Mendoza 11/6/13 1. Munirathnam Srikanth ComputeNext 11/6/13 1. Sergio Patino 11/6/13 1. Andrew Caldwell 11/6/13 1. Alex Derzhi 11/7/13 1. Olivier Biesmans 11/7/13 1. Gabriel Rosendorf The Weather Companies 11/8/13 1. Nathaniel Eliot 11/9/13 1. Thibaut Notteboom 11/10/13 1. Walter Huf 11/11/13 1. David Larken Nolen 11/11/13 1. Makana Greenwell 11/12/13 1. Mathew Hartley 11/12/13 1. Julie Rice PTC Inc 11/13/13 1. Matt Welch PTC Inc 11/13/13 1. Jonathan Bass PTC Inc 11/13/13 1. Joe Rocklin 11/14/13 1. Zaininnari 11/14/13 1. Jean Mertz 11/18/13 1. Gleb Borisov 11/19/13 1. Curtis Stewart 11/19/13 1. Jim Park RamTank Inc 11/19/13 1. Pierre Ynard Criteo 11/19/13 1. Sergey Balbeko 11/20/13 1. Igor Serko 11/20/13 1. Jason Giedymin 11/20/13 1. Luca Pradovera 11/20/13 1. Jason Giedymin 11/20/13 1. Shaun Rowe 11/21/13 1. Chad Cloes Intuit 11/21/13 1. Rick Mendes Intuit 11/21/13 1. Grant Hoffman Intuit 11/21/13 1. Capen Brinkley Intuit 11/21/13 1. Kevin Young Intuit 11/21/13 1. Walter Askew IV 11/22/13 1. Spencer Smith 11/23/13 1. Spencer Smith 11/23/13 1. Connor Goodwolf 11/23/13 1. Seiji Komatsu 11/24/13 1. Steve Domin GoCardless 11/24/13 1. Milos Gajdos GoCardless 11/24/13 1. Harry Marr GoCardless 11/24/13 1. Komatsu Seiji 11/25/13 1. Gokulnath Manakkattil 11/26/13 1. Joel Moss 11/27/13 1. Drew J. Sonne 11/30/13 1. Sascha Mollering ZANOX AG 11/30/13 1. Boris Komraz 12/1/13 1. Mark O'Connor 12/1/13 1. Ivan Larionov 12/2/13 1. Pierre Carrier 12/2/13 1. Joe A. Kemp ARINC 12/3/13 1. Douglas Mendizabal 12/3/13 1. Tejay Cardon Lockheed Martin Corporation 12/3/13 1. David Deal Lockheed Martin Corporation 12/3/13 1. Jason Loveland Lockheed Martin Corporation 12/3/13 1. Friedrich Clausen 12/4/13 1. Paul Kehrer 12/4/13 1. Stephan Renatus 12/5/13 1. Abhijit Hiremagalur 12/5/13 1. Wojciech Oledzki 12/5/13 1. Johannes Plunien 12/7/13 1. Benjamin Demaree 12/8/13 1. Friedel Ziegelmayer 12/13/13 1. Jason Vervlied 12/13/13 1. Fabian Lee 12/15/13 1. Tino Breddin 12/16/13 1. Cameron Cope Brightcove 12/16/13 1. Jason Perry Brightcove 12/16/13 1. Eric Moakley Brightcove 12/16/13 1. Joshua Spiewak Brightcove 12/16/13 1. John Schectman Brightcove 12/16/13 1. Keegan Holley 12/16/13 1. James La Spada 12/17/13 1. Jesué Sousa Cunha Junior 12/17/13 1. Dan Rathbone 12/17/13 1. Jay Geeseman 12/19/13 1. David Bernick 12/20/13 1. Primož Verdnik 12/20/13 1. Yavor Nikolov 12/26/13 1. Joseph C. Stump Sprint.ly, Inc 12/27/13 1. Justin T. Abrahms Sprint.ly, Inc 12/27/13 1. Theodore Chuong Nordsieck 12/29/13 1. Thomas Noonan II 12/30/13 1. Coman Ioan Andrei 12/30/13 1. Steven Geerts Schuberg Phillis B.V. 1/2/14 1. Scott Russell 1/3/14 1. Lothar Wieske 1/3/14 1. Paul Czarkowski 1/3/14 1. Ramil Lim 1/3/14 1. Jeff Byrnes 1/3/14 1. Christopher James Saylor 1/4/14 1. Gary Cao 1/6/14 1. Christopher William Pernicano 1/6/14 1. Emmanuel Idi 1/6/14 1. Reid Beels 1/7/14 1. Anthony LoBono 1/8/14 1. Barthélemy Vessemont 1/10/14 1. Michael Holtzman 1/10/14 1. Tristan O'Neil Cramer Development 1/10/14 1. Brian Cobb Cramer Development 1/10/14 1. Brett Chalupa Cramer Development 1/10/14 1. Dan Volkens Cramer Development 1/10/14 1. Ryan Keairns Cramer Development 1/10/14 1. Nikhil Benesch 1/12/14 1. Christian Höltje 1/13/14 1. Alexander C Corvin 1/17/14 1. Samuel Chambers 1/18/14 1. Cheah Chu Yeow 1/18/14 1. Kazuki Saito 1/18/14 1. Pete Richards 1/19/14 1. Jimmy McCrory 1/19/14 1. Olivier Dolbeau 1/20/14 1. Andrew Brown BlackBerry, Inc. 1/20/14 1. Phil Oliva BlackBerry, Inc. 1/20/14 1. Dave Urschatz BlackBerry, Inc. 1/20/14 1. Ishtiaq Ahmed 1/25/14 1. Caleb Land 1/26/14 1. Charles B Johnson 1/26/14 1. Diego Rodriguez 1/27/14 1. Nitin Mohan 1/27/14 1. Szymon Szypulski 1/29/14 1. Jaime Gil de Sagredo 1/29/14 1. Jose Luis Ferrer Riera 1/29/14 1. Yury Velikanau 1/29/14 1. Jeroen Jacobs 1/30/14 1. W. Hart Hoover Rackspace 2/3/14 1. Jerry Richardson Disruptive Ventures, Inc 2/4/14 1. Brian Dwyer 2/4/14 1. Aaron O'Mullan FriendCode, Inc 2/5/14 1. Jean-Baptiste Dalido 2/6/14 1. Juri Timoshin 2/6/14 1. Pascal Laporte 2/6/14 1. Steven Cummings Cerner Innovation Inc 2/6/14 1. Pascal Laporte 2/6/14 1. Mick Brooks 2/7/14 1. Aurélien Noce 2/7/14 1. Charlie Huggard Cerner Innovation Inc 2/11/14 1. Matthias Arnason Engine Yard 2/12/14 1. Bryan Taylor 2/12/14 1. Michael Dillion 2/17/14 1. Seth Kingry 2/18/14 1. Wesley David DeCesare Crux Hosted Services 2/18/14 1. Kent Shultz 2/18/14 1. Adam Durana 2/21/14 1. Jacob McCann 2/21/14 1. Andriy Tyurnikov 2/21/14 1. Anton Koldaev 2/21/14 1. Matthew Rathbone 2/21/14 1. Egor Medvedev 2/23/14 1. Eric Tucker Blue Spurs 2/24/14 1. Tomas Gutierrez 2/24/14 1. Jordan Burke 2/25/14 1. Zvi Effron 2/25/14 1. Pranay Manwatkar 2/26/14 1. Kaspars Mickevics 2/26/14 1. Roman Gorodeckij 2/27/14 1. Matthew Baxa 2/27/14 1. Maxime Caumartin 2/27/14 1. Brandon Raabe 2/28/14 1. Alan Grosskurth 2/28/14 1. Nathan Milford 2/28/14 1. Jacob Vosmaer GitLab.com 3/1/14 1. Job van der Voort GitLab.com 3/1/14 1. Marin Jankovski GitLab.com 3/1/14 1. Martin Glaß 3/1/14 1. Sergey Sergeev 3/1/14 1. Marshall Ian Farmer 3/2/14 1. Markus Schabel 3/3/14 1. Torben Knerr 3/4/14 1. Pavel Sadikov 3/5/14 1. David King 3/7/14 1. Charles Guenther Yelp 3/7/14 1. Kris Wehner Yelp 3/7/14 1. Brian Fletcher Workday 3/10/14 1. Brad Pokorny IBM 3/10/14 1. John Warren IBM 3/10/14 1. Lance Bragstad IBM 3/10/14 1. Luis Garcia IBM 3/10/14 1. Mark Vanderwiel IBM 3/10/14 1. Mathew Odden IBM 3/10/14 1. Andrew Coulton 3/11/14 1. Andrew Ordiales 3/11/14 1. Will Hattingh 3/12/14 1. Eohyung Lee 3/13/14 1. Matthew Zito BMC Software Inc 3/13/14 1. Nick Galbreath 3/13/14 1. Thomas Duckering 3/14/14 1. Stanley Halka 3/14/14 1. Michael Morris 3/14/14 1. Gavin Montague itison 3/14/14 1. John Daniels itison 3/14/14 1. Matthias Endler 3/17/14 1. Greg Albrecht OnBeep, Inc. 3/19/14 1. Andy Issacson OnBeep, Inc. 3/19/14 1. Ben Graver OnBeep, Inc. 3/19/14 1. Jim Qin OnBeep, Inc. 3/19/14 1. Carlos Vinueza OnBeep, Inc. 3/19/14 1. Benson Miller Level 11 Consulting 3/19/14 1. Nik Ormseth Level 11 Consulting 3/19/14 1. James Francis Level 11 Consulting 3/19/14 1. Kevin Rivers Level 11 Consulting 3/19/14 1. Michael Dellanoce 3/20/14 1. Joey Line 3/21/14 1. Nick Lopez 3/22/14 1. Jason Byck 3/23/14 1. Ian Neubert 3/25/14 1. Brandon Taylor Groves 3/25/14 1. Sean Walberg 3/25/14 1. Matthijs Wijers 3/26/14 1. Tensibai Zhaoying 3/26/14 1. Michael Chletso 3/27/14 1. Joseph Korkames 3/27/14 1. Matthew Juszczak 3/27/14 1. Hongbin Lu 3/27/14 1. Ryan Lewon 3/28/14 1. Tiru Srikantha 3/29/14 1. Calvin Worsnup 3/31/14 1. Andres More 4/1/14 1. Joe Richards 4/1/14 1. Mikael Henriksson 4/2/14 1. Benjamin Dalton LeMasurier 4/2/14 1. Dirk Moermans 4/3/14 1. Pavel Yudin 4/4/14 1. Ed Neville Linaro Limited 4/4/14 1. Andrew McDermott Linaro Limited 4/4/14 1. Ron Nandy Linaro Limited 4/4/14 1. Florian Holzhauer 4/4/14 1. Joshua Yotty 4/4/14 1. Narendra V Dharmavarapu 4/4/14 1. Brian Wilson Leake 4/6/14 1. John Northrup 4/7/14 1. Yoichi Isozaki 4/7/14 1. Jordan Evans 4/8/14 1. Oliver Kohl 4/9/14 1. Craig Monson 4/10/14 1. Rob Brown 4/10/14 1. Alexander Myasnikov 4/11/14 1. Matthew Hodgkins 4/12/14 1. Aaron Quint 4/12/14 1. Jaewoo Kim 4/17/14 1. Vasiliy Tolstov 4/17/14 1. Ryan Moe 4/18/14 1. G. Panula 4/21/14 1. Chris Antenesse 4/21/14 1. Lloyd Chan 4/22/14 1. Satoshi Tanaka 4/22/14 1. Tim Heckman 4/24/14 1. Trevor Lauder 4/25/14 1. Nathan Haneysmith Nordstrom 4/25/14 1. Aaron Lane 4/26/14 1. Bao Nguyen 4/27/14 1. Salvatore Poliandro III 4/28/14 1. Eric Zhoe 4/29/14 1. Alex Kahn 4/29/14 1. Brendan Murtagh 4/29/14 1. Tyler Cipriani 4/29/14 1. Syunsuke Komma WESEEK 4/29/14 1. Yuki Takei WESEEK 4/29/14 1. Trevor Bramwell 5/1/14 1. Robert Tarrall 5/1/14 1. Josh Reichardt 5/1/14 1. Trevor Lauder Intuit 5/1/14 1. Jake Plimack 5/2/14 1. Brian D Clark 5/3/14 1. Roman Chukh 5/4/14 1. Nick Montgomery 5/4/14 1. Patrick Moore 5/5/14 1. Edmund Dipple 5/6/14 1. Kyle Boorky 5/6/14 1. Olivier Larivain 5/6/14 1. Aaron Valade 5/7/14 1. Jonathan Serafini 5/7/14 1. Jason Nelson Rackspace 5/8/14 1. Alexander Meng 5/8/14 1. Kyle McGovern Cerner Innovation Inc 5/8/14 1. Jesse Washburn 5/8/14 1. Ian Blenke 5/9/14 1. Jochen Seeber 5/9/14 1. Florin Stan 5/9/14 1. Bearnard Hibbins 5/12/14 1. Amruta Krishna Cerner Innovation Inc 5/13/14 1. Daniel Zautner 5/13/14 1. Andrew DuFour 5/13/14 1. David Gil Oliva 5/19/14 1. Emmanuel Sciara 5/20/14 1. Francois Visconte 5/21/14 1. Cyril Scetbon 5/21/14 1. Doug Wilson CustomInk 4/4/14 1. Meherez Alachheb 5/22/14 1. Ash Wilson Rackspace 5/22/14 1. Alexey Velikiy 5/21/14 1. Anand Suresh 5/24/14 1. Christoph Krämer 5/26/14 1. Marcus Nilsson 5/27/14 1. Eric Black 5/27/14 1. Michiel Sikkes 5/29/14 1. Miguel Landaeta 5/29/14 1. Claude Ballew Jr 5/29/14 1. Carlos Macasaet 5/29/14 1. Steve Jansen 6/3/14 1. Jake Champlin 6/4/14 1. Alistair Stead Iniqa UK, Ltd 6/4/14 1. Fahd Sultan 6/4/14 1. Martin Smith III Rackspace 6/4/14 1. Klaas Jan Wierenga 6/5/14 1. Michael Bumann 6/6/14 1. Grant Hudgens 6/6/14 1. Rob Redpath World Wide Web Hosting, LLC 6/6/14 1. Benjamin Ahrens 6/9/14 1. Alessio Franceschelli 6/10/14 1. Joseph Bowman 6/11/14 1. William Cody Crawford 6/11/14 1. Ryan Trauntvein 6/12/14 1. Adam Lavin 6/14/14 1. Brett Cave Jemstep 6/13/14 1. Matt Wrock 6/16/14 1. William Clark 6/17/14 1. Karsten McMinn 6/17/14 1. Alexander Simonov 6/17/14 1. James Coleman 6/18/14 1. Charles Ruhl 6/18/14 1. Pushkar Subhash Raste 6/19/14 1. John Coleman 6/20/14 1. Joshua Rutherford 6/20/14 1. Elijah Buck 6/21/14 1. Nikalai Stakanov 6/22/14 1. Blair Hamilton 6/22/14 1. Jeffrey Goldschrafe 6/24/14 1. Vijay Bheemineni 6/25/14 1. Joshua Benjamin 6/25/14 1. Stafford Brunk 6/25/14 1. Sumit Gupta 6/26/14 1. Jan Mara 6/27/14 chef-12.14.60/CONTRIBUTING.md000066400000000000000000000213011276456504500151160ustar00rootroot00000000000000# Contributing to Chef We are glad you want to contribute to Chef! We utilize **Github Issues** for issue tracking and contributions. You can contribute in two ways: 1. Reporting an issue or making a feature request [here](#issues). 2. Adding features or fixing bugs yourself and contributing your code to Chef. ## Contribution Process We have a 3 step process that utilizes **Github Issues**: 1. Sign or be added to an existing [Contributor License Agreement (CLA)](https://supermarket.chef.io/become-a-contributor). 2. Create a Github Pull Request. 3. Do [Code Review](#cr) with the **Chef Engineering Team** or **Chef Core Committers** on the pull request. ### Chef Pull Requests Chef is built to last. We strive to ensure high quality throughout the Chef experience. In order to ensure this, we require a couple of things for all pull requests to Chef: 1. **Tests:** To ensure high quality code and protect against future regressions, we require all the code in Chef to have at least unit test coverage. See the [spec/unit](https://github.com/chef/chef/tree/master/spec/unit) directory for the existing tests and use `bundle exec rake spec` to run them. 2. **Green Travis Run:** We use [Travis CI](https://travis-ci.org/) in order to run our tests continuously on all the pull requests. We require the Travis runs to succeed on every pull request before being merged. ### Chef Code Review Process The Chef Code Review process happens on Github pull requests. See [this article](https://help.github.com/articles/using-pull-requests) if you're not familiar with Github Pull Requests. Once you open a pull request, the **Chef Engineering Team** or **Chef Core Committers** will review your code and respond to you with any feedback they might have. The process at this point is as follows: 1. 2 thumbs-ups are required from the **Chef Engineering Team** or **Chef Core Committers** for all merges. 2. When ready, your pull request will be tagged with label `Ready For Merge`. 3. Your patch will be merged into `master` including necessary documentation updates and you will be included in `CHANGELOG.md`. Our goal is to have patches merged in 2 weeks after they are marked to be merged. If you would like to learn about when your code will be available in a release of Chef, read more about [Chef Release Cycles](#chef-release-cycles). ### Contributor License Agreement (CLA) Licensing is very important to open source projects. It helps ensure the software continues to be available under the terms that the author desired. Chef uses [the Apache 2.0 license](https://github.com/chef/chef/blob/master/LICENSE) to strike a balance between open contribution and allowing you to use the software however you would like to. The license tells you what rights you have that are provided by the copyright holder. It is important that the contributor fully understands what rights they are licensing and agrees to them. Sometimes the copyright holder isn't the contributor, such as when the contributor is doing work for a company. To make a good faith effort to ensure these criteria are met, Chef requires an Individual CLA or a Corporate CLA for contributions. This agreement helps ensure you are aware of the terms of the license you are contributing your copyrighted works under, which helps to prevent the inclusion of works in the projects that the contributor does not hold the rights to share. It only takes a few minutes to complete a CLA, and you retain the copyright to your contribution. You can complete our [Individual CLA](https://supermarket.chef.io/icla-signatures/new) online. If you're contributing on behalf of your employer and they retain the copyright for your works, have your employer fill out our [Corporate CLA](https://supermarket.chef.io/ccla-signatures/new) instead. ### Chef Obvious Fix Policy Small contributions such as fixing spelling errors, where the content is small enough to not be considered intellectual property, can be submitted by a contributor as a patch, without a CLA. As a rule of thumb, changes are obvious fixes if they do not introduce any new functionality or creative thinking. As long as the change does not affect functionality, some likely examples include the following: - Spelling / grammar fixes - Typo correction, white space and formatting changes - Comment clean up - Bug fixes that change default return values or error codes stored in constants - Adding logging messages or debugging output - Changes to 'metadata' files like Gemfile, .gitignore, build scripts, etc. - Moving source files from one directory or package to another **Whenever you invoke the "obvious fix" rule, please say so in your commit message:** ``` ------------------------------------------------------------------------ commit 370adb3f82d55d912b0cf9c1d1e99b132a8ed3b5 Author: danielsdeleo Date: Wed Sep 18 11:44:40 2013 -0700 Fix typo in config file docs. Obvious fix. ------------------------------------------------------------------------ ``` ## Chef Issue Tracking Chef Issue Tracking is handled using Github Issues. If you are familiar with Chef and know the component that is causing you a problem or if you have a feature request on a specific component you can file an issue in the corresponding Github project. All of our Open Source Software can be found in our [Github organization](https://github.com/chef/). There is also a listing of the various Chef products and where to file issues that can be found in the Chef docs in the [community contributions section](https://docs.chef.io/community_contributions.html#issues-and-bug-reports). Otherwise you can file your issue in the [Chef project](https://github.com/chef/chef/issues) and we will make sure it gets filed against the appropriate project. ### Useful Github Queries Contributions go through a review process to improve code quality and avoid regressions. Managing a large number of contributions requires a workflow to provide queues for work such as triage, code review, and merging. A semi-formal process has evolved over the life of the project. Chef maintains this process pending community development and acceptance of an [RFC](https://github.com/chef/chef-rfc). These queries will help track contributions through this process: - [Issues that are not assigned to a team](https://github.com/chef/chef/issues?q=is%3Aopen+-label%3AAIX+-label%3ABSD+-label%3Awindows+-label%3A%22Chef+Core%22++-label%3A%22Dev+Tools%22+-label%3AUbuntu+-label%3A%22Enterprise+Linux%22+-label%3A%22Ready+For+Merge%22+-label%3AMac+-label%3ASolaris+) - [Untriaged Issues](https://github.com/chef/chef/issues?q=is%3Aopen+is%3Aissue+-label%3ABug+-label%3AEnhancement+-label%3A%22Tech+Cleanup%22+-label%3A%22Ready+For+Merge%22) - [PRs to be Reviewed](https://github.com/chef/chef/labels/Pending%20Maintainer%20Review) - [Suitable for First Contribution](https://github.com/chef/chef/labels/Easy) ## Chef Release Cycles Our primary shipping vehicle is operating system specific packages that includes all the requirements of Chef. We call these [Omnibus packages](https://github.com/chef/omnibus) We also release our software as gems to [Rubygems](https://rubygems.org/) but we strongly recommend using Chef packages since they are the only combination of native libraries & gems required by Chef that we test throughly. Our version numbering roughly follows [Semantic Versioning](http://semver.org/) standard. Our standard version numbers look like X.Y.Z which mean: - X is a major release, which may not be fully compatible with prior major releases - Y is a minor release, which adds both new features and bug fixes - Z is a patch release, which adds just bug fixes After shipping a release of Chef we bump the `Minor` version by one to start development of the next minor releaae. All merges to master trigger an increment of the `Patch` version, and a build through our internal testing pipeline. We do a `Minor` release approximately every month, which consist of shipping one of the already auto-incremented and tested `Patch` versions. For example after shiping 12.10.24, we incremented Chef to 12.11.0\. From there 18 commits where merged bringing the version to 12.11.18, which we shipped as an omnibus package. Announcements of releases are made to the [chef mailing list](https://discourse.chef.io/c/chef-release) when they are available. ## Chef Community Chef is made possible by a strong community of developers and system administrators. If you have any questions or if you would like to get involved in the Chef community you can check out: - [Chef Mailing List](https://discourse.chef.io/) - [Chef Community Slack](https://community-slack.chef.io/) Also here are some additional pointers to some awesome Chef content: - [Chef Docs](https://docs.chef.io/) - [Learn Chef](https://learn.chef.io/) - [Chef Inc.](https://www.chef.io/) chef-12.14.60/DOC_CHANGES.md000066400000000000000000000003711276456504500147500ustar00rootroot00000000000000 ## Doc changes for Chef 12.13 chef-12.14.60/Gemfile000066400000000000000000000053331276456504500141670ustar00rootroot00000000000000# This buys us the ability to be included in other Gemfiles require_relative "tasks/gemfile_util" extend GemfileUtil source "https://rubygems.org" # 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", group: [ :default, :omnibus_package, :development ] gem "bundler" gem "cheffish" group(:omnibus_package) do gem "appbundler" gem "rb-readline" gem "nokogiri" end group(:omnibus_package, :pry) do gem "pry" gem "pry-byebug" gem "pry-remote" gem "pry-stack_explorer" end # These are used for external tests group(:integration) do gem "chef-provisioning" gem "chef-provisioning-aws" gem "chef-rewind" gem "chef-sugar" gem "chefspec" gem "halite" gem "poise" gem "poise-boiler", git: "https://github.com/poise/poise-boiler" gem "knife-windows" gem "foodcritic" # We pin this so nobody brings in a cucumber-core incompatible with cucumber latest gem "cucumber", ">= 2.4.0" # We pin oc-chef-pedant to prevent it from updating out of lockstep with chef-zero gem "oc-chef-pedant", git: "https://github.com/chef/chef-server" end group(:docgen) do gem "yard" end group(:maintenance) do gem "tomlrb" # To sync maintainers with github gem "octokit" gem "netrc" end # Everything except AIX group(:linux, :bsd, :mac_os_x, :solaris, :windows) do # may need to disable this in insolation on fussy builds like AIX, RHEL4, etc gem "ruby-prof" end # Everything except AIX and Windows group(:linux, :bsd, :mac_os_x, :solaris) do gem "ruby-shadow", platforms: :ruby end group(:development, :test) do gem "simplecov" # for testing new chefstyle rules # gem 'chefstyle', github: 'chef/chefstyle' gem "chefstyle", git: "https://github.com/chef/chefstyle.git", branch: "master" end group(:changelog) do gem "github_changelog_generator" end group(:travis) do # See `bundler-audit` in .travis.yml gem "bundler-audit", git: "https://github.com/rubysec/bundler-audit.git" gem "travis" end instance_eval(ENV["GEMFILE_MOD"]) if ENV["GEMFILE_MOD"] # If you want to load debugging tools into the bundle exec sandbox, # add these additional dependencies into chef/Gemfile.local eval(IO.read(__FILE__ + ".local"), binding) if File.exist?(__FILE__ + ".local") chef-12.14.60/Gemfile.lock000066400000000000000000000342711276456504500151210ustar00rootroot00000000000000GIT remote: https://github.com/chef/chef-server revision: dea545b10cbe209b89b30e782a58763407ba4526 specs: oc-chef-pedant (2.2.0) activesupport (~> 3.2) erubis (~> 2.7) mixlib-authentication (~> 1.4) mixlib-config (~> 2.0) mixlib-shellout (>= 1.1) net-http-spy (~> 0.2) rest-client (>= 1.6) rspec (~> 3.2) rspec-rerun (~> 1.0) rspec_junit_formatter (~> 0.2) GIT remote: https://github.com/chef/chefstyle.git revision: c36dcbd6c2c21d2e19db77d9fbdf2402d0bacccf branch: master specs: chefstyle (0.4.0) rubocop (= 0.42.0) GIT remote: https://github.com/poise/poise-boiler revision: 9b8d1393b0dc06af625e3dcc4c0e0a53b2975657 specs: poise-boiler (1.11.1.pre) bundler chefspec (~> 5.0) codeclimate-test-reporter (~> 0.4) codecov (~> 0.0, >= 0.0.2) foodcritic (~> 7.0) fuubar (~> 2.0) git (~> 1.2) halite (~> 1.2) kitchen-docker (>= 2.6.0.rc.0) kitchen-ec2 (~> 1.0) kitchen-sync (~> 2.1) kitchen-vagrant mixlib-shellout (>= 1.4, < 3.0) poise-profiler (~> 1.0) pry pry-byebug rake (>= 10.4, < 12.0) rspec (~> 3.2) rspec-its (~> 1.2) simplecov (~> 0.9) test-kitchen (~> 1.7, >= 1.7.1) travis (~> 1.8, >= 1.8.1) vagrant-wrapper winrm (~> 2.0) winrm-fs (~> 1.0) yard (~> 0.8) yard-classmethods (~> 1.0) GIT remote: https://github.com/rubysec/bundler-audit.git revision: b7123d7b294f244165d9469f22b37a559e235fc2 specs: bundler-audit (0.5.0) bundler (~> 1.2) thor (~> 0.18) PATH remote: . specs: chef (12.14.60) addressable bundler (>= 1.10) chef-config (= 12.14.60) chef-zero (>= 4.8) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) ffi-yajl (~> 2.2) highline (~> 1.6, >= 1.6.9) iniparse (~> 1.4) mixlib-archive (>= 0.2.0) mixlib-authentication (~> 1.4) mixlib-cli (~> 1.7) mixlib-log (~> 1.3) mixlib-shellout (~> 2.0) net-sftp (~> 2.1, >= 2.1.2) net-ssh (>= 2.9, < 4.0) net-ssh-multi (~> 1.1) ohai (>= 8.6.0.alpha.1, < 9) plist (~> 3.2) proxifier (~> 1.0) rspec-core (~> 3.5) rspec-expectations (~> 3.5) rspec-mocks (~> 3.5) rspec_junit_formatter (~> 0.2.0) serverspec (~> 2.7) specinfra (~> 2.10) syslog-logger (~> 1.6) uuidtools (~> 2.1.5) chef (12.14.60-universal-mingw32) addressable bundler (>= 1.10) chef-config (= 12.14.60) chef-zero (>= 4.8) diff-lcs (~> 1.2, >= 1.2.4) erubis (~> 2.7) ffi (~> 1.9) ffi-yajl (~> 2.2) highline (~> 1.6, >= 1.6.9) iniparse (~> 1.4) mixlib-archive (>= 0.2.0) mixlib-authentication (~> 1.4) mixlib-cli (~> 1.7) mixlib-log (~> 1.3) mixlib-shellout (~> 2.0) net-sftp (~> 2.1, >= 2.1.2) net-ssh (>= 2.9, < 4.0) net-ssh-multi (~> 1.1) ohai (>= 8.6.0.alpha.1, < 9) plist (~> 3.2) proxifier (~> 1.0) rspec-core (~> 3.5) rspec-expectations (~> 3.5) rspec-mocks (~> 3.5) rspec_junit_formatter (~> 0.2.0) serverspec (~> 2.7) specinfra (~> 2.10) syslog-logger (~> 1.6) uuidtools (~> 2.1.5) win32-api (~> 1.5.3) win32-dir (~> 0.5.0) win32-event (~> 0.6.1) win32-eventlog (= 0.6.3) win32-mmap (~> 0.4.1) win32-mutex (~> 0.4.2) win32-process (~> 0.8.2) win32-service (~> 0.8.7) windows-api (~> 0.4.4) wmi-lite (~> 1.0) PATH remote: chef-config specs: chef-config (12.14.60) addressable fuzzyurl mixlib-config (~> 2.0) mixlib-shellout (~> 2.0) GEM remote: https://rubygems.org/ specs: activesupport (3.2.22.4) i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) addressable (2.4.0) appbundler (0.9.0) mixlib-cli (~> 1.4) artifactory (2.3.3) ast (2.3.0) aws-sdk (2.5.10) aws-sdk-resources (= 2.5.10) aws-sdk-core (2.5.10) jmespath (~> 1.0) aws-sdk-resources (2.5.10) aws-sdk-core (= 2.5.10) aws-sdk-v1 (1.66.0) json (~> 1.4) nokogiri (>= 1.4.4) backports (3.6.8) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) builder (3.2.2) byebug (9.0.5) chef-api (0.7.0) logify (~> 0.1) mime-types chef-provisioning (2.0.1) cheffish (~> 4.0) inifile (>= 2.0.2) mixlib-install (~> 1.0) net-scp (~> 1.0) net-ssh (>= 2.9, < 4.0) net-ssh-gateway (~> 1.2.0) winrm-fs (~> 1.0) chef-provisioning-aws (2.0.0) aws-sdk (>= 2.1.26, < 3.0) aws-sdk-v1 (>= 1.59.0) chef-provisioning (~> 2.0) retryable (~> 2.0, >= 2.0.1) ubuntu_ami (~> 0.4, >= 0.4.1) chef-rewind (0.0.9) chef-sugar (3.4.0) chef-zero (5.1.0) ffi-yajl (~> 2.2) hashie (>= 2.0, < 4.0) mixlib-log (~> 1.3) rack (~> 2.0) uuidtools (~> 2.1) cheffish (4.0.0) chef-zero (~> 5.0) net-ssh chefspec (5.0.0) chef (>= 12.0) fauxhai (~> 3.6) rspec (~> 3.0) codeclimate-test-reporter (0.6.0) simplecov (>= 0.7.1, < 1.0.0) codecov (0.1.5) json simplecov url coderay (1.1.1) colorize (0.8.1) cucumber (2.4.0) builder (>= 2.1.2) cucumber-core (~> 1.5.0) cucumber-wire (~> 0.0.1) diff-lcs (>= 1.1.3) gherkin (~> 4.0) multi_json (>= 1.7.5, < 2.0) multi_test (>= 0.1.2) cucumber-core (1.5.0) gherkin (~> 4.0) cucumber-wire (0.0.1) debug_inspector (0.0.2) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) diff-lcs (1.2.5) docile (1.1.5) domain_name (0.5.20160826) unf (>= 0.0.5, < 1.0.0) erubis (2.7.0) ethon (0.9.0) ffi (>= 1.3.0) excon (0.52.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) faraday_middleware (0.10.0) faraday (>= 0.7.4, < 0.10) fauxhai (3.8.0) net-ssh ffi (1.9.14) ffi (1.9.14-x86-mingw32) ffi-win32-extensions (1.0.3) ffi ffi-yajl (2.3.0) libyajl2 (~> 1.2) foodcritic (7.1.0) cucumber-core (>= 1.3) erubis nokogiri (>= 1.5, < 2.0) rake rufus-lru (~> 1.0) treetop (~> 1.4) yajl-ruby (~> 1.1) fuubar (2.2.0) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) fuzzyurl (0.9.0) gh (0.14.0) addressable backports faraday (~> 0.8) multi_json (~> 1.0) net-http-persistent (>= 2.7) net-http-pipeline gherkin (4.0.0) git (1.3.0) github_api (0.14.5) addressable (~> 2.4.0) descendants_tracker (~> 0.0.4) faraday (~> 0.8, < 0.10) hashie (>= 3.4) oauth2 (~> 1.0) github_changelog_generator (1.13.1) colorize (~> 0.7) github_api (~> 0.12) rake (>= 10.0) gssapi (1.2.0) ffi (>= 1.0.1) gyoku (1.3.1) builder (>= 2.1.2) halite (1.3.0) bundler chef (~> 12.0) stove (~> 4.0) thor hashie (3.4.4) highline (1.7.8) http-cookie (1.0.2) domain_name (~> 0.5) httpclient (2.8.2.3) i18n (0.7.0) inifile (3.0.0) iniparse (1.4.2) ipaddress (0.8.3) jmespath (1.3.1) json (1.8.3) jwt (1.5.4) kitchen-docker (2.6.0.rc.0) test-kitchen (>= 1.0.0) kitchen-ec2 (1.1.0) aws-sdk (~> 2) excon multi_json retryable (~> 2.0) test-kitchen (~> 1.4, >= 1.4.1) kitchen-sync (2.1.1) net-sftp test-kitchen (>= 1.0.0) kitchen-vagrant (0.20.0) test-kitchen (~> 1.4) knife-windows (1.6.0) winrm-elevated (~> 1.0) launchy (2.4.3) addressable (~> 2.3) libyajl2 (1.2.0) little-plugger (1.1.4) logging (2.1.0) little-plugger (~> 1.1) multi_json (~> 1.10) logify (0.2.0) method_source (0.8.2) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) mini_portile2 (2.1.0) mixlib-archive (0.2.0) mixlib-log mixlib-authentication (1.4.1) mixlib-log mixlib-cli (1.7.0) mixlib-config (2.2.4) mixlib-install (1.1.0) artifactory mixlib-shellout mixlib-versioning mixlib-log (1.7.1) mixlib-shellout (2.2.7) mixlib-shellout (2.2.7-universal-mingw32) win32-process (~> 0.8.2) wmi-lite (~> 1.0) mixlib-versioning (1.1.0) multi_json (1.12.1) multi_test (0.1.2) multi_xml (0.5.5) multipart-post (2.0.0) net-http-persistent (2.9.4) net-http-pipeline (1.0.1) net-http-spy (0.2.1) net-scp (1.2.1) net-ssh (>= 2.6.5) net-sftp (2.1.2) net-ssh (>= 2.6.5) net-ssh (3.2.0) net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) net-ssh-multi (1.2.1) net-ssh (>= 2.6.5) net-ssh-gateway (>= 1.2.0) net-telnet (0.1.1) netrc (0.11.0) 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.2.0) faraday (>= 0.8, < 0.10) jwt (~> 1.0) multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) ohai (8.20.0) chef-config (>= 12.5.0.alpha.1, < 13) ffi (~> 1.9) ffi-yajl (~> 2.2) ipaddress mixlib-cli mixlib-config (~> 2.0) mixlib-log (>= 1.7.1, < 2.0) mixlib-shellout (~> 2.0) plist (~> 3.1) systemu (~> 2.6.4) wmi-lite (~> 1.0) parser (2.3.1.2) ast (~> 2.2) pkg-config (1.1.7) plist (3.2.0) poise (2.7.1) halite (~> 1.0) poise-profiler (1.0.1) halite (~> 1.0) polyglot (0.3.5) powerpack (0.1.1) proxifier (1.0.3) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) pry-byebug (3.4.0) byebug (~> 9.0) pry (~> 0.10) pry-remote (0.1.8) pry (~> 0.9) slop (~> 3.0) pry-stack_explorer (0.4.9.2) binding_of_caller (>= 0.7) pry (>= 0.9.11) pusher-client (0.6.2) json websocket (~> 1.0) rack (2.0.1) rainbow (2.1.0) rake (11.2.2) rb-readline (0.5.3) rest-client (2.0.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rest-client (2.0.0-x86-mingw32) ffi (~> 1.9) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) retryable (2.0.4) rspec (3.5.0) rspec-core (~> 3.5.0) rspec-expectations (~> 3.5.0) rspec-mocks (~> 3.5.0) rspec-core (3.5.3) rspec-support (~> 3.5.0) rspec-expectations (3.5.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.5.0) rspec-its (1.2.0) rspec-core (>= 3.0.0) rspec-expectations (>= 3.0.0) rspec-mocks (3.5.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.5.0) rspec-rerun (1.1.0) rspec (~> 3.0) rspec-support (3.5.0) rspec_junit_formatter (0.2.3) builder (< 4) rspec-core (>= 2, < 4, != 2.12.0) rubocop (0.42.0) parser (>= 2.3.1.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-prof (0.16.2) ruby-progressbar (1.8.1) ruby-shadow (2.5.0) rubyntlm (0.6.0) rubyzip (1.2.0) rufus-lru (1.1.0) safe_yaml (1.0.4) sawyer (0.7.0) addressable (>= 2.3.5, < 2.5) faraday (~> 0.8, < 0.10) serverspec (2.36.1) multi_json rspec (~> 3.0) rspec-its specinfra (~> 2.53) sfl (2.2) simplecov (0.12.0) docile (~> 1.1.0) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) slop (3.6.0) specinfra (2.61.3) net-scp net-ssh (>= 2.7, < 4.0) net-telnet sfl stove (4.1.1) chef-api (~> 0.5) logify (~> 0.2) syslog-logger (1.6.8) systemu (2.6.5) test-kitchen (1.12.0) mixlib-install (~> 1.0, >= 1.0.4) mixlib-shellout (>= 1.2, < 3.0) net-scp (~> 1.1) net-ssh (>= 2.9, < 4.0) net-ssh-gateway (~> 1.2.0) safe_yaml (~> 1.0) thor (~> 0.18) thor (0.19.1) thread_safe (0.3.5) tomlrb (1.2.3) travis (1.8.2) backports faraday (~> 0.9) faraday_middleware (~> 0.9, >= 0.9.1) gh (~> 0.13) highline (~> 1.6) launchy (~> 2.1) pusher-client (~> 0.4) typhoeus (~> 0.6, >= 0.6.8) treetop (1.6.8) polyglot (~> 0.3) typhoeus (0.8.0) ethon (>= 0.8.0) ubuntu_ami (0.4.1) unf (0.1.4) unf_ext unf_ext (0.0.7.2) unf_ext (0.0.7.2-x86-mingw32) unicode-display_width (1.1.1) url (0.3.2) uuidtools (2.1.5) vagrant-wrapper (2.0.3) websocket (1.2.3) win32-api (1.5.3-universal-mingw32) win32-dir (0.5.1) ffi (>= 1.0.0) win32-event (0.6.3) win32-ipc (>= 0.6.0) win32-eventlog (0.6.3) ffi win32-ipc (0.7.0) ffi win32-mmap (0.4.2) ffi win32-mutex (0.4.3) win32-ipc (>= 0.6.0) win32-process (0.8.3) ffi (>= 1.0.0) win32-service (0.8.9) ffi ffi-win32-extensions windows-api (0.4.4) win32-api (>= 1.4.5) winrm (2.0.1) builder (>= 2.1.2) erubis (~> 2.7) 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-elevated (1.0.0) winrm (~> 2.0) winrm-fs (~> 1.0) winrm-fs (1.0.0) erubis (~> 2.7) logging (>= 1.6.1, < 3.0) rubyzip (~> 1.1) winrm (~> 2.0) wmi-lite (1.0.0) yajl-ruby (1.2.1) yard (0.9.5) yard-classmethods (1.0.0) yard PLATFORMS ruby x86-mingw32 DEPENDENCIES appbundler bundler bundler-audit! chef! chef-config! chef-provisioning chef-provisioning-aws chef-rewind chef-sugar cheffish chefspec chefstyle! cucumber (>= 2.4.0) foodcritic github_changelog_generator halite knife-windows netrc nokogiri oc-chef-pedant! octokit poise poise-boiler! pry pry-byebug pry-remote pry-stack_explorer rake rb-readline ruby-prof ruby-shadow simplecov tomlrb travis yard BUNDLED WITH 1.12.5 chef-12.14.60/LICENSE000066400000000000000000000251421276456504500137010ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. chef-12.14.60/MAINTAINERS.md000066400000000000000000000146051276456504500147720ustar00rootroot00000000000000 # Maintainers This file lists how the Chef project is maintained. When making changes to the system, this file tells you who needs to review your patch - you need a simple majority of maintainers for the relevant subsystems to provide a :+1: on your pull request. Additionally, you need to not receive a veto from a Lieutenant or the Project Lead. Check out [How Chef is Maintained](https://github.com/chef/chef-rfc/blob/master/rfc030-maintenance-policy.md#how-the-project-is-maintained) for details on the process, how to become a maintainer, lieutenant, or the project lead. # Project Lead * [Adam Jacob](https://github.com/adamhjk) ## Components ## Chef Core Handles the core parts of the Chef DSL, base resource and provider infrastructure, the Chef applications and [omnibus-chef](https://github.com/chef/omnibus-chef). Includes anything not covered by another component. To mention the team, use @chef/client-core ### Lieutenant * [Thom May](https://github.com/thommay) ### Maintainers * [Bryan McLellan](https://github.com/btm) * [Noah Kantrowitz](https://github.com/coderanger) * [Daniel DeLeo](https://github.com/danielsdeleo) * [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) * [Steven Murawski](https://github.com/smurawski) * [Tyler Ball](https://github.com/tyler-ball) * [Ranjib Dey](https://github.com/ranjib) * [Matt Wrock](https://github.com/mwrock) ## Ohai To mention the team, use @chef/ohai ### Lieutenant * [Claire McQuin](https://github.com/mcquin) ### Maintainers * [Bryan McLellan](https://github.com/btm) * [Tim Smith](https://github.com/tas50) ## Dev Tools ChefDK, Chef Zero, Knife, Chef Apply and Chef Shell. 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) ## Test Tools ChefSpec To mention the team, use @chef/client-test-tools ### Lieutenant * [Seth Vargo](https://github.com/sethvargo) ### Maintainers * [Joshua Timberman](https://github.com/jtimberman) * [Lamont Granquist](https://github.com/lamont-granquist) * [Ranjib Dey](https://github.com/ranjib) ## Chef Provisioning Chef Provisioning and Drivers. Supported Drivers are listed in the [README](https://github.com/chef/chef-provisioning/blob/master/README.md#chef-provisioning). To mention the team, use @chef/provisioning ### Lieutenant * [Tyler Ball](https://github.com/tyler-ball) ### Maintainers * [John Keiser](https://github.com/jkeiser) * [Stuart Preston](https://github.com/stuartpreston) * [JJ Asghar](https://github.com/jjasghar) * [João Cravo](https://github.com/joaogbcravo) * [Harley Alaniz](https://github.com/thehar) ## Platform Specific Components The specific components of Chef related to a given platform - including (but not limited to) resources, providers, and the core DSL. ## Enterprise Linux To mention the team, use @chef/client-enterprise-linux ### Lieutenant * [Jon Cowie](https://github.com/jonlives) ### Maintainers * [Phil Dibowitz](https://github.com/jaymzh) * [Lamont Granquist](https://github.com/lamont-granquist) ## Ubuntu To mention the team, use @chef/client-ubuntu ### Lieutenant * [Ranjib Dey](https://github.com/ranjib) ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) * [Thom May](https://github.com/thommay) ## Windows To mention the team, use @chef/client-windows ### Lieutenant * [Bryan McLellan](https://github.com/btm) ### Maintainers * [Jay Mundrawala](https://github.com/jaym) * [Kartik Cating-Subramanian](https://github.com/ksubrama) * [Steven Murawski](https://github.com/smurawski) * [Salim Alam](https://github.com/chefsalim) * [Matt Wrock](https://github.com/mwrock) ## Solaris To mention the team, use @chef/client-solaris ### Lieutenant * [Thom May](https://github.com/thommay) ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) ## AIX To mention the team, use @chef/client-aix ### Lieutenant * [Lamont Granquist](https://github.com/lamont-granquist) ## Mac OS X To mention the team, use @chef/client-os-x ### Lieutenant * [Joshua Timberman](https://github.com/jtimberman) ### Maintainers * [Tyler Ball](https://github.com/tyler-ball) ## Debian To mention the team, use @chef/client-debian ### Lieutenant * [Thom May](https://github.com/thommay) ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) ## Cisco NX-OS To mention the team, use @chef/client-nxos ### Lieutenant * [Adam Leff](https://github.com/adamleff) ### Maintainers * [Adam Leff](https://github.com/adamleff) ## Cisco IOS XR To mention the team, use @chef/client-iosxr ### Lieutenant * [Adam Leff](https://github.com/adamleff) ### Maintainers * [Adam Leff](https://github.com/adamleff) ## Fedora To mention the team, use @chef/client-fedora ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) ## openSUSE To mention the team, use @chef/client-opensuse ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) ## SUSE Enterprise Linux Server To mention the team, use @chef/client-suse ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) ## FreeBSD To mention the team, use @chef/client-freebsd ### Lieutenant * [Aaron Kalin](https://github.com/martinisoft) ### Maintainers * [Cory Stephenson](https://github.com/Aevin1387) * [David Aronsohn](https://github.com/tbunnyman) * [Bryant Lippert](https://github.com/AgentMeerkat) ## OpenBSD To mention the team, use @chef/client-openbsd ### Lieutenant * [Joe Miller](https://github.com/joemiller) ## Gentoo To mention the team, use @chef/client-gentoo ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) ## OmniOS To mention the team, use @chef/client-omnios ### Maintainers * [Thom May](https://github.com/thommay) ## ArchLinux To mention the team, use @chef/client-archlinux ### Maintainers * [Lamont Granquist](https://github.com/lamont-granquist) * [Ryan Cragun](https://github.com/ryancragun) chef-12.14.60/MAINTAINERS.toml000066400000000000000000000215501276456504500153420ustar00rootroot00000000000000# # This file is structured to be consumed by both humans and computers. # To update the generated Markdown, run `bundle exec rake maintainers:generate` # To synchronize the maintainers with the github teams, run `bundle exec rake maintainers:synchronize` # It is a TOML document containing Markdown # [Preamble] title = "Maintainers" text = """ This file lists how the Chef project is maintained. When making changes to the system, this file tells you who needs to review your patch - you need a simple majority of maintainers for the relevant subsystems to provide a :+1: on your pull request. Additionally, you need to not receive a veto from a Lieutenant or the Project Lead. Check out [How Chef is Maintained](https://github.com/chef/chef-rfc/blob/master/rfc030-maintenance-policy.md#how-the-project-is-maintained) for details on the process, how to become a maintainer, lieutenant, or the project lead. """ [Org] [Org.Lead] title = "Project Lead" person = "adamhjk" [Org.Components] title = "Components" [Org.Components.Core] title = "Chef Core" team = "client-core" text = """ Handles the core parts of the Chef DSL, base resource and provider infrastructure, the Chef applications and [omnibus-chef](https://github.com/chef/omnibus-chef). Includes anything not covered by another component. """ lieutenant = "thommay" maintainers = [ "btm", "coderanger", "danielsdeleo", "fujin", "jaymzh", "jaym", "jkeiser", "jonlives", "lamont-granquist", "mcquin", "smurawski", "tyler-ball", "ranjib", "mwrock" ] [Org.Components.Ohai] title = "Ohai" team = "ohai" lieutenant = "mcquin" maintainers = [ "btm", "tas50" ] [Org.Components.DevTools] title = "Dev Tools" team = "client-dev-tools" text = "ChefDK, Chef Zero, Knife, Chef Apply and Chef Shell." paths = [ "lib/chef/knife.rb", "lib/chef/knife/", "spec/functional/knife/", "spec/integration/knife/", "spec/unit/knife/" ] maintainers = [ "danielsdeleo", "jkeiser", "jtimberman", "lamont-granquist", "stevendanna" ] [Org.Components.TestTools] title = "Test Tools" team = "client-test-tools" text = "ChefSpec" lieutenant = "sethvargo" maintainers = [ "jtimberman", "lamont-granquist", "ranjib" ] [Org.Components.Provisioning] title = "Chef Provisioning" team = "provisioning" text = """ Chef Provisioning and Drivers. Supported Drivers are listed in the [README](https://github.com/chef/chef-provisioning/blob/master/README.md#chef-provisioning). """ lieutenant = "tyler-ball" maintainers = [ "jkeiser", "stuartpreston", "jjasghar", "joaogbcravo", "thehar" ] [Org.Components.Subsystems] title = "Platform Specific Components" text = """ The specific components of Chef related to a given platform - including (but not limited to) resources, providers, and the core DSL. """ [Org.Components.Subsystems."Enterprise Linux"] title = "Enterprise Linux" team = "client-enterprise-linux" lieutenant = "jonlives" maintainers = [ "jaymzh", "lamont-granquist" ] [Org.Components.Subsystems.Ubuntu] title = "Ubuntu" team = "client-ubuntu" lieutenant = "ranjib" maintainers = [ "lamont-granquist", "thommay" ] [Org.Components.Subsystems.Windows] title = "Windows" team = "client-windows" lieutenant = "btm" maintainers = [ "jaym", "ksubrama", "smurawski", "chefsalim", "mwrock" ] [Org.Components.Subsystems.Solaris] title = "Solaris" team = "client-solaris" lieutenant = "thommay" maintainers = [ "lamont-granquist" ] [Org.Components.Subsystems.AIX] title = "AIX" team = "client-aix" lieutenant = "lamont-granquist" [Org.Components.Subsystems."Mac OS X"] title = "Mac OS X" team = "client-os-x" lieutenant = "jtimberman" maintainers = [ "tyler-ball" ] [Org.Components.Subsystems.Debian] title = "Debian" team = "client-debian" lieutenant = "thommay" maintainers = [ "lamont-granquist" ] [Org.Components.Subsystems.CiscoNXOS] title = "Cisco NX-OS" team = "client-nxos" lieutenant = "adamleff" maintainers = [ "adamleff" ] [Org.Components.Subsystems.CiscoIOSXR] title = "Cisco IOS XR" team = "client-iosxr" lieutenant = "adamleff" maintainers = [ "adamleff" ] [Org.Components.Subsystems.Fedora] title = "Fedora" team = "client-fedora" maintainers = [ "lamont-granquist" ] [Org.Components.Subsystems.openSUSE] title = "openSUSE" team = "client-opensuse" maintainers = [ "lamont-granquist" ] [Org.Components.Subsystems."SUSE Enterprise Linux"] title = "SUSE Enterprise Linux Server" team = "client-suse" maintainers = [ "lamont-granquist" ] [Org.Components.Subsystems.FreeBSD] title = "FreeBSD" team = "client-freebsd" lieutenant = "martinisoft" maintainers = [ "Aevin1387", "tBunnyMan", "AgentMeerkat" ] [Org.Components.Subsystems.OpenBSD] title = "OpenBSD" team = "client-openbsd" lieutenant = "joemiller" [Org.Components.Subsystems.Gentoo] title = "Gentoo" team = "client-gentoo" maintainers = [ "lamont-granquist" ] [Org.Components.Subsystems.OmniOS] title = "OmniOS" team = "client-omnios" maintainers = [ "thommay" ] [Org.Components.Subsystems.ArchLinux] title = "ArchLinux" team = "client-archlinux" maintainers = [ "lamont-granquist", "ryancragun" ] [people] [people.adamhjk] Name = "Adam Jacob" GitHub = "adamhjk" [people.adamleff] Name = "Adam Leff" GitHub = "adamleff" [people.Aevin1387] Name = "Cory Stephenson" GitHub = "Aevin1387" [people.AgentMeerkat] Name = "Bryant Lippert" GitHub = "AgentMeerkat" [people.btm] Name = "Bryan McLellan" GitHub = "btm" [people.danielsdeleo] Name = "Daniel DeLeo" GitHub = "danielsdeleo" [people.fujin] Name = "AJ Christensen" GitHub = "fujin" [people.jaymzh] Name = "Phil Dibowitz" GitHub = "jaymzh" [people.jaym] Name = "Jay Mundrawala" GitHub = "jaym" [people.jonlives] Name = "Jon Cowie" GitHub = "jonlives" [people.jtimberman] Name = "Joshua Timberman" GitHub = "jtimberman" [people.lamont-granquist] Name = "Lamont Granquist" GitHub = "lamont-granquist" [people.martinisoft] Name = "Aaron Kalin" GitHub = "martinisoft" [people.mcquin] Name = "Claire McQuin" GitHub = "mcquin" [people.ranjib] Name = "Ranjib Dey" GitHub = "ranjib" [people.sethvargo] Name = "Seth Vargo" GitHub = "sethvargo" [people.smurawski] Name = "Steven Murawski" GitHub = "smurawski" [people.stevendanna] Name = "Steven Danna" GitHub = "stevendanna" [people.tBunnyMan] Name = "David Aronsohn" GitHub = "tbunnyman" IRC = "tBunnyMan" Twitter = "OnlyHaveCans" [people.thommay] Name = "Thom May" GitHub = "thommay" IRC = "thom" Twitter = "thommay" [people.tyler-ball] Name = "Tyler Ball" GitHub = "tyler-ball" [people.ksubrama] Name = "Kartik Cating-Subramanian" GitHub = "ksubrama" [people.joemiller] Name = "Joe Miller" GitHub = "joemiller" [people.coderanger] Name = "Noah Kantrowitz" GitHub = "coderanger" [people.ryancragun] Name = "Ryan Cragun" GitHub = "ryancragun" [people.chefsalim] Name = "Salim Alam" GitHub = "chefsalim" [people.mwrock] Name = "Matt Wrock" GitHub = "mwrock" [people.tas50] Name = "Tim Smith" GitHub = "tas50" [people.jkeiser] Name = "John Keiser" GitHub = "jkeiser" [people.stuartpreston] Name = "Stuart Preston" GitHub = "stuartpreston" [people.jjasghar] Name = "JJ Asghar" GitHub = "jjasghar" Twitter = "jjasghar" IRC = "j^2" [people.joaogbcravo] Name = "João Cravo" GitHub = "joaogbcravo" Twitter = "joaogbcravo" [people.thehar] Name = "Harley Alaniz" GitHub = "thehar" chef-12.14.60/NOTICE000066400000000000000000000017201276456504500135740ustar00rootroot00000000000000Chef NOTICE =========== Developed at Chef (https://www.chef.io). Contributors and Copyright holders: * Copyright 2008-2016, Adam Jacob * Copyright 2008-2016, Arjuna Christensen * Copyright 2008-2016, Bryan McLellan * Copyright 2008-2016, Ezra Zygmuntowicz * Copyright 2009-2016, Sean Cribbs * Copyright 2009-2016, Christopher Brown * Copyright 2009-2016, Thom May * Copyright 2009-2016, Joe Williams Chef incorporates code modified from Open4 (http://www.codeforpeople.com/lib/ruby/open4/), which was written by Ara T. Howard. Chef incorporates code modified from deep_merge (http://trac.misuse.org/science/wiki/DeepMerge), which is Copyright 2008-2016, Steve Midgley Chef incorporates code modified from diff-lcs (http://diff-lcs.rubyforge.org/), which is Copyright (c) 2004–2013 Austin Ziegler chef-12.14.60/README.md000066400000000000000000000433151276456504500141550ustar00rootroot00000000000000# Chef [![Code Climate](https://codeclimate.com/github/chef/chef.svg)](https://codeclimate.com/github/chef/chef) [![Build Status Master](https://travis-ci.org/chef/chef.svg?branch=master)](https://travis-ci.org/chef/chef) [![Build Status Master](https://ci.appveyor.com/api/projects/status/github/chef/chef?branch=master&svg=true&passingText=master%20-%20Ok&pendingText=master%20-%20Pending&failingText=master%20-%20Failing)](https://ci.appveyor.com/project/Chef/chef/branch/master) Want to try Chef? Get started with [learnchef](https://learn.chef.io) * Documentation: [http://docs.chef.io](http://docs.chef.io) * Source: [http://github.com/chef/chef/tree/master](http://github.com/chef/chef/tree/master) * Tickets/Issues: [https://github.com/chef/chef/issues](https://github.com/chef/chef/issues) * Slack: [Chef Community Slack](https://community-slack.chef.io/) * Mailing list: [https://discourse.chef.io](https://discourse.chef.io) Chef is a configuration management tool designed to bring automation to your entire infrastructure. This README focuses on developers who want to modify Chef source code. If you just want to use Chef, check out these resources: * [learnchef](https://learn.chef.io): Getting started guide * [docs.chef.io](http://docs.chef.io): Comprehensive User Docs * [Installer Downloads](https://www.chef.io/download-chef-client/): Install Chef as a complete package ## Installing From Git **NOTE:** Unless you have a specific reason to install from source (to try a new feature, contribute a patch, or run chef on an OS for which no package is available), you should head to the [installer page](https://www.chef.io/download-chef-client/) to get a prebuilt package. ### Prerequisites Install these via your platform's preferred method (`apt`, `yum`, `ports`, `emerge`, etc.): * git * C compiler, header files, etc. On Ubuntu/Debian, use the `build-essential` package. * ruby 2.1.0 or later * rubygems * bundler gem ### Chef Installation Then get the source and install it: ```bash # Clone this repo git clone https://github.com/chef/chef.git # cd into the source tree cd chef # Install dependencies with bundler bundle install # Build a gem bundle exec rake gem # Install the gem you just built gem install pkg/chef-VERSION.gem ``` ## Contributing/Development Before working on the code, if you plan to contribute your changes, you need to read the [Chef Contributions document](http://docs.chef.io/community_contributions.html). The general development process is: 1. Fork this repo and clone it to your workstation. 2. Create a feature branch for your change. 3. Write code and tests. 4. Push your feature branch to github and open a pull request against master. Once your repository is set up, you can start working on the code. We do utilize RSpec for test driven development, so you'll need to get a development environment running. Follow the above procedure ("Installing from Git") to get your local copy of the source running. ## Reporting Issues Issues can be reported by using [GitHub Issues](https://github.com/chef/chef/issues). Full details on how to report issues can be found in the [CONTRIBUTING](https://github.com/chef/chef/blob/master/CONTRIBUTING.md#-chef-issue-tracking) doc. Note that this repository is primarily for reporting chef-client issues. For reporting issues against other Chef projects, please look up the appropriate repository to report issues against in the Chef docs in the [community contributions section](https://docs.chef.io/community_contributions.html#issues-and-bug-reports). If you can't detemine the appropriate place to report an issue, then please open it against the repository you think best fits and it will be directed to the appropriate project. ## Testing We use RSpec for unit/spec tests. It is not necessary to start the development environment to run the specs--they are completely standalone. ```bash # Run All the Tests bundle exec rake spec # Run a Single Test File bundle exec rspec spec/PATH/TO/FILE_spec.rb # Run a Subset of Tests bundle exec rspec spec/PATH/TO/DIR ``` When you submit a pull request, we will automatically run the functional and unit tests in spec/functional/ and spec/unit/ respectively. These will be run on Ubuntu through Travis CI, and on Windows through AppVeyor. The status of these runs will be displayed with your pull request. ## Building the Full Package To build chef as a standalone package (with ruby and all dependent libraries included in a .deb, .rpm, .pkg or .msi), we use the omnibus system. Go to the [omnibus README](omnibus/README.md) to find out how to build! ## Updating Dependencies If you want to change our constraints (change which packages and versions we accept in the chef), there are several places to do so: * To add or remove a gem from chef, or update a gem version, edit [Gemfile](Gemfile). * To change the version of binary packages, edit [version_policy.rb](version_policy.rb). * To add new packages to chef, edit [omnibus/config/projects/chef.rb](omnibus/config/projects/chef.rb). Once you've made any changes you want, you have to update the lockfiles that actually drive the build: * To update chef's gem dependencies to the very latest versions available, run `rake bundle:update`. * To update chef's gem dependencies *conservatively* (changing as little as possible), run `rake bundle:install`. * To update specific gems only, run `rake bundle:update[gem1 gem2 ...]` * **`bundle update` and `bundle install` will *not* work, on purpose:** the rake task handles both the windows and non-windows lockfiles and updates them in sync. To perform a full update of all dependencies in chef (including binary packages, tests and build system dependencies), run `rake dependencies`. This will update the `Gemfile.lock`, `omnibus/Gemfile.lock`, `acceptance/Gemfile.lock`, `omnibus/Berksfile.lock`, and `omnibus_overrides.rb`. It will also show you any outdated dependencies due to conflicting constraints. Some outdated dependencies are to be expected; it will inform you if any new ones appear that we don't know about, and tell you how to proceed. # How Chef Builds and Versions Chef is an amalgam of many components. These components update all the time, necessitating new builds. This is an overview of the process of versioning, building and releasing Chef. ## Chef Packages Chef is distributed as packages for debian, rhel, ubuntu, windows, solaris, aix, and os x. It includes a large number of components from various sources, and these are versioned and maintained separately from chef project, which bundles them all together conveniently for the user. These packages go through several milestones: - `master`: When code is checked in to master, the patch version of chef is bumped (e.g. 0.9.10 -> 0.9.11) and a build is kicked off automatically to create and test the packages in Chef's Jenkins cluster. - `unstable`: When a package is built, it enters the unstable channel. When all packages for all OS's have successfully built, the test phase is kicked off in Jenkins across all supported OS's. These builds are password-protected and generally only available to the test systems. - `current`: If the packages pass all the tests on all supported OS's, it is promoted as a unit to `current`, and is available via Chef's artifactory by running `curl https://www.chef.io/chef/install.sh | sudo bash -s -- -c current -P chef` - `stable`: Periodically, Chef will pick a release to "bless" for folks who would like a slower update schedule than "every time a build passes the tests." When this happens, it is manually promoted to stable and an announcement is sent to the list. It can be reached at https://downloads.chef.io or installed using the `curl` command without specifying `-c current`. Packages in `stable` are no longer available in `current`. Additionally, periodically Chef will update the desired versions of chef components and check that in to `master`, triggering a new build with the updated components in it. ## Automated Version Bumping Whenever a change is checked in to `master`, the patch version of `chef` is bumped. To do this, the `lita-versioner` bot listens to github for merged PRs, and when it finds one, takes these actions: 1. Bumps the patch version in `lib/chef/version.rb` (e.g. 0.9.14 -> 0.9.15). 2. Runs `rake bundle:install` to update the `Gemfile.lock` to include the new version. 3. Pushes to `master` and submits a new build to Chef's Jenkins cluster. ## Bumping the minor version of Chef After each "official" stable release we need to bump the minor version. To do this: 1. Manually increment the minor version in the VERSION file that is in the root of this repo. and reset the patch version to 0. Assuming the current version is `12.10.57` you would edit `VERSION` to be `12.11.0`. 2. Run `bundle exec rake version` which will copy the version to the respective `version.rb` files in chef and chef-config. 3. Run `bundle exec rake bundle:install` to update the base Gemfile.lock Submit a PR with the changes made by the above. ## Component Versions Chef has two sorts of component: ruby components like `berkshelf` and `test-kitchen`, and binary components like `openssl` and even `ruby` itself. In general, you can find all chef desired versions in the [Gemfile](Gemfile) and [version_policy.rb](version_policy.rb) files. The [Gemfile.lock](Gemfile.lock) is the locked version of the Gemfile, and [omnibus_overrides](omnibus_overrides.rb) is the locked version of omnibus. [build](omnibus/Gemfile) and [test](acceptance/Gemfile) Gemfiles and [Berksfile](omnibus/Berksfile) version the toolset we use to build and test. ### Binary Components The versions of binary components (as well as rubygems and bundler, which can't be versioned in a Gemfile) are stored in [version_policy.rb](version_policy.rb) (the `OMNIBUS_OVERRIDES` constant) and locked in [omnibus_overrides](omnibus_overrides.rb). `rake dependencies` will update the `bundler` version, and the rest are be updated manually by Chef every so often. These have software definitions either in [omnibus/config/software](omnibus/config/software) or, more often, in the [omnibus-software](https://github.com/chef/omnibus-software/tree/master/config/software) project. ### Rubygems Components Most of the actual front-facing software in chef is composed of ruby projects. berkshelf, test-kitchen and even chef itself are made of ruby gems. Chef uses the typical ruby way of controlling rubygems versions, the `Gemfile`. Specifically, the `Gemfile` at the top of chef repository governs the version of every single gem we install into chef package. It's a one-stop shop. Our rubygems component versions are locked down with `Gemfile.lock`, and can be updated with `rake dependencies`. There are three gems versioned outside the `Gemfile`: `rubygems`, `bundler` and `chef`. `rubygems` and `bundler` are in the `RUBYGEMS_AT_LATEST_VERSION` constant in [version_policy.rb](version-policy.rb) and locked in [omnibus_overrides](omnibus_overrides.rb). They are kept up to date by `rake dependencies`. **Windows**: [Gemfile.lock](Gemfile.lock) is generated platform-agnostic, and then generated again for Windows. The one file has the solution for both Linux and Windows. The tool we use to generate Windows-specific lockfiles on non-Windows machines is [tasks/bin/bundle-platform](bundle-platform), which takes the first argument and sets `Gem.platforms`, and then calls `bundle` with the remaining arguments. ### Build Tooling Versions Of special mention is the software we use to build omnibus itself. There are two distinct bits of code that control the versions of compilers, make, git, and other tools we use to build. First, the Jenkins machines that run the build are configured entirely by the [opscode-ci cookbook](https://github.com/chef-cookbooks/opscode-ci) cookbook. They install most of the tools we use via `build-essentials`, and standardize the build environment so we can tear down and bring up builders at will. These machines are kept alive long-running, are periodically updated by Chef to the latest opscode-ci, omnibus and build-essentials cookbooks. Second, the version of omnibus we use to build chef is governed by `omnibus/Gemfile`. When software definitions or the omnibus framework is updated, this is the file that drives whether we pick it up. The omnibus tooling versions are locked down with `omnibus/Gemfile.lock`, and can be updated by running `rake dependencies`. ### Test Versions chef is tested by the [chef-acceptance framework](https://github.com/chef/chef-acceptance), which contains suites that are run on the Jenkins test machines. The definitions of the tests are in the `acceptance` directory. The version of chef-acceptance and test-kitchen, are governed by `acceptance/Gemfile`. The test tooling versions are locked down with `acceptance/Gemfile.lock`, which can be updated by running `rake dependencies`. ## The Build Process The actual Chef build process is done with Omnibus, and has several general steps: 1. `bundle install` from `chef/Gemfile.lock` 2. Reinstall any gems that came from git or path using `rake install` 3. appbundle chef, chef, test-kitchen and berkshelf 4. Put miscellaneous powershell scripts and cleanup ### Kicking Off The Build The build is kicked off in Jenkins by running this on the machine (which is already the correct OS and already has the correct dependencies, loaded by the `omnibus` cookbook): ``` load-omnibus-toolchain.bat cd chef/omnibus bundle install bundle exec omnibus build chef ``` This causes the [chef project definition](omnibus/config/projects/chef.rb) to load, which runs the [chef-complete](omnibus/config/software/chef-complete.rb) software definition, the primary software definition driving the whole build process. The reason we embed it all in a software definiton instead of the project is to take advantage of omnibus caching: omnibus will invalidate the entire project (and recompile ruby, openssl, and everything else) if you change anything at all in the project file. Not so with a software definition. ### Installing the Gems The primary build definition that installs the many Chef rubygems is [`software/chef.rb`](omnibus/software/chef.rb). This has dependencies on any binary libraries, ruby, rubygems and bundler. It has a lot of steps, so it uses a [library](omnibus/files/chef/build-chef.rb) to help reuse code and make it manageable to look at. What it does: 1. Depends on software defs for pre-cached gems (see "Gems and Caching" below). 2. Installs all gems from the bundle: - Sets up a `.bundle/config` ([code](omnibus/files/chef/build-chef.rb#L17-L39)) with --retries=4, --jobs=1, --without=development,no_, and `build.config.nokogiri` to pass. - Sets up a common environment, standardizing the compilers and flags we use, in [`env`](omnibus/files/chef-gem/build-chef-gem.rb#L32-L54). - [Runs](omnibus/config/software/chef.rb#L68) `bundle install --verbose` 3. Reinstalls any gems that were installed via path: - [Runs](omnibus/files/chef/build-chef.rb#L80) `bundle list --paths` to get the installed directories of all gems. - For each gem not installed in the main gem dir, [runs](omnibus/files/chef/build-chef.rb#L89) `rake install` from the installed gem directory. - [Deletes](omnibus/files/chef/build-chef.rb#L139-L143) the bundler git cache and path- and git-installed gems from the build. 4. [Creates](omnibus/files/chef/build-chef.rb#L102-L152) `/opt/chef/Gemfile` and `/opt/chef/Gemfile.lock` with the gems that were installed in the build. #### Gems and Caching Some gems take a super long time to install (particularly native-compiled ones such as nokogiri and dep-selector-libgecode) and do not change version very often. In order to avoid doing this work every time, we take advantage of omnibus caching by separating out these gems into their own software definitions. [chef-gem-dep-selector-libgecode](omnibus/config/software/chef-gem-dep-selector-libgecode.rb) for example. Each of these gems uses the `config/files/chef-gem/build-chef-gem` library to define itself. The name of the software definition itself indicates the . We only create software definitions for long-running gems. Everything else is just installed in the [chef](omnibus/config/software/chef.rb) software definition in a big `bundle install` catchall. Most gems we just install in the single `chef` software definition. The first thing ### Appbundling After the gems are installed, we *appbundle* them in [chef-appbundle](omnibus/config/software/chef-appbundle.rb). This creates binstubs that use the bundle to pin the software . During the process of appbundling, we update the gem's `Gemfile` to include the locks in the top level `/opt/chef/Gemfile.lock`, so we can guarantee they will never pick up things outside the build. We then run `bundle lock` to update the gem's `Gemfile.lock`, and `bundle check` to ensure all the gems are actually installed. The appbundler then uses these pins. ### Other Cleanup Finally, chef does several more steps including installing powershell scripts and shortcuts, and removing extra documentation to keep the build slim. # License Chef - A configuration management system | | | |:---------------------|:-----------------------------------------| | **Author:** | Adam Jacob () | **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. chef-12.14.60/RELEASE_NOTES.md000066400000000000000000000023261276456504500152450ustar00rootroot00000000000000*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/-/release_notes.html` for the official Chef release notes.* # Chef Client Release Notes 12.13: Highlights for this release: - Ohai 8.18 includes a new plugin for gathering available user shells. Additionally for OS X users there is a new hardware plugin that gathers system information, and we’ve added detection of VMware and VirtualBox installations. ## Updated Dependencies - ruby - 2.1.9 (was 2.1.8) ### Updated Gems - chef-zero - 4.8.0 (was 4.7.0) - cheffish - 2.0.5 (was 2.0.4) - compat_resource - 12.10.7 (was 12.10.6) - ffi - 1.9.14 (was 1.9.10) - ffi-yajl - 2.3.0 (was 2.2.3) - fuzzyurl - 0.9.0 (was 0.8.0) - mixlib-cli - 1.7.0 (was 1.6.0) - mixlib-log - 1.7.0 (was 1.6.0) - ohai - 8.18.0 (was 8.17.1) - pry - 0.10.4 (was 0.10.3) - rspec - 3.5.0 (was 3.4.0) - rspec-core - 3.5.2 (was 3.4.4) - rspec-expectations - 3.5.0 (was 3.4.0) - rspec-mocks - 3.5.0 (was 3.4.1) - rspec-support - 3.5.0 (was 3.4.1) - simplecov - 0.12.0 (was 0.11.2) - specinfra - 2.60.3 (was 2.59.4) - mixlib-archive - 0.2.0 (added to package) chef-12.14.60/ROADMAP.md000066400000000000000000000007701276456504500143010ustar00rootroot00000000000000# Roadmap This file provides direction for the project as set forth by [RFC030#Roadmap](https://github.com/chef/chef-rfc/blob/master/rfc030-maintenance-policy.md#roadmap). It is drafted by the Project Lead and the Lieutenants, with input from Maintainers and Contributors. ## 2016 Q1 * Windows - core windows_feature - Move windows_feature from Windows cookbook, add caching * Windows - knife windows bootstrap, knife windows winrm in core - Migrate Windows bootstrapping functionality into core chef-12.14.60/Rakefile000066400000000000000000000044621276456504500143430ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # 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. # VERSION = IO.read(File.expand_path("../VERSION", __FILE__)).strip require "rubygems" require "chef/version" require "chef-config/package_task" require "rdoc/task" require_relative "tasks/rspec" require_relative "tasks/maintainers" require_relative "tasks/cbgb" require_relative "tasks/dependencies" require_relative "tasks/changelog" ChefConfig::PackageTask.new(File.expand_path("..", __FILE__), "Chef", "chef") do |package| package.component_paths = ["chef-config"] package.generate_version_class = true end # Add a conservative dependency update to version:bump (which was created by PackageTask) task "version:bump" => %w{version:bump_patch version:update} task "version:bump" => %w{version:bump_patch version:update} task :pedant, :chef_zero_spec task :build_eventlog do Dir.chdir "ext/win32-eventlog/" do system "rake build" end end task :register_eventlog do Dir.chdir "ext/win32-eventlog/" do system "rake register" end end begin require "chefstyle" require "rubocop/rake_task" RuboCop::RakeTask.new(:style) do |task| task.options += ["--display-cop-names", "--no-color"] end rescue LoadError puts "chefstyle/rubocop is not available. gem install chefstyle to do style checking." end begin require "yard" DOC_FILES = [ "README.rdoc", "LICENSE", "spec/tiny_server.rb", "lib/**/*.rb" ] namespace :yard do desc "Create YARD documentation" YARD::Rake::YardocTask.new(:html) do |t| t.files = DOC_FILES t.options = ["--format", "html"] end end rescue LoadError puts "yard is not available. (sudo) gem install yard to generate yard documentation." end chef-12.14.60/VERSION000066400000000000000000000000101276456504500137270ustar00rootroot0000000000000012.14.60chef-12.14.60/acceptance/000077500000000000000000000000001276456504500147565ustar00rootroot00000000000000chef-12.14.60/acceptance/.gitignore000066400000000000000000000001001276456504500167350ustar00rootroot00000000000000.acceptance_logs .acceptance_data data-collector/Berksfile.lock chef-12.14.60/acceptance/.shared/000077500000000000000000000000001276456504500163025ustar00rootroot00000000000000chef-12.14.60/acceptance/.shared/kitchen_acceptance/000077500000000000000000000000001276456504500220755ustar00rootroot00000000000000chef-12.14.60/acceptance/.shared/kitchen_acceptance/.kitchen.digitalocean.yml000066400000000000000000000014311276456504500267440ustar00rootroot00000000000000# Not quite ready yet driver: name: digitalocean digitalocean_access_token: <%= ENV['DIGITALOCEAN_API_TOKEN'] %> region: <%= ENV['DIGITALOCEAN_REGION'] %> size: 2gb ssh_key_ids: <%= ENV['DIGITALOCEAN_SSH_KEYS'] %> transport: ssh_key: <%= ENV['DIGITALOCEAN_SSH_KEY_PATH'] %> provisioner: name: chef_zero product_name: chef product_version: latest channel: current # busser installation relies on this <% if ENV["KITCHEN_CHEF_PRODUCT"] %> verifier: chef_omnibus_root: "/opt/<%= ENV["KITCHEN_CHEF_PRODUCT"] %>" <% end %> platforms: <% %w(centos-6.5 centos-7.0 fedora-21 debian-8.1 ubuntu-12.04 ubuntu-14.04 ubuntu-15.10 ).each do |platform| %> - name: #{platform} driver_config: image: <%= "#{platform.gsub('.', '-')}-x64" %> <% end %> chef-12.14.60/acceptance/.shared/kitchen_acceptance/.kitchen.ec2.yml000066400000000000000000000160351276456504500250000ustar00rootroot00000000000000# Not quite ready yet <% def file_if_exists(path) path = File.expand_path(path) File.exist?(path) ? path : nil end %> driver: name: ec2 tags: X-Project: chef-ci-acceptance aws_ssh_key_id: <%= ENV['AWS_SSH_KEY_ID'] || ENV['USER'] || ENV['USERNAME'] %> # test-specific stuff region: us-west-2 subnet_id: subnet-19ac017c security_group_ids: ["sg-e401eb83", "sg-96274af3"] instance_type: m3.large # associate_public_ip: true # Don't enable public IP, as subnet specified is behind VPN # busser installation relies on this <% if ENV["KITCHEN_CHEF_PRODUCT"] %> verifier: chef_omnibus_root: "/opt/<%= ENV["KITCHEN_CHEF_PRODUCT"] %>" <% end %> transport: ssh_key: <%= file_if_exists("~/.ssh/#{ENV['AWS_SSH_KEY_ID'] || ENV['USER'] || ENV['USERNAME']}.pem") || file_if_exists("~/.ssh/#{ENV['AWS_SSH_KEY_ID'] || ENV['USER'] || ENV['USERNAME']}") || file_if_exists("~/.ssh/id_rsa") %> provisioner: name: chef_zero product_name: <%= ENV["KITCHEN_CHEF_PRODUCT"] %> product_version: <%= ENV["KITCHEN_CHEF_VERSION"] %> channel: <%= ENV["KITCHEN_CHEF_CHANNEL"] %> client_rb: audit_mode: :enabled attributes: chef_acceptance: "true" use_system_chef: "true" platforms: # # AIX # # - name: aix-6.1 # - name: aix-7.1 # # Debian # - name: debian-8 driver: image_search: name: debian-jessie-* owner-id: "379101102735" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine transport: username: admin - name: debian-7 driver: image_search: name: debian-wheezy-* owner-id: "379101102735" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: standard image-type: machine transport: username: admin # # Ubuntu # - name: ubuntu-15.10 driver: image_search: name: ubuntu/images/*/ubuntu-*-15.10-amd64-server-* owner-id: "099720109477" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine transport: username: ubuntu - name: ubuntu-14.04 driver: image_search: name: ubuntu/images/*/ubuntu-*-14.04-*-server-* owner-id: "099720109477" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine transport: username: ubuntu - name: ubuntu-12.04 driver: image_search: name: ubuntu/images/*/ubuntu-*-12.04-*-server-* owner-id: "099720109477" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine transport: username: ubuntu # # Red Hat Enterprise Linux # - name: el-7 driver: image_search: name: RHEL-7.* owner-id: "309956199498" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine transport: username: ec2-user - name: el-6 driver: image_search: name: RHEL-6.* owner-id: "309956199498" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine transport: username: ec2-user - name: el-5 driver: image_search: name: RHEL-5.* owner-id: "309956199498" architecture: x86_64 virtualization-type: paravirtual block-device-mapping.volume-type: gp2 image-type: machine transport: username: ec2-user # # FreeBSD # - name: freebsd-10 driver: image_search: name: FreeBSD/EC2 10.*-RELEASE* owner-id: "118940168514" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine transport: username: ec2-user - name: freebsd-9 driver: image_search: name: FreeBSD/EC2 9.*-RELEASE* owner-id: "118940168514" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine transport: username: ec2-user - name: freebsd-8 driver: image_search: name: FreeBSD/EC2 8.*-RELEASE* owner-id: "118940168514" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: standard image-type: machine transport: username: ec2-user # # OS/X # # - name: mac_os_x-10.11 # - name: mac_os_x-10.10 # - name: mac_os_x-10.9 # - name: mac_os_x-10.8 # # Nexus??? # # - name: nexus-7 # # Solaris # # - name: solaris-11 # - name: solaris-10 # # Windows # - name: windows-2012r2 provisioner: architecture: <%= ENV["KITCHEN_CHEF_WIN_ARCHITECTURE"] %> driver: image_search: name: Windows_Server-2012-R2*-English-*-Base-* owner-alias: amazon architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine user_data: | & netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" profile=public protocol=tcp localport=5985 remoteip=localsubnet new remoteip=any #Set script execution to unrestricted & Set-ExecutionPolicy Unrestricted -Force transport: username: administrator - name: windows-2012 driver: image_search: name: Windows_Server-2012-RTM*-English-*-Base-* owner-alias: amazon architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine transport: username: administrator - name: windows-2008r2 driver: image_search: name: Windows_Server-2008-R2*-English-*-Base-* owner-alias: amazon architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine transport: username: administrator # # Centos # - name: centos-7 driver: image_search: name: CentOS Linux 7 * owner-alias: aws-marketplace architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: standard image-type: machine transport: username: root - name: centos-6 driver: image_search: name: CentOS-6.5-GA-* owner-alias: aws-marketplace architecture: x86_64 virtualization-type: paravirtual block-device-mapping.volume-type: standard image-type: machine transport: username: root # # Fedora # - name: fedora-21 driver: image_search: name: Fedora-Cloud-Base-21-* owner-id: "125523088429" architecture: x86_64 virtualization-type: hvm block-device-mapping.volume-type: gp2 image-type: machine chef-12.14.60/acceptance/.shared/kitchen_acceptance/.kitchen.vagrant.yml000066400000000000000000000022061276456504500257640ustar00rootroot00000000000000driver: name: vagrant forward_agent: yes customize: cpus: 2 memory: 1024 provisioner: name: chef_zero product_name: <%= ENV["KITCHEN_CHEF_PRODUCT"] %> product_version: <%= ENV["KITCHEN_CHEF_VERSION"] %> channel: <%= ENV["KITCHEN_CHEF_CHANNEL"] %> client_rb: audit_mode: :enabled attributes: chef_acceptance: "true" use_system_chef: "true" # busser installation relies on this <% if ENV["KITCHEN_CHEF_PRODUCT"] %> verifier: chef_omnibus_root: "/opt/<%= ENV["KITCHEN_CHEF_PRODUCT"] %>" <% end %> platforms: <% %w( debian-8 debian-7 debian-6 ubuntu-15.10 ubuntu-14.04 el-7 el-6 el-5 freebsd-10 freebsd-9 fedora-21 ).each do |platform| %> - name: <%= platform %> driver: box: opscode-<%= platform %> box_url: http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_<%= platform %>_chef-provisionerless.box <% end %> # freebsd-8 # ubuntu-12.04 # centos-7 - name: centos-6 driver: box: bento/centos-6.7 <% %w( 2012r2 2012 2008r2 ).each do |version| %> - name: windows-<%= version %> driver: box: chef/windows-server-<%= version %>-standard # URL is atlas <% end %> chef-12.14.60/acceptance/.shared/kitchen_acceptance/libraries/000077500000000000000000000000001276456504500240515ustar00rootroot00000000000000chef-12.14.60/acceptance/.shared/kitchen_acceptance/libraries/kitchen.rb000066400000000000000000000057601276456504500260330ustar00rootroot00000000000000require 'chef/mixin/shell_out' module KitchenAcceptance class Kitchen < Chef::Resource resource_name :kitchen property :command, String, name_property: true property :driver, %w(ec2 vagrant), coerce: proc { |v| v.to_s }, default: lazy { ENV["KITCHEN_DRIVER"] || :ec2 } property :instances, String, default: lazy { ENV["KITCHEN_INSTANCES"] ? ENV["KITCHEN_INSTANCES"] : "" } property :kitchen_dir, String, default: Chef.node['chef-acceptance']['suite-dir'] property :chef_product, String, default: lazy { ENV["KITCHEN_CHEF_PRODUCT"] || begin # Figure out if we're in chefdk or chef if ::File.exist?(::File.expand_path("../../chef-dk.gemspec", node['chef-acceptance']['suite-dir'])) "chefdk" else "chef" end end } property :chef_channel, String, default: lazy { ENV["KITCHEN_CHEF_CHANNEL"] || # Pick up current if we can't connect to artifactory (ENV["ARTIFACTORY_USERNAME"] ? "unstable" : "current") } property :chef_version, String, default: lazy { ENV["KITCHEN_CHEF_VERSION"] || # If we're running the chef or chefdk projects in jenkins, pick up the project name. (ENV["PROJECT_NAME"] == chef_product ? ENV["OMNIBUS_BUILD_VERSION"] : nil) || "latest" } property :artifactory_username, String, default: lazy { ENV["ARTIFACTORY_USERNAME"] ? ENV["ARTIFACTORY_USERNAME"] : "" } property :artifactory_password, String, default: lazy { ENV["ARTIFACTORY_PASSWORD"] ? ENV["ARTIFACTORY_PASSWORD"] : "" } property :env, Hash, default: {} property :kitchen_options, String, default: lazy { ENV["PROJECT_NAME"] ? "-c -l debug" : "-c" } action :run do ruby_block "copy_kitchen_logs_to_data_path" do block do cmd_env = { "KITCHEN_DRIVER" => driver, "KITCHEN_INSTANCES" => instances, "KITCHEN_LOCAL_YAML" => ::File.expand_path("../../.kitchen.#{driver}.yml", __FILE__), "KITCHEN_CHEF_PRODUCT" => chef_product, "KITCHEN_CHEF_CHANNEL" => chef_channel, "KITCHEN_CHEF_VERSION" => chef_version, "ARTIFACTORY_USERNAME" => artifactory_username, "ARTIFACTORY_PASSWORD" => artifactory_password }.merge(new_resource.env) suite = kitchen_dir.split("/").last kitchen_log_path = ENV["WORKSPACE"] ? "#{ENV["WORKSPACE"]}/chef-acceptance-data/logs" : "#{kitchen_dir}/../.acceptance_data/logs/" begin shell_out!("bundle exec kitchen #{command}#{instances ? " #{instances}" : ""}#{kitchen_options ? " #{kitchen_options}" : ""}", env: cmd_env, timeout: 60 * 30, live_stream: STDOUT, cwd: kitchen_dir) ensure FileUtils.mkdir_p("#{kitchen_log_path}/#{suite}/#{command}") FileUtils.cp_r("#{kitchen_dir}/.kitchen/logs/.", "#{kitchen_log_path}/#{suite}/#{command}") end end end end end end chef-12.14.60/acceptance/.shared/kitchen_acceptance/metadata.rb000066400000000000000000000000321276456504500241750ustar00rootroot00000000000000name "kitchen_acceptance" chef-12.14.60/acceptance/Gemfile000066400000000000000000000006021276456504500162470ustar00rootroot00000000000000source "https://rubygems.org" gem "chef-acceptance", github: "chef/chef-acceptance" gem "kitchen-ec2" gem "inspec" # Pinning to github for kitchen-vagrant because 0.19.0 incorrectly # puts in a box_url for bento when a vagrant box in atlas is specified gem "kitchen-vagrant" gem "windows_chef_zero" gem "kitchen-inspec" gem "test-kitchen" gem "winrm-elevated" gem "berkshelf", "4.3.5" chef-12.14.60/acceptance/Gemfile.lock000066400000000000000000000133701276456504500172040ustar00rootroot00000000000000GIT remote: git://github.com/chef/chef-acceptance.git revision: e92ddae46d2126864698b9c8e4fc4ec2dcc46c55 specs: chef-acceptance (0.2.0) mixlib-shellout (~> 2.0) thor (~> 0.19) GEM remote: https://rubygems.org/ specs: addressable (2.4.0) artifactory (2.3.3) aws-sdk (2.5.8) aws-sdk-resources (= 2.5.8) aws-sdk-core (2.5.8) jmespath (~> 1.0) aws-sdk-resources (2.5.8) aws-sdk-core (= 2.5.8) berkshelf (4.3.5) addressable (~> 2.3, >= 2.3.4) berkshelf-api-client (~> 2.0, >= 2.0.2) buff-config (~> 1.0) buff-extensions (~> 1.0) buff-shell_out (~> 0.1) celluloid (= 0.16.0) celluloid-io (~> 0.16.1) cleanroom (~> 1.0) faraday (~> 0.9) httpclient (~> 2.7) minitar (~> 0.5, >= 0.5.4) mixlib-archive (~> 0.1) octokit (~> 4.0) retryable (~> 2.0) ridley (~> 4.5) solve (~> 2.0) thor (~> 0.19) berkshelf-api-client (2.0.2) faraday (~> 0.9.1) httpclient (~> 2.7.0) ridley (~> 4.5) buff-config (1.0.1) buff-extensions (~> 1.0) varia_model (~> 0.4) buff-extensions (1.0.0) buff-ignore (1.1.1) 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.13.37) fuzzyurl mixlib-config (~> 2.0) mixlib-shellout (~> 2.0) cleanroom (1.0.0) coderay (1.1.1) diff-lcs (1.2.5) docker-api (1.31.0) excon (>= 0.38.0) json erubis (2.7.0) excon (0.52.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) ffi (1.9.14) fuzzyurl (0.9.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) httpclient (2.7.2) inspec (0.32.0) hashie (~> 3.4) json (>= 1.8, < 3.0) method_source (~> 0.8) mixlib-log pry (~> 0) rainbow (~> 2) rspec (~> 3) rspec-its (~> 1.2) rubyzip (~> 1.1) sslshake (~> 1) thor (~> 0.19) train (>= 0.16.0, < 1.0) jmespath (1.3.1) json (2.0.2) kitchen-ec2 (1.1.0) aws-sdk (~> 2) excon multi_json retryable (~> 2.0) test-kitchen (~> 1.4, >= 1.4.1) kitchen-inspec (0.15.1) 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-archive (0.2.0) mixlib-log mixlib-authentication (1.4.1) mixlib-log mixlib-config (2.2.4) mixlib-install (1.1.0) artifactory mixlib-shellout mixlib-versioning mixlib-log (1.7.1) mixlib-shellout (2.2.7) mixlib-versioning (1.1.0) molinillo (0.4.5) multi_json (1.12.1) multipart-post (2.0.0) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (3.2.0) net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) nio4r (1.2.1) nori (2.6.0) octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) rainbow (2.1.0) retryable (2.0.4) ridley (4.6.1) addressable buff-config (~> 1.0) buff-extensions (~> 1.0) buff-ignore (~> 1.1.1) buff-shell_out (~> 0.1) celluloid (~> 0.16.0) celluloid-io (~> 0.16.1) chef-config (>= 12.5.0) erubis faraday (~> 0.9.0) hashie (>= 2.0.2, < 4.0.0) httpclient (~> 2.7) json (>= 1.7.7) mixlib-authentication (>= 1.3.0) retryable (~> 2.0) semverse (~> 1.1) varia_model (~> 0.4.0) rspec (3.5.0) rspec-core (~> 3.5.0) rspec-expectations (~> 3.5.0) rspec-mocks (~> 3.5.0) rspec-core (3.5.3) rspec-support (~> 3.5.0) rspec-expectations (3.5.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.5.0) rspec-its (1.2.0) rspec-core (>= 3.0.0) rspec-expectations (>= 3.0.0) rspec-mocks (3.5.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.5.0) rspec-support (3.5.0) 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) sslshake (1.0.12) test-kitchen (1.12.0) mixlib-install (~> 1.0, >= 1.0.4) mixlib-shellout (>= 1.2, < 3.0) net-scp (~> 1.1) net-ssh (>= 2.9, < 4.0) net-ssh-gateway (~> 1.2.0) safe_yaml (~> 1.0) thor (~> 0.18) thor (0.19.1) timers (4.0.4) hitimes train (0.19.0) docker-api (~> 1.26) json (>= 1.8, < 3.0) mixlib-shellout (~> 2.0) net-scp (~> 1.2) net-ssh (>= 2.9, < 4.0) winrm (~> 2.0) winrm-fs (~> 1.0) varia_model (0.4.1) buff-extensions (~> 1.0) hashie (>= 2.0.2, < 4.0.0) windows_chef_zero (2.0.0) test-kitchen (>= 1.2.1) winrm (2.0.1) builder (>= 2.1.2) erubis (~> 2.7) 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-elevated (1.0.0) winrm (~> 2.0) winrm-fs (~> 1.0) winrm-fs (1.0.0) erubis (~> 2.7) logging (>= 1.6.1, < 3.0) rubyzip (~> 1.1) winrm (~> 2.0) PLATFORMS ruby DEPENDENCIES berkshelf (= 4.3.5) chef-acceptance! inspec kitchen-ec2 kitchen-inspec kitchen-vagrant test-kitchen windows_chef_zero winrm-elevated BUNDLED WITH 1.12.5 chef-12.14.60/acceptance/README.md000066400000000000000000000113671276456504500162450ustar00rootroot00000000000000# Acceptance Testing for Chef Client This folder contains acceptance tests that are required for Chef client release readiness. ## Getting started The tests use the _chef-acceptance_ gem as the high level framework. All the gems needed to run these tests can be installed with Bundler. ### Important Note! Before running chef-acceptance, you *MUST* do the following on your current session: ``` export APPBUNDLER_ALLOW_RVM=true ``` ## Pre-requisites / One time set up ### Set up for local VM (Vagrant) If you intend to run the acceptance tests on a local VM, the supported solution is to use Vagrant. Ensure that Vagrant is installed on the machine that tests will run from, along with a virtualization driver (E.g.: VirtualBox). Set up the KITCHEN_DRIVER environment variable appropriately (value should be "vagrant"). E.g.: ``` export KITCHEN_DRIVER=vagrant ``` Add this to your shell profile or startup script as needed. ### Set up for cloud VM (EC2) If you intend to run the acceptance tests on a cloud VM, the supported solution is to use EC2. The steps you will need to do are: 1. Add your AWS credentials to the machine - e.g., to the ~/.aws/credentials directory. - The easiest way to do this is to download the aws command line (`brew install awscli` on OS/X) and run `aws configure`. 2. Create or import a SSH key to AWS. (If you already have one, you can skip the import/create) - In the AWS console, click Key Pairs, then Create Key Pair or Import Key Pair. 3. Copy or move the private key file (USERNAME.pem) to the SSH folder (e.g. `~/.ssh/USERNAME.pem`). - If you Created a key pair in step 2, download the private key and move it to `~/.ssh`. - If you Importd a key pair in step 2, just ensure your private key is in `~/.ssh` and has the same name as the key pair (`~/.ssh/USERNAME` or `~/.ssh/USERNAME.pem`). 4. Set AWS_SSH_KEY_ID to the SSH key name. - This is **optional** if your AWS SSH key name is your local username. - You may want to set this in your shell `.profile`. ```shell export AWS_SSH_KEY_ID=name-of-private-key ``` 5. Set the private key to only be readable by root ```shell chmod 0400 ~/.ssh/USERNAME.pem ``` 6. Set up the KITCHEN_DRIVER environment variable appropriately (value should be "ec2"). (This is optional, as ec2 is the default.) E.g.: ```shell export KITCHEN_DRIVER=ec2 ``` Add this to your shell profile or startup script as needed. 7. **Connect to Chef VPN**. The instances you create will not have public IPs! ## Setting up and running a test suite To get started, do a bundle install from the acceptance directory: ```shell chef/acceptance$ bundle install --binstubs ``` To get some basic info and ensure chef-acceptance can be run, do: ```shell chef/acceptance$ bin/chef-acceptance info ``` To run a particular test suite, do the following: ```shell chef/acceptance$ bin/chef-acceptance test TEST_SUITE ``` To restrict which OS's will run, use the KITCHEN_INSTANCES environment variable: ```shell chef/acceptance$ export KITCHEN_INSTANCES=*-ubuntu-1404 chef/acceptance$ bin/chef-acceptance test cookbook-git ``` If KITCHEN_INSTANCES is not specified, the default instances are default-ubuntu-1404 and default-windows-windows-2012r2. All selected instances will be run in *parallel* if the driver supports it (ec2 does, vagrant doesn't). ## Optional Settings In addition to the environment settings above, there are a number of key values that are available to set for changing the way the acceptance tests are run. ### KITCHEN_CHEF_CHANNEL Use this setting to specify which channel we will pull the chef build from. The default is to use the "current" channel, unless the ARTIFACTORY_USERNAME is set (which normally happens when running under Jenkins), in which case the default is changed to "unstable". ```shell export KITCHEN_CHEF_CHANNEL=name-of-channel ``` ### KITCHEN_CHEF_VERSION Use this setting to override the version of the Chef client that is installed. The default is to get the latest version in the desired channel. ```shell export KITCHEN_CHEF_VERSION=version-of-chef-client ``` ### ARTIFACTORY_USERNAME / ARTIFACTORY_PASSWORD If the desired channel to get the Chef client from is "unstable", the following settings need to be exported: ```shell export ARTIFACTORY_USERNAME=username export ARTIFACTORY_PASSWORD=password ``` ## Future Work Currently, there is no simple mechanism for chef-acceptance to build an Omnibus package of the developers local chef instance and run acceptance tests on it - the only packages that can be exercised are ones that come from one of the pipeline channels (unstable, current or stable). This is not an issue when adding acceptance tests for pre-existing functionality (as that functionality is presumed to already be in a build in one of the pipeline channels). chef-12.14.60/acceptance/basics/000077500000000000000000000000001276456504500162225ustar00rootroot00000000000000chef-12.14.60/acceptance/basics/.acceptance/000077500000000000000000000000001276456504500203665ustar00rootroot00000000000000chef-12.14.60/acceptance/basics/.acceptance/acceptance-cookbook/000077500000000000000000000000001276456504500242605ustar00rootroot00000000000000chef-12.14.60/acceptance/basics/.acceptance/acceptance-cookbook/.gitignore000066400000000000000000000000141276456504500262430ustar00rootroot00000000000000nodes/ tmp/ chef-12.14.60/acceptance/basics/.acceptance/acceptance-cookbook/metadata.rb000066400000000000000000000000711276456504500263630ustar00rootroot00000000000000name 'acceptance-cookbook' depends "kitchen_acceptance" chef-12.14.60/acceptance/basics/.acceptance/acceptance-cookbook/recipes/000077500000000000000000000000001276456504500257125ustar00rootroot00000000000000chef-12.14.60/acceptance/basics/.acceptance/acceptance-cookbook/recipes/destroy.rb000066400000000000000000000000221276456504500277220ustar00rootroot00000000000000kitchen "destroy" chef-12.14.60/acceptance/basics/.acceptance/acceptance-cookbook/recipes/provision.rb000066400000000000000000000000231276456504500302620ustar00rootroot00000000000000kitchen "converge" chef-12.14.60/acceptance/basics/.acceptance/acceptance-cookbook/recipes/verify.rb000066400000000000000000000000211276456504500275340ustar00rootroot00000000000000kitchen "verify" chef-12.14.60/acceptance/basics/.kitchen.yml000066400000000000000000000001511276456504500204450ustar00rootroot00000000000000suites: - name: chef-current-install includes: [ubuntu-14.04, windows-server-2012r2] run_list: chef-12.14.60/acceptance/basics/test/000077500000000000000000000000001276456504500172015ustar00rootroot00000000000000chef-12.14.60/acceptance/basics/test/integration/000077500000000000000000000000001276456504500215245ustar00rootroot00000000000000chef-12.14.60/acceptance/basics/test/integration/chef-current-install/000077500000000000000000000000001276456504500255555ustar00rootroot00000000000000chef-12.14.60/acceptance/basics/test/integration/chef-current-install/serverspec/000077500000000000000000000000001276456504500277365ustar00rootroot00000000000000chef-12.14.60/acceptance/basics/test/integration/chef-current-install/serverspec/chef_client_spec.rb000066400000000000000000000012441276456504500335410ustar00rootroot00000000000000 require "spec_helper" gem_path = "/opt/chef/embedded/bin/gem" white_list = %w{chef-config json rake} describe "gem list" do it "should not have non-whitelisted duplicate gems" do gems = command("#{gem_path} list").stdout duplicate_gems = gems.lines().select { |l| l.include?(",") }.collect { |l| l.split(" ").first } puts "Duplicate gems found: #{duplicate_gems}" if duplicate_gems.length > 0 non_whitelisted_duplicates = duplicate_gems.select { |l| !white_list.include?(l) } puts "Non white listed duplicates: #{non_whitelisted_duplicates}" if non_whitelisted_duplicates.length > 0 (non_whitelisted_duplicates.length).should be == 0 end end chef-12.14.60/acceptance/basics/test/integration/chef-current-install/serverspec/spec_helper.rb000066400000000000000000000001451276456504500325540ustar00rootroot00000000000000require "serverspec" require "pathname" set :backend, :exec set :path, "/bin:/usr/local/bin:$PATH" chef-12.14.60/acceptance/basics/test/integration/helpers/000077500000000000000000000000001276456504500231665ustar00rootroot00000000000000chef-12.14.60/acceptance/basics/test/integration/helpers/serverspec/000077500000000000000000000000001276456504500253475ustar00rootroot00000000000000chef-12.14.60/acceptance/basics/test/integration/helpers/serverspec/Gemfile000066400000000000000000000003661276456504500266470ustar00rootroot00000000000000source "https://rubygems.org" # Until https://github.com/test-kitchen/busser-serverspec/pull/42 is merged and # released, we need to include rake and rspec-core in the Gemfile gem "rake" gem "rspec-core" gem "busser-serverspec" gem "serverspec" chef-12.14.60/acceptance/data-collector/000077500000000000000000000000001276456504500176535ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/000077500000000000000000000000001276456504500220175ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/acceptance-cookbook/000077500000000000000000000000001276456504500257115ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/acceptance-cookbook/.gitignore000066400000000000000000000000141276456504500276740ustar00rootroot00000000000000nodes/ tmp/ chef-12.14.60/acceptance/data-collector/.acceptance/acceptance-cookbook/metadata.rb000066400000000000000000000001261276456504500300150ustar00rootroot00000000000000name 'acceptance-cookbook' depends "kitchen_acceptance" depends "data-collector-test" chef-12.14.60/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/000077500000000000000000000000001276456504500273435ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/destroy.rb000066400000000000000000000002051276456504500313560ustar00rootroot00000000000000log "Running 'destroy' recipe from the acceptance-cookbook in directory '#{node['chef-acceptance']['suite-dir']}'" kitchen "destroy" chef-12.14.60/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/provision.rb000066400000000000000000000002101276456504500317110ustar00rootroot00000000000000log "Running 'provision' recipe from the acceptance-cookbook in directory '#{node['chef-acceptance']['suite-dir']}'" kitchen "converge" chef-12.14.60/acceptance/data-collector/.acceptance/acceptance-cookbook/recipes/verify.rb000066400000000000000000000002031276456504500311670ustar00rootroot00000000000000log "Running 'verify' recipe from the acceptance-cookbook in directory '#{node['chef-acceptance']['suite-dir']}'" kitchen "verify" chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/000077500000000000000000000000001276456504500256715ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/.gitignore000066400000000000000000000001761276456504500276650ustar00rootroot00000000000000.vagrant Berksfile.lock *~ *# .#* \#*# .*.sw[a-z] *.un~ # Bundler Gemfile.lock bin/* .bundle/* .kitchen/ .kitchen.local.yml chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/Berksfile000066400000000000000000000000571276456504500275240ustar00rootroot00000000000000source 'https://supermarket.chef.io' metadata chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/files/000077500000000000000000000000001276456504500267735ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/files/default/000077500000000000000000000000001276456504500304175ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/files/default/api.rb000066400000000000000000000025241276456504500315200ustar00rootroot00000000000000require "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 chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/files/default/apigemfile000066400000000000000000000000551276456504500324440ustar00rootroot00000000000000source "https://rubygems.org" gem "sinatra" client-rb-both-mode.rb000066400000000000000000000003061276456504500344170ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/files/defaultchef_server_url "http://localhost:8889" node_name "data-collector-test" data_collector.server_url "http://localhost:9292/data-collector/v0" data_collector.mode :both client-rb-client-mode.rb000066400000000000000000000003101276456504500347340ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/files/defaultchef_server_url "http://localhost:8889" node_name "data-collector-test" data_collector.server_url "http://localhost:9292/data-collector/v0" data_collector.mode :client client-rb-no-endpoint.rb000066400000000000000000000001161276456504500347720ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/files/defaultchef_server_url "http://localhost:8889" node_name "data-collector-test" client-rb-solo-mode.rb000066400000000000000000000003061276456504500344370ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/files/defaultchef_server_url "http://localhost:8889" node_name "data-collector-test" data_collector.server_url "http://localhost:9292/data-collector/v0" data_collector.mode :solo chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/files/default/config.ru000066400000000000000000000000621276456504500322320ustar00rootroot00000000000000require_relative "./api" run Sinatra::Application chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/metadata.rb000066400000000000000000000003541276456504500300000ustar00rootroot00000000000000name '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' chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/recipes/000077500000000000000000000000001276456504500273235ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/.acceptance/data-collector-test/recipes/default.rb000066400000000000000000000015171276456504500313000ustar00rootroot00000000000000api_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 chef-12.14.60/acceptance/data-collector/.kitchen.yml000066400000000000000000000002231276456504500220760ustar00rootroot00000000000000verifier: name: busser suites: - name: default includes: - ubuntu-14.04 run_list: - recipe[data-collector-test::default] chef-12.14.60/acceptance/data-collector/Berksfile000066400000000000000000000002461276456504500215060ustar00rootroot00000000000000source "https://supermarket.chef.io" cookbook "data-collector-test", path: File.join(File.expand_path(File.dirname(__FILE__)), ".acceptance", "data-collector-test") chef-12.14.60/acceptance/data-collector/test/000077500000000000000000000000001276456504500206325ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/test/integration/000077500000000000000000000000001276456504500231555ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/test/integration/default/000077500000000000000000000000001276456504500246015ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/test/integration/default/serverspec/000077500000000000000000000000001276456504500267625ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/test/integration/default/serverspec/default_spec.rb000066400000000000000000000152751276456504500317570ustar00rootroot00000000000000# # Author:: Adam Leff ( 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" 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" 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" end chef-12.14.60/acceptance/data-collector/test/integration/helpers/000077500000000000000000000000001276456504500246175ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/test/integration/helpers/serverspec/000077500000000000000000000000001276456504500270005ustar00rootroot00000000000000chef-12.14.60/acceptance/data-collector/test/integration/helpers/serverspec/Gemfile000066400000000000000000000003661276456504500303000ustar00rootroot00000000000000source "https://rubygems.org" # Until https://github.com/test-kitchen/busser-serverspec/pull/42 is merged and # released, we need to include rake and rspec-core in the Gemfile gem "rake" gem "rspec-core" gem "busser-serverspec" gem "serverspec" chef-12.14.60/acceptance/fips/000077500000000000000000000000001276456504500157175ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/.acceptance/000077500000000000000000000000001276456504500200635ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/.acceptance/acceptance-cookbook/000077500000000000000000000000001276456504500237555ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/.acceptance/acceptance-cookbook/.gitignore000066400000000000000000000000141276456504500257400ustar00rootroot00000000000000nodes/ tmp/ chef-12.14.60/acceptance/fips/.acceptance/acceptance-cookbook/metadata.rb000066400000000000000000000000701276456504500260570ustar00rootroot00000000000000name "acceptance-cookbook" depends "kitchen_acceptance" chef-12.14.60/acceptance/fips/.acceptance/acceptance-cookbook/recipes/000077500000000000000000000000001276456504500254075ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/.acceptance/acceptance-cookbook/recipes/destroy.rb000066400000000000000000000000221276456504500274170ustar00rootroot00000000000000kitchen "destroy" chef-12.14.60/acceptance/fips/.acceptance/acceptance-cookbook/recipes/provision.rb000066400000000000000000000000231276456504500277570ustar00rootroot00000000000000kitchen "converge" chef-12.14.60/acceptance/fips/.acceptance/acceptance-cookbook/recipes/verify.rb000066400000000000000000000000211276456504500272310ustar00rootroot00000000000000kitchen "verify" chef-12.14.60/acceptance/fips/.kitchen.yml000066400000000000000000000002611276456504500201440ustar00rootroot00000000000000suites: - name: fips-unit-functional includes: [centos-6, windows-2012r2] run_list: - name: fips-integration includes: [centos-6, windows-2012r2] run_list: chef-12.14.60/acceptance/fips/test/000077500000000000000000000000001276456504500166765ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/test/integration/000077500000000000000000000000001276456504500212215ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/test/integration/fips-integration/000077500000000000000000000000001276456504500245035ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/test/integration/fips-integration/serverspec/000077500000000000000000000000001276456504500266645ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/test/integration/fips-integration/serverspec/Gemfile000066400000000000000000000004141276456504500301560ustar00rootroot00000000000000source "https://rubygems.org" # Until https://github.com/test-kitchen/busser-serverspec/pull/42 is merged and # released, we need to include rake and rspec-core in the Gemfile gem "rake" gem "rspec-core" gem "busser-serverspec" gem "serverspec" gem "mixlib-shellout" chef-12.14.60/acceptance/fips/test/integration/fips-integration/serverspec/fips-integration_spec.rb000066400000000000000000000021561276456504500335110ustar00rootroot00000000000000require "mixlib/shellout" require "bundler" describe "Chef Fips Integration Specs" do def windows? if RUBY_PLATFORM =~ /mswin|mingw|windows/ true else false end end let(:omnibus_root) do if windows? "c:/opscode/chef" else "/opt/chef" end end let(:env) do { "PATH" => [ "#{omnibus_root}/embedded/bin", ENV["PATH"] ].join(File::PATH_SEPARATOR), "BUNDLE_GEMFILE" => "#{omnibus_root}/Gemfile", "GEM_PATH" => nil, "GEM_CACHE" => nil, "GEM_HOME" => nil, "BUNDLE_IGNORE_CONFIG" => "true", "BUNDLE_FROZEN" => "1", "CHEF_FIPS" => "1" } end let(:chef_dir) do cmd = Mixlib::ShellOut.new("bundle show chef", env: env).run_command cmd.error! cmd.stdout.chomp end def run_rspec_test(test) Bundler.with_clean_env do cmd = Mixlib::ShellOut.new( "bundle exec rspec -f documentation -t ~requires_git #{test}", env: env, cwd: chef_dir, timeout: 3600 ) cmd.run_command.error! end end it "passes the integration specs" do skip #run_rspec_test("spec/integration") end end chef-12.14.60/acceptance/fips/test/integration/fips-unit-functional/000077500000000000000000000000001276456504500252775ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/test/integration/fips-unit-functional/serverspec/000077500000000000000000000000001276456504500274605ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/test/integration/fips-unit-functional/serverspec/Gemfile000066400000000000000000000003431276456504500307530ustar00rootroot00000000000000source "https://rubygems.org" # Until https://github.com/test-kitchen/busser-serverspec/pull/42 is merged and # released, we need to include rake and rspec-core in the Gemfile gem "rake" gem "rspec-core" gem "mixlib-shellout" fips-unit-functional_spec.rb000066400000000000000000000022561276456504500350230ustar00rootroot00000000000000chef-12.14.60/acceptance/fips/test/integration/fips-unit-functional/serverspecrequire "mixlib/shellout" require "bundler" describe "Chef Fips Unit/Functional Specs" do def windows? if RUBY_PLATFORM =~ /mswin|mingw|windows/ true else false end end let(:omnibus_root) do if windows? "c:/opscode/chef" else "/opt/chef" end end let(:env) do { "PATH" => [ "#{omnibus_root}/embedded/bin", ENV["PATH"] ].join(File::PATH_SEPARATOR), "BUNDLE_GEMFILE" => "#{omnibus_root}/Gemfile", "GEM_PATH" => nil, "GEM_CACHE" => nil, "GEM_HOME" => nil, "BUNDLE_IGNORE_CONFIG" => "true", "BUNDLE_FROZEN" => "1", "CHEF_FIPS" => "1" } end let(:chef_dir) do cmd = Mixlib::ShellOut.new("bundle show chef", env: env).run_command cmd.error! cmd.stdout.chomp end def run_rspec_test(test) Bundler.with_clean_env do cmd = Mixlib::ShellOut.new( "bundle exec rspec -f documentation -t ~requires_git #{test}", env: env, cwd: chef_dir, timeout: 3600 ) cmd.run_command.error! end end it "passes the unit specs" do run_rspec_test("spec/unit") end it "passes the functional specs" do run_rspec_test("spec/functional") end end chef-12.14.60/acceptance/omnitruck/000077500000000000000000000000001276456504500167715ustar00rootroot00000000000000chef-12.14.60/acceptance/omnitruck/.acceptance/000077500000000000000000000000001276456504500211355ustar00rootroot00000000000000chef-12.14.60/acceptance/omnitruck/.acceptance/acceptance-cookbook/000077500000000000000000000000001276456504500250275ustar00rootroot00000000000000chef-12.14.60/acceptance/omnitruck/.acceptance/acceptance-cookbook/.gitignore000066400000000000000000000000141276456504500270120ustar00rootroot00000000000000nodes/ tmp/ chef-12.14.60/acceptance/omnitruck/.acceptance/acceptance-cookbook/metadata.rb000066400000000000000000000000331276456504500271300ustar00rootroot00000000000000name 'acceptance-cookbook' chef-12.14.60/acceptance/omnitruck/.acceptance/acceptance-cookbook/recipes/000077500000000000000000000000001276456504500264615ustar00rootroot00000000000000chef-12.14.60/acceptance/omnitruck/.acceptance/acceptance-cookbook/recipes/destroy.rb000066400000000000000000000001601276456504500304740ustar00rootroot00000000000000log "NOOP 'destroy' recipe from the acceptance-cookbook in directory '#{node['chef-acceptance']['suite-dir']}'" chef-12.14.60/acceptance/omnitruck/.acceptance/acceptance-cookbook/recipes/provision.rb000066400000000000000000000001621276456504500310350ustar00rootroot00000000000000log "NOOP 'provision' recipe from the acceptance-cookbook in directory '#{node['chef-acceptance']['suite-dir']}'" chef-12.14.60/acceptance/omnitruck/.acceptance/acceptance-cookbook/recipes/verify.rb000066400000000000000000000037571276456504500303260ustar00rootroot00000000000000control_group "omnitruck" do require 'chef/http' require 'chef/json_compat' # We do this to be able to reference 'rest' both inside and outside example # blocks rest = Chef::HTTP.new("https://omnitruck.chef.io/chef/metadata", headers: {"Accept" => "application/json"}) let(:rest) { rest } def request(url) Chef::JSONCompat.parse(rest.get(url))["sha256"] end shared_examples "32 matches 64" do |version| it "only returns 32-bit packages" do sha32 = request("?p=windows&pv=2012r2&v=#{version}&m=i386") sha64 = request("?p=windows&pv=2012r2&v=#{version}&m=x86_64") expect(sha32).to eq(sha64) end end context "from the current channel" do it "returns both 32-bit and 64-bit packages" do # We cannot verify from the returned URL if the package is 64 or 32 bit because # it is often lying, so we just make sure they are different. # The current channel is often cleaned so only the latest builds are in # it, so we just request the latest version instead of trying to check # old versions sha32 = request("?p=windows&pv=2012r2&m=i386&prerelease=true") sha64 = request("?p=windows&pv=2012r2&m=x86_64&prerelease=true") expect(sha32).to_not eq(sha64) end end context "from the stable channel" do %w{11 12.3 12.4.2 12.6.0 12.8.1}.each do |version| describe "with version #{version}" do include_examples "32 matches 64", version end end begin rest.get("?p=windows&pv=2012r2&v=12.9") describe "with version 12.9" do it "returns both 32-bit and 64-bit packages" do sha32 = request("?p=windows&pv=2012r2&v=12.9&m=i386") sha64 = request("?p=windows&pv=2012r2&v=12.9&m=x86_64") expect(sha32).to_not eq(sha64) end end rescue Net::HTTPServerException => e # Once 12.9 is released this will stop 404ing and the example # will be executed unless e.response.code == "404" raise end end end end chef-12.14.60/acceptance/top-cookbooks/000077500000000000000000000000001276456504500175475ustar00rootroot00000000000000chef-12.14.60/acceptance/top-cookbooks/.acceptance/000077500000000000000000000000001276456504500217135ustar00rootroot00000000000000chef-12.14.60/acceptance/top-cookbooks/.acceptance/acceptance-cookbook/000077500000000000000000000000001276456504500256055ustar00rootroot00000000000000chef-12.14.60/acceptance/top-cookbooks/.acceptance/acceptance-cookbook/.gitignore000066400000000000000000000000141276456504500275700ustar00rootroot00000000000000nodes/ tmp/ chef-12.14.60/acceptance/top-cookbooks/.acceptance/acceptance-cookbook/libraries/000077500000000000000000000000001276456504500275615ustar00rootroot00000000000000chef-12.14.60/acceptance/top-cookbooks/.acceptance/acceptance-cookbook/libraries/cookbook_kitchen.rb000066400000000000000000000030601276456504500334200ustar00rootroot00000000000000class CookbookKitchen < KitchenAcceptance::Kitchen resource_name :cookbook_kitchen property :command, default: lazy { name.split(" ")[0] } property :kitchen_dir, default: lazy { ::File.join(repository_root, cookbook_relative_dir) } property :test_cookbook, String, default: lazy { name.split(" ")[1] } property :repository, String, default: lazy { "chef-cookbooks/#{test_cookbook}" }, coerce: proc { |v| # chef-cookbooks/runit -> https://github.com/chef-cookbooks/runit.git if !v.include?(':') "https://github.com/#{v}.git" else v end } property :repository_root, String, default: lazy { ::File.join(Chef.node["chef-acceptance"]["suite-dir"], "test_run", test_cookbook) } property :branch, String, default: "master" property :cookbook_relative_dir, String, default: "" property :env, default: lazy { { "BUNDLE_GEMFILE" => ::File.expand_path("../Gemfile", Chef.node["chef-acceptance"]["suite-dir"]), # "KITCHEN_GLOBAL_YAML" => ::File.join(kitchen_dir, ".kitchen.yml"), "KITCHEN_YAML" => ::File.join(node["chef-acceptance"]["suite-dir"], ".kitchen.#{test_cookbook}.yml") } } action :run do # Ensure the parent directory exists directory ::File.expand_path("..", repository_root) do recursive true end # Grab the cookbook # TODO Grab the source URL from supermarket # TODO get git to include its kitchen tests in the cookbook. git repository_root do repository new_resource.repository branch new_resource.branch end super() end end chef-12.14.60/acceptance/top-cookbooks/.acceptance/acceptance-cookbook/libraries/top_cookbooks.rb000066400000000000000000000021361276456504500327630ustar00rootroot00000000000000class TopCookbooks < Chef::Resource resource_name :top_cookbooks property :command, String, name_property: true # Disabling all windows tests until winrm issue is properly settled. # action :run do cookbook_kitchen "#{command} docker" do end cookbook_kitchen "#{command} git" do end cookbook_kitchen "#{command} learn-the-basics-ubuntu" do repository "learn-chef/learn-chef-acceptance" cookbook_relative_dir "cookbooks/learn-the-basics-ubuntu" end cookbook_kitchen "#{command} learn-the-basics-windows" do repository "learn-chef/learn-chef-acceptance" cookbook_relative_dir "cookbooks/learn-the-basics-windows" end cookbook_kitchen "#{command} powershell" do end cookbook_kitchen "#{command} iis" do end cookbook_kitchen "#{command} sql_server" do end cookbook_kitchen "#{command} winbox" do repository "adamedx/winbox" end # cookbook_kitchen "#{command} windows" do # end # cookbook_kitchen "#{command} chocolatey" do # repository "chocolatey/chocolatey-cookbook" # end end end chef-12.14.60/acceptance/top-cookbooks/.acceptance/acceptance-cookbook/metadata.rb000066400000000000000000000000711276456504500277100ustar00rootroot00000000000000name "acceptance-cookbook" depends "kitchen_acceptance" chef-12.14.60/acceptance/top-cookbooks/.acceptance/acceptance-cookbook/recipes/000077500000000000000000000000001276456504500272375ustar00rootroot00000000000000chef-12.14.60/acceptance/top-cookbooks/.acceptance/acceptance-cookbook/recipes/destroy.rb000066400000000000000000000000301276456504500312460ustar00rootroot00000000000000top_cookbooks "destroy" chef-12.14.60/acceptance/top-cookbooks/.acceptance/acceptance-cookbook/recipes/provision.rb000066400000000000000000000000311276456504500316060ustar00rootroot00000000000000top_cookbooks "converge" chef-12.14.60/acceptance/top-cookbooks/.acceptance/acceptance-cookbook/recipes/verify.rb000066400000000000000000000000271276456504500310670ustar00rootroot00000000000000top_cookbooks "verify" chef-12.14.60/acceptance/top-cookbooks/.gitignore000066400000000000000000000000121276456504500215300ustar00rootroot00000000000000test_run/ chef-12.14.60/acceptance/top-cookbooks/.kitchen.chocolatey.yml000066400000000000000000000002241276456504500241240ustar00rootroot00000000000000suites: - name: default run_list: - recipe[chocolatey::default] - recipe[chocolatey_test::default] includes: [windows-2012r2] chef-12.14.60/acceptance/top-cookbooks/.kitchen.docker.yml000066400000000000000000000002661276456504500232470ustar00rootroot00000000000000suites: - name: docker-default attributes: docker: version: 1.10.0 run_list: - recipe[apt] - recipe[chef-apt-docker] includes: [ubuntu-14.04] chef-12.14.60/acceptance/top-cookbooks/.kitchen.git.yml000066400000000000000000000005041276456504500225560ustar00rootroot00000000000000suites: - name: git-default # Ubuntu images need to run apt update run_list: ["recipe[apt]","recipe[git]"] includes: [ubuntu-14.04] - name: git-source run_list: ["recipe[git::source]"] includes: [nonexistent] - name: git-default-windows run_list: ["recipe[git]"] includes: [windows-2012r2] chef-12.14.60/acceptance/top-cookbooks/.kitchen.iis.yml000066400000000000000000000001341276456504500225560ustar00rootroot00000000000000suites: - name: iis run_list: ["recipe[iis::default]"] includes: [windows-2012r2] chef-12.14.60/acceptance/top-cookbooks/.kitchen.learn-the-basics-rhel.yml000066400000000000000000000003361276456504500260470ustar00rootroot00000000000000suites: - name: learn-the-basics-rhel-default run_list: - recipe[learn-the-basics-rhel::setup] - recipe[learn-the-basics-rhel::lesson1] - recipe[learn-the-basics-rhel::lesson2] includes: [el-6] chef-12.14.60/acceptance/top-cookbooks/.kitchen.learn-the-basics-ubuntu.yml000066400000000000000000000003561276456504500264410ustar00rootroot00000000000000suites: - name: learn-the-basics-ubuntu-default run_list: - recipe[learn-the-basics-ubuntu::setup] - recipe[learn-the-basics-ubuntu::lesson1] - recipe[learn-the-basics-ubuntu::lesson2] includes: [ubuntu-14.04] chef-12.14.60/acceptance/top-cookbooks/.kitchen.learn-the-basics-windows.yml000066400000000000000000000003641276456504500266100ustar00rootroot00000000000000suites: - name: learn-the-basics-windows-default run_list: - recipe[learn-the-basics-windows::setup] - recipe[learn-the-basics-windows::lesson1] - recipe[learn-the-basics-windows::lesson2] includes: [windows-2012r2] chef-12.14.60/acceptance/top-cookbooks/.kitchen.powershell.yml000066400000000000000000000001561276456504500241620ustar00rootroot00000000000000suites: - name: powershell run_list: ["recipe[powershell::powershell2]"] includes: [windows-2012r2] chef-12.14.60/acceptance/top-cookbooks/.kitchen.sql_server.yml000066400000000000000000000002401276456504500241550ustar00rootroot00000000000000suites: - name: sql_server run_list: ["recipe[sql_server::default]"] attributes: { sql_server: { accept_eula: true } } includes: [windows-2012r2] chef-12.14.60/acceptance/top-cookbooks/.kitchen.winbox.yml000066400000000000000000000002001276456504500232720ustar00rootroot00000000000000suites: - name: default run_list: - recipe[winbox::default] includes: [windows-2012r2] verifier: name: dummy chef-12.14.60/acceptance/top-cookbooks/.kitchen.windows.yml000066400000000000000000000017711276456504500234740ustar00rootroot00000000000000suites: # Waiting on https://github.com/chef-cookbooks/windows/pull/350 # - name: tasks # run_list: # - recipe[windows::default] # - recipe[minimal::tasks] # includes: [windows-2012r2] - name: path run_list: - recipe[windows::default] - recipe[minimal::path] includes: [windows-2012r2] - name: certificate run_list: - recipe[windows::default] - recipe[minimal::certificate] includes: [windows-2012r2] # Package is deprecated # - name: package # run_list: # - recipe[windows::default] # - recipe[minimal::package] # includes: [windows-2012r2] - name: feature run_list: - recipe[windows::default] - recipe[minimal::feature] includes: [windows-2012r2] - name: http_acl run_list: - recipe[windows::default] - recipe[minimal::http_acl] includes: [windows-2012r2] - name: font run_list: - recipe[windows::default] - recipe[minimal::font] includes: [windows-2012r2] chef-12.14.60/acceptance/trivial/000077500000000000000000000000001276456504500164305ustar00rootroot00000000000000chef-12.14.60/acceptance/trivial/.acceptance/000077500000000000000000000000001276456504500205745ustar00rootroot00000000000000chef-12.14.60/acceptance/trivial/.acceptance/acceptance-cookbook/000077500000000000000000000000001276456504500244665ustar00rootroot00000000000000chef-12.14.60/acceptance/trivial/.acceptance/acceptance-cookbook/.gitignore000066400000000000000000000000141276456504500264510ustar00rootroot00000000000000nodes/ tmp/ chef-12.14.60/acceptance/trivial/.acceptance/acceptance-cookbook/metadata.rb000066400000000000000000000000701276456504500265700ustar00rootroot00000000000000name "acceptance-cookbook" depends "kitchen_acceptance" chef-12.14.60/acceptance/trivial/.acceptance/acceptance-cookbook/recipes/000077500000000000000000000000001276456504500261205ustar00rootroot00000000000000chef-12.14.60/acceptance/trivial/.acceptance/acceptance-cookbook/recipes/destroy.rb000066400000000000000000000000221276456504500301300ustar00rootroot00000000000000kitchen "destroy" chef-12.14.60/acceptance/trivial/.acceptance/acceptance-cookbook/recipes/provision.rb000066400000000000000000000000201276456504500304650ustar00rootroot00000000000000kitchen "setup" chef-12.14.60/acceptance/trivial/.acceptance/acceptance-cookbook/recipes/verify.rb000066400000000000000000000000211276456504500277420ustar00rootroot00000000000000kitchen "verify" chef-12.14.60/acceptance/trivial/.kitchen.yml000066400000000000000000000002031276456504500206510ustar00rootroot00000000000000verifier: name: inspec suites: - name: chef-current-install includes: [ubuntu-14.04, windows-server-2012r2] run_list: chef-12.14.60/acceptance/trivial/test/000077500000000000000000000000001276456504500174075ustar00rootroot00000000000000chef-12.14.60/acceptance/trivial/test/integration/000077500000000000000000000000001276456504500217325ustar00rootroot00000000000000chef-12.14.60/acceptance/trivial/test/integration/chef-current-install/000077500000000000000000000000001276456504500257635ustar00rootroot00000000000000chef-12.14.60/acceptance/trivial/test/integration/chef-current-install/inspec/000077500000000000000000000000001276456504500272445ustar00rootroot00000000000000chef-12.14.60/acceptance/trivial/test/integration/chef-current-install/inspec/chef_client_spec.rb000066400000000000000000000003341276456504500330460ustar00rootroot00000000000000chef_version = ENV["KITCHEN_CHEF_VERSION"].split("+")[0] describe command("chef-client -v") do its("exit_status") { should eq 0 } its(:stdout) { should match /Chef: #{chef_version}/ } if chef_version != "latest" end chef-12.14.60/acceptance/vendor/000077500000000000000000000000001276456504500162535ustar00rootroot00000000000000chef-12.14.60/acceptance/vendor/bundle/000077500000000000000000000000001276456504500175245ustar00rootroot00000000000000chef-12.14.60/acceptance/vendor/bundle/bundler/000077500000000000000000000000001276456504500211575ustar00rootroot00000000000000chef-12.14.60/acceptance/vendor/bundle/bundler/gems/000077500000000000000000000000001276456504500221125ustar00rootroot00000000000000chef-12.14.60/acceptance/vendor/bundle/bundler/gems/chef-acceptance-47e931cec100/000077500000000000000000000000001276456504500266315ustar00rootroot00000000000000chef-12.14.60/acceptance/vendor/bundle/bundler/gems/chef-acceptance-e92ddae46d21/000077500000000000000000000000001276456504500267775ustar00rootroot00000000000000chef-12.14.60/acceptance/windows-service/000077500000000000000000000000001276456504500201065ustar00rootroot00000000000000chef-12.14.60/acceptance/windows-service/.acceptance/000077500000000000000000000000001276456504500222525ustar00rootroot00000000000000chef-12.14.60/acceptance/windows-service/.acceptance/acceptance-cookbook/000077500000000000000000000000001276456504500261445ustar00rootroot00000000000000chef-12.14.60/acceptance/windows-service/.acceptance/acceptance-cookbook/.gitignore000066400000000000000000000000141276456504500301270ustar00rootroot00000000000000nodes/ tmp/ chef-12.14.60/acceptance/windows-service/.acceptance/acceptance-cookbook/metadata.rb000066400000000000000000000000701276456504500302460ustar00rootroot00000000000000name "acceptance-cookbook" depends "kitchen_acceptance" chef-12.14.60/acceptance/windows-service/.acceptance/acceptance-cookbook/recipes/000077500000000000000000000000001276456504500275765ustar00rootroot00000000000000chef-12.14.60/acceptance/windows-service/.acceptance/acceptance-cookbook/recipes/destroy.rb000066400000000000000000000000231276456504500316070ustar00rootroot00000000000000#kitchen "destroy" chef-12.14.60/acceptance/windows-service/.acceptance/acceptance-cookbook/recipes/provision.rb000066400000000000000000000000241276456504500321470ustar00rootroot00000000000000#kitchen "converge" chef-12.14.60/acceptance/windows-service/.acceptance/acceptance-cookbook/recipes/verify.rb000066400000000000000000000000221276456504500314210ustar00rootroot00000000000000#kitchen "verify" chef-12.14.60/acceptance/windows-service/.kitchen.yml000066400000000000000000000001561276456504500223360ustar00rootroot00000000000000verifier: name: inspec suites: - name: chef-windows-service includes: [windows-2012r2] run_list: chef-12.14.60/acceptance/windows-service/test/000077500000000000000000000000001276456504500210655ustar00rootroot00000000000000chef-12.14.60/acceptance/windows-service/test/integration/000077500000000000000000000000001276456504500234105ustar00rootroot00000000000000chef-12.14.60/acceptance/windows-service/test/integration/chef-windows-service/000077500000000000000000000000001276456504500274435ustar00rootroot00000000000000chef-12.14.60/acceptance/windows-service/test/integration/chef-windows-service/inspec/000077500000000000000000000000001276456504500307245ustar00rootroot00000000000000chef_windows_service_spec.rb000066400000000000000000000030401276456504500364000ustar00rootroot00000000000000chef-12.14.60/acceptance/windows-service/test/integration/chef-windows-service/inspeconly_if do os["family"] == "windows" end describe command("chef-service-manager") do it { should exist } its("exit_status") { should eq 0 } end describe service("chef-client") do it { should_not be_enabled } it { should_not be_installed } it { should_not be_running } end describe command("/opscode/chef/bin/chef-service-manager.bat -a install") do its("exit_status") { should eq 0 } its(:stdout) { should match /Service 'chef-client' has successfully been installed./ } end describe service("chef-client") do it { should be_enabled } it { should be_installed } it { should_not be_running } end describe command("/opscode/chef/bin/chef-service-manager.bat -a start") do its("exit_status") { should eq 0 } its(:stdout) { should match /Service 'chef-client' is now 'running'/ } end describe service("chef-client") do it { should be_enabled } it { should be_installed } it { should be_running } end describe command("/opscode/chef/bin/chef-service-manager.bat -a stop") do its("exit_status") { should eq 0 } its(:stdout) { should match /Service 'chef-client' is now 'stopped'/ } end describe service("chef-client") do it { should be_enabled } it { should be_installed } it { should_not be_running } end describe command("/opscode/chef/bin/chef-service-manager.bat -a uninstall") do its("exit_status") { should eq 0 } its(:stdout) { should match /Service chef-client deleted/ } end describe service("chef-client") do it { should_not be_enabled } it { should_not be_installed } it { should_not be_running } end chef-12.14.60/appveyor.yml000066400000000000000000000017551276456504500152700ustar00rootroot00000000000000version: "master-{build}" os: Windows Server 2012 R2 platform: - x64 environment: matrix: - ruby_version: "23-x64" - ruby_version: "23" clone_folder: c:\projects\chef clone_depth: 1 skip_tags: true branches: only: - master install: - systeminfo - winrm quickconfig -q - SET PATH=C:\Ruby%ruby_version%\bin;%PATH% - echo %PATH% - ruby --version - gem update --system || gem update --system || gem update --system - gem install bundler --quiet --no-ri --no-rdoc || gem install bundler --quiet --no-ri --no-rdoc || gem install bundler --quiet --no-ri --no-rdoc - update_rubygems - gem --version - bundler --version - SET BUNDLE_IGNORE_CONFIG=true - SET BUNDLE_FROZEN=1 - SET BUNDLE_WITHOUT=development:guard:maintenance:tools:integration:changelog:docgen:travis:style:omnibus_package:aix:bsd:linux:mac_os_x:solaris build_script: - bundle install || bundle install || bundle install test_script: - SET SPEC_OPTS=--format progress - bundle exec rake spec chef-12.14.60/bin/000077500000000000000000000000001276456504500134405ustar00rootroot00000000000000chef-12.14.60/bin/chef-apply000077500000000000000000000016041276456504500154170ustar00rootroot00000000000000#!/usr/bin/env ruby # # ./chef-apply - Run a single chef recipe # # Author:: Bryan W. Berry () # Copyright:: Copyright 2012-2016, Bryan W. Berry # 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 "rubygems" $:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) require "chef/application/apply" Chef::Application::Apply.new.run chef-12.14.60/bin/chef-client000077500000000000000000000016121276456504500155470ustar00rootroot00000000000000#!/usr/bin/env ruby # # ./chef-client - Run the chef client # # Author:: AJ Christensen () # 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 "rubygems" $:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) require "chef" require "chef/application/client" Chef::Application::Client.new.run chef-12.14.60/bin/chef-service-manager000077500000000000000000000026511276456504500173450ustar00rootroot00000000000000#!/usr/bin/env ruby # # ./chef-service-manager - Control chef-service on Windows platforms. # # Author:: Serdar Sutay (serdar@chef.io) # Copyright:: Copyright 2013-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 "rubygems" $:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) require "chef" require "chef/application/windows_service_manager" if Chef::Platform.windows? chef_client_service = { :service_name => "chef-client", :service_display_name => "Chef Client Service", :service_description => "Runs Chef Client on regular, configurable intervals.", :service_file_path => File.expand_path("../chef-windows-service", $PROGRAM_NAME), :delayed_start => true, :dependencies => ["Winmgmt"], } Chef::Application::WindowsServiceManager.new(chef_client_service).run else puts "chef-service-manager is only available on Windows platforms." end chef-12.14.60/bin/chef-shell000077500000000000000000000017051276456504500154030ustar00rootroot00000000000000#!/usr/bin/env ruby # # ./chef-shell - Run the Chef REPL (Shell) # # Author:: Daniel DeLeo () # Copyright:: Copyright (c) 2009 # 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. begin require "rubygems" rescue LoadError end require "irb" require "irb/completion" require "irb/ext/save-history" $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))) require "chef/shell" Shell.start chef-12.14.60/bin/chef-solo000077500000000000000000000016121276456504500152450ustar00rootroot00000000000000#!/usr/bin/env ruby # # ./chef-solo - Run the chef client, in stand-alone mode # # Author:: AJ Christensen () # 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 "rubygems" $:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) require "chef/application/solo" Chef::Application::Solo.new.run chef-12.14.60/bin/chef-windows-service000077500000000000000000000022001276456504500174130ustar00rootroot00000000000000#!/usr/bin/env ruby # # Author:: Jay Mundrawala () # # Copyright:: 2014, Chef Software, Inc. # # 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. # # Note: # The file is used by appbundler to generate a ruby file that # we can execute using the correct gems. The batch file we # generate will call that file, and will be registered as # a windows service. require "rubygems" $:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) require "chef" require "chef/application/windows_service" if Chef::Platform.windows? Chef::Application::WindowsService.mainloop else puts "chef-windows-service is only available on Windows platforms." end chef-12.14.60/bin/knife000077500000000000000000000016021276456504500144610ustar00rootroot00000000000000#!/usr/bin/env ruby # # ./knife - Chef CLI interface # # Author:: Adam Jacob () # Copyright:: Copyright 2009-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 "rubygems" $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))) require "chef/application/knife" Chef::Application::Knife.new.run chef-12.14.60/chef-config/000077500000000000000000000000001276456504500150405ustar00rootroot00000000000000chef-12.14.60/chef-config/.gitignore000066400000000000000000000001271276456504500170300ustar00rootroot00000000000000/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ chef-12.14.60/chef-config/.rspec000066400000000000000000000000141276456504500161500ustar00rootroot00000000000000--color -fd chef-12.14.60/chef-config/.travis.yml000066400000000000000000000014511276456504500171520ustar00rootroot00000000000000language: ruby sudo: false # Early warning system to catch if Rubygems breaks something before_install: gem update --system branches: only: - master matrix: include: - rvm: 2.0 - rvm: 2.1 notifications: on_change: true on_failure: true on_success: change on_pull_requests: false irc: channels: - chat.freenode.net#chef-hacking hipchat: rooms: # Build Statuses - secure: G8MNo94L8bmWWwkH2/ViB2QaZnZHZscYM/mEjDbOGd15sqrruwckeARyBoUcRI7P1C6AFmS4IKCNVXa6KzX4Pbh51gQWM92zRpRTZpplwtXz53/1l8ajLFLLMLvEMTlBFAANUKEUFAQPY4dMa14V3Qc5oijfIncN61k4nZNTKpY= - rvm: 2.2 # Open Source - secure: hmcex4PpG5dn8WvjndONO4xCUKOC5kPU/bUEGRrfVbe2YKJE7t0XXbNDC96W/xBgzgnJzvf1Er0zJKDrNf4qEDEWFoozdN00WLcqREgaLLS3Seto2FjR/BpBk5q+sCV0rwwEMms2P4Qk+VSnDCnm9EaeM55hOabqNuOrRzoZLBQ= chef-12.14.60/chef-config/Gemfile000066400000000000000000000001401276456504500163260ustar00rootroot00000000000000source "https://rubygems.org" # Specify your gem's dependencies in chef-config.gemspec gemspec chef-12.14.60/chef-config/LICENSE000066400000000000000000000251421276456504500160510ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. chef-12.14.60/chef-config/README.md000066400000000000000000000001001276456504500163060ustar00rootroot00000000000000# ChefConfig This repo is experimental. Use at your own risk. chef-12.14.60/chef-config/Rakefile000066400000000000000000000007441276456504500165120ustar00rootroot00000000000000require "chef-config/package_task" ChefConfig::PackageTask.new(File.expand_path("..", __FILE__), "ChefConfig", "chef-config") do |package| package.module_path = "chef-config" end task :default => :spec 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 chef-12.14.60/chef-config/chef-config.gemspec000066400000000000000000000021131276456504500205520ustar00rootroot00000000000000# coding: utf-8 lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "chef-config/version" Gem::Specification.new do |spec| spec.name = "chef-config" spec.version = ChefConfig::VERSION spec.authors = ["Adam Jacob"] spec.email = ["adam@chef.io"] spec.summary = %q{Chef's default configuration and config loading} spec.homepage = "https://github.com/chef/chef" spec.license = "Apache-2.0" spec.require_paths = ["lib"] spec.add_dependency "mixlib-shellout", "~> 2.0" spec.add_dependency "mixlib-config", "~> 2.0" spec.add_dependency "fuzzyurl" spec.add_dependency "addressable" spec.add_development_dependency "rake", "~> 10.0" %w{rspec-core rspec-expectations rspec-mocks}.each do |rspec| spec.add_development_dependency(rspec, "~> 3.2") end spec.files = %w{Rakefile LICENSE README.md} + Dir.glob("*.gemspec") + Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) } spec.bindir = "bin" spec.executables = [] end chef-12.14.60/chef-config/lib/000077500000000000000000000000001276456504500156065ustar00rootroot00000000000000chef-12.14.60/chef-config/lib/chef-config.rb000066400000000000000000000012351276456504500203040ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # module ChefConfig end chef-12.14.60/chef-config/lib/chef-config/000077500000000000000000000000001276456504500177565ustar00rootroot00000000000000chef-12.14.60/chef-config/lib/chef-config/config.rb000066400000000000000000001321241276456504500215530ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Brown () # Author:: AJ Christensen () # Author:: Mark Mzyk () # Author:: Kyle Goodwin () # 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 "mixlib/config" require "pathname" require "chef-config/fips" require "chef-config/logger" require "chef-config/windows" require "chef-config/path_helper" require "chef-config/mixin/fuzzy_hostname_matcher" require "mixlib/shellout" require "uri" require "addressable/uri" require "openssl" module ChefConfig class Config extend Mixlib::Config extend ChefConfig::Mixin::FuzzyHostnameMatcher # Evaluates the given string as config. # # +filename+ is used for context in stacktraces, but doesn't need to be the name of an actual file. def self.from_string(string, filename) self.instance_eval(string, filename, 1) end def self.inspect configuration.inspect end def self.platform_specific_path(path) path = PathHelper.cleanpath(path) if ChefConfig.windows? # turns \etc\chef\client.rb and \var\chef\client.rb into C:/chef/client.rb if env["SYSTEMDRIVE"] && path[0] == '\\' && path.split('\\')[2] == "chef" path = PathHelper.join(env["SYSTEMDRIVE"], path.split('\\', 3)[2]) end end path end def self.add_formatter(name, file_path = nil) formatters << [name, file_path] end def self.add_event_logger(logger) event_handlers << logger end # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.) configurable(:config_file) default(:config_dir) do if config_file PathHelper.dirname(PathHelper.canonical_path(config_file, false)) else PathHelper.join(user_home, ".chef", "") end end default :formatters, [] def self.is_valid_url?(uri) url = uri.to_s.strip /^http:\/\// =~ url || /^https:\/\// =~ url || /^chefzero:/ =~ url end # Override the config dispatch to set the value of multiple server options simultaneously # # === Parameters # url:: String to be set for all of the chef-server-api URL's # configurable(:chef_server_url).writes_value do |uri| unless is_valid_url? uri raise ConfigurationError, "#{uri} is an invalid chef_server_url." end uri.to_s.strip end # When you are using ActiveSupport, they monkey-patch 'daemonize' into Kernel. # So while this is basically identical to what method_missing would do, we pull # it up here and get a real method written so that things get dispatched # properly. configurable(:daemonize).writes_value { |v| v } # The root where all local chef object data is stored. cookbooks, data bags, # environments are all assumed to be in separate directories under this. # chef-solo uses these directories for input data. knife commands # that upload or download files (such as knife upload, knife role from file, # etc.) work. default :chef_repo_path do if self.configuration[:cookbook_path] if self.configuration[:cookbook_path].kind_of?(String) File.expand_path("..", self.configuration[:cookbook_path]) else self.configuration[:cookbook_path].map do |path| File.expand_path("..", path) end end elsif configuration[:cookbook_artifact_path] File.expand_path("..", self.configuration[:cookbook_artifact_path]) else cache_path end end def self.find_chef_repo_path(cwd) # In local mode, we auto-discover the repo root by looking for a path with "cookbooks" under it. # This allows us to run config-free. path = cwd until File.directory?(PathHelper.join(path, "cookbooks")) || File.directory?(PathHelper.join(path, "cookbook_artifacts")) new_path = File.expand_path("..", path) if new_path == path ChefConfig.logger.warn("No cookbooks directory found at or above current directory. Assuming #{cwd}.") return cwd end path = new_path end ChefConfig.logger.info("Auto-discovered chef repository at #{path}") path end def self.derive_path_from_chef_repo_path(child_path) if chef_repo_path.kind_of?(String) PathHelper.join(chef_repo_path, child_path) else chef_repo_path.uniq.map { |path| PathHelper.join(path, child_path) } end end # Location of acls on disk. String or array of strings. # Defaults to /acls. default(:acl_path) { derive_path_from_chef_repo_path("acls") } # Location of clients on disk. String or array of strings. # Defaults to /clients. default(:client_path) { derive_path_from_chef_repo_path("clients") } # Location of client keys on disk. String or array of strings. # Defaults to /client_keys. default(:client_key_path) { derive_path_from_chef_repo_path("client_keys") } # Location of containers on disk. String or array of strings. # Defaults to /containers. default(:container_path) { derive_path_from_chef_repo_path("containers") } # Location of cookbook_artifacts on disk. String or array of strings. # Defaults to /cookbook_artifacts. default(:cookbook_artifact_path) { derive_path_from_chef_repo_path("cookbook_artifacts") } # Location of cookbooks on disk. String or array of strings. # Defaults to /cookbooks. If chef_repo_path # is not specified, this is set to [/var/chef/cookbooks, /var/chef/site-cookbooks]). default(:cookbook_path) do if self.configuration[:chef_repo_path] derive_path_from_chef_repo_path("cookbooks") else Array(derive_path_from_chef_repo_path("cookbooks")).flatten + Array(derive_path_from_chef_repo_path("site-cookbooks")).flatten end end # Location of data bags on disk. String or array of strings. # Defaults to /data_bags. default(:data_bag_path) { derive_path_from_chef_repo_path("data_bags") } # Location of environments on disk. String or array of strings. # Defaults to /environments. default(:environment_path) { derive_path_from_chef_repo_path("environments") } # Location of groups on disk. String or array of strings. # Defaults to /groups. default(:group_path) { derive_path_from_chef_repo_path("groups") } # Location of nodes on disk. String or array of strings. # Defaults to /nodes. default(:node_path) { derive_path_from_chef_repo_path("nodes") } # Location of policies on disk. String or array of strings. # Defaults to /policies. default(:policy_path) { derive_path_from_chef_repo_path("policies") } # Location of policy_groups on disk. String or array of strings. # Defaults to /policy_groups. default(:policy_group_path) { derive_path_from_chef_repo_path("policy_groups") } # Location of roles on disk. String or array of strings. # Defaults to /roles. default(:role_path) { derive_path_from_chef_repo_path("roles") } # Location of users on disk. String or array of strings. # Defaults to /users. default(:user_path) { derive_path_from_chef_repo_path("users") } # Location of policies on disk. String or array of strings. # Defaults to /policies. default(:policy_path) { derive_path_from_chef_repo_path("policies") } # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity default :enforce_path_sanity, true # Formatted Chef Client output is a beta feature, disabled by default: default :formatter, "null" # The number of times the client should retry when registering with the server default :client_registration_retries, 5 # An array of paths to search for knife exec scripts if they aren't in the current directory default :script_path, [] # The root of all caches (checksums, cache and backup). If local mode is on, # this is under the user's home directory. default(:cache_path) do if local_mode PathHelper.join(config_dir, "local-mode-cache") else primary_cache_root = platform_specific_path("/var") primary_cache_path = platform_specific_path("/var/chef") # Use /var/chef as the cache path only if that folder exists and we can read and write # into it, or /var exists and we can read and write into it (we'll create /var/chef later). # Otherwise, we'll create .chef under the user's home directory and use that as # the cache path. unless path_accessible?(primary_cache_path) || path_accessible?(primary_cache_root) secondary_cache_path = PathHelper.join(user_home, ".chef") ChefConfig.logger.info("Unable to access cache at #{primary_cache_path}. Switching cache to #{secondary_cache_path}") secondary_cache_path else primary_cache_path end end end # Returns true only if the path exists and is readable and writeable for the user. def self.path_accessible?(path) File.exists?(path) && File.readable?(path) && File.writable?(path) end # Where cookbook files are stored on the server (by content checksum) default(:checksum_path) { PathHelper.join(cache_path, "checksums") } # Where chef's cache files should be stored default(:file_cache_path) { PathHelper.join(cache_path, "cache") } # Where backups of chef-managed files should go default(:file_backup_path) { PathHelper.join(cache_path, "backup") } # The chef-client (or solo) lockfile. # # If your `file_cache_path` resides on a NFS (or non-flock()-supporting # fs), it's recommended to set this to something like # '/tmp/chef-client-running.pid' default(:lockfile) { PathHelper.join(file_cache_path, "chef-client-running.pid") } ## Daemonization Settings ## # What user should Chef run as? default :user, nil default :group, nil default :umask, 0022 # Valid log_levels are: # * :debug # * :info # * :warn # * :fatal # These work as you'd expect. There is also a special `:auto` setting. # When set to :auto, Chef will auto adjust the log verbosity based on # context. When a tty is available (usually because the user is running chef # in a console), the log level is set to :warn, and output formatters are # used as the primary mode of output. When a tty is not available, the # logger is the primary mode of output, and the log level is set to :info default :log_level, :auto # Logging location as either an IO stream or string representing log file path default :log_location, STDOUT # Using `force_formatter` causes chef to default to formatter output when STDOUT is not a tty default :force_formatter, false # Using `force_logger` causes chef to default to logger output when STDOUT is a tty default :force_logger, false # Using 'stream_execute_output' will have Chef always stream the execute output default :stream_execute_output, false # Using `show_download_progress` will display the overall progress # of a remote file download default :show_download_progress, false # How often to update the progress meter, in percent default :download_progress_interval, 10 default :http_retry_count, 5 default :http_retry_delay, 5 default :interval, nil default :once, nil default :json_attribs, nil # toggle info level log items that can create a lot of output default :verbose_logging, true default :node_name, nil default :diff_disabled, false default :diff_filesize_threshold, 10000000 default :diff_output_threshold, 1000000 default :local_mode, false # Configures the mode of operation for ChefFS, which is applied to the # ChefFS-based knife commands and chef-client's local mode. (ChefFS-based # knife commands include: knife delete, knife deps, knife diff, knife down, # knife edit, knife list, knife show, knife upload, and knife xargs.) # # Valid values are: # * "static": ChefFS only manages objects that exist in a traditional Chef # Repo as of Chef 11. # * "everything": ChefFS manages all object types that existed on the OSS # Chef 11 server. # * "hosted_everything": ChefFS manages all object types as of the Chef 12 # Server, including RBAC objects and Policyfile objects (new to Chef 12). default :repo_mode do if local_mode && !chef_zero.osc_compat "hosted_everything" elsif chef_server_url =~ /\/+organizations\/.+/ "hosted_everything" else "everything" end end default :pid_file, nil # Whether Chef Zero local mode should bind to a port. All internal requests # will go through the socketless code path regardless, so the socket is # only needed if other processes will connect to the local mode server. # # For compatibility this is set to true but it will be changed to false in # the future. default :listen, true config_context :chef_zero do config_strict_mode true default(:enabled) { ChefConfig::Config.local_mode } default :host, "localhost" default :port, 8889.upto(9999) # Will try ports from 8889-9999 until one works # When set to a String, Chef Zero disables multitenant support. This is # what you want when using Chef Zero to serve a single Chef Repo. Setting # this to `false` enables multi-tenant. default :single_org, "chef" # Whether Chef Zero should operate in a mode analogous to OSS Chef Server # 11 (true) or Chef Server 12 (false). Chef Zero can still serve # policyfile objects in Chef 11 mode, as long as `repo_mode` is set to # "hosted_everything". The primary differences are: # * Chef 11 mode doesn't support multi-tennant, so there is no # distinction between global and org-specific objects (since there are # no orgs). # * Chef 11 mode doesn't expose RBAC objects default :osc_compat, false end default :chef_server_url, "https://localhost:443" default(:chef_server_root) do # if the chef_server_url is a path to an organization, aka # 'some_url.../organizations/*' then remove the '/organization/*' by default if self.configuration[:chef_server_url] =~ /\/organizations\/\S*$/ self.configuration[:chef_server_url].split("/")[0..-3].join("/") elsif self.configuration[:chef_server_url] # default to whatever chef_server_url is self.configuration[:chef_server_url] else "https://localhost:443" end end default :rest_timeout, 300 default :yum_timeout, 900 default :yum_lock_timeout, 30 default :solo, false # Are we running in old Chef Solo legacy mode? default :solo_legacy_mode, false default :splay, nil default :why_run, false default :color, false default :client_fork, true default :ez, false default :enable_reporting, true default :enable_reporting_url_fatals, false # Possible values for :audit_mode # :enabled, :disabled, :audit_only, # # TODO: 11 Dec 2014: Currently audit-mode is an experimental feature # and is disabled by default. When users choose to enable audit-mode, # a warning is issued in application/client#reconfigure. # This can be removed when audit-mode is enabled by default. default :audit_mode, :disabled # Chef only needs ohai to run the hostname plugin for the most basic # functionality. If the rest of the ohai plugins are not needed (like in # most of our testing scenarios) default :minimal_ohai, false ### # Policyfile Settings # # Policyfile is a feature where a node gets its run list and cookbook # version set from a single document on the server instead of expanding the # run list and having the server compute the cookbook version set based on # environment constraints. # # Policyfiles are auto-versioned. The user groups nodes by `policy_name`, # which generally describes a hosts's functional role, and `policy_group`, # which generally groups nodes by deployment phase (a.k.a., "environment"). # The Chef Server maps a given set of `policy_name` plus `policy_group` to # a particular revision of a policy. default :policy_name, nil default :policy_group, nil # Policyfiles can have multiple run lists, via the named run list feature. # Generally this will be set by a CLI option via Chef::Application::Client, # but it could be set in client.rb if desired. default :named_run_list, nil # During initial development, users were required to set `use_policyfile true` # in `client.rb` to opt-in to policyfile use. Chef Client now examines # configuration, node json, and the stored node to determine if policyfile # usage is desired. This flag is still honored if set, but is unnecessary. default :use_policyfile, false # Policyfiles can be used in a native mode (default) or compatibility mode. # Native mode requires Chef Server 12.1 (it can be enabled via feature flag # on some prior versions). In native mode, policies and associated # cookbooks are accessed via feature-specific APIs. In compat mode, # policies are stored as data bags and cookbooks are stored at the # cookbooks/ endpoint. Compatibility mode can be dangerous on existing Chef # Servers; it's recommended to upgrade your Chef Server rather than use # compatibility mode. Compatibility mode remains available so you can use # policyfiles with servers that don't yet support the native endpoints. default :policy_document_native_api, true # When policyfiles are used in compatibility mode, `policy_name` and # `policy_group` are instead specified using a combined configuration # setting, `deployment_group`. For example, if policy_name should be # "webserver" and policy_group should be "staging", then `deployment_group` # should be set to "webserver-staging", which is the name of the data bag # item that the policy will be stored as. NOTE: this setting only has an # effect if `policy_document_native_api` is set to `false`. default :deployment_group, nil # Set these to enable SSL authentication / mutual-authentication # with the server # Client side SSL cert/key for mutual auth default :ssl_client_cert, nil default :ssl_client_key, nil # Whether or not to verify the SSL cert for all HTTPS requests. When set to # :verify_peer (default), all HTTPS requests will be validated regardless of other # SSL verification settings. When set to :verify_none no HTTPS requests will # be validated. default :ssl_verify_mode, :verify_peer # Whether or not to verify the SSL cert for HTTPS requests to the Chef # server API. If set to `true`, the server's cert will be validated # regardless of the :ssl_verify_mode setting. This is set to `true` when # running in local-mode. # NOTE: This is a workaround until verify_peer is enabled by default. default(:verify_api_cert) { ChefConfig::Config.local_mode } # Path to the default CA bundle files. default :ssl_ca_path, nil default(:ssl_ca_file) do if ChefConfig.windows? && embedded_dir cacert_path = File.join(embedded_dir, "ssl/certs/cacert.pem") cacert_path if File.exist?(cacert_path) else nil end end # A directory that contains additional SSL certificates to trust. Any # certificates in this directory will be added to whatever CA bundle ruby # is using. Use this to add self-signed certs for your Chef Server or local # HTTP file servers. default(:trusted_certs_dir) { PathHelper.join(config_dir, "trusted_certs") } # A directory that contains additional configuration scripts to load for chef-client default(:client_d_dir) { PathHelper.join(config_dir, "client.d") } # A directory that contains additional configuration scripts to load for solo default(:solo_d_dir) { PathHelper.join(config_dir, "solo.d") } # A directory that contains additional configuration scripts to load for # the workstation config default(:config_d_dir) { PathHelper.join(config_dir, "config.d") } # Where should chef-solo download recipes from? default :recipe_url, nil # Set to true if Chef is to set OpenSSL to run in FIPS mode default(:fips) do # CHEF_FIPS is used in testing to override checking for system level # enablement. There are 3 possible values that this variable may have: # nil - no override and the system will be checked # empty - FIPS is NOT enabled # a non empty value - FIPS is enabled if ENV["CHEF_FIPS"] == "" false else !ENV["CHEF_FIPS"].nil? || ChefConfig.fips? end end # Initialize openssl def self.init_openssl if fips self.enable_fips_mode end end # Sets the version of the signed header authentication protocol to use (see # the 'mixlib-authorization' project for more detail). Currently, versions # 1.0, 1.1, and 1.3 are available. default :authentication_protocol_version do if fips "1.3" else "1.1" end end # This key will be used to sign requests to the Chef server. This location # must be writable by Chef during initial setup when generating a client # identity on the server. # # The chef-server will look up the public key for the client using the # `node_name` of the client. # # If chef-zero is enabled, this defaults to nil (no authentication). default(:client_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/client.pem") } # When registering the client, should we allow the client key location to # be a symlink? eg: /etc/chef/client.pem -> /etc/chef/prod-client.pem # If the path of the key goes through a directory like /tmp this should # never be set to true or its possibly an easily exploitable security hole. default :follow_client_key_symlink, false # This secret is used to decrypt encrypted data bag items. default(:encrypted_data_bag_secret) do if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret")) platform_specific_path("/etc/chef/encrypted_data_bag_secret") else nil end end # As of Chef 11.0, version "1" is the default encrypted data bag item # format. Version "2" is available which adds encrypt-then-mac protection. # To maintain compatibility, versions other than 1 must be opt-in. # # Set this to `2` if you have chef-client 11.6.0+ in your infrastructure. # Set this to `3` if you have chef-client 11.?.0+, ruby 2 and OpenSSL >= 1.0.1 in your infrastructure. (TODO) default :data_bag_encrypt_version, 1 # When reading data bag items, any supported version is accepted. However, # if all encrypted data bags have been generated with the version 2 format, # it is recommended to disable support for earlier formats to improve # security. For example, the version 2 format is identical to version 1 # except for the addition of an HMAC, so an attacker with MITM capability # could downgrade an encrypted data bag to version 1 as part of an attack. default :data_bag_decrypt_minimum_version, 0 # If there is no file in the location given by `client_key`, chef-client # will temporarily use the "validator" identity to generate one. If the # `client_key` is not present and the `validation_key` is also not present, # chef-client will not be able to authenticate to the server. # # The `validation_key` is never used if the `client_key` exists. # # If chef-zero is enabled, this defaults to nil (no authentication). default(:validation_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/validation.pem") } default :validation_client_name, "chef-validator" # When creating a new client via the validation_client account, Chef 11 # servers allow the client to generate a key pair locally and send the # public key to the server. This is more secure and helps offload work from # the server, enhancing scalability. If enabled and the remote server # implements only the Chef 10 API, client registration will not work # properly. # # The default value is `true`. Set to `false` to disable client-side key # generation (server generates client keys). default(:local_key_generation) { true } # Zypper package provider gpg checks. Set to true to enable package # gpg signature checking. This will be default in the # future. Setting to false disables the warnings. # Leaving this set to nil or false is a security hazard! default :zypper_check_gpg, nil # Report Handlers default :report_handlers, [] # Event Handlers default :event_handlers, [] default :disable_event_loggers, false # Exception Handlers default :exception_handlers, [] # Start handlers default :start_handlers, [] # Syntax Check Cache. Knife keeps track of files that is has already syntax # checked by storing files in this directory. `syntax_check_cache_path` is # the new (and preferred) configuration setting. If not set, knife will # fall back to using cache_options[:path], which is deprecated but exists in # many client configs generated by pre-Chef-11 bootstrappers. default(:syntax_check_cache_path) { cache_options[:path] } # Deprecated: # Move this to the default value of syntax_cache_path when this is removed. default(:cache_options) { { :path => PathHelper.join(config_dir, "syntaxcache") } } # Whether errors should be raised for deprecation warnings. When set to # `false` (the default setting), a warning is emitted but code using # deprecated methods/features/etc. should work normally otherwise. When set # to `true`, usage of deprecated methods/features will raise a # `DeprecatedFeatureError`. This is used by Chef's tests to ensure that # deprecated functionality is not used internally by Chef. End users # should generally leave this at the default setting (especially in # production), but it may be useful when testing cookbooks or other code if # the user wishes to aggressively address deprecations. default(:treat_deprecation_warnings_as_errors) do # Using an environment variable allows this setting to be inherited in # tests that spawn new processes. ENV.key?("CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS") end # knife configuration data config_context :knife do # XXX: none of these default values are applied to knife (and would create a backcompat # break in knife if this bug was fixed since many of the defaults below are wrong). this appears # to be the start of an attempt to be able to use config_strict_mode true? if so, this approach # is fraught with peril because this namespace is used by every knife plugin in the wild and # we would need to validate every cli option in every knife attribute out there and list them all here. # # based on the way that people may define `knife[:foobar] = "something"` for the knife-foobar # gem plugin i'm pretty certain we can never turn on anything like config_string_mode since # any config value may be a typo or it may be in some gem in some knife plugin we don't know about. # # we do still need to maintain at least one of these so that the knife config hash gets # created. # # this whole situation is deeply unsatisfying. default :ssh_port, nil default :ssh_user, nil default :ssh_attribute, nil default :ssh_gateway, nil default :bootstrap_version, nil default :bootstrap_proxy, nil default :bootstrap_template, nil default :secret, nil default :secret_file, nil default :identity_file, nil default :host_key_verify, nil default :forward_agent, nil default :sort_status_reverse, nil default :hints, {} end def self.set_defaults_for_windows # Those lists of regular expressions define what chef considers a # valid user and group name # From http://technet.microsoft.com/en-us/library/cc776019(WS.10).aspx principal_valid_regex_part = '[^"\/\\\\\[\]\:;|=,+*?<>]+' default :user_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ] default :group_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ] default :fatal_windows_admin_check, false end def self.set_defaults_for_nix # Those lists of regular expressions define what chef considers a # valid user and group name # # user/group cannot start with '-', '+' or '~' # user/group cannot contain ':', ',' or non-space-whitespace or null byte # everything else is allowed (UTF-8, spaces, etc) and we delegate to your O/S useradd program to barf or not # copies: http://anonscm.debian.org/viewvc/pkg-shadow/debian/trunk/debian/patches/506_relaxed_usernames?view=markup default :user_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ] default :group_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ] end # Those lists of regular expressions define what chef considers a # valid user and group name if ChefConfig.windows? set_defaults_for_windows else set_defaults_for_nix end # This provides a hook which rspec can stub so that we can avoid twiddling # global state in tests. def self.env ENV end def self.windows_home_path ChefConfig.logger.deprecation("Chef::Config.windows_home_path is now deprecated. Consider using Chef::Util::PathHelper.home instead.") PathHelper.home end # returns a platform specific path to the user home dir if set, otherwise default to current directory. default( :user_home ) { PathHelper.home || Dir.pwd } # Enable file permission fixup for selinux. Fixup will be done # only if selinux is enabled in the system. default :enable_selinux_file_permission_fixup, true # Use atomic updates (i.e. move operation) while updating contents # of the files resources. When set to false copy operation is # used to update files. # # NOTE: CHANGING THIS SETTING MAY CAUSE CORRUPTION, DATA LOSS AND # INSTABILITY. default :file_atomic_update, true # There are 3 possible values for this configuration setting. # true => file staging is done in the destination directory # false => file staging is done via tempfiles under ENV['TMP'] # :auto => file staging will try using destination directory if possible and # will fall back to ENV['TMP'] if destination directory is not usable. default :file_staging_uses_destdir, :auto # Exit if another run is in progress and the chef-client is unable to # get the lock before time expires. If nil, no timeout is enforced. (Exits # immediately if 0.) default :run_lock_timeout, nil # Number of worker threads for syncing cookbooks in parallel. Increasing # this number can result in gateway errors from the server (namely 503 and 504). # If you are seeing this behavior while using the default setting, reducing # the number of threads will help. default :cookbook_sync_threads, 10 # At the beginning of the Chef Client run, the cookbook manifests are downloaded which # contain URLs for every file in every relevant cookbook. Most of the files # (recipes, resources, providers, libraries, etc) are immediately synchronized # at the start of the run. The handling of "files" and "templates" directories, # however, have two modes of operation. They can either all be downloaded immediately # at the start of the run (no_lazy_load==true) or else they can be lazily loaded as # cookbook_file or template resources are converged which require them (no_lazy_load==false). # # The advantage of lazily loading these files is that unnecessary files are not # synchronized. This may be useful to users with large files checked into cookbooks which # are only selectively downloaded to a subset of clients which use the cookbook. However, # better solutions are to either isolate large files into individual cookbooks and only # include those cookbooks in the run lists of the servers that need them -- or move to # using remote_file and a more appropriate backing store like S3 for large file # distribution. # # The disadvantages of lazily loading files are that users some time find it # confusing that their cookbooks are not fully synchronzied to the cache initially, # and more importantly the time-sensitive URLs which are in the manifest may time # out on long Chef runs before the resource that uses the file is converged # (leading to many confusing 403 errors on template/cookbook_file resources). # default :no_lazy_load, true # Default for the chef_gem compile_time attribute. Nil is the same as true but will emit # warnings on every use of chef_gem prompting the user to be explicit. If the user sets this to # true then the user will get backcompat behavior but with a single nag warning that cookbooks # may break with this setting in the future. The false setting is the recommended setting and # will become the default. default :chef_gem_compile_time, nil # A whitelisted array of attributes you want sent over the wire when node # data is saved. # The default setting is nil, which collects all data. Setting to [] will not # collect any data for save. default :automatic_attribute_whitelist, nil default :default_attribute_whitelist, nil default :normal_attribute_whitelist, nil default :override_attribute_whitelist, nil # Pull down all the rubygems versions from rubygems and cache them the first time we do a gem_package or # chef_gem install. This is memory-expensive and will grow without bounds, but will reduce network # round trips. default :rubygems_cache_enabled, false config_context :windows_service do # Set `watchdog_timeout` to the number of seconds to wait for a chef-client run # to finish default :watchdog_timeout, 2 * (60 * 60) # 2 hours end # Add an empty and non-strict config_context for chefdk. This lets the user # have code like `chefdk.generator_cookbook "/path/to/cookbook"` in their # config.rb, and it will be ignored by tools like knife and ohai. ChefDK # itself can define the config options it accepts and enable strict mode, # and that will only apply when running `chef` commands. 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) configurable(:https_proxy) configurable(:https_proxy_user) configurable(:https_proxy_pass) configurable(:ftp_proxy) configurable(:ftp_proxy_user) configurable(:ftp_proxy_pass) configurable(:no_proxy) # Public method that users should call to export proxies to the appropriate # environment variables. This method should be called after the config file is # parsed and loaded. # TODO add some post-file-parsing logic that automatically calls this so # users don't have to def self.export_proxies export_proxy("http", http_proxy, http_proxy_user, http_proxy_pass) if http_proxy export_proxy("https", https_proxy, https_proxy_user, https_proxy_pass) if https_proxy export_proxy("ftp", ftp_proxy, ftp_proxy_user, ftp_proxy_pass) if ftp_proxy export_no_proxy(no_proxy) if no_proxy end # Character classes for Addressable # See https://www.ietf.org/rfc/rfc3986.txt 3.2.1 # The user part may not have a : in it USER = Addressable::URI::CharacterClasses::UNRESERVED + Addressable::URI::CharacterClasses::SUB_DELIMS # The password part may have any valid USERINFO characters PASSWORD = USER + "\\:" # Builds a proxy uri and exports it to the appropriate environment variables. Examples: # http://username:password@hostname:port # https://username@hostname:port # ftp://hostname:port # when # scheme = "http", "https", or "ftp" # hostport = hostname:port or scheme://hostname:port # user = username # pass = password # @api private def self.export_proxy(scheme, path, user, pass) path = "#{scheme}://#{path}" unless path.include?("://") # URI.split returns the following parts: # [scheme, userinfo, host, port, registry, path, opaque, query, fragment] uri = Addressable::URI.encode(path, Addressable::URI) if user && !user.empty? userinfo = Addressable::URI.encode_component(user, USER) if pass userinfo << ":#{Addressable::URI.encode_component(pass, PASSWORD)}" end uri.userinfo = userinfo end path = uri.to_s ENV["#{scheme}_proxy".downcase] = path unless ENV["#{scheme}_proxy".downcase] ENV["#{scheme}_proxy".upcase] = path unless ENV["#{scheme}_proxy".upcase] end # @api private def self.export_no_proxy(value) ENV["no_proxy"] = value unless ENV["no_proxy"] ENV["NO_PROXY"] = value unless ENV["NO_PROXY"] end # Given a scheme, host, and port, return the correct proxy URI based on the # set environment variables, unless exluded by no_proxy, in which case nil # is returned def self.proxy_uri(scheme, host, port) proxy_env_var = ENV["#{scheme}_proxy"].to_s.strip # Check if the proxy string contains a scheme. If not, add the url's scheme to the # proxy before parsing. The regex /^.*:\/\// matches, for example, http://. Reusing proxy # here since we are really just trying to get the string built correctly. proxy = if !proxy_env_var.empty? if proxy_env_var =~ /^.*:\/\// URI.parse(proxy_env_var) else URI.parse("#{scheme}://#{proxy_env_var}") end end return proxy unless fuzzy_hostname_match_any?(host, ENV["no_proxy"]) end # Chef requires an English-language UTF-8 locale to function properly. We attempt # to use the 'locale -a' command and search through a list of preferences until we # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be # able to use that even if there is no English locale on the server, but Mac, Solaris, # AIX, etc do not have that locale. We then try to find an English locale and fall # back to 'C' if we do not. The choice of fallback is pick-your-poison. If we try # to do the work to return a non-US UTF-8 locale then we fail inside of providers when # things like 'svn info' return Japanese and we can't parse them. OTOH, if we pick 'C' then # we will blow up on UTF-8 characters. Between the warn we throw and the Encoding # exception that ruby will throw it is more obvious what is broken if we drop UTF-8 by # default rather than drop English. # # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'. def self.guess_internal_locale # https://github.com/opscode/chef/issues/2181 # Some systems have the `locale -a` command, but the result has # invalid characters for the default encoding. # # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8", # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding. cmd = Mixlib::ShellOut.new("locale -a").run_command cmd.error! locales = cmd.stdout.split case when locales.include?("C.UTF-8") "C.UTF-8" when locales.include?("en_US.UTF-8"), locales.include?("en_US.utf8") "en_US.UTF-8" when locales.include?("en.UTF-8") "en.UTF-8" else # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8 guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i } unless guesses.empty? guessed_locale = guesses.first # Transform into the form en_ZZ.UTF-8 guessed_locale.gsub(/UTF-?8$/i, "UTF-8") else ChefConfig.logger.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support." "C" end end rescue if ChefConfig.windows? ChefConfig.logger.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else." else ChefConfig.logger.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed." end "en_US.UTF-8" end default :internal_locale, guess_internal_locale # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g. # japanese windows encodings). If we do not do this, then knife upload will fail when a cookbook's # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been # passed. Effectively, the Chef Ecosystem is globally UTF-8 by default. Anyone who wants to be # able to upload Shift_JIS or ISO-8859-1 files needs to mark *those* files explicitly with # magic tags to make ruby correctly identify the encoding being used. Changing this default will # break Chef community cookbooks and is very highly discouraged. default :ruby_encoding, Encoding::UTF_8 default :rubygems_url, "https://rubygems.org" # If installed via an omnibus installer, this gives the path to the # "embedded" directory which contains all of the software packaged with # omnibus. This is used to locate the cacert.pem file on windows. def self.embedded_dir Pathname.new(_this_file).ascend do |path| if path.basename.to_s == "embedded" return path.to_s end end nil end # Path to this file in the current install. def self._this_file File.expand_path(__FILE__) end # Set fips mode in openssl. Do any patching necessary to make # sure Chef runs do not crash. # @api private def self.enable_fips_mode OpenSSL.fips_mode = true require "digest" require "digest/sha1" require "digest/md5" # Remove pre-existing constants if they do exist to reduce the # amount of log spam and warnings. Digest.send(:remove_const, "SHA1") if Digest.const_defined?("SHA1") Digest.const_set("SHA1", OpenSSL::Digest::SHA1) OpenSSL::Digest.send(:remove_const, "MD5") if OpenSSL::Digest.const_defined?("MD5") OpenSSL::Digest.const_set("MD5", Digest::MD5) ChefConfig.logger.debug "FIPS mode is enabled." end end end chef-12.14.60/chef-config/lib/chef-config/exceptions.rb000066400000000000000000000014631276456504500224700ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef-config/windows" require "chef-config/logger" module ChefConfig class ConfigurationError < ArgumentError; end class InvalidPath < StandardError; end end chef-12.14.60/chef-config/lib/chef-config/fips.rb000066400000000000000000000030651276456504500212500ustar00rootroot00000000000000# # Author:: Matt Wrock () # Copyright:: Copyright (c) 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. # module ChefConfig def self.fips? if ChefConfig.windows? begin require "win32/registry" rescue LoadError return false end # from http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129(v=vs.85).aspx reg_type = case ::RbConfig::CONFIG["target_cpu"] when "i386" Win32::Registry::KEY_READ | 0x100 when "x86_64" Win32::Registry::KEY_READ | 0x200 else Win32::Registry::KEY_READ end begin Win32::Registry::HKEY_LOCAL_MACHINE.open('System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy', reg_type) do |policy| policy["Enabled"] != 0 end rescue Win32::Registry::Error false end else fips_path = "/proc/sys/crypto/fips_enabled" File.exist?(fips_path) && File.read(fips_path).chomp != "0" end end end chef-12.14.60/chef-config/lib/chef-config/logger.rb000066400000000000000000000024451276456504500215670ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # module ChefConfig # Implements enough of Logger's API that we can use it in place of a real # logger for `ChefConfig.logger` class NullLogger def <<(_msg) end def add(_severity, _message = nil, _progname = nil) end def debug(_progname = nil, &block) end def info(_progname = nil, &block) end def warn(_progname = nil, &block) end def deprecation(_progname = nil, &block) end def error(_progname = nil, &block) end def fatal(_progname = nil, &block) end end @logger = NullLogger.new def self.logger=(new_logger) @logger = new_logger end def self.logger @logger end end chef-12.14.60/chef-config/lib/chef-config/mixin/000077500000000000000000000000001276456504500211025ustar00rootroot00000000000000chef-12.14.60/chef-config/lib/chef-config/mixin/dot_d.rb000066400000000000000000000022121276456504500225150ustar00rootroot00000000000000# # 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-config/path_helper" module ChefConfig module Mixin module DotD def load_dot_d(path) dot_d_files = begin entries = Array.new entries << Dir.glob(File.join( ChefConfig::PathHelper.escape_glob_dir(path), "*.rb")) entries.flatten.select do |entry| File.file?(entry) end end dot_d_files.sort.map do |conf| apply_config(IO.read(conf), conf) end end end end end chef-12.14.60/chef-config/lib/chef-config/mixin/fuzzy_hostname_matcher.rb000066400000000000000000000023301276456504500262150ustar00rootroot00000000000000# # 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 "fuzzyurl" module ChefConfig module Mixin module FuzzyHostnameMatcher def fuzzy_hostname_match_any?(hostname, matches) return matches.to_s.split(/\s*,\s*/).compact.any? do |m| fuzzy_hostname_match?(hostname, m) end if (hostname != nil) && (matches != nil) false end def fuzzy_hostname_match?(hostname, match) # Do greedy matching by adding wildcard if it is not specified match = "*" + match if !match.start_with?("*") Fuzzyurl.matches?(Fuzzyurl.mask(hostname: match), hostname) end end end end chef-12.14.60/chef-config/lib/chef-config/package_task.rb000066400000000000000000000215671276456504500227330ustar00rootroot00000000000000# # Author:: Kartik Null Cating-Subramanian () # Copyright:: Copyright 2015-2016, Chef, 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 "rake" require "rubygems" require "rubygems/package_task" module ChefConfig class PackageTask < Rake::TaskLib # Full path to root of top-level repository. All other files (like VERSION or # lib//version.rb are rooted at this path). attr_accessor :root_path # Name of the top-level module/library build built. This is used to define # the top level module which contains VERSION and MODULE_ROOT. attr_accessor :module_name # Name of the gem being built. This is used to find the lines to fix in # Gemfile.lock. attr_accessor :gem_name # Should the generated version.rb be in a class or module? Default is false (module). attr_accessor :generate_version_class # Paths to the roots of any components that also support ChefPackageTask. # If relative paths are provided, they are rooted against root_path. attr_accessor :component_paths # This is the module name as it appears on the path "lib/module/". # e.g. for module_name "ChefDK", you'd want module_path to be "chef-dk". # The default is module_name but lower-cased. attr_writer :module_path def module_path @module_path || module_name.downcase end # Directory used to store package files and output that is generated. # This has the same meaning (or lack thereof) as package_dir in # rake/packagetask. attr_accessor :package_dir # Name of git remote used to push tags during a release. Default is origin. attr_accessor :git_remote def initialize(root_path = nil, module_name = nil, gem_name = nil) init(root_path, module_name, gem_name) yield self if block_given? define unless root_path.nil? || module_name.nil? end def init(root_path, module_name, gem_name) @root_path = root_path @module_name = module_name @gem_name = gem_name @component_paths = [] @module_path = nil @package_dir = "pkg" @git_remote = "origin" @generate_version_class = false end def component_full_paths component_paths.map { |path| File.expand_path(path, root_path) } end def version_rb_path File.expand_path("lib/#{module_path}/version.rb", root_path) end def chef_root_path module_name == "Chef" ? root_path : File.dirname(root_path) end def version_file_path File.join(chef_root_path, "VERSION") end def gemfile_lock_path File.join(root_path, "Gemfile.lock") end def version IO.read(version_file_path).strip end def full_package_dir File.expand_path(package_dir, root_path) end def class_or_module generate_version_class ? "class" : "module" end def with_clean_env(&block) if defined?(Bundler) Bundler.with_clean_env(&block) else yield end end def define raise "Need to provide package root and module name" if root_path.nil? || module_name.nil? desc "Build Gems of component dependencies" task :package_components do component_full_paths.each do |component_path| Dir.chdir(component_path) do sh "rake package" end end end task :package => :package_components desc "Build and install component dependencies" task :install_components => :package_components do component_full_paths.each do |component_path| Dir.chdir(component_path) do sh "rake install" end end end task :install => :install_components desc "Clean up builds of component dependencies" task :clobber_component_packages do component_full_paths.each do |component_path| Dir.chdir(component_path) do sh "rake clobber_package" end end end task :clobber_package => :clobber_component_packages desc "Update the version number for component dependencies" task :update_components_versions do component_full_paths.each do |component_path| Dir.chdir(component_path) do sh "rake version" end end end namespace :version do desc 'Regenerate lib/#{@module_path}/version.rb from VERSION file' task :update => :update_components_versions do update_version_rb update_gemfile_lock end task :bump => %w{version:bump_patch version:update} task :show do puts version end # Add 1 to the current patch version in the VERSION file, and write it back out. task :bump_patch do current_version = version new_version = current_version.sub(/^(\d+\.\d+\.)(\d+)/) { "#{$1}#{$2.to_i + 1}" } puts "Updating version in #{version_rb_path} from #{current_version.chomp} to #{new_version.chomp}" IO.write(version_file_path, new_version) end def update_version_rb # rubocop:disable Lint/NestedMethodDefinition puts "Updating #{version_rb_path} to include version #{version} ..." contents = <<-VERSION_RB # Copyright:: Copyright 2010-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. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # NOTE: This file is generated by running `rake version` in the top level of # this repo. Do not edit this manually. Edit the VERSION file and run the rake # task instead. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #{class_or_module} #{module_name} #{module_name.upcase}_ROOT = File.expand_path("../..", __FILE__) VERSION = "#{version}" end # # NOTE: the Chef::Version class is defined in version_class.rb # # NOTE: DO NOT Use the Chef::Version class on #{module_name}::VERSIONs. The # Chef::Version class is for _cookbooks_ only, and cannot handle # pre-release versions like "10.14.0.rc.2". Please use Rubygem's # Gem::Version class instead. # VERSION_RB IO.write(version_rb_path, contents) end def update_gemfile_lock # rubocop:disable Lint/NestedMethodDefinition if File.exist?(gemfile_lock_path) puts "Updating #{gemfile_lock_path} to include version #{version} ..." contents = IO.read(gemfile_lock_path) contents.gsub!(/^\s*(chef|chef-config)\s*\((= )?\S+\)\s*$/) do |line| line.gsub(/\((= )?\d+(\.\d+)+/) { "(#{$1}#{version}" } end IO.write(gemfile_lock_path, contents) end end end task :version => "version:update" gemspec_platform_to_install = "" Dir[File.expand_path("*.gemspec", root_path)].reverse_each do |gemspec_path| gemspec = eval(IO.read(gemspec_path)) Gem::PackageTask.new(gemspec) do |task| task.package_dir = full_package_dir end gemspec_platform_to_install = "-#{gemspec.platform}" if gemspec.platform != Gem::Platform::RUBY && Gem::Platform.match(gemspec.platform) end desc "Build and install a #{module_path} gem" task :install => [:package] do with_clean_env do full_module_path = File.join(full_package_dir, module_path) sh %{gem install #{full_module_path}-#{version}#{gemspec_platform_to_install}.gem --no-rdoc --no-ri} end end task :uninstall do sh %{gem uninstall #{module_path} -x -v #{version} } end desc "Build it, tag it and ship it" task :ship => [:clobber_package, :gem] do sh("git tag #{version}") sh("git push #{git_remote} --tags") Dir[File.expand_path("*.gem", full_package_dir)].reverse_each do |built_gem| sh("gem push #{built_gem}") end end end end end chef-12.14.60/chef-config/lib/chef-config/path_helper.rb000066400000000000000000000252451276456504500226060ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef-config/windows" require "chef-config/logger" require "chef-config/exceptions" module ChefConfig class PathHelper # Maximum characters in a standard Windows path (260 including drive letter and NUL) WIN_MAX_PATH = 259 def self.dirname(path) if ChefConfig.windows? # Find the first slash, not counting trailing slashes end_slash = path.size loop do slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1) if !slash return end_slash == path.size ? "." : path_separator elsif slash == end_slash - 1 end_slash = slash else return path[0..slash - 1] end end else ::File.dirname(path) end end BACKSLASH = '\\'.freeze def self.path_separator if ChefConfig.windows? File::ALT_SEPARATOR || BACKSLASH else File::SEPARATOR end end def self.join(*args) path_separator_regex = Regexp.escape(File::SEPARATOR) unless path_separator == File::SEPARATOR path_separator_regex << Regexp.escape(path_separator) end trailing_slashes = /[#{path_separator_regex}]+$/ leading_slashes = /^[#{path_separator_regex}]+/ args.flatten.inject() do |joined_path, component| joined_path = joined_path.sub(trailing_slashes, "") component = component.sub(leading_slashes, "") joined_path + "#{path_separator}#{component}" end end def self.validate_path(path) if ChefConfig.windows? unless printable?(path) msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings." ChefConfig.logger.error(msg) raise ChefConfig::InvalidPath, msg end if windows_max_length_exceeded?(path) ChefConfig.logger.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'") path.insert(0, "\\\\?\\") end end path end def self.windows_max_length_exceeded?(path) # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx unless path =~ /^\\\\?\\/ if path.length > WIN_MAX_PATH return true end end false end def self.printable?(string) # returns true if string is free of non-printable characters (escape sequences) # this returns false for whitespace escape sequences as well, e.g. \n\t if string =~ /[^[:print:]]/ false else true end end # Produces a comparable path. def self.canonical_path(path, add_prefix = true) # First remove extra separators and resolve any relative paths abs_path = File.absolute_path(path) if ChefConfig.windows? # Add the \\?\ API prefix on Windows unless add_prefix is false # Downcase on Windows where paths are still case-insensitive abs_path.gsub!(::File::SEPARATOR, path_separator) if add_prefix && abs_path !~ /^\\\\?\\/ abs_path.insert(0, "\\\\?\\") end abs_path.downcase! end abs_path end # This is the INVERSE of Pathname#cleanpath, it converts forward # slashes to backwhacks for Windows. Since the Ruby API and the # Windows APIs all consume forward slashes, this helper function # should only be used for *DISPLAY* logic to send strings back # to the user with backwhacks. Internally, filename paths should # generally be stored with forward slashes for consistency. It is # not necessary or desired to blindly convert pathnames to have # backwhacks on Windows. # # Generally, if the user isn't going to be seeing it, you should be # using Pathname#cleanpath intead of this function. def self.cleanpath(path) path = Pathname.new(path).cleanpath.to_s # ensure all forward slashes are backslashes if ChefConfig.windows? path = path.gsub(File::SEPARATOR, path_separator) end path end def self.paths_eql?(path1, path2) canonical_path(path1) == canonical_path(path2) end # Note: this method is deprecated. Please use escape_glob_dirs # Paths which may contain glob-reserved characters need # to be escaped before globbing can be done. # http://stackoverflow.com/questions/14127343 def self.escape_glob(*parts) path = cleanpath(join(*parts)) path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\" + x } end # This function does not switch to backslashes for windows # This is because only forwardslashes should be used with dir (even for windows) def self.escape_glob_dir(*parts) path = Pathname.new(join(*parts)).cleanpath.to_s path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\" + x } end def self.relative_path_from(from, to) Pathname.new(cleanpath(to)).relative_path_from(Pathname.new(cleanpath(from))) end # Retrieves the "home directory" of the current user while trying to ascertain the existence # of said directory. The path returned uses / for all separators (the ruby standard format). # If the home directory doesn't exist or an error is otherwise encountered, nil is returned. # # If a set of path elements is provided, they are appended as-is to the home path if the # homepath exists. # # If an optional block is provided, the joined path is passed to that block if the home path is # valid and the result of the block is returned instead. # # Home-path discovery is performed once. If a path is discovered, that value is memoized so # that subsequent calls to home_dir don't bounce around. # # See self.all_homes. def self.home(*args) @@home_dir ||= self.all_homes { |p| break p } if @@home_dir path = File.join(@@home_dir, *args) block_given? ? (yield path) : path end end # See self.home. This method performs a similar operation except that it yields all the different # possible values of 'HOME' that one could have on this platform. Hence, on windows, if # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice. # This method goes out and checks the existence of each location at the time of the call. # # The return is a list of all the returned values from each block invocation or a list of paths # if no block is provided. def self.all_homes(*args) paths = [] if ChefConfig.windows? # By default, Ruby uses the the following environment variables to determine Dir.home: # HOME # HOMEDRIVE HOMEPATH # USERPROFILE # Ruby only checks to see if the variable is specified - not if the directory actually exists. # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive) # while USERPROFILE points to the location where the user application settings and profile are stored. HOME # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is # HOMESHARE instead of HOMEDRIVE. # # We instead walk down the following and only include paths that actually exist. # HOME # HOMEDRIVE HOMEPATH # HOMESHARE HOMEPATH # USERPROFILE paths << ENV["HOME"] paths << ENV["HOMEDRIVE"] + ENV["HOMEPATH"] if ENV["HOMEDRIVE"] && ENV["HOMEPATH"] paths << ENV["HOMESHARE"] + ENV["HOMEPATH"] if ENV["HOMESHARE"] && ENV["HOMEPATH"] paths << ENV["USERPROFILE"] end paths << Dir.home if ENV["HOME"] # Depending on what environment variables we're using, the slashes can go in any which way. # Just change them all to / to keep things consistent. # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on # the particular brand of kool-aid you consume. This code assumes that \ and / are both # path separators on any system being used. paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path } # Filter out duplicate paths and paths that don't exist. valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path.force_encoding("utf-8")) } valid_paths = valid_paths.uniq # Join all optional path elements at the end. # If a block is provided, invoke it - otherwise just return what we've got. joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) } if block_given? joined_paths.each { |p| yield p } else joined_paths end end # Determine if the given path is protected by OS X System Integrity Protection. def self.is_sip_path?(path, node) if node["platform"] == "mac_os_x" && Gem::Version.new(node["platform_version"]) >= Gem::Version.new("10.11") # todo: parse rootless.conf for this? sip_paths = [ "/System", "/bin", "/sbin", "/usr" ] sip_paths.each do |sip_path| ChefConfig.logger.info("This is a SIP path, checking if it in exceptions list.") return true if path.start_with?(sip_path) end false else false end end # Determine if the given path is on the exception list for OS X System Integrity Protection. def self.writable_sip_path?(path) # todo: parse rootless.conf for this? sip_exceptions = [ "/System/Library/Caches", "/System/Library/Extensions", "/System/Library/Speech", "/System/Library/User Template", "/usr/libexec/cups", "/usr/local", "/usr/share/man" ] sip_exceptions.each do |exception_path| return true if path.start_with?(exception_path) end ChefConfig.logger.error("Cannot write to a SIP Path on OS X 10.11+") false end end end chef-12.14.60/chef-config/lib/chef-config/version.rb000066400000000000000000000025701276456504500217740ustar00rootroot00000000000000# Copyright:: Copyright 2010-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. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # NOTE: This file is generated by running `rake version` in the top level of # this repo. Do not edit this manually. Edit the VERSION file and run the rake # task instead. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! module ChefConfig CHEFCONFIG_ROOT = File.expand_path("../..", __FILE__) VERSION = "12.14.60" end # # NOTE: the Chef::Version class is defined in version_class.rb # # NOTE: DO NOT Use the Chef::Version class on ChefConfig::VERSIONs. The # Chef::Version class is for _cookbooks_ only, and cannot handle # pre-release versions like "10.14.0.rc.2". Please use Rubygem's # Gem::Version class instead. # chef-12.14.60/chef-config/lib/chef-config/windows.rb000066400000000000000000000014161276456504500217770ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # module ChefConfig def self.windows? if RUBY_PLATFORM =~ /mswin|mingw|windows/ true else false end end end chef-12.14.60/chef-config/lib/chef-config/workstation_config_loader.rb000066400000000000000000000134071276456504500255470ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef-config/config" require "chef-config/exceptions" require "chef-config/logger" require "chef-config/path_helper" require "chef-config/windows" require "chef-config/mixin/dot_d" module ChefConfig class WorkstationConfigLoader include ChefConfig::Mixin::DotD # Path to a config file requested by user, (e.g., via command line option). Can be nil attr_accessor :explicit_config_file # TODO: initialize this with a logger for Chef and Knife def initialize(explicit_config_file, logger = nil) @explicit_config_file = explicit_config_file @chef_config_dir = nil @config_location = nil @logger = logger || NullLogger.new end def no_config_found? config_location.nil? end def config_location @config_location ||= (explicit_config_file || locate_local_config) end def chef_config_dir if @chef_config_dir.nil? @chef_config_dir = false full_path = working_directory.split(File::SEPARATOR) (full_path.length - 1).downto(0) do |i| candidate_directory = File.join(full_path[0..i] + [".chef"]) if File.exist?(candidate_directory) && File.directory?(candidate_directory) @chef_config_dir = candidate_directory break end end end @chef_config_dir end def load # Ignore it if there's no explicit_config_file and can't find one at a # default path. if !config_location.nil? if explicit_config_file && !path_exists?(config_location) raise ChefConfig::ConfigurationError, "Specified config file #{config_location} does not exist" end # Have to set Config.config_file b/c other config is derived from it. Config.config_file = config_location apply_config(IO.read(config_location), config_location) end load_dot_d(Config[:config_d_dir]) if Config[:config_d_dir] end # (Private API, public for test purposes) def env ENV end # (Private API, public for test purposes) def path_exists?(path) Pathname.new(path).expand_path.exist? end private def have_config?(path) if path_exists?(path) logger.info("Using config at #{path}") true else logger.debug("Config not found at #{path}, trying next option") false end end def locate_local_config candidate_configs = [] # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine) if env["KNIFE_HOME"] candidate_configs << File.join(env["KNIFE_HOME"], "config.rb") candidate_configs << File.join(env["KNIFE_HOME"], "knife.rb") end # Look for $PWD/knife.rb if Dir.pwd candidate_configs << File.join(Dir.pwd, "config.rb") candidate_configs << File.join(Dir.pwd, "knife.rb") end # Look for $UPWARD/.chef/knife.rb if chef_config_dir candidate_configs << File.join(chef_config_dir, "config.rb") candidate_configs << File.join(chef_config_dir, "knife.rb") end # Look for $HOME/.chef/knife.rb PathHelper.home(".chef") do |dot_chef_dir| candidate_configs << File.join(dot_chef_dir, "config.rb") candidate_configs << File.join(dot_chef_dir, "knife.rb") end candidate_configs.find do |candidate_config| have_config?(candidate_config) end end def working_directory a = if ChefConfig.windows? env["CD"] else env["PWD"] end || Dir.pwd a end def apply_config(config_content, config_file_path) Config.from_string(config_content, config_file_path) rescue SignalException raise rescue SyntaxError => e message = "" message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n" message << "#{e.class.name}: #{e.message}\n" if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/] line = file_line[/:([\d]+)$/, 1].to_i message << highlight_config_error(config_file_path, line) end raise ChefConfig::ConfigurationError, message rescue Exception => e message = "You have an error in your config file #{config_file_path}\n\n" message << "#{e.class.name}: #{e.message}\n" filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/) filtered_trace.each { |bt_line| message << " " << bt_line << "\n" } if !filtered_trace.empty? line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1] message << highlight_config_error(config_file_path, line_nr.to_i) end raise ChefConfig::ConfigurationError, message end def highlight_config_error(file, line) config_file_lines = [] IO.readlines(file).each_with_index { |l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}" } if line == 1 lines = config_file_lines[0..3] else lines = config_file_lines[Range.new(line - 2, line)] end "Relevant file content:\n" + lines.join("\n") + "\n" end def logger @logger end end end chef-12.14.60/chef-config/spec/000077500000000000000000000000001276456504500157725ustar00rootroot00000000000000chef-12.14.60/chef-config/spec/spec_helper.rb000066400000000000000000000064501276456504500206150ustar00rootroot00000000000000require "chef-config/windows" # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. config.expect_with :rspec do |expectations| # This option will default to `true` in RSpec 4. It makes the `description` # and `failure_message` of custom matchers include text for helper methods # defined using `chain`, e.g.: # be_bigger_than(2).and_smaller_than(4).description # # => "be bigger than 2 and smaller than 4" # ...rather than: # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true end # rspec-mocks config goes here. You can use an alternate test double # library (such as bogus or mocha) by changing the `mock_with` option here. config.mock_with :rspec do |mocks| # Prevents you from mocking or stubbing a method that does not exist on # a real object. This is generally recommended, and will default to # `true` in RSpec 4. mocks.verify_partial_doubles = true end # These two settings work together to allow you to limit a spec run # to individual examples or groups you care about by tagging them with # `:focus` metadata. When nothing is tagged with `:focus`, all examples # get run. config.filter_run :focus config.run_all_when_everything_filtered = true config.filter_run_excluding :windows_only => true unless ChefConfig.windows? config.filter_run_excluding :unix_only => true if ChefConfig.windows? # Limits the available syntax to the non-monkey patched syntax that is # recommended. For more details, see: # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching config.disable_monkey_patching! # This setting enables warnings. It's recommended, but in some cases may # be too noisy due to issues in dependencies. config.warnings = true # Many RSpec users commonly either run the entire suite or an individual # file, and it's useful to allow more verbose output when running an # individual spec file. if config.files_to_run.one? # Use the documentation formatter for detailed output, # unless a formatter has already been configured # (e.g. via a command-line flag). config.default_formatter = "doc" end # Print the 10 slowest examples and example groups at the # end of the spec run, to help surface which specs are running # particularly slow. # config.profile_examples = 10 # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = :random # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed end chef-12.14.60/chef-config/spec/unit/000077500000000000000000000000001276456504500167515ustar00rootroot00000000000000chef-12.14.60/chef-config/spec/unit/config_spec.rb000066400000000000000000001117301276456504500215600ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Kyle Goodwin () # 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" require "chef-config/config" RSpec.describe ChefConfig::Config do before(:each) do ChefConfig::Config.reset # By default, treat deprecation warnings as errors in tests. ChefConfig::Config.treat_deprecation_warnings_as_errors(true) # Set environment variable so the setting persists in child processes ENV["CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS"] = "1" end describe "config attribute writer: chef_server_url" do before do ChefConfig::Config.chef_server_url = "https://junglist.gen.nz" end it "sets the server url" do expect(ChefConfig::Config.chef_server_url).to eq("https://junglist.gen.nz") end context "when the url has a leading space" do before do ChefConfig::Config.chef_server_url = " https://junglist.gen.nz" end it "strips the space from the url when setting" do expect(ChefConfig::Config.chef_server_url).to eq("https://junglist.gen.nz") end end context "when the url is a frozen string" do before do ChefConfig::Config.chef_server_url = " https://junglist.gen.nz".freeze end it "strips the space from the url when setting without raising an error" do expect(ChefConfig::Config.chef_server_url).to eq("https://junglist.gen.nz") end end context "when the url is invalid" do it "raises an exception" do expect { ChefConfig::Config.chef_server_url = "127.0.0.1" }.to raise_error(ChefConfig::ConfigurationError) end end end describe "when configuring formatters" do # if TTY and not(force-logger) # formatter = configured formatter or default formatter # formatter goes to STDOUT/ERR # if log file is writeable # log level is configured level or info # log location is file # else # log level is warn # log location is STDERR # end # elsif not(TTY) and force formatter # formatter = configured formatter or default formatter # if log_location specified # formatter goes to log_location # else # formatter goes to STDOUT/ERR # end # else # formatter = "null" # log_location = configured-value or defualt # log_level = info or defualt # end # it "has an empty list of formatters by default" do expect(ChefConfig::Config.formatters).to eq([]) end it "configures a formatter with a short name" do ChefConfig::Config.add_formatter(:doc) expect(ChefConfig::Config.formatters).to eq([[:doc, nil]]) end it "configures a formatter with a file output" do ChefConfig::Config.add_formatter(:doc, "/var/log/formatter.log") expect(ChefConfig::Config.formatters).to eq([[:doc, "/var/log/formatter.log"]]) end end [ false, true ].each do |is_windows| context "On #{is_windows ? 'Windows' : 'Unix'}" do def to_platform(*args) ChefConfig::Config.platform_specific_path(*args) end before :each do allow(ChefConfig).to receive(:windows?).and_return(is_windows) end describe "class method: platform_specific_path" do if is_windows it "should return a windows path on windows systems" do path = "/etc/chef/cookbooks" allow(ChefConfig::Config).to receive(:env).and_return({ "SYSTEMDRIVE" => "C:" }) # match on a regex that looks for the base path with an optional # system drive at the beginning (c:) # system drive is not hardcoded b/c it can change and b/c it is not present on linux systems expect(ChefConfig::Config.platform_specific_path(path)).to eq("C:\\chef\\cookbooks") end else it "should return given path on non-windows systems" do path = "/etc/chef/cookbooks" expect(ChefConfig::Config.platform_specific_path(path)).to eq("/etc/chef/cookbooks") end end end describe "default values" do let :primary_cache_path do if is_windows "#{ChefConfig::Config.env['SYSTEMDRIVE']}\\chef" else "/var/chef" end end let :secondary_cache_path do if is_windows "#{ChefConfig::Config[:user_home]}\\.chef" else "#{ChefConfig::Config[:user_home]}/.chef" end end before do if is_windows allow(ChefConfig::Config).to receive(:env).and_return({ "SYSTEMDRIVE" => "C:" }) ChefConfig::Config[:user_home] = 'C:\Users\charlie' else ChefConfig::Config[:user_home] = "/Users/charlie" end allow(ChefConfig::Config).to receive(:path_accessible?).and_return(false) end describe "ChefConfig::Config[:fips]" do let(:fips_enabled) { false } before(:all) do @original_env = ENV.to_hash end after(:all) do ENV.clear ENV.update(@original_env) end before(:each) do ENV["CHEF_FIPS"] = nil allow(ChefConfig).to receive(:fips?).and_return(fips_enabled) end it "returns false when no environment is set and not enabled on system" do expect(ChefConfig::Config[:fips]).to eq(false) end context "when ENV['CHEF_FIPS'] is empty" do before do ENV["CHEF_FIPS"] = "" end it "returns false" do expect(ChefConfig::Config[:fips]).to eq(false) end end context "when ENV['CHEF_FIPS'] is set" do before do ENV["CHEF_FIPS"] = "1" end it "returns true" do expect(ChefConfig::Config[:fips]).to eq(true) end end context "when fips is enabled on system" do let(:fips_enabled) { true } it "returns true" do expect(ChefConfig::Config[:fips]).to eq(true) end end end describe "ChefConfig::Config[:chef_server_root]" do context "when chef_server_url isn't set manually" do it "returns the default of 'https://localhost:443'" do expect(ChefConfig::Config[:chef_server_root]).to eq("https://localhost:443") end end context "when chef_server_url matches '../organizations/*' without a trailing slash" do before do ChefConfig::Config[:chef_server_url] = "https://example.com/organizations/myorg" end it "returns the full URL without /organizations/*" do expect(ChefConfig::Config[:chef_server_root]).to eq("https://example.com") end end context "when chef_server_url matches '../organizations/*' with a trailing slash" do before do ChefConfig::Config[:chef_server_url] = "https://example.com/organizations/myorg/" end it "returns the full URL without /organizations/*" do expect(ChefConfig::Config[:chef_server_root]).to eq("https://example.com") end end context "when chef_server_url matches '..organizations..' but not '../organizations/*'" do before do ChefConfig::Config[:chef_server_url] = "https://organizations.com/organizations" end it "returns the full URL without any modifications" do expect(ChefConfig::Config[:chef_server_root]).to eq(ChefConfig::Config[:chef_server_url]) end end context "when chef_server_url is a standard URL without the string organization(s)" do before do ChefConfig::Config[:chef_server_url] = "https://example.com/some_other_string" end it "returns the full URL without any modifications" do expect(ChefConfig::Config[:chef_server_root]).to eq(ChefConfig::Config[:chef_server_url]) end end end describe "ChefConfig::Config[:cache_path]" do context "when /var/chef exists and is accessible" do it "defaults to /var/chef" do allow(ChefConfig::Config).to receive(:path_accessible?).with(to_platform("/var/chef")).and_return(true) expect(ChefConfig::Config[:cache_path]).to eq(primary_cache_path) end end context "when /var/chef does not exist and /var is accessible" do it "defaults to /var/chef" do allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false) allow(ChefConfig::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(true) expect(ChefConfig::Config[:cache_path]).to eq(primary_cache_path) end end context "when /var/chef does not exist and /var is not accessible" do it "defaults to $HOME/.chef" do allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false) allow(ChefConfig::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(false) expect(ChefConfig::Config[:cache_path]).to eq(secondary_cache_path) end end context "when /var/chef exists and is not accessible" do it "defaults to $HOME/.chef" do allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(true) allow(File).to receive(:readable?).with(to_platform("/var/chef")).and_return(true) allow(File).to receive(:writable?).with(to_platform("/var/chef")).and_return(false) expect(ChefConfig::Config[:cache_path]).to eq(secondary_cache_path) end end context "when chef is running in local mode" do before do ChefConfig::Config.local_mode = true end context "and config_dir is /a/b/c" do before do ChefConfig::Config.config_dir to_platform("/a/b/c") end it "cache_path is /a/b/c/local-mode-cache" do expect(ChefConfig::Config.cache_path).to eq(to_platform("/a/b/c/local-mode-cache")) end end context "and config_dir is /a/b/c/" do before do ChefConfig::Config.config_dir to_platform("/a/b/c/") end it "cache_path is /a/b/c/local-mode-cache" do expect(ChefConfig::Config.cache_path).to eq(to_platform("/a/b/c/local-mode-cache")) end end end end it "ChefConfig::Config[:stream_execute_output] defaults to false" do expect(ChefConfig::Config[:stream_execute_output]).to eq(false) end it "ChefConfig::Config[:show_download_progress] defaults to false" do expect(ChefConfig::Config[:show_download_progress]).to eq(false) end it "ChefConfig::Config[:download_progress_interval] defaults to every 10%" do expect(ChefConfig::Config[:download_progress_interval]).to eq(10) end it "ChefConfig::Config[:file_backup_path] defaults to /var/chef/backup" do allow(ChefConfig::Config).to receive(:cache_path).and_return(primary_cache_path) backup_path = is_windows ? "#{primary_cache_path}\\backup" : "#{primary_cache_path}/backup" expect(ChefConfig::Config[:file_backup_path]).to eq(backup_path) end it "ChefConfig::Config[:ssl_verify_mode] defaults to :verify_peer" do expect(ChefConfig::Config[:ssl_verify_mode]).to eq(:verify_peer) end it "ChefConfig::Config[:ssl_ca_path] defaults to nil" do expect(ChefConfig::Config[:ssl_ca_path]).to be_nil end describe "ChefConfig::Config[:repo_mode]" do context "when local mode is enabled" do before { ChefConfig::Config[:local_mode] = true } it "defaults to 'hosted_everything'" do expect(ChefConfig::Config[:repo_mode]).to eq("hosted_everything") end context "and osc_compat is enabled" do before { ChefConfig::Config.chef_zero.osc_compat = true } it "defaults to 'everything'" do expect(ChefConfig::Config[:repo_mode]).to eq("everything") end end end context "when local mode is not enabled" do context "and the chef_server_url is multi-tenant" do before { ChefConfig::Config[:chef_server_url] = "https://chef.example/organizations/example" } it "defaults to 'hosted_everything'" do expect(ChefConfig::Config[:repo_mode]).to eq("hosted_everything") end end context "and the chef_server_url is not multi-tenant" do before { ChefConfig::Config[:chef_server_url] = "https://chef.example/" } it "defaults to 'everything'" do expect(ChefConfig::Config[:repo_mode]).to eq("everything") end end end end describe "ChefConfig::Config[:chef_repo_path]" do context "when cookbook_path is set to a single path" do before { ChefConfig::Config[:cookbook_path] = "/home/anne/repo/cookbooks" } it "is set to a path one directory up from the cookbook_path" do expected = File.expand_path("/home/anne/repo") expect(ChefConfig::Config[:chef_repo_path]).to eq(expected) end end context "when cookbook_path is set to multiple paths" do before do ChefConfig::Config[:cookbook_path] = [ "/home/anne/repo/cookbooks", "/home/anne/other_repo/cookbooks", ] end it "is set to an Array of paths one directory up from the cookbook_paths" do expected = [ "/home/anne/repo", "/home/anne/other_repo"].map { |p| File.expand_path(p) } expect(ChefConfig::Config[:chef_repo_path]).to eq(expected) end end context "when cookbook_path is not set but cookbook_artifact_path is set" do before do ChefConfig::Config[:cookbook_path] = nil ChefConfig::Config[:cookbook_artifact_path] = "/home/roxie/repo/cookbook_artifacts" end it "is set to a path one directory up from the cookbook_artifact_path" do expected = File.expand_path("/home/roxie/repo") expect(ChefConfig::Config[:chef_repo_path]).to eq(expected) end end context "when cookbook_path is not set" do before { ChefConfig::Config[:cookbook_path] = nil } it "is set to the cache_path" do expect(ChefConfig::Config[:chef_repo_path]).to eq(ChefConfig::Config[:cache_path]) end end end # On Windows, we'll detect an omnibus build and set this to the # cacert.pem included in the package, but it's nil if you're on Windows # w/o omnibus (e.g., doing development on Windows, custom build, etc.) if !is_windows it "ChefConfig::Config[:ssl_ca_file] defaults to nil" do expect(ChefConfig::Config[:ssl_ca_file]).to be_nil end end it "ChefConfig::Config[:data_bag_path] defaults to /var/chef/data_bags" do allow(ChefConfig::Config).to receive(:cache_path).and_return(primary_cache_path) data_bag_path = is_windows ? "#{primary_cache_path}\\data_bags" : "#{primary_cache_path}/data_bags" expect(ChefConfig::Config[:data_bag_path]).to eq(data_bag_path) end it "ChefConfig::Config[:environment_path] defaults to /var/chef/environments" do allow(ChefConfig::Config).to receive(:cache_path).and_return(primary_cache_path) environment_path = is_windows ? "#{primary_cache_path}\\environments" : "#{primary_cache_path}/environments" expect(ChefConfig::Config[:environment_path]).to eq(environment_path) end it "ChefConfig::Config[:cookbook_artifact_path] defaults to /var/chef/cookbook_artifacts" do allow(ChefConfig::Config).to receive(:cache_path).and_return(primary_cache_path) environment_path = is_windows ? "#{primary_cache_path}\\cookbook_artifacts" : "#{primary_cache_path}/cookbook_artifacts" expect(ChefConfig::Config[:cookbook_artifact_path]).to eq(environment_path) end describe "setting the config dir" do context "when the config file is given with a relative path" do before do ChefConfig::Config.config_file = "client.rb" end it "expands the path when determining config_dir" do # config_dir goes through PathHelper.canonical_path, which # downcases on windows because the FS is case insensitive, so we # have to downcase expected and actual to make the tests work. expect(ChefConfig::Config.config_dir.downcase).to eq(to_platform(Dir.pwd).downcase) end it "does not set derived paths at FS root" do ChefConfig::Config.local_mode = true expect(ChefConfig::Config.cache_path.downcase).to eq(to_platform(File.join(Dir.pwd, "local-mode-cache")).downcase) end end context "when the config file is /etc/chef/client.rb" do before do config_location = to_platform("/etc/chef/client.rb").downcase allow(File).to receive(:absolute_path).with(config_location).and_return(config_location) ChefConfig::Config.config_file = config_location end it "config_dir is /etc/chef" do expect(ChefConfig::Config.config_dir).to eq(to_platform("/etc/chef").downcase) end context "and chef is running in local mode" do before do ChefConfig::Config.local_mode = true end it "config_dir is /etc/chef" do expect(ChefConfig::Config.config_dir).to eq(to_platform("/etc/chef").downcase) end end context "when config_dir is set to /other/config/dir/" do before do ChefConfig::Config.config_dir = to_platform("/other/config/dir/") end it "yields the explicit value" do expect(ChefConfig::Config.config_dir).to eq(to_platform("/other/config/dir/")) end end end context "when the user's home dir is /home/charlie/" do before do ChefConfig::Config.user_home = to_platform("/home/charlie") end it "config_dir is /home/charlie/.chef/" do expect(ChefConfig::Config.config_dir).to eq(ChefConfig::PathHelper.join(to_platform("/home/charlie/.chef"), "")) end context "and chef is running in local mode" do before do ChefConfig::Config.local_mode = true end it "config_dir is /home/charlie/.chef/" do expect(ChefConfig::Config.config_dir).to eq(ChefConfig::PathHelper.join(to_platform("/home/charlie/.chef"), "")) end end end end if is_windows describe "finding the windows embedded dir" do let(:default_config_location) { "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" } let(:alternate_install_location) { "c:/my/alternate/install/place/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" } let(:non_omnibus_location) { "c:/my/dev/stuff/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" } let(:default_ca_file) { "c:/opscode/chef/embedded/ssl/certs/cacert.pem" } it "finds the embedded dir in the default location" do allow(ChefConfig::Config).to receive(:_this_file).and_return(default_config_location) expect(ChefConfig::Config.embedded_dir).to eq("c:/opscode/chef/embedded") end it "finds the embedded dir in a custom install location" do allow(ChefConfig::Config).to receive(:_this_file).and_return(alternate_install_location) expect(ChefConfig::Config.embedded_dir).to eq("c:/my/alternate/install/place/chef/embedded") end it "doesn't error when not in an omnibus install" do allow(ChefConfig::Config).to receive(:_this_file).and_return(non_omnibus_location) expect(ChefConfig::Config.embedded_dir).to be_nil end it "sets the ssl_ca_cert path if the cert file is available" do allow(ChefConfig::Config).to receive(:_this_file).and_return(default_config_location) allow(File).to receive(:exist?).with(default_ca_file).and_return(true) expect(ChefConfig::Config.ssl_ca_file).to eq(default_ca_file) end end end end describe "ChefConfig::Config[:user_home]" do it "should set when HOME is provided" do expected = to_platform("/home/kitten") allow(ChefConfig::PathHelper).to receive(:home).and_return(expected) expect(ChefConfig::Config[:user_home]).to eq(expected) end it "falls back to the current working directory when HOME and USERPROFILE is not set" do allow(ChefConfig::PathHelper).to receive(:home).and_return(nil) expect(ChefConfig::Config[:user_home]).to eq(Dir.pwd) end end describe "ChefConfig::Config[:encrypted_data_bag_secret]" do let(:db_secret_default_path) { to_platform("/etc/chef/encrypted_data_bag_secret") } before do allow(File).to receive(:exist?).with(db_secret_default_path).and_return(secret_exists) end context "/etc/chef/encrypted_data_bag_secret exists" do let(:secret_exists) { true } it "sets the value to /etc/chef/encrypted_data_bag_secret" do expect(ChefConfig::Config[:encrypted_data_bag_secret]).to eq db_secret_default_path end end context "/etc/chef/encrypted_data_bag_secret does not exist" do let(:secret_exists) { false } it "sets the value to nil" do expect(ChefConfig::Config[:encrypted_data_bag_secret]).to be_nil end end end describe "ChefConfig::Config[:event_handlers]" do it "sets a event_handlers to an empty array by default" do expect(ChefConfig::Config[:event_handlers]).to eq([]) end it "should be able to add custom handlers" do o = Object.new ChefConfig::Config[:event_handlers] << o expect(ChefConfig::Config[:event_handlers]).to be_include(o) end end describe "ChefConfig::Config[:user_valid_regex]" do context "on a platform that is not Windows" do it "allows one letter usernames" do any_match = ChefConfig::Config[:user_valid_regex].any? { |regex| regex.match("a") } expect(any_match).to be_truthy end end end describe "ChefConfig::Config[:internal_locale]" do let(:shell_out) do cmd = instance_double("Mixlib::ShellOut", exitstatus: 0, stdout: locales, error!: nil) allow(cmd).to receive(:run_command).and_return(cmd) cmd end let(:locales) { locale_array.join("\n") } before do allow(Mixlib::ShellOut).to receive(:new).with("locale -a").and_return(shell_out) end shared_examples_for "a suitable locale" do it "returns an English UTF-8 locale" do expect(ChefConfig.logger).to_not receive(:warn).with(/Please install an English UTF-8 locale for Chef to use/) expect(ChefConfig.logger).to_not receive(:debug).with(/Defaulting to locale en_US.UTF-8 on Windows/) expect(ChefConfig.logger).to_not receive(:debug).with(/No usable locale -a command found/) expect(ChefConfig::Config.guess_internal_locale).to eq expected_locale end end context "when the result includes 'C.UTF-8'" do include_examples "a suitable locale" do let(:locale_array) { [expected_locale, "en_US.UTF-8"] } let(:expected_locale) { "C.UTF-8" } end end context "when the result includes 'en_US.UTF-8'" do include_examples "a suitable locale" do let(:locale_array) { ["en_CA.UTF-8", expected_locale, "en_NZ.UTF-8"] } let(:expected_locale) { "en_US.UTF-8" } end end context "when the result includes 'en_US.utf8'" do include_examples "a suitable locale" do let(:locale_array) { ["en_CA.utf8", "en_US.utf8", "en_NZ.utf8"] } let(:expected_locale) { "en_US.UTF-8" } end end context "when the result includes 'en.UTF-8'" do include_examples "a suitable locale" do let(:locale_array) { ["en.ISO8859-1", expected_locale] } let(:expected_locale) { "en.UTF-8" } end end context "when the result includes 'en_*.UTF-8'" do include_examples "a suitable locale" do let(:locale_array) { [expected_locale, "en_CA.UTF-8", "en_GB.UTF-8"] } let(:expected_locale) { "en_AU.UTF-8" } end end context "when the result includes 'en_*.utf8'" do include_examples "a suitable locale" do let(:locale_array) { ["en_AU.utf8", "en_CA.utf8", "en_GB.utf8"] } let(:expected_locale) { "en_AU.UTF-8" } end end context "when the result does not include 'en_*.UTF-8'" do let(:locale_array) { ["af_ZA", "af_ZA.ISO8859-1", "af_ZA.ISO8859-15", "af_ZA.UTF-8"] } it "should fall back to C locale" do expect(ChefConfig.logger).to receive(:warn).with("Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support.") expect(ChefConfig::Config.guess_internal_locale).to eq "C" end end context "on error" do let(:locale_array) { [] } let(:shell_out_cmd) { instance_double("Mixlib::ShellOut") } before do allow(Mixlib::ShellOut).to receive(:new).and_return(shell_out_cmd) allow(shell_out_cmd).to receive(:run_command) allow(shell_out_cmd).to receive(:error!).and_raise(Mixlib::ShellOut::ShellCommandFailed, "this is an error") end it "should default to 'en_US.UTF-8'" do if is_windows expect(ChefConfig.logger).to receive(:debug).with("Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else.") else expect(ChefConfig.logger).to receive(:debug).with("No usable locale -a command found, assuming you have en_US.UTF-8 installed.") end expect(ChefConfig::Config.guess_internal_locale).to eq "en_US.UTF-8" end end end end end describe "export_proxies" do before(:all) do @original_env = ENV.to_hash ENV["http_proxy"] = nil ENV["https_proxy"] = nil ENV["ftp_proxy"] = nil ENV["no_proxy"] = nil end after(:all) do ENV.clear ENV.update(@original_env) end let(:http_proxy) { "http://localhost:7979" } let(:https_proxy) { "https://localhost:7979" } let(:ftp_proxy) { "ftp://localhost:7979" } let(:proxy_user) { "http_user" } let(:proxy_pass) { "http_pass" } context "when http_proxy, proxy_pass and proxy_user are set" do before do ChefConfig::Config.http_proxy = http_proxy ChefConfig::Config.http_proxy_user = proxy_user ChefConfig::Config.http_proxy_pass = proxy_pass end it "exports ENV['http_proxy']" do expect(ENV).to receive(:[]=).with("http_proxy", "http://http_user:http_pass@localhost:7979") expect(ENV).to receive(:[]=).with("HTTP_PROXY", "http://http_user:http_pass@localhost:7979") ChefConfig::Config.export_proxies end end context "when https_proxy, proxy_pass and proxy_user are set" do before do ChefConfig::Config.https_proxy = https_proxy ChefConfig::Config.https_proxy_user = proxy_user ChefConfig::Config.https_proxy_pass = proxy_pass end it "exports ENV['https_proxy']" do expect(ENV).to receive(:[]=).with("https_proxy", "https://http_user:http_pass@localhost:7979") expect(ENV).to receive(:[]=).with("HTTPS_PROXY", "https://http_user:http_pass@localhost:7979") ChefConfig::Config.export_proxies end end context "when ftp_proxy, proxy_pass and proxy_user are set" do before do ChefConfig::Config.ftp_proxy = ftp_proxy ChefConfig::Config.ftp_proxy_user = proxy_user ChefConfig::Config.ftp_proxy_pass = proxy_pass end it "exports ENV['ftp_proxy']" do expect(ENV).to receive(:[]=).with("ftp_proxy", "ftp://http_user:http_pass@localhost:7979") expect(ENV).to receive(:[]=).with("FTP_PROXY", "ftp://http_user:http_pass@localhost:7979") ChefConfig::Config.export_proxies end end shared_examples "no user pass" do it "does not populate the user or password" do expect(ENV).to receive(:[]=).with("http_proxy", "http://localhost:7979") expect(ENV).to receive(:[]=).with("HTTP_PROXY", "http://localhost:7979") ChefConfig::Config.export_proxies end end context "when proxy_pass and proxy_user are passed as empty strings" do before do ChefConfig::Config.http_proxy = http_proxy ChefConfig::Config.http_proxy_user = "" ChefConfig::Config.http_proxy_pass = proxy_pass end include_examples "no user pass" end context "when proxy_pass and proxy_user are not provided" do before do ChefConfig::Config.http_proxy = http_proxy end include_examples "no user pass" end context "when the proxy is provided without a scheme" do before do ChefConfig::Config.http_proxy = "localhost:1111" end it "automatically adds the scheme to the proxy url" do expect(ENV).to receive(:[]=).with("http_proxy", "http://localhost:1111") expect(ENV).to receive(:[]=).with("HTTP_PROXY", "http://localhost:1111") ChefConfig::Config.export_proxies end end shared_examples "no export" do it "does not export any proxy settings" do ChefConfig::Config.export_proxies expect(ENV["http_proxy"]).to eq(nil) expect(ENV["https_proxy"]).to eq(nil) expect(ENV["ftp_proxy"]).to eq(nil) expect(ENV["no_proxy"]).to eq(nil) end end context "when nothing is set" do include_examples "no export" end context "when all the users and passwords are set but no proxies are set" do before do ChefConfig::Config.http_proxy_user = proxy_user ChefConfig::Config.http_proxy_pass = proxy_pass ChefConfig::Config.https_proxy_user = proxy_user ChefConfig::Config.https_proxy_pass = proxy_pass ChefConfig::Config.ftp_proxy_user = proxy_user ChefConfig::Config.ftp_proxy_pass = proxy_pass end include_examples "no export" end context "no_proxy is set" do before do ChefConfig::Config.no_proxy = "localhost" end it "exports ENV['no_proxy']" do expect(ENV).to receive(:[]=).with("no_proxy", "localhost") expect(ENV).to receive(:[]=).with("NO_PROXY", "localhost") ChefConfig::Config.export_proxies end end end describe "proxy_uri" do subject(:proxy_uri) { described_class.proxy_uri(scheme, host, port) } let(:env) { {} } let(:scheme) { "http" } let(:host) { "test.example.com" } let(:port) { 8080 } let(:proxy) { "#{proxy_prefix}#{proxy_host}:#{proxy_port}" } let(:proxy_prefix) { "http://" } let(:proxy_host) { "proxy.mycorp.com" } let(:proxy_port) { 8080 } before do stub_const("ENV", env) end shared_examples_for "a proxy uri" do it "contains the host" do expect(proxy_uri.host).to eq(proxy_host) end it "contains the port" do expect(proxy_uri.port).to eq(proxy_port) end end context "when the config setting is normalized (does not contain the scheme)" do include_examples "a proxy uri" do let(:proxy_prefix) { "" } let(:env) do { "#{scheme}_proxy" => proxy, "no_proxy" => nil, } end end end context "when the proxy is set by the environment" do include_examples "a proxy uri" do let(:scheme) { "https" } let(:env) do { "https_proxy" => "https://jane_username:opensesame@proxy.mycorp.com:8080", } end end end context "when an empty proxy is set by the environment" do let(:env) do { "https_proxy" => "", } end it "does not fail with URI parse exception" do expect { proxy_uri }.to_not raise_error end end context "when no_proxy is set" do context "when no_proxy is the exact host" do let(:env) do { "http_proxy" => proxy, "no_proxy" => host, } end it { is_expected.to eq nil } end context "when no_proxy includes the same domain with a wildcard" do let(:env) do { "http_proxy" => proxy, "no_proxy" => "*.example.com", } end it { is_expected.to eq nil } end context "when no_proxy is included on a list" do let(:env) do { "http_proxy" => proxy, "no_proxy" => "chef.io,getchef.com,opscode.com,test.example.com", } end it { is_expected.to eq nil } end context "when no_proxy is included on a list with wildcards" do let(:env) do { "http_proxy" => proxy, "no_proxy" => "10.*,*.example.com", } end it { is_expected.to eq nil } end context "when no_proxy is a domain with a dot prefix" do let(:env) do { "http_proxy" => proxy, "no_proxy" => ".example.com", } end it { is_expected.to eq nil } end context "when no_proxy is a domain with no wildcard" do let(:env) do { "http_proxy" => proxy, "no_proxy" => "example.com", } end it { is_expected.to eq nil } end end end describe "allowing chefdk configuration outside of chefdk" do it "allows arbitrary settings in the chefdk config context" do expect { ChefConfig::Config.chefdk.generator_cookbook("/path") }.to_not raise_error end end describe "Treating deprecation warnings as errors" do context "when using our default RSpec configuration" do it "defaults to treating deprecation warnings as errors" do expect(ChefConfig::Config[:treat_deprecation_warnings_as_errors]).to be(true) end it "sets CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS environment variable" do expect(ENV["CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS"]).to eq("1") end it "treats deprecation warnings as errors in child processes when testing" do # Doing a full integration test where we launch a child process is slow # and liable to break for weird reasons (bundler env stuff, etc.), so # we're just checking that the presence of the environment variable # causes treat_deprecation_warnings_as_errors to be set to true after a # config reset. ChefConfig::Config.reset expect(ChefConfig::Config[:treat_deprecation_warnings_as_errors]).to be(true) end end context "outside of our test environment" do before do ENV.delete("CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS") ChefConfig::Config.reset end it "defaults to NOT treating deprecation warnings as errors" do expect(ChefConfig::Config[:treat_deprecation_warnings_as_errors]).to be(false) end end end end chef-12.14.60/chef-config/spec/unit/fips_spec.rb000066400000000000000000000065411276456504500212570ustar00rootroot00000000000000# # Author:: Matt Wrock () # Copyright:: Copyright (c) 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/fips" require "spec_helper" RSpec.describe "ChefConfig.fips?" do let(:enabled) { "0" } context "on *nix" do let(:fips_path) { "/proc/sys/crypto/fips_enabled" } before(:each) do allow(ChefConfig).to receive(:windows?).and_return(false) allow(::File).to receive(:exist?).with(fips_path).and_return(true) allow(::File).to receive(:read).with(fips_path).and_return(enabled) end context "fips file is present and contains 1" do let(:enabled) { "1" } it "returns true" do expect(ChefConfig.fips?).to be(true) end end context "fips file does not contain 1" do let(:enabled) { "0" } it "returns false" do expect(ChefConfig.fips?).to be(false) end end context "fips file is not present" do before do allow(::File).to receive(:exist?).with(fips_path).and_return(false) end it "returns false" do expect(ChefConfig.fips?).to be(false) end end end context "on windows", :windows_only do let(:fips_key) { 'System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy' } let(:win_reg_entry) { { "Enabled" => enabled } } before(:each) do allow(ChefConfig).to receive(:windows?).and_return(true) allow(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).with(fips_key, arch).and_yield(win_reg_entry) end shared_examples "fips_detection" do context "fips enabled key is set to 1" do let(:enabled) { 1 } it "returns true" do expect(ChefConfig.fips?).to be(true) end end context "fips enabled key is set to 0" do let(:enabled) { 0 } it "returns false" do expect(ChefConfig.fips?).to be(false) end end context "fips key does not exist" do before do allow(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).and_raise(Win32::Registry::Error, 50) end it "returns false" do expect(ChefConfig.fips?).to be(false) end end end context "on 32 bit ruby" do let(:arch) { Win32::Registry::KEY_READ | 0x100 } before { stub_const("::RbConfig::CONFIG", { "target_cpu" => "i386" } ) } it_behaves_like "fips_detection" end context "on 64 bit ruby" do let(:arch) { Win32::Registry::KEY_READ | 0x200 } before { stub_const("::RbConfig::CONFIG", { "target_cpu" => "x86_64" } ) } it_behaves_like "fips_detection" end context "on unknown ruby" do let(:arch) { Win32::Registry::KEY_READ } before { stub_const("::RbConfig::CONFIG", { "target_cpu" => nil } ) } it_behaves_like "fips_detection" end end end chef-12.14.60/chef-config/spec/unit/path_helper_spec.rb000066400000000000000000000257741276456504500226220ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef-config/path_helper" require "spec_helper" RSpec.describe ChefConfig::PathHelper do let(:path_helper) { described_class } shared_examples_for "common_functionality" do describe "join" do it "joins starting with '' resolve to absolute paths" do expect(path_helper.join("", "a", "b")).to eq("#{path_helper.path_separator}a#{path_helper.path_separator}b") end it "joins ending with '' add a / to the end" do expect(path_helper.join("a", "b", "")).to eq("a#{path_helper.path_separator}b#{path_helper.path_separator}") end end describe "dirname" do it "dirname('abc') is '.'" do expect(path_helper.dirname("abc")).to eq(".") end it "dirname('/') is '/'" do expect(path_helper.dirname(path_helper.path_separator)).to eq(path_helper.path_separator) end it "dirname('a/b/c') is 'a/b'" do expect(path_helper.dirname(path_helper.join("a", "b", "c"))).to eq(path_helper.join("a", "b")) end it "dirname('a/b/c/') is 'a/b'" do expect(path_helper.dirname(path_helper.join("a", "b", "c", ""))).to eq(path_helper.join("a", "b")) end it "dirname('/a/b/c') is '/a/b'" do expect(path_helper.dirname(path_helper.join("", "a", "b", "c"))).to eq(path_helper.join("", "a", "b")) end end end context "on windows" do before(:each) do allow(ChefConfig).to receive(:windows?).and_return(true) end include_examples("common_functionality") it "path_separator is \\" do expect(path_helper.path_separator).to eq('\\') end describe "platform-specific #join behavior" do it "joins components on Windows when some end with unix separators" do expect(path_helper.join('C:\\foo/', "bar", "baz")).to eq('C:\\foo\\bar\\baz') end it "joins components when some end with separators" do expected = path_helper.cleanpath("/foo/bar/baz") expected = "C:#{expected}" expect(path_helper.join('C:\\foo\\', "bar", "baz")).to eq(expected) end it "joins components when some end and start with separators" do expected = path_helper.cleanpath("/foo/bar/baz") expected = "C:#{expected}" expect(path_helper.join('C:\\foo\\', "bar/", "/baz")).to eq(expected) end it "joins components that don't end in separators" do expected = path_helper.cleanpath("/foo/bar/baz") expected = "C:#{expected}" expect(path_helper.join('C:\\foo', "bar", "baz")).to eq(expected) end end it "cleanpath changes slashes into backslashes and leaves backslashes alone" do expect(path_helper.cleanpath('/a/b\\c/d/')).to eq('\\a\\b\\c\\d') end it "cleanpath does not remove leading double backslash" do expect(path_helper.cleanpath('\\\\a/b\\c/d/')).to eq('\\\\a\\b\\c\\d') end end context "on unix" do before(:each) do allow(ChefConfig).to receive(:windows?).and_return(false) end include_examples("common_functionality") it "path_separator is /" do expect(path_helper.path_separator).to eq("/") end it "cleanpath removes extra slashes alone" do expect(path_helper.cleanpath("/a///b/c/d/")).to eq("/a/b/c/d") end describe "platform-specific #join behavior" do it "joins components when some end with separators" do expected = path_helper.cleanpath("/foo/bar/baz") expect(path_helper.join("/foo/", "bar", "baz")).to eq(expected) end it "joins components when some end and start with separators" do expected = path_helper.cleanpath("/foo/bar/baz") expect(path_helper.join("/foo/", "bar/", "/baz")).to eq(expected) end it "joins components that don't end in separators" do expected = path_helper.cleanpath("/foo/bar/baz") expect(path_helper.join("/foo", "bar", "baz")).to eq(expected) end end end describe "validate_path" do context "on windows" do before(:each) do # pass by default allow(ChefConfig).to receive(:windows?).and_return(true) allow(path_helper).to receive(:printable?).and_return(true) allow(path_helper).to receive(:windows_max_length_exceeded?).and_return(false) end it "returns the path if the path passes the tests" do expect(path_helper.validate_path("C:\\ThisIsRigged")).to eql("C:\\ThisIsRigged") end it "does not raise an error if everything looks great" do expect { path_helper.validate_path("C:\\cool path\\dude.exe") }.not_to raise_error end it "raises an error if the path has invalid characters" do allow(path_helper).to receive(:printable?).and_return(false) expect { path_helper.validate_path("Newline!\n") }.to raise_error(ChefConfig::InvalidPath) end it "Adds the \\\\?\\ prefix if the path exceeds MAX_LENGTH and does not have it" do long_path = "C:\\" + "a" * 250 + "\\" + "b" * 250 prefixed_long_path = "\\\\?\\" + long_path allow(path_helper).to receive(:windows_max_length_exceeded?).and_return(true) expect(path_helper.validate_path(long_path)).to eql(prefixed_long_path) end end end describe "windows_max_length_exceeded?" do it "returns true if the path is too long (259 + NUL) for the API" do expect(path_helper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 6)).to be_truthy end it "returns false if the path is not too long (259 + NUL) for the standard API" do expect(path_helper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 5)).to be_falsey end it "returns false if the path is over 259 characters but uses the \\\\?\\ prefix" do expect(path_helper.windows_max_length_exceeded?("\\\\?\\C:\\" + "a" * 250 + "\\" + "b" * 250)).to be_falsey end end describe "printable?" do it "returns true if the string contains no non-printable characters" do expect(path_helper.printable?("C:\\Program Files (x86)\\Microsoft Office\\Files.lst")).to be_truthy end it "returns true when given 'abc' in unicode" do expect(path_helper.printable?("\u0061\u0062\u0063")).to be_truthy end it "returns true when given japanese unicode" do expect(path_helper.printable?("\uff86\uff87\uff88")).to be_truthy end it "returns false if the string contains a non-printable character" do expect(path_helper.printable?("\my files\work\notes.txt")).to be_falsey end # This isn't necessarily a requirement, but here to be explicit about functionality. it "returns false if the string contains a newline or tab" do expect(path_helper.printable?("\tThere's no way,\n\t *no* way,\n\t that you came from my loins.\n")).to be_falsey end end describe "canonical_path" do context "on windows", :windows_only do it "returns an absolute path with backslashes instead of slashes" do expect(path_helper.canonical_path("\\\\?\\C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini") end it "adds the \\\\?\\ prefix if it is missing" do expect(path_helper.canonical_path("C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini") end it "returns a lowercase path" do expect(path_helper.canonical_path("\\\\?\\C:\\CASE\\INSENSITIVE")).to eq("\\\\?\\c:\\case\\insensitive") end end context "not on windows", :unix_only do it "returns a canonical path" do expect(path_helper.canonical_path("/etc//apache.d/sites-enabled/../sites-available/default")).to eq("/etc/apache.d/sites-available/default") end end end describe "paths_eql?" do it "returns true if the paths are the same" do allow(path_helper).to receive(:canonical_path).with("bandit").and_return("c:/bandit/bandit") allow(path_helper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit") expect(path_helper.paths_eql?("bandit", "../bandit/bandit")).to be_truthy end it "returns false if the paths are different" do allow(path_helper).to receive(:canonical_path).with("bandit").and_return("c:/Bo/Bandit") allow(path_helper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit") expect(path_helper.paths_eql?("bandit", "../bandit/bandit")).to be_falsey end end describe "escape_glob" do it "escapes characters reserved by glob" do path = "C:\\this\\*path\\[needs]\\escaping?" escaped_path = "C:\\\\this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?" expect(path_helper.escape_glob(path)).to eq(escaped_path) end context "when given more than one argument" do it "joins, cleanpaths, and escapes characters reserved by glob" do args = ["this/*path", "[needs]", "escaping?"] escaped_path = if ChefConfig.windows? "this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?" else "this/\\*path/\\[needs\\]/escaping\\?" end expect(path_helper).to receive(:join).with(*args).and_call_original expect(path_helper).to receive(:cleanpath).and_call_original expect(path_helper.escape_glob(*args)).to eq(escaped_path) end end end describe "escape_glob_dir" do it "escapes characters reserved by glob without using backslashes for path separators" do path = "C:/this/*path/[needs]/escaping?" escaped_path = "C:/this/\\*path/\\[needs\\]/escaping\\?" expect(path_helper.escape_glob_dir(path)).to eq(escaped_path) end context "when given more than one argument" do it "joins, cleanpaths, and escapes characters reserved by glob" do args = ["this/*path", "[needs]", "escaping?"] escaped_path = "this/\\*path/\\[needs\\]/escaping\\?" expect(path_helper).to receive(:join).with(*args).and_call_original expect(path_helper.escape_glob_dir(*args)).to eq(escaped_path) end end end describe "all_homes" do before do stub_const("ENV", env) allow(ChefConfig).to receive(:windows?).and_return(is_windows) end context "on windows" do let (:is_windows) { true } end context "on unix" do let (:is_windows) { false } context "when HOME is not set" do let (:env) { {} } it "returns an empty array" do expect(path_helper.all_homes).to eq([]) end end end end end chef-12.14.60/chef-config/spec/unit/workstation_config_loader_spec.rb000066400000000000000000000272501276456504500255550ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "tempfile" require "chef-config/exceptions" require "chef-config/windows" require "chef-config/workstation_config_loader" RSpec.describe ChefConfig::WorkstationConfigLoader do let(:explicit_config_location) { nil } let(:env) { {} } let(:config_loader) do described_class.new(explicit_config_location).tap do |c| allow(c).to receive(:env).and_return(env) end end before do # We set this to nil so that a dev workstation will # not interfere with the tests. ChefConfig::Config[:config_d_dir] = nil end # Test methods that do I/O or reference external state which are stubbed out # elsewhere. describe "external dependencies" do let(:config_loader) { described_class.new(nil) } it "delegates to ENV for env" do expect(config_loader.env).to equal(ENV) end it "tests a path's existence" do expect(config_loader.path_exists?("/nope/nope/nope/nope/frab/jab/nab")).to be(false) expect(config_loader.path_exists?(__FILE__)).to be(true) end end describe "locating the config file" do context "without an explicit config" do before do allow(config_loader).to receive(:path_exists?).with(an_instance_of(String)).and_return(false) end it "has no config if HOME is not set" do expect(config_loader.config_location).to be(nil) expect(config_loader.no_config_found?).to be(true) end context "when HOME is set and contains a knife.rb" do let(:home) { "/Users/example.user" } before do allow(ChefConfig::PathHelper).to receive(:home).with(".chef").and_yield(File.join(home, ".chef")) allow(config_loader).to receive(:path_exists?).with("#{home}/.chef/knife.rb").and_return(true) end it "uses the config in HOME/.chef/knife.rb" do expect(config_loader.config_location).to eq("#{home}/.chef/knife.rb") end context "and has a config.rb" do before do allow(config_loader).to receive(:path_exists?).with("#{home}/.chef/config.rb").and_return(true) end it "uses the config in HOME/.chef/config.rb" do expect(config_loader.config_location).to eq("#{home}/.chef/config.rb") end context "and/or a parent dir contains a .chef dir" do let(:env_pwd) { "/path/to/cwd" } before do if ChefConfig.windows? env["CD"] = env_pwd else env["PWD"] = env_pwd end allow(config_loader).to receive(:path_exists?).with("#{env_pwd}/.chef/knife.rb").and_return(true) allow(File).to receive(:exist?).with("#{env_pwd}/.chef").and_return(true) allow(File).to receive(:directory?).with("#{env_pwd}/.chef").and_return(true) end it "prefers the config from parent_dir/.chef" do expect(config_loader.config_location).to eq("#{env_pwd}/.chef/knife.rb") end context "and the parent dir's .chef dir has a config.rb" do before do allow(config_loader).to receive(:path_exists?).with("#{env_pwd}/.chef/config.rb").and_return(true) end it "prefers the config from parent_dir/.chef" do expect(config_loader.config_location).to eq("#{env_pwd}/.chef/config.rb") end context "and/or the current working directory contains a .chef dir" do let(:cwd) { Dir.pwd } before do allow(config_loader).to receive(:path_exists?).with("#{cwd}/knife.rb").and_return(true) end it "prefers a knife.rb located in the cwd" do expect(config_loader.config_location).to eq("#{cwd}/knife.rb") end context "and the CWD's .chef dir has a config.rb" do before do allow(config_loader).to receive(:path_exists?).with("#{cwd}/config.rb").and_return(true) end it "prefers a config located in the cwd" do expect(config_loader.config_location).to eq("#{cwd}/config.rb") end context "and/or KNIFE_HOME is set" do let(:knife_home) { "/path/to/knife/home" } before do env["KNIFE_HOME"] = knife_home allow(config_loader).to receive(:path_exists?).with("#{knife_home}/knife.rb").and_return(true) end it "prefers a knife located in KNIFE_HOME" do expect(config_loader.config_location).to eq("/path/to/knife/home/knife.rb") end context "and KNIFE_HOME contains a config.rb" do before do env["KNIFE_HOME"] = knife_home allow(config_loader).to receive(:path_exists?).with("#{knife_home}/config.rb").and_return(true) end it "prefers a config.rb located in KNIFE_HOME" do expect(config_loader.config_location).to eq("/path/to/knife/home/config.rb") end end end end end end end end end context "when the current working dir is inside a symlinked directory" do before do # pwd according to your shell is /home/someuser/prod/chef-repo, but # chef-repo is a symlink to /home/someuser/codes/chef-repo env["CD"] = "/home/someuser/prod/chef-repo" # windows env["PWD"] = "/home/someuser/prod/chef-repo" # unix allow(Dir).to receive(:pwd).and_return("/home/someuser/codes/chef-repo") end it "loads the config from the non-dereferenced directory path" do expect(File).to receive(:exist?).with("/home/someuser/prod/chef-repo/.chef").and_return(false) expect(File).to receive(:exist?).with("/home/someuser/prod/.chef").and_return(true) expect(File).to receive(:directory?).with("/home/someuser/prod/.chef").and_return(true) expect(config_loader).to receive(:path_exists?).with("/home/someuser/prod/.chef/knife.rb").and_return(true) expect(config_loader.config_location).to eq("/home/someuser/prod/.chef/knife.rb") end end end context "when given an explicit config to load" do let(:explicit_config_location) { "/path/to/explicit/config.rb" } it "prefers the explicit config" do expect(config_loader.config_location).to eq(explicit_config_location) end end end describe "loading the config file" do context "when no explicit config is specifed and no implicit config is found" do before do allow(config_loader).to receive(:path_exists?).with(an_instance_of(String)).and_return(false) end it "skips loading" do expect(config_loader.config_location).to be(nil) expect(config_loader).not_to receive(:apply_config) config_loader.load end end context "when an explicit config is given but it doesn't exist" do let(:explicit_config_location) { "/nope/nope/nope/frab/jab/nab" } it "raises a configuration error" do expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError) end end context "when the config file exists" do let(:config_content) { "" } # We need to keep a reference to the tempfile because while #close does # not unlink the file, the object being GC'd will. let(:tempfile) do Tempfile.new("Chef-WorkstationConfigLoader-rspec-test").tap do |t| t.print(config_content) t.close end end let(:explicit_config_location) do tempfile.path end after { File.unlink(explicit_config_location) if File.exist?(explicit_config_location) } context "and is valid" do let(:config_content) { "config_file_evaluated(true)" } it "loads the config" do expect(config_loader).to receive(:apply_config).and_call_original config_loader.load expect(ChefConfig::Config.config_file_evaluated).to be(true) end it "sets ChefConfig::Config.config_file" do config_loader.load expect(ChefConfig::Config.config_file).to eq(explicit_config_location) end end context "and has a syntax error" do let(:config_content) { "{{{{{:{{" } it "raises a ConfigurationError" do expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError) end end context "and raises a ruby exception during evaluation" do let(:config_content) { ":foo\n:bar\nraise 'oops'\n:baz\n" } it "raises a ConfigurationError" do expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError) end end end end describe "when loading config.d" do context "when the conf.d directory exists" do let(:config_content) { "" } let(:tempdir) { Dir.mktmpdir("chef-workstation-test") } let!(:confd_file) do Tempfile.new(["Chef-WorkstationConfigLoader-rspec-test", ".rb"], tempdir).tap do |t| t.print(config_content) t.close end end before do ChefConfig::Config[:config_d_dir] = tempdir allow(config_loader).to receive(:path_exists?).with( an_instance_of(String)).and_return(false) end after do FileUtils.remove_entry_secure tempdir end context "and is valid" do let(:config_content) { "config_d_file_evaluated(true)" } it "loads the config" do expect(config_loader).to receive(:apply_config).and_call_original config_loader.load expect(ChefConfig::Config.config_d_file_evaluated).to be(true) end end context "and has a syntax error" do let(:config_content) { "{{{{{:{{" } it "raises a ConfigurationError" do expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError) end end context "has a non rb file" do let(:sytax_error_content) { "{{{{{:{{" } let(:config_content) { "config_d_file_evaluated(true)" } let!(:not_confd_file) do Tempfile.new(["Chef-WorkstationConfigLoader-rspec-test", ".foorb"], tempdir).tap do |t| t.print(sytax_error_content) t.close end end it "does not load the non rb file" do expect { config_loader.load }.not_to raise_error expect(ChefConfig::Config.config_d_file_evaluated).to be(true) end end end context "when the conf.d directory does not exist" do before do ChefConfig::Config[:config_d_dir] = "/nope/nope/nope/nope/notdoingit" end it "does not load anything" do expect(config_loader).not_to receive(:apply_config) end end end end chef-12.14.60/chef.gemspec000066400000000000000000000045341276456504500151500ustar00rootroot00000000000000$:.unshift(File.dirname(__FILE__) + "/lib") require "chef/version" Gem::Specification.new do |s| s.name = "chef" s.version = Chef::VERSION s.platform = Gem::Platform::RUBY s.extra_rdoc_files = ["README.md", "CONTRIBUTING.md", "LICENSE" ] s.summary = "A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure." s.description = s.summary s.license = "Apache-2.0" s.author = "Adam Jacob" s.email = "adam@chef.io" s.homepage = "http://www.chef.io" s.required_ruby_version = ">= 2.2.0" s.add_dependency "chef-config", "= #{Chef::VERSION}" s.add_dependency "mixlib-cli", "~> 1.7" s.add_dependency "mixlib-log", "~> 1.3" s.add_dependency "mixlib-authentication", "~> 1.4" s.add_dependency "mixlib-shellout", "~> 2.0" s.add_dependency "mixlib-archive", ">= 0.2.0" s.add_dependency "ohai", ">= 8.6.0.alpha.1", "< 9" s.add_dependency "ffi-yajl", "~> 2.2" s.add_dependency "net-ssh", ">= 2.9", "< 4.0" s.add_dependency "net-ssh-multi", "~> 1.1" s.add_dependency "net-sftp", "~> 2.1", ">= 2.1.2" s.add_dependency "highline", "~> 1.6", ">= 1.6.9" s.add_dependency "erubis", "~> 2.7" s.add_dependency "diff-lcs", "~> 1.2", ">= 1.2.4" s.add_dependency "chef-zero", ">= 4.8" s.add_dependency "plist", "~> 3.2" s.add_dependency "iniparse", "~> 1.4" s.add_dependency "addressable" # Audit mode requires these, so they are non-developmental dependencies now %w{rspec-core rspec-expectations rspec-mocks}.each { |gem| s.add_dependency gem, "~> 3.5" } s.add_dependency "rspec_junit_formatter", "~> 0.2.0" s.add_dependency "serverspec", "~> 2.7" s.add_dependency "specinfra", "~> 2.10" s.add_dependency "syslog-logger", "~> 1.6" s.add_dependency "uuidtools", "~> 2.1.5" s.add_dependency "proxifier", "~> 1.0" # v1.10 is needed as a runtime dep now for 'bundler/inline' # very deliberately avoiding putting a ceiling on this to avoid depsolver conflicts. s.add_dependency "bundler", ">= 1.10" s.bindir = "bin" s.executables = %w{ chef-client chef-solo knife chef-shell chef-apply } s.require_paths = %w{ lib lib-backcompat } s.files = %w{Gemfile Rakefile LICENSE README.md CONTRIBUTING.md VERSION} + Dir.glob("{distro,lib,lib-backcompat,tasks,acceptance,spec}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) } + Dir.glob("*.gemspec") end chef-12.14.60/ci/000077500000000000000000000000001276456504500132635ustar00rootroot00000000000000chef-12.14.60/ci/bundle_install.sh000077500000000000000000000005421276456504500166220ustar00rootroot00000000000000#!/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 chef-12.14.60/ci/dependency_update.sh000077500000000000000000000001501276456504500172760ustar00rootroot00000000000000#!/bin/sh set -evx . ci/bundle_install.sh bundle exec rake dependencies git checkout .bundle/config chef-12.14.60/ci/verify-chef.bat000077500000000000000000000033451276456504500161720ustar00rootroot00000000000000 @ECHO OFF REM ; %PROJECT_NAME% is set by Jenkins, this allows us to use the same script to verify REM ; Chef and Angry Chef cd C:\opscode\%PROJECT_NAME%\bin REM ; We don't want to add the embedded bin dir to the main PATH as this REM ; could mask issues in our binstub shebangs. SET EMBEDDED_BIN_DIR=C:\opscode\%PROJECT_NAME%\embedded\bin ECHO. REM ; Set the temporary directory to a custom location, and wipe it before REM ; and after the tests run. SET TEMP=%TEMP%\cheftest SET TMP=%TMP%\cheftest RMDIR /S /Q %TEMP% MKDIR %TEMP% FOR %%b IN ( chef-client knife chef-solo ohai ) DO ( ECHO Checking for existence of binfile `%%b`... IF EXIST %%b ( ECHO ...FOUND IT! ) ELSE ( GOTO :error ) ECHO. ) call chef-client --version REM ; Exercise various packaged tools to validate binstub shebangs call %EMBEDDED_BIN_DIR%\ruby --version call %EMBEDDED_BIN_DIR%\gem --version call %EMBEDDED_BIN_DIR%\bundle --version call %EMBEDDED_BIN_DIR%\rspec --version SET PATH=C:\opscode\%PROJECT_NAME%\bin;C:\opscode\%PROJECT_NAME%\embedded\bin;%PATH% REM ; Test against the vendored chef gem (cd into the output of "bundle show chef") for /f "delims=" %%a in ('bundle show chef') do cd %%a IF NOT EXIST "Gemfile.lock" ( ECHO "Chef gem does not contain a Gemfile.lock! This is needed to run any tests." GOTO :error ) IF "%PIPELINE_NAME%" == "chef-fips" ( set CHEF_FIPS=1 ) REM ; ffi-yajl must run in c-extension mode for perf, so force it so we don't accidentally fall back to ffi set FORCE_FFI_YAJL=ext set BUNDLE_GEMFILE=C:\opscode\%PROJECT_NAME%\Gemfile set BUNDLE_IGNORE_CONFIG=true set BUNDLE_FROZEN=1 call bundle exec rspec -r rspec_junit_formatter -f RspecJunitFormatter -o %WORKSPACE%\test.xml -f documentation spec/functional chef-12.14.60/ci/verify-chef.sh000077500000000000000000000105551276456504500160370ustar00rootroot00000000000000#!/bin/sh set -evx # Set up a custom tmpdir, and clean it up before and after the tests TMPDIR="${TMPDIR:-/tmp}/cheftest" export TMPDIR rm -rf $TMPDIR mkdir -p $TMPDIR # $PROJECT_NAME is set by Jenkins, this allows us to use the same script to verify # Chef and Angry Chef PATH=/opt/$PROJECT_NAME/bin:$PATH export PATH BIN_DIR=/opt/$PROJECT_NAME/bin export BIN_DIR # We don't want to add the embedded bin dir to the main PATH as this # could mask issues in our binstub shebangs. EMBEDDED_BIN_DIR=/opt/$PROJECT_NAME/embedded/bin export EMBEDDED_BIN_DIR # If we are on Mac our symlinks are located under /usr/local/bin # otherwise they are under /usr/bin if [ -f /usr/bin/sw_vers ]; then USR_BIN_DIR="/usr/local/bin" else USR_BIN_DIR="/usr/bin" fi export USR_BIN_DIR # sanity check that we're getting the correct symlinks from the pre-install script # solaris doesn't have readlink or test -e. ls -n is different on BSD. proceed with caution. if [ ! -L $USR_BIN_DIR/chef-client ] || [ `ls -l $USR_BIN_DIR/chef-client | awk '{print$NF}'` != "$BIN_DIR/chef-client" ]; then echo "$USR_BIN_DIR/chef-client symlink to $BIN_DIR/chef-client was not correctly created by the pre-install script!" exit 1 fi if [ ! -L $USR_BIN_DIR/knife ] || [ `ls -l $USR_BIN_DIR/knife | awk '{print$NF}'` != "$BIN_DIR/knife" ]; then echo "$USR_BIN_DIR/knife symlink to $BIN_DIR/knife was not correctly created by the pre-install script!" exit 1 fi if [ ! -L $USR_BIN_DIR/chef-solo ] || [ `ls -l $USR_BIN_DIR/chef-solo | awk '{print$NF}'` != "$BIN_DIR/chef-solo" ]; then echo "$USR_BIN_DIR/chef-solo symlink to $BIN_DIR/chef-solo was not correctly created by the pre-install script!" exit 1 fi if [ ! -L $USR_BIN_DIR/ohai ] || [ `ls -l $USR_BIN_DIR/ohai | awk '{print$NF}'` != "$BIN_DIR/ohai" ]; then echo "$USR_BIN_DIR/ohai symlink to $BIN_DIR/ohai was not correctly created by the pre-install script!" exit 1 fi # Ensure the calling environment (disapproval look Bundler) does not # infect our Ruby environment created by the `chef-client` cli. for ruby_env_var in _ORIGINAL_GEM_PATH \ BUNDLE_BIN_PATH \ BUNDLE_GEMFILE \ GEM_HOME \ GEM_PATH \ GEM_ROOT \ RUBYLIB \ RUBYOPT \ RUBY_ENGINE \ RUBY_ROOT \ RUBY_VERSION do unset $ruby_env_var done chef-client --version # Exercise various packaged tools to validate binstub shebangs $EMBEDDED_BIN_DIR/ruby --version $EMBEDDED_BIN_DIR/gem --version $EMBEDDED_BIN_DIR/bundle --version $EMBEDDED_BIN_DIR/rspec --version # ffi-yajl must run in c-extension mode or we take perf hits, so we force it # before running rspec so that we don't wind up testing the ffi mode FORCE_FFI_YAJL=ext export FORCE_FFI_YAJL # ACCEPTANCE environment variable will be set on acceptance testers. # If is it set; we run the acceptance tests, otherwise run rspec tests. if [ "x$ACCEPTANCE" != "x" ]; then # Find the Chef gem and cd there. OLD_PATH=$PATH PATH=/opt/$PROJECT_NAME/bin:/opt/$PROJECT_NAME/embedded/bin:$PATH cd /opt/$PROJECT_NAME CHEF_GEM=`bundle show chef` PATH=$OLD_PATH # On acceptance testers we have Chef DK. We will use its Ruby environment # to cut down the gem installation time. PATH=/opt/chefdk/bin:/opt/chefdk/embedded/bin:$PATH export PATH # copy acceptance suites into workspace SUITE_PATH=$WORKSPACE/acceptance mkdir -p $SUITE_PATH cp -R $CHEF_GEM/acceptance/. $SUITE_PATH sudo chown -R $USER:$USER $SUITE_PATH cd $SUITE_PATH env PATH=$PATH AWS_SSH_KEY_ID=$AWS_SSH_KEY_ID bundle install --deployment env PATH=$PATH AWS_SSH_KEY_ID=$AWS_SSH_KEY_ID KITCHEN_DRIVER=ec2 KITCHEN_CHEF_CHANNEL=unstable bundle exec chef-acceptance test --force-destroy --data-path $WORKSPACE/chef-acceptance-data else PATH=/opt/$PROJECT_NAME/bin:/opt/$PROJECT_NAME/embedded/bin:$PATH export PATH # Test against the installed Chef gem cd /opt/$PROJECT_NAME CHEF_GEM=`bundle show chef` cd $CHEF_GEM if [ ! -f "Gemfile.lock" ]; then echo "Chef gem does not contain a Gemfile.lock! This is needed to run any tests." exit 1 fi sudo env BUNDLE_GEMFILE=/opt/$PROJECT_NAME/Gemfile BUNDLE_IGNORE_CONFIG=true BUNDLE_FROZEN=1 PATH=$PATH TERM=xterm bundle exec rspec -r rspec_junit_formatter -f RspecJunitFormatter -o $WORKSPACE/test.xml -f documentation spec/functional fi chef-12.14.60/ci/version_bump.sh000077500000000000000000000001501276456504500163260ustar00rootroot00000000000000#!/bin/sh set -evx . ci/bundle_install.sh bundle exec rake version:bump git checkout .bundle/config chef-12.14.60/ci/version_show.sh000077500000000000000000000000271276456504500163460ustar00rootroot00000000000000#!/bin/sh cat VERSION chef-12.14.60/distro/000077500000000000000000000000001276456504500141745ustar00rootroot00000000000000chef-12.14.60/distro/common/000077500000000000000000000000001276456504500154645ustar00rootroot00000000000000chef-12.14.60/distro/common/man/000077500000000000000000000000001276456504500162375ustar00rootroot00000000000000chef-12.14.60/distro/common/man/man1/000077500000000000000000000000001276456504500170735ustar00rootroot00000000000000chef-12.14.60/distro/common/man/man1/README.md000066400000000000000000000040551276456504500203560ustar00rootroot00000000000000# Man pages for Knife The source of the Chef Documentation is located at http://docs.opscode.com/. This README documents how the man pages for all of the Knife subcommands that are built into the chef-client are managed. ## Source Files The source files are located in the chef-docs repository: https://github.com/opscode/chef-docs Each Knife subcommand has its own source folder. The folder naming pattern begins with man_. Each man page is a single file called index.html. In the conf.py file, the following settings are unique to each man page: `today` setting is used to define the Chef version. This is because we don't want an arbitrary date populated in the file, yet we still need a version number. For example: `today = 'Chef 11.8`. `project` setting is set to be the same as the name of the subcommand. For example: `project = u'knife-foo'`. `Options for man page output` settings are set to be similar across all man pages, but each one needs to be tailored specifically for the name of the man page. All of the other settings in the General Configuration section should be left alone. These exist to ensure that all of the doc builds are sharing the right common elements and have the same overall presentation. ## Building Docs The docs are built using Sphinx and must be set to the `-b man` output. Currently, the man pages are built locally and then added to the Chef builds in chef-master. ## Editing These files should never be edited. All of the content is pulled in from elsewhere in the chef-docs repo at build time. If changes need to be made, those changes are done elsewhere and then the man pages must be rebuilt. This is to help ensure that all of the changes are made across all of the locations in which these documents need to live. For example, by design, every Knife subcommand with a man page also has an HTML doc at docs.opscode.com/knife_foo.html. ## License [Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/) ## Questions? Open an [Issue](https://github.com/opscode/chef-docs/issues) and ask. chef-12.14.60/distro/common/man/man1/chef-shell.1000066400000000000000000000116421276456504500211730ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "CHEF-SHELL" "1" "Chef 12.0" "" "chef-shell" .SH NAME chef-shell \- The man page for the chef-shell command line tool. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp chef\-shell is a recipe debugging tool that allows the use of breakpoints within recipes. chef\-shell runs as an Interactive Ruby (IRb) session. chef\-shell supports both recipe and attribute file syntax, as well as interactive debugging features. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 chef\-shell is the new name for Shef as of Chef 11.x\&. chef\-shell is backwards compatible and aside from the name change, has the same set of functionality as with previous releases. .UNINDENT .UNINDENT .sp The chef\-shell executable is run as a command\-line tool. .SH MODES .sp chef\-shell is tool that allows knife to be run using an Interactive Ruby (IRb) session. chef\-shell currently supports recipe and attribute file syntax, as well as interactive debugging features. chef\-shell has three run modes: .TS center; |l|l|. _ T{ Mode T} T{ Description T} _ T{ Standalone T} T{ No cookbooks are loaded, and the run list is empty. This mode is the default. T} _ T{ Solo T} T{ chef\-shell acts as a chef\-solo client. It attempts to load the chef\-solo configuration file and JSON attributes. If the JSON attributes set a run list, it will be honored. Cookbooks will be loaded in the same way that chef\-solo loads them. chef\-solo mode is activated with the \fB\-s\fP or \fB\-\-solo\fP command line option, and JSON attributes are specified in the same way as for chef\-solo, with \fB\-j /path/to/chef\-solo.json\fP\&. T} _ T{ Client T} T{ chef\-shell acts as a chef\-client\&. During startup, it reads the chef\-client configuration file and contacts the Chef server to get attributes and cookbooks. The run list will be set in the same way as normal chef\-client runs. chef\-client mode is activated with the \fB\-z\fP or \fB\-\-client\fP options. You can also specify the configuration file with \fB\-c CONFIG\fP and the server URL with \fB\-S SERVER_URL\fP\&. T} _ .TE .SH OPTIONS .sp This command has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C chef\-shell OPTION VALUE OPTION VALUE ... .ft P .fi .UNINDENT .UNINDENT .sp This command has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-standalone\fP Use to run chef\-shell in standalone mode. .TP .B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP The configuration file to use. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-j PATH\fP, \fB\-\-json\-attributes PATH\fP The path to a file that contains JSON data. .sp Use this option to define a \fBrun_list\fP object. For example, a JSON file similar to: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C "run_list": [ "recipe[base]", "recipe[foo]", "recipe[bar]", "role[webserver]" ], .ft P .fi .UNINDENT .UNINDENT .sp may be used by running \fBchef\-client \-j path/to/file.json\fP\&. .sp In certain situations this option may be used to update \fBnormal\fP attributes. .sp \fBWARNING:\fP .INDENT 7.0 .INDENT 3.5 Any other attribute type that is contained in this JSON file will be treated as a \fBnormal\fP attribute. For example, attempting to update \fBoverride\fP attributes using the \fB\-j\fP option: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "name": "dev\-99", "description": "Install some stuff", "override_attributes": { "apptastic": { "enable_apptastic": "false", "apptastic_tier_name": "dev\-99.bomb.com" } } } .ft P .fi .UNINDENT .UNINDENT .sp will result in a node object similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "name": "maybe\-dev\-99", "normal": { "name": "dev\-99", "description": "Install some stuff", "override_attributes": { "apptastic": { "enable_apptastic": "false", "apptastic_tier_name": "dev\-99.bomb.com" } } } } .ft P .fi .UNINDENT .UNINDENT .UNINDENT .UNINDENT .TP .B \fB\-l LEVEL\fP, \fB\-\-log\-level LEVEL\fP The level of logging that will be stored in a log file. .TP .B \fB\-s\fP, \fB\-\-solo\fP Use to run chef\-shell in chef\-solo mode. .TP .B \fB\-S CHEF_SERVER_URL\fP, \fB\-\-server CHEF_SERVER_URL\fP The URL for the Chef server\&. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-z\fP, \fB\-\-client\fP Use to run chef\-shell in chef\-client mode. .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-bootstrap.1000066400000000000000000000175621276456504500222770ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-BOOTSTRAP" "1" "Chef 12.0" "" "knife bootstrap" .SH NAME knife-bootstrap \- The man page for the knife bootstrap subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp A bootstrap is a process that installs the chef\-client on a target system so that it can run as a chef\-client and communicate with a Chef server\&. .sp The \fBknife bootstrap\fP subcommand is used to run a bootstrap operation that installs the chef\-client on the target system. The bootstrap operation must specify the IP address or FQDN of the target system. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife bootstrap FQDN_or_IP_ADDRESS (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-A\fP, \fB\-\-forward\-agent\fP Use to enable SSH agent forwarding. .TP .B \fB\-\-bootstrap\-curl\-options OPTIONS\fP Use to specify arbitrary options to be added to the bootstrap command when using cURL\&. This option may not be used in the same command with \fB\-\-bootstrap\-install\-command\fP\&. .TP .B \fB\-\-bootstrap\-install\-command COMMAND\fP Use to execute a custom installation command sequence for the chef\-client\&. This option may not be used in the same command with \fB\-\-bootstrap\-curl\-options\fP, \fB\-\-bootstrap\-install\-sh\fP, or \fB\-\-bootstrap\-wget\-options\fP\&. .TP .B \fB\-\-bootstrap\-install\-sh URL\fP Use to fetch and execute an installation script at the specified URL. This option may not be used in the same command with \fB\-\-bootstrap\-install\-command\fP\&. .TP .B \fB\-\-bootstrap\-no\-proxy NO_PROXY_URL_or_IP\fP A URL or IP address that specifies a location that should not be proxied. .sp \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 This option is used internally by Chef to help verify bootstrap operations during testing and should never be used during an actual bootstrap operation. .UNINDENT .UNINDENT .TP .B \fB\-\-bootstrap\-proxy PROXY_URL\fP The proxy server for the node that is the target of a bootstrap operation. .TP .B \fB\-\-bootstrap\-version VERSION\fP The version of the chef\-client to install. .TP .B \fB\-\-bootstrap\-wget\-options OPTIONS\fP Use to specify arbitrary options to be added to the bootstrap command when using GNU Wget\&. This option may not be used in the same command with \fB\-\-bootstrap\-install\-command\fP\&. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-G GATEWAY\fP, \fB\-\-ssh\-gateway GATEWAY\fP The SSH tunnel or gateway that is used to run a bootstrap action on a machine that is not accessible from the workstation. .TP .B \fB\-\-hint HINT_NAME[=HINT_FILE]\fP Use to specify an Ohai hint to be set on the target node. .sp Ohai hints are used to tell Ohai something about the system that it is running on that it would not be able to discover itself. An Ohai hint exists if a JSON file exists in the hint directory with the same name as the hint. For example, calling \fBhint?(\(aqantartica\(aq)\fP in an Ohai plugin would return an empty hash if the file \fBantartica.json\fP existed in the hints directory, and return nil if the file does not exist. .sp If the hint file contains JSON content, it will be returned as a hash from the call to \fBhint?\fP\&. .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C { "snow": true, "penguins": "many" } .ft P .fi .UNINDENT .UNINDENT .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C arctic_hint = hint?(\(aqantartica\(aq) if arctic_hint[\(aqsnow\(aq] "There are #{arctic_hint[\(aqpenguins\(aq]} penguins here." else "There is no snow here, and penguins like snow." end .ft P .fi .UNINDENT .UNINDENT .sp The default directory in which hint files are located is \fB/etc/chef/ohai/hints/\fP\&. Use the \fBOhai::Config[:hints_path]\fP setting in the client.rb file to customize this location. .sp \fBHINT_FILE\fP is the name of the JSON file. \fBHINT_NAME\fP is the name of a hint in a JSON file. Use multiple \fB\-\-hint\fP options to specify multiple hints. .TP .B \fB\-i IDENTITY_FILE\fP, \fB\-\-identity\-file IDENTITY_FILE\fP The SSH identity file used for authentication. Key\-based authentication is recommended. .TP .B \fB\-j JSON_ATTRIBS\fP, \fB\-\-json\-attributes JSON_ATTRIBS\fP A JSON string that is added to the first run of a chef\-client\&. .TP .B \fB\-N NAME\fP, \fB\-\-node\-name NAME\fP The name of the node. .TP .B \fB\-\-[no\-]host\-key\-verify\fP Use \fB\-\-no\-host\-key\-verify\fP to disable host key verification. Default setting: \fB\-\-host\-key\-verify\fP\&. .TP .B \fB\-\-[no\-]node\-verify\-api\-cert\fP Use \fBverify_api_cert\fP to only do SSL validation of the Chef server connection; may be needed if the chef\-client needs to talk to other services that have broken SSL certificates. If this option is not specified, the setting for \fBverify_api_cert\fP in the configuration file is applied. .TP .B \fB\-\-node\-ssl\-verify\-mode PEER_OR_NONE\fP The verify mode for HTTPS requests. .sp Use \fB:verify_none\fP to do no validation of SSL certificates. .sp Use \fB:verify_peer\fP to do validation of all SSL certificates, including the Chef server connections, S3 connections, and any HTTPS \fBremote_file\fP resource URLs used in the chef\-client run. This is the recommended setting. .sp If this option is not specified, the setting for \fBssl_verify_mode\fP in the configuration file is applied. .TP .B \fB\-p PORT\fP, \fB\-\-ssh\-port PORT\fP The SSH port. .TP .B \fB\-P PASSWORD\fP, \fB\-\-ssh\-password PASSWORD\fP The SSH password. This can be used to pass the password directly on the command line. If this option is not specified (and a password is required) knife will prompt for the password. .TP .B \fB\-\-prerelease\fP Use to install pre\-release gems. .TP .B \fB\-r RUN_LIST\fP, \fB\-\-run\-list RUN_LIST\fP A comma\-separated list of roles and/or recipes to be applied. .TP .B \fB\-\-secret SECRET\fP The encryption key that is used for values contained within a data bag item. .TP .B \fB\-\-secret\-file FILE\fP The path to the file that contains the encryption key. .TP .B \fB\-\-sudo\fP Use to execute a bootstrap operation with sudo\&. .TP .B \fB\-t TEMPLATE\fP, \fB\-\-bootstrap\-template TEMPLATE\fP Use to specify the bootstrap template to use. This may specify the name of a bootstrap template\-\-\-\fBchef\-full\fP, for example\-\-\-or it may specify the full path to an Embedded Ruby (ERB) template that defines a custom bootstrap. Default value: \fBchef\-full\fP, which installs the chef\-client using the omnibus installer on all supported platforms. .TP .B \fB\-\-use\-sudo\-password\fP Use to perform a bootstrap operation with sudo; specify the password with the \fB\-P\fP (or \fB\-\-ssh\-password\fP) option. .TP .B \fB\-V \-V\fP Use to run the initial chef\-client run at the \fBdebug\fP log\-level (e.g. \fBchef\-client \-l debug\fP). .TP .B \fB\-x USERNAME\fP, \fB\-\-ssh\-user USERNAME\fP The SSH user name. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife bootstrap 192.168.1.1 \-x username \-P PASSWORD \-\-sudo .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife bootstrap 192.168.1.1 \-x username \-i ~/.ssh/id_rsa \-\-sudo .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-client.1000066400000000000000000000260121276456504500215260ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-CLIENT" "1" "Chef 12.0" "" "knife client" .SH NAME knife-client \- The man page for the knife client subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp Every request made by the chef\-client to the Chef server must be an authenticated request using the Chef server API and a private key. When the chef\-client makes a request to the Chef server, the chef\-client authenticates each request using a private key located in \fB/etc/chef/client.pem\fP\&. .sp However, during the first chef\-client run, this private key does not exist. Instead, the chef\-client will attempt to use the private key assigned to the chef\-validator, located in \fB/etc/chef/validation.pem\fP\&. (If, for any reason, the chef\-validator is unable to make an authenticated request to the Chef server, the initial chef\-client run will fail.) .sp During the initial chef\-client run, the chef\-client will register with the Chef server using the private key assigned to the chef\-validator, after which the chef\-client will obtain a \fBclient.pem\fP private key for all future authentication requests to the Chef server\&. .sp After the initial chef\-client run has completed successfully, the chef\-validator is no longer required and may be deleted from the node. Use the \fBdelete_validation\fP recipe found in the \fBchef\-client\fP cookbook (\fI\%https://github.com/opscode\-cookbooks/chef\-client\fP) to remove the chef\-validator\&. .sp The \fBknife client\fP subcommand is used to manage an API client list and their associated RSA public key\-pairs. This allows authentication requests to be made to the Chef server by any entity that uses the Chef server API, such as the chef\-client and knife\&. .SH COMMON OPTIONS .sp The following options may be used with any of the arguments available to the \fBknife client\fP subcommand: .INDENT 0.0 .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH BULK DELETE .sp The \fBbulk delete\fP argument is used to delete any API client that matches a pattern defined by a regular expression. The regular expression must be within quotes and not be surrounded by forward slashes (\fB/\fP). .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client bulk delete REGEX .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .SH CREATE .sp The \fBcreate\fP argument is used to create a new API client\&. This process will generate an RSA key pair for the named API client\&. The public key will be stored on the Chef server and the private key will be displayed on \fBSTDOUT\fP or written to a named file. .INDENT 0.0 .IP \(bu 2 For the chef\-client, the private key should be copied to the system as \fB/etc/chef/client.pem\fP\&. .IP \(bu 2 For knife, the private key is typically copied to \fB~/.chef/client_name.pem\fP and referenced in the knife.rb configuration file. .UNINDENT .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client create CLIENT_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-admin\fP Use to create a client as an admin client. This is required for any user to access Open Source Chef as an administrator. This option only works when used with the open source Chef server and will have no effect when used with Enterprise Chef\&. .TP .B \fB\-f FILE\fP, \fB\-\-file FILE\fP Use to save a private key to the specified file name. .TP .B \fB\-\-validator\fP Use to create the client as the chef\-validator\&. Default value: \fBtrue\fP\&. .UNINDENT .sp \fBExamples\fP .sp To create a chef\-client that can access the Chef server API as an administrator\-\-\-sometimes referred to as an "API chef\-client"\-\-\-with the name "exampleorg" and save its private key to a file, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client create exampleorg \-a \-f "/etc/chef/client.pem" .ft P .fi .UNINDENT .UNINDENT .sp When running the \fBcreate\fP argument on Enterprise Chef, be sure to omit the \fB\-a\fP option: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client create exampleorg \-f "/etc/chef/client.pem" .ft P .fi .UNINDENT .UNINDENT .SH DELETE .sp The \fBdelete\fP argument is used to delete a registered API client\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client delete CLIENT_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To delete a client with the name "client_foo", enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client delete client_foo .ft P .fi .UNINDENT .UNINDENT .sp Type \fBY\fP to confirm a deletion. .SH EDIT .sp The \fBedit\fP argument is used to edit the details of a registered API client\&. When this argument is run, knife will open $EDITOR to enable editing of the \fBadmin\fP attribute. (None of the other attributes should be changed using this argument.) When finished, knife will update the Chef server with those changes. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client edit CLIENT_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To edit a client with the name "exampleorg", enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client edit exampleorg .ft P .fi .UNINDENT .UNINDENT .SH LIST .sp The \fBlist\fP argument is used to view a list of registered API client\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client list (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-w\fP, \fB\-\-with\-uri\fP Use to show the corresponding URIs. .UNINDENT .sp \fBExamples\fP .sp To verify the API client list for the Chef server, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client list .ft P .fi .UNINDENT .UNINDENT .sp to return something similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C exampleorg i\-12345678 rs\-123456 .ft P .fi .UNINDENT .UNINDENT .sp To verify that an API client can authenticate to the Chef server correctly, try getting a list of clients using \fB\-u\fP and \fB\-k\fP options to specify its name and private key: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client list \-u ORGNAME \-k .chef/ORGNAME.pem .ft P .fi .UNINDENT .UNINDENT .SH REREGISTER .sp The \fBreregister\fP argument is used to regenerate an RSA key pair for an API client\&. The public key will be stored on the Chef server and the private key will be displayed on \fBSTDOUT\fP or written to a named file. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 Running this argument will invalidate the previous RSA key pair, making it unusable during authentication to the Chef server\&. .UNINDENT .UNINDENT .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client reregister CLIENT_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP Use to save a private key to the specified file name. .UNINDENT .sp \fBExamples\fP .sp To regenerate the RSA key pair for a client named "testclient" and save it to a file named "rsa_key", enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client regenerate testclient \-f rsa_key .ft P .fi .UNINDENT .UNINDENT .SH SHOW .sp The \fBshow\fP argument is used to show the details of an API client\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client show CLIENT_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP The attribute (or attributes) to show. .UNINDENT .sp \fBExamples\fP .sp To view a client named "testclient", enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife client show testclient .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C admin: false chef_type: client json_class: Chef::ApiClient name: testclient public_key: .ft P .fi .UNINDENT .UNINDENT .sp To view information in JSON format, use the \fB\-F\fP common option as part of the command like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role show devops \-F json .ft P .fi .UNINDENT .UNINDENT .sp Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP\&. .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-configure.1000066400000000000000000000103061276456504500222300ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-CONFIGURE" "1" "Chef 12.0" "" "knife configure" .SH NAME knife-configure \- The man page for the knife configure subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife configure\fP subcommand is used to create the knife.rb and client.rb files so that they can be distributed to workstations and nodes. .sp \fBSyntax\fP .sp This subcommand has the following syntax when creating a knife.rb file: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife configure (options) .ft P .fi .UNINDENT .UNINDENT .sp and the following syntax when creating a client.rb file: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife configure client DIRECTORY .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-\-admin\-client\-key PATH\fP The path to the private key used by the client, typically a file named \fBadmin.pem\fP\&. .TP .B \fB\-\-admin\-client\-name NAME\fP The name of the client, typically the name of the admin client. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-i\fP, \fB\-\-initial\fP Use to create a API client, typically an administrator client on a freshly\-installed Chef server\&. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-r REPO\fP, \fB\-\-repository REPO\fP The path to the chef\-repo\&. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-\-validation\-client\-name NAME\fP The name of the validation client. .TP .B \fB\-\-validation\-key PATH\fP The path to the validation key used by the client, typically a file named \fBvalidation.pem\fP\&. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife configure .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife configure client \(aq/directory\(aq .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-cookbook-site.1000066400000000000000000000411441276456504500230230ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-COOKBOOK-SITE" "1" "Chef 12.0" "" "knife cookbook site" .SH NAME knife-cookbook-site \- The man page for the knife cookbook site subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The Cookbooks Site API is used to provide access to the cookbooks community hosted at \fI\%https://supermarket.getchef.com/cookbooks\fP\&. All of the cookbooks in the community are accessible through a RESTful API located at \fI\%https://supermarket.getchef.com/api/v1/cookbooks\fP by using any of the supported endpoints. In most cases, using knife and the \fBknife cookbook site\fP sub\-command (and any of its arguments) is the recommended method of interacting with these cookbooks, but in some cases, using the Cookbooks Site API directly may make sense. .sp The \fBknife cookbook site\fP subcommand is used to interact with cookbooks that are located at \fI\%https://supermarket.getchef.com/cookbooks\fP\&. A user account is required for any community actions that write data to this site. The following arguments do not require a user account: \fBdownload\fP, \fBsearch\fP, \fBinstall\fP, and \fBlist\fP\&. .SH COMMON OPTIONS .sp The following options may be used with any of the arguments available to the \fBknife cookbook site\fP subcommand: .INDENT 0.0 .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH DOWNLOAD .sp The \fBdownload\fP argument is used to download a cookbook from the community website. A cookbook will be downloaded as a tar.gz archive and placed in the current working directory. If a cookbook (or cookbook version) has been deprecated and the \fB\-\-force\fP option is not used, knife will alert the user that the cookbook is deprecated and then will provide the name of the most recent non\-deprecated version of that cookbook. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site download COOKBOOK_NAME [COOKBOOK_VERSION] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fBCOOKBOOK_VERSION\fP The version of a cookbook to be downloaded. If a cookbook has only one version, this option does not need to be specified. If a cookbook has more than one version and this option is not specified, the most recent version of the cookbook will be downloaded. .TP .B \fB\-f FILE\fP, \fB\-\-file FILE\fP The file to which a cookbook download is written. .TP .B \fB\-\-force\fP Use to overwrite an existing directory. .UNINDENT .sp \fBExamples\fP .sp To download the cookbook \fBgetting\-started\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site download getting\-started .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Downloading getting\-started from the cookbooks site at version 0.3.0 to /Users/sdanna/opscodesupport/getting\-started\-0.3.0.tar.gz Cookbook saved: /Users/sdanna/opscodesupport/getting\-started\-0.3.0.tar.gz .ft P .fi .UNINDENT .UNINDENT .SH INSTALL .sp The \fBinstall\fP argument is used to install a cookbook that has been downloaded from the community site to a local git repository . This action uses the git version control system in conjunction with the \fI\%https://supermarket.getchef.com/cookbooks\fP site to install community\-contributed cookbooks to the local chef\-repo\&. Using this argument does the following: .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .IP 1. 3 A new "pristine copy" branch is created in git for tracking the upstream. .IP 2. 3 All existing versions of a cookbook are removed from the branch. .IP 3. 3 The cookbook is downloaded from \fI\%https://supermarket.getchef.com/cookbooks\fP in the tar.gz format. .IP 4. 3 The downloaded cookbook is untarred and its contents are committed to git and a tag is created. .IP 5. 3 The "pristine copy" branch is merged into the master branch. .UNINDENT .UNINDENT .UNINDENT .sp This process allows the upstream cookbook in the master branch to be modified while letting git maintain changes as a separate patch. When an updated upstream version becomes available, those changes can be merged while maintaining any local modifications. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site install COOKBOOK_NAME [COOKBOOK_VERSION] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-b\fP, \fB\-\-use\-current\-branch\fP Use to ensure that the current branch is used. .TP .B \fB\-B BRANCH\fP, \fB\-\-branch BRANCH\fP The name of the default branch. This will default to the master branch. .TP .B \fBCOOKBOOK_VERSION\fP The version of the cookbook to be installed. If a version is not specified, the most recent version of the cookbook will be installed. .TP .B \fB\-D\fP, \fB\-\-skip\-dependencies\fP Use to ensure that all cookbooks to which the installed cookbook has a dependency will not be installed. .TP .B \fB\-o PATH:PATH\fP, \fB\-\-cookbook\-path PATH:PATH\fP The directory in which cookbooks are created. This can be a colon\-separated path. .UNINDENT .sp \fBExamples\fP .sp To install the cookbook \fBgetting\-started\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site install getting\-started .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Installing getting\-started to /Users/sdanna/opscodesupport/.chef/../cookbooks Checking out the master branch. Creating pristine copy branch chef\-vendor\-getting\-started Downloading getting\-started from the cookbooks site at version 0.3.0 to /Users/sdanna/opscodesupport/.chef/../cookbooks/getting\-started.tar.gz Cookbook saved: /Users/sdanna/opscodesupport/.chef/../cookbooks/getting\-started.tar.gz Removing pre\-existing version. Uncompressing getting\-started version /Users/sdanna/opscodesupport/.chef/../cookbooks. removing downloaded tarball 1 files updated, committing changes Creating tag cookbook\-site\-imported\-getting\-started\-0.3.0 Checking out the master branch. Updating 4d44b5b..b4c32f2 Fast\-forward cookbooks/getting\-started/README.rdoc | 4 +++ cookbooks/getting\-started/attributes/default.rb | 1 + cookbooks/getting\-started/metadata.json | 29 ++++++++++++++++++++ cookbooks/getting\-started/metadata.rb | 6 ++++ cookbooks/getting\-started/recipes/default.rb | 23 +++++++++++++++ .../templates/default/chef\-getting\-started.txt.erb | 5 +++ 6 files changed, 68 insertions(+), 0 deletions(\-) create mode 100644 cookbooks/getting\-started/README.rdoc create mode 100644 cookbooks/getting\-started/attributes/default.rb create mode 100644 cookbooks/getting\-started/metadata.json create mode 100644 cookbooks/getting\-started/metadata.rb create mode 100644 cookbooks/getting\-started/recipes/default.rb create mode 100644 cookbooks/getting\-started/templates/default/chef\-getting\-started.txt.erb Cookbook getting\-started version 0.3.0 successfully installed .ft P .fi .UNINDENT .UNINDENT .SH LIST .sp The \fBlist\fP argument is used to view a list of cookbooks that are currently available at \fI\%https://supermarket.getchef.com/cookbooks\fP\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site list .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-w\fP, \fB\-\-with\-uri\fP Use to show the corresponding URIs. .UNINDENT .sp \fBExamples\fP .sp To view a list of cookbooks at \fI\%https://supermarket.getchef.com/cookbooks\fP server, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site list .ft P .fi .UNINDENT .UNINDENT .sp to return: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 1password homesick rabbitmq 7\-zip hostname rabbitmq\-management AmazonEC2Tag hosts rabbitmq_chef R hosts\-awareness rackspaceknife accounts htop radiant ack\-grep hudson rails activemq icinga rails_enterprise ad id3lib redis\-package ad\-likewise iftop redis2 ant iis redmine [...truncated...] .ft P .fi .UNINDENT .UNINDENT .SH SEARCH .sp The \fBsearch\fP argument is used to search for a cookbook at \fI\%https://supermarket.getchef.com/cookbooks\fP\&. A search query is used to return a list of cookbooks at \fI\%https://supermarket.getchef.com/cookbooks\fP and uses the same syntax as the \fBknife search\fP sub\-command. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site search SEARCH_QUERY (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To search for all of the cookbooks that can be used with Apache, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site search apache* .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C apache2: cookbook: https://supermarket.chef.io/api/v1/cookbooks/apache2 cookbook_description: Installs and configures apache2 using Debian symlinks with helper definitions cookbook_maintainer: opscode cookbook_name: apache2 instiki: cookbook: https://supermarket.chef.io/api/v1/cookbooks/instiki cookbook_description: Installs instiki, a Ruby on Rails wiki server under passenger+Apache2. cookbook_maintainer: jtimberman cookbook_name: instiki kickstart: cookbook: https://supermarket.chef.io/api/v1/cookbooks/kickstart cookbook_description: Creates apache2 vhost and serves a kickstart file. cookbook_maintainer: opscode cookbook_name: kickstart [...truncated...] .ft P .fi .UNINDENT .UNINDENT .SH SHARE .sp The \fBshare\fP argument is used to add a cookbook to \fI\%https://supermarket.getchef.com/cookbooks\fP\&. This action will require a user account and a certificate for \fI\%https://supermarket.getchef.com\fP\&. By default, knife will use the user name and API key that is identified in the configuration file used during the upload; otherwise these values must be specified on the command line or in an alternate configuration file. If a cookbook already exists on \fI\%https://supermarket.getchef.com/cookbooks\fP, then only an owner or maintainer of that cookbook can make updates. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site share COOKBOOK_NAME CATEGORY (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fBCATEGORY\fP The cookbook category: \fB"Databases"\fP, \fB"Web Servers"\fP, \fB"Process Management"\fP, \fB"Monitoring & Trending"\fP, \fB"Programming Languages"\fP, \fB"Package Management"\fP, \fB"Applications"\fP, \fB"Networking"\fP, \fB"Operating Systems & Virtualization"\fP, \fB"Utilities"\fP, or \fB"Other"\fP\&. .TP .B \fB\-n\fP, \fB\-\-dry\-run\fP Use to take no action and only print out results. Default: \fBfalse\fP\&. .TP .B \fB\-o PATH:PATH\fP, \fB\-\-cookbook\-path PATH:PATH\fP The directory in which cookbooks are created. This can be a colon\-separated path. .UNINDENT .sp \fBExamples\fP .sp To share a cookbook named \fBapache2\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site share "apache2" "Web Servers" .ft P .fi .UNINDENT .UNINDENT .SH SHOW .sp The \fBshow\fP argument is used to view information about a cookbook on \fI\%https://supermarket.getchef.com/cookbooks\fP\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site show COOKBOOK_NAME [COOKBOOK_VERSION] .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fBCOOKBOOK_VERSION\fP The version of a cookbook to be shown. If a cookbook has only one version, this option does not need to be specified. If a cookbook has more than one version and this option is not specified, a list of cookbook versions will be returned. .UNINDENT .sp \fBExamples\fP .sp To show the details for a cookbook named \fBhaproxy\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site show haproxy .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C average_rating: category: Networking created_at: 2009\-10\-25T23:51:07Z description: Installs and configures haproxy external_url: latest_version: https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/1_0_3 maintainer: opscode name: haproxy updated_at: 2011\-06\-30T21:53:25Z versions: https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/1_0_3 https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/1_0_2 https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/1_0_1 https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/1_0_0 https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/0_8_1 https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/0_8_0 https://supermarket.chef.io/api/v1/cookbooks/haproxy/versions/0_7_0 .ft P .fi .UNINDENT .UNINDENT .sp To view information in JSON format, use the \fB\-F\fP common option as part of the command like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role show devops \-F json .ft P .fi .UNINDENT .UNINDENT .sp Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP\&. .SH UNSHARE .sp The \fBunshare\fP argument is used to stop the sharing of a cookbook at \fI\%https://supermarket.getchef.com/cookbooks\fP\&. Only the maintainer of a cookbook may perform this action. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site unshare COOKBOOK_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To unshare a cookbook named \fBgetting\-started\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook site unshare getting\-started .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-cookbook.1000066400000000000000000000455541276456504500220720ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-COOKBOOK" "1" "Chef 12.0" "" "knife cookbook" .SH NAME knife-cookbook \- The man page for the knife cookbook subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp A cookbook is the fundamental unit of configuration and policy distribution. Each cookbook defines a scenario, such as everything needed to install and configure MySQL, and then it contains all of the components that are required to support that scenario, including: .INDENT 0.0 .IP \(bu 2 Attribute values that are set on nodes .IP \(bu 2 Definitions that allow the creation of reusable collections of resources .IP \(bu 2 File distributions .IP \(bu 2 Libraries that extend the chef\-client and/or provide helpers to Ruby code .IP \(bu 2 Recipes that specify which resources to manage and the order in which those resources will be applied .IP \(bu 2 Custom resources and providers .IP \(bu 2 Templates .IP \(bu 2 Versions .IP \(bu 2 Metadata about recipes (including dependencies), version constraints, supported platforms, and so on .UNINDENT .sp The \fBknife cookbook\fP subcommand is used to interact with cookbooks that are located on the Chef server or the local chef\-repo\&. .SH COMMON OPTIONS .sp The following options may be used with any of the arguments available to the \fBknife cookbook\fP subcommand: .INDENT 0.0 .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH BULK DELETE .sp The \fBbulk delete\fP argument is used to delete cookbook files that match a pattern defined by a regular expression. The regular expression must be within quotes and not be surrounded by forward slashes (/). .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook bulk delete REGEX (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-p\fP, \fB\-\-purge\fP Use to entirely remove a cookbook (or cookbook version) from the Chef server\&. This action should be used carefully because only one copy of any single file is stored on the Chef server\&. Consequently, purging a cookbook will disable any other cookbook that references one or more files from a cookbook that has been purged. .UNINDENT .sp \fBExamples\fP .sp Use a regular expression to define the pattern used to bulk delete cookbooks: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook bulk delete "^[0\-9]{3}$" \-p .ft P .fi .UNINDENT .UNINDENT .SH CREATE .sp The \fBcreate\fP argument is used to create a new cookbook directory on the local machine, including the following directories and files: .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .IP \(bu 2 cookbook/attributes .IP \(bu 2 cookbook/CHANGELOG.md .IP \(bu 2 cookbook/definitions .IP \(bu 2 cookbook/files/default .IP \(bu 2 cookbook/libraries .IP \(bu 2 cookbook/metadata.rb .IP \(bu 2 cookbook/providers .IP \(bu 2 cookbook/README.md (or .rdoc) .IP \(bu 2 cookbook/recipes/default.rb .IP \(bu 2 cookbook/resources .IP \(bu 2 cookbook/templates/default .UNINDENT .UNINDENT .UNINDENT .sp After the cookbook is created, it can be uploaded to the Chef server using the \fBknife upload\fP argument. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook create COOKBOOK_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-C COPYRIGHT_HOLDER\fP, \fB\-\-copyright COPYRIGHT_HOLDER\fP The name of the copyright holder. This option will place a copyright notice that contains the name of the copyright holder in each of the pre\-created files. If this option is not specified, a copyright name of "your_company_name" will be used instead; it can be easily modified later. .TP .B \fB\-I LICENSE\fP, \fB\-\-license LICENSE\fP The type of license under which a cookbook is distributed: \fBapachev2\fP, \fBgplv2\fP, \fBgplv3\fP, \fBmit\fP, or \fBnone\fP (default). This option will place the appropriate license notice in the pre\-created files: \fBApache v2.0\fP (for \fBapachev2\fP), \fBGPL v2\fP (for \fBgplv2\fP), \fBGPL v3\fP (for \fBgplv3\fP), \fBMIT\fP (for \fBmit\fP), or \fBlicense \(aqProprietary \- All Rights Reserved\fP (for \fBnone\fP). Be aware of the licenses for files inside of a cookbook and be sure to follow any restrictions they describe. .TP .B \fB\-m EMAIL\fP, \fB\-\-email EMAIL\fP The email address for the individual who maintains the cookbook. This option will place an email address in each of the pre\-created files. If this option is not specified, an email name of "your_email" will be used instead; it can be easily modified later. .TP .B \fB\-o PATH\fP, \fB\-\-cookbook\-path PATH\fP The directory in which cookbooks are created. This can be a colon\-separated path. .TP .B \fB\-r FORMAT\fP, \fB\-\-readme\-format FORMAT\fP The document format of the readme file: \fBmd\fP (markdown) and \fBrdoc\fP (Ruby docs). .UNINDENT .sp \fBExamples\fP .sp To create a cookbook named "my_cookbook" with copyright, email, license, and readme format options specified, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook create my_cookbook \-C "My Name" \-m "my@email.com" \-I apachev2 \-r md .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C ** Creating cookbook my_cookbook ** Creating README for cookbook: my_cookbook ** Creating metadata for cookbook: my_cookbook .ft P .fi .UNINDENT .UNINDENT .SH DELETE .sp The \fBdelete\fP argument is used to delete a specified cookbook or cookbook version on the Chef server (and not locally). .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook delete COOKBOOK_NAME [COOKBOOK_VERSION] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-all\fP Use to delete all cookbooks (and cookbook versions). .TP .B \fBCOOKBOOK_VERSION\fP The version of a cookbook to be deleted. If a cookbook has only one version, this option does not need to be specified. If a cookbook has more than one version and this option is not specified, knife will prompt for a version. .TP .B \fB\-p\fP, \fB\-\-purge\fP Use to entirely remove a cookbook (or cookbook version) from the Chef server\&. This action should be used carefully because only one copy of any single file is stored on the Chef server\&. Consequently, purging a cookbook will disable any other cookbook that references one or more files from a cookbook that has been purged. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook delete cookbook_name version .ft P .fi .UNINDENT .UNINDENT .sp For example: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook delete smartmon 0.8 .ft P .fi .UNINDENT .UNINDENT .sp Type \fBY\fP to confirm a deletion. .SH DOWNLOAD .sp The \fBdownload\fP argument is used to download a cookbook from the Chef server to the current working directory. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook download COOKBOOK_NAME [COOKBOOK_VERSION] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-d DOWNLOAD_DIRECTORY\fP, \fB\-\-dir DOWNLOAD_DIRECTORY\fP The directory in which cookbooks are located. .TP .B \fB\-f\fP, \fB\-\-force\fP Use to overwrite an existing directory. .TP .B \fB\-N\fP, \fB\-\-latest\fP Use to download the most recent version of a cookbook. .UNINDENT .sp \fBExamples\fP .sp To download a cookbook named "smartmon", enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook download smartmon .ft P .fi .UNINDENT .UNINDENT .SH LIST .sp The \fBlist\fP argument is used to view a list of cookbooks that are currently available on the Chef server\&. The list will contain only the most recent version for each cookbook by default. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook list (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-all\fP Use to return all available versions for every cookbook. .TP .B \fB\-w\fP, \fB\-\-with\-uri\fP Use to show the corresponding URIs. .UNINDENT .sp \fBExamples\fP .sp To view a list of cookbooks: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook list .ft P .fi .UNINDENT .UNINDENT .SH METADATA .sp The \fBmetadata\fP argument is used to generate the metadata for one or more cookbooks. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook metadata (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-all\fP Use to generate metadata for all cookbooks. .TP .B \fB\-o PATH:PATH\fP, \fB\-\-cookbook\-path PATH:PATH\fP The directory in which cookbooks are created. This can be a colon\-separated path. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook metadata \-a .ft P .fi .UNINDENT .UNINDENT .SH METADATA FROM FILE .sp The \fBmetadata from file\fP argument is used to load the metadata for a cookbook from a file. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook metadata from file FILE .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook metadata from file /path/to/file .ft P .fi .UNINDENT .UNINDENT .SH SHOW .sp The \fBshow\fP argument is used to view information about a cookbook, parts of a cookbook (attributes, definitions, files, libraries, providers, recipes, resources, and templates), or a file that is associated with a cookbook (including attributes such as checksum or specificity). .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook show COOKBOOK_NAME [COOKBOOK_VERSION] [PART...] [FILE_NAME] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fBCOOKBOOK_VERSION\fP The version of a cookbook to be shown. If a cookbook has only one version, this option does not need to be specified. If a cookbook has more than one version and this option is not specified, a list of cookbook versions will be returned. .TP .B \fB\-f FQDN\fP, \fB\-\-fqdn FQDN\fP The FQDN of the host. .TP .B \fBFILE_NAME\fP The name of a file that is associated with a cookbook. .TP .B \fB\-p PLATFORM\fP, \fB\-\-platform PLATFORM\fP The platform for which a cookbook is designed. .TP .B \fBPART\fP The part of the cookbook to show: \fBattributes\fP, \fBdefinitions\fP, \fBfiles\fP, \fBlibraries\fP, \fBproviders\fP, \fBrecipes\fP, \fBresources\fP, or \fBtemplates\fP\&. More than one part can be specified. .TP .B \fB\-V PLATFORM_VERSION\fP, \fB\-\-platform\-version PLATFORM_VERSION\fP The version of the platform. .TP .B \fB\-w\fP, \fB\-\-with\-uri\fP Use to show the corresponding URIs. .UNINDENT .sp \fBExamples\fP .sp To get the list of available versions of a cookbook named "getting\-started", enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook show getting\-started .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C getting\-started 0.3.0 0.2.0 .ft P .fi .UNINDENT .UNINDENT .sp To show a list of data about a cookbook using the name of the cookbook and the version, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook show getting\-started 0.3.0 .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C attributes: checksum: fa0fc4abf3f6787aeb5c3c5c35de667c name: default.rb path: attributes/default.rb specificity: default url: https://somelongurlhere.com chef_type: cookbook_version cookbook_name: getting\-started definitions: [] files: [] frozen?: false json_class: Chef::CookbookVersion libraries: [] .ft P .fi .UNINDENT .UNINDENT .sp To only view data about "templates", enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook show getting\-started 0.3.0 templates .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C checksum: a29d6f254577b830091f140c3a78b1fe name: chef\-getting\-started.txt.erb path: templates/default/chef\-getting\-started.txt.erb specificity: default url: https://someurlhere.com .ft P .fi .UNINDENT .UNINDENT .sp To view information in JSON format, use the \fB\-F\fP common option as part of the command like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role show devops \-F json .ft P .fi .UNINDENT .UNINDENT .sp Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP\&. .SH TEST .sp The \fBtest\fP argument is used to test a cookbook for syntax errors. This argument uses Ruby syntax checking to verify every file in a cookbook that ends in .rb and Embedded Ruby (ERB)\&. This argument will respect \&.chefignore files when determining which cookbooks to test for syntax errors. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook test COOKBOOK_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-all\fP Use to test all cookbooks. .TP .B \fB\-o PATH:PATH\fP, \fB\-\-cookbook\-path PATH:PATH\fP The directory in which cookbooks are created. This can be a colon\-separated path. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook test cookbook_name .ft P .fi .UNINDENT .UNINDENT .SH UPLOAD .sp The \fBupload\fP argument is used to upload one or more cookbooks (and any files that are associated with those cookbooks) from a local repository to the Chef server\&. Only files that do not already exist on the Chef server will be uploaded. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 Use a \&.chefignore file to prevent the upload of specific files and file types, such as temporary files or files placed in folders by version control systems. The \&.chefignore file must be located in the root of the cookbook repository and must use rules similar to filename globbing (as defined by the Ruby \fBFile.fnmatch\fP syntax). .UNINDENT .UNINDENT .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 Empty directories are not uploaded to the Chef server\&. To upload an empty directory, create a "dot" file\-\-\-e.g. \fB\&.keep\fP\-\-\-in that directory to ensure that the directory itself is not empty. .UNINDENT .UNINDENT .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook upload [COOKBOOK_NAME...] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-all\fP Use to upload all cookbooks. .TP .B \fB\-\-concurrency\fP The number of allowed concurrent connections. Default: \fB10\fP\&. .TP .B \fB\-d\fP, \fB\-\-include\-dependencies\fP Use to ensure that when a cookbook has a dependency on one (or more) cookbooks, those cookbooks will also be uploaded. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP Use to set the environment version dependency to the cookbook version being uploaded. .TP .B \fB\-\-force\fP Use to update a cookbook even if the \fB\-\-freeze\fP flag has been set. .TP .B \fB\-\-freeze\fP Use to require changes to a cookbook be included as a new version. Only the \fB\-\-force\fP option can override this setting. .TP .B \fB\-o PATH:PATH\fP, \fB\-\-cookbook\-path PATH:PATH\fP The directory in which cookbooks are created. This can be a colon\-separated path. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook upload cookbook_name .ft P .fi .UNINDENT .UNINDENT .sp To upload a cookbook, and then prevent other users from being able to make changes to it, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife cookbook upload redis \-\-freeze .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Uploading redis... Upload completed .ft P .fi .UNINDENT .UNINDENT .sp If a cookbook is frozen and the \fB\-\-force\fP option is not specified, knife will return an error message similar to the following: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Uploading redis... ERROR: Version 0.1.6 of cookbook redis is frozen. Use \-\-force to override. .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-data-bag.1000066400000000000000000000324001276456504500217060ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-DATA-BAG" "1" "Chef 12.0" "" "knife data bag" .SH NAME knife-data-bag \- The man page for the knife data bag subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp A data bag is a global variable that is stored as JSON data and is accessible from a Chef server\&. A data bag is indexed for searching and can be loaded by a recipe or accessed during a search. .sp A data bag item may be encrypted using \fI\%shared secret encryption\fP\&. This allows each data bag item to store confidential information (such as a database password) or to be managed in a source control system (without plain\-text data appearing in revision history). Each data bag item may be encrypted individually; if a data bag contains multiple encrypted data bag items, these data bag items are not required to share the same encryption keys. .sp The \fBknife data bag\fP subcommand is used to manage arbitrary stores of globally available JSON data. .SH COMMON OPTIONS .sp The following options may be used with any of the arguments available to the \fBknife data bag\fP subcommand: .INDENT 0.0 .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH CREATE .sp The \fBcreate\fP argument is used to add a data bag to the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag create DATA_BAG_NAME [DATA_BAG_ITEM] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fBDATA_BAG_ITEM\fP The name of a specific item within a data bag. .TP .B \fB\-\-secret SECRET\fP The encryption key that is used for values contained within a data bag item. If \fBsecret\fP is not specified, the chef\-client will look for a secret at the path specified by the \fBencrypted_data_bag_secret\fP setting in the client.rb file. .TP .B \fB\-\-secret\-file FILE\fP The path to the file that contains the encryption key. .UNINDENT .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 For encrypted data bag items, use \fIeither\fP \fB\-\-secret\fP or \fB\-\-secret\-file\fP, not both. .UNINDENT .UNINDENT .sp \fBExamples\fP .sp To create a data bag named "admins", enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag create admins .ft P .fi .UNINDENT .UNINDENT .sp to return: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Created data_bag[admins] .ft P .fi .UNINDENT .UNINDENT .SH DELETE .sp The \fBdelete\fP argument is used to delete a data bag or a data bag item from a Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag delete DATA_BAG_NAME [DATA_BAG_ITEM] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fBDATA_BAG_ITEM\fP The name of a specific item within a data bag. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag delete data_bag_name .ft P .fi .UNINDENT .UNINDENT .sp To delete an item named "charlie", enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag delete admins charlie .ft P .fi .UNINDENT .UNINDENT .sp Type \fBY\fP to confirm a deletion. .SH EDIT .sp The \fBedit\fP argument is used to edit the data contained in a data bag. If encryption is being used, the data bag will be decrypted, the data will be made available in the $EDITOR, and then encrypted again before saving it to the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag edit DATA_BAG_NAME [DATA_BAG_ITEM] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fBDATA_BAG_ITEM\fP The name of a specific item within a data bag. .TP .B \fB\-\-secret SECRET\fP The encryption key that is used for values contained within a data bag item. If \fBsecret\fP is not specified, the chef\-client will look for a secret at the path specified by the \fBencrypted_data_bag_secret\fP setting in the client.rb file. .TP .B \fB\-\-secret\-file FILE\fP The path to the file that contains the encryption key. .UNINDENT .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 For encrypted data bag items, use \fIeither\fP \fB\-\-secret\fP or \fB\-\-secret\-file\fP, not both. .UNINDENT .UNINDENT .sp \fBExamples\fP .sp To edit the contents of a data bag, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag edit dogs tibetanspaniel .ft P .fi .UNINDENT .UNINDENT .sp where \fBdogs\fP is the name of the data bag and \fBtibetanspaniel\fP is the name of the data bag item. This will return something similar to the following in the knife editor: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "name":"data_bag_item_dogs_tibetanspaniel", "json_class":"Chef::DataBagItem", "chef_type":"data_bag_item", "data_bag":"dogs", "raw_data": { "description":"small dog that likes to sit in windows", "id":"tibetanspaniel" } } .ft P .fi .UNINDENT .UNINDENT .sp Make the necessary changes to the key\-value pairs under \fBraw_data\fP and save them. .sp To edit an item named "charlie" that is contained in a data bag named "admins", enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag edit admins charlie .ft P .fi .UNINDENT .UNINDENT .sp to open the $EDITOR\&. Once opened, you can update the data before saving it to the Chef server\&. For example, by changing: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "id": "charlie" } .ft P .fi .UNINDENT .UNINDENT .sp to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "id": "charlie", "uid": 1005, "gid": "ops", "shell": "/bin/zsh", "comment": "Crazy Charlie" } .ft P .fi .UNINDENT .UNINDENT .SH FROM FILE .sp The \fBfrom file\fP argument is used to: .INDENT 0.0 .IP \(bu 2 Add a data bag item to a data bag .IP \(bu 2 Update the contents of an existing data bag item .UNINDENT .sp The data bag itself must already exist on the Chef server and must be specified as part of the command. The contents of the data bag item are specified using a JSON file. This JSON file may be located at a relative or absolute path; its location must be specified as part of the command. The JSON file that defines the contents of the data bag item must at least contain the name of the data bag item\-\-\-\fB"id": "name"\fP\&. .sp \fBWARNING:\fP .INDENT 0.0 .INDENT 3.5 A chef\-client must be version 11.6 (or higher) when using the \fBknife data bag from file\fP argument with the Enterprise Chef or Open Source Chef version 11 servers. .UNINDENT .UNINDENT .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag from file DATA_BAG_NAME_or_PATH .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-all\fP Use to upload all data bags found at the specified path. .TP .B \fB\-\-secret SECRET\fP The encryption key that is used for values contained within a data bag item. If \fBsecret\fP is not specified, the chef\-client will look for a secret at the path specified by the \fBencrypted_data_bag_secret\fP setting in the client.rb file. .TP .B \fB\-\-secret\-file FILE\fP The path to the file that contains the encryption key. .UNINDENT .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 For encrypted data bag items, use \fIeither\fP \fB\-\-secret\fP or \fB\-\-secret\-file\fP, not both. .UNINDENT .UNINDENT .sp \fBExamples\fP .sp To create a data bag on the Chef server from a file: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag from file "path to JSON file" .ft P .fi .UNINDENT .UNINDENT .sp To create a data bag named "devops_data" that contains encrypted data, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag from file devops_data \-\-secret\-file "path to decryption file" .ft P .fi .UNINDENT .UNINDENT .SH LIST .sp The \fBlist\fP argument is used to view a list of data bags that are currently available on the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag list .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-w\fP, \fB\-\-with\-uri\fP Use to show the corresponding URIs. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag list .ft P .fi .UNINDENT .UNINDENT .SH SHOW .sp The \fBshow\fP argument is used to view the contents of a data bag. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag show DATA_BAG_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fBDATA_BAG_ITEM\fP The name of a specific item within a data bag. .TP .B \fB\-\-secret SECRET\fP The encryption key that is used for values contained within a data bag item. If \fBsecret\fP is not specified, the chef\-client will look for a secret at the path specified by the \fBencrypted_data_bag_secret\fP setting in the client.rb file. .TP .B \fB\-\-secret\-file FILE\fP The path to the file that contains the encryption key. .UNINDENT .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 For encrypted data bag items, use \fIeither\fP \fB\-\-secret\fP or \fB\-\-secret\-file\fP, not both. .UNINDENT .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag show admins .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C charlie .ft P .fi .UNINDENT .UNINDENT .sp To show the contents of a specific item within data bag, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag show admins charlie .ft P .fi .UNINDENT .UNINDENT .sp to return: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C comment: Crazy Charlie gid: ops id: charlie shell: /bin/zsh uid: 1005 .ft P .fi .UNINDENT .UNINDENT .sp To show the contents of a data bag named \fBpasswords\fP with an item that contains encrypted data named \fBmysql\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag show passwords mysql .ft P .fi .UNINDENT .UNINDENT .sp to return: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C ## sample: { "id": "mysql", "pass": "trywgFA6R70NO28PNhMpGhEvKBZuxouemnbnAUQsUyo=\en", "user": "e/p+8WJYVHY9fHcEgAAReg==\en" } .ft P .fi .UNINDENT .UNINDENT .sp To show the decrypted contents of the same data bag, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag show \-\-secret\-file /path/to/decryption/file passwords mysql .ft P .fi .UNINDENT .UNINDENT .sp to return: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C ## sample: { "id": "mysql", "pass": "thesecret123", "user": "fred" } .ft P .fi .UNINDENT .UNINDENT .sp To view information in JSON format, use the \fB\-F\fP common option as part of the command like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag show admins \-F json .ft P .fi .UNINDENT .UNINDENT .sp Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP\&. .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-delete.1000066400000000000000000000105071276456504500215140ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-DELETE" "1" "Chef 12.0" "" "knife delete" .SH NAME knife-delete \- The man page for the knife delete subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife delete\fP subcommand is used to delete an object from a Chef server\&. This subcommand works similar to \fBknife cookbook delete\fP, \fBknife data bag delete\fP, \fBknife environment delete\fP, \fBknife node delete\fP, and \fBknife role delete\fP, but with a single verb (and a single action). .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife delete [PATTERN...] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-\-both\fP Use to delete both local and remote copies of an object. Default: \fBfalse\fP\&. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-repo\-path PATH\fP The path to the chef\-repo\&. This setting will override the default path to the chef\-repo\&. Default: same as specified by \fBchef_repo_path\fP in config.rb. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-concurrency\fP The number of allowed concurrent connections. Default: \fB10\fP\&. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-local\fP Use to delete only the local copy of an object. (A remote copy will not be deleted.) Default: \fBfalse\fP\&. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-r\fP, \fB\-\-[no\-]recurse\fP Use \fB\-\-recurse\fP to delete directories recursively. Default: \fB\-\-no\-recurse\fP\&. .TP .B \fB\-\-repo\-mode MODE\fP The layout of the local chef\-repo\&. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP\&. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP\&. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-deps.1000066400000000000000000000135511276456504500212070ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-DEPS" "1" "Chef 12.0" "" "knife deps" .SH NAME knife-deps \- The man page for the knife deps subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife deps\fP subcommand is used to identify dependencies for a node, role, or cookbook. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife deps (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-repo\-path PATH\fP The path to the chef\-repo\&. This setting will override the default path to the chef\-repo\&. Default: same as specified by \fBchef_repo_path\fP in config.rb. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-concurrency\fP The number of allowed concurrent connections. Default: \fB10\fP\&. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-\-[no\-]recurse\fP Use \fB\-\-recurse\fP to list dependencies recursively. This option can only be used when \fB\-\-tree\fP is set to \fBtrue\fP\&. Default: \fB\-\-no\-recurse\fP\&. .TP .B \fB\-\-remote\fP Use to determine dependencies from objects located on the Chef server instead of in the local chef\-repo\&. Default: \fBfalse\fP\&. .TP .B \fB\-\-repo\-mode MODE\fP The layout of the local chef\-repo\&. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP\&. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP\&. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-\-tree\fP Use to show dependencies in a visual tree structure (including duplicates, if they exist). Default: \fBfalse\fP\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife deps nodes/node_name.json .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife deps roles/role_name.json .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife deps cookbooks/cookbook_name.json .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife deps environments/environment_name.json .ft P .fi .UNINDENT .UNINDENT .sp To find the dependencies for a combination of nodes, cookbooks, roles, and/or environments: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife deps cookbooks/git.json cookbooks/github.json roles/base.json environments/desert.json nodes/mynode.json .ft P .fi .UNINDENT .UNINDENT .sp A wildcard can be used to return all of the child nodes. For example, all of the environments: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife deps environments/*.json .ft P .fi .UNINDENT .UNINDENT .sp Use the \fB\-\-tree\fP option to view the results with structure: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife deps roles/webserver.json .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C roles/webserver.json roles/base.json cookbooks/github cookbooks/git cookbooks/users cookbooks/apache2 .ft P .fi .UNINDENT .UNINDENT .sp The output of \fBknife deps\fP can be passed to \fBknife upload\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload \(gaknife deps nodes/*.json .ft P .fi .UNINDENT .UNINDENT .sp The output of \fBknife deps\fP can be passed to \fBknife xargs\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife deps nodes/*.json | xargs knife upload .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-diff.1000066400000000000000000000157761276456504500211770ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-DIFF" "1" "Chef 12.0" "" "knife diff" .SH NAME knife-diff \- The man page for the knife diff subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife diff\fP subcommand is used to compare the differences between files and directories on the Chef server and in the chef\-repo\&. For example, to compare files on the Chef server prior to an uploading or downloading files using the \fBknife download\fP and \fBknife upload\fP subcommands, or to ensure that certain files in multiple production environments are the same. This subcommand is similar to the \fBgit diff\fP command that can be used to diff what is in the chef\-repo with what is synced to a git repository. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife diff [PATTERN...] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-repo\-path PATH\fP The path to the chef\-repo\&. This setting will override the default path to the chef\-repo\&. Default: same as specified by \fBchef_repo_path\fP in config.rb. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-cookbook\-version VERSION\fP The version of a cookbook to be downloaded. .TP .B \fB\-\-concurrency\fP The number of allowed concurrent connections. Default: \fB10\fP\&. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-\-diff\-filter=[(A|D|M|T)...[*]]\fP Use to select only files that have been added (\fBA\fP), deleted (\fBD\fP), modified (\fBM\fP), and/or have had their type changed (\fBT\fP). Any combination of filter characters may be used, including no filter characters. Use \fB*\fP to select all paths if a file matches other criteria in the comparison. Default value: \fBnil\fP\&. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-name\-only\fP Use to show only the names of modified files. .TP .B \fB\-\-name\-status\fP Use to show only the names of files with a status of \fBAdded\fP, \fBDeleted\fP, \fBModified\fP, or \fBType Changed\fP\&. .TP .B \fB\-\-no\-recurse\fP Use \fB\-\-no\-recurse\fP to disable listing a directory recursively. Default: \fB\-\-recurse\fP\&. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-\-repo\-mode MODE\fP The layout of the local chef\-repo\&. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP\&. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP\&. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBknife.rb File Settings\fP .sp In addition to the default settings in a knife.rb file, there are other subcommand\-specific settings that can be added. When a subcommand is run, knife will use: .INDENT 0.0 .IP 1. 3 A value passed via the command\-line .IP 2. 3 A value contained in the knife.rb file .IP 3. 3 The default value .UNINDENT .sp A value passed via the command line will override a value in the knife.rb file; a value in a knife.rb file will override a default value. .sp The following \fBknife diff\fP settings can be added to the knife.rb file: .INDENT 0.0 .TP .B \fBknife[:chef_repo_path]\fP Use to add the \fB\-\-chef\-repo\-path\fP option. .TP .B \fBknife[:concurrency]\fP Use to add the \fB\-\-concurrency\fP option. .TP .B \fBknife[:name_only]\fP Use to add the \fB\-\-name\-only\fP option. .TP .B \fBknife[:name_status]\fP Use to add the \fB\-\-name\-status\fP option. .TP .B \fBknife[:recurse]\fP Use to add the \fB\-\-recurse\fP option. .TP .B \fBknife[:repo_mode]\fP Use to add the \fB\-\-repo\-mode\fP option. .UNINDENT .sp \fBExamples\fP .sp To compare the \fBbase.json\fP role to a \fBwebserver.json\fP role, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife diff roles/base.json roles/webserver.json .ft P .fi .UNINDENT .UNINDENT .sp To compare the differences between the local chef\-repo and the files that are on the Chef server, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife diff .ft P .fi .UNINDENT .UNINDENT .sp To diff a node named \fBnode\-lb\fP and then only return files that have been added, deleted, modified, or changed, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife diff \-\-name\-status node\-lb .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C node\-lb/recipes/eip.rb node\-lb/recipes/heartbeat\-int.rb node\-lb/templates/default/corpsite.conf.erb node\-lb/files/default/wildcard.node.com.crt node\-lb/files/default/wildcard.node.com.crt\-2009 node\-lb/files/default/wildcard.node.com.key node\-lb/.gitignore node\-lb/Rakefile .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-download.1000066400000000000000000000164321276456504500220640ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-DOWNLOAD" "1" "Chef 12.0" "" "knife download" .SH NAME knife-download \- The man page for the knife download subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife download\fP subcommand is used to download roles, cookbooks, environments, nodes, and data bags from the Chef server to the current working directory. It can be used to back up data on the Chef server, inspect the state of one or more files, or to extract out\-of\-process changes users may have made to files on the Chef server, such as if a user made a change that bypassed version source control. This subcommand is often used in conjunction with \fBknife diff\fP, which can be used to see exactly what changes will be downloaded, and then \fBknife upload\fP, which does the opposite of \fBknife download\fP\&. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download [PATTERN...] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-repo\-path PATH\fP The path to the chef\-repo\&. This setting will override the default path to the chef\-repo\&. Default: same as specified by \fBchef_repo_path\fP in config.rb. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-concurrency\fP The number of allowed concurrent connections. Default: \fB10\fP\&. .TP .B \fB\-\-cookbook\-version VERSION\fP The version of a cookbook to be downloaded. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-\-[no\-]diff\fP Use to download only new and modified files. Set to \fBfalse\fP to download all files. Default: \fB\-\-diff\fP\&. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-\-[no\-]force\fP Use \fB\-\-force\fP to download files even when the file on the hard drive is identical to the object on the server (role, cookbook, etc.). By default, files are compared to see if they have equivalent content, and local files are only overwritten if they are different. Default: \fB\-\-no\-force\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-n\fP, \fB\-\-dry\-run\fP Use to take no action and only print out results. Default: \fBfalse\fP\&. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-\-[no\-]purge\fP Use \fB\-\-purge\fP to delete local files and directories that do not exist on the Chef server\&. By default, if a role, cookbook, etc. does not exist on the Chef server, the local file for said role will be left alone and NOT deleted. Default: \fB\-\-no\-purge\fP\&. .TP .B \fB\-\-[no\-]recurse\fP Use \fB\-\-no\-recurse\fP to disable downloading a directory recursively. Default: \fB\-\-recurse\fP\&. .TP .B \fB\-\-repo\-mode MODE\fP The layout of the local chef\-repo\&. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP\&. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP\&. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp To download the entire chef\-repo from the Chef server, browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download / .ft P .fi .UNINDENT .UNINDENT .sp To download the \fBcookbooks/\fP directory from the Chef server, browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download cookbooks .ft P .fi .UNINDENT .UNINDENT .sp or from anywhere in the chef\-repo, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download /cookbooks .ft P .fi .UNINDENT .UNINDENT .sp To download the \fBenvironments/\fP directory from the Chef server, browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download environments .ft P .fi .UNINDENT .UNINDENT .sp or from anywhere in the chef\-repo, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download /environments .ft P .fi .UNINDENT .UNINDENT .sp To download an environment named "production" from the Chef server, browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download environments/production.json .ft P .fi .UNINDENT .UNINDENT .sp or from the \fBenvironments/\fP directory, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download production.json .ft P .fi .UNINDENT .UNINDENT .sp To download the \fBroles/\fP directory from the Chef server, browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download roles .ft P .fi .UNINDENT .UNINDENT .sp or from anywhere in the chef\-repo, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download /roles .ft P .fi .UNINDENT .UNINDENT .sp To download all cookbooks that start with "apache" and belong to the "webserver" role, browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife download cookbooks/apache\e* roles/webserver.json .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-edit.1000066400000000000000000000100471276456504500211760ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-EDIT" "1" "Chef 12.0" "" "knife edit" .SH NAME knife-edit \- The man page for the knife edit subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife edit\fP subcommand is used to edit objects on the Chef server\&. This subcommand works similar to \fBknife cookbook edit\fP, \fBknife data bag edit\fP, \fBknife environment edit\fP, \fBknife node edit\fP, and \fBknife role edit\fP, but with a single verb (and a single action). .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife edit (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-repo\-path PATH\fP The path to the chef\-repo\&. This setting will override the default path to the chef\-repo\&. Default: same as specified by \fBchef_repo_path\fP in config.rb. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-concurrency\fP The number of allowed concurrent connections. Default: \fB10\fP\&. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-local\fP Use to show files in the local chef\-repo instead of a remote location. Default: \fBfalse\fP\&. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-\-repo\-mode MODE\fP The layout of the local chef\-repo\&. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP\&. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP\&. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-environment.1000066400000000000000000000256241276456504500226240ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-ENVIRONMENT" "1" "Chef 12.0" "" "knife environment" .SH NAME knife-environment \- The man page for the knife environment subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp An environment is a way to map an organization\(aqs real\-life workflow to what can be configured and managed when using Chef server\&. Every organization begins with a single environment called the \fB_default\fP environment, which cannot be modified (or deleted). Additional environments can be created to reflect each organization\(aqs patterns and workflow. For example, creating \fBproduction\fP, \fBstaging\fP, \fBtesting\fP, and \fBdevelopment\fP environments. Generally, an environment is also associated with one (or more) cookbook versions. .sp The \fBknife environment\fP subcommand is used to manage environments within a single organization on the Chef server\&. .SH COMMON OPTIONS .sp The following options may be used with any of the arguments available to the \fBknife environment\fP subcommand: .INDENT 0.0 .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH COMPARE .sp The \fBcompare\fP argument is used to compare the cookbook version constraints that are set on one (or more) environments. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment compare [ENVIRONMENT_NAME...] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-all\fP Use to upload all environments found at the specified path. .TP .B \fB\-m\fP, \fB\-\-mismatch\fP Use to show only matching versions. .UNINDENT .sp \fBExample\fP .sp To compare cookbook versions for a single environment: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment compare development .ft P .fi .UNINDENT .UNINDENT .sp to return something similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C development apache 2.3.1 windows 4.1.2 .ft P .fi .UNINDENT .UNINDENT .sp To compare cookbook versions for multiple environments: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment compare development staging .ft P .fi .UNINDENT .UNINDENT .sp to return something similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C development staging apache 2.3.1 1.2.2 windows 4.1.2 1.0.0 postgresql 1.0.0 1.0.0 .ft P .fi .UNINDENT .UNINDENT .sp To compare all cookbook versions for all environments: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment compare \-\-all .ft P .fi .UNINDENT .UNINDENT .sp to return something similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C staging development ulimit latest latest redisio latest latest journly latest latest aws latest latest test latest latest unicorn latest latest sensu latest latest runit latest latest templater latest latest powershell latest latest openssl latest latest rbenv latest latest rabbitmq latest latest postgresql latest latest mysql latest latest ohai latest latest git latest latest erlang latest latest ssh_known_hosts latest latest nginx latest latest database latest latest yum latest latest xfs latest latest apt latest latest dmg latest latest chef_handler latest latest windows 1.0.0 4.1.2 .ft P .fi .UNINDENT .UNINDENT .SH CREATE .sp The \fBcreate\fP argument is used to add an environment object to the Chef server\&. When this argument is run, knife will open $EDITOR to enable editing of the \fBENVIRONMENT\fP description field (unless a description is specified as part of the command). When finished, knife will add the environment to the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment create ENVIRONMENT_NAME \-d DESCRIPTION .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-d DESCRIPTION\fP, \fB\-\-description DESCRIPTION\fP The description of the environment. This value will populate the description field for the environment on the Chef server\&. .UNINDENT .sp \fBExamples\fP .sp To create an environment named \fBdev\fP with a description of \fBThe development environment.\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment create dev \-d "The development environment." .ft P .fi .UNINDENT .UNINDENT .SH DELETE .sp The \fBdelete\fP argument is used to delete an environment from a Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment delete ENVIRONMENT_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To delete an environment named \fBdev\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment delete dev .ft P .fi .UNINDENT .UNINDENT .sp Type \fBY\fP to confirm a deletion. .SH EDIT .sp The \fBedit\fP argument is used to edit the attributes of an environment. When this argument is run, knife will open $EDITOR to enable editing of \fBENVIRONMENT\fP attributes. When finished, knife will update the Chef server with those changes. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment edit ENVIRONMENT_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To edit an environment named \fBdevops\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment edit devops .ft P .fi .UNINDENT .UNINDENT .SH FROM FILE .sp The \fBfrom file\fP argument is used to add or update an environment using a JSON or Ruby DSL description. It must be run with the \fBcreate\fP or \fBedit\fP arguments. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment [create | edit] from file FILE (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-all\fP Use to upload all environments found at the specified path. .UNINDENT .sp \fBExamples\fP .sp To add an environment using data contained in a JSON file: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment create devops from file "path to JSON file" .ft P .fi .UNINDENT .UNINDENT .sp or: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment edit devops from file "path to JSON file" .ft P .fi .UNINDENT .UNINDENT .SH LIST .sp The \fBlist\fP argument is used to list all of the environments that are currently available on the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment list \-w .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-w\fP, \fB\-\-with\-uri\fP Use to show the corresponding URIs. .UNINDENT .sp \fBExamples\fP .sp To view a list of environments: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment list \-w .ft P .fi .UNINDENT .UNINDENT .SH SHOW .sp The \fBshow\fP argument is used to display information about the specified environment. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment show ENVIRONMENT_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To view information about the \fBdev\fP environment enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife environment show dev .ft P .fi .UNINDENT .UNINDENT .sp to return: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C % knife environment show dev chef_type: environment cookbook_versions: default_attributes: description: json_class: Chef::Environment name: dev override_attributes: \e\e \e\e \e\e \e\e .ft P .fi .UNINDENT .UNINDENT .sp To view information in JSON format, use the \fB\-F\fP common option as part of the command like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role show devops \-F json .ft P .fi .UNINDENT .UNINDENT .sp Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP\&. .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-exec.1000066400000000000000000000175041276456504500212020ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-EXEC" "1" "Chef 12.0" "" "knife exec" .SH NAME knife-exec \- The man page for the knife exec subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife exec\fP subcommand uses the knife configuration file to execute Ruby scripts in the context of a fully configured chef\-client\&. This subcommand is most often used to run scripts that will only access Chef server one time (or otherwise very infrequently). Use this subcommand any time that an operation does not warrant full usage of the knife subcommand library. .sp \fBAuthenticated API Requests\fP .sp The \fBknife exec\fP subcommand can be used to make authenticated API requests to the Chef server using the following methods: .TS center; |l|l|. _ T{ Method T} T{ Description T} _ T{ \fBapi.delete\fP T} T{ Use to delete an object from the Chef server\&. T} _ T{ \fBapi.get\fP T} T{ Use to get the details of an object on the Chef server\&. T} _ T{ \fBapi.post\fP T} T{ Use to add an object to the Chef server\&. T} _ T{ \fBapi.put\fP T} T{ Use to update an object on the Chef server\&. T} _ .TE .sp These methods are used with the \fB\-E\fP option, which executes that string locally on the workstation using chef\-shell\&. These methods have the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife exec \-E \(aqapi.method(/endpoint)\(aq .ft P .fi .UNINDENT .UNINDENT .sp where: .INDENT 0.0 .IP \(bu 2 \fBapi.method\fP is the corresponding authentication method \-\-\- \fBapi.delete\fP, \fBapi.get\fP, \fBapi.post\fP, or \fBapi.put\fP .IP \(bu 2 \fB/endpoint\fP is an endpoint in the Chef server API .UNINDENT .sp For example, to get the data for a node named "Example_Node": .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife exec \-E \(aqputs api.get("/nodes/Example_Node")\(aq .ft P .fi .UNINDENT .UNINDENT .sp and to ensure that the output is visible in the console, add the \fBputs\fP in front of the API authorization request: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife exec \-E \(aqputs api.get("/nodes/Example_Node")\(aq .ft P .fi .UNINDENT .UNINDENT .sp where \fBputs\fP is the shorter version of the \fB$stdout.puts\fP predefined variable in Ruby\&. .sp The following example shows how to add a client named "IBM305RAMAC" and the \fB/clients\fP endpoint, and then return the private key for that user in the console: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ client_desc = { "name" => "IBM305RAMAC", "admin" => false } new_client = api.post("/clients", client_desc) puts new_client["private_key"] .ft P .fi .UNINDENT .UNINDENT .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife exec SCRIPT (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-E CODE\fP, \fB\-\-exec CODE\fP A string of code that will be executed. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-p PATH:PATH\fP, \fB\-\-script\-path PATH:PATH\fP A colon\-separated path at which Ruby scripts are located. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp There are three ways to use \fBknife exec\fP to run Ruby script files. For example: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife exec /path/to/script_file .ft P .fi .UNINDENT .UNINDENT .sp or: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife exec \-E \(aqRUBY CODE\(aq .ft P .fi .UNINDENT .UNINDENT .sp or: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife exec RUBY CODE ^D .ft P .fi .UNINDENT .UNINDENT .sp To check the status of knife using a Ruby script named \fBstatus.rb\fP (which looks like): .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C printf "%\-5s %\-12s %\-8s %s\en", "Check In", "Name", "Ruby", "Recipes" nodes.all do |n| checkin = Time.at(n[\(aqohai_time\(aq]).strftime("%F %R") rubyver = n[\(aqlanguages\(aq][\(aqruby\(aq][\(aqversion\(aq] recipes = n.run_list.expand(_default).recipes.join(", ") printf "%\-20s %\-12s %\-8s %s\en", checkin, n.name, rubyver, recipes end .ft P .fi .UNINDENT .UNINDENT .sp and is located in a directory named \fBscripts/\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife exec scripts/status.rb .ft P .fi .UNINDENT .UNINDENT .sp To show the available free memory for all nodes, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife exec \-E \(aqnodes.all {|n| puts "#{n.name} has #{n.memory.total} free memory"}\(aq .ft P .fi .UNINDENT .UNINDENT .sp To list all of the available search indexes, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife exec \-E \(aqputs api.get("search").keys\(aq .ft P .fi .UNINDENT .UNINDENT .sp To query a node for multiple attributes using a Ruby script named \fBsearch_attributes.rb\fP (which looks like): .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C % cat scripts/search_attributes.rb query = ARGV[2] attributes = ARGV[3].split(",") puts "Your query: #{query}" puts "Your attributes: #{attributes.join(" ")}" results = {} search(:node, query) do |n| results[n.name] = {} attributes.each {|a| results[n.name][a] = n[a]} end puts results exit 0 .ft P .fi .UNINDENT .UNINDENT .sp enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C % knife exec scripts/search_attributes.rb "hostname:test_system" ipaddress,fqdn .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Your query: hostname:test_system Your attributes: ipaddress fqdn {"test_system.example.com"=>{"ipaddress"=>"10.1.1.200", "fqdn"=>"test_system.example.com"}} .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-index-rebuild.1000066400000000000000000000030411276456504500230000ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-INDEX-REBUILD" "1" "Chef 12.0" "" "knife index rebuild" .SH NAME knife-index-rebuild \- The man page for the knife index rebuild subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife index rebuild\fP subcommand is used to rebuild the search indexes for the open source Chef server\&. This operation is destructive and may take some time. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 This subcommand ONLY works when run against the open source Chef server version 10.x. This subcommand will NOT run against open source Chef server 11, Enterprise Chef (including hosted Enterprise Chef), or Private Chef\&. .UNINDENT .UNINDENT .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife index rebuild .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-list.1000066400000000000000000000120241276456504500212210ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-LIST" "1" "Chef 12.0" "" "knife list" .SH NAME knife-list \- The man page for the knife list subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife list\fP subcommand is used to view a list of objects on the Chef server\&. This subcommand works similar to \fBknife cookbook list\fP, \fBknife data bag list\fP, \fBknife environment list\fP, \fBknife node list\fP, and \fBknife role list\fP, but with a single verb (and a single action). .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife list [PATTERN...] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-1\fP Use to show only one column of results. Default: \fBfalse\fP\&. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-repo\-path PATH\fP The path to the chef\-repo\&. This setting will override the default path to the chef\-repo\&. Default: same as specified by \fBchef_repo_path\fP in config.rb. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-concurrency\fP The number of allowed concurrent connections. Default: \fB10\fP\&. .TP .B \fB\-d\fP Use to prevent a directory\(aqs children from showing when a directory matches a pattern. Default value: \fBfalse\fP\&. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-f\fP, \fB\-\-flat\fP Use to show a list of file names. Set to \fBfalse\fP to view ls\-like output. Default: \fBfalse\fP\&. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-local\fP Use to return only the contents of the local directory. Default: \fBfalse\fP\&. .TP .B \fB\-p\fP Use to show directories with trailing slashes (/). Default: \fBfalse\fP\&. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-R\fP Use to list directories recursively. Default: \fBfalse\fP\&. .TP .B \fB\-\-repo\-mode MODE\fP The layout of the local chef\-repo\&. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP\&. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP\&. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp For example, to view a list of roles on the Chef server: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife list roles/ .ft P .fi .UNINDENT .UNINDENT .sp To view a list of roles and environments on the Chef server: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife list roles/ environments/ .ft P .fi .UNINDENT .UNINDENT .sp To view a list of absolutely everything on the Chef server: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife list \-R / .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-node.1000066400000000000000000000317531276456504500212050ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-NODE" "1" "Chef 12.0" "" "knife node" .SH NAME knife-node \- The man page for the knife node subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp A node is any physical, virtual, or cloud machine that is configured to be maintained by a chef\-client\&. .sp The \fBknife node\fP subcommand is used to manage the nodes that exist on a Chef server\&. .SH COMMON OPTIONS .sp The following options may be used with any of the arguments available to the \fBknife node\fP subcommand: .INDENT 0.0 .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH BULK DELETE .sp The \fBbulk delete\fP argument is used to delete one or more nodes that match a pattern defined by a regular expression. The regular expression must be within quotes and not be surrounded by forward slashes (/). .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node bulk delete REGEX .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp Use a regular expression to define the pattern used to bulk delete nodes: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node bulk delete "^[0\-9]{3}$" .ft P .fi .UNINDENT .UNINDENT .sp Type \fBY\fP to confirm a deletion. .SH CREATE .sp The \fBcreate\fP argument is used to add a node to the Chef server\&. Node data is stored as JSON on the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node create NODE_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To add a node, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node create node1 .ft P .fi .UNINDENT .UNINDENT .sp In the $EDITOR enter the node data in JSON: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C ## sample: { "normal": { }, "name": "foobar", "override": { }, "default": { }, "json_class": "Chef::Node", "automatic": { }, "run_list": [ "recipe[zsh]", "role[webserver]" ], "chef_type": "node" } .ft P .fi .UNINDENT .UNINDENT .sp When finished, save it. .SH DELETE .sp The \fBdelete\fP argument is used to delete a node from the Chef server\&. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 Deleting a node will not delete any corresponding API clients. .UNINDENT .UNINDENT .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node delete NODE_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node delete node_name .ft P .fi .UNINDENT .UNINDENT .SH EDIT .sp The \fBedit\fP argument is used to edit the details of a node on a Chef server\&. Node data is stored as JSON on the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node edit NODE_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-all\fP Displays a node in the $EDITOR\&. By default, attributes that are default, override, or automatic are not shown. .UNINDENT .sp \fBExamples\fP .sp To edit the data for a node named \fBnode1\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node edit node1 \-a .ft P .fi .UNINDENT .UNINDENT .sp Update the role data in JSON: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C ## sample: { "normal": { }, "name": "node1", "override": { }, "default": { }, "json_class": "Chef::Node", "automatic": { }, "run_list": [ "recipe[devops]", "role[webserver]" ], "chef_type": "node" } .ft P .fi .UNINDENT .UNINDENT .sp When finished, save it. .SH FROM FILE .sp The \fBfrom file\fP argument is used to create a node using existing node data as a template. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node from file FILE .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To add a node using data contained in a JSON file: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node from file "path to JSON file" .ft P .fi .UNINDENT .UNINDENT .SH LIST .sp The \fBlist\fP argument is used to view all of the nodes that exist on a Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node list (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-w\fP, \fB\-\-with\-uri\fP Use to show the corresponding URIs. .UNINDENT .sp \fBExamples\fP .sp To verify the list of nodes that are registered with the Chef server, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node list .ft P .fi .UNINDENT .UNINDENT .sp to return something similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C i\-12345678 rs\-123456 .ft P .fi .UNINDENT .UNINDENT .SH RUN_LIST ADD .sp The \fBrun_list add\fP argument is used to add run\-list items (roles or recipes) to a node. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node run_list add NODE_NAME RUN_LIST_ITEM (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a ITEM\fP, \fB\-\-after ITEM\fP Use this to add the run list item after the specified run list item. .TP .B \fB\-b ITEM\fP, \fB\-\-before ITEM\fP Use this to add the run list item before the specified run list item. .UNINDENT .sp \fBExamples\fP .sp To add a role to a run\-list, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node run_list add node \(aqrole[ROLE_NAME]\(aq .ft P .fi .UNINDENT .UNINDENT .sp To add roles and recipes to a run\-list, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node run_list add node \(aqrecipe[COOKBOOK::RECIPE_NAME],recipe[COOKBOOK::RECIPE_NAME],role[ROLE_NAME]\(aq .ft P .fi .UNINDENT .UNINDENT .sp To add a recipe to a run\-list using the fully qualified format, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node run_list add node \(aqrecipe[COOKBOOK::RECIPE_NAME]\(aq .ft P .fi .UNINDENT .UNINDENT .sp To add a recipe to a run\-list using the cookbook format, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node run_list add node \(aqCOOKBOOK::RECIPE_NAME\(aq .ft P .fi .UNINDENT .UNINDENT .sp To add the default recipe of a cookbook to a run\-list, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node run_list add node \(aqCOOKBOOK\(aq .ft P .fi .UNINDENT .UNINDENT .SH RUN_LIST REMOVE .sp The \fBrun_list remove\fP argument is used to remove run\-list items (roles or recipes) from a node. A recipe must be in one of the following formats: fully qualified, cookbook, or default. Both roles and recipes must be in quotes, for example: \fB\(aqrole[ROLE_NAME]\(aq\fP or \fB\(aqrecipe[COOKBOOK::RECIPE_NAME]\(aq\fP\&. Use a comma to separate roles and recipes when removing more than one, like this: \fB\(aqrecipe[COOKBOOK::RECIPE_NAME],COOKBOOK::RECIPE_NAME,role[ROLE_NAME]\(aq\fP\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node run_list remove NODE_NAME RUN_LIST_ITEM .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To remove a role from a run\-list, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node run_list remove node \(aqrole[ROLE_NAME]\(aq .ft P .fi .UNINDENT .UNINDENT .sp To remove a recipe from a run\-list using the fully qualified format, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node run_list remove node \(aqrecipe[COOKBOOK::RECIPE_NAME]\(aq .ft P .fi .UNINDENT .UNINDENT .SH SHOW .sp The \fBshow\fP argument is used to display information about a node. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node show NODE_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP The attribute (or attributes) to show. .TP .B \fB\-l\fP, \fB\-\-long\fP Use to display all attributes in the output and to show the output as JSON\&. .TP .B \fB\-m\fP, \fB\-\-medium\fP Use to display normal attributes in the output and to show the output as JSON\&. .TP .B \fB\-r\fP, \fB\-\-run\-list\fP Use to show only the run\-list. .UNINDENT .sp \fBExamples\fP .sp To view all data for a node named \fBbuild\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node show build .ft P .fi .UNINDENT .UNINDENT .sp to return: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C Node Name: build Environment: _default FQDN: IP: Run List: Roles: Recipes: Platform: .ft P .fi .UNINDENT .UNINDENT .sp To show basic information about a node, truncated and nicely formatted: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife node show .ft P .fi .UNINDENT .UNINDENT .sp To show all information about a node, nicely formatted: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife node show \-l .ft P .fi .UNINDENT .UNINDENT .sp To list a single node attribute: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife node show \-a .ft P .fi .UNINDENT .UNINDENT .sp where \fB\fP is something like kernel or platform. (This doesn\(aqt work for nested attributes like \fBnode[kernel][machine]\fP because \fBknife node show\fP doesn\(aqt understand nested attributes.) .sp To view the FQDN for a node named \fBi\-12345678\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node show i\-12345678 \-a fqdn .ft P .fi .UNINDENT .UNINDENT .sp to return: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C fqdn: ip\-10\-251\-75\-20.ec2.internal .ft P .fi .UNINDENT .UNINDENT .sp To view the run list for a node named \fBdev\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife node show dev \-r .ft P .fi .UNINDENT .UNINDENT .sp To view information in JSON format, use the \fB\-F\fP common option as part of the command like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role show devops \-F json .ft P .fi .UNINDENT .UNINDENT .sp Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP\&. .sp To view node information in raw JSON, use the \fB\-l\fP or \fB\-\-long\fP option: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife node show \-l \-F json .ft P .fi .UNINDENT .UNINDENT .sp and/or: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife node show \-l \-\-format=json .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-raw.1000066400000000000000000000101221276456504500210340ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-RAW" "1" "Chef 12.0" "" "knife raw" .SH NAME knife-raw \- The man page for the knife raw subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife raw\fP subcommand is used to send a REST request to an endpoint in the Chef server API\&. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife raw REQUEST_PATH (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-i FILE\fP, \fB\-\-input FILE\fP The name of a file to be used with the \fBPUT\fP or a \fBPOST\fP request. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-m METHOD\fP, \fB\-\-method METHOD\fP The request method: \fBDELETE\fP, \fBGET\fP, \fBPOST\fP, or \fBPUT\fP\&. Default value: \fBGET\fP\&. .TP .B \fB\-\-[no\-]pretty\fP Use \fB\-\-no\-pretty\fP to disable pretty\-print output for JSON\&. Default: \fB\-\-pretty\fP\&. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp To view information about a client: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife raw /clients/ .ft P .fi .UNINDENT .UNINDENT .sp To view information about a node: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife raw /nodes/ .ft P .fi .UNINDENT .UNINDENT .sp To delete a data bag, enter a command similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife raw \-m DELETE /data/foo .ft P .fi .UNINDENT .UNINDENT .sp to return something similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "name":"foo", "json_class":"Chef::DataBag", "chef_type":"data_bag" } .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-recipe-list.1000066400000000000000000000032711276456504500224720ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-RECIPE-LIST" "1" "Chef 12.0" "" "knife recipe list" .SH NAME knife-recipe-list \- The man page for the knife recipe list subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife recipe list\fP subcommand is used to view all of the recipes that are on a Chef server\&. A regular expression can be used to limit the results to recipes that match a specific pattern. The regular expression must be within quotes and not be surrounded by forward slashes (/). .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife recipe list REGEX .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To view a list of recipes: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife recipe list \(aqcouchdb::*\(aq .ft P .fi .UNINDENT .UNINDENT .sp to return: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C couchdb::main_monitors couchdb::master couchdb::default couchdb::org_cleanu .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-role.1000066400000000000000000000215471276456504500212210ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-ROLE" "1" "Chef 12.0" "" "knife role" .SH NAME knife-role \- The man page for the knife role subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp A role is a way to define certain patterns and processes that exist across nodes in an organization as belonging to a single job function. Each role consists of zero (or more) attributes and a run\-list. Each node can have zero (or more) roles assigned to it. When a role is run against a node, the configuration details of that node are compared against the attributes of the role, and then the contents of that role\(aqs run\-list are applied to the node\(aqs configuration details. When a chef\-client runs, it merges its own attributes and run\-lists with those contained within each assigned role. .sp The \fBknife role\fP subcommand is used to manage the roles that are associated with one or more nodes on a Chef server\&. .SH COMMON OPTIONS .sp The following options may be used with any of the arguments available to the \fBknife role\fP subcommand: .INDENT 0.0 .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH BULK DELETE .sp The \fBbulk delete\fP argument is used to delete one or more roles that match a pattern defined by a regular expression. The regular expression must be within quotes and not be surrounded by forward slashes (/). .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role bulk delete REGEX .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp Use a regular expression to define the pattern used to bulk delete roles: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role bulk delete "^[0\-9]{3}$" .ft P .fi .UNINDENT .UNINDENT .SH CREATE .sp The \fBcreate\fP argument is used to add a role to the Chef server\&. Role data is saved as JSON on the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role create ROLE_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-d DESCRIPTION\fP, \fB\-\-description DESCRIPTION\fP The description of the role. This value will populate the description field for the role on the Chef server\&. .UNINDENT .sp \fBExamples\fP .sp To add a role named \fBrole1\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role create role1 .ft P .fi .UNINDENT .UNINDENT .sp In the $EDITOR enter the role data in JSON: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C ## sample: { "name": "role1", "default_attributes": { }, "json_class": "Chef::Role", "run_list": [\(aqrecipe[cookbook_name::recipe_name], role[role_name]\(aq ], "description": "", "chef_type": "role", "override_attributes": { } } .ft P .fi .UNINDENT .UNINDENT .sp When finished, save it. .SH DELETE .sp The \fBdelete\fP argument is used to delete a role from the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role delete ROLE_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role delete devops .ft P .fi .UNINDENT .UNINDENT .sp Type \fBY\fP to confirm a deletion. .SH EDIT .sp The \fBedit\fP argument is used to edit role details on the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role edit ROLE_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To edit the data for a role named \fBrole1\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role edit role1 .ft P .fi .UNINDENT .UNINDENT .sp Update the role data in JSON: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C ## sample: { "name": "role1", "default_attributes": { }, "json_class": "Chef::Role", "run_list": [\(aqrecipe[cookbook_name::recipe_name], role[role_name]\(aq ], "description": "This is the description for the role1 role.", "chef_type": "role", "override_attributes": { } } .ft P .fi .UNINDENT .UNINDENT .sp When finished, save it. .SH FROM FILE .sp The \fBfrom file\fP argument is used to create a role using existing JSON data as a template. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role from file FILE .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To view role details based on the values contained in a JSON file: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role from file "path to JSON file" .ft P .fi .UNINDENT .UNINDENT .SH LIST .sp The \fBlist\fP argument is used to view a list of roles that are currently available on the Chef server\&. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role list .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-w\fP, \fB\-\-with\-uri\fP Use to show the corresponding URIs. .UNINDENT .sp \fBExamples\fP .sp To view a list of roles on the Chef server and display the URI for each role returned, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role list \-w .ft P .fi .UNINDENT .UNINDENT .SH SHOW .sp The \fBshow\fP argument is used to view the details of a role. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role show ROLE_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP The attribute (or attributes) to show. .UNINDENT .sp \fBExamples\fP .sp To view information in JSON format, use the \fB\-F\fP common option as part of the command like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role show devops \-F json .ft P .fi .UNINDENT .UNINDENT .sp Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP\&. .sp To view information in JSON format, use the \fB\-F\fP common option as part of the command like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife role show devops \-F json .ft P .fi .UNINDENT .UNINDENT .sp Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP\&. .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-search.1000066400000000000000000000212471276456504500215220ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-SEARCH" "1" "Chef 12.0" "" "knife search" .SH NAME knife-search \- The man page for the knife search subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp Search indexes allow queries to be made for any type of data that is indexed by the Chef server, including data bags (and data bag items), environments, nodes, and roles. A defined query syntax is used to support search patterns like exact, wildcard, range, and fuzzy. A search is a full\-text query that can be done from several locations, including from within a recipe, by using the \fBsearch\fP subcommand in knife, the \fBsearch\fP method in the Recipe DSL, and by using the \fB/search\fP or \fB/search/INDEX\fP endpoints in the Chef server API\&. The search engine is based on Apache Solr and is run from the Chef server\&. .sp The \fBknife search\fP subcommand is used run a search query for information that is indexed on a Chef server\&. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search INDEX SEARCH_QUERY .ft P .fi .UNINDENT .UNINDENT .sp where \fBINDEX\fP is one of \fBclient\fP, \fBenvironment\fP, \fBnode\fP, \fBrole\fP, or the name of a data bag and \fBSEARCH_QUERY\fP is the search query syntax for the query that will be executed. .sp \fBINDEX\fP is implied if omitted, and will default to \fBnode\fP\&. For example: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search \(aq*:*\(aq \-i .ft P .fi .UNINDENT .UNINDENT .sp will return something similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 8 items found centos\-62\-dev opensuse\-1203 ubuntu\-1304\-dev ubuntu\-1304\-orgtest ubuntu\-1204\-ohai\-test ubuntu\-1304\-ifcfg\-test ohai\-test win2k8\-dev .ft P .fi .UNINDENT .UNINDENT .sp and is the same search as: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search node \(aq*:*" \-i .ft P .fi .UNINDENT .UNINDENT .sp If the \fBSEARCH_QUERY\fP does not contain a colon character (\fB:\fP), then the default query pattern is \fBtags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}*\fP, which means the following two search queries are effectively the same: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search ubuntu .ft P .fi .UNINDENT .UNINDENT .sp or: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search node "tags:*ubuntu* OR roles:*ubuntu* OR fqdn:*ubuntu* (etc.)" .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This sub\-command has the following options: .INDENT 0.0 .TP .B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP The attribute (or attributes) to show. .TP .B \fB\-b ROW\fP, \fB\-\-start ROW\fP The row at which return results will begin. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-i\fP, \fB\-\-id\-only\fP Use to show only matching object IDs. .TP .B \fBINDEX\fP The name of the index to be queried: \fBclient\fP, \fBenvironment\fP, \fBnode\fP, \fBrole\fP, or \fBDATA_BAG_NAME\fP\&. Default index: \fBnode\fP\&. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-l\fP, \fB\-\-long\fP Use to display all attributes in the output and to show the output as JSON\&. .TP .B \fB\-m\fP, \fB\-\-medium\fP Use to display normal attributes in the output and to show the output as JSON\&. .TP .B \fB\-o SORT\fP, \fB\-\-sort SORT\fP The order in which search results will be sorted. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-q SEARCH_QUERY\fP, \fB\-\-query SEARCH_QUERY\fP Use to protect search queries that start with a hyphen (\-). A \fB\-q\fP query may be specified as an argument or an option, but not both. .TP .B \fB\-r\fP, \fB\-\-run\-list\fP Use to show only the run\-list. .TP .B \fB\-R INT\fP, \fB\-\-rows INT\fP The number of rows to be returned. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fBSEARCH_QUERY\fP The search query used to identify a a list of items on a Chef server\&. This option uses the same syntax as the \fBsearch\fP sub\-command. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp To search for the IDs of all nodes running on the Amazon EC2 platform, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search node \(aqec2:*\(aq \-i .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 4 items found ip\-0A7CA19F.ec2.internal ip\-0A58CF8E.ec2.internal ip\-0A58E134.ec2.internal ip\-0A7CFFD5.ec2.internal .ft P .fi .UNINDENT .UNINDENT .sp To search for the instance type (flavor) of all nodes running on the Amazon EC2 platform, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search node \(aqec2:*\(aq \-a ec2.instance_type .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 4 items found ec2.instance_type: m1.large id: ip\-0A7CA19F.ec2.internal ec2.instance_type: m1.large id: ip\-0A58CF8E.ec2.internal ec2.instance_type: m1.large id: ip\-0A58E134.ec2.internal ec2.instance_type: m1.large id: ip\-0A7CFFD5.ec2.internal .ft P .fi .UNINDENT .UNINDENT .sp To search for all nodes running Ubuntu, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search node \(aqplatform:ubuntu\(aq .ft P .fi .UNINDENT .UNINDENT .sp To search for all nodes running CentOS in the production environment, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search node \(aqchef_environment:production AND platform:centos\(aq .ft P .fi .UNINDENT .UNINDENT .sp To find a nested attribute, use a pattern similar to the following: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search node \-a . .ft P .fi .UNINDENT .UNINDENT .sp To build a search query to use more than one attribute, use an underscore (\fB_\fP) to separate each attribute. For example, the following query will search for all nodes running a specific version of Ruby: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search node "languages_ruby_version:1.9.3" .ft P .fi .UNINDENT .UNINDENT .sp To build a search query that can find a nested attribute: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search node name: \-a kernel.machine .ft P .fi .UNINDENT .UNINDENT .sp To test a search query that will be used in a \fBknife ssh\fP command: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife search node "role:web NOT name:web03" .ft P .fi .UNINDENT .UNINDENT .sp where the query in the previous example will search all servers that have the \fBweb\fP role, but not on the server named \fBweb03\fP\&. .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-serve.1000066400000000000000000000070001276456504500213700ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-SERVE" "1" "Chef 12.0" "" "knife serve" .SH NAME knife-serve \- The man page for the knife serve subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife serve\fP subcommand is used to run a persistent chef\-zero against the local chef\-repo\&. (chef\-zero is a lightweight Chef server that runs in\-memory on the local machine.) This is the same as running the chef\-client executable with the \fB\-\-local\-mode\fP option. The \fBchef_repo_path\fP is located automatically and the Chef server will bind to the first available port between \fB8889\fP and \fB9999\fP\&. \fBknife serve\fP will print the URL for the local Chef server, so that it may be added to the knife.rb file. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife serve (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-show.1000066400000000000000000000110541276456504500212300ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-SHOW" "1" "Chef 12.0" "" "knife show" .SH NAME knife-show \- The man page for the knife show subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife show\fP subcommand is used to view the details of one (or more) objects on the Chef server\&. This subcommand works similar to \fBknife cookbook show\fP, \fBknife data bag show\fP, \fBknife environment show\fP, \fBknife node show\fP, and \fBknife role show\fP, but with a single verb (and a single action). .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife show [PATTERN...] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP The attribute (or attributes) to show. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-repo\-path PATH\fP The path to the chef\-repo\&. This setting will override the default path to the chef\-repo\&. Default: same as specified by \fBchef_repo_path\fP in config.rb. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-concurrency\fP The number of allowed concurrent connections. Default: \fB10\fP\&. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-local\fP Use to show local files instead of remote files. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-\-repo\-mode MODE\fP The layout of the local chef\-repo\&. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP\&. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP\&. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp To show all cookbooks in the \fBcookbooks/\fP directory: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife show cookbooks/ .ft P .fi .UNINDENT .UNINDENT .sp or, (if already in the \fBcookbooks/\fP directory in the local chef\-repo): .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife show .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife show roles/ environments/ .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-ssh.1000066400000000000000000000247001276456504500210470ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-SSH" "1" "Chef 12.0" "" "knife ssh" .SH NAME knife-ssh \- The man page for the knife ssh subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife ssh\fP subcommand is used to invoke SSH commands (in parallel) on a subset of nodes within an organization, based on the results of a \fI\%search query\fP made to the Chef server\&. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssh SEARCH_QUERY SSH_COMMAND (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-a SSH_ATTR\fP, \fB\-\-attribute SSH_ATTR\fP The attribute that is used when opening the SSH connection. The default attribute is the FQDN of the host. Other possible values include a public IP address, a private IP address, or a hostname. .TP .B \fB\-A\fP, \fB\-\-forward\-agent\fP Use to enable SSH agent forwarding. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-C NUM\fP, \fB\-\-concurrency NUM\fP The number of allowed concurrent connections. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-G GATEWAY\fP, \fB\-\-ssh\-gateway GATEWAY\fP The SSH tunnel or gateway that is used to run a bootstrap action on a machine that is not accessible from the workstation. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-i IDENTITY_FILE\fP, \fB\-\-identity\-file IDENTIFY_FILE\fP The SSH identity file used for authentication. Key\-based authentication is recommended. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-m\fP, \fB\-\-manual\-list\fP Use to define a search query as a space\-separated list of servers. If there is more than one item in the list, put quotes around the entire list. For example: \fB\-\-manual\-list "server01 server 02 server 03"\fP .TP .B \fB\-\-[no\-]host\-key\-verify\fP Use \fB\-\-no\-host\-key\-verify\fP to disable host key verification. Default setting: \fB\-\-host\-key\-verify\fP\&. .TP .B \fBOTHER\fP The shell type. Possible values: \fBinteractive\fP, \fBscreen\fP, \fBtmux\fP, \fBmacterm\fP, or \fBcssh\fP\&. (\fBcsshx\fP is deprecated in favor of \fBcssh\fP\&.) .TP .B \fB\-p PORT\fP, \fB\-\-ssh\-port PORT\fP The SSH port. .TP .B \fB\-P PASSWORD\fP, \fB\-\-ssh\-password PASSWORD\fP The SSH password. This can be used to pass the password directly on the command line. If this option is not specified (and a password is required) knife will prompt for the password. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fBSEARCH_QUERY\fP The search query used to return a list of servers to be accessed using SSH and the specified \fBSSH_COMMAND\fP\&. This option uses the same syntax as the search sub\-command. .TP .B \fBSSH_COMMAND\fP The command that will be run against the results of a search query. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-x USER_NAME\fP, \fB\-\-ssh\-user USER_NAME\fP The SSH user name. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp To find the uptime of all of web servers running Ubuntu on the Amazon EC2 platform, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssh "role:web" "uptime" \-x ubuntu \-a ec2.public_hostname .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C ec2\-174\-129\-127\-206.compute\-1.amazonaws.com 13:50:47 up 1 day, 23:26, 1 user, load average: 0.25, 0.18, 0.11 ec2\-67\-202\-63\-102.compute\-1.amazonaws.com 13:50:47 up 1 day, 23:33, 1 user, load average: 0.12, 0.13, 0.10 ec2\-184\-73\-9\-250.compute\-1.amazonaws.com 13:50:48 up 16:45, 1 user, load average: 0.30, 0.22, 0.13 ec2\-75\-101\-240\-230.compute\-1.amazonaws.com 13:50:48 up 1 day, 22:59, 1 user, load average: 0.24, 0.17, 0.11 ec2\-184\-73\-60\-141.compute\-1.amazonaws.com 13:50:48 up 1 day, 23:30, 1 user, load average: 0.32, 0.17, 0.15 .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssh \(aqname:*\(aq \(aqsudo chef\-client\(aq .ft P .fi .UNINDENT .UNINDENT .sp To force a chef\-client run on all of the web servers running Ubuntu on the Amazon EC2 platform, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssh "role:web" "sudo chef\-client" \-x ubuntu \-a ec2.public_hostname .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C ec2\-67\-202\-63\-102.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:37 +0000] INFO: Starting Chef Run (Version 0.9.10) ec2\-174\-129\-127\-206.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:37 +0000] INFO: Starting Chef Run (Version 0.9.10) ec2\-184\-73\-9\-250.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:38 +0000] INFO: Starting Chef Run (Version 0.9.10) ec2\-75\-101\-240\-230.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:38 +0000] INFO: Starting Chef Run (Version 0.9.10) ec2\-184\-73\-60\-141.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:38 +0000] INFO: Starting Chef Run (Version 0.9.10) ec2\-174\-129\-127\-206.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Chef Run complete in 1.419243 seconds ec2\-174\-129\-127\-206.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: cleaning the checksum cache ec2\-174\-129\-127\-206.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Running report handlers ec2\-174\-129\-127\-206.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Report handlers complete ec2\-67\-202\-63\-102.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Chef Run complete in 1.578265 seconds ec2\-67\-202\-63\-102.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: cleaning the checksum cache ec2\-67\-202\-63\-102.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Running report handlers ec2\-67\-202\-63\-102.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Report handlers complete ec2\-184\-73\-9\-250.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Chef Run complete in 1.638884 seconds ec2\-184\-73\-9\-250.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: cleaning the checksum cache ec2\-184\-73\-9\-250.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Running report handlers ec2\-184\-73\-9\-250.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Report handlers complete ec2\-75\-101\-240\-230.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Chef Run complete in 1.540257 seconds ec2\-75\-101\-240\-230.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: cleaning the checksum cache ec2\-75\-101\-240\-230.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Running report handlers ec2\-75\-101\-240\-230.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Report handlers complete ec2\-184\-73\-60\-141.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Chef Run complete in 1.502489 seconds ec2\-184\-73\-60\-141.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: cleaning the checksum cache ec2\-184\-73\-60\-141.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Running report handlers ec2\-184\-73\-60\-141.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Report handlers complete .ft P .fi .UNINDENT .UNINDENT .sp To query for all nodes that have the \fBwebserver\fP role and then use SSH to run the command \fBsudo chef\-client\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssh "role:webserver" "sudo chef\-client" .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssh name:* "sudo aptitude upgrade \-y" .ft P .fi .UNINDENT .UNINDENT .sp To specify the shell type used on the nodes returned by a search query: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssh roles:opscode\-omnitruck macterm .ft P .fi .UNINDENT .UNINDENT .sp where \fBscreen\fP is one of the following values: \fBcssh\fP, \fBinteractive\fP, \fBmacterm\fP, \fBscreen\fP, or \fBtmux\fP\&. If the node does not have the shell type installed, knife will return an error similar to the following: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C you need the rb\-appscript gem to use knife ssh macterm. \(ga(sudo) gem install rb\-appscript\(ga to install ERROR: LoadError: cannot load such file \-\- appscript .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-ssl-check.1000066400000000000000000000145331276456504500221310ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-SSL-CHECK" "1" "Chef 12.0" "" "knife ssl check" .SH NAME knife-ssl-check \- The man page for the knife ssl check subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife ssl check\fP subcommand is used to verify the SSL configuration for the Enterprise Chef and/or Open Source Chef servers, or at another location specified by a URL or URI. .sp \fBWARNING:\fP .INDENT 0.0 .INDENT 3.5 When verification of a remote server\(aqs SSL certificate is disabled, the chef\-client will issue a warning similar to "SSL validation of HTTPS requests is disabled. HTTPS connections are still encrypted, but the chef\-client is not able to detect forged replies or man\-in\-the\-middle attacks." To configure SSL for the chef\-client, set \fBssl_verify_mode\fP to \fB:verify_peer\fP (recommended) \fBor\fP \fBverify_api_cert\fP to \fBtrue\fP in the client.rb file. .UNINDENT .UNINDENT .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssl check URI .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-a SSH_ATTR\fP, \fB\-\-attribute SSH_ATTR\fP The attribute that is used when opening the SSH connection. The default attribute is the FQDN of the host. Other possible values include a public IP address, a private IP address, or a hostname. .TP .B \fB\-A\fP, \fB\-\-forward\-agent\fP Use to enable SSH agent forwarding. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-C NUM\fP, \fB\-\-concurrency NUM\fP The number of allowed concurrent connections. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-G GATEWAY\fP, \fB\-\-ssh\-gateway GATEWAY\fP The SSH tunnel or gateway that is used to run a bootstrap action on a machine that is not accessible from the workstation. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-i IDENTITY_FILE\fP, \fB\-\-identity\-file IDENTIFY_FILE\fP The SSH identity file used for authentication. Key\-based authentication is recommended. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-m\fP, \fB\-\-manual\-list\fP Use to define a search query as a space\-separated list of servers. If there is more than one item in the list, put quotes around the entire list. For example: \fB\-\-manual\-list "server01 server 02 server 03"\fP .TP .B \fB\-\-[no\-]host\-key\-verify\fP Use \fB\-\-no\-host\-key\-verify\fP to disable host key verification. Default setting: \fB\-\-host\-key\-verify\fP\&. .TP .B \fBOTHER\fP The shell type. Possible values: \fBinteractive\fP, \fBscreen\fP, \fBtmux\fP, \fBmacterm\fP, or \fBcssh\fP\&. (\fBcsshx\fP is deprecated in favor of \fBcssh\fP\&.) .TP .B \fB\-p PORT\fP, \fB\-\-ssh\-port PORT\fP The SSH port. .TP .B \fB\-P PASSWORD\fP, \fB\-\-ssh\-password PASSWORD\fP The SSH password. This can be used to pass the password directly on the command line. If this option is not specified (and a password is required) knife will prompt for the password. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fBSEARCH_QUERY\fP The search query used to return a list of servers to be accessed using SSH and the specified \fBSSH_COMMAND\fP\&. This option uses the same syntax as the search sub\-command. .TP .B \fBSSH_COMMAND\fP The command that will be run against the results of a search query. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-x USER_NAME\fP, \fB\-\-ssh\-user USER_NAME\fP The SSH user name. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp The following examples show how to use this knife subcommand: .sp \fBVerify the SSL configuration for the Chef server\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssl check .ft P .fi .UNINDENT .UNINDENT .sp \fBVerify the SSL configuration for the chef\-client\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssl check \-c /etc/chef/client.rb .ft P .fi .UNINDENT .UNINDENT .sp \fBVerify an external server\(aqs SSL certificate\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssl check URL_or_URI .ft P .fi .UNINDENT .UNINDENT .sp for example: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssl check https://www.getchef.com .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-ssl-fetch.1000066400000000000000000000152331276456504500221430ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-SSL-FETCH" "1" "Chef 12.0" "" "knife ssl fetch" .SH NAME knife-ssl-fetch \- The man page for the knife ssl fetch subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife ssl fetch\fP subcommand is used to copy SSL certificates from an HTTPS server to the \fBtrusted_certs_dir\fP directory that is used by knife and the chef\-client to store trusted SSL certificates. When these certificates match the hostname of the remote server, running \fBknife ssl fetch\fP is the only step required to verify a remote server that is accessed by either knife or the chef\-client\&. .sp \fBWARNING:\fP .INDENT 0.0 .INDENT 3.5 It is the user\(aqs responsibility to verify the authenticity of every SSL certificate before downloading it to the \fBtrusted_certs_dir\fP directory. knife will use any certificate in that directory as if it is a 100% trusted and authentic SSL certificate. knife will not be able to determine if any certificate in this directory has been tampered with, is forged, malicious, or otherwise harmful. Therefore it is essential that users take the proper steps before downloading certificates into this directory. .UNINDENT .UNINDENT .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssl fetch URI_FOR_HTTPS_SERVER .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-a SSH_ATTR\fP, \fB\-\-attribute SSH_ATTR\fP The attribute that is used when opening the SSH connection. The default attribute is the FQDN of the host. Other possible values include a public IP address, a private IP address, or a hostname. .TP .B \fB\-A\fP, \fB\-\-forward\-agent\fP Use to enable SSH agent forwarding. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-C NUM\fP, \fB\-\-concurrency NUM\fP The number of allowed concurrent connections. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-G GATEWAY\fP, \fB\-\-ssh\-gateway GATEWAY\fP The SSH tunnel or gateway that is used to run a bootstrap action on a machine that is not accessible from the workstation. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-i IDENTITY_FILE\fP, \fB\-\-identity\-file IDENTIFY_FILE\fP The SSH identity file used for authentication. Key\-based authentication is recommended. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-m\fP, \fB\-\-manual\-list\fP Use to define a search query as a space\-separated list of servers. If there is more than one item in the list, put quotes around the entire list. For example: \fB\-\-manual\-list "server01 server 02 server 03"\fP .TP .B \fB\-\-[no\-]host\-key\-verify\fP Use \fB\-\-no\-host\-key\-verify\fP to disable host key verification. Default setting: \fB\-\-host\-key\-verify\fP\&. .TP .B \fBOTHER\fP The shell type. Possible values: \fBinteractive\fP, \fBscreen\fP, \fBtmux\fP, \fBmacterm\fP, or \fBcssh\fP\&. (\fBcsshx\fP is deprecated in favor of \fBcssh\fP\&.) .TP .B \fB\-p PORT\fP, \fB\-\-ssh\-port PORT\fP The SSH port. .TP .B \fB\-P PASSWORD\fP, \fB\-\-ssh\-password PASSWORD\fP The SSH password. This can be used to pass the password directly on the command line. If this option is not specified (and a password is required) knife will prompt for the password. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fBSEARCH_QUERY\fP The search query used to return a list of servers to be accessed using SSH and the specified \fBSSH_COMMAND\fP\&. This option uses the same syntax as the search sub\-command. .TP .B \fBSSH_COMMAND\fP The command that will be run against the results of a search query. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-x USER_NAME\fP, \fB\-\-ssh\-user USER_NAME\fP The SSH user name. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp The following examples show how to use this knife subcommand: .sp \fBFetch the SSL certificates used by Knife from the Chef server\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssl fetch .ft P .fi .UNINDENT .UNINDENT .sp \fBFetch the SSL certificates used by the chef\-client from the Chef server\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssl fetch \-c /etc/chef/client.rb .ft P .fi .UNINDENT .UNINDENT .sp \fBFetch SSL certificates from a URL or URI\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssl fetch URL_or_URI .ft P .fi .UNINDENT .UNINDENT .sp for example: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife ssl fetch https://www.getchef.com .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-status.1000066400000000000000000000153561276456504500216040ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-STATUS" "1" "Chef 12.0" "" "knife status" .SH NAME knife-status \- The man page for the knife status subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife status\fP subcommand is used to display a brief summary of the nodes on a Chef server, including the time of the most recent successful chef\-client run. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife status (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-H\fP, \fB\-\-hide\-healthy\fP Use to hide nodes on which a chef\-client run has occurred within the previous hour. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fBSEARCH_QUERY\fP The search query used to identify a a list of items on a Chef server\&. This option uses the same syntax as the \fBsearch\fP sub\-command. .TP .B \fB\-r RUN_LIST\fP, \fB\-\-run\-list RUN_LIST\fP A comma\-separated list of roles and/or recipes to be applied. .TP .B \fB\-s\fP, \fB\-\-sort\-reverse\fP Use to sort a list by last run time, descending. .TP .B \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp To include run lists in the status, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife status \-\-run\-list .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 20 hours ago, dev\-vm.chisamore.com, ubuntu 10.04, dev\-vm.chisamore.com, 10.66.44.126, role[lb]. 3 hours ago, i\-225f954f, ubuntu 10.04, ec2\-67\-202\-63\-102.compute\-1.amazonaws.com, 67.202.63.102, role[web]. 3 hours ago, i\-a45298c9, ubuntu 10.04, ec2\-174\-129\-127\-206.compute\-1.amazonaws.com, 174.129.127.206, role[web]. 3 hours ago, i\-5272a43f, ubuntu 10.04, ec2\-184\-73\-9\-250.compute\-1.amazonaws.com, 184.73.9.250, role[web]. 3 hours ago, i\-226ca64f, ubuntu 10.04, ec2\-75\-101\-240\-230.compute\-1.amazonaws.com, 75.101.240.230, role[web]. 3 hours ago, i\-f65c969b, ubuntu 10.04, ec2\-184\-73\-60\-141.compute\-1.amazonaws.com, 184.73.60.141, role[web]. .ft P .fi .UNINDENT .UNINDENT .sp To show the status for nodes on which the chef\-client did not run successfully within the past hour, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife status \-\-hide\-healthy .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 1 hour ago, i\-256f884f, ubuntu 12.04, ec2\-67\-202\-63\-102.compute\-1.amazonaws.com, 67.202.63.102, role[web]. 1 hour ago, i\-a47823c9, ubuntu 10.04, ec2\-174\-129\-127\-206.compute\-1.amazonaws.com, 184.129.143.111, role[lb]. .ft P .fi .UNINDENT .UNINDENT .sp To show the status of a subset of nodes that are returned by a specific query, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife status "role:web" \-\-run\-list .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 3 hours ago, i\-225f954f, ubuntu 10.04, ec2\-67\-202\-63\-102.compute\-1.amazonaws.com, 67.202.63.102, role[web]. 3 hours ago, i\-a45298c9, ubuntu 10.04, ec2\-174\-129\-127\-206.compute\-1.amazonaws.com, 174.129.127.206, role[web]. 3 hours ago, i\-5272a43f, ubuntu 10.04, ec2\-184\-73\-9\-250.compute\-1.amazonaws.com, 184.73.9.250, role[web]. 3 hours ago, i\-226ca64f, ubuntu 10.04, ec2\-75\-101\-240\-230.compute\-1.amazonaws.com, 75.101.240.230, role[web]. 3 hours ago, i\-f65c969b, ubuntu 10.04, ec2\-184\-73\-60\-141.compute\-1.amazonaws.com, 184.73.60.141, role[web]. .ft P .fi .UNINDENT .UNINDENT .sp To view the status of all nodes in the organization, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife status .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C 20 hours ago, dev\-vm.chisamore.com, ubuntu 10.04, dev\-vm.chisamore.com, 10.66.44.126 3 hours ago, i\-225f954f, ubuntu 10.04, ec2\-67\-202\-63\-102.compute\-1.amazonaws.com, 67.202.63.102 3 hours ago, i\-a45298c9, ubuntu 10.04, ec2\-174\-129\-127\-206.compute\-1.amazonaws.com, 174.129.127.206 3 hours ago, i\-5272a43f, ubuntu 10.04, ec2\-184\-73\-9\-250.compute\-1.amazonaws.com, 184.73.9.250 3 hours ago, i\-226ca64f, ubuntu 10.04, ec2\-75\-101\-240\-230.compute\-1.amazonaws.com, 75.101.240.230 3 hours ago, i\-f65c969b, ubuntu 10.04, ec2\-184\-73\-60\-141.compute\-1.amazonaws.com, 184.73.60.141 .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-tag.1000066400000000000000000000111221276456504500210170ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-TAG" "1" "Chef 12.0" "" "knife tag" .SH NAME knife-tag \- The man page for the knife tag subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp A tag is a custom description that is applied to a node. A tag, once applied, can be helpful when managing nodes using knife or when building recipes by providing alternate methods of grouping similar types of information. .sp The \fBknife tag\fP subcommand is used to apply tags to nodes on a Chef server\&. .SH COMMON OPTIONS .sp The following options may be used with any of the arguments available to the \fBknife tag\fP subcommand: .INDENT 0.0 .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH CREATE .sp The \fBcreate\fP argument is used to add one or more tags to a node. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife tag create NODE_NAME [TAG...] .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To create tags named \fBseattle\fP, \fBportland\fP, and \fBvancouver\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife tag create node seattle portland vancouver .ft P .fi .UNINDENT .UNINDENT .SH DELETE .sp The \fBdelete\fP argument is used to delete one or more tags from a node. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife tag delete NODE_NAME [TAG...] .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp To delete tags named \fBdenver\fP and \fBphoenix\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife tag delete node denver phoenix .ft P .fi .UNINDENT .UNINDENT .sp Type \fBY\fP to confirm a deletion. .SH LIST .sp The \fBlist\fP argument is used to list all of the tags that have been applied to a node. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife tag list [NODE_NAME...] .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-upload.1000066400000000000000000000155031276456504500215370ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-UPLOAD" "1" "Chef 12.0" "" "knife upload" .SH NAME knife-upload \- The man page for the knife upload subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife upload\fP subcommand is used to upload roles, cookbooks, environments, and data bags to the Chef server from the current working directory in the chef\-repo\&. This subcommand is often used in conjunction with \fBknife diff\fP, which can be used to see exactly what changes will be uploaded, and then \fBknife download\fP, which does the opposite of \fBknife upload\fP\&. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload [PATTERN...] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-repo\-path PATH\fP The path to the chef\-repo\&. This setting will override the default path to the chef\-repo\&. Default: same as specified by \fBchef_repo_path\fP in config.rb. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-concurrency\fP The number of allowed concurrent connections. Default: \fB10\fP\&. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-\-[no\-]diff\fP Use to upload only new and modified files. Set to \fBfalse\fP to upload all files. Default: \fBtrue\fP\&. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-\-[no\-]force\fP Use \fB\-\-force\fP to upload roles, cookbooks, etc. even if the file in the directory is identical (by default, no \fBPOST\fP or \fBPUT\fP is performed unless an actual change would be made). Default: \fB\-\-no\-force\fP\&. .TP .B \fB\-\-[no\-]freeze\fP Use to require changes to a cookbook be included as a new version. Only the \fB\-\-force\fP option can override this setting. Default: \fBfalse\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-n\fP, \fB\-\-dry\-run\fP Use to take no action and only print out results. Default: \fBfalse\fP\&. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-\-[no\-]purge\fP Use \fB\-\-purge\fP to delete roles, cookbooks, etc. from the Chef server if their corresponding files do not exist in the chef\-repo\&. By default, such objects are left alone and NOT purged. Default: \fB\-\-no\-purge\fP\&. .TP .B \fB\-\-[no\-]recurse\fP Use \fB\-\-no\-recurse\fP to disable uploading a directory recursively. Default: \fB\-\-recurse\fP\&. .TP .B \fB\-\-repo\-mode MODE\fP The layout of the local chef\-repo\&. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP\&. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP\&. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp Browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload .ft P .fi .UNINDENT .UNINDENT .sp or from anywhere in the chef\-repo, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload / .ft P .fi .UNINDENT .UNINDENT .sp Browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload cookbooks .ft P .fi .UNINDENT .UNINDENT .sp or from anywhere in the chef\-repo, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload /cookbooks .ft P .fi .UNINDENT .UNINDENT .sp Browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload environments .ft P .fi .UNINDENT .UNINDENT .sp or from anywhere in the chef\-repo, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload /environments .ft P .fi .UNINDENT .UNINDENT .sp Browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload environments/production.json .ft P .fi .UNINDENT .UNINDENT .sp or from the \fBenvironments/\fP directory, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload production.json .ft P .fi .UNINDENT .UNINDENT .sp Browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload roles .ft P .fi .UNINDENT .UNINDENT .sp or from anywhere in the chef\-repo, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload /roles .ft P .fi .UNINDENT .UNINDENT .sp Browse to the top level of the chef\-repo and enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload cookbooks/apache\e* roles/webserver.json .ft P .fi .UNINDENT .UNINDENT .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife upload \(gaknife deps nodes/*.json\(ga .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-user.1000066400000000000000000000177271276456504500212430ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-USER" "1" "Chef 12.0" "" "knife user" .SH NAME knife-user \- The man page for the knife user subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife user\fP subcommand is used to manage the list of users and their associated RSA public key\-pairs. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 This subcommand ONLY works when run against the open source Chef server version 10.x. This subcommand will NOT run against open source Chef server 11, Enterprise Chef (including hosted Enterprise Chef), or Private Chef\&. .UNINDENT .UNINDENT .SH COMMON OPTIONS .sp The following options may be used with any of the arguments available to the \fBknife user\fP subcommand: .INDENT 0.0 .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH CREATE .sp The \fBcreate\fP argument is used to create a user. This process will generate an RSA key pair for the named user. The public key will be stored on the Chef server and the private key will be displayed on \fBSTDOUT\fP or written to a named file. .INDENT 0.0 .IP \(bu 2 For the user, the private key should be copied to the system as \fB/etc/chef/client.pem\fP\&. .IP \(bu 2 For knife, the private key is typically copied to \fB~/.chef/client_name.pem\fP and referenced in the knife.rb configuration file. .UNINDENT .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user create USER_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a\fP, \fB\-\-admin\fP Use to create a client as an admin client. This is required for any user to access Open Source Chef as an administrator. This option only works when used with the open source Chef server and will have no effect when used with Enterprise Chef\&. .TP .B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP Use to save a private key to the specified file name. .TP .B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP The user password. .TP .B \fB\-\-user\-key FILE_NAME\fP All users are assigned a public key. Use to write the public key to a file. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user create "Radio Birdman" \-f /keys/user_name .ft P .fi .UNINDENT .UNINDENT .SH DELETE .sp The \fBdelete\fP argument is used to delete a registered user. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user delete USER_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user delete "Steve Danno" .ft P .fi .UNINDENT .UNINDENT .SH EDIT .sp The \fBedit\fP argument is used to edit the details of a user. When this argument is run, knife will open $EDITOR\&. When finished, knife will update the Chef server with those changes. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user edit USER_NAME .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This command does not have any specific options. .sp \fBExamples\fP .sp None. .SH LIST .sp The \fBlist\fP argument is used to view a list of registered users. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user list (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-w\fP, \fB\-\-with\-uri\fP Use to show the corresponding URIs. .UNINDENT .sp \fBExamples\fP .sp None. .SH REREGISTER .sp The \fBreregister\fP argument is used to regenerate an RSA key pair for a user. The public key will be stored on the Chef server and the private key will be displayed on \fBSTDOUT\fP or written to a named file. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 Running this argument will invalidate the previous RSA key pair, making it unusable during authentication to the Chef server\&. .UNINDENT .UNINDENT .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user reregister USER_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP Use to save a private key to the specified file name. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user reregister "Robert Younger" .ft P .fi .UNINDENT .UNINDENT .SH SHOW .sp The \fBshow\fP argument is used to show the details of a user. .sp \fBSyntax\fP .sp This argument has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user show USER_NAME (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This argument has the following options: .INDENT 0.0 .TP .B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP The attribute (or attributes) to show. .UNINDENT .sp \fBExamples\fP .sp To view a user named \fBDennis Teck\fP, enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user show "Dennis Teck" .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C chef_type: user json_class: Chef::User name: Dennis Teck public_key: .ft P .fi .UNINDENT .UNINDENT .sp To view information in JSON format, use the \fB\-F\fP common option as part of the command like this: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife user show "Dennis Teck" \-F json .ft P .fi .UNINDENT .UNINDENT .sp (Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP, e.g. \fB\-F yaml\fP for YAML\&.) .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife-xargs.1000066400000000000000000000137601276456504500214020ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE-XARGS" "1" "Chef 12.0" "" "knife xargs" .SH NAME knife-xargs \- The man page for the knife xargs subcommand. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp The \fBknife xargs\fP subcommand is used to take patterns from standard input, download as JSON, run a command against the downloaded JSON, and then upload any changes. .sp \fBSyntax\fP .sp This subcommand has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife xargs [PATTERN...] (options) .ft P .fi .UNINDENT .UNINDENT .sp \fBOptions\fP .sp This subcommand has the following options: .INDENT 0.0 .TP .B \fB\-0\fP Use to show a \fBNULL\fP character (\fB\e0\fP) instead of white space as the separator. Default: \fBfalse\fP\&. .TP .B \fB\-\-chef\-repo\-path PATH\fP The path to the chef\-repo\&. This setting will override the default path to the chef\-repo\&. Default: same as specified by \fBchef_repo_path\fP in config.rb. .TP .B \fB\-\-concurrency\fP The number of allowed concurrent connections. Default: \fB10\fP\&. .TP .B \fB\-\-[no\-]diff\fP Use to show a diff when a file changes. Default: \fB\-\-diff\fP\&. .TP .B \fB\-\-dry\-run\fP Use to prevent changes from being uploaded to the Chef server\&. Default: \fBfalse\fP\&. .TP .B \fB\-\-[no\-]force\fP Use to force the upload of files even if they haven\(aqt been changed. Default: \fB\-\-no\-force\fP\&. .TP .B \fB\-I REPLACE_STRING\fP, \fB\-\-replace REPLACE_STRING\fP Use to define a string that will be used to replace all occurrences of a file name. Default: \fBnil\fP\&. .TP .B \fB\-J REPLACE_STRING\fP, \fB\-\-replace\-first REPLACE_STRING\fP Use to define a string that will be used to replace the first occurrence of a file name. Default: \fBnil\fP\&. .TP .B \fB\-\-local\fP Use to build or execute a command line against a local file. Set to \fBfalse\fP to build or execute against a remote file. Default: \fBfalse\fP\&. .TP .B \fB\-n MAX_ARGS\fP, \fB\-\-max\-args MAX_ARGS\fP The maximum number of arguments per command line. Default: \fBnil\fP\&. .TP .B \fB\-p [PATTERN...]\fP, \fB\-\-pattern [PATTERN...]\fP One (or more) patterns for a command line. If this option is not specified, a list of patterns may be expected on standard input. Default: \fBnil\fP\&. .TP .B \fB\-\-repo\-mode MODE\fP The layout of the local chef\-repo\&. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP\&. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default value: \fBdefault\fP\&. .TP .B \fB\-s LENGTH\fP, \fB\-\-max\-chars LENGTH\fP The maximum size (in characters) for a command line. Default: \fBnil\fP\&. .TP .B \fB\-t\fP Use to run the print command on the command line. Default: \fBnil\fP\&. .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .sp \fBExamples\fP .sp The following examples show various ways of listing all nodes on the server, and then using Perl to replace \fBgrantmc\fP with \fBgmc\fP: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife list \(aqnodes/*\(aq | knife xargs "perl \-i \-pe \(aqs/grantmc/gmc\(aq" .ft P .fi .UNINDENT .UNINDENT .sp or without quotes and the backslash escaped: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife list /nodes/\e* | knife xargs "perl \-i \-pe \(aqs/grantmc/gmc\(aq" .ft P .fi .UNINDENT .UNINDENT .sp or by using the \fB\-\-pattern\fP option: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife xargs \-\-pattern \(aq/nodes.*\(aq "perl \-i \-pe \(aqs/grantmc/gmc\(aq" .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man1/knife.1000066400000000000000000000230651276456504500202570ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "KNIFE" "1" "Chef 12.0" "" "knife" .SH NAME knife \- The man page for the knife command line tool. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp knife is a command\-line tool that provides an interface between a local chef\-repo and the Chef server\&. knife helps users to manage: .INDENT 0.0 .IP \(bu 2 Nodes .IP \(bu 2 Cookbooks and recipes .IP \(bu 2 Roles .IP \(bu 2 Stores of JSON data (data bags), including encrypted data .IP \(bu 2 Environments .IP \(bu 2 Cloud resources, including provisioning .IP \(bu 2 The installation of the chef\-client on management workstations .IP \(bu 2 Searching of indexed data on the Chef server .UNINDENT .sp Knife subcommands: .INDENT 0.0 .IP \(bu 2 knife bootstrap .IP \(bu 2 knife client .IP \(bu 2 knife configure .IP \(bu 2 knife cookbook .IP \(bu 2 knife cookbook site .IP \(bu 2 knife data bag .IP \(bu 2 knife delete .IP \(bu 2 knife deps .IP \(bu 2 knife diff .IP \(bu 2 knife download .IP \(bu 2 knife edit .IP \(bu 2 knife environment .IP \(bu 2 knife exec .IP \(bu 2 knife list .IP \(bu 2 knife node .IP \(bu 2 knife raw .IP \(bu 2 knife recipe list .IP \(bu 2 knife role .IP \(bu 2 knife search .IP \(bu 2 knife show .IP \(bu 2 knife ssh .IP \(bu 2 knife status .IP \(bu 2 knife tag .IP \(bu 2 knife upload .IP \(bu 2 knife user .IP \(bu 2 knife xargs .UNINDENT .SH WORKING WITH KNIFE .sp knife runs from a management workstation and sits in\-between a Chef server and an organization\(aqs infrastructure. knife interacts with a Chef server by using the same REST API that is used by a chef\-client\&. Role\-based authentication controls (RBAC) can be used to authorize changes when knife is run with Enterprise Chef\&. knife is configured during workstation setup, but subsequent modifications can be made using the knife.rb configuration file. .SS Common Options .sp The following options can be run with all knife sub\-commands and plug\-ins: .INDENT 0.0 .TP .B \fB\-c CONFIG_FILE\fP, \fB\-\-config CONFIG_FILE\fP The configuration file to use. For example, when knife is run from a node that is configured to be managed by the Chef server, this option is used to allow knife to use the same credentials as the chef\-client when communicating with the Chef server\&. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. .TP .B \fB\-d\fP, \fB\-\-disable\-editing\fP Use to prevent the $EDITOR from being opened and to accept data as\-is. .TP .B \fB\-\-defaults\fP Use to have knife use the default value instead of asking a user to provide one. .TP .B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP The $EDITOR that is used for all interactive commands. .TP .B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP The name of the environment. When this option is added to a command, the command will run only against the named environment. This option is ignored during search queries made using the \fBknife search\fP subcommand. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP\&. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-k KEY\fP, \fB\-\-key KEY\fP The private key that knife will use to sign requests made by the API client to the Chef server\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. .TP .B \fB\-\-print\-after\fP Use to show data after a destructive operation. .TP .B \fB\-s URL\fP, \fB\-\-server\-url URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user name used by knife to sign requests made by the API client to the Chef server\&. Authentication will fail if the user name does not match the private key. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-V\fP, \fB\-\-verbose\fP Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity. .TP .B \fB\-y\fP, \fB\-\-yes\fP Use to respond to all confirmation prompts with "Yes". knife will not ask for confirmation. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SS JSON Data Format .sp Most data is entered using a text editor in JSON format, unless the \fB\-\-disable\-editing\fP option is entered as part of a command. (Encrypted data bags use YAML, which is a superset of JSON\&.) JSON is a common, language\-independent data format that provides a simple text representation of arbitrary data structures. For more information about JSON, see \fI\%http://www.json.org/\fP or \fI\%http://en.wikipedia.org/wiki/JSON\fP\&. .SS Set the Text Editor .sp Some knife commands, such as \fBknife data bag edit\fP, require that information be edited as JSON data using a text editor. For example, the following command: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ knife data bag edit admins admin_name .ft P .fi .UNINDENT .UNINDENT .sp will open up the text editor with data similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "id": "admin_name" } .ft P .fi .UNINDENT .UNINDENT .sp Changes to that file can then be made: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "id": "Justin C." "description": "I am passing the time by letting time pass over me ..." } .ft P .fi .UNINDENT .UNINDENT .sp The type of text editor that is used by knife can be configured by adding an entry to the knife.rb file or by setting an \fBEDITOR\fP environment variable. For example, to configure the text editor to always open with vim, add the following to the knife.rb file: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife[:editor] = "/usr/bin/vim" .ft P .fi .UNINDENT .UNINDENT .sp When a Microsoft Windows file path is enclosed in a double\-quoted string (" "), the same backslash character (\fB\e\fP) that is used to define the file path separator is also used in Ruby to define an escape character. The knife.rb file is a Ruby file; therefore, file path separators must be escaped. In addition, spaces in the file path must be replaced with \fB~1\fP so that the length of each section within the file path is not more than 8 characters. For example, if EditPad Pro is the text editor of choice and is located at the following path: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C C:\e\eProgram Files (x86)\eEditPad Pro\eEditPad.exe .ft P .fi .UNINDENT .UNINDENT .sp the setting in the knife.rb file would be similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife[:editor] = "C:\e\eProgra~1\e\eEditPa~1\e\eEditPad.exe" .ft P .fi .UNINDENT .UNINDENT .sp One approach to working around the double\- vs. single\-quote issue is to put the single\-quotes outside of the double\-quotes. For example, for Notepad++: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife[:editor] = \(aq"C:\eProgram Files (x86)\eNotepad++\enotepad++.exe \-nosession \-multiInst"\(aq .ft P .fi .UNINDENT .UNINDENT .sp for Sublime Text: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife[:editor] = \(aq"C:\eProgram Files\eSublime Text 2\esublime_text.exe \-\-wait"\(aq .ft P .fi .UNINDENT .UNINDENT .sp for TextPad: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife[:editor] = \(aq"C:\eProgram Files (x86)\eTextPad 7\eTextPad.exe"\(aq .ft P .fi .UNINDENT .UNINDENT .sp and for vim: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C knife[:editor] = \(aq"C:\eProgram Files (x86)\evim\evim74\egvim.exe"\(aq .ft P .fi .UNINDENT .UNINDENT .SS Using Quotes .sp Values can be entered with double quotes (" ") or single quotes (\(aq \(aq), but this should be done consistently. .SS Sub\-commands .sp knife comes with a collection of built in subcommands that work together to provide all of the functionality required to take specific actions against any object in an organization, including cookbooks, nodes, roles, data bags, environments, and users. A knife plugin extends the functionality beyond built\-in subcommands. .sp knife has the following subcommands: \fBbootstrap\fP, \fBclient\fP, \fBconfigure\fP, \fBcookbook\fP, \fBcookbook site\fP, \fBdata bag\fP, \fBdelete\fP, \fBdeps\fP, \fBdiff\fP, \fBdownload\fP, \fBedit\fP, \fBenvironment\fP, \fBexec\fP, \fBindex rebuild\fP, \fBlist\fP, \fBnode\fP, \fBrecipe list\fP, \fBrole\fP, \fBsearch\fP, \fBshow\fP, \fBssh\fP, \fBstatus\fP, \fBtag\fP, \fBupload\fP, \fBuser\fP, and \fBxargs\fP\&. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 The following subcommands run only against the open source Chef server: \fBindex rebuild\fP and \fBuser\fP\&. .UNINDENT .UNINDENT .SS Syntax .sp All knife subcommands have the following syntax: .INDENT 0.0 .INDENT 3.5 knife subcommand [ARGUMENT] (options) .UNINDENT .UNINDENT .sp Each subcommand has its own set of arguments and options. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 All syntax examples in this document show variables in ALL_CAPS. For example \fB\-u PORT_LIST\fP (where PORT_LIST is a comma\-separated list of local and public UDP ports) or \fB\-F FORMAT\fP (where FORMAT determines the output format, either \fBsummary\fP, \fBtext\fP, \fBjson\fP, \fByaml\fP, or \fBpp\fP). These variables often require specific values that are unique to each organization. .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man8/000077500000000000000000000000001276456504500171025ustar00rootroot00000000000000chef-12.14.60/distro/common/man/man8/chef-apply.8000066400000000000000000000040561276456504500212300ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "CHEF-APPLY" "8" "Chef 12.0" "" "chef-client" .SH NAME chef-apply \- The man page for the chef-apply command line tool. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp chef\-apply allows a single recipe to be run from the command line. .SH OPTIONS .sp This command has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C chef\-apply name_of_recipe.rb .ft P .fi .UNINDENT .UNINDENT .sp This tool has the following options: .INDENT 0.0 .TP .B \fB\-e RECIPE_TEXT\fP, \fB\-\-execute RECIPE_TEXT\fP Use to execute a resource using a string. .TP .B \fB\-l LEVEL\fP, \fB\-\-log_level LEVEL\fP The level of logging that will be stored in a log file. .TP .B \fB\-s\fP, \fB\-\-stdin\fP Use to execute a resource using standard input. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-W\fP, \fB\-\-why\-run\fP Use to run the executable in why\-run mode, which is a type of chef\-client run that does everything except modify the system. Use why\-run mode to understand why the chef\-client makes the decisions that it makes and to learn more about the current and proposed state of the system. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .UNINDENT .SH EXAMPLES .sp To use chef\-apply to run a recipe named \fBmachinations.rb\fP, enter the following: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ chef\-apply machinations.rb .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man8/chef-client.8000066400000000000000000000316001276456504500213540ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "CHEF-CLIENT" "8" "Chef 12.0" "" "chef-client" .SH NAME chef-client \- The man page for the chef-client command line tool. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp A chef\-client is an agent that runs locally on every node that is under management by Chef\&. When a chef\-client is run, it will perform all of the steps that are required to bring the node into the expected state, including: .INDENT 0.0 .IP \(bu 2 Registering and authenticating the node with the Chef server .IP \(bu 2 Building the node object .IP \(bu 2 Synchronizing cookbooks .IP \(bu 2 Compiling the resource collection by loading each of the required cookbooks, including recipes, attributes, and all other dependencies .IP \(bu 2 Taking the appropriate and required actions to configure the node .IP \(bu 2 Looking for exceptions and notifications, handling each as required .UNINDENT .sp The chef\-client executable is run as a command\-line tool. .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 A client.rb file is used to specify the configuration details for the chef\-client\&. .INDENT 0.0 .IP \(bu 2 This file is loaded every time this executable is run .IP \(bu 2 On UNIX\- and Linux\-based machines, the default location for this file is \fB/etc/chef/client.rb\fP; on Microsoft Windows machines, the default location for this file is \fBC:\echef\eclient.rb\fP; use the \fB\-\-config\fP option from the command line to change this location .IP \(bu 2 This file is not created by default .IP \(bu 2 When a client.rb file is present in this directory, the settings contained within that file will override the default configuration settings .UNINDENT .UNINDENT .UNINDENT .SH OPTIONS .sp This command has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C chef\-client OPTION VALUE OPTION VALUE ... .ft P .fi .UNINDENT .UNINDENT .sp This command has the following options: .INDENT 0.0 .TP .B \fB\-A\fP, \fB\-\-fatal\-windows\-admin\-check\fP Use to cause a chef\-client run to fail when the chef\-client does not have administrator privileges in Microsoft Windows\&. .TP .B \fB\-\-chef\-zero\-port PORT\fP The port on which chef\-zero will listen. If a port is not specified\-\-\-individually, as range of ports, or from the \fBchef_zero.port\fP setting in the client.rb file\-\-\-the chef\-client will scan for ports between 8889\-9999 and will pick the first port that is available. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBdoc\fP (default) or \fBmin\fP\&. .sp Use \fBdoc\fP to print the progress of the chef\-client run using full strings that display a summary of updates as they occur. .sp Use \fBmin\fP to print the progress of the chef\-client run using single characters. A summary of updates is printed at the end of the chef\-client run. A dot (\fB\&.\fP) is printed for events that do not have meaningful status information, such as loading a file or synchronizing a cookbook. For resources, a dot (\fB\&.\fP) is printed when the resource is up to date, an \fBS\fP is printed when the resource is skipped by \fBnot_if\fP or \fBonly_if\fP, and a \fBU\fP is printed when the resource is updated. .sp Other formatting options are available when those formatters are configured in the client.rb file using the \fBadd_formatter\fP option. .TP .B \fB\-\-force\-formatter\fP Use to show formatter output instead of logger output. .TP .B \fB\-\-force\-logger\fP Use to show logger output instead of formatter output. .TP .B \fB\-g GROUP\fP, \fB\-\-group GROUP\fP The name of the group that owns a process. This is required when starting any executable as a daemon. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-i SECONDS\fP, \fB\-\-interval SECONDS\fP The frequency (in seconds) at which the chef\-client runs. When the chef\-client is run at intervals, \fB\-\-splay\fP and \fB\-\-interval\fP values are applied before the chef\-client run. Default value: \fB1800\fP\&. .TP .B \fB\-j PATH\fP, \fB\-\-json\-attributes PATH\fP The path to a file that contains JSON data. .sp Use this option to define a \fBrun_list\fP object. For example, a JSON file similar to: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C "run_list": [ "recipe[base]", "recipe[foo]", "recipe[bar]", "role[webserver]" ], .ft P .fi .UNINDENT .UNINDENT .sp may be used by running \fBchef\-client \-j path/to/file.json\fP\&. .sp In certain situations this option may be used to update \fBnormal\fP attributes. .sp \fBWARNING:\fP .INDENT 7.0 .INDENT 3.5 Any other attribute type that is contained in this JSON file will be treated as a \fBnormal\fP attribute. For example, attempting to update \fBoverride\fP attributes using the \fB\-j\fP option: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "name": "dev\-99", "description": "Install some stuff", "override_attributes": { "apptastic": { "enable_apptastic": "false", "apptastic_tier_name": "dev\-99.bomb.com" } } } .ft P .fi .UNINDENT .UNINDENT .sp will result in a node object similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "name": "maybe\-dev\-99", "normal": { "name": "dev\-99", "description": "Install some stuff", "override_attributes": { "apptastic": { "enable_apptastic": "false", "apptastic_tier_name": "dev\-99.bomb.com" } } } } .ft P .fi .UNINDENT .UNINDENT .UNINDENT .UNINDENT .TP .B \fB\-k KEY_FILE\fP, \fB\-\-client_key KEY_FILE\fP The location of the file which contains the client key. Default value: \fB/etc/chef/client.pem\fP\&. .TP .B \fB\-K KEY_FILE\fP, \fB\-\-validation_key KEY_FILE\fP The location of the file which contains the key used when a chef\-client is registered with a Chef server\&. A validation key is signed using the \fBvalidation_client_name\fP for authentication. Default value: \fB/etc/chef/validation.pem\fP\&. .TP .B \fB\-l LEVEL\fP, \fB\-\-log_level LEVEL\fP The level of logging that will be stored in a log file. .TP .B \fB\-L LOGLOCATION\fP, \fB\-\-logfile c\fP The location in which log file output files will be saved. If this location is set to something other than \fBSTDOUT\fP, standard output logging will still be performed (otherwise there would be no output other than to a file). This is recommended when starting any executable as a daemon. Default value: \fBSTDOUT\fP\&. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. Default setting: \fB\-\-color\fP\&. .TP .B \fB\-N NODE_NAME\fP, \fB\-\-node\-name NODE_NAME\fP The name of the node. .TP .B \fB\-o RUN_LIST_ITEM\fP, \fB\-\-override\-runlist RUN_LIST_ITEM\fP Replace the current run list with the specified items. This option will not clear the list of cookbooks (and related files) that is cached on the node. .TP .B \fB\-\-once\fP Use to run the chef\-client only once and to cancel \fBinterval\fP and \fBsplay\fP options. .TP .B \fB\-P PID_FILE\fP, \fB\-\-pid PID_FILE\fP The location in which a process identification number (pid) is saved. An executable, when started as a daemon, will write the pid to the specified file. Default value: \fB/tmp/name\-of\-executable.pid\fP\&. .TP .B \fB\-r RUN_LIST_ITEM\fP, \fB\-\-runlist RUN_LIST_ITEM\fP Use to permanently replace the current run\-list with the specified run\-list items. .TP .B \fB\-R\fP, \fB\-\-enable\-reporting\fP Use to enable Chef reporting, which performs data collection during a chef\-client run. .TP .B \fBRECIPE_FILE\fP The path to a recipe. For example, if a recipe file is in the current directory, use \fBrecipe_file.rb\fP\&. This is typically used with the \fB\-\-local\-mode\fP option. .TP .B \fB\-\-run\-lock\-timeout SECONDS\fP The amount of time (in seconds) to wait for a chef\-client run to finish. Default value: not set (indefinite). Set to \fB0\fP to cause a second chef\-client to exit immediately. .TP .B \fB\-s SECONDS\fP, \fB\-\-splay SECONDS\fP A number (in seconds) to add to the \fBinterval\fP that is used to determine the frequency of chef\-client runs. This number can help prevent server load when there are many clients running at the same time. When the chef\-client is run at intervals, \fB\-\-splay\fP and \fB\-\-interval\fP values are applied before the chef\-client run. .TP .B \fB\-S CHEF_SERVER_URL\fP, \fB\-\-server CHEF_SERVER_URL\fP The URL for the Chef server\&. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user that owns a process. This is required when starting any executable as a daemon. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-W\fP, \fB\-\-why\-run\fP Use to run the executable in why\-run mode, which is a type of chef\-client run that does everything except modify the system. Use why\-run mode to understand why the chef\-client makes the decisions that it makes and to learn more about the current and proposed state of the system. .TP .B \fB\-z\fP, \fB\-\-local\-mode\fP Use to run the chef\-client in local mode. This allows all commands that work against the Chef server to also work against the local chef\-repo\&. .UNINDENT .SH RUN WITH ELEVATED PRIVILEGES .sp The chef\-client may need to be run with elevated privileges in order to get a recipe to converge correctly. On UNIX and UNIX\-like operating systems this can be done by running the command as root. On Microsoft Windows this can be done by running the command prompt as an administrator. .SS Linux .sp On Linux, the following error sometimes occurs when the permissions used to run the chef\-client are incorrect: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ chef\-client [Tue, 29 Nov 2011 19:46:17 \-0800] INFO: *** Chef 10.X.X *** [Tue, 29 Nov 2011 19:46:18 \-0800] WARN: Failed to read the private key /etc/chef/client.pem: # .ft P .fi .UNINDENT .UNINDENT .sp This can be resolved by running the command as root. There are a few ways this can be done: .INDENT 0.0 .IP \(bu 2 Log in as root and then run the chef\-client .IP \(bu 2 Use \fBsu\fP to become the root user, and then run the chef\-client\&. For example: .INDENT 2.0 .INDENT 3.5 .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ su .ft P .fi .UNINDENT .UNINDENT .sp and then: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ chef\-client .ft P .fi .UNINDENT .UNINDENT .UNINDENT .UNINDENT .IP \(bu 2 Use the sudo utility .INDENT 2.0 .INDENT 3.5 .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ sudo chef\-client .ft P .fi .UNINDENT .UNINDENT .UNINDENT .UNINDENT .IP \(bu 2 Give a user access to read \fB/etc/chef\fP and also the files accessed by the chef\-client\&. This requires super user privileges and, as such, is not a recommended approach .UNINDENT .SS Windows .sp On Microsoft Windows, running without elevated privileges (when they are necessary) is an issue that fails silently. It will appear that the chef\-client completed its run successfully, but the changes will not have been made. When this occurs, do one of the following to run the chef\-client as the administrator: .INDENT 0.0 .IP \(bu 2 Log in to the administrator account. (This is not the same as an account in the administrator\(aqs security group.) .IP \(bu 2 Run the chef\-client process from the administrator account while being logged into another account. Run the following command: .INDENT 2.0 .INDENT 3.5 .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ runas /user:Administrator "cmd /C chef\-client" .ft P .fi .UNINDENT .UNINDENT .sp This will prompt for the administrator account password. .UNINDENT .UNINDENT .IP \(bu 2 Open a command prompt by right\-clicking on the command prompt application, and then selecting \fBRun as administrator\fP\&. After the command window opens, the chef\-client can be run as the administrator .UNINDENT .SH EXAMPLES .sp \fBStart a Chef run when the chef\-client is running as a daemon\fP .sp A chef\-client that is running as a daemon can be woken up and started by sending the process a \fBSIGUSR1\fP\&. For example, to trigger a chef\-client run on a machine running Linux: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ sudo killall \-USR1 chef\-client .ft P .fi .UNINDENT .UNINDENT .sp \fBStart a Chef run manually\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ ps auxw|grep chef\-client .ft P .fi .UNINDENT .UNINDENT .sp to return something like: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C root 66066 0.9 0.0 2488880 264 s001 S+ 10:26AM 0:03.05 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby /usr/bin/chef\-client \-i 3600 \-s 20 .ft P .fi .UNINDENT .UNINDENT .sp and then enter: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ sudo kill \-USR1 66066 .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/man/man8/chef-solo.8000066400000000000000000000215671276456504500210650ustar00rootroot00000000000000.\" Man page generated from reStructuredText. . .TH "CHEF-SOLO" "8" "Chef 12.0" "" "chef-solo" .SH NAME chef-solo \- The man page for the chef-solo command line tool. . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .sp chef\-solo is an open source version of the chef\-client that allows using cookbooks with nodes without requiring access to a Chef server\&. chef\-solo runs locally and requires that a cookbook (and any of its dependencies) be on the same physical disk as the node. chef\-solo is a limited\-functionality version of the chef\-client and \fBdoes not support\fP the following: .INDENT 0.0 .IP \(bu 2 Node data storage .IP \(bu 2 Search indexes .IP \(bu 2 Centralized distribution of cookbooks .IP \(bu 2 A centralized API that interacts with and integrates infrastructure components .IP \(bu 2 Authentication or authorization .IP \(bu 2 Persistent attributes .UNINDENT .sp \fBNOTE:\fP .INDENT 0.0 .INDENT 3.5 chef\-solo can be run as a daemon. .UNINDENT .UNINDENT .sp The chef\-solo executable is run as a command\-line tool. .sp \fBOptions\fP .sp This command has the following syntax: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C chef\-solo OPTION VALUE OPTION VALUE ... .ft P .fi .UNINDENT .UNINDENT .sp This command has the following options: .INDENT 0.0 .TP .B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP The configuration file to use. .TP .B \fB\-d\fP, \fB\-\-daemonize\fP Use to run the executable as a daemon. This option may not be used in the same command with the \fB\-\-[no\-]fork\fP option. .sp This option is only available on machines that run in UNIX or Linux environments. For machines that are running Microsoft Windows that require similar functionality, use the \fBchef\-client::service\fP recipe in the \fBchef\-client\fP cookbook: \fI\%http://community.opscode.com/cookbooks/chef\-client\fP\&. This will install a chef\-client service under Microsoft Windows using the Windows Service Wrapper\&. .TP .B \fB\-E ENVIRONMENT_NAME\fP, \fB\-\-environment ENVIRONMENT_NAME\fP The name of the environment. .TP .B \fB\-f\fP, \fB\-\-[no\-]fork\fP Use to contain the chef\-client run in a secondary process with dedicated RAM. When the chef\-client run is complete the RAM will be returned to the master process. This option helps ensure that a chef\-client will use a steady amount of RAM over time because the master process will not run recipes. This option will also help prevent memory leaks (such as those that can be introduced by the code contained within a poorly designed cookbook). Use \fB\-\-no\-fork\fP to disable running the chef\-client in fork node. Default value: \fB\-\-fork\fP\&. This option may not be used in the same command with the \fB\-\-daemonize\fP and \fB\-\-interval\fP options. .TP .B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP The output format: \fBdoc\fP (default) or \fBmin\fP\&. .sp Use \fBdoc\fP to print the progress of the chef\-client run using full strings that display a summary of updates as they occur. .sp Use \fBmin\fP to print the progress of the chef\-client run using single characters. A summary of updates is printed at the end of the chef\-client run. A dot (\fB\&.\fP) is printed for events that do not have meaningful status information, such as loading a file or synchronizing a cookbook. For resources, a dot (\fB\&.\fP) is printed when the resource is up to date, an \fBS\fP is printed when the resource is skipped by \fBnot_if\fP or \fBonly_if\fP, and a \fBU\fP is printed when the resource is updated. .sp Other formatting options are available when those formatters are configured in the client.rb file using the \fBadd_formatter\fP option. .TP .B \fB\-\-force\-formatter\fP Use to show formatter output instead of logger output. .TP .B \fB\-\-force\-logger\fP Use to show logger output instead of formatter output. .TP .B \fB\-g GROUP\fP, \fB\-\-group GROUP\fP The name of the group that owns a process. This is required when starting any executable as a daemon. .TP .B \fB\-h\fP, \fB\-\-help\fP Shows help for the command. .TP .B \fB\-i SECONDS\fP, \fB\-\-interval SECONDS\fP The frequency (in seconds) at which the chef\-client runs. When the chef\-client is run at intervals, \fB\-\-splay\fP and \fB\-\-interval\fP values are applied before the chef\-client run. This option may not be used in the same command with the \fB\-\-[no\-]fork\fP option. .TP .B \fB\-j PATH\fP, \fB\-\-json\-attributes PATH\fP The path to a file that contains JSON data. .sp Use this option to define a \fBrun_list\fP object. For example, a JSON file similar to: .INDENT 7.0 .INDENT 3.5 .sp .nf .ft C "run_list": [ "recipe[base]", "recipe[foo]", "recipe[bar]", "role[webserver]" ], .ft P .fi .UNINDENT .UNINDENT .sp may be used by running \fBchef\-client \-j path/to/file.json\fP\&. .sp In certain situations this option may be used to update \fBnormal\fP attributes. .sp \fBWARNING:\fP .INDENT 7.0 .INDENT 3.5 Any other attribute type that is contained in this JSON file will be treated as a \fBnormal\fP attribute. For example, attempting to update \fBoverride\fP attributes using the \fB\-j\fP option: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "name": "dev\-99", "description": "Install some stuff", "override_attributes": { "apptastic": { "enable_apptastic": "false", "apptastic_tier_name": "dev\-99.bomb.com" } } } .ft P .fi .UNINDENT .UNINDENT .sp will result in a node object similar to: .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C { "name": "maybe\-dev\-99", "normal": { "name": "dev\-99", "description": "Install some stuff", "override_attributes": { "apptastic": { "enable_apptastic": "false", "apptastic_tier_name": "dev\-99.bomb.com" } } } } .ft P .fi .UNINDENT .UNINDENT .UNINDENT .UNINDENT .TP .B \fB\-l LEVEL\fP, \fB\-\-log_level LEVEL\fP The level of logging that will be stored in a log file. .TP .B \fB\-L LOGLOCATION\fP, \fB\-\-logfile c\fP The location in which log file output files will be saved. If this location is set to something other than \fBSTDOUT\fP, standard output logging will still be performed (otherwise there would be no output other than to a file). This is recommended when starting any executable as a daemon. .TP .B \fB\-\-[no\-]color\fP Use to view colored output. Default setting: \fB\-\-color\fP\&. .TP .B \fB\-N NODE_NAME\fP, \fB\-\-node\-name NODE_NAME\fP The name of the node. .TP .B \fB\-o RUN_LIST_ITEM\fP, \fB\-\-override\-runlist RUN_LIST_ITEM\fP Replace the current run list with the specified items. .TP .B \fB\-r RECIPE_URL\fP, \fB\-\-recipe\-url RECIPE_URL\fP The URL location from which a remote cookbook tar.gz will be downloaded. .TP .B \fB\-\-run\-lock\-timeout SECONDS\fP The amount of time (in seconds) to wait for a chef\-client run to finish. Default value: not set (indefinite). Set to \fB0\fP to cause a second chef\-client to exit immediately. .TP .B \fB\-s SECONDS\fP, \fB\-\-splay SECONDS\fP A number (in seconds) to add to the \fBinterval\fP that is used to determine the frequency of chef\-client runs. This number can help prevent server load when there are many clients running at the same time. When the chef\-client is run at intervals, \fB\-\-splay\fP and \fB\-\-interval\fP values are applied before the chef\-client run. .TP .B \fB\-u USER\fP, \fB\-\-user USER\fP The user that owns a process. This is required when starting any executable as a daemon. .TP .B \fB\-v\fP, \fB\-\-version\fP The version of the chef\-client\&. .TP .B \fB\-W\fP, \fB\-\-why\-run\fP Use to run the executable in why\-run mode, which is a type of chef\-client run that does everything except modify the system. Use why\-run mode to understand why the chef\-client makes the decisions that it makes and to learn more about the current and proposed state of the system. .UNINDENT .sp \fBExamples\fP .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ chef\-solo \-c ~/solo.rb \-j ~/node.json \-r http://www.example.com/chef\-solo.tar.gz .ft P .fi .UNINDENT .UNINDENT .sp The tar.gz archived into the \fBfile_cache_path\fP, and then extracted to \fBcookbooks_path\fP\&. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ chef\-solo \-c ~/solo.rb \-j ~/node.json .ft P .fi .UNINDENT .UNINDENT .sp chef\-solo will look in the solo.rb file to determine the directory in which cookbooks are located. .INDENT 0.0 .INDENT 3.5 .sp .nf .ft C $ chef\-solo \-c ~/solo.rb \-j http://www.example.com/node.json \-r http://www.example.com/chef\-solo.tar.gz .ft P .fi .UNINDENT .UNINDENT .SH AUTHOR Chef .\" Generated by docutils manpage writer. . chef-12.14.60/distro/common/markdown/000077500000000000000000000000001276456504500173065ustar00rootroot00000000000000chef-12.14.60/distro/common/markdown/README000066400000000000000000000002371276456504500201700ustar00rootroot00000000000000This directory contains markdown documentation that is used in other places. For example, markdown (.mkd) documents that are generated as man pages with ronn. chef-12.14.60/distro/common/markdown/man1/000077500000000000000000000000001276456504500201425ustar00rootroot00000000000000chef-12.14.60/distro/common/markdown/man1/chef-shell.mkd000066400000000000000000000146311276456504500226560ustar00rootroot00000000000000chef-shell(1) -- Interactive Chef Console ========================================= ## SYNOPSIS __chef-shell__ [_named configuration_] _(options)_ * `-S`, `--server CHEF_SERVER_URL`: The chef server URL * `-z`, `--client`: chef-client mode * `-c`, `--config CONFIG`: The configuration file to use * `-j`, `--json-attributes JSON_ATTRIBS`: Load attributes from a JSON file or URL * `-l`, `--log-level LOG_LEVEL`: Set the logging level * `-s`, `--solo`: chef-solo session * `-a`, `--standalone`: standalone session * `-v`, `--version`: Show chef version * `-h`, `--help`: Show command options When no --config option is specified, chef-shell attempts to load a default configuration file: * If a _named configuration_ is given, chef-shell will load ~/.chef/_named configuration_/chef_shell.rb * If no _named configuration_ is given chef-shell will load ~/.chef/chef_shell.rb if it exists * chef-shell falls back to loading /etc/chef/client.rb or /etc/chef/solo.rb if -z or -s options are given and no chef_shell.rb can be found. * The --config option takes precedence over implicit configuration paths. ## DESCRIPTION `chef-shell` is an irb(1) (interactive ruby) session customized for Chef. `chef-shell` serves two primary functions: it provides a means to interact with a Chef Server interactively using a convenient DSL; it allows you to define and run Chef recipes interactively. ## SYNTAX chef-shell uses irb's subsession feature to provide multiple modes of interaction. In addition to the primary mode which is entered on start, `recipe` and `attributes` modes are available. ## PRIMARY MODE The following commands are available in the primary session: * `help`: Prints a list of available commands * `version`: Prints the Chef version * `recipe`: Switches to `recipe` mode * `attributes`: Switches to `attributes` mode * `run_chef`: Initiates a chef run * `reset`: reinitializes chef-shell session * `echo :on|:off`: Turns irb's echo function on or off. Echo is _on_ by default. * `tracing :on|:off`: Turns irb's function tracing feature on or off. Tracing is extremely verbose and expected to be of interest primarily to developers. * `node`: Returns the _node_ object for the current host. See knife-node(1) for more information about nodes. * `ohai`: Prints the attributes of _node_ In addition to these commands, chef-shell provides a DSL for accessing data on the Chef Server. When working with remote data in chef-shell, you chain method calls in the form _object type_._operation_, where _object type_ is in plural form. The following object types are available: * `nodes` * `roles` * `data_bags` * `clients` * `cookbooks` For each _object type_ the following operations are available: * _object type_.all(_&block_): Loads all items from the server. If the optional code _block_ is given, each item will be passed to the block and the results returned, similar to ruby's `Enumerable#map` method. * _object type_.show(_object name_): Aliased as _object type_.load Loads the singular item identified by _object name_. * _object type_.search(_query_, _&block_): Aliased as _object type_.find Runs a search against the server and returns the matching items. If the optional code _block_ is given each item will be passed to the block and the results returned. The _query_ may be a Solr/Lucene format query given as a String, or a Hash of conditions. If a Hash is given, the options will be ANDed together. To join conditions with OR, use negative queries, or any advanced search syntax, you must provide give the query in String form. * _object type_.transform(:all|_query_, _&block_): Aliased as _object type_.bulk_edit Bulk edit objects by processing them with the (required) code _block_. You can edit all objects of the given type by passing the Symbol `:all` as the argument, or only a subset by passing a _query_ as the argument. The _query_ is evaluated in the same way as with __search__. The return value of the code _block_ is used to alter the behavior of `transform`. If the value returned from the block is `nil` or `false`, the object will not be saved. Otherwise, the object is saved after being passed to the block. This behavior can be exploited to create a dry run to test a data transformation. ## RECIPE MODE Recipe mode implements Chef's recipe DSL. Exhaustively documenting this DSL is outside the scope of this document. See the following pages in the Chef documentation for more information: * * Once you have defined resources in the recipe, you can trigger a convergence run via `run_chef` ## EXAMPLES * A "Hello World" interactive recipe chef > recipe chef:recipe > echo :off chef:recipe > file "/tmp/hello\_world" chef:recipe > run\_chef [Sat, 09 Apr 2011 08:56:56 -0700] INFO: Processing file[/tmp/hello\_world] action create ((irb#1) line 2) [Sat, 09 Apr 2011 08:56:56 -0700] INFO: file[/tmp/hello\_world] created file /tmp/hello\_world chef:recipe > pp ls '/tmp' [".", "..", "hello\_world"] * Search for _nodes_ by role, and print their IP addresses chef > nodes.find(:roles => 'monitoring-server') {|n| n[:ipaddress] } => ["10.254.199.5"] * Remove the role _obsolete_ from every node in the system chef > nodes.transform(:all) {|n| n.run\_list.delete('role[obsolete]') } => [node[chef098b2.opschef.com], node[ree-woot], node[graphite-dev], node[fluke.localdomain], node[ghost.local], node[kallistec]] ## BUGS `chef-shell` often does not perfectly replicate the context in which chef-client(8) configures a host, which may lead to discrepancies in observed behavior. `chef-shell` has to duplicate much code from chef-client's internal libraries and may become out of sync with the behavior of those libraries. ## SEE ALSO chef-client(8) knife(1) ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. chef-shell was written by Daniel DeLeo. ## DOCUMENTATION This manual page was written by Daniel DeLeo . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF chef-shell is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-bootstrap.mkd000066400000000000000000000125261276456504500237540ustar00rootroot00000000000000knife-bootstrap(1) -- Install Chef Client on a remote host ======================================== ## SYNOPSIS __knife__ __bootstrap__ _(options)_ * `-i`, `--identity-file IDENTITY_FILE`: The SSH identity file used for authentication * `-N`, `--node-name NAME`: The Chef node name for your new node * `-P`, `--ssh-password PASSWORD`: The ssh password * `-x`, `--ssh-user USERNAME`: The ssh username * `-p`, `--ssh-port PORT`: The ssh port * `--bootstrap-version VERSION`: The version of Chef to install * `--bootstrap-proxy PROXY_URL`: `The proxy server for the node being bootstrapped` * `--prerelease`: Install pre-release Chef gems * `-r`, `--run-list RUN_LIST`: Comma separated list of roles/recipes to apply * `--template-file TEMPLATE`: Full path to location of template to use * `--sudo`: Execute the bootstrap via sudo * `-d`, `--distro DISTRO`: Bootstrap a distro using a template * `--[no-]host-key-verify`: Enable host key verification, which is the default behavior. * `--hint HINT_NAME[=HINT_FILE]`: Provide the name of a hint (with option JSON file) to set for use by Ohai plugins. ## DESCRIPTION Performs a Chef Bootstrap on the target node. The goal of the bootstrap is to get Chef installed on the target system so it can run Chef Client with a Chef Server. The main assumption is a baseline OS installation exists. This sub-command is used internally by some cloud computing plugins. The bootstrap sub-command supports supplying a template to perform the bootstrap steps. If the distro is not specified (via `-d` or `--distro` option), an Ubuntu 10.04 host bootstrapped with RubyGems is assumed. The __DISTRO__ value corresponds to the base filename of the template, in other words `DISTRO`.erb. A template file can be specified with the `--template-file` option in which case the __DISTRO__ is not used. The sub-command looks in the following locations for the template to use: * `bootstrap` directory in the installed Chef Knife library. * `bootstrap` directory in the `$PWD/.chef`. * `bootstrap` directory in the users `$HOME/.chef`. The default bootstrap templates are scripts that get copied to the target node (FQDN). The following distros are supported: * centos5-gems * fedora13-gems * ubuntu10.04-gems * ubuntu10.04-apt The gems installations will use RubyGems 1.3.6 and Chef installed as a gem. The apt installation will use the Opscode APT repository. In addition to handling the software installation, these bootstrap templates do the following: - Write the validation.pem per the local knife configuration. - Write a default config file for Chef (`/etc/chef/client.rb`) using values from the `knife.rb`. - Create a JSON attributes file containing the specified run list and run Chef. In the case of the RubyGems, the `client.rb` will be written from scratch with a minimal set of values; see __EXAMPLES__. In the case of APT Package installation, `client.rb` will have the `validation_client_name` appended if it is not set to `chef-validator` (default config value), and the `node_name` will be added if `chef_node_name` option is specified. When this is complete, the bootstrapped node will have: - Latest Chef version installed from RubyGems or APT Packages from Opscode. This may be a later version than the local system. - Be validated with the configured Chef Server. - Have run Chef with its default run list if one is specified. Additional custom bootstrap templates can be created and stored in `.chef/bootstrap/DISTRO.erb`, replacing __DISTRO__ with the value passed with the `-d` or `--distro` option. See __EXAMPLES__ for more information. ## EXAMPLES Setting up a custom bootstrap is fairly straightforward. Create a `.chef/bootstrap` directory in your Chef Repository or in `$HOME/.chef/bootstrap`. Then create the ERB template file. mkdir ~/.chef/bootstrap vi ~/.chef/bootstrap/debian5.0-apt.erb For example, to create a new bootstrap template that should be used when setting up a new Debian node. Edit the template to run the commands, set up the validation certificate and the client configuration file, and finally to run chef-client on completion. The bootstrap template can be called with: knife bootstrap mynode.example.com --template-file ~/.chef/bootstrap/debian5.0-apt.erb Or, knife bootstrap mynode.example.com --distro debian5.0-apt The `--distro` parameter will automatically look in the `~/.chef/bootstrap` directory for a file named `debian5.0-apt.erb`. Templates provided by the Chef installation are located in `BASEDIR/lib/chef/knife/bootstrap/*.erb`, where _BASEDIR_ is the location where the package or Gem installed the Chef client libraries. ## BUGS `knife bootstrap` is not capable of bootstrapping multiple hosts in parallel. The bootstrap script is passed as an argument to sh(1) on the remote system, so sensitive information contained in the script will be visible to other users via the process list using tools such as ps(1). ## SEE ALSO __knife-ssh__(1) ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-client.mkd000066400000000000000000000063211276456504500232110ustar00rootroot00000000000000knife-client(1) -- Manage Chef API Clients ======================================== ## SYNOPSIS __knife__ __client__ _sub-command_ _(options)_ ## SUB-COMMANDS Client subcommands follow a basic create, read, update, delete (CRUD) pattern. The Following subcommands are available: ## BULK DELETE __knife client bulk delete__ _regex_ _(options)_ Delete clients where the client name matches the regular expression _regex_ on the Chef Server. The regular expression should be given as a quoted string, and not surrounded by forward slashes. ## CREATE __knife client create__ _client name_ _(options)_ * `-a`, `--admin `: Create the client as an admin * `-f`, `--file FILE`: Write the key to a file Create a new client. This generates an RSA keypair. The private key will be displayed on _STDOUT_ or written to the named file. The public half will be stored on the Server. For _chef-client_ systems, the private key should be copied to the system as `/etc/chef/client.pem`. Admin clients should be created for users that will use _knife_ to access the API as an administrator. The private key will generally be copied to `~/.chef/client\_name.pem` and referenced in the `knife.rb` configuration file. ## DELETE __knife client delete__ _client name_ _(options)_ Deletes a registered client. ## EDIT __client edit__ _client name_ _(options)_ Edit a registered client. ## LIST __client list__ _(options)_ * `-w`, `--with-uri`: Show corresponding URIs List all registered clients. ## REREGISTER __client reregister__ _client name_ _(options)_ * `-f`, `--file FILE`: Write the key to a file Regenerate the RSA keypair for a client. The public half will be stored on the server and the private key displayed on _STDOUT_ or written to the named file. This operation will invalidate the previous keypair used by the client, preventing it from authenticating with the Chef Server. Use care when reregistering the validator client. ## SHOW __client show__ _client name_ _(options)_ * `-a`, `--attribute ATTR`: Show only one attribute Show a client. Output format is determined by the --format option. ## DESCRIPTION Clients are identities used for communication with the Chef Server API, roughly equivalent to user accounts on the Chef Server, except that clients only communicate with the Chef Server API and are authenticated via request signatures. In the typical case, there will be one client object on the server for each node, and the corresponding client and node will have identical names. In the Chef authorization model, there is one special client, the "validator", which is authorized to create new non-administrative clients but has minimal privileges otherwise. This identity is used as a sort of "guest account" to create a client identity when initially setting up a host for management with Chef. ## SEE ALSO __knife-node__(1) ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-configure.mkd000066400000000000000000000053031276456504500237130ustar00rootroot00000000000000knife-configure(1) -- Generate configuration files for knife or Chef Client ======================================== ## SYNOPSIS __knife__ __configure__ [client] _(options)_ ## DESCRIPTION Generates a knife.rb configuration file interactively. When given the --initial option, also creates a new administrative user. ## CONFIGURE SUBCOMMANDS ## __knife configure__ _(options)_ * `-i`, `--initial`: Create an initial API Client * `-r`, `--repository REPO`: The path to your chef-repo Create a configuration file for knife. This will prompt for values to enter into the file. Default values are listed in square brackets if no other entry is typed. See __knife__(1) for a description of configuration options. __knife configure client__ _directory_ Read the _knife.rb_ config file and generate a config file suitable for use in _/etc/chef/client.rb_ and copy the validation certificate into the specified _directory_. ## EXAMPLES * On a freshly installed Chef Server, use _knife configure -i_ to create an administrator and knife configuration file. Leave the field blank to accept the default value. On most systems, the default values are acceptable. user@host$ knife configure -i Please enter the chef server URL: [http://localhost:4000] Please enter a clientname for the new client: [username] Please enter the existing admin clientname: [chef-webui] Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem] Please enter the validation clientname: [chef-validator] Please enter the location of the validation key: [/etc/chef/validation.pem] Please enter the path to a chef repository (or leave blank): Creating initial API user... Created (or updated) client[username] Configuration file written to /home/username/.chef/knife.rb This creates a new administrator client named _username_, writes a configuration file to _/home/username/.chef/knife.rb_, and the private key to _/home/username/.chef/username.pem_. The configuration file and private key may be copied to another system to facilitate administration of the Chef Server from a remote system. Depending on the value given for the Chef Server URL, you may need to modify that setting after copying to a remote host. ## SEE ALSO __knife__(1) __knife-client__(1) ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-cookbook-site.mkd000066400000000000000000000101771276456504500245070ustar00rootroot00000000000000knife-cookbook-site(1) -- Install and update open source cookbooks ======================================== ## SYNOPSIS __knife__ __cookbook site__ _sub-command_ _(options)_ ## COOKBOOK SITE SUB-COMMANDS `knife cookbook site` provides the following subcommands: ## INSTALL __cookbook site install COOKBOOK [VERSION]__ _(options)_ * `-D`, `--skip-dependencies `: Skip automatic installation of dependencies. * `-o`, `--cookbook-path PATH`: Install cookbooks to PATH * `-B`, `--branch BRANCH`: Default branch to work with [defaults to master] Uses git(1) version control in conjunction with the cookbook site to install community contributed cookbooks to your local cookbook repository. Running `knife cookbook site install` does the following: 1. A new "pristine copy" branch is created in git for tracking the upstream; 2. All existing cookbooks are removed from the branch; 3. The cookbook is downloaded from the cookbook site in tarball form; 4. The downloaded cookbook is untarred, and its contents committed via git; 5. The pristine copy branch is merged into the master branch. By installing cookbook with this process, you can locally modify the upstream cookbook in your master branch and let git maintain your changes as a separate patch. When an updated upstream version becomes available, you will be able to merge the upstream changes while maintaining your local modifications. Unless _--skip-dependencies_ is specified, the process is applied recursively to all the cookbooks _COOKBOOK_ depends on (via metadata _dependencies_). ## DOWNLOAD __knife cookbook site download COOKBOOK [VERSION]__ _(options)_ * `-f`, `--file FILE`: The filename to write to * `--force`: Force download deprecated cookbook Downloads a specific cookbook from the Community site, optionally specifying a certain version. ## LIST __knife cookbook site list__ _(options)_ * `-w`, `--with-uri`: Show corresponding URIs Lists available cookbooks from the Community site. ## SEARCH __knife cookbook site search QUERY__ _(options)_ Searches for available cookbooks matching the specified query. ## SHARE __knife cookbook site share COOKBOOK CATEGORY__ _(options)_ * `-k`, `--key KEY`: API Client Key * `-u`, `--user USER`: API Client Username * `-o`, `--cookbook-path PATH:PATH`: A colon-separated path to look for cookbooks in Uploads the specified cookbook using the given category to the Opscode cookbooks site. Requires a login user and certificate for the Opscode Cookbooks site. By default, knife will use the username and API key you've configured in your configuration file; otherwise you must explicitly set these values on the command line or use an alternate configuration file. ## UNSHARE __knife cookbook site unshare COOKBOOK__ Stops sharing the specified cookbook on the Opscode cookbooks site. ## SHOW __knife cookbook site show COOKBOOK [VERSION]__ _(options)_ Shows information from the site about a particular cookbook. ## DESCRIPTION The cookbook site, , is a cookbook distribution service operated by Opscode. This service provides users with a central location to publish cookbooks for sharing with other community members. `knife cookbook site` commands provide an interface to the cookbook site's HTTP API. For commands that read data from the API, no account is required. In order to upload cookbooks using the `knife cookbook site share` command, you must create an account on the cookbook site and configure your credentials via command line option or in your knife configuration file. ## EXAMPLES Uploading cookbooks to the Opscode cookbooks site: knife cookbook site share example Other -k ~/.chef/USERNAME.pem -u USERNAME ## SEE ALSO __knife-cookbook(1)__ ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-cookbook.mkd000066400000000000000000000230361276456504500235430ustar00rootroot00000000000000knife-cookbook(1) -- upload and manage chef cookbooks ======================================== ## SYNOPSIS __knife__ __cookbook__ _sub-command_ _(options)_ ## SUB-COMMANDS `knife cookbook` supports the following sub commands: ## LIST __knife cookbook list__ _(options)_ * `-a`, `--all`: show all versions of a cookbook instead of just the most recent * `-w`, `--with-uri`: show corresponding uris Lists the cookbooks available on the Chef server. ## SHOW __knife cookbook show cookbook [version] [part] [filename]__ _(options)_ * `-f`, `--fqdn fqdn `: the fqdn of the host to see the file for * `-p`, `--platform platform `: the platform to see the file for * `-v`, `--platform-version version`: the platform version to see the file for * `-w`, `--with-uri`: Show corresponding URIs show a particular part of a _cookbook_ for the specified _version_. _part_ can be one of: * _attributes_ * _definitions_ * _files_ * _libraries_ * _providers_ * _recipes_ * _resources_ * _templates_ ## UPLOAD __knife cookbook upload [cookbooks...]__ _(options)_ * `-a`, `--all`: upload all cookbooks, rather than just a single cookbook * `-o`, `--cookbook-path path:path`: a colon-separated path to look for cookbooks in * `-d`, `--upload-dependencies`: Uploads additional cookbooks that this cookbook lists in as dependencies in its metadata. * `-E`, `--environment ENVIRONMENT`: An _ENVIRONMENT_ to apply the uploaded cookbooks to. Specifying this option will cause knife to edit the _ENVIRONMENT_ to place a strict version constraint on the cookbook version(s) uploaded. * `--freeze`: Sets the frozen flag on the uploaded cookbook(s) Any future attempt to modify the cookbook without changing the version number will return an error unless --force is specified. * `--force`: Overrides the frozen flag on a cookbook, allowing you to overwrite a cookbook version that has previously been uploaded with the --freeze option. Uploads one or more cookbooks from your local cookbook repository(ies) to the Chef Server. Only files that don't yet exist on the server will be uploaded. As the command parses the name args as 1..n cookbook names: `knife cookbook upload COOKBOOK COOKBOOK ...` works for one to many cookbooks. ## DOWNLOAD __knife cookbook download cookbook [version]__ _(options)_ * `-d`, `--dir download_directory`: the directory to download the cookbook into * `-f`, `--force`: overwrite an existing directory with the download * `-n`, `--latest`: download the latest version of the cookbook download a cookbook from the chef server. if no version is specified and only one version exists on the server, that version will be downloaded. if no version is specified and multiple versions are available on the server, you will be prompted for a version to download. ## DELETE __knife cookbook delete cookbook [version]__ _(options)_ * `-a`, `--all`: delete all versions * `-p`, `--purge`: purge files from backing store. this will disable any cookbook that contains any of the same files as the cookbook being purged. delete the specified _version_ of the named _cookbook_. if no version is specified, and only one version exists on the server, that version will be deleted. if multiple versions are available on the server, you will be prompted for a version to delete. ## BULK DELETE __knife cookbook bulk delete regex__ _(options)_ * `-p`, `--purge`: purge files from backing store. this will disable any cookbook that contains any of the same files as the cookbook being purged. delete cookbooks on the chef server based on a regular expression. the regular expression (_regex_) should be in quotes, not in //'s. ## COOKBOOK CREATE __knife cookbook create cookbook__ _(options)_ * `-o`, `--cookbook-path path`: the directory where the cookbook will be created * `-r`, `--readme-format format`: format of the readme file md, mkd, txt, rdoc * `-C`, `--copyright copyright`: name of copyright holder * `-i`, `--license license`: license for cookbook, apachev2 or none * `-m`, `--email email`: email address of cookbook maintainer this is a helper command that creates a new cookbook directory in the `cookbook_path`. the following directories and files are created for the named cookbook. * cookbook/attributes * cookbook/definitions * cookbook/files/default * cookbook/libraries * cookbook/metadata.rb * cookbook/providers * cookbook/readme.md * cookbook/recipes/default.rb * cookbook/resources * cookbook/templates/default supported readme formats are 'md' (default), 'mkd', 'txt', 'rdoc'. the readme file will be written with the specified extension and a set of helpful starting headers. specify `-C` or `--copyright` with the name of the copyright holder as your name or your company/organization name in a quoted string. if this value is not specified an all-caps string `your_company_name` is used which can be easily changed with find/replace. specify `-i` or `--license` with the license that the cookbook is distributed under for sharing with other people or posting to the opscode cookbooks site. be aware of the licenses of files you put inside the cookbook and follow any restrictions they describe. when using `none` (default) or `apachev2`, comment header text and metadata file are pre-filled. the `none` license will be treated as non-redistributable. specify `-m` or `--email` with the email address of the cookbook's maintainer. if this value is not specified, an all-caps string `your_email` is used which can easily be changed with find/replace. the cookbook copyright, license, email and readme_format settings can be filled in the `knife.rb`, for example with default values: cookbook_copyright "your_company_name" cookbook_license "none" cookbook_email "your_email" readme_format "md" ## METADATA __knife cookbook metadata cookbook__ _(options)_ * `-a`, `--all`: generate metadata for all cookbooks, rather than just a single cookbook * `-o`, `--cookbook-path path:path`: a colon-separated path to look for cookbooks in generate cookbook metadata for the named _cookbook_. the _path_ used here specifies where the cookbooks directory is located and corresponds to the `cookbook_path` configuration option. ## METADATA FROM FILE __knife cookbook metadata from file__ _(options)_ load the cookbook metadata from a specified file. ## TEST __knife cookbook test [cookbooks...]__ _(options)_ * `-a`, `--all`: test all cookbooks, rather than just a single cookbook * `-o`, `--cookbook-path path:path`: a colon-separated path to look for cookbooks in test the specified cookbooks for syntax errors. this uses the built-in ruby syntax checking option for files in the cookbook ending in `.rb`, and the erb syntax check for files ending in `.erb` (templates). ## RECIPE LIST __knife recipe list [PATTERN]__ List available recipes from the server. Specify _PATTERN_ as a regular expression to limit the results. ## DESCRIPTION Cookbooks are the fundamental unit of distribution in Chef. They encapsulate all recipes of resources and assets used to configure a particular aspect of the infrastructure. The following sub-commands can be used to manipulate the cookbooks stored on the Chef Server. On disk, cookbooks are directories with a defined structure. The following directories may appear within a cookbook: * COOKBOOK/attributes/: Ruby files that define default parameters to be used in recipes * COOKBOOK/definitions/: Ruby files that contain _resource definitions_ * COOKBOOK/files/SPECIFICITY: Files of arbitrary type. These files may be downloaded by chef-client(8) when configuring a host. * COOKBOOK/libraries/: Ruby files that contain library code needed for recipes * COOKBOOK/providers/: Ruby files that contain Lightweight Provider definitions * COOKBOOK/recipes/: Ruby files that use Chef's recipe DSL to describe the desired configuration of a system * COOKBOOK/resources/: Ruby files that contain Lightweight Resource definitions * COOKBOOK/templates/SPECIFICITY: ERuby (ERb) template files. These are referenced by _recipes_ and evaluated to dynamically generate configuration files. __SPECIFICITY__ is a feature of _files_ and _templates_ that allow you to specify alternate files to be used on a specific OS platform or host. The default specificity setting is _default_, that is files in `COOKBOOK/files/default` will be used when a more specific copy is not available. Further documentation for this feature is available on the Chef wiki: Cookbooks also contain a metadata file that defines various properties of the cookbook. The most important of these are the _version_ and the _dependencies_. The _version_ is used in combination with environments to select which copy of a given cookbook is distributed to a node. The _dependencies_ are used by the server to determine which additional cookbooks must be distributed to a given host when it requires a cookbook. ## SEE ALSO __knife-environment(1)__ __knife-cookbook-site(1)__ ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-data-bag.mkd000066400000000000000000000102471276456504500233750ustar00rootroot00000000000000knife-data-bag(1) -- Store arbitrary data on a Chef Server ======================================== ## SYNOPSIS __knife__ __data bag__ _sub-command_ _(options)_ ## DESCRIPTION Data bags are stores of arbitrary JSON data. Each data bag is a collection that may contain many items. Data Bag Items are indexed by the Chef Server and can be searched via __knife-search__(1). Data bags are available to all nodes configured by __chef-client__(8), and are therefore a convenient mechanism to store global information, such as lists of administrative accounts that should be configured on all hosts. ## DATA BAG SUB-COMMANDS ## CREATE __knife data bag create__ _bag name_ [item id] _(options)_ * `-s`, `--secret SECRET`: A secret key used to encrypt the data bag item. See __encryption support__ below. * `--secret-file SECRET_FILE`: The path to a file containing the secret key to be used to encrypt the data bag item. If _item id_ is given, creates a new, empty data bag item and opens it for editing in your editor. The data bag will be created if it does not exist. If _item id_ is not given, the data bag will be created. ## DELETE __knife data bag delete__ _bag name_ [item id] _(options)_ Delete a data bag, or an item from a data bag. ## EDIT __knife data bag edit__ _bag name_ _item id_ _(options)_ * `-s`, `--secret SECRET`: A secret key used to encrypt the data bag item. See __encryption support__ below. * `--secret-file SECRET_FILE`: The path to a file containing the secret key to be used to encrypt the data bag item. Edit an item in a data bag. ## FROM FILE __knife data bag from file__ _bag name_ _file_ _(options)_ __knife data bag from file__ _bag name_ _file1_ _file2_ _file3_ _(options)_ __knife data bag from file__ _bag name_ _folder_ _(options)_ * `-s`, `--secret SECRET`: A secret key used to encrypt the data bag item. See __encryption support__ below. * `--secret-file SECRET_FILE`: The path to a file containing the secret key to be used to encrypt the data bag item. Load a data bag item from a JSON file. If _file_ is a relative or absolute path to the file, that file will be used. Otherwise, the _file_ parameter is treated as the base name of a data bag file in a Chef repository, and `knife` will search for the file in `./data_bags/bag_name/file`. For example `knife data bag from file users dan.json` would attempt to load the file `./data_bags/users/dan.json`. ## LIST __knife data bag list__ _(options)_ * `-w`, `--with-uri`: Show corresponding URIs Lists the data bags that exist on the Chef Server. ## SHOW __knife data bag show BAG [ITEM]__ _(options)_ * `-s`, `--secret SECRET`: A secret key used to encrypt the data bag item. See __encryption support__ below. * `--secret-file SECRET_FILE`: The path to a file containing the secret key to be used to encrypt the data bag item. Show a specific data bag or an item in a data bag. The output will be formatted according to the --format option. ## ENCRYPTION SUPPORT Data Bag Items may be encrypted to keep their contents secret. This may be desirable when storing sensitive information such as database passwords, API keys, etc. Data Bag Item encryption uses the AES-256 CBC symmetric key algorithm. __CAVEATS:__ Keys are not encrypted; only values are encrypted. The "id" of a Data Bag Item is not encrypted, since it is used by Chef Server to store the item in its database. For example, given the following data bag item: {"id": "important_passwords", "secret_password": "opensesame"} The key "secret\_password" will be visible to an evesdropper, but the value "opensesame" will be protected. Both the key "id" and its value "important\_passwords" will be visible to an evesdropper. Chef Server does not provide a secure mechanism for distributing encryption keys. ## SEE ALSO __knife-search__(1) ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. http://docs.chef.io/ chef-12.14.60/distro/common/markdown/man1/knife-environment.mkd000066400000000000000000000124511276456504500243000ustar00rootroot00000000000000knife-environment(1) -- Define cookbook policies for the environments in your infrastructure ======================================== ## SYNOPSIS __knife__ __environment__ _sub-command_ _(options)_ ## SUBCOMMANDS Environment subcommands follow a basic create, read, update, delete (CRUD) pattern. The following subcommands are available: ## CREATE __knife environment create__ _environment_ _(options)_ * `-d`, `--description DESCRIPTION`: The value of the description field. Create a new environment object on the Chef Server. The envrionment will be opened in the text editor for editing prior to creation if the -n option is not present. ## DELETE __knife environment delete__ _environment_ _(options)_ Destroy an environment on the Chef Server. A prompt for confirmation will be displayed if the -y options is not given. ## EDIT __knife environment edit__ _environment_ _(options)_ Fetch _environment_ and display it in the text editor for editing. The environment will be saved to the Chef Server when the editing session exits. ## FROM FILE __knife environment from file__ _file_ _(options)_ Create or update an environment from the JSON or Ruby format _file_. See __format__ for the proper format of this file. ## LIST __knife environment list__ _(options)_ * `-w`, `--with-uri`: Show the resource URI for each environment ## SHOW __knife environment show__ _environment_ _(options)_ ## DESCRIPTION Environments provide a means to apply policies to hosts in your infrastructure based on business function. For example, you may have a separate copy of your infrastructure called "dev" that runs the latest version of your application and should use the newest versions of your cookbooks when configuring systems, and a production instance of your infrastructure where you wish to update code and cookbooks in a more controlled fashion. In Chef, this function is implemented with _environments_. Environments contain two major components: a set of cookbook version constraints and environment attributes. ## SYNTAX A cookbook version constraint is comprised of a _cookbook name_ and a _version constraint_. The _cookbook name_ is the name of a cookbook in your system, and the _version constraint_ is a String describing the version(s) of that cookbook allowed in the environment. Only one _version constraint_ is supported for a given _cookbook name_. The exact syntax used to define a cookbook version constraint varies depending on whether you use the JSON format or the Ruby format. In the JSON format, the cookbook version constraints for an environment are represented as a single JSON object, like this: {"apache2": ">= 1.5.0"} In the Ruby format, the cookbook version constraints for an environment are represented as a Ruby Hash, like this: {"apache2" => ">= 1.5.0"} A _version number_ is a String comprised of two or three digits separated by a dot (.) character, or in other words, strings of the form "major.minor" or "major.minor.patch". "1.2" and "1.2.3" are examples of valid version numbers. Version numbers containing more than three digits or alphabetic characters are not supported. A _version constraint_ String is composed of an _operator_ and a _version number_. The following operators are available: * `= VERSION`: Equality. Only the exact version specified may be used. * `> VERSION`: Greater than. Only versions greater than `VERSION` may be used. * `>= VERSION`: Greater than or equal to. Only versions equal to VERSION or greater may be used. * `< VERSION`: Less than. Only versions less than VERSION may be used. * `<= VERSION`: Less than or equal to. Only versions lesser or equal to VERSION may be used. * `~> VERSION`: Pessimistic greater than. Depending on the number of components in the given VERSION, the constraint will be optimistic about future minor or patch revisions only. For example, `~> 1.1` will match any version less than `2.0` and greater than or equal to `1.1.0`, whereas `~> 2.0.5` will match any version less than `2.1.0` and greater than or equal to `2.0.5`. ## FORMAT The JSON format of an envioronment is as follows: { "name": "dev", "description": "The development environment", "cookbook_versions": { "couchdb": "= 11.0.0" }, "json_class": "Chef::Environment", "chef_type": "environment", "default_attributes": { "apache2": { "listen_ports": [ "80", "443" ] } }, "override_attributes": { "aws_s3_bucket": "production" } } The Ruby format of an environment is as follows: name "dev" description "The development environment" cookbook_versions "couchdb" => "= 11.0.0" default_attributes "apache2" => { "listen_ports" => [ "80", "443" ] } override_attributes "aws_s3_bucket" => "production" ## SEE ALSO __knife-node(1)__ __knife-cookbook(1)__ __knife-role(1)__ ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Daniel DeLeo . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-exec.mkd000066400000000000000000000024451276456504500226620ustar00rootroot00000000000000knife-exec(1) -- Run user scripts using the Chef API DSL ======================================== ## SYNOPSIS __knife__ __exec__ _(options)_ * `-E`, `--exec CODE`: Provide a snippet of code to evaluate on the command line ## DESCRIPTION `knife exec` runs arbitrary ruby scripts in a context similar to that of the chef-shell(1) DSL. See the chef-shell documentation for a description of the commands available. ## EXAMPLES * Make an API call against an arbitrary endpoint: knife exec -E 'api.get("nodes/fluke.localdomain/cookbooks")' => list of cookbooks for the node _fluke.localdomain_ * Remove the role _obsolete_ from all nodes: knife exec -E 'nodes.transform(:all){|n| n.run\_list.delete("role[obsolete]")}' * Generate the expanded run list for hosts in the `webserver` role: knife exec -E 'nodes.find(:roles => "webserver") {|n| n.expand!; n[:recipes]}' ## SEE ALSO __chef-shell(1)__ ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-index.mkd000066400000000000000000000017371276456504500230500ustar00rootroot00000000000000knife-index(1) -- Rebuild the search index on a Chef Server ======================================== ## SYNOPSIS __knife__ __index rebuild__ _(options)_ * `-y`, `--yes`: don't bother to ask if I'm sure ## DESCRIPTION Rebuilds all the search indexes on the server. This is accomplished by deleting all objects from the search index, and then forwarding each item in the database to __chef-expander__(8) via __rabbitmq-server__(1). Depending on the number of objects in the database, it may take some time for all objects to be indexed and available for search. ## SEE ALSO __knife-search__(1) ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-node.mkd000066400000000000000000000106761276456504500226700ustar00rootroot00000000000000knife-node(1) -- Manage the hosts in your infrastructure ======================================== ## SYNOPSIS __knife__ __node__ _sub-command_ _(options)_ ## DESCRIPTION Nodes are data structures that represent hosts configured with Chef. Nodes have a __name__, a String that uniquely identifies the node, __attributes__, a nested Hash of properties that describe how the host should be configured, a __chef\_environment__, a String representing the environment to which the node belongs, and a __run\_list__, an ordered list of __recipes__ or __roles__ that chef-client should apply when configuring a host. When a host communicates with a Chef Server, it authenticates using its __node\_name__ for identification and signs its reqests with a private key. The Server validates the request by looking up a __client__ object with a name identical to the __node\_name__ submitted with the request and verifes the signature using the public key for that __client__ object. __NOTE__ that the __client__ is a different object in the system. It is associated with a node by virtue of having a matching name. By default __chef-client__(8) will create a node using the FQDN of the host for the node name, though this may be overridden by configuration settings. ## NODE SUB-COMMANDS The following `node` subcommands are available: ## BULK DELETE __knife node bulk delete__ _regex_ _(options)_ Deletes nodes for which the name matches the regular expression _regex_ on the Chef Server. The regular expression should be given in quotes, and should not be surrounded with forward slashes (as is typical of regular expression literals in scripting languages). ## CREATE __knife node create__ _name_ _(options)_ Create a new node. Unless the --disable-editing option is given, an empty node object will be created and displayed in your text editor. If the editor exits with a successful exit status, the node data will be posted to the Chef Server to create the node. ## DELETE __knife node delete__ _name_ _(options)_ Deletes the node identified by _name_ on the Chef Server. ## EDIT __knife node edit__ _name_ _(options)_ * `-a`, `--all`: Display all node data in the editor. By default, default, override, and automatic attributes are not shown. Edit the node identified by _name_. Like __knife node create__, the node will be displayed in your text editor unless the -n option is present. ## FROM FILE __knife node from file__ _file_ _(options)_ Create a node from a JSON format _file_. ## LIST __knife node list__ _(options)_ * `-w`, `--with-uri`: Show corresponding URIs List all nodes. ## RUN\_LIST ADD __knife node run_list add__ _name_ _run list item_ _(options)_ * `-a`, `--after ITEM`: Place the ENTRY in the run list after ITEM Add the _run list item_ to the node's `run_list`. See Run list ## RUN\_LIST REMOVE __knife node run_list remove__ _node name_ _run list item_ _(options)_ Remove the _run list item_ from the node's `run_list`. ## SHOW __knife node show__ _node name_ _(options)_ * `-a`, `--attribute [ATTR]`: Show only one attribute * `-r`, `--run-list `: Show only the run list * `-F`, `--format FORMAT`: Display the node in a different format. * `-m`, `--medium`: Display more, but not all, of the node's data when using the default _summary_ format Displays the node identified by _node name_ on stdout. ## RUN LIST ITEM FORMAT Run list items may be either roles or recipes. When adding a role to a run list, the correct syntax is "role[ROLE\_NAME]" When adding a recipe to a run list, there are several valid formats: * Fully Qualified Format: "recipe[COOKBOOK::RECIPE\_NAME]", for example, "recipe[chef::client]" * Cookbook Recipe Format: For brevity, the recipe part of the fully qualified format may be omitted, and recipes specified as "COOKBOOK::RECIPE\_NAME", e.g., "chef::client" * Default Recipe Format: When adding the default recipe of a cookbook to a run list, the recipe name may be omitted as well, e.g., "chef::default" may be written as just "chef" ## SEE ALSO __knife-client__(1) __knife-search__(1) __knife-role__(1) ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-role.mkd000066400000000000000000000043741276456504500227020ustar00rootroot00000000000000knife-role(1) -- Group common configuration settings ======================================== ## SYNOPSIS __knife__ __role__ _sub-command_ _(options)_ ## ROLE SUB-COMMANDS The following `role` subcommands are available: ## LIST __knife role list__ _(options)_ * `-w`, `--with-uri`: Show corresponding URIs List roles. ## SHOW __knife role show ROLE__ _(options)_ * `-a`, `--attribute ATTR`: Show only one attribute Show a specific role. ## CREATE __knife role create ROLE__ _(options)_ * `-d`, `--description`: The role description Create a new role. ## EDIT __knife role edit ROLE__ _(options)_ Edit a role. ## FROM FILE __knife role from file FILE__ _(options)_ Create or update a role from a role Ruby DSL (`.rb`) or JSON file. ## DELETE __knife role delete ROLE__ _(options)_ Delete a role. ## BULK DELETE __knife role bulk delete REGEX__ _(options)_ Delete roles on the Chef Server based on a regular expression. The regular expression (_REGEX_) should be in quotes, not in //'s. ## DESCRIPTION Roles provide a mechanism to group repeated configuration settings. Roles are data structures that contain __default\_attributes__, and __override_attributes__, which are nested hashes of configuration settings, and a __run_list__, which is an ordered list of recipes and roles that should be applied to a host by chef-client. __default_attributes__ will be overridden if they conflict with a value on a node that includes the role. Conversely, __override_attributes__ will override any values set on nodes that apply them. When __chef-client__(8) configures a host, it will "expand" the __run_list__ included in that host's node data. The expansion process will recursively replace any roles in the run\_list with that role's run\_list. ## SEE ALSO __knife-node(1)__ __knife-environment(1)__ ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-search.mkd000066400000000000000000000123351276456504500232020ustar00rootroot00000000000000knife-search(1) -- Find objects on a Chef Server by query ======================================== ## SYNOPSIS __knife__ __search INDEX QUERY__ _(options)_ * `-a`, `--attribute ATTR`: Show only one attribute * `-i`, `--id-only`: Show only the ID of matching objects * `-q`, `--query QUERY`: The search query; useful to protect queries starting with - * `-R`, `--rows INT`: The number of rows to return * `-r`, `--run-list`: Show only the run list * `-o`, `--sort SORT`: The order to sort the results in * `-b`, `--start ROW`: The row to start returning results at * `-m`, `--medium`: Display medium sized output when searching nodes using the default summary format * `-l`, `--long`: Display long output when searching nodes using the default summary format ## DESCRIPTION Search is a feature of the Chef Server that allows you to use a full-text search engine to query information about your infrastructure and applications. You can utilize this service via search calls in a recipe or the knife search command. The search syntax is based on Lucene. ## INDEXES Search indexes are a feature of the Chef Server and the search sub-command allows querying any of the available indexes using SOLR query syntax. The following data types are indexed for search: * _node_ * _role_ * _environment_ * _clients_ * _data bag_ Data bags are indexed by the data bag's name. For example, to search a data bag named "admins": knife search admins "field:search_pattern" ## QUERY SYNTAX Queries have the form `field:search_pattern` where `field` is a key in the JSON description of the relevant objects (nodes, roles, environments, or data bags). Both `field` and `search_pattern` are case-sensitive. `search_pattern` can be an exact, wildcard, range, or fuzzy match (see below). The `field` supports exact matching and limited wildcard matching. Searches will return the relevant objects (nodes, roles, environments, or data bags) where the `search_pattern` matches the object's value of `field`. ### FIELD NAMES Field names are the keys within the JSON description of the object being searched. Nested Keys can be searched by placing an underscore ("_") between key names. ### WILDCARD MATCHING FOR FIELD NAMES The field name also has limited support for wildcard matching. Both the "*" and "?" wildcards (see below) can be used within a field name; however, they cannot be the first character of the field name. ### EXACT MATCHES Without any search modifiers, a search returns those fields for which the `search_pattern` exactly matches the value of `field` in the JSON description of the object. ### WILDCARD MATCHES Search support both single- and multi-character wildcard searches within a search pattern. '?' matches exactly one character. '*' matches zero or more characters. ### RANGE MATCHES Range searches allows one to match values between two given values. To match values between X and Y, inclusively, use square brackets: knife search INDEX 'field:[X TO Y] To match values between X and Y, exclusively, use curly brackets: knife search INDEX 'field:{X TO Y}' Values are sorted in lexicographic order. ### FUZZY MATCHES Fuzzy searches allows one to match values based on the Levenshtein Distance algorithm. To perform a fuzzy match, append a tilda (~) to the search term: knife search INDEX 'field:term~' This search would return nodes whose `field` was 'perm' or 'germ'. ### BOOLEAN OPERATORS The boolean operators NOT, AND, and OR are supported. To find values of `field` that are not X: knife search INDEX 'field:(NOT X)' To find records where `field1` is X and `field2` is Y: knife search INDEX 'field1:X AND field2:Y' To find records where `field` is X or Y: knife search INDEX 'field:X OR field:Y' ### QUOTING AND SPECIAL CHARACTERS In order to avoid having special characters and escape sequences within your search term interpreted by either Ruby or the shell, enclose them in single quotes. Search terms that include spaces should be enclosed in double-quotes: knife search INDEX 'field:"term with spaces"' The following characters must be escaped: + - && || ! ( ) { } [ ] ^ " ~ * ? : \ ## EXAMPLES Find the nodes with the fully-qualified domain name (FQDN) www.example.com: knife search node 'fqdn:www.example.com' Find the nodes running a version of Ubuntu: knife search node 'platform:ubuntu*' Find all nodes running CentOS in the production environment: knife search node 'chef_environment:production AND platform:centos' ## KNOWN BUGS * Searches against the client index return no results in most cases. (CHEF-2477) * Searches using the fuzzy match operator (~) produce an error. (CHEF-2478) ## SEE ALSO __knife-ssh__(1) [Lucene Query Parser Syntax](http://lucene.apache.org/java/2_3_2/queryparsersyntax.html) ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-ssh.mkd000066400000000000000000000046631276456504500225370ustar00rootroot00000000000000knife-ssh(1) -- Run a command or interactive session on multiple remote hosts ======================================== ## SYNOPSIS __knife__ __ssh QUERY COMMAND__ _(options)_ * `-a`, `--attribute ATTR `: The attribute to use for opening the connection - default is fqdn * `-C`, `--concurrency NUM `: The number of concurrent connections * `-m`, `--manual-list `: QUERY is a space separated list of servers * `-P`, `--ssh-password PASSWORD`: The ssh password * `-x`, `--ssh-user USERNAME `: The ssh username * `-i`, `--identity-file IDENTITY_FILE`: The SSH identity file used for authentication * `-p`, `--ssh-port PORT`: The ssh port * `--[no-]host-key-verify`: Verify host key, enabled by default. ## DESCRIPTION The _ssh_ sub-command opens an ssh session to each of the nodes in the search results of the _QUERY_. This sub-command requires that the net-ssh-multi and highline Ruby libraries are installed. On Debian systems, these are the libnet-ssh-multi-ruby and libhighline-ruby packages. They can also be installed as RubyGems (net-ssh-multi and highline, respectively). ## TERMINAL MULTIPLEXING AND TERMINAL TAB SUPPORT `knife ssh` integrates with several terminal multiplexer programs to provide a more convenient means of managing multiple ssh sessions. When the _COMMAND_ option matches one of these, `knife ssh` will create multiple interactive ssh sessions running locally in the terminal multiplexer instead of invoking the command on the remote host. The available multiplexers are: * `interactive`: A built-in multiplexer. `interactive` supports running commands on a subset of the connected hosts in parallel * __screen__(1): Runs ssh interactively inside `screen`. ~/.screenrc will be sourced if it exists. * __tmux__(1): Runs ssh interactively inside tmux. * `macterm` (Mac OS X only): Opens a Terminal.app window and creates a tab for each ssh session. You must install the rb-appscript gem before you can use this option. ## SEE ALSO __knife-search__(1) ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-status.mkd000066400000000000000000000022511276456504500232540ustar00rootroot00000000000000knife-status(1) -- Display status information for the nodes in your infrastructure ======================================== ## SYNOPSIS __knife__ __status__ _(options)_ * `-r`, `--run-list RUN_LIST`: Show the run list ## DESCRIPTION The _status_ sub-command searches the Chef Server for all nodes and displays information about the last time the node checked into the server and executed a `node.save`. The fields displayed are the relative checkin time, the node name, it's operating system platform and version, the fully-qualified domain name and the default IP address. If the `-r` option is given, the node's run list will also be displayed. Note that depending on the configuration of the nodes, the FQDN and IP displayed may not be publicly reachable. ## SEE ALSO __knife-search__(1) ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife-tag.mkd000066400000000000000000000015551276456504500225120ustar00rootroot00000000000000knife-tag(1) -- Apply tags to nodes on a Chef Server ======================================== ## SYNOPSIS __knife__ __tag__ _subcommand_ _(options)_ ## TAG SUBCOMMANDS The following `tag` subcommands are available: ## CREATE __knife tag create__ _node_ _tag_ [_..._] Adds one or more tags to _node_ ## DELETE __knife tag delete__ _node_ _tag_ [_..._] Removes one or more tags from _node_ ## LIST __knife tag list__ _node_ Lists the tags applied to _node_ ## SEE ALSO __knife-node(1)__ ## AUTHOR Chef was written by Adam Jacob with many contributions from the community. ## DOCUMENTATION This manual page was written by Daniel DeLeo . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man1/knife.mkd000066400000000000000000000173751276456504500217500ustar00rootroot00000000000000knife(1) -- Chef Server API client utility ======================================== ## SYNOPSIS __knife__ _sub-command_ [_argument_...] _(options)_ ## DESCRIPTION Knife is a command-line utility used to manage data on a Chef server through the HTTP(S) API. Knife is organized into groups of subcommands centered around the various object types in Chef. Each category of subcommand is documented in its own manual page. Available topics are: * bootstrap * client * configure * cookbook-site * cookbook * data-bag * environment * exec * index * node * recipe * role * search * ssh * status * tag If the knife manuals are in your `MANPATH`, you can access help for the above topics using `man knife-TOPIC`; otherwise, you can view the documentation using `knife help TOPIC`. ## OPTIONS * `-s`, `--server-url` URL: Chef Server URL, corresponds to `Chef::Config` `chef_server_url`. * `-k`, `--key` KEY: API Client Key, corresponds to `Chef::Config` `client_key`. * `-c`, `--config` CONFIG: The configuration file to use * `-E`, `--environment ENVIRONMENT`: Set the Chef environment (except for in searches, where this will be flagrantly ignored) * `-e`, `--editor` EDITOR: Set the editor to use for interactive commands * `-F`, `--format` FORMAT: Which format to use for output. See FORMATS for details. * `-d`, `--disable-editing`: Do not open EDITOR, just accept the data as is * `-u`, `--user` USER: API Client Username, corresponds to `Chef::Config` `node_name`. * `-p`, `--print-after`: Show the data after a destructive operation * `-v`, `--version`: Show chef version * `-V`, `--verbose`: More verbose output. Use twice for max verbosity. * `-y`, `--yes`: Say yes to all prompts for confirmation * `--defaults`: Accept default values for all questions * `--[no-]color`: Use colored output. Color enabled by default. * `-h`, `--help`: Show the available options for a command. ## SUB-COMMANDS Sub-commands that operate on the basic Chef data types are structured as _NOUN verb NOUN (options)_. For all data types, the following commands are available: * create (create) * list and show (read) * edit (update) * delete (destroy) Knife also includes commands that take actions other than displaying or modifying data on the Chef Server, such as __knife-ssh(1)__. ## CONFIGURATION The knife configuration file is a Ruby DSL to set configuration parameters for Knife's __GENERAL OPTIONS__. The default location for the config file is `~/.chef/knife.rb`. If managing multiple Chef repositories, per-repository config files can be created. The file must be `.chef/knife.rb` in the current directory of the repository. If the config file exists, knife uses these settings for __GENERAL OPTIONS__ defaults. * `node_name`: User or client identity (i.e., _name_) to use for authenticating requests to the Chef Server. * `client_key`: Private key file to authenticate to the Chef server. Corresponds to the `-k` or `--key` option. * `chef_server_url`: URL of the Chef server. Corresponds to the `-s` or `--server-url` option. This is requested from the user when running this sub-command. * `syntax_check_cache_path`: Specifies the path to a directory where knife caches information about files that it has syntax checked. * `validation_client_name`: Specifies the name of the client used to validate new clients. * `validation_key`: Specifies the private key file to use when bootstrapping new hosts. See knife-client(1) for more information about the validation client. * `cookbook_copyright`, `cookbook_email`, `cookbook_license`, `readme_format` Used by `knife cookbook create` sub-command to specify the copyright holder, maintainer email, license and readme format (respectively) for new cookbooks. The copyright holder is listed as the maintainer in the cookbook's metadata and as the Copyright in the comments of the default recipe. The maintainer email is used in the cookbook metadata. The license determines what preamble to put in the comment of the default recipe, and is listed as the license in the cookbook metadata. Currently supported licenses are "apachev2" and "none". Any other values will result in an empty license in the metadata (needs to be filled in by the author), and no comment preamble in the default recipe. Currently supported readme formats are "md", "mkd", "txt", and "rdoc". Any other value will result in an unformatted README. ## FILES _~/.chef/knife.rb_ Ruby DSL configuration file for knife. See __CONFIGURATION__. ## FORMATS The amount of content displayed and the output format are modified by the `--format` option. If no alternate format is selected, the default is summary. Valid formats are: * `summary`: displays the node in a custom, summarized format (default) * `text`: displays the node data in its entirety using the colorized tree display * `json`: displays the node in JSON format * `yaml`: displays the node in YAML format * `pp`: displays the node using Ruby's pretty printer. For brevity, only the first character of the format is required, for example, -Fj will produce JSON format output. ## CHEF WORKFLOW When working with Chef and Knife in the local repository, the recommended workflow outline looks like: * Create repository. A skeleton sample is provided at _http://github.com/opscode/chef-repo/_. * Configure knife, see __CONFIGURATION__. * Download cookbooks from the Opscode cookbooks site, see __COOKBOOK SITE SUB-COMMANDS__. * Or, create new cookbooks, see `cookbook create` sub-command. * Commit changes to the version control system. See your tool's documentation. * Upload cookbooks to the Chef Server, see __COOKBOOK SUB-COMMANDS__. * Launch instances in the Cloud, OR provision new hosts; see __CLOUD COMPUTING SUB-COMMANDS__ and __BOOTSTRAP SUB-COMMANDS__. * Watch Chef configure systems! A note about git: Opscode and many folks in the Chef community use git, but it is not required, except in the case of the `cookbook site vendor` sub-command, as it uses git directly. Version control is strongly recommended though, and git fits with a lot of the workflow paradigms. ## EXAMPLES ## ENVIRONMENT * `EDITOR`: The text editor to use for editing data. The --editor option takes precedence over this value, and the --disable-editing option suppresses data editing entirely. ## SEE ALSO __chef-client(8)__ __chef-server(8)__ __chef-shell(1)__ __knife-bootstrap(1)__ __knife-client(1)__ __knife-configure(1)__ __knife-cookbook-site(1)__ __knife-cookbook(1)__ __knife-data-bag(1)__ __knife-environment(1)__ __knife-exec(1)__ __knife-index(1)__ __knife-node(1)__ __knife-recipe(1)__ __knife-role(1)__ __knife-search(1)__ __knife-ssh(1)__ __knife-tag(1)__ Complete Chef documentation is available online: JSON is JavaScript Object Notation SOLR is an open source search engine. __git(1)__ is a version control system This manual page was generated from Markdown with __ronn(1)__ ## AUTHOR Chef was written by Adam Jacob of Opscode (), with contributions from the community. ## DOCUMENTATION This manual page was written by Joshua Timberman . ## LICENSE Both Chef and this documentation are released under the terms of the Apache 2.0 License. You may view the license online: On some systems, the complete text of the Apache 2.0 License may be found in `/usr/share/common-licenses/Apache-2.0`. ## CHEF Knife is distributed with Chef. chef-12.14.60/distro/common/markdown/man8/000077500000000000000000000000001276456504500201515ustar00rootroot00000000000000chef-12.14.60/distro/common/markdown/man8/chef-client.mkd000066400000000000000000000051641276456504500230350ustar00rootroot00000000000000chef-client(8) -- Runs a client node connecting to a chef-server. ======================================== ## SYNOPSIS __chef-client__ _(options)_ * `-S`, `--server CHEFSERVERURL`: The chef server URL * `-c`, `--config CONFIG`: The configuration file to use * `-d`, `--daemonize`: Daemonize the process * `-g`, `--group GROUP`: Group to set privilege to * `-i`, `--interval SECONDS`: Run chef-client periodically, in seconds * `-j`, `--json-attributes JSON_ATTRIBS`: Load attributes from a JSON file or URL * `-E`, `--environment ENVIRONMENT`: Set the Chef Environment on the node * `-l`, `--log_level LEVEL`: Set the log level (debug, info, warn, error, fatal) * `-L`, `--logfile LOGLOCATION`: Set the log file location, defaults to STDOUT - recommended for daemonizing * `-N`, `--node-name NODE_NAME`: The node name for this client * `-o`, `--override-runlist`: Replace current run list with specified items * `-K`, `--validation_key KEY_FILE`: Set the validation key file location, used for registering new clients * `-k`, `--client_key KEY_FILE`: Set the client key file location * `-s`, `--splay SECONDS`: The splay time for running at intervals, in seconds * `-u`, `--user USER`: User to set privilege to * `-P`, `--pid PIDFILE`: Set the PID file location, defaults to /tmp/chef-client.pid * `--once`: Cancel any interval or splay options, run chef once and exit * `--skip-cookbook-sync`: Skip cookbook synchronization * `-v`, `--version`: Show chef version * `-h`, `--help`: Show this message ## DESCRIPTION The Chef Client is where almost all of the work in Chef is done. It communicates with the Chef Server via REST, authenticates via Signed Header Authentication, and compiles and executes Cookbooks. A Chef Client does work on behalf of a Node. A single Chef Client can run recipes for multiple Nodes. Clients are where all the action happens - the Chef Server and Chef Expander are largely services that exist only to provide the Client with information. ## SEE ALSO Full documentation for Chef and chef-client is located on docs site, http://docs.chef.io/. ## AUTHOR Chef was written by Adam Jacob of Opscode (http://www.opscode.com), with contributions from the community. This manual page was written by Joshua Timberman with help2man. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. On Debian systems, the complete text of the Apache 2.0 License can be found in /usr/share/common-licenses/Apache-2.0. chef-12.14.60/distro/common/markdown/man8/chef-expander.mkd000066400000000000000000000053151276456504500233630ustar00rootroot00000000000000chef-expander(8) -- fetches messages from RabbitMQ, processes, and loads into chef-solr ======================================== ## SYNOPSIS __chef-expander__ _(options)_ * `-c`, `--config CONFIG_FILE`: a configuration file to use * `-i`, `--index INDEX`: the slot this node will occupy in the ring * `-n`, `--node-count NUMBER`: the number of nodes in the ring * `-l`, `--log-level LOG_LEVEL`: set the log level * `-L`, `--logfile LOG_LOCATION`: Logfile to use * `-d`, `--daemonize`: fork into the background * `-P`, `--pid PIDFILE`: PID file * `-h`, `--help`: show help message * `-v`, `--version`: show the version and exit ## DESCRIPTION Chef Expander fetches messages from RabbitMQ, processes them into the correct format to be loaded into Solr and loads them into Solr. __Running Chef Expander__ Chef Expander is designed for clustered operation, though small installations will only need one worker process. To run Chef Expander with one worker process, run chef-expander -n 1. You will then have a master and worker process, which looks like this in ps: your-shell> ps aux|grep expander you 52110 0.1 0.7 2515476 62748 s003 S+ 3:49PM 0:00.80 chef-expander worker #1 (vnodes 0-1023) you 52108 0.1 0.5 2492880 41696 s003 S+ 3:49PM 0:00.91 ruby bin/chef-expander -n 1 Workers are single threaded and therefore cannot use more than 100% of a single CPU. If you find that your queues are getting backlogged, increase the number of workers __Design__ Chef Expander uses 1024 queues (called vnodes in some places) to allow you to scale the number of Chef Expander workers to meet the needs of your infrastructure. When objects are saved in the API server, they are added to queues based on their database IDs. These queues can be assigned to different Chef Expander workers to distribute the load of processing the index updates. __Chef Expander Operation and Troubleshooting__ Chef Expander includes chef-expanderctl, a management program that allows you to get status information or change the logging verbosity (without restarting). See __chef-expanderctl__(8) for details. ## SEE ALSO __chef-expanderctl__(8) __chef-solr__(8) Full documentation for Chef and chef-server is located on docs site, http://docs.chef.io/. ## AUTHOR Chef was written by Adam Jacob of Opscode (http://www.opscode.com), with contributions from the community. This manual page was created by Nuo Yan . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. On Debian systems, the complete text of the Apache 2.0 License can be found in /usr/share/common-licenses/Apache-2.0. chef-12.14.60/distro/common/markdown/man8/chef-expanderctl.mkd000066400000000000000000000034121276456504500240620ustar00rootroot00000000000000chef-expanderctl(8) -- management program for chef-expander ======================================== ## SYNOPSIS __chef-expanderctl__ _COMMAND_ __Commands:__ * `help`: Show help message * `queue-depth`: display the aggregate queue backlog * `queue-status`: show the backlog and consumer count for each vnode queue * `node-status`: show the status of the nodes in the cluster * `log-level`: sets the log level of all nodes in the cluster ## DESCRIPTION Chef-expanderctl is a management program that allows you to get status information or change the logging verbosity (without restarting). chef-expanderctl has the following commands: * __chef-expanderctl help__ prints usage. * __chef-expanderctl queue-depth__ Shows the total number of messages in the queues. * __chef-expanderctl queue-status__ Show the number of messages in each queue. This is mainly of use when debugging a Chef Expander cluster. * __chef-expanderctl log-level LEVEL__ Sets the log level on a running Chef Expander or cluster. If you suspect that a worker process is stuck, as long as you are using clustered operation, you can simply kill the worker process and it will be restarted by the master process. ## SEE ALSO __chef-expander-cluster__(8) __chef-solr__(8) Full documentation for Chef and chef-server is located on docs site, http://docs.chef.io/. ## AUTHOR Chef was written by Adam Jacob of Opscode (http://www.opscode.com), with contributions from the community. This manual page was created by Nuo Yan . Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. On Debian systems, the complete text of the Apache 2.0 License can be found in /usr/share/common-licenses/Apache-2.0. chef-12.14.60/distro/common/markdown/man8/chef-server-webui.mkd000066400000000000000000000120641276456504500241730ustar00rootroot00000000000000chef-server-webui(8) -- Start the Chef Server merb application slice providing Web User Interface (Management Console). ======================================== ## SYNOPSIS __chef-server-webui__ _(options)_ * `-u`, `--user USER`: This flag is for having chef-server-webui run as a user other than the one currently logged in. Note: if you set this you must also provide a --group option for it to take effect. * `-G`, `--group GROUP`: This flag is for having chef-server-webui run as a group other than the one currently logged in. Note: if you set this you must also provide a --user option for it to take effect. * `-d`, `--daemonize`: This will run a single chef-server-webui in the background. * `-N`, `--no-daemonize`: This will allow you to run a cluster in console mode. * `-c`, `--cluster-nodes NUM_MERBS`: Number of merb daemons to run for chef-server-webui. * `-I`, `--init-file FILE`: File to use for initialization on load, defaults to config/init.rb. * `-p`, `--port PORTNUM`: Port to run chef-server-webui on, defaults to 4040. Additional nodes (-c) listen on incrementing port numbers. * `-o`, `--socket-file FILE`: Socket file to run chef-server-webui on, defaults to [Merb.root]/log/merb.sock. This is for web servers, like thin, that use sockets. Specify this *only* if you *must*. * `-s`, `--socket SOCKNUM`: Socket number to run chef-server-webui on, defaults to 0. * `-n`, `--name NAME`: Set the name of the application. This is used in the process title and log file names. * `-P`, `--pid PIDFILE`: PID file, defaults to [Merb.root]/log/merb.main.pid for the master process and[Merb.root]/log/merb.[port number].pid for worker processes. For clusters, use %s to specify where in the file chef-server-webui should place the port number. For instance: -P myapp.%s.pid. * `-h`, `--host HOSTNAME`: Host to bind to (default is 0.0.0.0). * `-m`, `--merb-root PATH_TO_APP_ROOT`: The path to the Merb.root for the app you want to run (default is current working directory). * `-a`, `--adapter ADAPTER`: The rack adapter to use to run chef-server-webui (default is mongrel) [mongrel, emongrel, thin, ebb, fastcgi, webrick]. * `-R`, `--rackup FILE`: Load an alternate Rack config file (default is config/rack.rb). * `-i`, `--irb-console`: This flag will start chef-server-webui in irb console mode. All your models and other classes will be available for you in an irb session. * `-S`, `--sandbox`: This flag will enable a sandboxed irb console. If your ORM supports transactions, all edits will be rolled back on exit. * `-l`, `--log-level LEVEL`: Log levels can be set to any of these options: debug < info < warn < error < fatal (default is info). * `-L`, `--log LOGFILE`: A string representing the logfile to use. Defaults to [Merb.root]/log/merb.[main].log for the master process and [Merb.root]/log/merb[port number].logfor worker processes. * `-e`, `--environment STRING`: Environment to run Merb under [development, production, testing] (default is development). * `-r`, `--script-runner ['RUBY CODE'| FULL_SCRIPT_PATH]`: Command-line option to run scripts and/or code in the chef-server-webui app. * `-K`, `-graceful PORT or all`: Gracefully kill chef-server-webui proceses by port number. Use chef-server -K all to gracefully kill all merbs. * `-k`, `--kill PORT`: Force kill one merb worker by port number. This will cause the worker to be respawned. * `--fast-deploy`: Reload the code, but not yourinit.rb or gems. * `-X`, `--mutex on/off`: This flag is for turning the mutex lock on and off. * `-D`, `--debugger`: Run chef-server-webui using rDebug. * `-V`, `--verbose`: Print extra information. * `-C`, `--console-trap`: Enter an irb console on ^C. * `-?`, `-H`, `--help`: Show this help message. ## DESCRIPTION The Chef Server WebUI (Management Console) is a Merb application slice. The default listen port is 4040. The Management Console is Chef Server's web interface. Nodes, roles, cookbooks, data bags, and API clients can be managed through the Management Console. Search can also be done on the console. In order to start using the Management Console, you need to first create a user or change the default password on the "admin" user. The default credentials are: - `Username`: admin - `Password`: p@ssw0rd1 ## SEE ALSO Full documentation for Chef and chef-server-webui (Management Console) is located on the Chef docs site, http://docs.chef.io/. ## AUTHOR Chef was written by Adam Jacob of Opscode (http://www.opscode.com), with contributions from the community. This manual page was written by Joshua Timberman with help2man for the Debian project (but may be used by others). Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. On Debian systems, the complete text of the Apache 2.0 License can be found in /usr/share/common-licenses/Apache-2.0. chef-12.14.60/distro/common/markdown/man8/chef-server.mkd000066400000000000000000000120441276456504500230600ustar00rootroot00000000000000chef-server(8) - Start the Chef Server merb application slice. ======================================== ## SYNOPSIS __chef-server__ _(options)_ * `-u`, `--user USER`: This flag is for having chef-server-webui run as a user other than the one currently logged in. Note: if you set this you must also provide a --group option for it to take effect. * `-G`, `--group GROUP`: This flag is for having chef-server-webui run as a group other than the one currently logged in. Note: if you set this you must also provide a --user option for it to take effect. * `-d`, `--daemonize`: This will run a single chef-server-webui in the background. * `-N`, `--no-daemonize`: This will allow you to run a cluster in console mode. * `-c`, `--cluster-nodes NUM_MERBS`: Number of merb daemons to run for chef-server-webui. * `-I`, `--init-file FILE`: File to use for initialization on load, defaults to config/init.rb. * `-p`, `--port PORTNUM`: Port to run chef-server-webui on, defaults to 4040. Additional nodes (-c) listen on incrementing port numbers. * `-o`, `--socket-file FILE`: Socket file to run chef-server-webui on, defaults to [Merb.root]/log/merb.sock. This is for web servers, like thin, that use sockets. Specify this *only* if you *must*. * `-s`, `--socket SOCKNUM`: Socket number to run chef-server-webui on, defaults to 0. * `-n`, `--name NAME`: Set the name of the application. This is used in the process title and log file names. * `-P`, `--pid PIDFILE`: PID file, defaults to [Merb.root]/log/merb.main.pid for the master process and[Merb.root]/log/merb.[port number].pid for worker processes. For clusters, use %s to specify where in the file chef-server-webui should place the port number. For instance: -P myapp.%s.pid. * `-h`, `--host HOSTNAME`: Host to bind to (default is 0.0.0.0). * `-m`, `--merb-root PATH_TO_APP_ROOT`: The path to the Merb.root for the app you want to run (default is current working directory). * `-a`, `--adapter ADAPTER`: The rack adapter to use to run chef-server-webui (default is mongrel) [mongrel, emongrel, thin, ebb, fastcgi, webrick]. * `-R`, `--rackup FILE`: Load an alternate Rack config file (default is config/rack.rb). * `-i`, `--irb-console`: This flag will start chef-server-webui in irb console mode. All your models and other classes will be available for you in an irb session. * `-S`, `--sandbox`: This flag will enable a sandboxed irb console. If your ORM supports transactions, all edits will be rolled back on exit. * `-l`, `--log-level LEVEL`: Log levels can be set to any of these options: debug < info < warn < error < fatal (default is info). * `-L`, `--log LOGFILE`: A string representing the logfile to use. Defaults to [Merb.root]/log/merb.[main].log for the master process and [Merb.root]/log/merb[port number].logfor worker processes. * `-e`, `--environment STRING`: Environment to run Merb under [development, production, testing] (default is development). * `-r`, `--script-runner ['RUBY CODE'| FULL_SCRIPT_PATH]`: Command-line option to run scripts and/or code in the chef-server-webui app. * `-K`, `-graceful PORT or all`: Gracefully kill chef-server-webui proceses by port number. Use chef-server -K all to gracefully kill all merbs. * `-k`, `--kill PORT`: Force kill one merb worker by port number. This will cause the worker to be respawned. * `--fast-deploy`: Reload the code, but not yourinit.rb or gems. * `-X`, `--mutex on/off`: This flag is for turning the mutex lock on and off. * `-D`, `--debugger`: Run chef-server-webui using rDebug. * `-V`, `--verbose`: Print extra information. * `-C`, `--console-trap`: Enter an irb console on ^C. * `-?`, `-H`, `--help`: Show this help message. ## DESCRIPTION The Chef Server provides a central point for the distribution of Cookbooks, management and authentication of Nodes, and the use of Search. It provides a REST API. The API service is what clients use to interact with the server to manage node configuration in Chef. By default, the service is started on port 4000 as a Merb application slice running with the thin server adapter. The two methods of interaction with the API for humans are the command-line tool Knife and the Management Console. The Chef Client library is used for interacting with the API for client nodes. ## SEE ALSO __chef-client__(8) __chef-server-webui__(8) __knife__(1) Full documentation for Chef and chef-server is located on docs site, http://docs.chef.io/. ## AUTHOR Chef was written by Adam Jacob of Opscode (http://www.opscode.com), with contributions from the community. This manual page was written by Joshua Timberman with help2man. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. On Debian systems, the complete text of the Apache 2.0 License can be found in /usr/share/common-licenses/Apache-2.0. chef-12.14.60/distro/common/markdown/man8/chef-solo.mkd000066400000000000000000000074201276456504500225300ustar00rootroot00000000000000chef-solo(8) -- Runs chef in solo mode against a specified cookbook location. ======================================== ## SYNOPSIS __chef-solo__ _(options)_ * `-c`, `--config CONFIG`: The configuration file to use * `-d`, `--daemonize`: Daemonize the process * `-g`, `--group GROUP`: Group to set privilege to * `-i`, `--interval SECONDS`: Run chef-client periodically, in seconds * `-j`, `--json-attributes JSON_ATTRIBS`: Load attributes from a JSON file or URL * `-l`, `--log_level LEVEL`: Set the log level (debug, info, warn, error, fatal) * `-L`, `--logfile LOGLOCATION`: Set the log file location, defaults to STDOUT - recommended for daemonizing * `-N`, `--node-name NODE_NAME`: The node name for this client * `-r`, `--recipe-url RECIPE_URL`: Pull down a remote gzipped tarball of recipes and untar it to the cookbook cache. * `-s`, `--splay SECONDS`: The splay time for running at intervals, in seconds * `-u`, `--user USER`: User to set privilege to * `-v`, `--version`: Show chef version * `-h`, `--help`: Show this message ## DESCRIPTION Chef Solo allows you to run Chef Cookbooks in the absence of a Chef Server. To do this, the complete cookbook needs to be present on disk. By default Chef Solo will look in /etc/chef/solo.rb for its configuration. This configuration file has two required variables: file_cache_path and cookbook_path. For example: file_cache_path "/var/chef-solo" cookbook_path "/var/chef-solo/cookbooks" For your own systems, you can change this to reflect any directory you like, but you'll need to specify absolute paths and the cookbook_path directory should be a subdirectory of the file_cache_path. You can also specify cookbook_path as an array, passing multiple locations to search for cookbooks. For example: file_cache_path "/var/chef-solo" cookbook_path ["/var/chef-solo/cookbooks", "/var/chef-solo/site-cookbooks"] Note that earlier entries are now overridden by later ones. Since chef-solo doesn't have any interaction with a Chef Server, you'll need to specify node-specifc attributes in a JSON file. This can be located on the target system itself, or it can be stored on a remote server such as S3, or a web server on your network. Within the JSON file, you'll also specify the recipes that Chef should run in the "run_list". An example JSON file, which sets a resolv.conf: { "resolver": { "nameservers": [ "10.0.0.1" ], "search":"int.example.com" }, "run_list": [ "recipe[resolver]" ] } Then you can run chef-solo with -j to specify the JSON file. It will look for cookbooks in the cookbook_path configured in the configuration file, and apply attributes and use the run_list from the JSON file specified. You can use -c to specify the path to the configuration file (if you don't want chef-solo to use the default). You can also specify -r for a cookbook tarball. For example: chef-solo -c ~/solo.rb -j ~/node.json -r http://www.example.com/chef-solo.tar.gz In the above case, chef-solo would extract the tarball to your specified cookbook_path, use ~/solo.rb as the configuration file, and apply attributes and use the run_list from ~/node.json. ## SEE ALSO Full documentation for Chef and chef-solo is located on the Chef docs site, http://docs.chef.io/. ## AUTHOR Chef was written by Adam Jacob of Opscode (http://www.opscode.com), with contributions from the community. This manual page was written by Joshua Timberman with help2man. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. On Debian systems, the complete text of the Apache 2.0 License can be found in /usr/share/common-licenses/Apache-2.0. chef-12.14.60/distro/common/markdown/man8/chef-solr.mkd000066400000000000000000000057561276456504500225450ustar00rootroot00000000000000chef-solr(8) -- Runs as Chef's search server ======================================== ## SYNOPSIS __chef-solr__ _(options)_ * `-c`, `--config CONFIG`: The configuration file to use * `-d`, `--daemonize`: Daemonize the process * `-g`, `--group GROUP`: Group to set privilege to * `-l`, `--log_level LEVEL`: Set the log level (debug, info, warn, error, fatal) * `-L`, `--logfile LOGLOCATION`: Set the log file location, defaults to STDOUT - recommended for daemonizing * `-P`, `--pid PIDFILE`: Set the PID file location, defaults to /tmp/chef-solr.pid * `-D`, `--solr-data-dir PATH`: Where the Solr data lives * `-x`, `--solor-heap-size SIZE`: Set the size of the Java Heap * `-H`, `--solr-home-dir PATH`: Solr home directory * `-j`, `--java-opts OPTS`: Raw options passed to Java * `-x`, `--solor-heap-size`: Set the size of the Java Heap * `-W`, `--solr-jetty-dir PATH`: Where to place the Solr Jetty instance * `-u`, `--user USER`: User to set privilege to * `-v`, `--version`: Show chef-solr version * `-h`, `--help`: Show this message ## DESCRIPTION Chef-solr provides search service for Chef. You need to have both chef-solr and chef-expander-cluster running in order for search to work. __Installation__ Make sure you backed up your data if you are upgrading from a previous version. Run chef-solr-installer to upgrade your Chef Solr installation. Answer "yes" when prompted for confirmation. The process should look like this: yourshell> chef-solr-installer Configuration setting solr_heap_size is unknown and will be ignored Chef Solr is already installed in /var/chef/solr Do you want to overwrite the current install? All existing Solr data will be lost. [y/n] y Removing the existing Chef Solr installation rm -rf /var/chef/solr rm -rf /var/chef/solr-jetty rm -rf /var/chef/solr/data Creating Solr Home Directory mkdir -p /var/chef/solr entering /var/chef/solr tar zxvf /Users/ddeleo/opscode/chef/chef-solr/solr/solr-home.tar.gz Creating Solr Data Directory mkdir -p /var/chef/solr/data Unpacking Solr Jetty mkdir -p /var/chef/solr-jetty entering /var/chef/solr-jetty tar zxvf /Users/ddeleo/opscode/chef/chef-solr/solr/solr-jetty.tar.gz Successfully installed Chef Solr. You can restore your search index using `knife index rebuild` ## SEE ALSO __chef-expander-cluster__(8) Full documentation for Chef and chef-server is located on the Chef Docs site, http://docs.chef.io/. ## AUTHOR Chef was written by Adam Jacob of Opscode (http://www.opscode.com), with contributions from the community. This manual page was written by Joshua Timberman with help2man. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License. On Debian systems, the complete text of the Apache 2.0 License can be found in /usr/share/common-licenses/Apache-2.0. chef-12.14.60/distro/powershell/000077500000000000000000000000001276456504500163605ustar00rootroot00000000000000chef-12.14.60/distro/powershell/chef/000077500000000000000000000000001276456504500172655ustar00rootroot00000000000000chef-12.14.60/distro/powershell/chef/chef.psm1000066400000000000000000000303361276456504500210010ustar00rootroot00000000000000 function Load-Win32Bindings { Add-Type -TypeDefinition @" using System; using System.Diagnostics; using System.Runtime.InteropServices; namespace Chef { [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct STARTUPINFO { public uint cb; public string lpReserved; public string lpDesktop; public string lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public STARTF dwFlags; public ShowWindow wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } [Flags] public enum CreationFlags : int { NONE = 0, DEBUG_PROCESS = 0x00000001, DEBUG_ONLY_THIS_PROCESS = 0x00000002, CREATE_SUSPENDED = 0x00000004, DETACHED_PROCESS = 0x00000008, CREATE_NEW_CONSOLE = 0x00000010, CREATE_NEW_PROCESS_GROUP = 0x00000200, CREATE_UNICODE_ENVIRONMENT = 0x00000400, CREATE_SEPARATE_WOW_VDM = 0x00000800, CREATE_SHARED_WOW_VDM = 0x00001000, CREATE_PROTECTED_PROCESS = 0x00040000, EXTENDED_STARTUPINFO_PRESENT = 0x00080000, CREATE_BREAKAWAY_FROM_JOB = 0x01000000, CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000, CREATE_DEFAULT_ERROR_MODE = 0x04000000, CREATE_NO_WINDOW = 0x08000000, } [Flags] public enum STARTF : uint { STARTF_USESHOWWINDOW = 0x00000001, STARTF_USESIZE = 0x00000002, STARTF_USEPOSITION = 0x00000004, STARTF_USECOUNTCHARS = 0x00000008, STARTF_USEFILLATTRIBUTE = 0x00000010, STARTF_RUNFULLSCREEN = 0x00000020, // ignored for non-x86 platforms STARTF_FORCEONFEEDBACK = 0x00000040, STARTF_FORCEOFFFEEDBACK = 0x00000080, STARTF_USESTDHANDLES = 0x00000100, } public enum ShowWindow : short { SW_HIDE = 0, SW_SHOWNORMAL = 1, SW_NORMAL = 1, SW_SHOWMINIMIZED = 2, SW_SHOWMAXIMIZED = 3, SW_MAXIMIZE = 3, SW_SHOWNOACTIVATE = 4, SW_SHOW = 5, SW_MINIMIZE = 6, SW_SHOWMINNOACTIVE = 7, SW_SHOWNA = 8, SW_RESTORE = 9, SW_SHOWDEFAULT = 10, SW_FORCEMINIMIZE = 11, SW_MAX = 11 } public enum StandardHandle : int { Input = -10, Output = -11, Error = -12 } public static class Kernel32 { [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CreateProcess( string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandles, CreationFlags dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("kernel32.dll", SetLastError=true)] public static extern IntPtr GetStdHandle( StandardHandle nStdHandle); [DllImport("kernel32", SetLastError=true)] public static extern int WaitForSingleObject( IntPtr hHandle, int dwMilliseconds); [DllImport("kernel32", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle( IntPtr hObject); [DllImport("kernel32", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetExitCodeProcess( IntPtr hProcess, out int lpExitCode); } } "@ } function Run-ExecutableAndWait($AppPath, $ArgumentString) { # Use the Win32 API to create a new process and wait for it to terminate. $null = Load-Win32Bindings $si = New-Object Chef.STARTUPINFO $pi = New-Object Chef.PROCESS_INFORMATION $si.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($si) $si.wShowWindow = [Chef.ShowWindow]::SW_SHOW $si.dwFlags = [Chef.STARTF]::STARTF_USESTDHANDLES $si.hStdError = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Error) $si.hStdOutput = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Output) $si.hStdInput = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Input) $pSec = New-Object Chef.SECURITY_ATTRIBUTES $pSec.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($pSec) $pSec.bInheritHandle = $true $tSec = New-Object Chef.SECURITY_ATTRIBUTES $tSec.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($tSec) $tSec.bInheritHandle = $true $success = [Chef.Kernel32]::CreateProcess($AppPath, $ArgumentString, [ref] $pSec, [ref] $tSec, $true, [Chef.CreationFlags]::NONE, [IntPtr]::Zero, $pwd, [ref] $si, [ref] $pi) if (-Not $success) { $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() throw "Unable to create process [$ArgumentString]. Error code $reason." } $waitReason = [Chef.Kernel32]::WaitForSingleObject($pi.hProcess, -1) if ($waitReason -ne 0) { if ($waitReason -eq -1) { $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() throw "Could not wait for process to terminate. Error code $reason." } else { throw "WaitForSingleObject failed with return code $waitReason - it's impossible!" } } $success = [Chef.Kernel32]::GetExitCodeProcess($pi.hProcess, [ref] $global:LASTEXITCODE) if (-Not $success) { $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() throw "Process exit code unavailable. Error code $reason." } $success = [Chef.Kernel32]::CloseHandle($pi.hProcess) if (-Not $success) { $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() throw "Unable to release process handle. Error code $reason." } $success = [Chef.Kernel32]::CloseHandle($pi.hThread) if (-Not $success) { $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() throw "Unable to release thread handle. Error code $reason." } } function Get-ScriptDirectory { if (!$PSScriptRoot) { $Invocation = (Get-Variable MyInvocation -Scope 1).Value $PSScriptRoot = Split-Path $Invocation.MyCommand.Path } $PSScriptRoot } function Run-RubyCommand($command, $argList) { # This method exists to take the given list of arguments and get it past ruby's command-line # interpreter unscathed and untampered. See https://github.com/ruby/ruby/blob/trunk/win32/win32.c#L1582 # for a list of transformations that ruby attempts to perform with your command-line arguments # before passing it onto a script. The most important task is to defeat the globbing # and wild-card expansion that ruby performs. Note that ruby does not use MSVCRT's argc/argv # and deliberately reparses the raw command-line instead. # # To stop ruby from interpreting command-line arguments as globs, they need to be enclosed in ' # Ruby doesn't allow any escape characters inside '. This unfortunately prevents us from sending # any strings which themselves contain '. Ruby does allow multi-fragment arguments though. # "foo bar"'baz qux'123"foo" is interpreted as 1 argument because there are no un-escaped # whitespace there. The argument would be interpreted as the string "foo barbaz qux123foo". # This lets us escape ' characters by exiting the ' quoted string, injecting a "'" fragment and # then resuming the ' quoted string again. # # In the process of defeating ruby, one must also defeat the helpfulness of powershell. # When arguments come into this method, the standard PS rules for interpreting cmdlet arguments # apply. When using & (call operator) and providing an array of arguments, powershell (verified # on PS 4.0 on Windows Server 2012R2) will not evaluate them but (contrary to documentation), # it will still marginally interpret them. The behaviour of PS 5.0 seems to be different but # ignore that for now. If any of the provided arguments has a space in it, powershell checks # the first and last character to ensure that they are " characters (and that's all it checks). # If they are not, it will blindly surround that argument with " characters. It won't do this # operation if no space is present, even if other special characters are present. If it notices # leading and trailing " characters, it won't actually check to see if there are other " # characters in the string. Since PS 5.0 changes this behavior, we could consider using the --% # "stop screwing up my arguments" operator, which is available since PS 3.0. When encountered # --% indicates that the rest of line is to be sent literally... except if the parser encounters # %FOO% cmd style environment variables. Because reasons. And there is no way to escape the # % character in *any* waym shape or form. # https://connect.microsoft.com/PowerShell/feedback/details/376207/executing-commands-which-require-quotes-and-variables-is-practically-impossible # # In case you think that you're either reading this incorrectly or that I'm full of shit, here # are some examples. These use EchoArgs.exe from the PowerShell Community Extensions package. # I have not included the argument parsing output from EchoArgs.exe to prevent confusing you with # more details about MSVCRT's parsing algorithm. # # $x = "foo '' bar `"baz`"" # & EchoArgs @($x, $x) # Command line: # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" "foo '' bar "baz"" "foo '' bar "baz"" # # $x = "abc'123'nospace`"lulz`"!!!" # & EchoArgs @($x, $x) # Command line: # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" abc'123'nospace"lulz"!!! abc'123'nospace"lulz"!!! # # $x = "`"`"Look ma! Tonnes of spaces! 'foo' 'bar'`"`"" # & EchoArgs @($x, $x) # Command line: # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" ""Look ma! Tonnes of spaces! 'foo' 'bar'"" ""Look ma! Tonnes of spaces! 'foo' 'bar'"" # # Given all this, we can now device a strategy to work around all these immensely helpful, well # documented and useful tools by looking at each incoming argument, escaping any ' characters # with a '"'"' sequence, surrounding each argument with ' & joining them with a space separating # them. # There is another bug (https://bugs.ruby-lang.org/issues/11142) that causes ruby to mangle any # "" two-character double quote sequence but since we always emit our strings inside ' except for # ' characters, this should be ok. Just remember that an argument '' should get translated to # ''"'"''"'"'' on the command line. If those intervening empty ''s are not present, the presence # of "" will cause ruby to mangle that argument. $transformedList = $argList | foreach { "'" + ( $_ -replace "'","'`"'`"'" ) + "'" } $fortifiedArgString = $transformedList -join ' ' # Use the correct embedded ruby path. We'll be deployed at a path that looks like # [C:\opscode or some other prefix]\chef\modules\chef $ruby = Join-Path (Get-ScriptDirectory) "..\..\embedded\bin\ruby.exe" $commandPath = Join-Path (Get-ScriptDirectory) "..\..\bin\$command" Run-ExecutableAndWait $ruby """$ruby"" '$commandPath' $fortifiedArgString" } function chef-apply { Run-RubyCommand 'chef-apply' $args } function chef-client { Run-RubyCommand 'chef-client' $args } function chef-service-manager { Run-RubyCommand 'chef-service-manager' $args } function chef-shell { Run-RubyCommand 'chef-shell' $args } function chef-solo { Run-RubyCommand 'chef-solo' $args } function chef-windows-service { Run-RubyCommand 'chef-windows-service' $args } function knife { Run-RubyCommand 'knife' $args } Export-ModuleMember -function chef-apply Export-ModuleMember -function chef-client Export-ModuleMember -function chef-service-manager Export-ModuleMember -function chef-shell Export-ModuleMember -function chef-solo Export-ModuleMember -function chef-windows-service Export-ModuleMember -function knife # To debug this module, uncomment the line below and then run the following. # Export-ModuleMember -function Run-RubyCommand # Remove-Module chef # Import-Module chef # "puts ARGV" | Out-File C:\opscode\chef\bin\puts_args # Run-RubyCommand puts_args 'Here' "are" some '"very interesting"' 'arguments[to]' "`"try out`"" chef-12.14.60/ext/000077500000000000000000000000001276456504500134705ustar00rootroot00000000000000chef-12.14.60/ext/win32-eventlog/000077500000000000000000000000001276456504500162535ustar00rootroot00000000000000chef-12.14.60/ext/win32-eventlog/Rakefile000066400000000000000000000023641276456504500177250ustar00rootroot00000000000000require "rubygems" require "rake" require "mkmf" desc "Building event log dll" def ensure_present(commands) commands.each do |c| unless find_executable c warn "Could not find #{c}. Windows Event Logging will not correctly function." end end end EVT_MC_FILE = "chef-log.man" EVT_RC_FILE = "chef-log.rc" EVT_RESOURCE_OBJECT = "resource.o" EVT_SHARED_OBJECT = "chef-log.dll" MC = "windmc" RC = "windres" CC = "gcc" ensure_present [MC, RC, CC] task :build => [EVT_RESOURCE_OBJECT, EVT_SHARED_OBJECT] task :default => [:build, :register] file EVT_RC_FILE => EVT_MC_FILE do sh "#{MC} #{EVT_MC_FILE}" end file EVT_RESOURCE_OBJECT => EVT_RC_FILE do sh "#{RC} -i #{EVT_RC_FILE} -o #{EVT_RESOURCE_OBJECT}" end file EVT_SHARED_OBJECT => EVT_RESOURCE_OBJECT do sh "#{CC} -o #{EVT_SHARED_OBJECT} -shared #{EVT_RESOURCE_OBJECT}" end task :register => EVT_SHARED_OBJECT do require "win32/eventlog" dll_file = File.expand_path(EVT_SHARED_OBJECT) begin Win32::EventLog.add_event_source( :source => "Application", :key_name => "Chef", :event_message_file => dll_file, :category_message_file => dll_file ) rescue Errno::EIO => e puts "Skipping event log registration due to missing privileges: #{e}" end end chef-12.14.60/ext/win32-eventlog/chef-log.man000066400000000000000000000014151276456504500204350ustar00rootroot00000000000000MessageId=10000 SymbolicName=RUN_START Language=English Starting Chef Client run v%1 . MessageId=10001 SymbolicName=RUN_STARTED Language=English Started Chef Client run %1 . MessageId=10002 SymbolicName=RUN_COMPLETED Language=English Completed Chef Client run %1 in %2 seconds . MessageId=10003 SymbolicName=RUN_FAILED Language=English Failed Chef Client run %1 in %2 seconds.%n Exception type: %3%n Exception message: %4%n Exception backtrace: %5%n . MessageId=10100 SymbolicName=INFO Language=English [INFO] %1 . MessageId=10101 SymbolicName=WARN Language=English [WARN] %1 . MessageId=10102 SymbolicName=DEBUG Language=English [DEBUG] %1 . MessageId=10103 SymbolicName=ERROR Language=English [ERROR] %1 . MessageId=10104 SymbolicName=FATAL Language=English [FATAL] %1 . chef-12.14.60/kitchen-tests/000077500000000000000000000000001276456504500154555ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/.chef/000077500000000000000000000000001276456504500164405ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/.chef/client.rb000066400000000000000000000003621276456504500202440ustar00rootroot00000000000000 chef_dir = File.expand_path(File.dirname(__FILE__)) repo_dir = File.expand_path(File.join(chef_dir, '..')) log_level :info chef_repo_path repo_dir local_mode true cache_path "#{ENV['HOME']}/.cache/chef" audit_mode :enabledchef-12.14.60/kitchen-tests/.kitchen.travis.yml000066400000000000000000000063771276456504500212270ustar00rootroot00000000000000--- driver: name: dokken privileged: true chef_version: latest transport: name: dokken provisioner: name: chef_github root_path: /opt/kitchen require_chef_omnibus: latest chef_omnibus_url: "https://omnitruck.chef.io/install.sh" chef_omnibus_install_options: "-c current" github_owner: "chef" github_repo: "chef" refname: <%= ENV['TRAVIS_COMMIT'] %> github_access_token: <%= ENV['KITCHEN_GITHUB_TOKEN'] %> data_path: test/fixtures # disable file provider diffs so we don't overflow travis' line limit client_rb: diff_disabled: true verifier: name: inspec format: progress platforms: - 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 wget ca-certificates - 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 wget ca-certificates - 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 wget - 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 wget - 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 wget - 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 wget - RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers - name: ubuntu-12.04 driver: image: ubuntu-upstart:12.04 pid_one_command: /sbin/init intermediate_instructions: - RUN /usr/bin/apt-get update - RUN /usr/bin/apt-get -y install zlib1g-dev sudo net-tools wget ca-certificates - name: ubuntu-14.04 driver: image: ubuntu-upstart:14.04 pid_one_command: /sbin/init intermediate_instructions: - RUN /usr/bin/apt-get update - RUN /usr/bin/apt-get -y install zlib1g-dev sudo net-tools wget ca-certificates - name: ubuntu-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 wget ca-certificates - 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] chef-12.14.60/kitchen-tests/.kitchen.yml000066400000000000000000000012671276456504500177110ustar00rootroot00000000000000--- driver: name: vagrant customize: cpus: 4 memory: 2048 verifier: name: inspec format: progress provisioner: name: chef_github chef_omnibus_url: "https://omnitruck.chef.io/install.sh" chef_omnibus_install_options: "-c current" github_owner: "chef" github_repo: "chef" refname: <%= %x(git rev-parse HEAD) %> data_path: test/fixtures client_rb: diff_disabled: true platforms: - name: ubuntu-12.04 - name: ubuntu-14.04 - name: ubuntu-16.04 - name: centos-7.2 - name: centos-6.7 # needs fixing for 5.11 # - name: centos-5.11 suites: - name: webapp run_list: - recipe[base::default] # - recipe[webapp::default] attributes: chef-12.14.60/kitchen-tests/Berksfile000066400000000000000000000003221276456504500173030ustar00rootroot00000000000000source "https://supermarket.getchef.com" cookbook "webapp", path: "cookbooks/webapp" cookbook "base", path: "cookbooks/base" cookbook "php", "~> 1.5.0" cookbook "resolver", github: "chef-cookbooks/resolver" chef-12.14.60/kitchen-tests/Berksfile.lock000066400000000000000000000046341276456504500202440ustar00rootroot00000000000000DEPENDENCIES base path: cookbooks/base php (~> 1.5.0) resolver git: git://github.com/chef-cookbooks/resolver.git revision: 8bf9034dabc47d29a07870e4059c32114f2c820a webapp path: cookbooks/webapp GRAPH apache2 (3.2.2) apt (4.0.2) compat_resource (>= 12.10) aws (3.4.1) ohai (>= 2.1.0) base (0.1.0) apt (>= 0.0.0) build-essential (>= 0.0.0) chef-client (>= 0.0.0) chef_hostname (>= 0.0.0) logrotate (>= 0.0.0) multipackage (>= 0.0.0) nscd (>= 0.0.0) ntp (>= 0.0.0) openssh (>= 0.0.0) resolver (>= 0.0.0) selinux (>= 0.0.0) sudo (>= 0.0.0) ubuntu (>= 0.0.0) users (>= 0.0.0) yum-epel (>= 0.0.0) build-essential (6.0.4) compat_resource (>= 12.10) mingw (>= 1.1) seven_zip (>= 0.0.0) chef-client (5.0.0) cron (>= 1.7.0) logrotate (>= 1.9.0) windows (>= 1.42.0) chef-sugar (3.4.0) chef_handler (1.4.0) chef_hostname (0.4.1) compat_resource (>= 0.0.0) compat_resource (12.10.7) cron (1.7.6) database (2.3.1) aws (>= 0.0.0) mysql (~> 5.0) mysql-chef_gem (~> 0.0) postgresql (>= 1.0.0) xfs (>= 0.0.0) iis (4.2.0) windows (>= 1.34.6) iptables (2.2.0) logrotate (2.1.0) compat_resource (>= 0.0.0) mingw (1.2.4) compat_resource (>= 0.0.0) seven_zip (>= 0.0.0) multipackage (3.0.28) compat_resource (>= 0.0.0) mysql (5.6.3) yum-mysql-community (>= 0.0.0) mysql-chef_gem (0.0.5) build-essential (>= 0.0.0) mysql (>= 0.0.0) nscd (4.1.0) compat_resource (>= 0.0.0) ntp (2.0.0) windows (>= 1.38.0) ohai (4.2.0) compat_resource (>= 12.10) openssh (2.0.0) iptables (>= 1.0) openssl (4.4.0) chef-sugar (>= 3.1.1) php (1.5.0) build-essential (>= 0.0.0) iis (>= 0.0.0) mysql (>= 0.0.0) windows (>= 0.0.0) xml (>= 0.0.0) yum-epel (>= 0.0.0) postgresql (4.0.6) apt (>= 1.9.0) build-essential (>= 0.0.0) openssl (~> 4.0) resolver (1.3.1) selinux (0.9.0) seven_zip (2.0.1) windows (>= 1.2.2) sudo (2.11.0) ubuntu (1.2.1) apt (>= 0.0.0) users (3.0.0) webapp (0.1.0) apache2 (~> 3.2.2) database (~> 2.3.1) mysql (~> 5.6.3) php (~> 1.5.0) windows (1.44.3) chef_handler (>= 0.0.0) xfs (2.0.1) xml (2.0.0) build-essential (>= 0.0.0) chef-sugar (>= 0.0.0) yum (3.11.0) yum-epel (0.7.1) yum (>= 3.6.3) yum-mysql-community (0.3.0) yum (>= 3.2) chef-12.14.60/kitchen-tests/Gemfile000066400000000000000000000003271276456504500167520ustar00rootroot00000000000000source "https://rubygems.org" 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" chef-12.14.60/kitchen-tests/Gemfile.lock000066400000000000000000000134251276456504500177040ustar00rootroot00000000000000GEM remote: https://rubygems.org/ specs: addressable (2.4.0) artifactory (2.3.3) aws-sdk (2.5.5) aws-sdk-resources (= 2.5.5) aws-sdk-core (2.5.5) jmespath (~> 1.0) aws-sdk-resources (2.5.5) aws-sdk-core (= 2.5.5) berkshelf (4.3.5) addressable (~> 2.3, >= 2.3.4) berkshelf-api-client (~> 2.0, >= 2.0.2) buff-config (~> 1.0) buff-extensions (~> 1.0) buff-shell_out (~> 0.1) celluloid (= 0.16.0) celluloid-io (~> 0.16.1) cleanroom (~> 1.0) faraday (~> 0.9) httpclient (~> 2.7) minitar (~> 0.5, >= 0.5.4) mixlib-archive (~> 0.1) octokit (~> 4.0) retryable (~> 2.0) ridley (~> 4.5) solve (~> 2.0) thor (~> 0.19) berkshelf-api-client (2.0.2) faraday (~> 0.9.1) httpclient (~> 2.7.0) ridley (~> 4.5) buff-config (1.0.1) buff-extensions (~> 1.0) varia_model (~> 0.4) buff-extensions (1.0.0) buff-ignore (1.1.1) 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.13.37) fuzzyurl mixlib-config (~> 2.0) mixlib-shellout (~> 2.0) cleanroom (1.0.0) coderay (1.1.1) diff-lcs (1.2.5) docker-api (1.31.0) excon (>= 0.38.0) json erubis (2.7.0) excon (0.52.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) ffi (1.9.14) ffi (1.9.14-x86-mingw32) fuzzyurl (0.9.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.31.0) hashie (~> 3.4) json (~> 1.8) method_source (~> 0.8) molinillo (~> 0) pry (~> 0) rainbow (~> 2) rspec (~> 3) rspec-its (~> 1.2) rubyzip (~> 1.1) sslshake (~> 1) thor (~> 0.19) train (>= 0.16.0, < 1.0) jmespath (1.3.1) json (1.8.3) kitchen-appbundle-updater (0.1.2) kitchen-dokken (0.0.32) docker-api (~> 1.29) test-kitchen (~> 1.5) kitchen-ec2 (1.1.0) aws-sdk (~> 2) excon multi_json retryable (~> 2.0) test-kitchen (~> 1.4, >= 1.4.1) kitchen-inspec (0.15.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-archive (0.2.0) mixlib-log mixlib-authentication (1.4.1) mixlib-log mixlib-config (2.2.2) mixlib-install (1.1.0) artifactory mixlib-shellout mixlib-versioning mixlib-log (1.7.1) mixlib-shellout (2.2.7) mixlib-shellout (2.2.7-universal-mingw32) win32-process (~> 0.8.2) wmi-lite (~> 1.0) mixlib-versioning (1.1.0) molinillo (0.4.5) multi_json (1.12.1) multipart-post (2.0.0) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (3.2.0) net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) nio4r (1.2.1) nori (2.6.0) octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) rainbow (2.1.0) retryable (2.0.4) ridley (4.6.1) addressable buff-config (~> 1.0) buff-extensions (~> 1.0) buff-ignore (~> 1.1.1) buff-shell_out (~> 0.1) celluloid (~> 0.16.0) celluloid-io (~> 0.16.1) chef-config (>= 12.5.0) erubis faraday (~> 0.9.0) hashie (>= 2.0.2, < 4.0.0) httpclient (~> 2.7) json (>= 1.7.7) mixlib-authentication (>= 1.3.0) retryable (~> 2.0) semverse (~> 1.1) varia_model (~> 0.4.0) rspec (3.5.0) rspec-core (~> 3.5.0) rspec-expectations (~> 3.5.0) rspec-mocks (~> 3.5.0) rspec-core (3.5.2) rspec-support (~> 3.5.0) rspec-expectations (3.5.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.5.0) rspec-its (1.2.0) rspec-core (>= 3.0.0) rspec-expectations (>= 3.0.0) rspec-mocks (3.5.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.5.0) rspec-support (3.5.0) 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) sslshake (1.0.12) test-kitchen (1.11.1) mixlib-install (~> 1.0, >= 1.0.4) mixlib-shellout (>= 1.2, < 3.0) net-scp (~> 1.1) net-ssh (>= 2.9, < 4.0) net-ssh-gateway (~> 1.2.0) safe_yaml (~> 1.0) thor (~> 0.18) thor (0.19.1) timers (4.0.4) hitimes train (0.17.0) 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) vagrant-wrapper (2.0.3) 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) 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.3) erubis (~> 2.7) logging (>= 1.6.1, < 3.0) rubyzip (~> 1.1) winrm (~> 1.5) wmi-lite (1.0.0) PLATFORMS ruby x86-mingw32 DEPENDENCIES berkshelf kitchen-appbundle-updater kitchen-dokken kitchen-ec2 kitchen-inspec kitchen-vagrant ridley test-kitchen vagrant-wrapper BUNDLED WITH 1.12.5 chef-12.14.60/kitchen-tests/README.md000066400000000000000000000112371276456504500167400ustar00rootroot00000000000000# End-To-End Testing for Chef Client Here we seek to provide end-to-end testing of Chef Client through cookbooks which exercise many of the available resources, providers, and common patterns. The cookbooks here are designed to ensure certain capabilities remain functional with updates to the client code base. ## Getting started All the gems needed to run these tests can be installed with Bundler. ```shell chef/kitchen-tests$ bundle install ``` To ensure everything is working properly, and to see which platforms can have tests executed on them, run ```shell chef/kitchen-tests$ bundle exec kitchen list ``` You should see output similar to ```shell Instance Driver Provisioner Last Action webapp-ubuntu-1204 Vagrant ChefSolo ``` ## Testing We use Test Kitchen to build instances, test client code, and destroy instances. If you are unfamiliar with Test Kitchen we recommend checking out the [tutorial](http://kitchen.ci/) along with the `kitchen-vagrant` [driver documentation](https://github.com/test-kitchen/kitchen-vagrant). Test Kitchen is configured to manipulate instances using [Vagrant](http://www.vagrantup.com/) when testing locally, and [Amazon EC2](http://aws.amazon.com/ec2/) when testing pull requests on [Travis CI](https://travis-ci.com). ### Commands Kitchen instances are led through a series of states. The instance states, and the actions taken to transition into each state, are in order: * `destroy`: Delete all information for and terminate one or more instances. * This is equivalent to running `vagrant destroy` to stop and delete a Vagrant machine. * `create`: Start one or more instances. * This is equivalent to running `vagrant up --no-provision` to start a Vagrant instance. * `converge`: Use a provisioner to configure one or more instances. * By default, Test Kitchen is configured to use the `ChefSolo` provisioner which: * Prepares local files for transfer, * Installs the latest release of Chef Omnibus, * Downloads Chef Client source code from the prescribed GitHub repository and reference, * Builds and installs a `chef` gem from the downloaded source, * Runs `chef-client`. * `setup`: Prepare to run automated tests. Installs `busser` and related gems on one or more instances. * `verify`: Run automated tests on one or more instances. When transitioning between states, actions for any and all intermediate states will performed. Executing the `create` then the `verify` commands is equivalent to executing `create`, `converge`, `setup`, and `verify` one-by-one and in order. The only exception is `destroy`, which will immediately transfer that machine's state to destroyed. The `test` command takes one or more instances through all the states, in order: `destroy`, `create`, `converge`, `setup`, `verify`, `destroy`. To see a list of available commands, type `bundle exec kitchen help`. To see more information about a particular command, type `bundle exec kitchen help `. ### Configuring your tests Test Kitchen is configured for local testing in the `.kitchen.yml` file which resides in this directory. You will need to configure the provisioner before running the tests. The provisioner can be configured to pull client source code from a GitHub repository using any valid Git reference. You are encouraged to modify any of these settings, but please return them to their original values before submitting a pull request for review (unless, of course, your changes are enhancements to the default provisioner settings). By default, the provisioner is configured to pull your most recent commit to `opscode/chef`. You can change this by modifying the `github` and `branch` provisioner options: * `github`: Set this to `"/"`. The default is `"opscode/chef"`. * `branch`: This can be any valid git reference (e.g., branch name, tag, or commit SHA). If omitted, it defaults to `master`. The branch you choose must be accessible on GitHub. You cannot use a local commit at this time. ### Testing pull requests These end-to-end tests are also configured to run with Travis on EC2 instances when you submit a pull request to `opscode/chef`. Kitchen is configured to pull chef client source code from the branch it is testing. There is no need to modify `.kitchen.travis.yml` unless you are contributing tests. ## Contributing We would love to fill out our end-to-end testing coverage! If you have cookbooks and tests that you would like to see become a part of client testing, we encourage you to submit a pull request with your additions. We request that you do not add platforms to `.kitchen.travis.yml`. Please file a request to add a platform under [Issues](https://github.com/opscode/chef/issues). chef-12.14.60/kitchen-tests/cookbooks/000077500000000000000000000000001276456504500174465ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/audit_test/000077500000000000000000000000001276456504500216135ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/audit_test/.gitignore000066400000000000000000000001571276456504500236060ustar00rootroot00000000000000.vagrant *~ *# .#* \#*# .*.sw[a-z] *.un~ # Bundler Gemfile.lock bin/* .bundle/* .kitchen/ .kitchen.local.yml chef-12.14.60/kitchen-tests/cookbooks/audit_test/.kitchen.yml000066400000000000000000000003211276456504500240350ustar00rootroot00000000000000--- driver: name: vagrant provisioner: name: chef_zero platforms: - name: ubuntu-12.04 - name: centos-6.5 suites: - name: default run_list: - recipe[audit_test::default] attributes: chef-12.14.60/kitchen-tests/cookbooks/audit_test/Berksfile000066400000000000000000000000631276456504500234430ustar00rootroot00000000000000source "https://supermarket.getchef.com" metadata chef-12.14.60/kitchen-tests/cookbooks/audit_test/Berksfile.lock000066400000000000000000000001251276456504500243710ustar00rootroot00000000000000DEPENDENCIES audit_test path: . metadata: true GRAPH audit_test (0.1.0) chef-12.14.60/kitchen-tests/cookbooks/audit_test/README.md000066400000000000000000000004611276456504500230730ustar00rootroot00000000000000# audit_test This cookbook has some basic recipes to test audit mode. In order to run these tests on your dev box: ``` $ bundle install $ bundle exec chef-client -c kitchen-tests/.chef/client.rb -z -o audit_test::default -l debug ``` Expected JSON output for the tests will be printed to `debug` log. chef-12.14.60/kitchen-tests/cookbooks/audit_test/chefignore000066400000000000000000000017161276456504500236540ustar00rootroot00000000000000# Put files/directories that should be ignored in this file when uploading # or sharing to the community site. # Lines that start with '# ' are comments. # OS generated files # ###################### .DS_Store Icon? nohup.out ehthumbs.db Thumbs.db # SASS # ######## .sass-cache # EDITORS # ########### \#* .#* *~ *.sw[a-z] *.bak REVISION TAGS* tmtags *_flymake.* *_flymake *.tmproj .project .settings mkmf.log ## COMPILED ## ############## a.out *.o *.pyc *.so *.com *.class *.dll *.exe */rdoc/ # Testing # ########### .watchr .rspec spec/* spec/fixtures/* test/* features/* Guardfile Procfile # SCM # ####### .git */.git .gitignore .gitmodules .gitconfig .gitattributes .svn */.bzr/* */.hg/* */.svn/* # Berkshelf # ############# Berksfile Berksfile.lock cookbooks/* tmp # Cookbooks # ############# CONTRIBUTING # Strainer # ############ Colanderfile Strainerfile .colander .strainer # Vagrant # ########### .vagrant Vagrantfile # Travis # ########## .travis.yml chef-12.14.60/kitchen-tests/cookbooks/audit_test/metadata.rb000066400000000000000000000003731276456504500237230ustar00rootroot00000000000000name "audit_test" maintainer "The Authors" maintainer_email "you@example.com" license "all_rights" description "Installs/Configures audit_test" long_description "Installs/Configures audit_test" version "0.1.0" chef-12.14.60/kitchen-tests/cookbooks/audit_test/recipes/000077500000000000000000000000001276456504500232455ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/audit_test/recipes/default.rb000066400000000000000000000007571276456504500252270ustar00rootroot00000000000000# # Cookbook Name:: audit_test # Recipe:: default # # Copyright 2014-2016, The Authors, All Rights Reserved. control_group "basic control group" do control "basic math" do it "should pass" do expect(2 - 2).to eq(0) end end end control_group "control group without top level control" do it "should pass" do expect(2 - 2).to eq(0) end end control_group "control group with empty control" do control "empty" end control_group "empty control group with block" do end chef-12.14.60/kitchen-tests/cookbooks/audit_test/recipes/error_duplicate_control_groups.rb000066400000000000000000000005121276456504500321120ustar00rootroot00000000000000# # Cookbook Name:: audit_test # Recipe:: error_duplicate_control_groups # # Copyright 2014-2016, The Authors, All Rights Reserved. control_group "basic control group" do it "should pass" do expect(2 - 2).to eq(0) end end control_group "basic control group" do it "should pass" do expect(2 - 2).to eq(0) end end chef-12.14.60/kitchen-tests/cookbooks/audit_test/recipes/error_no_block.rb000066400000000000000000000002471276456504500265740ustar00rootroot00000000000000# # Cookbook Name:: audit_test # Recipe:: error_no_block # # Copyright 2014-2016, The Authors, All Rights Reserved. control_group "empty control group without block" chef-12.14.60/kitchen-tests/cookbooks/audit_test/recipes/error_orphan_control.rb000066400000000000000000000003671276456504500300400ustar00rootroot00000000000000# # Cookbook Name:: audit_test # Recipe:: error_orphan_control # # Copyright 2014-2016, The Authors, All Rights Reserved. control_group "basic control group" do it "should pass" do expect(2 - 2).to eq(0) end end control "orphan control" chef-12.14.60/kitchen-tests/cookbooks/audit_test/recipes/failed_specs.rb000066400000000000000000000004411276456504500262120ustar00rootroot00000000000000# # Cookbook Name:: audit_test # Recipe:: failed_specs # # Copyright 2014-2016, The Authors, All Rights Reserved. control_group "basic control group" do control "basic math" do # Can not write a good control :( it "should pass" do expect(2 - 0).to eq(0) end end end chef-12.14.60/kitchen-tests/cookbooks/audit_test/recipes/serverspec_collision.rb000066400000000000000000000011651276456504500300310ustar00rootroot00000000000000# # Cookbook Name:: audit_test # Recipe:: serverspec_collision # # Copyright 2014-2016, The Authors, All Rights Reserved. file "/tmp/audit_test_file" do action :create content "Welcome to audit mode." end control_group "file auditing" do describe "test file" do it "says welcome" do expect(file("/tmp/audit_test_file")).to contain("Welcome") end end end file "/tmp/audit_test_file_2" do action :create content "Bye to audit mode." end control_group "end file auditing" do describe "end file" do it "says bye" do expect(file("/tmp/audit_test_file_2")).to contain("Bye") end end end chef-12.14.60/kitchen-tests/cookbooks/audit_test/recipes/serverspec_support.rb000066400000000000000000000014051276456504500275470ustar00rootroot00000000000000# # Cookbook Name:: audit_test # Recipe:: serverspec_support # # Copyright 2014-2016, The Authors, All Rights Reserved. file "/tmp/audit_test_file" do action :create content "Welcome to audit mode." end # package "curl" do # action :install # end control_group "serverspec helpers with types" do control "file helper" do it "says welcome" do expect(file("/tmp/audit_test_file")).to contain("Welcome") end end control service("com.apple.CoreRAID") do it { is_expected.to be_enabled } it { is_expected.not_to be_running } end # describe "package helper" do # it "works" do # expect(package("curl")).to be_installed # end # end control package("postgresql") do it { is_expected.to_not be_installed } end end chef-12.14.60/kitchen-tests/cookbooks/audit_test/recipes/with_include_recipe.rb000066400000000000000000000005411276456504500275770ustar00rootroot00000000000000# # Cookbook Name:: audit_test # Recipe:: with_include_recipe # # Copyright 2014-2016, The Authors, All Rights Reserved. include_recipe "audit_test::serverspec_collision" control_group "basic example" do it "should pass" do expect(2 - 2).to eq(0) end end include_recipe "audit_test::serverspec_collision" include_recipe "audit_test::default" chef-12.14.60/kitchen-tests/cookbooks/base/000077500000000000000000000000001276456504500203605ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/base/Berksfile000066400000000000000000000000751276456504500222130ustar00rootroot00000000000000source "https://api.berkshelf.com" metadata cookbook "apt" chef-12.14.60/kitchen-tests/cookbooks/base/README.md000066400000000000000000000000651276456504500216400ustar00rootroot00000000000000# webapp TODO: Enter the cookbook description here. chef-12.14.60/kitchen-tests/cookbooks/base/attributes/000077500000000000000000000000001276456504500225465ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/base/attributes/default.rb000066400000000000000000000067631276456504500245330ustar00rootroot00000000000000puts "CHEF SUGAR THINKS WE ARE ON UBUNTU" if ubuntu? puts "CHEF SUGAR THINKS WE ARE ON RHEL" if rhel? # # ubuntu cookbook overrides # default["ubuntu"]["include_source_packages"] = true default["ubuntu"]["components"] = "main restricted universe multiverse" # # openssh cookbook overrides # # turn off old protocols client-side default["openssh"]["client"]["rsa_authentication"] = "no" default["openssh"]["client"]["host_based_authentication"] = "no" # allow typical ssh v2 rsa/dsa/ecdsa key auth client-side default["openssh"]["client"]["pubkey_authentication"] = "yes" # allow password auth client-side (we can ssh 'to' hosts that require passwords) default["openssh"]["client"]["password_authentication"] = "yes" # turn off kerberos client-side default["openssh"]["client"]["gssapi_authentication"] = "no" default["openssh"]["client"]["check_host_ip"] = "no" # everone turns strict host key checking off anyway default["openssh"]["client"]["strict_host_key_checking"] = "no" # force protocol 2 default["openssh"]["client"]["protocol"] = "2" # it is mostly important that the aes*-ctr ciphers appear first in this list, the cbc ciphers are for compatibility default["openssh"]["server"]["ciphers"] = "aes256-ctr,aes192-ctr,aes128-ctr,aes256-cbc,aes192-cbc,aes128-cbc,blowfish-cbc,3des-cbc,cast128-cbc" # DNS causes long timeouts when connecting clients have busted DNS default["openssh"]["server"]["use_dns"] = "no" default["openssh"]["server"]["syslog_facility"] = "AUTH" # only allow access via ssh pubkeys, all other mechanisms including passwords are turned off for all users default["openssh"]["server"]["pubkey_authentication"] = "yes" default["openssh"]["server"]["rhosts_rsa_authentication"] = "no" default["openssh"]["server"]["rsa_authentication"] = "no" default["openssh"]["server"]["password_authentication"] = "no" default["openssh"]["server"]["host_based_authentication"] = "no" default["openssh"]["server"]["gssapi_authentication"] = "no" default["openssh"]["server"]["permit_root_login"] = "without-password" default["openssh"]["server"]["ignore_rhosts"] = "yes" default["openssh"]["server"]["permit_empty_passwords"] = "no" default["openssh"]["server"]["challenge_response_authentication"] = "no" default["openssh"]["server"]["kerberos_authentication"] = "no" # tcp keepalives are useful to keep connections up through VPNs and firewalls default["openssh"]["server"]["tcp_keepalive"] = "yes" default["openssh"]["server"]["use_privilege_separation"] = "yes" default["openssh"]["server"]["max_start_ups"] = "10" # PAM (i think) already prints the motd on login default["openssh"]["server"]["print_motd"] = "no" # force only protocol 2 connections default["openssh"]["server"]["protocol"] = "2" # allow tunnelling x-applications back to the client default["openssh"]["server"]["x11_forwarding"] = "yes" # # chef-client cookbook overrides # # always wait at least 30 mins (1800 secs) between daemonized chef-client runs default["chef_client"]["interval"] = 1800 # wait an additional random interval of up to 30 mins (1800 secs) between daemonized runs default["chef_client"]["splay"] = 1800 # only log what we change default["chef_client"]["config"]["verbose_logging"] = false # # resolver cookbook overrides # default["resolver"]["nameservers"] = [ "8.8.8.8", "8.8.4.4" ] default["resolver"]["search"] = "chef.io" # # sudo cookbook overrides # default["authorization"]["sudo"]["passwordless"] = true default["authorization"]["sudo"]["users"] = %w{vagrant centos ubuntu} # # nscd cookbook overrides # default["nscd"]["server_user"] = "nobody" chef-12.14.60/kitchen-tests/cookbooks/base/libraries/000077500000000000000000000000001276456504500223345ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/base/libraries/chef-sugar.rb000066400000000000000000000002341276456504500247040ustar00rootroot00000000000000require "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) chef-12.14.60/kitchen-tests/cookbooks/base/metadata.rb000066400000000000000000000011501276456504500224620ustar00rootroot00000000000000name "base" maintainer "" maintainer_email "" license "" 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 "chef_hostname" depends "logrotate" depends "multipackage" depends "nscd" depends "ntp" depends "openssh" depends "resolver" depends "selinux" depends "sudo" depends "ubuntu" depends "users" chef-12.14.60/kitchen-tests/cookbooks/base/recipes/000077500000000000000000000000001276456504500220125ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/base/recipes/default.rb000066400000000000000000000022421276456504500237630ustar00rootroot00000000000000# # Cookbook Name:: webapp # Recipe:: default # # Copyright (C) 2014 # 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"]) include_recipe "selinux::disabled" end yum_repository "epel" do enabled true description "Extra Packages for Enterprise Linux #{node['platform_version'].to_i} - $basearch" failovermethod "priority" gpgkey "https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-#{node['platform_version'].to_i}" gpgcheck true mirrorlist "https://mirrors.fedoraproject.org/metalink?repo=epel-#{node['platform_version'].to_i}&arch=$basearch" only_if { node["platform_family"] == "rhel" } end include_recipe "build-essential" include_recipe "#{cookbook_name}::packages" include_recipe "ntp" include_recipe "resolver" include_recipe "users::sysadmins" include_recipe "sudo" include_recipe "chef-client::delete_validation" include_recipe "chef-client::config" include_recipe "chef-client" # hack needed for debian-7 on docker directory "/var/run/sshd" include_recipe "openssh" include_recipe "nscd" include_recipe "logrotate" chef-12.14.60/kitchen-tests/cookbooks/base/recipes/packages.rb000066400000000000000000000006321276456504500241160ustar00rootroot00000000000000 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. pkgs.each do |pkg| multipackage pkgs end gems = %w{fpm aws-sdk} gems.each do |gem| chef_gem gem do compile_time false end end chef-12.14.60/kitchen-tests/cookbooks/webapp/000077500000000000000000000000001276456504500207245ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/webapp/Berksfile000066400000000000000000000000751276456504500225570ustar00rootroot00000000000000source "https://api.berkshelf.com" metadata cookbook "apt" chef-12.14.60/kitchen-tests/cookbooks/webapp/README.md000066400000000000000000000000651276456504500222040ustar00rootroot00000000000000# webapp TODO: Enter the cookbook description here. chef-12.14.60/kitchen-tests/cookbooks/webapp/attributes/000077500000000000000000000000001276456504500231125ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/webapp/attributes/default.rb000066400000000000000000000007031276456504500250630ustar00rootroot00000000000000default["apache"]["remote_host_ip"] = "127.0.0.1" default["webapp"]["database"] = "webapp" default["webapp"]["db_username"] = "webapp" default["webapp"]["path"] = "/srv/webapp" # XXX: apache2 cookbook 2.0.0 has bugs around changing the mpm and then attempting a graceful restart # which fails and leaves the service down. case node["platform"] when "ubuntu" if node["platform_version"].to_f >= 14.04 default[:apache][:mpm] = "event" end end chef-12.14.60/kitchen-tests/cookbooks/webapp/metadata.rb000066400000000000000000000004771276456504500230410ustar00rootroot00000000000000name "webapp" maintainer "" maintainer_email "" license "" description "Installs/Configures webapp" long_description "Installs/Configures webapp" version "0.1.0" depends "apache2", "~> 3.2.2" depends "database", "~> 2.3.1" depends "mysql", "~> 5.6.3" depends "php", "~> 1.5.0" chef-12.14.60/kitchen-tests/cookbooks/webapp/recipes/000077500000000000000000000000001276456504500223565ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/webapp/recipes/default.rb000066400000000000000000000026251276456504500243340ustar00rootroot00000000000000# # Cookbook Name:: webapp # Recipe:: default # # Copyright (C) 2014 # include_recipe "apache2" include_recipe "database::mysql" include_recipe "php" creds = Hash.new %w{mysql webapp}.each do |item_name| creds[item_name] = data_bag_item("passwords", item_name) end web_app "webapp" do server_name "localhost" server_aliases [node["fqdn"], node["hostname"], "localhost.localdomain"] docroot node["webapp"]["path"] cookbook "apache2" end mysql_service "default" do server_root_password creds["mysql"]["server_root_password"] server_repl_password creds["mysql"]["server_repl_password"] end mysql_database node["webapp"]["database"] do connection ({ :host => "localhost", :username => "root", :password => creds["mysql"]["server_root_password"], }) action :create end mysql_database_user node["webapp"]["db_username"] do connection ({ :host => "localhost", :username => "root", :password => creds["mysql"]["server_root_password"], }) password creds["webapp"]["db_password"] database_name node["webapp"]["database"] privileges [:select, :update, :insert, :create, :delete] action :grant end directory node["webapp"]["path"] do owner "root" group "root" mode "0755" action :create recursive true end template "#{node['webapp']['path']}/index.html" do source "index.html.erb" end template "#{node['webapp']['path']}/index.php" do source "index.php.erb" end chef-12.14.60/kitchen-tests/cookbooks/webapp/templates/000077500000000000000000000000001276456504500227225ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/webapp/templates/default/000077500000000000000000000000001276456504500243465ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/cookbooks/webapp/templates/default/index.html.erb000066400000000000000000000000751276456504500271140ustar00rootroot00000000000000

Hello, World!

chef-12.14.60/kitchen-tests/cookbooks/webapp/templates/default/index.php.erb000066400000000000000000000001731276456504500267360ustar00rootroot00000000000000 PHP Test Hello, World!

'; ?> chef-12.14.60/kitchen-tests/data_bags/000077500000000000000000000000001276456504500173625ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/data_bags/passwords/000077500000000000000000000000001276456504500214075ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/data_bags/passwords/mysql.json000066400000000000000000000001761276456504500234530ustar00rootroot00000000000000{ "id": "mysql", "server_root_password": "ilikerandompasswordstoo", "server_repl_password": "itoolikerandompasswords" } chef-12.14.60/kitchen-tests/data_bags/passwords/webapp.json000066400000000000000000000000771276456504500235640ustar00rootroot00000000000000{ "id": "webapp", "db_password": "supersecretdbpassword" } chef-12.14.60/kitchen-tests/data_bags/users/000077500000000000000000000000001276456504500205235ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/data_bags/users/adam.json000066400000000000000000000003401276456504500223150ustar00rootroot00000000000000{ "id": "adam", "uid": 666, // yes? i figure adam likes metal, shout out to iron maiden... "gid": 666, "shell": "/bin/zsh", "groups": [ "sysadmin" ], "comment": "Adam Jacob", "password": "*" } chef-12.14.60/kitchen-tests/test/000077500000000000000000000000001276456504500164345ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/test/fixtures/000077500000000000000000000000001276456504500203055ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/test/fixtures/platforms/000077500000000000000000000000001276456504500223145ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/test/fixtures/platforms/centos/000077500000000000000000000000001276456504500236075ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/test/fixtures/platforms/centos/5.json000066400000000000000000000003461276456504500246510ustar00rootroot00000000000000{ "apache": { "package": "httpd", "service_name": "httpd" }, "mysql": { "server_package": "mysql-server", "client_package": "mysql", "service_name": "mysqld" }, "php" : { "package": "php53" } } chef-12.14.60/kitchen-tests/test/fixtures/platforms/centos/6.json000066400000000000000000000003441276456504500246500ustar00rootroot00000000000000{ "apache": { "package": "httpd", "service_name": "httpd" }, "mysql": { "server_package": "mysql-server", "client_package": "mysql", "service_name": "mysqld" }, "php" : { "package": "php" } } chef-12.14.60/kitchen-tests/test/fixtures/platforms/ubuntu/000077500000000000000000000000001276456504500236365ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/test/fixtures/platforms/ubuntu/10.04.json000066400000000000000000000003671276456504500252010ustar00rootroot00000000000000{ "apache": { "package": "apache2", "service_name": "apache2" }, "mysql": { "server_package": "mysql-server-5.1", "client_package": "mysql-client-5.1", "service_name": "mysql" }, "php" : { "package": "php5" } } chef-12.14.60/kitchen-tests/test/fixtures/platforms/ubuntu/12.04.json000066400000000000000000000003671276456504500252030ustar00rootroot00000000000000{ "apache": { "package": "apache2", "service_name": "apache2" }, "mysql": { "server_package": "mysql-server-5.5", "client_package": "mysql-client-5.5", "service_name": "mysql" }, "php" : { "package": "php5" } } chef-12.14.60/kitchen-tests/test/fixtures/platforms/ubuntu/14.04.json000066400000000000000000000003671276456504500252050ustar00rootroot00000000000000{ "apache": { "package": "apache2", "service_name": "apache2" }, "mysql": { "server_package": "mysql-server-5.5", "client_package": "mysql-client-5.5", "service_name": "mysql" }, "php" : { "package": "php5" } } chef-12.14.60/kitchen-tests/test/fixtures/platforms/ubuntu/14.10.json000066400000000000000000000003671276456504500252020ustar00rootroot00000000000000{ "apache": { "package": "apache2", "service_name": "apache2" }, "mysql": { "server_package": "mysql-server-5.5", "client_package": "mysql-client-5.5", "service_name": "mysql" }, "php" : { "package": "php5" } } chef-12.14.60/kitchen-tests/test/fixtures/serverspec_helper.rb000066400000000000000000000017151276456504500243560ustar00rootroot00000000000000# Shamelessly copied from https://github.com/onehealth-cookbooks/apache2/blob/master/test/fixtures/serverspec_helper.rb # The commented-out platforms in the osmapping hash can be added once we have added them into # our .kitchen.yml and .kitchen.travis.yml and added the appropriate JSON under test/fixtures/platforms. require "serverspec" require "json" require "ffi_yajl" set :backend, :exec include Specinfra::Helper::Properties require "pp" pp os def load_nodestub case os[:family] when "ubuntu", "debian" platform = os[:family] platform_version = os[:release] when "redhat" platform = "centos" platform_version = os[:release].to_i end FFI_Yajl::Parser.parse(IO.read("#{ENV['BUSSER_ROOT']}/../kitchen/data/platforms/#{platform}/#{platform_version}.json"), :symbolize_names => true) end # centos-59 doesn't have /sbin in the default path, # so we must ensure it's on serverspec's path set :path, "$PATH:/sbin" set_property load_nodestub chef-12.14.60/kitchen-tests/test/integration/000077500000000000000000000000001276456504500207575ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/test/integration/webapp/000077500000000000000000000000001276456504500222355ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/test/integration/webapp/default_spec.rb000066400000000000000000000050441276456504500252230ustar00rootroot00000000000000#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 chef-12.14.60/kitchen-tests/vendor/000077500000000000000000000000001276456504500167525ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/vendor/bundle/000077500000000000000000000000001276456504500202235ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/vendor/bundle/bundler/000077500000000000000000000000001276456504500216565ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/vendor/bundle/bundler/gems/000077500000000000000000000000001276456504500226115ustar00rootroot00000000000000chef-12.14.60/kitchen-tests/vendor/bundle/bundler/gems/kitchen-ec2-fec3f199a646/000077500000000000000000000000001276456504500265355ustar00rootroot00000000000000chef-12.14.60/lib-backcompat/000077500000000000000000000000001276456504500155405ustar00rootroot00000000000000chef-12.14.60/lib-backcompat/chef/000077500000000000000000000000001276456504500164455ustar00rootroot00000000000000chef-12.14.60/lib-backcompat/chef/chef_fs/000077500000000000000000000000001276456504500200425ustar00rootroot00000000000000chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/000077500000000000000000000000001276456504500223655ustar00rootroot00000000000000chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/acl_entry.rb000066400000000000000000000002001276456504500246620ustar00rootroot00000000000000require "chef/chef_fs/file_system/chef_server/acl_entry" module Chef::ChefFS::FileSystem AclEntry = ChefServer::AclEntry end chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/already_exists_error.rb000066400000000000000000000016421276456504500271460ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/exceptions" Chef.log_deprecation "Individual ChefFS error files are deprecated. Please require 'chef/chef_fs/file_system/exceptions' rather than 'chef/chef_fs/file_system/#{File.basename(__FILE__, ".rb")}'." chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb000066400000000000000000000003101276456504500324140ustar00rootroot00000000000000require "chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir" module Chef::ChefFS::FileSystem ChefRepositoryFileSystemRootDir = Repository::ChefRepositoryFileSystemRootDir end chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/chef_server_root_dir.rb000066400000000000000000000002351276456504500271060ustar00rootroot00000000000000require "chef/chef_fs/file_system/chef_server/chef_server_root_dir" module Chef::ChefFS::FileSystem ChefServerRootDir = ChefServer::ChefServerRootDir end chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/cookbook_frozen_error.rb000066400000000000000000000016421276456504500273170ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/exceptions" Chef.log_deprecation "Individual ChefFS error files are deprecated. Please require 'chef/chef_fs/file_system/exceptions' rather than 'chef/chef_fs/file_system/#{File.basename(__FILE__, ".rb")}'." default_environment_cannot_be_modified_error.rb000066400000000000000000000016421276456504500337670ustar00rootroot00000000000000chef-12.14.60/lib-backcompat/chef/chef_fs/file_system# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/exceptions" Chef.log_deprecation "Individual ChefFS error files are deprecated. Please require 'chef/chef_fs/file_system/exceptions' rather than 'chef/chef_fs/file_system/#{File.basename(__FILE__, ".rb")}'." chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/file_system_error.rb000066400000000000000000000016421276456504500264510ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/exceptions" Chef.log_deprecation "Individual ChefFS error files are deprecated. Please require 'chef/chef_fs/file_system/exceptions' rather than 'chef/chef_fs/file_system/#{File.basename(__FILE__, ".rb")}'." chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/must_delete_recursively_error.rb000066400000000000000000000016421276456504500310740ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/exceptions" Chef.log_deprecation "Individual ChefFS error files are deprecated. Please require 'chef/chef_fs/file_system/exceptions' rather than 'chef/chef_fs/file_system/#{File.basename(__FILE__, ".rb")}'." chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/not_found_error.rb000066400000000000000000000016421276456504500261210ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/exceptions" Chef.log_deprecation "Individual ChefFS error files are deprecated. Please require 'chef/chef_fs/file_system/exceptions' rather than 'chef/chef_fs/file_system/#{File.basename(__FILE__, ".rb")}'." chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/operation_failed_error.rb000066400000000000000000000016421276456504500274320ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/exceptions" Chef.log_deprecation "Individual ChefFS error files are deprecated. Please require 'chef/chef_fs/file_system/exceptions' rather than 'chef/chef_fs/file_system/#{File.basename(__FILE__, ".rb")}'." chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/operation_not_allowed_error.rb000066400000000000000000000016421276456504500305150ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/exceptions" Chef.log_deprecation "Individual ChefFS error files are deprecated. Please require 'chef/chef_fs/file_system/exceptions' rather than 'chef/chef_fs/file_system/#{File.basename(__FILE__, ".rb")}'." chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/repository/000077500000000000000000000000001276456504500246045ustar00rootroot00000000000000chef_repository_file_system_acls_dir.rb000066400000000000000000000002241276456504500345170ustar00rootroot00000000000000chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/repositoryrequire "chef/chef_fs/file_system/repository/acls_dir" module Chef::ChefFS::FileSystem::Repository ChefRepositoryFileSystemAclsDir = AclsDir end chef_repository_file_system_client_keys_dir.rb000066400000000000000000000002471276456504500361130ustar00rootroot00000000000000chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/repositoryrequire "chef/chef_fs/file_system/repository/client_keys_dir" module Chef::ChefFS::FileSystem::Repository ChefRepositoryFileSystemClientKeysDir = ClientKeysDir end chef_repository_file_system_entry.rb000066400000000000000000000004631276456504500341050ustar00rootroot00000000000000chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/repositoryrequire "chef/chef_fs/file_system/repository/file_system_entry" module Chef::ChefFS::FileSystem::Repository Chef.log_deprecation "Chef::ChefFS::FileSystem::Repository::ChefRepositoryFileSystemEntry is deprecated. Please use FileSystemEntry directly" ChefRepositoryFileSystemEntry = FileSystemEntry end chef_repository_file_system_policies_dir.rb000066400000000000000000000002401276456504500354020ustar00rootroot00000000000000chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/repositoryrequire "chef/chef_fs/file_system/repository/policies_dir" module Chef::ChefFS::FileSystem::Repository ChefRepositoryFileSystemPoliciesDir = PoliciesDir end chef-12.14.60/lib-backcompat/chef/chef_fs/file_system/repository/file_system_root_dir.rb000066400000000000000000000021261276456504500313560ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/repository/file_system_entry" class Chef module ChefFS module FileSystem module Repository class FileSystemRootDir < FileSystemEntry def initialize(file_path) Chef.log_deprecation "Chef::ChefFS::FileSystem::Repository::FileSystemRootDir is deprecated." super("", nil, file_path) end end end end end end chef-12.14.60/lib/000077500000000000000000000000001276456504500134365ustar00rootroot00000000000000chef-12.14.60/lib/chef.rb000066400000000000000000000020471276456504500146730ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/version" require "chef/nil_argument" require "chef/mash" require "chef/exceptions" require "chef/log" require "chef/config" require "chef/providers" require "chef/resources" require "chef/shell_out" require "chef/daemon" require "chef/run_status" require "chef/handler" require "chef/handler/json_file" require "chef/event_dispatch/dsl" require "chef/chef_class" chef-12.14.60/lib/chef/000077500000000000000000000000001276456504500143435ustar00rootroot00000000000000chef-12.14.60/lib/chef/api_client.rb000066400000000000000000000144441276456504500170060ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Nuo Yan () # 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/mixin/params_validate" require "chef/mixin/from_file" require "chef/mash" require "chef/json_compat" require "chef/search/query" require "chef/server_api" # DEPRECATION NOTE # # This code will be removed in Chef 13 in favor of the code in Chef::ApiClientV1, # which will be moved to this namespace. New development should occur in # Chef::ApiClientV1 until the time before Chef 13. class Chef class ApiClient include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate # Create a new Chef::ApiClient object. def initialize @name = "" @public_key = nil @private_key = nil @admin = false @validator = false end # Gets or sets the client name. # # @params [Optional String] The name must be alpha-numeric plus - and _. # @return [String] The current value of the name. def name(arg = nil) set_or_return( :name, arg, :regex => /^[\-[:alnum:]_\.]+$/ ) end # Gets or sets whether this client is an admin. # # @params [Optional True/False] Should be true or false - default is false. # @return [True/False] The current value def admin(arg = nil) set_or_return( :admin, arg, :kind_of => [ TrueClass, FalseClass ] ) end # Gets or sets the public key. # # @params [Optional String] The string representation of the public key. # @return [String] The current value. def public_key(arg = nil) set_or_return( :public_key, arg, :kind_of => String ) end # Gets or sets whether this client is a validator. # # @params [Boolean] whether or not the client is a validator. If # `nil`, retrieves the already-set value. # @return [Boolean] The current value def validator(arg = nil) set_or_return( :validator, arg, :kind_of => [TrueClass, FalseClass] ) end # Gets or sets the private key. # # @params [Optional String] The string representation of the private key. # @return [String] The current value. def private_key(arg = nil) set_or_return( :private_key, arg, :kind_of => [String, FalseClass] ) end # The hash representation of the object. Includes the name and public_key. # Private key is included if available. # # @return [Hash] def to_hash result = { "name" => @name, "public_key" => @public_key, "validator" => @validator, "admin" => @admin, "json_class" => self.class.name, "chef_type" => "client", } result["private_key"] = @private_key if @private_key result end # The JSON representation of the object. # # @return [String] the JSON string. def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def self.from_hash(o) client = Chef::ApiClient.new client.name(o["name"] || o["clientname"]) client.private_key(o["private_key"]) if o.key?("private_key") client.public_key(o["public_key"]) client.admin(o["admin"]) client.validator(o["validator"]) client end def self.json_create(data) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::ApiClient#from_hash") from_hash(data) end def self.from_json(j) from_hash(Chef::JSONCompat.parse(j)) end def self.http_api Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0" }) end def self.reregister(name) api_client = load(name) api_client.reregister end def self.list(inflate = false) if inflate response = Hash.new Chef::Search::Query.new.search(:client) do |n| n = self.json_create(n) if n.instance_of?(Hash) response[n.name] = n end response else http_api.get("clients") end end # Load a client by name via the API def self.load(name) response = http_api.get("clients/#{name}") if response.kind_of?(Chef::ApiClient) response else from_hash(response) end end # Remove this client via the REST API def destroy http_api.delete("clients/#{@name}") end # Save this client via the REST API, returns a hash including the private key def save begin http_api.put("clients/#{name}", { :name => self.name, :admin => self.admin, :validator => self.validator }) rescue Net::HTTPServerException => e # If that fails, go ahead and try and update it if e.response.code == "404" http_api.post("clients", { :name => self.name, :admin => self.admin, :validator => self.validator }) else raise e end end end def reregister reregistered_self = http_api.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true }) if reregistered_self.respond_to?(:[]) private_key(reregistered_self["private_key"]) else private_key(reregistered_self.private_key) end self end # Create the client via the REST API def create http_api.post("clients", self) end # As a string def to_s "client[#{@name}]" end def inspect "Chef::ApiClient name:'#{name}' admin:'#{admin.inspect}' validator:'#{validator}' " + "public_key:'#{public_key}' private_key:'#{private_key}'" end def http_api @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0" }) end end end chef-12.14.60/lib/chef/api_client/000077500000000000000000000000001276456504500164525ustar00rootroot00000000000000chef-12.14.60/lib/chef/api_client/registration.rb000066400000000000000000000150201276456504500215070ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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 "chef/config" require "chef/server_api" require "chef/exceptions" class Chef class ApiClient # ==Chef::ApiClient::Registration # Manages the process of creating or updating a Chef::ApiClient on the # server and writing the resulting private key to disk. Registration uses # the validator credentials for its API calls. This allows it to bootstrap # a new client/node identity by borrowing the validator client identity # when creating a new client. class Registration attr_reader :destination attr_reader :name def initialize(name, destination, http_api: nil) @name = name @destination = destination @http_api = http_api @server_generated_private_key = nil end # Runs the client registration process, including creating the client on # the chef-server and writing its private key to disk. #-- # If client creation fails with a 5xx, it is retried up to 5 times. These # retries are on top of the retries with randomized exponential backoff # built in to Chef::ServerAPI. The retries here are a workaround for failures # caused by resource contention in Hosted Chef when creating a very large # number of clients simultaneously, (e.g., spinning up 100s of ec2 nodes # at once). Future improvements to the affected component should make # these retries unnecessary. def run assert_destination_writable! retries = Config[:client_registration_retries] || 5 client = nil begin client = api_client(create_or_update) rescue Net::HTTPFatalError => e # HTTPFatalError implies 5xx. raise if retries <= 0 retries -= 1 Chef::Log.warn("Failed to register new client, #{retries} tries remaining") Chef::Log.warn("Response: HTTP #{e.response.code} - #{e}") retry end write_key client end def assert_destination_writable! if (File.exists?(destination) && !File.writable?(destination)) || !File.writable?(File.dirname(destination)) abs_path = File.expand_path(destination) raise Chef::Exceptions::CannotWritePrivateKey, "I can't write your private key to #{abs_path} - check permissions?" end end def write_key ::File.open(destination, file_flags, 0600) do |f| f.print(private_key) end rescue IOError => e raise Chef::Exceptions::CannotWritePrivateKey, "Error writing private key to #{destination}: #{e}" end def create_or_update create rescue Net::HTTPServerException => e # If create fails because the client exists, attempt to update. This # requires admin privileges. raise unless e.response.code == "409" update end def create response = http_api.post("clients", post_data) @server_generated_private_key = response["private_key"] response end def update response = http_api.put("clients/#{name}", put_data) if response.respond_to?(:private_key) # Chef 11 @server_generated_private_key = response.private_key else # Chef 10 @server_generated_private_key = response["private_key"] end response end def api_client(response) return response if response.is_a?(Chef::ApiClient) client = Chef::ApiClient.new client.name(name) client.public_key(api_client_key(response, "public_key")) client.private_key(api_client_key(response, "private_key")) client end def api_client_key(response, key_name) if response[key_name] if response[key_name].respond_to?(:to_pem) response[key_name].to_pem else response[key_name] end elsif response["chef_key"] response["chef_key"][key_name] end end def put_data base_put_data = { :name => name, :admin => false } if self_generate_keys? base_put_data[:public_key] = generated_public_key else base_put_data[:private_key] = true end base_put_data end def post_data post_data = { :name => name, :admin => false } post_data[:public_key] = generated_public_key if self_generate_keys? post_data end def http_api @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0", :client_name => Chef::Config[:validation_client_name], :signing_key_filename => Chef::Config[:validation_key], } ) end # Whether or not to generate keys locally and post the public key to the # server. Delegates to `Chef::Config.local_key_generation`. Servers # before 11.0 do not support this feature. def self_generate_keys? Chef::Config.local_key_generation end def private_key if self_generate_keys? generated_private_key.to_pem else @server_generated_private_key end end def generated_private_key @generated_key ||= OpenSSL::PKey::RSA.generate(2048) end def generated_public_key generated_private_key.public_key.to_pem end def file_flags base_flags = File::CREAT | File::TRUNC | File::RDWR # Windows doesn't have symlinks, so it doesn't have NOFOLLOW if defined?(File::NOFOLLOW) && !Chef::Config[:follow_client_key_symlink] base_flags |= File::NOFOLLOW end base_flags end end end end chef-12.14.60/lib/chef/api_client_v1.rb000066400000000000000000000246321276456504500174140ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Nuo Yan () # 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/mixin/params_validate" require "chef/mixin/from_file" require "chef/mash" require "chef/json_compat" require "chef/search/query" require "chef/exceptions" require "chef/mixin/api_version_request_handling" require "chef/server_api" require "chef/api_client" # COMPATIBILITY NOTE # # This ApiClientV1 code attempts to make API V1 requests and falls back to # API V0 requests when it fails. New development should occur here instead # of Chef::ApiClient as this will replace that namespace when Chef 13 is released. # # If you need to default to API V0 behavior (i.e. you need GET client to return # a public key, etc), please use Chef::ApiClient and update your code to support # API V1 before you pull in Chef 13. class Chef class ApiClientV1 include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate include Chef::Mixin::ApiVersionRequestHandling SUPPORTED_API_VERSIONS = [0, 1] # Create a new Chef::ApiClientV1 object. def initialize @name = "" @public_key = nil @private_key = nil @admin = false @validator = false @create_key = nil end def chef_rest_v0 @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0", :inflate_json_class => false }) end def chef_rest_v1 @chef_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "1", :inflate_json_class => false }) end def self.http_api Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "1", :inflate_json_class => false }) end # Gets or sets the client name. # # @params [Optional String] The name must be alpha-numeric plus - and _. # @return [String] The current value of the name. def name(arg = nil) set_or_return( :name, arg, :regex => /^[\-[:alnum:]_\.]+$/ ) end # Gets or sets whether this client is an admin. # # @params [Optional True/False] Should be true or false - default is false. # @return [True/False] The current value def admin(arg = nil) set_or_return( :admin, arg, :kind_of => [ TrueClass, FalseClass ] ) end # Gets or sets the public key. # # @params [Optional String] The string representation of the public key. # @return [String] The current value. def public_key(arg = nil) set_or_return( :public_key, arg, :kind_of => String ) end # Gets or sets whether this client is a validator. # # @params [Boolean] whether or not the client is a validator. If # `nil`, retrieves the already-set value. # @return [Boolean] The current value def validator(arg = nil) set_or_return( :validator, arg, :kind_of => [TrueClass, FalseClass] ) end # Private key. The server will return it as a string. # Set to true under API V0 to have the server regenerate the default key. # # @params [Optional String] The string representation of the private key. # @return [String] The current value. def private_key(arg = nil) set_or_return( :private_key, arg, :kind_of => [String, TrueClass, FalseClass] ) end # Used to ask server to generate key pair under api V1 # # @params [Optional True/False] Should be true or false - default is false. # @return [True/False] The current value def create_key(arg = nil) set_or_return( :create_key, arg, :kind_of => [ TrueClass, FalseClass ] ) end # The hash representation of the object. Includes the name and public_key. # Private key is included if available. # # @return [Hash] def to_hash result = { "name" => @name, "validator" => @validator, "admin" => @admin, "chef_type" => "client", } result["private_key"] = @private_key unless @private_key.nil? result["public_key"] = @public_key unless @public_key.nil? result["create_key"] = @create_key unless @create_key.nil? result end # The JSON representation of the object. # # @return [String] the JSON string. def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def self.from_hash(o) client = Chef::ApiClientV1.new client.name(o["name"] || o["clientname"]) client.admin(o["admin"]) client.validator(o["validator"]) client.private_key(o["private_key"]) if o.key?("private_key") client.public_key(o["public_key"]) if o.key?("public_key") client.create_key(o["create_key"]) if o.key?("create_key") client end def self.from_json(j) Chef::ApiClientV1.from_hash(Chef::JSONCompat.from_json(j)) end def self.reregister(name) api_client = Chef::ApiClientV1.load(name) api_client.reregister end def self.list(inflate = false) if inflate response = Hash.new Chef::Search::Query.new.search(:client) do |n| n = self.from_hash(n) if n.instance_of?(Hash) response[n.name] = n end response else http_api.get("clients") end end # Load a client by name via the API def self.load(name) response = http_api.get("clients/#{name}") Chef::ApiClientV1.from_hash(response) end # Remove this client via the REST API def destroy chef_rest_v1.delete("clients/#{@name}") end # Save this client via the REST API, returns a hash including the private key def save begin update rescue Net::HTTPServerException => e # If that fails, go ahead and try and update it if e.response.code == "404" create else raise e end end end def reregister # Try API V0 and if it fails due to V0 not being supported, raise the proper error message. # reregister only supported in API V0 or lesser. reregistered_self = chef_rest_v0.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true }) if reregistered_self.respond_to?(:[]) private_key(reregistered_self["private_key"]) else private_key(reregistered_self.private_key) end self rescue Net::HTTPServerException => e # if there was a 406 related to versioning, give error explaining that # only API version 0 is supported for reregister command if e.response.code == "406" && e.response["x-ops-server-api-version"] version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"]) min_version = version_header["min_version"] max_version = version_header["max_version"] error_msg = reregister_only_v0_supported_error_msg(max_version, min_version) raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg) else raise e end end # Updates the client via the REST API def update # NOTE: API V1 dropped support for updating client keys via update (aka PUT), # but this code never supported key updating in the first place. Since # it was never implemented, we will simply ignore that functionality # as it is being deprecated. # Delete this comment after V0 support is dropped. payload = { :name => name } payload[:validator] = validator unless validator.nil? # DEPRECATION # This field is ignored in API V1, but left for backwards-compat, # can remove after API V0 is no longer supported. payload[:admin] = admin unless admin.nil? begin new_client = chef_rest_v1.put("clients/#{name}", payload) rescue Net::HTTPServerException => e # rescue API V0 if 406 and the server supports V0 supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) raise e unless supported_versions && supported_versions.include?(0) new_client = chef_rest_v0.put("clients/#{name}", payload) end Chef::ApiClientV1.from_hash(new_client) end # Create the client via the REST API def create payload = { :name => name, :validator => validator, # this field is ignored in API V1, but left for backwards-compat, # can remove after OSC 11 support is finished? :admin => admin, } begin # try API V1 raise Chef::Exceptions::InvalidClientAttribute, "You cannot set both public_key and create_key for create." if !create_key.nil? && !public_key.nil? payload[:public_key] = public_key unless public_key.nil? payload[:create_key] = create_key unless create_key.nil? new_client = chef_rest_v1.post("clients", payload) # get the private_key out of the chef_key hash if it exists if new_client["chef_key"] if new_client["chef_key"]["private_key"] new_client["private_key"] = new_client["chef_key"]["private_key"] end new_client["public_key"] = new_client["chef_key"]["public_key"] new_client.delete("chef_key") end rescue Net::HTTPServerException => e # rescue API V0 if 406 and the server supports V0 supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) raise e unless supported_versions && supported_versions.include?(0) # under API V0, a key pair will always be created unless public_key is # passed on initial POST payload[:public_key] = public_key unless public_key.nil? new_client = chef_rest_v0.post("clients", payload) end Chef::ApiClientV1.from_hash(self.to_hash.merge(new_client)) end # As a string def to_s "client[#{@name}]" end end end chef-12.14.60/lib/chef/application.rb000066400000000000000000000327251276456504500172040ustar00rootroot00000000000000# # Author:: AJ Christensen () # Author:: Mark Mzyk (mmzyk@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 "pp" require "socket" require "chef/config" require "chef/config_fetcher" require "chef/exceptions" require "chef/local_mode" require "chef/log" require "chef/platform" require "mixlib/cli" require "tmpdir" require "rbconfig" require "chef/application/exit_code" require "yaml" class Chef class Application include Mixlib::CLI def initialize super @chef_client = nil @chef_client_json = nil # Always switch to a readable directory. Keeps subsequent Dir.chdir() {} # from failing due to permissions when launched as a less privileged user. end # Reconfigure the application. You'll want to override and super this method. def reconfigure configure_chef configure_logging configure_encoding emit_warnings end # Get this party started def run setup_signal_handlers reconfigure setup_application run_application end def setup_signal_handlers trap("INT") do Chef::Application.fatal!("SIGINT received, stopping", Chef::Exceptions::SigInt.new) end trap("TERM") do Chef::Application.fatal!("SIGTERM received, stopping", Chef::Exceptions::SigTerm.new) end unless Chef::Platform.windows? trap("QUIT") do Chef::Log.info("SIGQUIT received, call stack:\n " + caller.join("\n ")) end trap("HUP") do Chef::Log.info("SIGHUP received, reconfiguring") reconfigure end end end # Parse configuration (options and config file) def configure_chef parse_options load_config_file Chef::Config.export_proxies Chef::Config.init_openssl end # Parse the config file def load_config_file config_fetcher = Chef::ConfigFetcher.new(config[:config_file]) # Some config settings are derived relative to the config file path; if # given as a relative path, this is computed relative to cwd, but # chef-client will later chdir to root, so we need to get the absolute path # here. config[:config_file] = config_fetcher.expanded_path if config[:config_file].nil? Chef::Log.warn("No config file found or specified on command line, using command line options.") elsif config_fetcher.config_missing? Chef::Log.warn("*****************************************") Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.") Chef::Log.warn("*****************************************") else config_content = config_fetcher.read_config apply_config(config_content, config[:config_file]) end extra_config_options = config.delete(:config_option) Chef::Config.merge!(config) if extra_config_options extra_parsed_options = extra_config_options.inject({}) do |memo, option| # Sanity check value. Chef::Application.fatal!("Unparsable config option #{option.inspect}") if option.empty? || !option.include?("=") # Split including whitespace if someone does truly odd like # --config-option "foo = bar" key, value = option.split(/\s*=\s*/, 2) # Call to_sym because Chef::Config expects only symbol keys. Also # runs a simple parse on the string for some common types. memo[key.to_sym] = YAML.safe_load(value) memo end Chef::Config.merge!(extra_parsed_options) end end def set_specific_recipes Chef::Config[:specific_recipes] = cli_arguments.map { |file| File.expand_path(file) } if cli_arguments.respond_to?(:map) end # Initialize and configure the logger. # === Loggers and Formatters # In Chef 10.x and previous, the Logger was the primary/only way that Chef # communicated information to the user. In Chef 10.14, a new system, "output # formatters" was added, and in Chef 11.0+ it is the default when running # chef in a console (detected by `STDOUT.tty?`). Because output formatters # are more complex than the logger system and users have less experience with # them, the config option `force_logger` is provided to restore the Chef 10.x # behavior. # # Conversely, for users who want formatter output even when chef is running # unattended, the `force_formatter` option is provided. # # === Auto Log Level # When `log_level` is set to `:auto` (default), the log level will be `:warn` # when the primary output mode is an output formatter (see # +using_output_formatter?+) and `:info` otherwise. # # === Automatic STDOUT Logging # When `force_logger` is configured (e.g., Chef 10 mode), a second logger # with output on STDOUT is added when running in a console (STDOUT is a tty) # and the configured log_location isn't STDOUT. This accounts for the case # that a user has configured a log_location in client.rb, but is running # chef-client by hand to troubleshoot a problem. def configure_logging configure_log_location Chef::Log.init(MonoLogger.new(Chef::Config[:log_location])) if want_additional_logger? configure_stdout_logger end 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", error) end # Turn `log_location :syslog` and `log_location :win_evt` into the # appropriate loggers. def configure_log_location log_location = Chef::Config[:log_location] return unless log_location.respond_to?(:to_sym) Chef::Config[:log_location] = case log_location.to_sym when :syslog then Chef::Log::Syslog.new when :win_evt then Chef::Log::WinEvt.new else log_location # Probably a path; let MonoLogger sort it out end end def configure_stdout_logger stdout_logger = MonoLogger.new(STDOUT) stdout_logger.formatter = Chef::Log.logger.formatter Chef::Log.loggers << stdout_logger end # Based on config and whether or not STDOUT is a tty, should we setup a # secondary logger for stdout? def want_additional_logger? ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger]) end # Use of output formatters is assumed if `force_formatter` is set or if # `force_logger` is not set and STDOUT is to a console (tty) def using_output_formatter? Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?) end def auto_log_level? Chef::Config[:log_level] == :auto end # if log_level is `:auto`, convert it to :warn (when using output formatter) # or :info (no output formatter). See also +using_output_formatter?+ def resolve_log_level if auto_log_level? if using_output_formatter? :warn else :info end else Chef::Config[:log_level] end end # Sets the default external encoding to UTF-8 (users can change this, but they shouldn't) def configure_encoding Encoding.default_external = Chef::Config[:ruby_encoding] end # Called prior to starting the application, by the run method def setup_application raise Chef::Exceptions::Application, "#{self}: you must override setup_application" end # Actually run the application def run_application raise Chef::Exceptions::Application, "#{self}: you must override run_application" end # Initializes Chef::Client instance and runs it def run_chef_client(specific_recipes = []) unless specific_recipes.respond_to?(:size) raise ArgumentError, "received non-Array like specific_recipes argument" end Chef::LocalMode.with_server_connectivity do override_runlist = config[:override_runlist] override_runlist ||= [] if specific_recipes.size > 0 @chef_client = Chef::Client.new( @chef_client_json, override_runlist: override_runlist, specific_recipes: specific_recipes, runlist: config[:runlist] ) @chef_client_json = nil if can_fork? fork_chef_client # allowed to run client in forked process else # Unforked interval runs are disabled, so this runs chef-client # once and then exits. If TERM signal is received, will "ignore" # the signal to finish converge. run_with_graceful_exit_option end @chef_client = nil end end private def can_fork? # win32-process gem exposes some form of :fork for Process # class. So we are separately ensuring that the platform we're # running on is not windows before forking. Chef::Config[:client_fork] && Process.respond_to?(:fork) && !Chef::Platform.windows? end # Run chef-client once and then exit. If TERM signal is received, ignores the # signal to finish the converge and exists. def run_with_graceful_exit_option # Override the TERM signal. trap("TERM") do Chef::Log.debug("SIGTERM received during converge," + " finishing converge to exit normally (send SIGINT to terminate immediately)") end @chef_client.run true end def fork_chef_client Chef::Log.info "Forking chef instance to converge..." pid = fork do # Want to allow forked processes to finish converging when # TERM singal is received (exit gracefully) trap("TERM") do Chef::Log.debug("SIGTERM received during converge," + " finishing converge to exit normally (send SIGINT to terminate immediately)") end client_solo = Chef::Config[:solo] ? "chef-solo" : "chef-client" $0 = "#{client_solo} worker: ppid=#{Process.ppid};start=#{Time.new.strftime("%R:%S")};" begin Chef::Log.debug "Forked instance now converging" @chef_client.run rescue Exception => e Chef::Log.error(e.to_s) exit Chef::Application.normalize_exit_code(e) else exit 0 end end Chef::Log.debug "Fork successful. Waiting for new chef pid: #{pid}" result = Process.waitpid2(pid) handle_child_exit(result) Chef::Log.debug "Forked instance successfully reaped (pid: #{pid})" true end def handle_child_exit(pid_and_status) status = pid_and_status[1] return true if status.success? message = if status.signaled? "Chef run process terminated by signal #{status.termsig} (#{Signal.list.invert[status.termsig]})" else "Chef run process exited unsuccessfully (exit code #{status.exitstatus})" end raise Exceptions::ChildConvergeError, message end def apply_config(config_content, config_file_path) Chef::Config.from_string(config_content, config_file_path) rescue Exception => error 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}'", error) end # This is a hook for testing def env ENV end def emit_warnings if Chef::Config[:chef_gem_compile_time] Chef.log_deprecation "setting chef_gem_compile_time to true is deprecated" end end class << self def debug_stacktrace(e) message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}" cause = e.cause if e.respond_to?(:cause) while cause != nil message << "\n\n>>>> Caused by #{cause.class}: #{cause}\n#{cause.backtrace.join("\n")}" cause = cause.respond_to?(:cause) ? cause.cause : nil end chef_stacktrace_out = "Generated at #{Time.now}\n" chef_stacktrace_out += message Chef::FileCache.store("chef-stacktrace.out", chef_stacktrace_out) Chef::Log.fatal("Stacktrace dumped to #{Chef::FileCache.load("chef-stacktrace.out", false)}") Chef::Log.fatal("Please provide the contents of the stacktrace.out file if you file a bug report") Chef::Log.debug(message) 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 = nil) Chef::Log.fatal(msg) Process.exit(normalize_exit_code(err)) end def exit!(msg, err = nil) Chef::Log.debug(msg) Process.exit(normalize_exit_code(err)) end end end end chef-12.14.60/lib/chef/application/000077500000000000000000000000001276456504500166465ustar00rootroot00000000000000chef-12.14.60/lib/chef/application/apply.rb000066400000000000000000000146041276456504500203250ustar00rootroot00000000000000# # Author:: Bryan W. Berry () # Author:: Daniel DeLeo () # Copyright:: Copyright 2012-2016, Bryan W. Berry # Copyright:: Copyright 2012-2016, Daniel DeLeo # 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 "chef/application" require "chef/client" require "chef/config" require "chef/log" require "fileutils" require "tempfile" require "chef/providers" require "chef/resources" class Chef::Application::Apply < Chef::Application banner "Usage: chef-apply [RECIPE_FILE | -e RECIPE_TEXT | -s] [OPTIONS]" option :execute, :short => "-e RECIPE_TEXT", :long => "--execute RECIPE_TEXT", :description => "Execute resources supplied in a string", :proc => nil option :stdin, :short => "-s", :long => "--stdin", :description => "Execute resources read from STDIN", :boolean => true option :json_attribs, :short => "-j JSON_ATTRIBS", :long => "--json-attributes JSON_ATTRIBS", :description => "Load attributes from a JSON file or URL", :proc => nil option :force_logger, :long => "--force-logger", :description => "Use logger output instead of formatter output", :boolean => true, :default => false option :force_formatter, :long => "--force-formatter", :description => "Use formatter output instead of logger output", :boolean => true, :default => false option :formatter, :short => "-F FORMATTER", :long => "--format FORMATTER", :description => "output format to use", :proc => lambda { |format| Chef::Config.add_formatter(format) } option :log_level, :short => "-l LEVEL", :long => "--log_level LEVEL", :description => "Set the log level (debug, info, warn, error, fatal)", :proc => lambda { |l| l.to_sym } option :help, :short => "-h", :long => "--help", :description => "Show this message", :on => :tail, :boolean => true, :show_options => true, :exit => 0 option :version, :short => "-v", :long => "--version", :description => "Show chef version", :boolean => true, :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" }, :exit => 0 option :why_run, :short => "-W", :long => "--why-run", :description => "Enable whyrun mode", :boolean => true option :profile_ruby, :long => "--[no-]profile-ruby", :description => "Dump complete Ruby call graph stack of entire Chef run (expert only)", :boolean => true, :default => false option :color, :long => "--[no-]color", :boolean => true, :default => true, :description => "Use colored output, defaults to enabled" option :minimal_ohai, :long => "--minimal-ohai", :description => "Only run the bare minimum ohai plugins chef needs to function", :boolean => true attr_reader :json_attribs def initialize super end def reconfigure parse_options Chef::Config.merge!(config) configure_logging Chef::Config.export_proxies Chef::Config.init_openssl parse_json end def parse_json if Chef::Config[:json_attribs] config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs]) @json_attribs = config_fetcher.fetch_json end end def read_recipe_file(file_name) if file_name.nil? 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}", Chef::Exceptions::RecipeNotFound.new) end recipe_fh = open(recipe_path) recipe_text = recipe_fh.read [recipe_text, recipe_fh] end end def get_recipe_and_run_context Chef::Config[:solo_legacy_mode] = true @chef_client = Chef::Client.new(@json_attribs) @chef_client.run_ohai @chef_client.load_node @chef_client.build_node run_context = if @chef_client.events.nil? Chef::RunContext.new(@chef_client.node, {}) else Chef::RunContext.new(@chef_client.node, {}, @chef_client.events) end recipe = Chef::Recipe.new("(chef-apply cookbook)", "(chef-apply recipe)", run_context) [recipe, run_context] end # write recipe to temp file, so in case of error, # user gets error w/ context def temp_recipe_file @recipe_fh = Tempfile.open("recipe-temporary-file") @recipe_fh.write(@recipe_text) @recipe_fh.rewind @recipe_filename = @recipe_fh.path end def run_chef_recipe if config[:execute] @recipe_text = config[:execute] temp_recipe_file elsif config[:stdin] @recipe_text = STDIN.read temp_recipe_file else if !ARGV[0] puts opt_parser 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 end recipe, run_context = get_recipe_and_run_context recipe.instance_eval(@recipe_text, @recipe_filename, 1) runner = Chef::Runner.new(run_context) begin runner.converge ensure @recipe_fh.close end Chef::Platform::Rebooter.reboot_if_needed!(runner) end def run_application begin parse_options run_chef_recipe Chef::Application.exit! "Exiting", 0 rescue SystemExit raise rescue Exception => e Chef::Application.debug_stacktrace(e) Chef::Application.fatal!("#{e.class}: #{e.message}", e) end end # Get this party started def run reconfigure run_application end end chef-12.14.60/lib/chef/application/client.rb000066400000000000000000000422451276456504500204600ustar00rootroot00000000000000# # Author:: AJ Christensen () # Author:: Mark Mzyk (mmzyk@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/application" require "chef/client" require "chef/config" require "chef/daemon" require "chef/log" require "chef/config_fetcher" require "chef/handler/error_report" require "chef/workstation_config_loader" require "chef/mixin/shell_out" require "chef-config/mixin/dot_d" require "mixlib/archive" class Chef::Application::Client < Chef::Application include Chef::Mixin::ShellOut include ChefConfig::Mixin::DotD # Mimic self_pipe sleep from Unicorn to capture signals safely SELF_PIPE = [] option :config_file, :short => "-c CONFIG", :long => "--config CONFIG", :description => "The configuration file to use" option :config_option, :long => "--config-option OPTION=VALUE", :description => "Override a single configuration option", :proc => lambda { |option, existing| (existing ||= []) << option existing } option :formatter, :short => "-F FORMATTER", :long => "--format FORMATTER", :description => "output format to use", :proc => lambda { |format| Chef::Config.add_formatter(format) } option :force_logger, :long => "--force-logger", :description => "Use logger output instead of formatter output", :boolean => true, :default => false option :force_formatter, :long => "--force-formatter", :description => "Use formatter output instead of logger output", :boolean => true, :default => false option :profile_ruby, :long => "--[no-]profile-ruby", :description => "Dump complete Ruby call graph stack of entire Chef run (expert only)", :boolean => true, :default => false option :color, :long => "--[no-]color", :boolean => true, :default => true, :description => "Use colored output, defaults to enabled" option :log_level, :short => "-l LEVEL", :long => "--log_level LEVEL", :description => "Set the log level (auto, debug, info, warn, error, fatal)", :proc => lambda { |l| l.to_sym } option :log_location, :short => "-L LOGLOCATION", :long => "--logfile LOGLOCATION", :description => "Set the log file location, defaults to STDOUT - recommended for daemonizing", :proc => nil option :help, :short => "-h", :long => "--help", :description => "Show this message", :on => :tail, :boolean => true, :show_options => true, :exit => 0 option :user, :short => "-u USER", :long => "--user USER", :description => "User to set privilege to", :proc => nil option :group, :short => "-g GROUP", :long => "--group GROUP", :description => "Group to set privilege to", :proc => nil unless Chef::Platform.windows? option :daemonize, :short => "-d [WAIT]", :long => "--daemonize [WAIT]", :description => "Daemonize the process. Accepts an optional integer which is the " \ "number of seconds to wait before the first daemonized run.", :proc => lambda { |wait| wait =~ /^\d+$/ ? wait.to_i : true } end option :pid_file, :short => "-P PID_FILE", :long => "--pid PIDFILE", :description => "Set the PID file location, for the chef-client daemon process. Defaults to /tmp/chef-client.pid", :proc => nil option :lockfile, :long => "--lockfile LOCKFILE", :description => "Set the lockfile location. Prevents multiple client processes from converging at the same time", :proc => nil option :interval, :short => "-i SECONDS", :long => "--interval SECONDS", :description => "Run chef-client periodically, in seconds", :proc => lambda { |s| s.to_i } option :once, :long => "--once", :description => "Cancel any interval or splay options, run chef once and exit", :boolean => true option :json_attribs, :short => "-j JSON_ATTRIBS", :long => "--json-attributes JSON_ATTRIBS", :description => "Load attributes from a JSON file or URL", :proc => nil option :node_name, :short => "-N NODE_NAME", :long => "--node-name NODE_NAME", :description => "The node name for this client", :proc => nil option :splay, :short => "-s SECONDS", :long => "--splay SECONDS", :description => "The splay time for running at intervals, in seconds", :proc => lambda { |s| s.to_i } option :chef_server_url, :short => "-S CHEFSERVERURL", :long => "--server CHEFSERVERURL", :description => "The chef server URL", :proc => nil option :validation_key, :short => "-K KEY_FILE", :long => "--validation_key KEY_FILE", :description => "Set the validation key file location, used for registering new clients", :proc => nil option :client_key, :short => "-k KEY_FILE", :long => "--client_key KEY_FILE", :description => "Set the client key file location", :proc => nil option :named_run_list, :short => "-n NAMED_RUN_LIST", :long => "--named-run-list NAMED_RUN_LIST", :description => "Use a policyfile's named run list instead of the default run list" option :environment, :short => "-E ENVIRONMENT", :long => "--environment ENVIRONMENT", :description => "Set the Chef Environment on the node" option :version, :short => "-v", :long => "--version", :description => "Show chef version", :boolean => true, :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" }, :exit => 0 option :override_runlist, :short => "-o RunlistItem,RunlistItem...", :long => "--override-runlist RunlistItem,RunlistItem...", :description => "Replace current run list with specified items for a single run", :proc => lambda {|items| items = items.split(",") items.compact.map do |item| Chef::RunList::RunListItem.new(item) end } option :runlist, :short => "-r RunlistItem,RunlistItem...", :long => "--runlist RunlistItem,RunlistItem...", :description => "Permanently replace current run list with specified items", :proc => lambda {|items| items = items.split(",") items.compact.map do |item| Chef::RunList::RunListItem.new(item) end } option :why_run, :short => "-W", :long => "--why-run", :description => "Enable whyrun mode", :boolean => true option :client_fork, :short => "-f", :long => "--[no-]fork", :description => "Fork client", :boolean => true option :recipe_url, :long => "--recipe-url=RECIPE_URL", :description => "Pull down a remote archive of recipes and unpack it to the cookbook cache. Only used in local mode." option :enable_reporting, :short => "-R", :long => "--enable-reporting", :description => "Enable reporting data collection for chef runs", :boolean => true option :local_mode, :short => "-z", :long => "--local-mode", :description => "Point chef-client at local repository", :boolean => true option :chef_zero_host, :long => "--chef-zero-host HOST", :description => "Host to start chef-zero on" option :chef_zero_port, :long => "--chef-zero-port PORT", :description => "Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works." option :disable_config, :long => "--disable-config", :description => "Refuse to load a config file and use defaults. This is for development and not a stable API", :boolean => true option :run_lock_timeout, :long => "--run-lock-timeout SECONDS", :description => "Set maximum duration to wait for another client run to finish, default is indefinitely.", :proc => lambda { |s| s.to_i } if Chef::Platform.windows? option :fatal_windows_admin_check, :short => "-A", :long => "--fatal-windows-admin-check", :description => "Fail the run when chef-client doesn't have administrator privileges on Windows", :boolean => true end option :audit_mode, :long => "--audit-mode MODE", :description => "Enable audit-mode with `enabled`. Disable audit-mode with `disabled`. Skip converge and only perform audits with `audit-only`", :proc => lambda { |mo| mo.tr("-", "_").to_sym } option :minimal_ohai, :long => "--minimal-ohai", :description => "Only run the bare minimum ohai plugins chef needs to function", :boolean => true option :listen, :long => "--[no-]listen", :description => "Whether a local mode (-z) server binds to a port", :boolean => true option :fips, :long => "--fips", :description => "Enable fips mode", :boolean => true option :delete_entire_chef_repo, :long => "--delete-entire-chef-repo", :description => "DANGEROUS: does what it says, only useful with --recipe-url", :boolean => true option :skip_cookbook_sync, :long => "--[no-]skip-cookbook-sync", :description => "Use cached cookbooks without overwriting local differences from the server", :boolean => false IMMEDIATE_RUN_SIGNAL = "1".freeze attr_reader :chef_client_json # Reconfigure the chef client # Re-open the JSON attributes and load them into the node def reconfigure super raise Chef::Exceptions::PIDFileLockfileMatch if Chef::Util::PathHelper.paths_eql? (Chef::Config[:pid_file] || "" ), (Chef::Config[:lockfile] || "") set_specific_recipes Chef::Config[:fips] = config[:fips] if config.has_key? :fips Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode) if Chef::Config.has_key?(:chef_repo_path) && Chef::Config.chef_repo_path.nil? Chef::Config.delete(:chef_repo_path) Chef::Log.warn "chef_repo_path was set in a config file but was empty. Assuming #{Chef::Config.chef_repo_path}" end if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path) Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd) end if Chef::Config[:recipe_url] if !Chef::Config.local_mode 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" FileUtils.rm_rf(recipes_path, :secure => true) end Chef::Log.debug "Creating path #{Chef::Config.chef_repo_path} to extract recipes into" FileUtils.mkdir_p(Chef::Config.chef_repo_path) tarball_path = File.join(Chef::Config.chef_repo_path, "recipes.tgz") fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path) Mixlib::Archive.new(tarball_path).extract(Chef::Config.chef_repo_path, perms: false, ignore: /^\.$/) end end Chef::Config.chef_zero.host = config[:chef_zero_host] if config[:chef_zero_host] Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port] if Chef::Config[:daemonize] Chef::Config[:interval] ||= 1800 end if Chef::Config[:once] Chef::Config[:interval] = nil Chef::Config[:splay] = nil end if !Chef::Config[:client_fork] && Chef::Config[:interval] && !Chef::Platform.windows? Chef::Application.fatal!(unforked_interval_error_message) end if Chef::Config[:json_attribs] config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs]) @chef_client_json = config_fetcher.fetch_json end if mode = config[:audit_mode] || Chef::Config[:audit_mode] expected_modes = [:enabled, :disabled, :audit_only] unless expected_modes.include?(mode) Chef::Application.fatal!(unrecognized_audit_mode(mode)) end end end def load_config_file if !config.has_key?(:config_file) && !config[:disable_config] if config[:local_mode] config[:config_file] = Chef::WorkstationConfigLoader.new(nil, Chef::Log).config_location else config[:config_file] = Chef::Config.platform_specific_path("/etc/chef/client.rb") end end # Load the client.rb configuration super # Load all config files in client.d load_dot_d(Chef::Config[:client_d_dir]) if Chef::Config[:client_d_dir] end def configure_logging super Mixlib::Authentication::Log.use_log_devices( Chef::Log ) Ohai::Log.use_log_devices( Chef::Log ) end def setup_application Chef::Daemon.change_privilege end def setup_signal_handlers super unless Chef::Platform.windows? SELF_PIPE.replace IO.pipe trap("USR1") do Chef::Log.info("SIGUSR1 received, waking up") SELF_PIPE[1].putc(IMMEDIATE_RUN_SIGNAL) # wakeup master process from select end end end # Run the chef client, optionally daemonizing or looping at intervals. def run_application if Chef::Config[:version] puts "Chef version: #{::Chef::VERSION}" end if !Chef::Config[:client_fork] || Chef::Config[:once] begin # run immediately without interval sleep, or splay run_chef_client(Chef::Config[:specific_recipes]) rescue SystemExit raise rescue Exception => e Chef::Application.fatal!("#{e.class}: #{e.message}", e) end else interval_run_chef_client end end private def interval_run_chef_client if Chef::Config[:daemonize] Chef::Daemon.daemonize("chef-client") # Start first daemonized run after configured number of seconds if Chef::Config[:daemonize].is_a?(Integer) sleep_then_run_chef_client(Chef::Config[:daemonize]) end end loop do sleep_then_run_chef_client(time_to_sleep) Chef::Application.exit!("Exiting", 0) if !Chef::Config[:interval] end end def sleep_then_run_chef_client(sleep_sec) @signal = test_signal unless @signal == IMMEDIATE_RUN_SIGNAL Chef::Log.debug("Sleeping for #{sleep_sec} seconds") interval_sleep(sleep_sec) end @signal = nil run_chef_client(Chef::Config[:specific_recipes]) rescue SystemExit => e raise rescue Exception => e if Chef::Config[:interval] Chef::Log.error("#{e.class}: #{e}") Chef::Log.debug("#{e.class}: #{e}\n#{e.backtrace.join("\n")}") retry end Chef::Application.fatal!("#{e.class}: #{e.message}", e) end def test_signal @signal = interval_sleep(0) end def time_to_sleep duration = 0 duration += rand(Chef::Config[:splay]) if Chef::Config[:splay] duration += Chef::Config[:interval] if Chef::Config[:interval] duration end def interval_sleep(sec) unless SELF_PIPE.empty? client_sleep(sec) else # Windows sleep(sec) end end def client_sleep(sec) return unless IO.select([ SELF_PIPE[0] ], nil, nil, sec) @signal = SELF_PIPE[0].getc.chr end def unforked_interval_error_message "Unforked chef-client interval runs are disabled in Chef 12." + "\nConfiguration settings:" + "#{"\n interval = #{Chef::Config[:interval]} seconds" if Chef::Config[:interval]}" + "\nEnable chef-client interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options." end def audit_mode_settings_explanation "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `audit_mode :enabled` in your config file." + "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `audit_mode :disabled` in your config file." + "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `audit_mode :audit_only` in your config file." + "\nAudit mode is disabled by default." end def unrecognized_audit_mode(mode) "Unrecognized setting #{mode} for audit mode." + audit_mode_settings_explanation end def fetch_recipe_tarball(url, path) Chef::Log.debug("Download recipes tarball from #{url} to #{path}") File.open(path, "wb") do |f| open(url) do |r| f.write(r.read) end end end end chef-12.14.60/lib/chef/application/exit_code.rb000066400000000000000000000164721276456504500211500ustar00rootroot00000000000000# # Author:: Steven Murawski () # 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 chef-12.14.60/lib/chef/application/knife.rb000066400000000000000000000134061276456504500202730ustar00rootroot00000000000000# # Author:: Adam Jacob ( "-c CONFIG", :long => "--config CONFIG", :description => "The configuration file to use", :proc => lambda { |path| File.expand_path(path, Dir.pwd) } option :config_option, :long => "--config-option OPTION=VALUE", :description => "Override a single configuration option", :proc => lambda { |option, existing| (existing ||= []) << option existing } verbosity_level = 0 option :verbosity, :short => "-V", :long => "--verbose", :description => "More verbose output. Use twice for max verbosity", :proc => Proc.new { verbosity_level += 1 }, :default => 0 option :color, :long => "--[no-]color", :boolean => true, :default => true, :description => "Use colored output, defaults to enabled" option :environment, :short => "-E ENVIRONMENT", :long => "--environment ENVIRONMENT", :description => "Set the Chef environment (except for in searches, where this will be flagrantly ignored)" option :editor, :short => "-e EDITOR", :long => "--editor EDITOR", :description => "Set the editor to use for interactive commands", :default => ENV["EDITOR"] option :disable_editing, :short => "-d", :long => "--disable-editing", :description => "Do not open EDITOR, just accept the data as is", :boolean => true, :default => false option :help, :short => "-h", :long => "--help", :description => "Show this message", :on => :tail, :boolean => true option :node_name, :short => "-u USER", :long => "--user USER", :description => "API Client Username" option :client_key, :short => "-k KEY", :long => "--key KEY", :description => "API Client Key", :proc => lambda { |path| File.expand_path(path, Dir.pwd) } option :chef_server_url, :short => "-s URL", :long => "--server-url URL", :description => "Chef Server URL" option :yes, :short => "-y", :long => "--yes", :description => "Say yes to all prompts for confirmation" option :defaults, :long => "--defaults", :description => "Accept default values for all questions" option :print_after, :long => "--print-after", :description => "Show the data after a destructive operation" option :format, :short => "-F FORMAT", :long => "--format FORMAT", :description => "Which format to use for output", :default => "summary" option :local_mode, :short => "-z", :long => "--local-mode", :description => "Point knife commands at local repository instead of server", :boolean => true option :chef_zero_host, :long => "--chef-zero-host HOST", :description => "Host to start chef-zero on" option :chef_zero_port, :long => "--chef-zero-port PORT", :description => "Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works." option :listen, :long => "--[no-]listen", :description => "Whether a local mode (-z) server binds to a port", :boolean => true option :version, :short => "-v", :long => "--version", :description => "Show chef version", :boolean => true, :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" }, :exit => 0 option :fips, :long => "--[no-]fips", :description => "Enable fips mode", :boolean => true, :default => nil # Run knife def run Mixlib::Log::Formatter.show_time = false validate_and_parse_options quiet_traps Chef::Knife.run(ARGV, options) exit 0 end private def quiet_traps trap("TERM") do exit 1 end trap("INT") do exit 2 end end def validate_and_parse_options # Checking ARGV validity *before* parse_options because parse_options # mangles ARGV in some situations if no_command_given? print_help_and_exit(1, NO_COMMAND_GIVEN) elsif no_subcommand_given? if want_help? || want_version? print_help_and_exit(0) else print_help_and_exit(2, NO_COMMAND_GIVEN) end end end def no_subcommand_given? ARGV[0] =~ /^-/ end def no_command_given? ARGV.empty? end def want_help? ARGV[0] =~ /^(--help|-h)$/ end def want_version? ARGV[0] =~ /^(--version|-v)$/ end def print_help_and_exit(exitcode = 1, fatal_message = nil) Chef::Log.error(fatal_message) if fatal_message begin self.parse_options rescue OptionParser::InvalidOption => e puts "#{e}\n" end puts self.opt_parser puts Chef::Knife.list_commands exit exitcode end end chef-12.14.60/lib/chef/application/solo.rb000066400000000000000000000272071276456504500201570ustar00rootroot00000000000000# # Author:: AJ Christensen () # Author:: Mark Mzyk (mmzyk@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" require "chef/application" require "chef/application/client" require "chef/client" require "chef/config" require "chef/daemon" require "chef/log" require "chef/rest" require "chef/config_fetcher" require "fileutils" require "chef/mixin/shell_out" require "pathname" require "chef-config/mixin/dot_d" require "mixlib/archive" class Chef::Application::Solo < Chef::Application include Chef::Mixin::ShellOut include ChefConfig::Mixin::DotD option :config_file, :short => "-c CONFIG", :long => "--config CONFIG", :default => Chef::Config.platform_specific_path("/etc/chef/solo.rb"), :description => "The configuration file to use" option :config_option, :long => "--config-option OPTION=VALUE", :description => "Override a single configuration option", :proc => lambda { |option, existing| (existing ||= []) << option existing } option :formatter, :short => "-F FORMATTER", :long => "--format FORMATTER", :description => "output format to use", :proc => lambda { |format| Chef::Config.add_formatter(format) } option :force_logger, :long => "--force-logger", :description => "Use logger output instead of formatter output", :boolean => true, :default => false option :force_formatter, :long => "--force-formatter", :description => "Use formatter output instead of logger output", :boolean => true, :default => false option :profile_ruby, :long => "--[no-]profile-ruby", :description => "Dump complete Ruby call graph stack of entire Chef run (expert only)", :boolean => true, :default => false option :color, :long => "--[no-]color", :boolean => true, :default => !Chef::Platform.windows?, :description => "Use colored output, defaults to enabled" option :log_level, :short => "-l LEVEL", :long => "--log_level LEVEL", :description => "Set the log level (debug, info, warn, error, fatal)", :proc => lambda { |l| l.to_sym } option :log_location, :short => "-L LOGLOCATION", :long => "--logfile LOGLOCATION", :description => "Set the log file location, defaults to STDOUT", :proc => nil option :help, :short => "-h", :long => "--help", :description => "Show this message", :on => :tail, :boolean => true, :show_options => true, :exit => 0 option :user, :short => "-u USER", :long => "--user USER", :description => "User to set privilege to", :proc => nil option :group, :short => "-g GROUP", :long => "--group GROUP", :description => "Group to set privilege to", :proc => nil unless Chef::Platform.windows? option :daemonize, :short => "-d", :long => "--daemonize", :description => "Daemonize the process", :proc => lambda { |p| true } end option :lockfile, :long => "--lockfile LOCKFILE", :description => "Set the lockfile location. Prevents multiple processes from converging at the same time", :proc => nil option :interval, :short => "-i SECONDS", :long => "--interval SECONDS", :description => "Run chef-client periodically, in seconds", :proc => lambda { |s| s.to_i } option :json_attribs, :short => "-j JSON_ATTRIBS", :long => "--json-attributes JSON_ATTRIBS", :description => "Load attributes from a JSON file or URL", :proc => nil option :node_name, :short => "-N NODE_NAME", :long => "--node-name NODE_NAME", :description => "The node name for this client", :proc => nil option :splay, :short => "-s SECONDS", :long => "--splay SECONDS", :description => "The splay time for running at intervals, in seconds", :proc => lambda { |s| s.to_i } option :recipe_url, :short => "-r RECIPE_URL", :long => "--recipe-url RECIPE_URL", :description => "Pull down a remote gzipped tarball of recipes and untar it to the cookbook cache." option :version, :short => "-v", :long => "--version", :description => "Show chef version", :boolean => true, :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" }, :exit => 0 option :override_runlist, :short => "-o RunlistItem,RunlistItem...", :long => "--override-runlist RunlistItem,RunlistItem...", :description => "Replace current run list with specified items", :proc => lambda {|items| items = items.split(",") items.compact.map do |item| Chef::RunList::RunListItem.new(item) end } option :client_fork, :short => "-f", :long => "--[no-]fork", :description => "Fork client", :boolean => true option :why_run, :short => "-W", :long => "--why-run", :description => "Enable whyrun mode", :boolean => true option :ez, :long => "--ez", :description => "A memorial for Ezra Zygmuntowicz", :boolean => true option :environment, :short => "-E ENVIRONMENT", :long => "--environment ENVIRONMENT", :description => "Set the Chef Environment on the node" option :run_lock_timeout, :long => "--run-lock-timeout SECONDS", :description => "Set maximum duration to wait for another client run to finish, default is indefinitely.", :proc => lambda { |s| s.to_i } option :minimal_ohai, :long => "--minimal-ohai", :description => "Only run the bare minimum ohai plugins chef needs to function", :boolean => true option :delete_entire_chef_repo, :long => "--delete-entire-chef-repo", :description => "DANGEROUS: does what it says, only useful with --recipe-url", :boolean => true option :solo_legacy_mode, :long => "--legacy-mode", :description => "Run chef-solo in legacy mode", :boolean => true attr_reader :chef_client_json # Get this party started def run setup_signal_handlers reconfigure for_ezra if Chef::Config[:ez] if !Chef::Config[:solo_legacy_mode] Chef::Application::Client.new.run else setup_application run_application end end def reconfigure super load_dot_d(Chef::Config[:solo_d_dir]) if Chef::Config[:solo_d_dir] set_specific_recipes Chef::Config[:solo] = true 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 # For back compat reasons, we need to ensure that we try and use the cache_path as a repo first Chef::Log.debug "Current chef_repo_path is #{Chef::Config.chef_repo_path}" if !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path) Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Chef::Config[:cache_path]) end Chef::Config[:local_mode] = true else configure_legacy_mode! end end def configure_legacy_mode! if Chef::Config[:daemonize] Chef::Config[:interval] ||= 1800 end Chef::Application.fatal!(unforked_interval_error_message) if !Chef::Config[:client_fork] && Chef::Config[:interval] if Chef::Config[:recipe_url] cookbooks_path = Array(Chef::Config[:cookbook_path]).detect { |e| Pathname.new(e).cleanpath.to_s =~ /\/cookbooks\/*$/ } recipes_path = File.expand_path(File.join(cookbooks_path, "..")) if Chef::Config[:delete_entire_chef_repo] Chef::Log.debug "Cleanup path #{recipes_path} before extract recipes into it" FileUtils.rm_rf(recipes_path, :secure => true) end Chef::Log.debug "Creating path #{recipes_path} to extract recipes into" FileUtils.mkdir_p(recipes_path) tarball_path = File.join(recipes_path, "recipes.tgz") fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path) Mixlib::Archive.new(tarball_path).extract(Chef::Config.chef_repo_path, perms: false, ignore: /^\.$/) end # json_attribs shuld be fetched after recipe_url tarball is unpacked. # Otherwise it may fail if points to local file from tarball. if Chef::Config[:json_attribs] config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs]) @chef_client_json = config_fetcher.fetch_json end # Disable auditing for solo Chef::Config[:audit_mode] = :disabled end def setup_application Chef::Daemon.change_privilege end def run_application if !Chef::Config[:client_fork] || Chef::Config[:once] # Run immediately without interval sleep or splay begin run_chef_client(Chef::Config[:specific_recipes]) rescue SystemExit raise rescue Exception => e Chef::Application.fatal!("#{e.class}: #{e.message}", e) end else interval_run_chef_client end end private def for_ezra puts <<-EOH For Ezra Zygmuntowicz: The man who brought you Chef Solo Early contributor to Chef Kind hearted open source advocate Rest in peace, Ezra. EOH end def interval_run_chef_client if Chef::Config[:daemonize] Chef::Daemon.daemonize("chef-client") end loop do begin sleep_sec = 0 sleep_sec += rand(Chef::Config[:splay]) if Chef::Config[:splay] sleep_sec += Chef::Config[:interval] if Chef::Config[:interval] if sleep_sec != 0 Chef::Log.debug("Sleeping for #{sleep_sec} seconds") sleep(sleep_sec) end run_chef_client if !Chef::Config[:interval] Chef::Application.exit! "Exiting", 0 end rescue SystemExit => e raise rescue Exception => e if Chef::Config[:interval] Chef::Log.error("#{e.class}: #{e}") Chef::Log.debug("#{e.class}: #{e}\n#{e.backtrace.join("\n")}") retry else Chef::Application.fatal!("#{e.class}: #{e.message}", e) end end end end def fetch_recipe_tarball(url, path) Chef::Log.debug("Download recipes tarball from #{url} to #{path}") File.open(path, "wb") do |f| open(url) do |r| f.write(r.read) end end end def unforked_interval_error_message "Unforked chef-client interval runs are disabled in Chef 12." + "\nConfiguration settings:" + "#{"\n interval = #{Chef::Config[:interval]} seconds" if Chef::Config[:interval]}" + "\nEnable chef-client interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options." end end chef-12.14.60/lib/chef/application/windows_service.rb000066400000000000000000000313751276456504500224160ustar00rootroot00000000000000# # Author:: Christopher Maier () # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef" require "chef/monologger" require "chef/application" require "chef/client" require "chef/config" require "chef/handler/error_report" require "chef/log" require "chef/http" require "mixlib/cli" require "socket" require "uri" require "win32/daemon" require "chef/mixin/shell_out" class Chef class Application class WindowsService < ::Win32::Daemon include Mixlib::CLI include Chef::Mixin::ShellOut option :config_file, :short => "-c CONFIG", :long => "--config CONFIG", :default => "#{ENV['SYSTEMDRIVE']}/chef/client.rb", :description => "" option :log_location, :short => "-L LOGLOCATION", :long => "--logfile LOGLOCATION", :description => "Set the log file location" option :splay, :short => "-s SECONDS", :long => "--splay SECONDS", :description => "The splay time for running at intervals, in seconds", :proc => lambda { |s| s.to_i } option :interval, :short => "-i SECONDS", :long => "--interval SECONDS", :description => "Set the number of seconds to wait between chef-client runs", :proc => lambda { |s| s.to_i } DEFAULT_LOG_LOCATION ||= "#{ENV['SYSTEMDRIVE']}/chef/client.log" def service_init @service_action_mutex = Mutex.new @service_signal = ConditionVariable.new reconfigure Chef::Log.info("Chef Client Service initialized") end def service_main(*startup_parameters) # Chef::Config is initialized during service_init # Set the initial timeout to splay sleep time timeout = rand Chef::Config[:splay] while running? # Grab the service_action_mutex to make a chef-client run @service_action_mutex.synchronize do begin Chef::Log.info("Next chef-client run will happen in #{timeout} seconds") @service_signal.wait(@service_action_mutex, timeout) # Continue only if service is RUNNING next if state != RUNNING # Reconfigure each time through to pick up any changes in the client file Chef::Log.info("Reconfiguring with startup parameters") reconfigure(startup_parameters) timeout = Chef::Config[:interval] # Honor splay sleep config timeout += rand Chef::Config[:splay] # run chef-client only if service is in RUNNING state next if state != RUNNING Chef::Log.info("Chef-Client service is starting a chef-client run...") run_chef_client rescue SystemExit => e # Do not raise any of the errors here in order to # prevent service crash Chef::Log.error("#{e.class}: #{e}") rescue Exception => e Chef::Log.error("#{e.class}: #{e}") end end end # Daemon class needs to have all the signal callbacks return # before service_main returns. Chef::Log.debug("Giving signal callbacks some time to exit...") sleep 1 Chef::Log.debug("Exiting service...") end ################################################################################ # Control Signal Callback Methods ################################################################################ def service_stop run_warning_displayed = false Chef::Log.info("STOP request from operating system.") loop do # See if a run is in flight if @service_action_mutex.try_lock # Run is not in flight. Wake up service_main to exit. @service_signal.signal @service_action_mutex.unlock break else unless run_warning_displayed Chef::Log.info("Currently a chef run is happening on this system.") Chef::Log.info("Service will stop when run is completed.") run_warning_displayed = true end Chef::Log.debug("Waiting for chef-client run...") sleep 1 end end Chef::Log.info("Service is stopping....") end def service_pause Chef::Log.info("PAUSE request from operating system.") # We don't need to wake up the service_main if it's waiting # since this is a PAUSE signal. if @service_action_mutex.locked? Chef::Log.info("Currently a chef-client run is happening.") Chef::Log.info("Service will pause once it's completed.") else Chef::Log.info("Service is pausing....") end end def service_resume # We don't need to wake up the service_main if it's waiting # since this is a RESUME signal. Chef::Log.info("RESUME signal received from the OS.") Chef::Log.info("Service is resuming....") end def service_shutdown Chef::Log.info("SHUTDOWN signal received from the OS.") # Treat shutdown similar to stop. service_stop end ################################################################################ # Internal Methods ################################################################################ private # Initializes Chef::Client instance and runs it def run_chef_client # The chef client will be started in a new process. We have used shell_out to start the chef-client. # The log_location and config_file of the parent process is passed to the new chef-client process. # We need to add the --no-fork, as by default it is set to fork=true. begin Chef::Log.info "Starting chef-client in a new process" # Pass config params to the new process config_params = " --no-fork" config_params += " -c #{Chef::Config[:config_file]}" unless Chef::Config[:config_file].nil? # log_location might be an event logger and if so we cannot pass as a command argument # but shed no tears! If the logger is an event logger, it must have been configured # as such in the config file and chef-client will use that when no arg is passed here config_params += " -L #{resolve_log_location}" if resolve_log_location.is_a?(String) # Starts a new process and waits till the process exits result = shell_out( "chef-client.bat #{config_params}", :timeout => Chef::Config[:windows_service][:watchdog_timeout], :logger => Chef::Log ) Chef::Log.debug "#{result.stdout}" Chef::Log.debug "#{result.stderr}" rescue Mixlib::ShellOut::CommandTimeout => e Chef::Log.error "chef-client timed out\n(#{e})" Chef::Log.error(<<-EOF) Your chef-client run timed out. You can increase the time chef-client is given to complete by configuring windows_service.watchdog_timeout in your client.rb. EOF rescue Mixlib::ShellOut::ShellCommandFailed => e Chef::Log.warn "Not able to start chef-client in new process (#{e})" rescue => e Chef::Log.error e ensure # Once process exits, we log the current process' pid Chef::Log.info "Child process exited (pid: #{Process.pid})" end end def apply_config(config_file_path) Chef::Config.from_file(config_file_path) Chef::Config.merge!(config) end # Lifted from Chef::Application, with addition of optional startup parameters # for playing nicely with Windows Services def reconfigure(startup_parameters = []) configure_chef startup_parameters configure_logging Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url unless Chef::Config[:exception_handlers].any? { |h| Chef::Handler::ErrorReport === h } Chef::Config[:exception_handlers] << Chef::Handler::ErrorReport.new end Chef::Config[:interval] ||= 1800 end # Lifted from application.rb # See application.rb for related comments. def configure_logging Chef::Log.init(MonoLogger.new(resolve_log_location)) if want_additional_logger? configure_stdout_logger end Chef::Log.level = resolve_log_level end def configure_stdout_logger stdout_logger = MonoLogger.new(STDOUT) stdout_logger.formatter = Chef::Log.logger.formatter Chef::Log.loggers << stdout_logger end # Based on config and whether or not STDOUT is a tty, should we setup a # secondary logger for stdout? def want_additional_logger? ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger]) end # Use of output formatters is assumed if `force_formatter` is set or if # `force_logger` is not set and STDOUT is to a console (tty) def using_output_formatter? Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?) end def auto_log_level? Chef::Config[:log_level] == :auto end def resolve_log_location # STDOUT is the default log location, but makes no sense for a windows service Chef::Config[:log_location] == STDOUT ? DEFAULT_LOG_LOCATION : Chef::Config[:log_location] end # if log_level is `:auto`, convert it to :warn (when using output formatter) # or :info (no output formatter). See also +using_output_formatter?+ def resolve_log_level if auto_log_level? if using_output_formatter? :warn else :info end else Chef::Config[:log_level] end end def configure_chef(startup_parameters) # Bit of a hack ahead: # It is possible to specify a service's binary_path_name with arguments, like "foo.exe -x argX". # It is also possible to specify startup parameters separately, either via the Services manager # or by using the registry (I think). # In order to accommodate all possible sources of parameterization, we first parse any command line # arguments. We then parse any startup parameters. This works, because Mixlib::CLI reuses its internal # 'config' hash; thus, anything in startup parameters will override any command line parameters that # might be set via the service's binary_path_name # # All these parameters then get layered on top of those from Chef::Config parse_options # Operates on ARGV by default parse_options startup_parameters begin case config[:config_file] when /^(http|https):\/\// Chef::HTTP.new("").streaming_request(config[:config_file]) { |f| apply_config(f.path) } else ::File.open(config[:config_file]) { |f| apply_config(f.path) } end rescue Errno::ENOENT Chef::Log.warn("*****************************************") Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.") Chef::Log.warn("*****************************************") Chef::Config.merge!(config) rescue SocketError 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}", Chef::Exceptions::DeprecatedExitCode.new) rescue Exception => error Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}", Chef::Exceptions::DeprecatedExitCode.new) end end end end end # To run this file as a service, it must be called as a script from within # the Windows Service framework. In that case, kick off the main loop! if __FILE__ == $0 Chef::Application::WindowsService.mainloop end chef-12.14.60/lib/chef/application/windows_service_manager.rb000066400000000000000000000161571276456504500241110ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # if RUBY_PLATFORM =~ /mswin|mingw32|windows/ require "win32/service" end require "chef/config" require "mixlib/cli" class Chef class Application # # This class is used to create and manage a windows service. # Service should be created using Daemon class from # win32/service gem. # For an example see: Chef::Application::WindowsService # # Outside programs are expected to use this class to manage # windows services. # class WindowsServiceManager include Mixlib::CLI option :action, :short => "-a ACTION", :long => "--action ACTION", :default => "status", :description => "Action to carry out on chef-service (install, uninstall, status, start, stop, pause, or resume)" option :config_file, :short => "-c CONFIG", :long => "--config CONFIG", :default => "#{ENV['SYSTEMDRIVE']}/chef/client.rb", :description => "The configuration file to use for chef runs" option :log_location, :short => "-L LOGLOCATION", :long => "--logfile LOGLOCATION", :description => "Set the log file location for chef-service" option :help, :short => "-h", :long => "--help", :description => "Show this message", :on => :tail, :boolean => true, :show_options => true, :exit => 0 option :version, :short => "-v", :long => "--version", :description => "Show chef version", :boolean => true, :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" }, :exit => 0 def initialize(service_options) # having to call super in initialize is the most annoying # anti-pattern :( super() raise ArgumentError, "Service definition is not provided" if service_options.nil? required_options = [:service_name, :service_display_name, :service_description, :service_file_path] required_options.each do |req_option| if !service_options.has_key?(req_option) raise ArgumentError, "Service definition doesn't contain required option #{req_option}" end end @service_name = service_options[:service_name] @service_display_name = service_options[:service_display_name] @service_description = service_options[:service_description] @service_file_path = service_options[:service_file_path] @service_start_name = service_options[:run_as_user] @password = service_options[:run_as_password] @delayed_start = service_options[:delayed_start] @dependencies = service_options[:dependencies] end def run(params = ARGV) parse_options(params) case config[:action] when "install" if service_exists? puts "Service #{@service_name} already exists on the system." else ruby = File.join(RbConfig::CONFIG["bindir"], "ruby") opts = "" opts << " -c #{config[:config_file]}" if config[:config_file] opts << " -L #{config[:log_location]}" if config[:log_location] # Quote the full paths to deal with possible spaces in the path name. # Also ensure all forward slashes are backslashes cmd = "\"#{ruby}\" \"#{@service_file_path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR) ::Win32::Service.new( :service_name => @service_name, :display_name => @service_display_name, :description => @service_description, # Prior to 0.8.5, win32-service creates interactive services by default, # and we don't want that, so we need to override the service type. :service_type => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS, :start_type => ::Win32::Service::SERVICE_AUTO_START, :binary_path_name => cmd, :service_start_name => @service_start_name, :password => @password, :dependencies => @dependencies ) ::Win32::Service.configure( :service_name => @service_name, :delayed_start => @delayed_start ) unless @delayed_start.nil? puts "Service '#{@service_name}' has successfully been installed." end when "status" if !service_exists? puts "Service #{@service_name} doesn't exist on the system." else puts "State of #{@service_name} service is: #{current_state}" end when "start" # TODO: allow override of startup parameters here? take_action("start", RUNNING) when "stop" take_action("stop", STOPPED) when "uninstall", "delete" take_action("stop", STOPPED) unless service_exists? puts "Service #{@service_name} doesn't exist on the system." else ::Win32::Service.delete(@service_name) puts "Service #{@service_name} deleted" end when "pause" take_action("pause", PAUSED) when "resume" take_action("resume", RUNNING) end end private # Just some state constants STOPPED = "stopped" RUNNING = "running" PAUSED = "paused" def service_exists? return ::Win32::Service.exists?(@service_name) end def take_action(action = nil, desired_state = nil) if service_exists? if current_state != desired_state ::Win32::Service.send(action, @service_name) wait_for_state(desired_state) puts "Service '#{@service_name}' is now '#{current_state}'." else puts "Service '#{@service_name}' is already '#{desired_state}'." end else puts "Cannot '#{action}' service '#{@service_name}'" puts "Service #{@service_name} doesn't exist on the system." end end def current_state ::Win32::Service.status(@service_name).current_state end # Helper method that waits for a status to change its state since state # changes aren't usually instantaneous. def wait_for_state(desired_state) while current_state != desired_state puts "One moment... #{current_state}" sleep 1 end end end end end chef-12.14.60/lib/chef/applications.rb000066400000000000000000000002041276456504500173520ustar00rootroot00000000000000require "chef/application/client" require "chef/application/knife" require "chef/application/solo" require "chef/application/apply" chef-12.14.60/lib/chef/audit/000077500000000000000000000000001276456504500154515ustar00rootroot00000000000000chef-12.14.60/lib/chef/audit/audit_event_proxy.rb000066400000000000000000000064251276456504500215550ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # RSpec::Support.require_rspec_core "formatters/base_text_formatter" class Chef class Audit class AuditEventProxy < ::RSpec::Core::Formatters::BaseFormatter ::RSpec::Core::Formatters.register self, :stop, :example_group_started # TODO I don't like this, but I don't see another way to pass this in # see rspec files configuration.rb#L671 and formatters.rb#L129 def self.events=(events) @@events = events end def events @@events end def example_group_started(notification) if notification.group.parent_groups.size == 1 # top level `control_group` block desc = notification.group.description Chef::Log.debug("Entered `control_group` block named #{desc}") events.control_group_started(desc) end end def stop(notification) Chef::Log.info("Successfully executed all `control_group` blocks and contained examples") notification.examples.each do |example| control_group_name, control_data = build_control_from(example) e = example.exception if e events.control_example_failure(control_group_name, control_data, e) else events.control_example_success(control_group_name, control_data) end end end private def build_control_from(example) described_class = example.metadata[:described_class] if described_class resource_type = described_class.class.name.split(":")[-1] resource_name = described_class.name end # The following code builds up the context - the list of wrapping `describe` or `control` blocks describe_groups = [] group = example.metadata[:example_group] # If the innermost block has a resource instead of a string, don't include it in context describe_groups.unshift(group[:description]) if described_class.nil? group = group[:parent_example_group] until group.nil? describe_groups.unshift(group[:description]) group = group[:parent_example_group] end # We know all of our examples each live in a top-level `control_group` block - get this name now outermost_group_desc = describe_groups.shift return outermost_group_desc, { :name => example.description, :desc => example.full_description, :resource_type => resource_type, :resource_name => resource_name, :context => describe_groups, :line_number => example.metadata[:line_number], } end end end end chef-12.14.60/lib/chef/audit/audit_reporter.rb000066400000000000000000000143721276456504500210350ustar00rootroot00000000000000# # Author:: Tyler Ball () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/event_dispatch/base" require "chef/audit/control_group_data" require "time" class Chef class Audit class AuditReporter < EventDispatch::Base attr_reader :rest_client, :audit_data, :ordered_control_groups, :run_status private :rest_client, :audit_data, :ordered_control_groups, :run_status PROTOCOL_VERSION = "0.1.1" def initialize(rest_client) @rest_client = rest_client # Ruby 1.9.3 and above "enumerate their values in the order that the corresponding keys were inserted." @ordered_control_groups = Hash.new @audit_phase_error = nil end def run_context run_status.run_context end def audit_phase_start(run_status) Chef::Log.debug("Audit Reporter starting") @audit_data = AuditData.new(run_status.node.name, run_status.run_id) @run_status = run_status end def audit_phase_complete(audit_output) Chef::Log.debug("Audit Reporter completed successfully without errors.") ordered_control_groups.each do |name, control_group| audit_data.add_control_group(control_group) end end # If the audit phase failed, its because there was some kind of error in the framework # that runs tests - normal errors are interpreted as EXAMPLE failures and captured. # We still want to send available audit information to the server so we process the # known control groups. def audit_phase_failed(error, audit_output) # The stacktrace information has already been logged elsewhere @audit_phase_error = error Chef::Log.debug("Audit Reporter failed.") ordered_control_groups.each do |name, control_group| audit_data.add_control_group(control_group) end end def run_completed(node) post_auditing_data end def run_failed(error) # Audit phase errors are captured when audit_phase_failed gets called. # The error passed here isn't relevant to auditing, so we ignore it. post_auditing_data end def control_group_started(name) if ordered_control_groups.has_key?(name) raise Chef::Exceptions::AuditControlGroupDuplicate.new(name) end metadata = run_context.audits[name].metadata ordered_control_groups.store(name, ControlGroupData.new(name, metadata)) end def control_example_success(control_group_name, example_data) control_group = ordered_control_groups[control_group_name] control_group.example_success(example_data) end def control_example_failure(control_group_name, example_data, error) control_group = ordered_control_groups[control_group_name] control_group.example_failure(example_data, error.message) end # If @audit_enabled is nil or true, we want to run audits def auditing_enabled? Chef::Config[:audit_mode] != :disabled end private def post_auditing_data unless auditing_enabled? Chef::Log.debug("Audit Reports are disabled. Skipping sending reports.") return end unless run_status Chef::Log.debug("Run failed before audit mode was initialized, not sending audit report to server") return end audit_data.start_time = iso8601ify(run_status.start_time) audit_data.end_time = iso8601ify(run_status.end_time) audit_history_url = "controls" Chef::Log.debug("Sending audit report (run-id: #{audit_data.run_id})") run_data = audit_data.to_hash if @audit_phase_error error_info = "#{@audit_phase_error.class}: #{@audit_phase_error.message}" error_info << "\n#{@audit_phase_error.backtrace.join("\n")}" if @audit_phase_error.backtrace run_data[:error] = error_info end Chef::Log.debug "Audit Report:\n#{Chef::JSONCompat.to_json_pretty(run_data)}" begin rest_client.post(audit_history_url, run_data, headers) rescue StandardError => e if e.respond_to? :response # 404 error code is OK. This means the version of server we're running against doesn't support # audit reporting. Don't alarm failure in this case. if e.response.code == "404" Chef::Log.debug("Server doesn't support audit reporting. Skipping report.") return else # Save the audit report to local disk error_file = "failed-audit-data.json" Chef::FileCache.store(error_file, Chef::JSONCompat.to_json_pretty(run_data), 0640) if Chef::Config.chef_zero.enabled Chef::Log.debug("Saving audit report to #{Chef::FileCache.load(error_file, false)}") else Chef::Log.error("Failed to post audit report to server. Saving report to #{Chef::FileCache.load(error_file, false)}") end end else Chef::Log.error("Failed to post audit report to server (#{e})") end if Chef::Config[:enable_reporting_url_fatals] Chef::Log.error("Reporting fatals enabled. Aborting run.") raise end end end def headers(additional_headers = {}) options = { "X-Ops-Audit-Report-Protocol-Version" => PROTOCOL_VERSION } options.merge(additional_headers) end def encode_gzip(data) "".tap do |out| Zlib::GzipWriter.wrap(StringIO.new(out)) { |gz| gz << data } end end def iso8601ify(time) time.utc.iso8601.to_s end end end end chef-12.14.60/lib/chef/audit/control_group_data.rb000066400000000000000000000075351276456504500216750ustar00rootroot00000000000000# # Author:: Tyler Ball () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "securerandom" class Chef class Audit class AuditData attr_reader :node_name, :run_id, :control_groups attr_accessor :start_time, :end_time def initialize(node_name, run_id) @node_name = node_name @run_id = run_id @control_groups = [] end def add_control_group(control_group) control_groups << control_group end def to_hash { :node_name => node_name, :run_id => run_id, :start_time => start_time, :end_time => end_time, :control_groups => control_groups.collect { |c| c.to_hash }, } end end class ControlGroupData attr_reader :name, :status, :number_succeeded, :number_failed, :controls, :metadata def initialize(name, metadata = {}) @status = "success" @controls = [] @number_succeeded = 0 @number_failed = 0 @name = name @metadata = metadata end def example_success(control_data) @number_succeeded += 1 control = create_control(control_data) control.status = "success" controls << control control end def example_failure(control_data, details) @number_failed += 1 @status = "failure" control = create_control(control_data) control.details = details if details control.status = "failure" controls << control control end def to_hash # We sort it so the examples appear in the output in the same order # they appeared in the recipe controls.sort! { |x, y| x.line_number <=> y.line_number } h = { :name => name, :status => status, :number_succeeded => number_succeeded, :number_failed => number_failed, :controls => controls.collect { |c| c.to_hash }, } # If there is a duplicate key, metadata will overwrite it add_display_only_data(h).merge(metadata) end private def create_control(control_data) ControlData.new(control_data) end # The id and control sequence number are ephemeral data - they are not needed # to be persisted and can be regenerated at will. They are only needed # for display purposes. def add_display_only_data(group) group[:id] = SecureRandom.uuid group[:controls].collect!.with_index do |c, i| # i is zero-indexed, and we want the display one-indexed c[:sequence_number] = i + 1 c end group end end class ControlData attr_reader :name, :resource_type, :resource_name, :context, :line_number attr_accessor :status, :details def initialize(control_data = {}) control_data.each do |k, v| self.instance_variable_set("@#{k}", v) end end def to_hash h = { :name => name, :status => status, :details => details, :resource_type => resource_type, :resource_name => resource_name, } h[:context] = context || [] h end end end end chef-12.14.60/lib/chef/audit/logger.rb000066400000000000000000000017011276456504500172540ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "stringio" class Chef class Audit class Logger def self.puts(message = "") @buffer ||= StringIO.new @buffer.puts(message) Chef::Log.info(message) end def self.read_buffer return "" if @buffer.nil? @buffer.string end end end end chef-12.14.60/lib/chef/audit/rspec_formatter.rb000066400000000000000000000023011276456504500211710ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "rspec/core" class Chef class Audit class RspecFormatter < RSpec::Core::Formatters::DocumentationFormatter RSpec::Core::Formatters.register self, :close # @api public # # Invoked at the very end, `close` allows the formatter to clean # up resources, e.g. open streams, etc. # # @param _notification [NullNotification] (Ignored) def close(_notification) # Normally Rspec closes the streams it's given. We don't want it for Chef. end end end end chef-12.14.60/lib/chef/audit/runner.rb000066400000000000000000000153701276456504500173150ustar00rootroot00000000000000# # Author:: Claire McQuin () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/audit/logger" class Chef class Audit class Runner attr_reader :run_context private :run_context def initialize(run_context) @run_context = run_context end def run setup register_control_groups do_run end def failed? RSpec.world.reporter.failed_examples.size > 0 end def num_failed RSpec.world.reporter.failed_examples.size end def num_total RSpec.world.reporter.examples.size end def exclusion_pattern Regexp.new(".+[\\\/]lib[\\\/]chef[\\\/]") end private # Prepare to run audits: # - Require files # - Configure RSpec # - Configure Specinfra/Serverspec def setup require_deps configure_rspec configure_specinfra end # RSpec uses a global configuration object, RSpec.configuration. We found # there was interference between the configuration for audit-mode and # the configuration for our own spec tests in these cases: # 1. Specinfra and Serverspec modify RSpec.configuration when loading. # 2. Setting output/error streams. # 3. Adding formatters. # 4. Defining example group aliases. # # Moreover, Serverspec loads its DSL methods into the global namespace, # which causes conflicts with the Chef namespace for resources and packages. # # We wait until we're in the audit-phase of the chef-client run to load # these files. This helps with the namespacing problems we saw, and # prevents Specinfra and Serverspec from modifying the RSpec configuration # used by our spec tests. def require_deps require "rspec" require "rspec/its" require "specinfra" require "specinfra/helper" require "specinfra/helper/set" require "serverspec/helper" require "serverspec/matcher" require "serverspec/subject" require "chef/audit/audit_event_proxy" require "chef/audit/rspec_formatter" Specinfra::Backend::Cmd.send(:include, Specinfra::Helper::Set) end # Configure RSpec just the way we like it: # - Set location of error and output streams # - Add custom audit-mode formatters # - Explicitly disable :should syntax # - Set :color option according to chef config # - Disable exposure of global DSL def configure_rspec set_streams add_formatters disable_should_syntax RSpec.configure do |c| c.color = Chef::Config[:color] c.expose_dsl_globally = false c.project_source_dirs = Array(Chef::Config[:cookbook_path]) c.backtrace_exclusion_patterns << exclusion_pattern end end # Set the error and output streams which audit-mode will use to report # human-readable audit information. # # This should always be called before #add_formatters. RSpec won't allow # the output stream to be changed for a formatter once the formatter has # been added. def set_streams RSpec.configuration.output_stream = Chef::Audit::Logger RSpec.configuration.error_stream = Chef::Audit::Logger end # Add formatters which we use to # 1. Output human-readable data to the output stream, # 2. Collect JSON data to send back to the analytics server. def add_formatters RSpec.configuration.add_formatter(Chef::Audit::AuditEventProxy) RSpec.configuration.add_formatter(Chef::Audit::RspecFormatter) Chef::Audit::AuditEventProxy.events = run_context.events end # Audit-mode uses RSpec 3. :should syntax is deprecated by default in # RSpec 3, so we explicitly disable it here. # # This can be removed once :should is removed from RSpec. def disable_should_syntax RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = :expect end end end # Set up the backend for Specinfra/Serverspec. :exec is the local system; on Windows, it is :cmd def configure_specinfra if Chef::Platform.windows? Specinfra.configuration.backend = :cmd Specinfra.configuration.os = { :family => "windows" } else Specinfra.configuration.backend = :exec end end # Iterates through the control groups registered to this run_context, builds an # example group (RSpec::Core::ExampleGroup) object per control group, and # registers the group with the RSpec.world. # # We could just store an array of example groups and not use RSpec.world, # but it may be useful later if we decide to apply our own ordering scheme # or use example group filters. def register_control_groups add_example_group_methods run_context.audits.each do |name, group| ctl_grp = RSpec::Core::ExampleGroup.__control_group__(*group.args, &group.block) RSpec.world.record(ctl_grp) end end # Add example group method aliases to RSpec. # # __control_group__: Used internally to create example groups from the control # groups saved in the run_context. # control: Used within the context of a control group block, like RSpec's # describe or context. def add_example_group_methods RSpec::Core::ExampleGroup.define_example_group_method :__control_group__ RSpec::Core::ExampleGroup.define_example_group_method :control end # Run the audits! def do_run # RSpec::Core::Runner wants to be initialized with an # RSpec::Core::ConfigurationOptions object, which is used to process # command line configuration arguments. We directly fiddle with the # internal RSpec configuration object, so we give nil here and let # RSpec pick up its own configuration and world. runner = RSpec::Core::Runner.new(nil) runner.run_specs(RSpec.world.ordered_example_groups) end end end end chef-12.14.60/lib/chef/chef_class.rb000066400000000000000000000166721276456504500167760ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # NOTE: This class is not intended for internal use by the chef-client code. Classes in # chef-client should still have objects like the node and run_context injected into them # via their initializers. This class is still global state and will complicate writing # unit tests for internal Chef objects. It is intended to be used only by recipe code. # NOTE: When adding require lines here you are creating tight coupling on a global that may be # included in many different situations and ultimately that ends in tears with circular requires. # Note the way that the run_context, provider_priority_map and resource_priority_map are "dependency # injected" into this class by other objects and do not reference the class symbols in those files # directly and we do not need to require those files here. require "chef/platform/provider_priority_map" require "chef/platform/resource_priority_map" require "chef/platform/provider_handler_map" require "chef/platform/resource_handler_map" class Chef class << self # # Public API # # # Get the node object # # @return [Chef::Node] node object of the chef-client run # attr_reader :node # # Get the run context # # @return [Chef::RunContext] run_context of the chef-client run # attr_reader :run_context # Register an event handler with user specified block # # @return[Chef::EventDispatch::Base] handler object def event_handler(&block) dsl = Chef::EventDispatch::DSL.new("Chef client DSL") dsl.instance_eval(&block) end # Get the array of providers associated with a resource_name for the current node # # @param resource_name [Symbol] name of the resource as a symbol # # @return [Array] Priority Array of Provider Classes to use for the resource_name on the node # def get_provider_priority_array(resource_name) result = provider_priority_map.get_priority_array(node, resource_name.to_sym) result = result.dup if result result end # # Get the array of resources associated with a resource_name for the current node # # @param resource_name [Symbol] name of the resource as a symbol # # @return [Array] Priority Array of Resource Classes to use for the resource_name on the node # def get_resource_priority_array(resource_name) result = resource_priority_map.get_priority_array(node, resource_name.to_sym) result = result.dup if result result end # # Set the array of providers associated with a resource_name for the current node # # @param resource_name [Symbol] name of the resource as a symbol # @param priority_array [Class, Array] Class or Array of Classes to set as the priority for resource_name on the node # @param filter [Hash] Chef::Nodearray-style filter # # @return [Array] Modified Priority Array of Provider Classes to use for the resource_name on the node # def set_provider_priority_array(resource_name, priority_array, *filter, &block) result = provider_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block) result = result.dup if result result end # # Get the array of resources associated with a resource_name for the current node # # @param resource_name [Symbol] name of the resource as a symbol # @param priority_array [Class, Array] Class or Array of Classes to set as the priority for resource_name on the node # @param filter [Hash] Chef::Nodearray-style filter # # @return [Array] Modified Priority Array of Resource Classes to use for the resource_name on the node # def set_resource_priority_array(resource_name, priority_array, *filter, &block) result = resource_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block) result = result.dup if result result end # # Dependency Injection API (Private not Public) # [ in the ruby sense these have to be public methods, but they are # *NOT* for public consumption ] # # # Sets the resource_priority_map # # @param resource_priority_map [Chef::Platform::ResourcePriorityMap] # # @api private def set_resource_priority_map(resource_priority_map) @resource_priority_map = resource_priority_map end # # Sets the provider_priority_map # # @param provider_priority_map [Chef::Platform::providerPriorityMap] # # @api private def set_provider_priority_map(provider_priority_map) @provider_priority_map = provider_priority_map end # # Sets the node object # # @api private # @param node [Chef::Node] def set_node(node) @node = node end # # Sets the run_context object # # @param run_context [Chef::RunContext] # # @api private def set_run_context(run_context) @run_context = run_context end # # Resets the internal state # # @api private def reset! @run_context = nil @node = nil @provider_priority_map = nil @resource_priority_map = nil @provider_handler_map = nil @resource_handler_map = nil end # @api private def provider_priority_map # these slurp in the resource+provider world, so be exceedingly lazy about requiring them @provider_priority_map ||= Chef::Platform::ProviderPriorityMap.instance end # @api private def resource_priority_map @resource_priority_map ||= Chef::Platform::ResourcePriorityMap.instance end # @api private def provider_handler_map @provider_handler_map ||= Chef::Platform::ProviderHandlerMap.instance end # @api private def resource_handler_map @resource_handler_map ||= Chef::Platform::ResourceHandlerMap.instance end # # Emit a deprecation message. # # @param message The message to send. # @param location The location. Defaults to the caller who called you (since # generally the person who triggered the check is the one that needs to be # fixed). # # @example # Chef.deprecation("Deprecated!") # # @api private this will likely be removed in favor of an as-yet unwritten # `Chef.log` def log_deprecation(message, location = nil) location ||= Chef::Log.caller_location # `run_context.events` is the primary deprecation target if we're in a # run. If we are not yet in a run, print to `Chef::Log`. if run_context && run_context.events run_context.events.deprecation(message, location) else Chef::Log.deprecation(message, location) end end end # @api private Only for test dependency injection; not evenly implemented as yet. def self.path_to(path) path end reset! end chef-12.14.60/lib/chef/chef_fs.rb000066400000000000000000000053051276456504500162700ustar00rootroot00000000000000require "chef/platform" # # ChefFS was designed to be a near-1:1 translation between Chef server endpoints # and local data, so that it could be used for: # # 1. User editing, diffing and viewing of server content locally # 2. knife download, upload and diff (supporting the above scenario) # 3. chef-client -z (serving user repository directly) # # This is the translation between chef-zero data stores (which correspond # closely to server endpoints) and the ChefFS repository format. # # |-----------------------------------|-----------------------------------| # | chef-zero DataStore | ChefFS (repository) | # |-----------------------------------|-----------------------------------| # | | org.json | # | association_requests/NAME | invitations.json | # | clients/NAME | clients/NAME.json | # | cookbooks/NAME/VERSION | cookbooks/NAME/metadata.rb | # | containers/NAME | containers/NAME.json | # | data/BAG/ITEM | data_bags/BAG/ITEM.json | # | environments/NAME | environments/NAME.json | # | groups/NAME | groups/NAME.json | # | nodes/NAME | nodes/NAME.json | # | policies/NAME/REVISION | policies/NAME-REVISION.json | # | policy_groups/NAME/policies/PNAME | policy_groups/NAME.json | # | roles/NAME | roles/NAME.json | # | sandboxes/ID | | # | users/NAME | members.json | # | file_store/COOKBOOK/VERSION/PATH | cookbooks/COOKBOOK/PATH | # | **/_acl | acls/**.json | # |-----------------------------------|-----------------------------------| # # # ## The Code # # There are two main entry points to ChefFS: # # - ChefServerRootDir represents the chef server (under an org) and surfaces a # filesystem-like interface (FSBaseObject / FSBaseDir) that maps the REST API # to the same format as you would have on disk. # - ChefRepositoryFileSystemRootDir represents the local repository where you # put your cookbooks, roles, policies, etc. # # Because these two map to a common directory structure, diff, upload, download, # and other filesystem operations, can easily be done in a generic manner. # # These are instantiated by Chef::ChefFS::Config's `chef_fs` and `local_fs` # methods. # class Chef module ChefFS def self.windows? Chef::Platform.windows? end end end chef-12.14.60/lib/chef/chef_fs/000077500000000000000000000000001276456504500157405ustar00rootroot00000000000000chef-12.14.60/lib/chef/chef_fs/chef_fs_data_store.rb000066400000000000000000001010611276456504500220660ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/cookbook_manifest" require "chef_zero/data_store/memory_store" require "chef_zero/data_store/data_already_exists_error" require "chef_zero/data_store/data_not_found_error" require "chef/chef_fs/file_pattern" require "chef/chef_fs/file_system" require "chef/chef_fs/file_system/exceptions" require "chef/chef_fs/file_system/memory/memory_root" require "fileutils" class Chef module ChefFS # # Translation layer between chef-zero's DataStore (a place where it expects # files to be stored) and ChefFS (the user's repository directory layout). # # chef-zero expects the data store to store files *its* way--for example, it # expects get("nodes/blah") to return the JSON text for the blah node, and # it expects get("cookbooks/blah/1.0.0") to return the JSON definition of # the blah cookbook version 1.0.0. # # The repository is defined the way the *user* wants their layout. These # two things are very similar in layout (for example, nodes are stored under # the nodes/ directory and their filename is the name of the node). # # However, there are a few differences that make this more than just a raw # file store: # # 1. Cookbooks are stored much differently. # - chef-zero places JSON text with the checksums for the cookbook at # /cookbooks/NAME/VERSION, and expects the JSON to contain URLs to the # actual files, which are stored elsewhere. # - The repository contains an actual directory with just the cookbook # files and a metadata.rb containing a version #. There is no JSON to # be found. # - Further, if versioned_cookbooks is false, that directory is named # /cookbooks/NAME and only one version exists. If versioned_cookbooks # is true, the directory is named /cookbooks/NAME-VERSION. # - Therefore, ChefFSDataStore calculates the cookbook JSON by looking at # the files in the cookbook and checksumming them, and reading metadata.rb # for the version and dependency information. # - ChefFSDataStore also modifies the cookbook file URLs so that they point # to /file_store/repo/ (the path to the actual file under the # repository root). For example, /file_store/repo/apache2/metadata.rb or # /file_store/repo/cookbooks/apache2/recipes/default.rb). # # 2. Sandboxes don't exist in the repository. # - ChefFSDataStore lets cookbooks be uploaded into a temporary memory # storage, and when the cookbook is committed, copies the files onto the # disk in the correct place (/cookbooks/apache2/recipes/default.rb). # # 3. Data bags: # - The Chef server expects data bags in /data/BAG/ITEM # - The repository stores data bags in /data_bags/BAG/ITEM # # 4. JSON filenames are generally NAME.json in the repository (e.g. /nodes/foo.json). # # 5. Org membership: # chef-zero stores user membership in an org as a series of empty files. # If an org has jkeiser and cdoherty as members, chef-zero expects these # files to exist: # # - `users/jkeiser` (content: '{}') # - `users/cdoherty` (content: '{}') # # ChefFS, on the other hand, stores user membership in an org as a single # file, `members.json`, with content: # # ```json # [ # { "user": { "username": "jkeiser" } }, # { "user": { "username": "cdoherty" } } # ] # ``` # # To translate between the two, we need to intercept requests to `users` # like so: # # - `list(users)` -> `get(/members.json)` # - `get(users/NAME)` -> `get(/members.json)`, see if it's in there # - `create(users/NAME)` -> `get(/members.json)`, add name, `set(/members.json)` # - `delete(users/NAME)` -> `get(/members.json)`, remove name, `set(/members.json)` # # 6. Org invitations: # chef-zero stores org membership invitations as a series of empty files. # If an org has invited jkeiser and cdoherty (and they have not yet accepted # the invite), chef-zero expects these files to exist: # # - `association_requests/jkeiser` (content: '{}') # - `association_requests/cdoherty` (content: '{}') # # ChefFS, on the other hand, stores invitations as a single file, # `invitations.json`, with content: # # ```json # [ # { "id" => "jkeiser-chef", 'username' => 'jkeiser' }, # { "id" => "cdoherty-chef", 'username' => 'cdoherty' } # ] # ``` # # To translate between the two, we need to intercept requests to `users` # like so: # # - `list(association_requests)` -> `get(/invitations.json)` # - `get(association_requests/NAME)` -> `get(/invitations.json)`, see if it's in there # - `create(association_requests/NAME)` -> `get(/invitations.json)`, add name, `set(/invitations.json)` # - `delete(association_requests/NAME)` -> `get(/invitations.json)`, remove name, `set(/invitations.json)` # class ChefFSDataStore # The base directories in a Chef Repo; even when these don't exist, a # matching GET for these objects will return an empty list instead of a # 404. BASE_DIRNAMES = %w{ clients cookbooks data environments nodes roles users containers groups policy_groups policies }.freeze # # Create a new ChefFSDataStore # # ==== Arguments # # [chef_fs] # A +ChefFS::FileSystem+ object representing the repository root. # Generally will be a +ChefFS::FileSystem::ChefRepositoryFileSystemRoot+ # object, created from +ChefFS::Config.local_fs+. # def initialize(chef_fs, chef_config = Chef::Config) @chef_fs = chef_fs @memory_store = ChefZero::DataStore::MemoryStore.new @repo_mode = chef_config[:repo_mode] end def publish_description "Reading and writing data to #{chef_fs.fs_description}" end attr_reader :chef_fs attr_reader :repo_mode def create_dir(path, name, *options) if use_memory_store?(path) @memory_store.create_dir(path, name, *options) else with_parent_dir(path + [name], *options) do |parent, name| begin parent.create_child(name, nil) rescue Chef::ChefFS::FileSystem::AlreadyExistsError => e raise ChefZero::DataStore::DataAlreadyExistsError.new(to_zero_path(e.entry), e) end end end end # # If you want to get the contents of /data/x/y from the server, # you say chef_fs.child('data').child('x').child('y').read. # It will make exactly one network request: GET /data/x/y # And that will return 404 if it doesn't exist. # # ChefFS objects do not go to the network until you ask them for data. # This means you can construct a /data/x/y ChefFS entry early. # # Alternative: # chef_fs.child('data') could have done a GET /data preemptively, # allowing it to know whether child('x') was valid (GET /data gives you # a list of data bags). Then child('x') could have done a GET /data/x, # allowing it to know whether child('y') (the item) existed. Finally, # we would do the GET /data/x/y to read the contents. Three network # requests instead of 1. # def create(path, name, data, *options) if use_memory_store?(path) @memory_store.create(path, name, data, *options) elsif path[0] == "cookbooks" && path.length == 2 # Do nothing. The entry gets created when the cookbook is created. # /policy_groups/GROUP/policies/NAME elsif path[0] == "policy_groups" && path[2] == "policies" # Just set or create the proper entry in the hash update_json(to_chef_fs_path(path[0..1]), {}, *options) do |group| if policies.has_key?(path[3]) raise ChefZero::DataStore::DataAlreadyExistsError.new(path, group) end group["policies"] ||= {} group["policies"][path[3]] = { "revision_id" => Chef::JSONCompat.parse(data) } group end # create [/organizations/ORG]/users/NAME (with content '{}') # Manipulate the `members.json` file that contains a list of all users elsif is_org? && path == [ "users" ] update_json("members.json", [], *options) do |members| # Format of each entry: { "user": { "username": "jkeiser" } } if members.any? { |member| member["user"]["username"] == name } raise ChefZero::DataStore::DataAlreadyExistsError.new(path, entry) end # Actually add the user members << { "user" => { "username" => name } } end # create [/organizations/ORG]/association_requests/NAME (with content '{}') # Manipulate the `invitations.json` file that contains a list of all users elsif is_org? && path == [ "association_requests" ] update_json("invitations.json", [], *options) do |invitations| # Format of each entry: { "id" => "jkeiser-chef", 'username' => 'jkeiser' } if invitations.any? { |member| member["username"] == name } raise ChefZero::DataStore::DataAlreadyExistsError.new(path) end # Actually add the user (TODO insert org name??) invitations << { "username" => name } end else if !data.is_a?(String) raise "set only works with strings" end with_parent_dir(path + [name], *options) do |parent, name| begin parent.create_child(name, data) rescue Chef::ChefFS::FileSystem::AlreadyExistsError => e raise ChefZero::DataStore::DataAlreadyExistsError.new(to_zero_path(e.entry), e) end end end end def get(path, request = nil) if use_memory_store?(path) @memory_store.get(path) elsif path[0] == "file_store" && path[1] == "repo" entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path[2..-1].join("/")) begin entry.read rescue Chef::ChefFS::FileSystem::NotFoundError => e raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end # /policy_groups/NAME/policies/POLICYNAME: return the revision of the given policy elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 4 # Just set or create the proper entry in the hash policy_group = get_json(to_chef_fs_path(path[0..1]), {}) if !policy_group["policies"] || !policy_group["policies"][path[3]] raise ChefZero::DataStore::DataNotFoundError.new(path, entry) end # The policy group looks like: # { # "policies": { # "x": { "revision_id": "10" } # } # } Chef::JSONCompat.to_json_pretty(policy_group["policies"][path[3]]["revision_id"]) # GET [/organizations/ORG]/users/NAME -> /users/NAME # Manipulates members.json elsif is_org? && path[0] == "users" && path.length == 2 if get_json("members.json", []).any? { |member| member["user"]["username"] == path[1] } "{}" else raise ChefZero::DataStore::DataNotFoundError.new(path) end # GET [/organizations/ORG]/association_requests/NAME -> /users/NAME # Manipulates invites.json elsif is_org? && path[0] == "association_requests" && path.length == 2 if get_json("invites.json", []).any? { |member| member["user"]["username"] == path[1] } "{}" else raise ChefZero::DataStore::DataNotFoundError.new(path) end # GET /cookbooks/NAME/VERSION or /cookbook_artifacts/NAME/IDENTIFIER elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 3 with_entry(path) do |entry| cookbook_type = path[0] result = nil begin result = Chef::CookbookManifest.new(entry.chef_object, policy_mode: cookbook_type == "cookbook_artifacts").to_hash rescue Chef::ChefFS::FileSystem::NotFoundError => e raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end result.each_pair do |key, value| if value.is_a?(Array) value.each do |file| if file.is_a?(Hash) && file.has_key?("checksum") relative = ["file_store", "repo", cookbook_type] if chef_fs.versioned_cookbooks || cookbook_type == "cookbook_artifacts" relative << "#{path[1]}-#{path[2]}" else relative << path[1] end relative = relative + file[:path].split("/") file["url"] = ChefZero::RestBase.build_uri(request.base_uri, relative) end end end end if cookbook_type == "cookbook_artifacts" result["metadata"] = result["metadata"].to_hash result["metadata"].delete_if do |key, value| value == [] || (value == {} && !%w{dependencies attributes recipes}.include?(key)) || (value == "" && %w{source_url issues_url}.include?(key)) || (value == false && key == "privacy") end end Chef::JSONCompat.to_json_pretty(result) end else with_entry(path) do |entry| begin entry.read rescue Chef::ChefFS::FileSystem::NotFoundError => e raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end end end end def set(path, data, *options) if use_memory_store?(path) @memory_store.set(path, data, *options) else if !data.is_a?(String) raise "set only works with strings: #{path} = #{data.inspect}" end # Write out the files! if %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 3 write_cookbook(path, data, *options) # Handle /policy_groups/some_policy_group/policies/some_policy_name elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 4 # Just set or create the proper entry in the hash update_json(to_chef_fs_path(path[0..1]), {}, *options) do |group| group["policies"] ||= {} group["policies"][path[3]] = { "revision_id" => Chef::JSONCompat.parse(data) } group end else with_parent_dir(path, *options) do |parent, name| child = parent.child(name) if child.exists? child.write(data) else parent.create_child(name, data) end end end end end def delete(path) if use_memory_store?(path) @memory_store.delete(path) # DELETE /policy_groups/GROUP/policies/POLICY elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 4 update_json(to_chef_fs_path(path[0..1]), {}) do |group| unless group["policies"] && group["policies"].has_key?(path[3]) raise ChefZero::DataStore::DataNotFoundError.new(path) end group["policies"].delete(path[3]) group end # DELETE [/organizations/ORG]/users/NAME # Manipulates members.json elsif is_org? && path[0] == "users" && path.length == 2 update_json("members.json", []) do |members| result = members.reject { |member| member["user"]["username"] == path[1] } if result.size == members.size raise ChefZero::DataStore::DataNotFoundError.new(path) end result end # DELETE [/organizations/ORG]/users/NAME # Manipulates members.json elsif is_org? && path[0] == "association_requests" && path.length == 2 update_json("invitations.json", []) do |invitations| result = invitations.reject { |invitation| invitation["username"] == path[1] } if result.size == invitations.size raise ChefZero::DataStore::DataNotFoundError.new(path) end result end else with_entry(path) do |entry| begin if %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length >= 3 entry.delete(true) else entry.delete(false) end rescue Chef::ChefFS::FileSystem::NotFoundError => e raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end end end end def delete_dir(path, *options) if use_memory_store?(path) @memory_store.delete_dir(path, *options) # DELETE /policies/POLICY elsif path[0] == "policies" && path.length == 2 with_entry(path[0..0]) do |policies| # /policies: # - a-1.0.0.json # - a-1.0.1.json # - b-2.0.0.json found_policy = false policies.children.each do |policy| # We want to delete just the ones that == POLICY next unless policy.name.rpartition("-")[0] == path[1] policy.delete(false) FileSystemCache.instance.delete!(policy.file_path) found_policy = true end raise ChefZero::DataStore::DataNotFoundError.new(path) if !found_policy end else with_entry(path) do |entry| begin entry.delete(options.include?(:recursive)) rescue Chef::ChefFS::FileSystem::NotFoundError => e raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end end end end def list(path) if use_memory_store?(path) @memory_store.list(path) # LIST /policies elsif path == [ "policies" ] with_entry([ path[0] ]) do |policies| begin policies.children.map { |policy| policy.name[0..-6].rpartition("-")[0] }.uniq rescue Chef::ChefFS::FileSystem::NotFoundError [] end end # LIST /policies/POLICY/revisions elsif path[0] == "policies" && path[2] == "revisions" && path.length == 3 with_entry([ path[0] ]) do |policies| # /policies: # - a-1.0.0.json # - a-1.0.1.json # - b-2.0.0.json revisions = [] policies.children.each do |policy| name, dash, revision = policy.name[0..-6].rpartition("-") revisions << revision if name == path[1] end raise ChefZero::DataStore::DataNotFoundError.new(path) if revisions.empty? revisions end elsif path[0] == "policy_groups" && path.length == 2 with_entry(path) do |entry| [ "policies" ] end elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 3 with_entry(path[0..1]) do |entry| policies = Chef::JSONCompat.parse(entry.read)["policies"] || {} policies.keys end elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 1 with_entry(path) do |entry| begin if path[0] == "cookbook_artifacts" entry.children.map { |child| child.name.rpartition("-")[0] }.uniq elsif chef_fs.versioned_cookbooks # /cookbooks/name-version -> /cookbooks/name entry.children.map { |child| split_name_version(child.name)[0] }.uniq else entry.children.map { |child| child.name } end rescue Chef::ChefFS::FileSystem::NotFoundError # If the cookbooks dir doesn't exist, we have no cookbooks (not 404) [] end end elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 2 if chef_fs.versioned_cookbooks || path[0] == "cookbook_artifacts" result = with_entry([ path[0] ]) do |entry| # list /cookbooks/name = filter /cookbooks/name-version down to name entry.children.map { |child| split_name_version(child.name) }. select { |name, version| name == path[1] }. map { |name, version| version } end if result.empty? raise ChefZero::DataStore::DataNotFoundError.new(path) end result else # list /cookbooks/name = version = get_single_cookbook_version(path) [version] end else result = with_entry(path) do |entry| begin entry.children.map { |c| zero_filename(c) }.sort rescue Chef::ChefFS::FileSystem::NotFoundError => e # /cookbooks, /data, etc. never return 404 if path_always_exists?(path) [] else raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end end end # Older versions of chef-zero do not understand policies and cookbook_artifacts, # don't give that stuff to them if path == [] && ChefZero::VERSION.to_f < 4.4 result.reject! { |child| %w{policies policy_data cookbook_artifacts}.include?(child) } end result end end def exists?(path) if use_memory_store?(path) @memory_store.exists?(path) # /policy_groups/NAME/policies/POLICYNAME elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 4 group = get_json(to_chef_fs_path(path[0..1]), {}) group["policies"] && group["policies"].has_key?(path[3]) else path_always_exists?(path) || Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists? end end def exists_dir?(path) if use_memory_store?(path) @memory_store.exists_dir?(path) elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 2 list([ path[0] ]).include?(path[1]) # /policies/NAME elsif path[0] == "policies" && path.length == 2 list([ path[0] ]).include?(path[1]) # /policy_groups/NAME/policies elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 3 exists_dir?(path[0..1]) else Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists? end end private def use_memory_store?(path) return path[0] == "sandboxes" || path[0] == "file_store" && path[1] == "checksums" || path == %w{environments _default} end def write_cookbook(path, data, *options) cookbook_type = path[0] if chef_fs.versioned_cookbooks cookbook_path = File.join(cookbook_type, "#{path[1]}-#{path[2]}") else cookbook_path = File.join(cookbook_type, path[1]) end # Create a little Chef::ChefFS memory filesystem with the data cookbook_fs = Chef::ChefFS::FileSystem::Memory::MemoryRoot.new("uploading") cookbook = Chef::JSONCompat.parse(data) cookbook.each_pair do |key, value| if value.is_a?(Array) value.each do |file| if file.is_a?(Hash) && file.has_key?("checksum") file_data = @memory_store.get(["file_store", "checksums", file["checksum"]]) cookbook_fs.add_file(File.join(cookbook_path, file["path"]), file_data) end end end end # Create the .uploaded-cookbook-version.json cookbooks = chef_fs.child(cookbook_type) if !cookbooks.exists? cookbooks = chef_fs.create_child(cookbook_type) end # We are calling a cookbooks-specific API, so get multiplexed_dirs out of the way if it is there if cookbooks.respond_to?(:multiplexed_dirs) cookbooks = cookbooks.write_dir end cookbooks.write_cookbook(cookbook_path, data, cookbook_fs) end def split_name_version(entry_name) name_version = entry_name.split("-") name = name_version[0..-2].join("-") version = name_version[-1] [name, version] end def to_chef_fs_path(path) _to_chef_fs_path(path).join("/") end def chef_fs_filename(path) _to_chef_fs_path(path)[-1] end def _to_chef_fs_path(path) path = path.dup # /data -> /data_bags # /data/BAG -> /data_bags/BAG # /data/BAG/ITEM -> /data_bags/BAG/ITEM.json if path[0] == "data" path[0] = "data_bags" if path.length >= 3 path[2] = "#{path[2]}.json" end # /client_keys/CLIENT/keys -> /client_keys/CLIENT # /client_keys/CLIENT/keys/KEYNAME -> /client_keys/CLIENT/KEYNAME.json elsif path[0] == "client_keys" path.delete_at(2) if path.length >= 3 path[-1] += ".json" end # /policies/POLICY/revisions/REVISION -> /policies/POLICY-REVISION.json elsif path[0] == "policies" && path[2] == "revisions" && path.length >= 4 path = [ "policies", "#{path[1]}-#{path[3]}.json" ] elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) if path.length == 2 raise ChefZero::DataStore::DataNotFoundError.new(path) elsif path.length >= 3 if chef_fs.versioned_cookbooks || path[0] == "cookbook_artifacts" # cookbooks/name/version -> cookbooks/name-version path = [ path[0], "#{path[1]}-#{path[2]}" ] + path[3..-1] else # cookbooks/name/version/... -> /cookbooks/name/... iff metadata says so version = get_single_cookbook_version(path) if path[2] == version path = path[0..1] + path[3..-1] else raise ChefZero::DataStore::DataNotFoundError.new(path) end end end elsif path[0] == "acls" # /acls/data -> /acls/data_bags if path[1] == "data" path[1] = "data_bags" end # /acls/containers|nodes|.../x.json # /acls/organization.json if path.length == 3 || path == %w{acls organization} path[-1] = "#{path[-1]}.json" end # /acls/containers|nodes|... do NOT drop into the next elsif, and do # not get .json appended # /nodes|clients|.../x.json elsif path.length == 2 path[-1] = "#{path[-1]}.json" end path end def to_zero_path(entry) path = entry.path.split("/")[1..-1] if path[0] == "data_bags" path[0] = "data" if path.length >= 3 path[2] = path[2][0..-6] end # /client_keys/CLIENT -> /client_keys/CLIENT/keys # /client_keys/CLIENT/KEYNAME.json -> /client_keys/CLIENT/keys/KEYNAME elsif path[0] == "client_keys" if path.size == 2 path << "keys" elsif path.size > 2 path[2..-1] = [ "keys", path[-1][0..-6] ] end elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) if chef_fs.versioned_cookbooks || path[0] == "cookbook_artifacts" # cookbooks/name-version/... -> cookbooks/name/version/... if path.length >= 2 name, version = split_name_version(path[1]) path = [ path[0], name, version ] + path[2..-1] end else if path.length >= 2 # cookbooks/name/... -> cookbooks/name/version/... version = get_single_cookbook_version(path) path = path[0..1] + [version] + path[2..-1] end end # /policies/NAME-REVISION.json -> /policies/NAME/revisions/REVISION elsif path[0] == "policies" if path.length >= 2 name, dash, revision = path[1][0..-6].rpartition("-") path = [ "policies", name, "revisions", revision ] end elsif path.length == 2 && path[0] != "cookbooks" path[1] = path[1][0..-6] end path end def zero_filename(entry) to_zero_path(entry)[-1] end def path_always_exists?(path) return path.length == 1 && BASE_DIRNAMES.include?(path[0]) end def with_entry(path) begin yield Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)) rescue Chef::ChefFS::FileSystem::NotFoundError => e raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) end end def with_parent_dir(path, *options) path = _to_chef_fs_path(path) begin yield get_dir(path[0..-2], options.include?(:create_dir)), path[-1] rescue Chef::ChefFS::FileSystem::NotFoundError => e err = ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) err.set_backtrace(e.backtrace) raise err end end def with_dir(path) # Do not automatically create data bags create = !(path[0] == "data" && path.size >= 2) begin yield get_dir(_to_chef_fs_path(path), create) rescue Chef::ChefFS::FileSystem::NotFoundError => e err = ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e) err.set_backtrace(e.backtrace) raise err end end def get_dir(path, create = false) result = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path.join("/")) if result.exists? result elsif create || path.size == 1 get_dir(path[0..-2], create).create_child(result.name, nil) else raise ChefZero::DataStore::DataNotFoundError.new(path) end end def get_single_cookbook_version(path) dir = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path[0..1].join("/")) metadata = ChefZero::CookbookData.metadata_from(dir, path[1], nil, []) metadata[:version] || "0.0.0" end def update_json(path, default_value, *options) entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path) begin input = Chef::JSONCompat.parse(entry.read) output = yield input entry.write(Chef::JSONCompat.to_json_pretty(output)) if output != Chef::JSONCompat.parse(entry.read) rescue Chef::ChefFS::FileSystem::NotFoundError # Send the default value to the caller, and create the entry if the caller updates it output = yield default_value parent = entry.parent parent = ensure_dir(parent) if options.include?(:create_dir) parent.create_child(entry.name, Chef::JSONCompat.to_json_pretty(output)) if output != [] end end def ensure_dir(entry) return entry if entry.exists? parent = entry.parent if parent ensure_dir(parent) parent.create_child(entry.name) end end def get_json(path, default_value) entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path) begin Chef::JSONCompat.parse(entry.read) rescue Chef::ChefFS::FileSystem::NotFoundError default_value end end def is_org? repo_mode == "hosted_everything" end end end end chef-12.14.60/lib/chef/chef_fs/command_line.rb000066400000000000000000000262661276456504500207260ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system" require "chef/chef_fs/file_system/exceptions" require "chef/util/diff" class Chef module ChefFS module CommandLine def self.diff_print(pattern, a_root, b_root, recurse_depth, output_mode, format_path = nil, diff_filter = nil, ui = nil) if format_path.nil? format_path = proc { |entry| entry.path_for_printing } end get_content = (output_mode != :name_only && output_mode != :name_status) found_match = false diff(pattern, a_root, b_root, recurse_depth, get_content).each do |type, old_entry, new_entry, old_value, new_value, error| found_match = true unless type == :both_nonexistent old_path = format_path.call(old_entry) new_path = format_path.call(new_entry) case type when :common_subdirectories if output_mode != :name_only && output_mode != :name_status yield "Common subdirectories: #{new_path}\n" end when :directory_to_file next if diff_filter && diff_filter !~ /T/ if output_mode == :name_only yield "#{new_path}\n" elsif output_mode == :name_status yield "T\t#{new_path}\n" else yield "File #{old_path} is a directory while file #{new_path} is a regular file\n" end when :file_to_directory next if diff_filter && diff_filter !~ /T/ if output_mode == :name_only yield "#{new_path}\n" elsif output_mode == :name_status yield "T\t#{new_path}\n" else yield "File #{old_path} is a regular file while file #{new_path} is a directory\n" end when :deleted # This is kind of a kludge - because the "new" entry isn't there, we can't predict # it's true file name, because we've not got enough information. So because we know # the two entries really ought to have the same extension, we'll just grab the old one # and use it. (This doesn't affect cookbook files, since they'll always have extensions) if File.extname(old_path) != File.extname(new_path) new_path += File.extname(old_path) end next if diff_filter && diff_filter !~ /D/ if output_mode == :name_only yield "#{new_path}\n" elsif output_mode == :name_status yield "D\t#{new_path}\n" elsif old_value result = "diff --knife #{old_path} #{new_path}\n" result << "deleted file\n" result << diff_text(old_path, "/dev/null", old_value, "") yield result else yield "Only in #{format_path.call(old_entry.parent)}: #{old_entry.name}\n" end when :added next if diff_filter && diff_filter !~ /A/ if output_mode == :name_only yield "#{new_path}\n" elsif output_mode == :name_status yield "A\t#{new_path}\n" elsif new_value result = "diff --knife #{old_path} #{new_path}\n" result << "new file\n" result << diff_text("/dev/null", new_path, "", new_value) yield result else yield "Only in #{format_path.call(new_entry.parent)}: #{new_entry.name}\n" end when :modified next if diff_filter && diff_filter !~ /M/ if output_mode == :name_only yield "#{new_path}\n" elsif output_mode == :name_status yield "M\t#{new_path}\n" else result = "diff --knife #{old_path} #{new_path}\n" result << diff_text(old_path, new_path, old_value, new_value) yield result end when :both_nonexistent when :added_cannot_upload when :deleted_cannot_download when :same # Skip these silently when :error if error.is_a?(Chef::ChefFS::FileSystem::OperationFailedError) ui.error "#{format_path.call(error.entry)} failed to #{error.operation}: #{error.message}" if ui error = true elsif error.is_a?(Chef::ChefFS::FileSystem::OperationNotAllowedError) ui.error "#{format_path.call(error.entry)} #{error.reason}." if ui else raise error end end end if !found_match ui.error "#{pattern}: No such file or directory on remote or local" if ui error = true end error end def self.diff(pattern, old_root, new_root, recurse_depth, get_content) Chef::ChefFS::Parallelizer.parallelize(Chef::ChefFS::FileSystem.list_pairs(pattern, old_root, new_root)) do |old_entry, new_entry| diff_entries(old_entry, new_entry, recurse_depth, get_content) end.flatten(1) end # Diff two known entries (could be files or dirs) def self.diff_entries(old_entry, new_entry, recurse_depth, get_content) # If both are directories if old_entry.dir? if new_entry.dir? if recurse_depth == 0 return [ [ :common_subdirectories, old_entry, new_entry ] ] else return Chef::ChefFS::Parallelizer.parallelize(Chef::ChefFS::FileSystem.child_pairs(old_entry, new_entry)) do |old_child, new_child| Chef::ChefFS::CommandLine.diff_entries(old_child, new_child, recurse_depth ? recurse_depth - 1 : nil, get_content) end.flatten(1) end # If old is a directory and new is a file elsif new_entry.exists? return [ [ :directory_to_file, old_entry, new_entry ] ] # If old is a directory and new does not exist elsif new_entry.parent.can_have_child?(old_entry.name, old_entry.dir?) return [ [ :deleted, old_entry, new_entry ] ] # If the new entry does not and *cannot* exist, report that. else return [ [ :new_cannot_upload, old_entry, new_entry ] ] end # If new is a directory and old is a file elsif new_entry.dir? if old_entry.exists? return [ [ :file_to_directory, old_entry, new_entry ] ] # If new is a directory and old does not exist elsif old_entry.parent.can_have_child?(new_entry.name, new_entry.dir?) return [ [ :added, old_entry, new_entry ] ] # If the new entry does not and *cannot* exist, report that. else return [ [ :old_cannot_upload, old_entry, new_entry ] ] end # Neither is a directory, so they are diffable with file diff else are_same, old_value, new_value = Chef::ChefFS::FileSystem.compare(old_entry, new_entry) if are_same if old_value == :none return [ [ :both_nonexistent, old_entry, new_entry ] ] else return [ [ :same, old_entry, new_entry ] ] end else if old_value == :none old_exists = false elsif old_value.nil? old_exists = old_entry.exists? else old_exists = true end if new_value == :none new_exists = false elsif new_value.nil? new_exists = new_entry.exists? else new_exists = true end # If one of the files doesn't exist, we only want to print the diff if the # other file *could be uploaded/downloaded*. if !old_exists && !old_entry.parent.can_have_child?(new_entry.name, new_entry.dir?) return [ [ :old_cannot_upload, old_entry, new_entry ] ] end if !new_exists && !new_entry.parent.can_have_child?(old_entry.name, old_entry.dir?) return [ [ :new_cannot_upload, old_entry, new_entry ] ] end if get_content # If we haven't read the values yet, get them now so that they can be diffed begin old_value = old_entry.read if old_value.nil? rescue Chef::ChefFS::FileSystem::NotFoundError old_value = :none end begin new_value = new_entry.read if new_value.nil? rescue Chef::ChefFS::FileSystem::NotFoundError new_value = :none end end if old_value == :none || (old_value == nil && !old_entry.exists?) return [ [ :added, old_entry, new_entry, old_value, new_value ] ] elsif new_value == :none return [ [ :deleted, old_entry, new_entry, old_value, new_value ] ] else return [ [ :modified, old_entry, new_entry, old_value, new_value ] ] end end end rescue Chef::ChefFS::FileSystem::FileSystemError => e return [ [ :error, old_entry, new_entry, nil, nil, e ] ] end class << self private def sort_keys(json_object) if json_object.is_a?(Array) json_object.map { |o| sort_keys(o) } elsif json_object.is_a?(Hash) new_hash = {} json_object.keys.sort.each { |key| new_hash[key] = sort_keys(json_object[key]) } new_hash else json_object end end def canonicalize_json(json_text) parsed_json = Chef::JSONCompat.parse(json_text) sorted_json = sort_keys(parsed_json) Chef::JSONCompat.to_json_pretty(sorted_json) end def diff_text(old_path, new_path, old_value, new_value) # Copy to tempfiles before diffing # TODO don't copy things that are already in files! Or find an in-memory diff algorithm begin new_tempfile = Tempfile.new("new") new_tempfile.write(new_value) new_tempfile.close begin old_tempfile = Tempfile.new("old") old_tempfile.write(old_value) old_tempfile.close result = Chef::Util::Diff.new.udiff(old_tempfile.path, new_tempfile.path) result = result.gsub(/^--- #{old_tempfile.path}/, "--- #{old_path}") result = result.gsub(/^\+\+\+ #{new_tempfile.path}/, "+++ #{new_path}") result ensure old_tempfile.close! end ensure new_tempfile.close! end end end end end end chef-12.14.60/lib/chef/chef_fs/config.rb000066400000000000000000000270521276456504500175400ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/log" require "chef/chef_fs/path_utils" class Chef module ChefFS # # Helpers to take Chef::Config and create chef_fs and local_fs (ChefFS # objects representing the server and local repository, respectively). # class Config # Not all of our object types pluralize by adding an 's', so we map them # out here: INFLECTIONS = { "acls" => "acl", "client_keys" => "client_key", "clients" => "client", "cookbooks" => "cookbook", "cookbook_artifacts" => "cookbook_artifact", "containers" => "container", "data_bags" => "data_bag", "environments" => "environment", "groups" => "group", "nodes" => "node", "roles" => "role", "users" => "user", "policies" => "policy", "policy_groups" => "policy_group", } INFLECTIONS.each { |k, v| k.freeze; v.freeze } INFLECTIONS.freeze # ChefFS supports three modes of operation: "static", "everything", and # "hosted_everything". These names are antiquated since Chef 12 moved # multi-tenant and RBAC to the open source product. In practice, they # mean: # # * static: just static objects that are included in a traditional # chef-repo, with no support for anything introduced in Chef 12 or # later. # * everything: all of the objects supported by the open source Chef # Server 11.x # * hosted_everything: (the name comes from Hosted Chef) supports # everything in Chef Server 12 and later, including RBAC objects and # Policyfile objects. # # The "static" and "everything" modes are used for backup and # upgrade/migration of older Chef Servers, so they should be considered # frozen in time. CHEF_11_OSS_STATIC_OBJECTS = %w{cookbooks cookbook_artifacts data_bags environments roles}.freeze CHEF_11_OSS_DYNAMIC_OBJECTS = %w{clients nodes users}.freeze RBAC_OBJECT_NAMES = %w{acls containers groups }.freeze CHEF_12_OBJECTS = %w{ cookbook_artifacts policies policy_groups client_keys }.freeze STATIC_MODE_OBJECT_NAMES = CHEF_11_OSS_STATIC_OBJECTS EVERYTHING_MODE_OBJECT_NAMES = (CHEF_11_OSS_STATIC_OBJECTS + CHEF_11_OSS_DYNAMIC_OBJECTS).freeze HOSTED_EVERYTHING_MODE_OBJECT_NAMES = (EVERYTHING_MODE_OBJECT_NAMES + RBAC_OBJECT_NAMES + CHEF_12_OBJECTS).freeze # # Create a new Config object which can produce a chef_fs and local_fs. # # ==== Arguments # # [chef_config] # A hash that looks suspiciously like +Chef::Config+. These hash keys # include: # # :chef_repo_path:: # The root where all local chef object data is stored. Mirrors # +Chef::Config.chef_repo_path+ # :cookbook_path, node_path, ...:: # Paths to cookbooks/, nodes/, data_bags/, etc. Mirrors # +Chef::Config.cookbook_path+, etc. Defaults to # +/cookbooks+, etc. # :repo_mode:: # The directory format on disk. 'everything', 'hosted_everything' and # 'static'. Default: autodetected based on whether the URL has # "/organizations/NAME." # :versioned_cookbooks:: # If true, the repository contains cookbooks with versions in their # name (apache2-1.0.0). If false, the repository just has one version # of each cookbook and the directory has the cookbook name (apache2). # Default: +false+ # :chef_server_url:: # The URL to the Chef server, e.g. https://api.opscode.com/organizations/foo. # Used as the server for the remote chef_fs, and to "guess" repo_mode # if not specified. # :node_name:: The username to authenticate to the Chef server with. # :client_key:: The private key for the user for authentication # :environment:: The environment in which you are presently working # :repo_mode:: # The repository mode, :hosted_everything, :everything or :static. # This determines the set of subdirectories the Chef server will offer # up. # :versioned_cookbooks:: Whether or not to include versions in cookbook names # # [cwd] # The current working directory to base relative Chef paths from. # Defaults to +Dir.pwd+. # # [options] # A hash of other, not-suspiciously-like-chef-config options: # :cookbook_version:: # When downloading cookbooks, download this cookbook version instead # of the latest. # # [ui] # The object to print output to, with "output", "warn" and "error" # (looks a little like a Chef::Knife::UI object, obtainable from # Chef::Knife.ui). # # ==== Example # # require 'chef/chef_fs/config' # config = Chef::ChefFS::Config.new # config.chef_fs.child('cookbooks').children.each do |cookbook| # puts "Cookbook on server: #{cookbook.name}" # end # config.local_fs.child('cookbooks').children.each do |cookbook| # puts "Local cookbook: #{cookbook.name}" # end # def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {}, ui = nil) @chef_config = chef_config @cwd = File.expand_path(cwd) @cookbook_version = options[:cookbook_version] if @chef_config[:repo_mode] == "everything" && is_hosted? && !ui.nil? ui.warn %Q{You have repo_mode set to 'everything', but your chef_server_url looks like it might be a hosted setup. If this is the case please use hosted_everything or allow repo_mode to default} end # Default to getting *everything* from the server. if !@chef_config[:repo_mode] if is_hosted? @chef_config[:repo_mode] = "hosted_everything" else @chef_config[:repo_mode] = "everything" end end end attr_reader :chef_config attr_reader :cwd attr_reader :cookbook_version def is_hosted? @chef_config[:chef_server_url] =~ /\/+organizations\/.+/ end def chef_fs @chef_fs ||= create_chef_fs end def create_chef_fs require "chef/chef_fs/file_system/chef_server/chef_server_root_dir" Chef::ChefFS::FileSystem::ChefServer::ChefServerRootDir.new("remote", @chef_config, :cookbook_version => @cookbook_version) end def local_fs @local_fs ||= create_local_fs end def create_local_fs require "chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir" Chef::ChefFS::FileSystem::Repository::ChefRepositoryFileSystemRootDir.new(object_paths, Array(chef_config[:chef_repo_path]).flatten, @chef_config) end # Returns the given real path's location relative to the server root. # # If chef_repo is /home/jkeiser/chef_repo, # and pwd is /home/jkeiser/chef_repo/cookbooks, # server_path('blah') == '/cookbooks/blah' # server_path('../roles/blah.json') == '/roles/blah' # server_path('../../readme.txt') == nil # server_path('*/*ab*') == '/cookbooks/*/*ab*' # server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah' # server_path('/home/*/chef_repo/cookbooks/blah') == nil # # If there are multiple different, manually specified paths to object locations # (cookbooks, roles, data bags, etc. can all have separate paths), and cwd+the # path reaches into one of them, we will return a path relative to the first # one to match it. Otherwise we expect the path provided to be to the chef # repo path itself. Paths that are not available on the server are not supported. # # Globs are allowed as well, but globs outside server paths are NOT # (presently) supported. See above examples. TODO support that. # # If the path does not reach into ANY specified directory, nil is returned. def server_path(file_path) target_path = Chef::ChefFS::PathUtils.realest_path(file_path, @cwd) # Check all object paths (cookbooks_dir, data_bags_dir, etc.) # These are either manually specified by the user or autogenerated relative # to chef_repo_path. object_paths.each_pair do |name, paths| paths.each do |path| object_abs_path = Chef::ChefFS::PathUtils.realest_path(path, @cwd) if relative_path = PathUtils.descendant_path(target_path, object_abs_path) return Chef::ChefFS::PathUtils.join("/#{name}", relative_path) end end end # Check chef_repo_path Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path| # We're using realest_path here but we really don't need to - we can just expand the # path and use realpath because a repo_path if provided *must* exist. realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path, @cwd) if Chef::ChefFS::PathUtils.os_path_eq?(target_path, realest_chef_repo_path) return "/" end end nil end # The current directory, relative to server root. This is a case-sensitive server path. # It only exists if the current directory is a child of one of the recognized object_paths below. def base_path @base_path ||= server_path(@cwd) end # Print the given server path, relative to the current directory def format_path(entry) server_path = entry.respond_to?(:display_path) ? entry.display_path : entry.path if base_path && server_path[0, base_path.length] == base_path if server_path == base_path return "." elsif server_path[base_path.length, 1] == "/" return server_path[base_path.length + 1, server_path.length - base_path.length - 1] elsif base_path == "/" && server_path[0, 1] == "/" return server_path[1, server_path.length - 1] end end server_path end private def object_paths @object_paths ||= begin result = {} case @chef_config[:repo_mode] when "static" object_names = STATIC_MODE_OBJECT_NAMES when "hosted_everything" object_names = HOSTED_EVERYTHING_MODE_OBJECT_NAMES else object_names = EVERYTHING_MODE_OBJECT_NAMES end object_names.each do |object_name| # cookbooks -> cookbook_path singular_name = INFLECTIONS[object_name] raise "Unknown object name #{object_name}" unless singular_name variable_name = "#{singular_name}_path" paths = Array(@chef_config[variable_name]).flatten result[object_name] = paths.map { |path| File.expand_path(path) } end result end end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/000077500000000000000000000000001276456504500203465ustar00rootroot00000000000000chef-12.14.60/lib/chef/chef_fs/data_handler/acl_data_handler.rb000066400000000000000000000013731276456504500241240ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" class Chef module ChefFS module DataHandler class AclDataHandler < DataHandlerBase def normalize(acl, entry) # Normalize the order of the keys for easier reading result = normalize_hash(acl, { "create" => {}, "read" => {}, "update" => {}, "delete" => {}, "grant" => {}, }) result.keys.each do |key| result[key] = normalize_hash(result[key], { "actors" => [], "groups" => [] }) result[key]["actors"] = result[key]["actors"].sort result[key]["groups"] = result[key]["groups"].sort end result end end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/client_data_handler.rb000066400000000000000000000022401276456504500246350ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" require "chef/api_client" class Chef module ChefFS module DataHandler class ClientDataHandler < DataHandlerBase def normalize(client, entry) defaults = { "name" => remove_dot_json(entry.name), "clientname" => remove_dot_json(entry.name), "admin" => false, "validator" => false, "chef_type" => "client", } # Handle the fact that admin/validator have changed type from string -> boolean client["admin"] = (client["admin"] == "true") if client["admin"].is_a?(String) client["validator"] = (client["validator"] == "true") if client["validator"].is_a?(String) if entry.respond_to?(:org) && entry.org defaults["orgname"] = entry.org end result = normalize_hash(client, defaults) result.delete("json_class") result end def preserve_key?(key) return key == "name" end def chef_class Chef::ApiClient end # There is no Ruby API for Chef::ApiClient end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/client_key_data_handler.rb000066400000000000000000000003221276456504500255040ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" require "chef/api_client" class Chef module ChefFS module DataHandler class ClientKeyDataHandler < DataHandlerBase end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/container_data_handler.rb000066400000000000000000000022441276456504500253450ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" class Chef module ChefFS module DataHandler class ContainerDataHandler < DataHandlerBase def normalize(container, entry) normalize_hash(container, { "containername" => remove_dot_json(entry.name), "containerpath" => remove_dot_json(entry.name), }) end def preserve_key?(key) return key == "containername" end # Verify that the JSON hash for this type has a key that matches its name. # # @param object [Object] JSON hash of the object # @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying # @yield [s] callback to handle errors # @yieldparam [s] error message def verify_integrity(object, entry) base_name = remove_dot_json(entry.name) if object["containername"] != base_name yield("Name in #{entry.path_for_printing} must be '#{base_name}' (is '#{object['containername']}')") end end # There is no chef_class for users, nor does to_ruby work. end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb000066400000000000000000000020411276456504500251640ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" require "chef/cookbook/metadata" class Chef module ChefFS module DataHandler class CookbookDataHandler < DataHandlerBase def normalize(cookbook, entry) version = entry.name name = entry.parent.name result = normalize_hash(cookbook, { "name" => "#{name}-#{version}", "version" => version, "cookbook_name" => name, "json_class" => "Chef::CookbookVersion", "chef_type" => "cookbook_version", "frozen?" => false, "metadata" => {}, }) result["metadata"] = normalize_hash(result["metadata"], { "version" => version, "name" => name, }) end def preserve_key?(key) return key == "cookbook_name" || key == "version" end def chef_class Chef::Cookbook::Metadata end # Not using this yet, so not sure if to_ruby will be useful. end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb000066400000000000000000000042521276456504500261240ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" require "chef/data_bag_item" class Chef module ChefFS module DataHandler class DataBagItemDataHandler < DataHandlerBase def normalize(data_bag_item, entry) # If it's wrapped with raw_data, unwrap it. if data_bag_item["json_class"] == "Chef::DataBagItem" && data_bag_item["raw_data"] data_bag_item = data_bag_item["raw_data"] end # chef_type and data_bag come back in PUT and POST results, but we don't # use those in knife-essentials. normalize_hash(data_bag_item, { "id" => remove_dot_json(entry.name), }) end def normalize_for_post(data_bag_item, entry) if data_bag_item["json_class"] == "Chef::DataBagItem" && data_bag_item["raw_data"] data_bag_item = data_bag_item["raw_data"] end { "name" => "data_bag_item_#{entry.parent.name}_#{remove_dot_json(entry.name)}", "json_class" => "Chef::DataBagItem", "chef_type" => "data_bag_item", "data_bag" => entry.parent.name, "raw_data" => normalize(data_bag_item, entry), } end def normalize_for_put(data_bag_item, entry) normalize_for_post(data_bag_item, entry) end def preserve_key?(key) return key == "id" end def chef_class Chef::DataBagItem end # Verify that the JSON hash for this type has a key that matches its name. # # @param object [Object] JSON hash of the object # @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying # @yield [s] callback to handle errors # @yieldparam [s] error message def verify_integrity(object, entry) base_name = remove_dot_json(entry.name) if object["raw_data"]["id"] != base_name yield("ID in #{entry.path_for_printing} must be '#{base_name}' (is '#{object['raw_data']['id']}')") end end # Data bags do not support .rb files (or if they do, it's undocumented) end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/data_handler_base.rb000066400000000000000000000146421276456504500243020ustar00rootroot00000000000000class Chef module ChefFS module DataHandler # # The base class for all *DataHandlers. # # DataHandlers' job is to know the innards of Chef objects and manipulate # JSON for them, adding defaults and formatting them. # class DataHandlerBase # # Remove all default values from a Chef object's JSON so that the only # thing you see are the values that have been explicitly set. # Achieves this by calling normalize({}, entry) to get the list of # defaults, and subtracting anything that is the same. # def minimize(object, entry) default_object = default(entry) object.each_pair do |key, value| if default_object[key] == value && !preserve_key?(key) object.delete(key) end end object end def remove_file_extension(name, ext = ".*") if %w{ .rb .json }.include?(File.extname(name)) File.basename(name, ext) else name end end alias_method :remove_dot_json, :remove_file_extension # # Return true if minimize() should preserve a key even if it is the same # as the default. Often used for ids and names. # def preserve_key?(key) false end # # Get the default value for an entry. Calls normalize({}, entry). # def default(entry) normalize({}, entry) end # # Utility function to help subclasses do normalize(). Pass in a hash # and a list of keys with defaults, and normalize will: # # 1. Fill in the defaults # 2. Put the actual values in the order of the defaults # 3. Move any other values to the end # # == Example # # normalize_hash({x: 100, c: 2, a: 1}, { a: 10, b: 20, c: 30}) # -> { a: 1, b: 20, c: 2, x: 100} # def normalize_hash(object, defaults) # Make a normalized result in the specified order for diffing result = {} defaults.each_pair do |key, default| result[key] = object.has_key?(key) ? object[key] : default end object.each_pair do |key, value| result[key] = value if !result.has_key?(key) end result end # Specialized function to normalize an object before POSTing it, since # some object types want slightly different values on POST. # If not overridden, this just calls normalize() def normalize_for_post(object, entry) normalize(object, entry) end # Specialized function to normalize an object before PUTing it, since # some object types want slightly different values on PUT. # If not overridden, this just calls normalize(). def normalize_for_put(object, entry) normalize(object, entry) end # # normalize a run list (an array of run list items). # Leaves recipe[name] and role[name] alone, and translates # name to recipe[name]. Then calls uniq on the result. # def normalize_run_list(run_list) run_list.map do |item| case item.to_s when /^recipe\[.*\]$/ item # explicit recipe when /^role\[.*\]$/ item # explicit role else "recipe[#{item}]" end end.uniq end # # Bring in an instance of this object from Ruby. (Like roles/x.rb) # def from_ruby(path) r = chef_class.new r.from_file(path) r.to_hash end # # Turn a JSON hash into a bona fide Chef object (like Chef::Node). # def chef_object(object) chef_class.from_hash(object) end # # Write out the Ruby file for this instance. (Like roles/x.rb) # def to_ruby(object) raise NotImplementedError end # # Get the class for instances of this type. Must be overridden. # def chef_class raise NotImplementedError end # # Helper to write out a Ruby file for a JSON hash. Writes out only # the keys specified in "keys"; anything else must be emitted by the # caller. # # == Example # # to_ruby_keys({"name" => "foo", "environment" => "desert", "foo": "bar"}, [ "name", "environment" ]) # -> # 'name "foo" # environment "desert"' # def to_ruby_keys(object, keys) result = "" keys.each do |key| if object[key] if object[key].is_a?(Hash) if object[key].size > 0 result << key first = true object[key].each_pair do |k, v| if first first = false else result << " " * key.length end result << " #{k.inspect} => #{v.inspect}\n" end end elsif object[key].is_a?(Array) if object[key].size > 0 result << key first = true object[key].each do |value| if first first = false else result << ", " end result << value.inspect end result << "\n" end elsif !object[key].nil? result << "#{key} #{object[key].inspect}\n" end end end result end # Verify that the JSON hash for this type has a key that matches its name. # # @param object [Object] JSON hash of the object # @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying # @yield [s] callback to handle errors # @yieldparam [s] error message def verify_integrity(object, entry) base_name = remove_file_extension(entry.name) if object["name"] != base_name yield("Name must be '#{base_name}' (is '#{object['name']}')") end end end # class DataHandlerBase end end end chef-12.14.60/lib/chef/chef_fs/data_handler/environment_data_handler.rb000066400000000000000000000021521276456504500257250ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" require "chef/environment" class Chef module ChefFS module DataHandler class EnvironmentDataHandler < DataHandlerBase def normalize(environment, entry) normalize_hash(environment, { "name" => remove_file_extension(entry.name), "description" => "", "cookbook_versions" => {}, "default_attributes" => {}, "override_attributes" => {}, "json_class" => "Chef::Environment", "chef_type" => "environment", }) end def preserve_key?(key) return key == "name" end def chef_class Chef::Environment end def to_ruby(object) result = to_ruby_keys(object, %w{name description default_attributes override_attributes}) if object["cookbook_versions"] object["cookbook_versions"].each_pair do |name, version| result << "cookbook #{name.inspect}, #{version.inspect}" end end result end end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/group_data_handler.rb000066400000000000000000000026671276456504500245300ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" require "chef/api_client" class Chef module ChefFS module DataHandler class GroupDataHandler < DataHandlerBase def normalize(group, entry) defaults = { "name" => remove_dot_json(entry.name), "groupname" => remove_dot_json(entry.name), "users" => [], "clients" => [], "groups" => [], } if entry.org defaults["orgname"] = entry.org end result = normalize_hash(group, defaults) if result["actors"] && result["actors"].sort.uniq == (result["users"] + result["clients"]).sort.uniq result.delete("actors") end result end def normalize_for_put(group, entry) result = super(group, entry) result["actors"] = { "users" => result["users"], "clients" => result["clients"], "groups" => result["groups"], } result.delete("users") result.delete("clients") result.delete("groups") result end def normalize_for_post(group, entry) normalize_for_put(group, entry) end def preserve_key?(key) return key == "name" end def chef_class Chef::ApiClient end # There is no Ruby API for Chef::ApiClient end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/node_data_handler.rb000066400000000000000000000015641276456504500243140ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" require "chef/node" class Chef module ChefFS module DataHandler class NodeDataHandler < DataHandlerBase def normalize(node, entry) result = normalize_hash(node, { "name" => remove_dot_json(entry.name), "json_class" => "Chef::Node", "chef_type" => "node", "chef_environment" => "_default", "override" => {}, "normal" => {}, "default" => {}, "automatic" => {}, "run_list" => [], }) result["run_list"] = normalize_run_list(result["run_list"]) result end def preserve_key?(key) return key == "name" end def chef_class Chef::Node end # Nodes do not support .rb files end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/organization_data_handler.rb000066400000000000000000000021621276456504500260660ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" class Chef module ChefFS module DataHandler class OrganizationDataHandler < DataHandlerBase def normalize(organization, entry) result = normalize_hash(organization, { "name" => entry.org, "full_name" => entry.org, "org_type" => "Business", "clientname" => "#{entry.org}-validator", "billing_plan" => "platform-free", }) result end def preserve_key?(key) return key == "name" end # Verify that the JSON hash for this type has a key that matches its name. # # @param object [Object] JSON hash of the object # @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying # @yield [s] callback to handle errors # @yieldparam [s] error message def verify_integrity(object, entry) if entry.org != object["name"] yield("Name must be '#{entry.org}' (is '#{object['name']}')") end end end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb000066400000000000000000000006261276456504500276320ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" class Chef module ChefFS module DataHandler class OrganizationInvitesDataHandler < DataHandlerBase def normalize(invites, entry) invites.map { |invite| invite.is_a?(Hash) ? invite["username"] : invite }.sort.uniq end def minimize(invites, entry) invites end end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb000066400000000000000000000006361276456504500276040ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" class Chef module ChefFS module DataHandler class OrganizationMembersDataHandler < DataHandlerBase def normalize(members, entry) members.map { |member| member.is_a?(Hash) ? member["user"]["username"] : member }.sort.uniq end def minimize(members, entry) members end end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/policy_data_handler.rb000066400000000000000000000031011276456504500246530ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" class Chef module ChefFS module DataHandler class PolicyDataHandler < DataHandlerBase def name_and_revision(name) # foo-1.0.0 = foo, 1.0.0 name = remove_dot_json(name) if name =~ /^(.*)-([^-]*)$/ name, revision_id = $1, $2 end revision_id ||= "0.0.0" [ name, revision_id ] end def normalize(policy, entry) # foo-1.0.0 = foo, 1.0.0 name, revision_id = name_and_revision(entry.name) defaults = { "name" => name, "revision_id" => revision_id, "run_list" => [], "cookbook_locks" => {}, } normalize_hash(policy, defaults) end # Verify that the JSON hash for this type has a key that matches its name. # # @param object [Object] JSON hash of the object # @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying # @yield [s] callback to handle errors # @yieldparam [s] error message def verify_integrity(object_data, entry) name, revision = name_and_revision(entry.name) if object_data["name"] != name yield("Object name '#{object_data['name']}' doesn't match entry '#{name}'.") end if object_data["revision_id"] != revision yield("Object revision ID '#{object_data['revision_id']}' doesn't match entry '#{revision}'.") end end end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/policy_group_data_handler.rb000066400000000000000000000020171276456504500260740ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" class Chef module ChefFS module DataHandler class PolicyGroupDataHandler < DataHandlerBase def normalize(policy_group, entry) defaults = { "name" => remove_dot_json(entry.name), "policies" => {}, } result = normalize_hash(policy_group, defaults) result.delete("uri") # not useful data result end # Verify that the JSON hash for this type has a key that matches its name. # # @param object [Object] JSON hash of the object # @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying # @yield [s] callback to handle errors # @yieldparam [s] error message def verify_integrity(object_data, entry) if object_data["policies"].empty? yield("Policy group #{object_data["name"]} does not have any policies in it.") end end end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/role_data_handler.rb000066400000000000000000000021621276456504500243230ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" require "chef/role" class Chef module ChefFS module DataHandler class RoleDataHandler < DataHandlerBase def normalize(role, entry) result = normalize_hash(role, { "name" => remove_file_extension(entry.name), "description" => "", "json_class" => "Chef::Role", "chef_type" => "role", "default_attributes" => {}, "override_attributes" => {}, "run_list" => [], "env_run_lists" => {}, }) result["run_list"] = normalize_run_list(result["run_list"]) result["env_run_lists"].each_pair do |env, run_list| result["env_run_lists"][env] = normalize_run_list(run_list) end result end def preserve_key?(key) return key == "name" end def chef_class Chef::Role end def to_ruby(object) to_ruby_keys(object, %w{name description default_attributes override_attributes run_list env_run_lists}) end end end end end chef-12.14.60/lib/chef/chef_fs/data_handler/user_data_handler.rb000066400000000000000000000014211276456504500243350ustar00rootroot00000000000000require "chef/chef_fs/data_handler/data_handler_base" class Chef module ChefFS module DataHandler class UserDataHandler < DataHandlerBase def normalize(user, entry) normalize_hash(user, { "name" => remove_dot_json(entry.name), "username" => remove_dot_json(entry.name), "display_name" => remove_dot_json(entry.name), "admin" => false, "json_class" => "Chef::WebUIUser", "chef_type" => "webui_user", "salt" => nil, "password" => nil, "openid" => nil, }) end def preserve_key?(key) return key == "name" end # There is no chef_class for users, nor does to_ruby work. end end end end chef-12.14.60/lib/chef/chef_fs/file_pattern.rb000066400000000000000000000251331276456504500207450ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs" require "chef/chef_fs/path_utils" class Chef module ChefFS # # Represents a glob pattern. This class is designed so that it can # match arbitrary strings, and tell you about partial matches. # # Examples: # * a*z # - Matches abcz # - Does not match ab/cd/ez # - Does not match xabcz # * a**z # - Matches abcz # - Matches ab/cd/ez # # Special characters supported: # * / (and \\ on Windows) - directory separators # * \* - match zero or more characters (but not directory separators) # * \*\* - match zero or more characters, including directory separators # * ? - match exactly one character (not a directory separator) # Only on Unix: # * [abc0-9] - match one of the included characters # * \\ - escape character: match the given character # class FilePattern # Initialize a new FilePattern with the pattern string. # # Raises +ArgumentError+ if empty file pattern is specified def initialize(pattern) @pattern = pattern end # The pattern string. attr_reader :pattern # Reports whether this pattern could match children of path. # If the pattern doesn't match the path up to this point or # if it matches and doesn't allow further children, this will # return false. # # ==== Attributes # # * +path+ - a path to check # # ==== Examples # # abc/def.could_match_children?('abc') == true # abc.could_match_children?('abc') == false # abc/def.could_match_children?('x') == false # a**z.could_match_children?('ab/cd') == true def could_match_children?(path) return false if path == "" # Empty string is not a path argument_is_absolute = Chef::ChefFS::PathUtils.is_absolute?(path) return false if is_absolute != argument_is_absolute path = path[1, path.length - 1] if argument_is_absolute path_parts = Chef::ChefFS::PathUtils.split(path) # If the pattern is shorter than the path (or same size), children will be larger than the pattern, and will not match. return false if regexp_parts.length <= path_parts.length && !has_double_star # If the path doesn't match up to this point, children won't match either. return false if path_parts.zip(regexp_parts).any? { |part, regexp| !regexp.nil? && !regexp.match(part) } # Otherwise, it's possible we could match: the path matches to this point, and the pattern is longer than the path. # TODO There is one edge case where the double star comes after some characters like abc**def--we could check whether the next # bit of path starts with abc in that case. return true end # Returns the immediate child of a path that would be matched # if this FilePattern was applied. If more than one child # could match, this method returns nil. # # ==== Attributes # # * +path+ - The path to look for an exact child name under. # # ==== Returns # # The next directory in the pattern under the given path. # If the directory part could match more than one child, it # returns +nil+. # # ==== Examples # # abc/def.exact_child_name_under('abc') == 'def' # abc/def/ghi.exact_child_name_under('abc') == 'def' # abc/*/ghi.exact_child_name_under('abc') == nil # abc/*/ghi.exact_child_name_under('abc/def') == 'ghi' # abc/**/ghi.exact_child_name_under('abc/def') == nil # # This method assumes +could_match_children?(path)+ is +true+. def exact_child_name_under(path) path = path[1, path.length - 1] if Chef::ChefFS::PathUtils.is_absolute?(path) dirs_in_path = Chef::ChefFS::PathUtils.split(path).length return nil if exact_parts.length <= dirs_in_path return exact_parts[dirs_in_path] end # If this pattern represents an exact path, returns the exact path. # # abc/def.exact_path == 'abc/def' # abc/*def.exact_path == 'abc/def' # abc/x\\yz.exact_path == 'abc/xyz' def exact_path return nil if has_double_star || exact_parts.any? { |part| part.nil? } result = Chef::ChefFS::PathUtils.join(*exact_parts) is_absolute ? Chef::ChefFS::PathUtils.join("", result) : result end # Returns the normalized version of the pattern, with / as the directory # separator, and "." and ".." removed. # # This does not presently change things like \b to b, but in the future # it might. def normalized_pattern calculate @normalized_pattern end # Tell whether this pattern matches absolute, or relative paths def is_absolute calculate @is_absolute end # Returns true+ if this pattern matches the path, false+ otherwise. # # abc/*/def.match?('abc/foo/def') == true # abc/*/def.match?('abc/foo') == false def match?(path) argument_is_absolute = Chef::ChefFS::PathUtils.is_absolute?(path) return false if is_absolute != argument_is_absolute path = path[1, path.length - 1] if argument_is_absolute !!regexp.match(path) end # Returns the string pattern def to_s pattern end private def regexp calculate @regexp end def regexp_parts calculate @regexp_parts end def exact_parts calculate @exact_parts end def has_double_star calculate @has_double_star end def calculate if !@regexp @is_absolute = Chef::ChefFS::PathUtils.is_absolute?(@pattern) full_regexp_parts = [] normalized_parts = [] @regexp_parts = [] @exact_parts = [] @has_double_star = false Chef::ChefFS::PathUtils.split(pattern).each do |part| regexp, exact, has_double_star = FilePattern.pattern_to_regexp(part) if has_double_star @has_double_star = true end # Skip // and /./ (pretend it's not there) if exact == "" || exact == "." next end # Back up when you see .. (unless the prior part has ** in it, in which case .. must be preserved) if exact == ".." if @is_absolute && normalized_parts.length == 0 # If we are at the root, just pretend the .. isn't there next elsif normalized_parts.length > 0 regexp_prev, exact_prev, has_double_star_prev = FilePattern.pattern_to_regexp(normalized_parts[-1]) if has_double_star_prev raise ArgumentError, ".. overlapping a ** is unsupported" end full_regexp_parts.pop normalized_parts.pop if !@has_double_star @regexp_parts.pop @exact_parts.pop end next end end # Build up the regexp full_regexp_parts << regexp normalized_parts << part if !@has_double_star @regexp_parts << Regexp.new("^#{regexp}$") @exact_parts << exact end end @regexp = Regexp.new("^#{full_regexp_parts.join(Chef::ChefFS::PathUtils.regexp_path_separator)}$") @normalized_pattern = Chef::ChefFS::PathUtils.join(*normalized_parts) @normalized_pattern = Chef::ChefFS::PathUtils.join("", @normalized_pattern) if @is_absolute end end def self.pattern_special_characters if Chef::ChefFS.windows? @pattern_special_characters ||= /(\*\*|\*|\?|[\*\?\.\|\(\)\[\]\{\}\+\\\\\^\$])/ else # Unix also supports character regexes and backslashes @pattern_special_characters ||= /(\\.|\[[^\]]+\]|\*\*|\*|\?|[\*\?\.\|\(\)\[\]\{\}\+\\\\\^\$])/ end @pattern_special_characters end def self.regexp_escape_characters [ "[", '\\', "^", "$", ".", "|", "?", "*", "+", "(", ")", "{", "}" ] end def self.pattern_to_regexp(pattern) regexp = "" exact = "" has_double_star = false pattern.split(pattern_special_characters).each_with_index do |part, index| # Odd indexes from the split are symbols. Even are normal bits. if index.even? exact << part if !exact.nil? regexp << part else case part # **, * and ? happen on both platforms. when "**" exact = nil has_double_star = true regexp << ".*" when "*" exact = nil regexp << '[^\/]*' when "?" exact = nil regexp << "." else if part[0, 1] == '\\' && part.length == 2 # backslash escapes are only supported on Unix, and are handled here by leaving the escape on (it means the same thing in a regex) exact << part[1, 1] if !exact.nil? if regexp_escape_characters.include?(part[1, 1]) regexp << part else regexp << part[1, 1] end elsif part[0, 1] == "[" && part.length > 1 # [...] happens only on Unix, and is handled here by *not* backslashing (it means the same thing in and out of regex) exact = nil regexp << part else exact += part if !exact.nil? regexp << "\\#{part}" end end end end [regexp, exact, has_double_star] end end end end chef-12.14.60/lib/chef/chef_fs/file_system.rb000066400000000000000000000400041276456504500206060ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/path_utils" require "chef/chef_fs/file_system/exceptions" require "chef/chef_fs/parallelizer" class Chef module ChefFS module FileSystem # Returns a list of all things under (and including) this entry that match the # given pattern. # # ==== Attributes # # * +root+ - Entry to start listing under # * +pattern+ - Chef::ChefFS::FilePattern to match children under # def self.list(root, pattern) Lister.new(root, pattern) end class Lister include Enumerable def initialize(root, pattern) @root = root @pattern = pattern end attr_reader :root attr_reader :pattern def each(&block) list_from(root, &block) end def list_from(entry, &block) # Include self in results if it matches if pattern.match?(entry.display_path) yield(entry) end if pattern.could_match_children?(entry.display_path) # If it's possible that our children could match, descend in and add matches. exact_child_name = pattern.exact_child_name_under(entry.display_path) # If we've got an exact name, don't bother listing children; just grab the # child with the given name. if exact_child_name exact_child = entry.child(exact_child_name) if exact_child list_from(exact_child, &block) end # Otherwise, go through all children and find any matches elsif entry.dir? results = Parallelizer.parallelize(entry.children) { |child| Chef::ChefFS::FileSystem.list(child, pattern) } results.flatten(1).each(&block) end end end end # Resolve the given path against the entry, returning # the entry at the end of the path. # # ==== Attributes # # * +entry+ - the entry to start looking under. Relative # paths will be resolved from here. # * +path+ - the path to resolve. If it starts with +/+, # the path will be resolved starting from +entry.root+. # # ==== Examples # # Chef::ChefFS::FileSystem.resolve_path(root_path, 'cookbooks/java/recipes/default.rb') # def self.resolve_path(entry, path) return entry if path.length == 0 return resolve_path(entry.root, path) if path[0, 1] == "/" && entry.root != entry if path[0, 1] == "/" path = path[1, path.length - 1] end result = entry Chef::ChefFS::PathUtils.split(path).each do |part| result = result.child(part) end result end # Copy everything matching the given pattern from src to dest. # # After this method completes, everything in dest matching the # given pattern will look identical to src. # # ==== Attributes # # * +pattern+ - Chef::ChefFS::FilePattern to match children under # * +src_root+ - the root from which things will be copied # * +dest_root+ - the root to which things will be copied # * +recurse_depth+ - the maximum depth to copy things. +nil+ # means infinite depth. 0 means no recursion. # * +options+ - hash of options: # - +purge+ - if +true+, items in +dest+ that are not in +src+ # will be deleted from +dest+. If +false+, these items will # be left alone. # - +force+ - if +true+, matching files are always copied from # +src+ to +dest+. If +false+, they will only be copied if # actually different (which will take time to determine). # - +dry_run+ - if +true+, action will not actually be taken; # things will be printed out instead. # # ==== Examples # # Chef::ChefFS::FileSystem.copy_to(FilePattern.new('/cookbooks'), # chef_fs, local_fs, nil, true) do |message| # puts message # end # def self.copy_to(pattern, src_root, dest_root, recurse_depth, options, ui = nil, format_path = nil) found_result = false error = false parallel_do(list_pairs(pattern, src_root, dest_root)) do |src, dest| found_result = true new_dest_parent = get_or_create_parent(dest, options, ui, format_path) child_error = copy_entries(src, dest, new_dest_parent, recurse_depth, options, ui, format_path) error ||= child_error end if !found_result && pattern.exact_path ui.error "#{pattern}: No such file or directory on remote or local" if ui error = true end error end # Yield entries for children that are in either +a_root+ or +b_root+, with # matching pairs matched up. # # ==== Yields # # Yields matching entries in pairs: # # [ a_entry, b_entry ] # # ==== Example # # Chef::ChefFS::FileSystem.list_pairs(FilePattern.new('**x.txt', a_root, b_root)).each do |a, b| # ... # end # def self.list_pairs(pattern, a_root, b_root) PairLister.new(pattern, a_root, b_root) end class PairLister include Enumerable def initialize(pattern, a_root, b_root) @pattern = pattern @a_root = a_root @b_root = b_root end attr_reader :pattern attr_reader :a_root attr_reader :b_root def each # Make sure everything on the server is also on the filesystem, and diff found_paths = Set.new Chef::ChefFS::FileSystem.list(a_root, pattern).each do |a| found_paths << a.display_path b = Chef::ChefFS::FileSystem.resolve_path(b_root, a.display_path) yield [ a, b ] end # Check the outer regex pattern to see if it matches anything on the # filesystem that isn't on the server Chef::ChefFS::FileSystem.list(b_root, pattern).each do |b| if !found_paths.include?(b.display_path) a = Chef::ChefFS::FileSystem.resolve_path(a_root, b.display_path) yield [ a, b ] end end end end # Get entries for children of either a or b, with matching pairs matched up. # # ==== Returns # # An array of child pairs. # # [ [ a_child, b_child ], ... ] # # If a child is only in a or only in b, the other child entry will be # retrieved by name (and will most likely be a "nonexistent child"). # # ==== Example # # Chef::ChefFS::FileSystem.child_pairs(a, b).length # def self.child_pairs(a, b) # If both are directories, recurse into them and diff the children instead of returning ourselves. result = [] a_children_names = Set.new a.children.each do |a_child| a_children_names << a_child.bare_name result << [ a_child, b.child(a_child.bare_name) ] end # Check b for children that aren't in a b.children.each do |b_child| if !a_children_names.include?(b_child.bare_name) result << [ a.child(b_child.bare_name), b_child ] end end result end def self.compare(a, b) are_same, a_value, b_value = a.compare_to(b) if are_same.nil? are_same, b_value, a_value = b.compare_to(a) end if are_same.nil? # TODO these reads can be parallelized begin a_value = a.read if a_value.nil? rescue Chef::ChefFS::FileSystem::NotFoundError a_value = :none end begin b_value = b.read if b_value.nil? rescue Chef::ChefFS::FileSystem::NotFoundError b_value = :none end are_same = (a_value == b_value) end [ are_same, a_value, b_value ] end class << self private # Copy two entries (could be files or dirs) def copy_entries(src_entry, dest_entry, new_dest_parent, recurse_depth, options, ui, format_path) # A NOTE about this algorithm: # There are cases where this algorithm does too many network requests. # knife upload with a specific filename will first check if the file # exists (a "dir" in the parent) before deciding whether to POST or # PUT it. If we just tried PUT (or POST) and then tried the other if # the conflict failed, we wouldn't need to check existence. # On the other hand, we may already have DONE the request, in which # case we shouldn't waste time trying PUT if we know the file doesn't # exist. # Will need to decide how that works with checksums, though. error = false begin dest_path = format_path.call(dest_entry) if ui src_path = format_path.call(src_entry) if ui if !src_entry.exists? if options[:purge] # If we would not have uploaded it, we will not purge it. if src_entry.parent.can_have_child?(dest_entry.name, dest_entry.dir?) if options[:dry_run] ui.output "Would delete #{dest_path}" if ui else begin dest_entry.delete(true) ui.output "Deleted extra entry #{dest_path} (purge is on)" if ui rescue Chef::ChefFS::FileSystem::NotFoundError ui.output "Entry #{dest_path} does not exist. Nothing to do. (purge is on)" if ui end end else ui.output ("Not deleting extra entry #{dest_path} (purge is off)") if ui end end elsif !dest_entry.exists? if new_dest_parent.can_have_child?(src_entry.name, src_entry.dir?) # If the entry can do a copy directly from filesystem, do that. if new_dest_parent.respond_to?(:create_child_from) if options[:dry_run] ui.output "Would create #{dest_path}" if ui else new_dest_parent.create_child_from(src_entry) ui.output "Created #{dest_path}" if ui end return end if src_entry.dir? if options[:dry_run] ui.output "Would create #{dest_path}" if ui new_dest_dir = new_dest_parent.child(src_entry.name) else new_dest_dir = new_dest_parent.create_child(src_entry.name, nil) ui.output "Created #{dest_path}" if ui end # Directory creation is recursive. if recurse_depth != 0 parallel_do(src_entry.children) do |src_child| new_dest_child = new_dest_dir.child(src_child.name) child_error = copy_entries(src_child, new_dest_child, new_dest_dir, recurse_depth ? recurse_depth - 1 : recurse_depth, options, ui, format_path) error ||= child_error end end else if options[:dry_run] ui.output "Would create #{dest_path}" if ui else child = new_dest_parent.create_child(src_entry.name, src_entry.read) ui.output "Created #{format_path.call(child)}" if ui end end end else # Both exist. # If the entry can do a copy directly, do that. if dest_entry.respond_to?(:copy_from) if options[:force] || compare(src_entry, dest_entry)[0] == false if options[:dry_run] ui.output "Would update #{dest_path}" if ui else dest_entry.copy_from(src_entry, options) ui.output "Updated #{dest_path}" if ui end end return end # If they are different types, log an error. if src_entry.dir? if dest_entry.dir? # If both are directories, recurse into their children if recurse_depth != 0 parallel_do(child_pairs(src_entry, dest_entry)) do |src_child, dest_child| child_error = copy_entries(src_child, dest_child, dest_entry, recurse_depth ? recurse_depth - 1 : recurse_depth, options, ui, format_path) error ||= child_error end end else # If they are different types. ui.error("File #{src_path} is a directory while file #{dest_path} is a regular file\n") if ui return end else if dest_entry.dir? ui.error("File #{src_path} is a regular file while file #{dest_path} is a directory\n") if ui return else # Both are files! Copy them unless we're sure they are the same.' if options[:diff] == false should_copy = false elsif options[:force] should_copy = true src_value = nil else are_same, src_value, _dest_value = compare(src_entry, dest_entry) should_copy = !are_same end if should_copy if options[:dry_run] ui.output "Would update #{dest_path}" if ui else src_value = src_entry.read if src_value.nil? dest_entry.write(src_value) ui.output "Updated #{dest_path}" if ui end end end end end rescue RubyFileError => e ui.warn "#{format_path.call(e.entry)} #{e.reason}." if ui rescue DefaultEnvironmentCannotBeModifiedError => e ui.warn "#{format_path.call(e.entry)} #{e.reason}." if ui rescue OperationFailedError => e ui.error "#{format_path.call(e.entry)} failed to #{e.operation}: #{e.message}" if ui error = true rescue OperationNotAllowedError => e ui.error "#{format_path.call(e.entry)} #{e.reason}." if ui error = true end error end def get_or_create_parent(entry, options, ui, format_path) parent = entry.parent if parent && !parent.exists? parent_path = format_path.call(parent) if ui parent_parent = get_or_create_parent(parent, options, ui, format_path) if options[:dry_run] ui.output "Would create #{parent_path}" if ui else parent = parent_parent.create_child(parent.name, nil) ui.output "Created #{parent_path}" if ui end end return parent end def parallel_do(enum, options = {}, &block) Chef::ChefFS::Parallelizer.parallel_do(enum, options, &block) end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/000077500000000000000000000000001276456504500202635ustar00rootroot00000000000000chef-12.14.60/lib/chef/chef_fs/file_system/base_fs_dir.rb000066400000000000000000000023141276456504500230500ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_object" require "chef/chef_fs/file_system/nonexistent_fs_object" class Chef module ChefFS module FileSystem class BaseFSDir < BaseFSObject def initialize(name, parent) super end def dir? true end def can_have_child?(name, is_dir) true end # An empty children array is an empty dir def empty? children.empty? end # Abstract: children end end end end chef-12.14.60/lib/chef/chef_fs/file_system/base_fs_object.rb000066400000000000000000000151741276456504500235500ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/path_utils" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem class BaseFSObject def initialize(name, parent) @parent = parent @name = name if parent @path = Chef::ChefFS::PathUtils.join(parent.path, name) else if name != "" raise ArgumentError, "Name of root object must be empty string: was '#{name}' instead" end @path = "/" end end attr_reader :name attr_reader :parent attr_reader :path alias_method :display_path, :path alias_method :display_name, :name alias_method :bare_name, :name # Override this if you have a special comparison algorithm that can tell # you whether this entry is the same as another--either a quicker or a # more reliable one. Callers will use this to decide whether to upload, # download or diff an object. # # You should not override this if you're going to do the standard # +self.read == other.read+. If you return +nil+, the caller will call # +other.compare_to(you)+ instead. Give them a chance :) # # ==== Parameters # # * +other+ - the entry to compare to # # ==== Returns # # * +[ are_same, value, other_value ]+ # +are_same+ may be +true+, +false+ or +nil+ (which means "don't know"). # +value+ and +other_value+ must either be the text of +self+ or +other+, # +:none+ (if the entry does not exist or has no value) or +nil+ if the # value was not retrieved. # * +nil+ if a definitive answer cannot be had and nothing was retrieved. # # ==== Example # # are_same, value, other_value = entry.compare_to(other) # if are_same.nil? # are_same, other_value, value = other.compare_to(entry) # end # if are_same.nil? # value = entry.read if value.nil? # other_value = entry.read if other_value.nil? # are_same = (value == other_value) # end def compare_to(other) nil end # Override can_have_child? to report whether a given file *could* be added # to this directory. (Some directories can't have subdirs, some can only have .json # files, etc.) def can_have_child?(name, is_dir) false end # Get a child of this entry with the given name. This MUST always # return a child, even if it is NonexistentFSObject. Overriders should # take caution not to do expensive network requests to get the list of # children to fulfill this request, unless absolutely necessary here; it # is intended as a quick way to traverse a hierarchy. # # For example, knife show /data_bags/x/y.json will call # root.child('data_bags').child('x').child('y.json'), which can then # directly perform a network request to retrieve the y.json data bag. No # network request was necessary to retrieve def child(name) if can_have_child?(name, true) || can_have_child?(name, false) result = make_child_entry(name) end result || NonexistentFSObject.new(name, self) end # Override children to report your *actual* list of children as an array. def children raise NotFoundError.new(self) if !exists? [] end # Expand this entry into a chef object (Chef::Role, ::Node, etc.) def chef_object raise NotFoundError.new(self) if !exists? nil end # Create a child of this entry with the given name and contents. If # contents is nil, create a directory. # # NOTE: create_child_from is an optional method that can also be added to # your entry class, and will be called without actually reading the # file_contents. This is used for knife upload /cookbooks/cookbookname. def create_child(name, file_contents) raise NotFoundError.new(self) if !exists? raise OperationNotAllowedError.new(:create_child, self) end # Delete this item, possibly recursively. Entries MUST NOT delete a # directory unless recurse is true. def delete(recurse) raise NotFoundError.new(self) if !exists? raise OperationNotAllowedError.new(:delete, self) end # Ask whether this entry is a directory. If not, it is a file. def dir? false end # Ask whether this entry exists. def exists? true end # Printable path, generally used to distinguish paths in one root from # paths in another. def path_for_printing if parent parent_path = parent.path_for_printing if parent_path == "." name else Chef::ChefFS::PathUtils.join(parent.path_for_printing, name) end else name end end def root parent ? parent.root : self end # Read the contents of this file entry. def read raise NotFoundError.new(self) if !exists? raise OperationNotAllowedError.new(:read, self) end # Write the contents of this file entry. def write(file_contents) raise NotFoundError.new(self) if !exists? raise OperationNotAllowedError.new(:write, self) end # Important directory attributes: name, parent, path, root # Overridable attributes: dir?, child(name), path_for_printing # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to, make_child_entry end # class BaseFsObject end end end require "chef/chef_fs/file_system/nonexistent_fs_object" chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/000077500000000000000000000000001276456504500225565ustar00rootroot00000000000000chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/acl_dir.rb000066400000000000000000000040671276456504500245070ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/base_fs_dir" require "chef/chef_fs/file_system/chef_server/acl_entry" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module ChefServer class AclDir < BaseFSDir def api_path parent.parent.child(name).api_path end def make_child_entry(name, exists = nil) result = @children.find { |child| child.name == name } if @children result || AclEntry.new(name, self, exists) end def can_have_child?(name, is_dir) !is_dir end def children if @children.nil? # Grab the ACTUAL children (/nodes, /containers, etc.) and get their names names = parent.parent.child(name).children.map { |child| child.dir? ? "#{child.name}.json" : child.name } @children = names.map { |name| make_child_entry(name, true) } end @children end def create_child(name, file_contents) raise OperationNotAllowedError.new(:create_child, self, nil, "ACLs can only be updated, and can only be created when the corresponding object is created.") end def data_handler parent.data_handler end def rest parent.rest end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/acl_entry.rb000066400000000000000000000044751276456504500250750ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/chef_server/rest_list_entry" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module ChefServer class AclEntry < RestListEntry PERMISSIONS = %w{create read update delete grant} def api_path "#{super}/_acl" end def display_path pth = if parent.name == "acls" "/acls/#{name}" else "/acls/#{parent.name}/#{name}" end File.extname(pth).empty? ? pth + ".json" : pth end def delete(recurse) raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self, nil, "ACLs cannot be deleted") end def write(file_contents) # ACL writes are fun. acls = data_handler.normalize(Chef::JSONCompat.parse(file_contents), self) PERMISSIONS.each do |permission| begin rest.put("#{api_path}/#{permission}", { permission => acls[permission] }) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}") end end end end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/acls_dir.rb000066400000000000000000000051461276456504500246710ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/base_fs_dir" require "chef/chef_fs/file_system/chef_server/acl_dir" require "chef/chef_fs/file_system/chef_server/cookbooks_acl_dir" require "chef/chef_fs/file_system/chef_server/policies_acl_dir" require "chef/chef_fs/file_system/chef_server/acl_entry" require "chef/chef_fs/data_handler/acl_data_handler" class Chef module ChefFS module FileSystem module ChefServer class AclsDir < BaseFSDir ENTITY_TYPES = %w{clients containers cookbook_artifacts cookbooks data_bags environments groups nodes policies policy_groups roles} # we don't read sandboxes, so we don't read their acls def data_handler @data_handler ||= Chef::ChefFS::DataHandler::AclDataHandler.new end def api_path parent.api_path end def make_child_entry(name) children.find { |child| child.name == name } end def can_have_child?(name, is_dir) is_dir ? ENTITY_TYPES.include?(name) : name == "organization.json" end def children if @children.nil? @children = ENTITY_TYPES.map do |entity_type| # All three of these can be versioned (NAME-VERSION), but only have # one ACL that covers them all (NAME.json). case entity_type when "cookbooks", "cookbook_artifacts" CookbooksAclDir.new(entity_type, self) when "policies" PoliciesAclDir.new(entity_type, self) else AclDir.new(entity_type, self) end end @children << AclEntry.new("organization.json", self, true) # the org acl is retrieved as GET /organizations/ORGNAME/ANYTHINGATALL/_acl end @children end def rest parent.rest end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb000066400000000000000000000205541276456504500273050ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/server_api" require "chef/chef_fs/file_system/chef_server/acls_dir" require "chef/chef_fs/file_system/base_fs_dir" require "chef/chef_fs/file_system/chef_server/rest_list_dir" require "chef/chef_fs/file_system/chef_server/cookbooks_dir" require "chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir" require "chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir" require "chef/chef_fs/file_system/chef_server/data_bags_dir" require "chef/chef_fs/file_system/chef_server/nodes_dir" require "chef/chef_fs/file_system/chef_server/org_entry" require "chef/chef_fs/file_system/chef_server/organization_invites_entry" require "chef/chef_fs/file_system/chef_server/organization_members_entry" require "chef/chef_fs/file_system/chef_server/policies_dir" require "chef/chef_fs/file_system/chef_server/policy_groups_dir" require "chef/chef_fs/file_system/chef_server/environments_dir" require "chef/chef_fs/data_handler/acl_data_handler" require "chef/chef_fs/data_handler/client_data_handler" require "chef/chef_fs/data_handler/environment_data_handler" require "chef/chef_fs/data_handler/node_data_handler" require "chef/chef_fs/data_handler/role_data_handler" require "chef/chef_fs/data_handler/user_data_handler" require "chef/chef_fs/data_handler/group_data_handler" require "chef/chef_fs/data_handler/container_data_handler" require "chef/chef_fs/data_handler/policy_group_data_handler" class Chef module ChefFS module FileSystem module ChefServer # # Represents the root of a Chef server (or organization), under which # nodes, roles, cookbooks, etc. can be found. # class ChefServerRootDir < BaseFSDir # # Create a new Chef server root. # # == Parameters # # [root_name] # A friendly name for the root, for printing--like "remote" or "chef_central". # [chef_config] # A hash with options that look suspiciously like Chef::Config, including the # following keys: # :chef_server_url:: The URL to the Chef server or top of the organization # :node_name:: The username to authenticate to the Chef server with # :client_key:: The private key for the user for authentication # :environment:: The environment in which you are presently working # :repo_mode:: # The repository mode, :hosted_everything, :everything or :static. # This determines the set of subdirectories the Chef server will # offer up. # :versioned_cookbooks:: whether or not to include versions in cookbook names # [options] # Other options: # :cookbook_version:: when cookbooks are retrieved, grab this version for them. # :freeze:: freeze cookbooks on upload # def initialize(root_name, chef_config, options = {}) super("", nil) @chef_server_url = chef_config[:chef_server_url] @chef_username = chef_config[:node_name] @chef_private_key = chef_config[:client_key] @environment = chef_config[:environment] @repo_mode = chef_config[:repo_mode] @versioned_cookbooks = chef_config[:versioned_cookbooks] @root_name = root_name @cookbook_version = options[:cookbook_version] # Used in knife diff and download for server cookbook version end attr_reader :chef_server_url attr_reader :chef_username attr_reader :chef_private_key attr_reader :environment attr_reader :repo_mode attr_reader :cookbook_version attr_reader :versioned_cookbooks def fs_description "Chef server at #{chef_server_url} (user #{chef_username}), repo_mode = #{repo_mode}" end def rest Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true, :api_version => "0") end def get_json(path) Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :api_version => "0").get(path) end def chef_rest Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key) end def api_path "" end def path_for_printing "#{@root_name}/" end def can_have_child?(name, is_dir) result = children.find { |child| child.name == name } result && !!result.dir? == !!is_dir end def org @org ||= begin path = Pathname.new(URI.parse(chef_server_url).path).cleanpath if File.dirname(path) == "/organizations" File.basename(path) else # In Chef 12, everything is in an org. "chef" end end end def make_child_entry(name) children.find { |child| child.name == name } end def children @children ||= begin result = [ # /cookbooks versioned_cookbooks ? VersionedCookbooksDir.new("cookbooks", self) : CookbooksDir.new("cookbooks", self), # /data_bags DataBagsDir.new("data_bags", self, "data"), # /environments EnvironmentsDir.new("environments", self, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new), # /roles RestListDir.new("roles", self, nil, Chef::ChefFS::DataHandler::RoleDataHandler.new), ] if repo_mode == "hosted_everything" result += [ # /acls AclsDir.new("acls", self), # /clients RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new), # /containers RestListDir.new("containers", self, nil, Chef::ChefFS::DataHandler::ContainerDataHandler.new), # /cookbook_artifacts CookbookArtifactsDir.new("cookbook_artifacts", self), # /groups RestListDir.new("groups", self, nil, Chef::ChefFS::DataHandler::GroupDataHandler.new), # /nodes NodesDir.new("nodes", self, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new), # /org.json OrgEntry.new("org.json", self), # /members.json OrganizationMembersEntry.new("members.json", self), # /invitations.json OrganizationInvitesEntry.new("invitations.json", self), # /policies PoliciesDir.new("policies", self, nil, Chef::ChefFS::DataHandler::PolicyDataHandler.new), # /policy_groups PolicyGroupsDir.new("policy_groups", self, nil, Chef::ChefFS::DataHandler::PolicyGroupDataHandler.new), ] elsif repo_mode != "static" result += [ # /clients RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new), # /nodes NodesDir.new("nodes", self, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new), # /users RestListDir.new("users", self, nil, Chef::ChefFS::DataHandler::UserDataHandler.new), ] end result.sort_by { |child| child.name } end end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/cookbook_artifact_dir.rb000066400000000000000000000023661276456504500274330ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/chef_server/cookbook_dir" class Chef module ChefFS module FileSystem module ChefServer class CookbookArtifactDir < CookbookDir def initialize(name, parent, options = {}) super(name, parent) @cookbook_name, dash, @version = name.rpartition("-") end def copy_from(other, options = {}) raise OperationNotAllowedError.new(:write, self, nil, "cannot be updated: cookbook artifacts are immutable once uploaded") end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir.rb000066400000000000000000000074011276456504500276110ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/chef_server/cookbooks_dir" require "chef/chef_fs/file_system/chef_server/cookbook_artifact_dir" class Chef module ChefFS module FileSystem module ChefServer # # /cookbook_artifacts # # Example children of /cookbook_artifacts: # # - apache2-ab234098245908ddf324a # - apache2-295387a9823745feff239 # - mysql-1a2b9e1298734dfe90444 # class CookbookArtifactsDir < CookbooksDir def make_child_entry(name) result = @children.find { |child| child.name == name } if @children result || CookbookArtifactDir.new(name, self) end def children @children ||= begin result = [] root.get_json("#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks| cookbooks["versions"].each do |cookbook_version| result << CookbookArtifactDir.new("#{cookbook_name}-#{cookbook_version['identifier']}", self) end end result.sort_by(&:name) end end # Knife currently does not understand versioned cookbooks # Cookbook Version uploader also requires a lot of refactoring # to make this work. So instead, we make a temporary cookbook # symlinking back to real cookbook, and upload the proxy. def upload_cookbook(other, options) cookbook_name, dash, identifier = other.name.rpartition("-") Dir.mktmpdir do |temp_cookbooks_path| proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}" # Make a symlink file_class.symlink other.file_path, proxy_cookbook_path # Instantiate a proxy loader using the temporary symlink proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.parent.chefignore) proxy_loader.load_cookbooks cookbook_to_upload = proxy_loader.cookbook_version cookbook_to_upload.identifier = identifier cookbook_to_upload.freeze_version if options[:freeze] # Instantiate a new uploader based on the proxy loader uploader = Chef::CookbookUploader.new(cookbook_to_upload, force: options[:force], rest: root.chef_rest, policy_mode: true) with_actual_cookbooks_dir(temp_cookbooks_path) do uploader.upload_cookbooks end # # When the temporary directory is being deleted on # windows, the contents of the symlink under that # directory is also deleted. So explicitly remove # the symlink without removing the original contents if we # are running on windows # if Chef::Platform.windows? Dir.rmdir proxy_cookbook_path end end end def can_have_child?(name, is_dir) is_dir && name.include?("-") end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb000066400000000000000000000206521276456504500255540ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/command_line" require "chef/chef_fs/file_system/chef_server/rest_list_dir" require "chef/chef_fs/file_system/chef_server/cookbook_subdir" require "chef/chef_fs/file_system/chef_server/cookbook_file" require "chef/chef_fs/file_system/exceptions" require "chef/cookbook_version" require "chef/cookbook_uploader" class Chef module ChefFS module FileSystem module ChefServer # Unversioned cookbook. # # /cookbooks/NAME # # Children look like: # # - metadata.rb # - attributes/ # - libraries/ # - recipes/ # class CookbookDir < BaseFSDir def initialize(name, parent, options = {}) super(name, parent) @exists = options[:exists] @cookbook_name = name @version = root.cookbook_version # nil unless --cookbook-version specified in download/diff end attr_reader :cookbook_name, :version COOKBOOK_SEGMENT_INFO = { :attributes => { :ruby_only => true }, :definitions => { :ruby_only => true }, :recipes => { :ruby_only => true }, :libraries => { :recursive => true }, :templates => { :recursive => true }, :files => { :recursive => true }, :resources => { :ruby_only => true, :recursive => true }, :providers => { :ruby_only => true, :recursive => true }, :root_files => {}, } def add_child(child) @children << child end def api_path "#{parent.api_path}/#{cookbook_name}/#{version || "_latest"}" end def make_child_entry(name) # Since we're ignoring the rules and doing a network request here, # we need to make sure we don't rethrow the exception. (child(name) # is not supposed to fail.) begin children.find { |child| child.name == name } rescue Chef::ChefFS::FileSystem::NotFoundError nil end end def can_have_child?(name, is_dir) # A cookbook's root may not have directories unless they are segment directories return name != "root_files" && COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) if is_dir return true end def children if @children.nil? @children = [] manifest = chef_object.manifest COOKBOOK_SEGMENT_INFO.each do |segment, segment_info| next unless manifest.has_key?(segment) # Go through each file in the manifest for the segment, and # add cookbook subdirs and files for it. manifest[segment].each do |segment_file| parts = segment_file[:path].split("/") # Get or create the path to the file container = self parts[0, parts.length - 1].each do |part| old_container = container container = old_container.children.find { |child| part == child.name } if !container container = CookbookSubdir.new(part, old_container, segment_info[:ruby_only], segment_info[:recursive]) old_container.add_child(container) end end # Create the file itself container.add_child(CookbookFile.new(parts[parts.length - 1], container, segment_file)) end end @children = @children.sort_by { |c| c.name } end @children end def dir? exists? end def delete(recurse) if recurse begin rest.delete(api_path) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}") rescue Net::HTTPServerException if $!.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "HTTP error deleting: #{e}") end end else raise NotFoundError.new(self) if !exists? raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively") end end # In versioned cookbook mode, actually check if the version exists # Probably want to cache this. def exists? if @exists.nil? @exists = parent.children.any? { |child| child.name == name } end @exists end def compare_to(other) if !other.dir? return [ !exists?, nil, nil ] end are_same = true Chef::ChefFS::CommandLine.diff_entries(self, other, nil, :name_only).each do |type, old_entry, new_entry| if [ :directory_to_file, :file_to_directory, :deleted, :added, :modified ].include?(type) are_same = false end end [ are_same, nil, nil ] end def copy_from(other, options = {}) parent.upload_cookbook_from(other, options) end def rest parent.rest end def chef_object # We cheat and cache here, because it seems like a good idea to keep # the cookbook view consistent with the directory structure. return @chef_object if @chef_object # The negative (not found) response is cached if @could_not_get_chef_object raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object) end begin # We want to fail fast, for now, because of the 500 issue :/ # This will make things worse for parallelism, a little, because # Chef::Config is global and this could affect other requests while # this request is going on. (We're not parallel yet, but we will be.) # Chef bug http://tickets.opscode.com/browse/CHEF-3066 old_retry_count = Chef::Config[:http_retry_count] begin Chef::Config[:http_retry_count] = 0 @chef_object ||= Chef::CookbookVersion.from_hash(root.get_json(api_path)) ensure Chef::Config[:http_retry_count] = old_retry_count end rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" @could_not_get_chef_object = e raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}") end # Chef bug http://tickets.opscode.com/browse/CHEF-3066 ... instead of 404 we get 500 right now. # Remove this when that bug is fixed. rescue Net::HTTPFatalError => e if e.response.code == "500" @could_not_get_chef_object = e raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}") end end end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb000066400000000000000000000046051276456504500257150ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_object" require "chef/http/simple" require "openssl" class Chef module ChefFS module FileSystem module ChefServer class CookbookFile < BaseFSObject def initialize(name, parent, file) super(name, parent) @file = file end attr_reader :file def checksum file[:checksum] end def read begin tmpfile = rest.streaming_request(file[:url]) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading #{file[:url]}: #{e}") rescue Net::HTTPServerException => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "#{e.message} retrieving #{file[:url]}") end begin tmpfile.open tmpfile.read ensure tmpfile.close! end end def rest parent.rest end def compare_to(other) other_value = nil if other.respond_to?(:checksum) other_checksum = other.checksum else begin other_value = other.read rescue Chef::ChefFS::FileSystem::NotFoundError return [ false, nil, :none ] end other_checksum = calc_checksum(other_value) end [ checksum == other_checksum, nil, other_value ] end private def calc_checksum(value) OpenSSL::Digest::MD5.hexdigest(value) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb000066400000000000000000000032651276456504500262670ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_dir" class Chef module ChefFS module FileSystem module ChefServer class CookbookSubdir < BaseFSDir def initialize(name, parent, ruby_only, recursive) super(name, parent) @children = [] @ruby_only = ruby_only @recursive = recursive end attr_reader :versions attr_reader :children def add_child(child) @children << child end def can_have_child?(name, is_dir) if is_dir return false if !@recursive else return false if @ruby_only && name !~ /\.rb$/ end true end def make_child_entry(name) result = @children.find { |child| child.name == name } if @children result || NonexistentFSObject.new(name, self) end def rest parent.rest end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/cookbooks_acl_dir.rb000066400000000000000000000030641276456504500265540ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/chef_server/acl_dir" class Chef module ChefFS module FileSystem module ChefServer class CookbooksAclDir < AclDir # If versioned_cookbooks is on, the list of cookbooks will have versions # in them. But all versions of a cookbook have the same acl, so even if # we have cookbooks/apache2-1.0.0 and cookbooks/apache2-1.1.2, we will # only have one acl: acls/cookbooks/apache2.json. Thus, the list of # children of acls/cookbooks is a unique list of cookbook *names*. def children if @children.nil? names = parent.parent.child(name).children.map { |child| "#{child.cookbook_name}.json" } @children = names.uniq.map { |name| make_child_entry(name, true) } end @children end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb000066400000000000000000000070421276456504500257350ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/chef_server/rest_list_dir" require "chef/chef_fs/file_system/chef_server/cookbook_dir" require "chef/chef_fs/file_system/exceptions" require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir" require "chef/mixin/file_class" require "tmpdir" class Chef module ChefFS module FileSystem module ChefServer # # /cookbooks # # Example children: # apache2/ # mysql/ # class CookbooksDir < RestListDir include Chef::Mixin::FileClass def make_child_entry(name) result = @children.find { |child| child.name == name } if @children result || CookbookDir.new(name, self) end def children @children ||= begin result = root.get_json(api_path).keys.map { |cookbook_name| CookbookDir.new(cookbook_name, self, exists: true) } result.sort_by(&:name) end end def create_child_from(other, options = {}) @children = nil upload_cookbook_from(other, options) end def upload_cookbook_from(other, options = {}) upload_cookbook(other, options) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}") rescue Net::HTTPServerException => e case e.response.code when "409" raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen") else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}") end rescue Chef::Exceptions::CookbookFrozen => e raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen") end def upload_cookbook(other, options) cookbook_to_upload = other.chef_object cookbook_to_upload.freeze_version if options[:freeze] uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest) with_actual_cookbooks_dir(other.parent.file_path) do uploader.upload_cookbooks end end # Work around the fact that CookbookUploader doesn't understand chef_repo_path (yet) def with_actual_cookbooks_dir(actual_cookbook_path) old_cookbook_path = Chef::Config.cookbook_path Chef::Config.cookbook_path = actual_cookbook_path if !Chef::Config.cookbook_path yield ensure Chef::Config.cookbook_path = old_cookbook_path end def can_have_child?(name, is_dir) is_dir end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/data_bag_dir.rb000066400000000000000000000051451276456504500254700ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/chef_server/rest_list_dir" require "chef/chef_fs/file_system/chef_server/data_bag_entry" require "chef/chef_fs/file_system/exceptions" require "chef/chef_fs/data_handler/data_bag_item_data_handler" class Chef module ChefFS module FileSystem module ChefServer class DataBagDir < RestListDir def initialize(name, parent, exists = nil) super(name, parent, nil, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new) @exists = nil end def dir? exists? end def read # This will only be called if dir? is false, which means exists? is false. raise Chef::ChefFS::FileSystem::NotFoundError.new(self) end def exists? if @exists.nil? @exists = parent.children.any? { |child| child.name == name } end @exists end def delete(recurse) if !recurse raise NotFoundError.new(self) if !exists? raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively") end begin rest.delete(api_path) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "HTTP error deleting: #{e}") end end end def make_child_entry(name, exists = nil) @children.find { |child| child.name == name } if @children DataBagEntry.new(name, self, exists) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/data_bag_entry.rb000066400000000000000000000010701276456504500260440ustar00rootroot00000000000000require "chef/chef_fs/file_system/chef_server/rest_list_entry" require "chef/chef_fs/data_handler/data_bag_item_data_handler" class Chef module ChefFS module FileSystem module ChefServer # /policies/NAME-REVISION.json # Represents the actual data at /organizations/ORG/policies/NAME/revisions/REVISION class DataBagEntry < RestListEntry def display_path pth = "/data_bags/#{parent.name}/#{name}" File.extname(pth).empty? ? pth + ".json" : pth end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/data_bags_dir.rb000066400000000000000000000052661276456504500256570ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/chef_server/rest_list_dir" require "chef/chef_fs/file_system/chef_server/data_bag_dir" class Chef module ChefFS module FileSystem module ChefServer class DataBagsDir < RestListDir def make_child_entry(name, exists = false) result = @children.find { |child| child.name == name } if @children result || DataBagDir.new(name, self, exists) end def children begin @children ||= root.get_json(api_path).keys.sort.map { |entry| make_child_entry(entry, true) } rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout getting children: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error getting children: #{e}") end end end def can_have_child?(name, is_dir) is_dir end def create_child(name, file_contents) begin rest.post(api_path, { "name" => name }) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating child '#{name}': #{e}") rescue Net::HTTPServerException => e if e.response.code == "409" raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Cannot create #{name} under #{path}: already exists") else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "HTTP error creating child '#{name}': #{e}") end end @children = nil DataBagDir.new(name, self, true) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/environments_dir.rb000066400000000000000000000034301276456504500264700ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_dir" require "chef/chef_fs/file_system/chef_server/rest_list_entry" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module ChefServer class EnvironmentsDir < RestListDir def make_child_entry(name, exists = nil) if File.basename(name, ".*") == "_default" DefaultEnvironmentEntry.new(name, self, exists) else super end end class DefaultEnvironmentEntry < RestListEntry def initialize(name, parent, exists = nil) super(name, parent) @exists = exists end def delete(recurse) raise NotFoundError.new(self) if !exists? raise DefaultEnvironmentCannotBeModifiedError.new(:delete, self) end def write(file_contents) raise NotFoundError.new(self) if !exists? raise DefaultEnvironmentCannotBeModifiedError.new(:write, self) end end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb000066400000000000000000000036531276456504500250600ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_dir" require "chef/chef_fs/file_system/chef_server/rest_list_entry" require "chef/chef_fs/file_system/exceptions" require "chef/chef_fs/data_handler/node_data_handler" class Chef module ChefFS module FileSystem module ChefServer class NodesDir < RestListDir # Identical to RestListDir.children, except supports environments def children begin @children ||= root.get_json(env_api_path).keys.sort.map do |key| make_child_entry(key, true) end rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}") rescue Net::HTTPServerException => e if $!.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}") end end end def env_api_path environment ? "environments/#{environment}/#{api_path}" : api_path end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/org_entry.rb000066400000000000000000000016061276456504500251160ustar00rootroot00000000000000require "chef/chef_fs/file_system/chef_server/rest_list_entry" require "chef/chef_fs/data_handler/organization_data_handler" class Chef module ChefFS module FileSystem module ChefServer # /organizations/NAME/org.json # Represents the actual data at /organizations/NAME (the full name, etc.) class OrgEntry < RestListEntry def data_handler Chef::ChefFS::DataHandler::OrganizationDataHandler.new end # /organizations/foo/org.json -> GET /organizations/foo def api_path parent.api_path end def display_path "/org.json" end def exists? parent.exists? end def delete(recurse) raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb000066400000000000000000000042551276456504500305770ustar00rootroot00000000000000require "chef/chef_fs/file_system/chef_server/rest_list_entry" require "chef/chef_fs/data_handler/organization_invites_data_handler" require "chef/json_compat" class Chef module ChefFS module FileSystem module ChefServer # /organizations/NAME/invitations.json # read data from: # - GET /organizations/NAME/association_requests # write data to: # - remove from list: DELETE /organizations/NAME/association_requests/id # - add to list: POST /organizations/NAME/association_requests class OrganizationInvitesEntry < RestListEntry def initialize(name, parent, exists = nil) super(name, parent) @exists = exists end def data_handler Chef::ChefFS::DataHandler::OrganizationInvitesDataHandler.new end # /organizations/foo/invites.json -> /organizations/foo/association_requests def api_path File.join(parent.api_path, "association_requests") end def display_path "/invitations.json" end def exists? parent.exists? end def delete(recurse) raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self) end def write(contents) desired_invites = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false)) actual_invites = _read_json.inject({}) { |h, val| h[val["username"]] = val["id"]; h } invites = actual_invites.keys (desired_invites - invites).each do |invite| begin rest.post(api_path, { "user" => invite }) rescue Net::HTTPServerException => e if e.response.code == "409" Chef::Log.warn("Could not invite #{invite} to organization #{org}: #{api_error_text(e.response)}") else raise end end end (invites - desired_invites).each do |invite| rest.delete(File.join(api_path, actual_invites[invite])) end end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/organization_members_entry.rb000066400000000000000000000041431276456504500305440ustar00rootroot00000000000000require "chef/chef_fs/file_system/chef_server/rest_list_entry" require "chef/chef_fs/data_handler/organization_members_data_handler" require "chef/json_compat" class Chef module ChefFS module FileSystem module ChefServer # /organizations/NAME/members.json # reads data from: # - GET /organizations/NAME/users # writes data to: # - remove from list: DELETE /organizations/NAME/users/name # - add to list: POST /organizations/NAME/users/name class OrganizationMembersEntry < RestListEntry def initialize(name, parent, exists = nil) super(name, parent) @exists = exists end def data_handler Chef::ChefFS::DataHandler::OrganizationMembersDataHandler.new end # /organizations/foo/members.json -> /organizations/foo/users def api_path File.join(parent.api_path, "users") end def display_path "/members.json" end def exists? parent.exists? end def delete(recurse) raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self) end def write(contents) desired_members = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false)) members = minimize_value(_read_json) (desired_members - members).each do |member| begin rest.post(api_path, "username" => member) rescue Net::HTTPServerException => e if %w{404 405}.include?(e.response.code) raise "Chef server at #{api_path} does not allow you to directly add members. Please either upgrade your Chef server or move the users you want into invitations.json instead of members.json." else raise end end end (members - desired_members).each do |member| rest.delete(File.join(api_path, member)) end end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/policies_acl_dir.rb000066400000000000000000000026571276456504500264010ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/chef_server/acl_dir" class Chef module ChefFS module FileSystem module ChefServer class PoliciesAclDir < AclDir # Policies are presented like /NAME-VERSION.json. But there is only # one ACL for a given NAME. So we find out the unique policy names, # and make one acls/policies/NAME.json for each one. def children if @children.nil? # /acls/policies -> List ../../policies names = parent.parent.child(name).children.map { |child| "#{child.policy_name}.json" } @children = names.uniq.map { |name| make_child_entry(name, true) } end @children end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/policies_dir.rb000066400000000000000000000152421276456504500255540ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/chef_server/rest_list_dir" require "chef/chef_fs/file_system/chef_server/policy_revision_entry" class Chef module ChefFS module FileSystem module ChefServer # # Server API: # /policies - list of policies by name # - /policies/NAME - represents a policy with all revisions # - /policies/NAME/revisions - list of revisions for that policy # - /policies/NAME/revisions/REVISION - actual policy-revision document # # Local Repository and ChefFS: # /policies - PoliciesDir - maps to server API /policies # - /policies/NAME-REVISION.json - PolicyRevision - maps to /policies/NAME/revisions/REVISION # class PoliciesDir < RestListDir # Children: NAME-REVISION.json for all revisions of all policies # # /nodes: { # "node1": "https://api.opscode.com/organizations/myorg/nodes/node1", # "node2": "https://api.opscode.com/organizations/myorg/nodes/node2", # } # # /policies: { # "foo": {} # } def make_child_entry(name, exists = nil) @children.find { |child| child.name == name } if @children PolicyRevisionEntry.new(name, self, exists) end # Children come from /policies in this format: # { # "foo": { # "uri": "https://api.opscode.com/organizations/essentials/policies/foo", # "revisions": { # "1.0.0": { # # }, # "1.0.1": { # # } # } # } # } def children begin # Grab the names of the children, append json, and make child entries @children ||= begin result = [] data = root.get_json(api_path) data.keys.sort.each do |policy_name| data[policy_name]["revisions"].keys.each do |policy_revision| filename = "#{policy_name}-#{policy_revision}.json" result << make_child_entry(filename, true) end end result end rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}") rescue Net::HTTPServerException => e # 404 = NotFoundError if $!.response.code == "404" # GET /organizations/ORG/policies returned 404, but that just might be because # we are talking to an older version of the server that doesn't support policies. # Do GET /orgqanizations/ORG to find out if the org exists at all. # TODO use server API version instead of a second network request. begin root.get_json(parent.api_path) # Return empty list if the organization exists but /policies didn't work [] rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}") end # Anything else is unexpected (OperationFailedError) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}") end end end # # Does POST with file_contents # def create_child(name, file_contents) # Parse the contents to ensure they are valid JSON begin object = Chef::JSONCompat.parse(file_contents) rescue Chef::Exceptions::JSON::ParseError => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}") end # Create the child entry that will be returned result = make_child_entry(name, true) # Normalize the file_contents before post (add defaults, etc.) if data_handler object = data_handler.normalize_for_post(object, result) data_handler.verify_integrity(object, result) do |error| raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}") end end # POST /api_path with the normalized file_contents begin policy_name, policy_revision = data_handler.name_and_revision(name) rest.post("#{api_path}/#{policy_name}/revisions", object) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}") rescue Net::HTTPServerException => e # 404 = NotFoundError if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) # 409 = AlreadyExistsError elsif $!.response.code == "409" raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists") # Anything else is unexpected (OperationFailedError) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}") end end # Clear the cache of children so that if someone asks for children # again, we will get it again @children = nil result end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/policy_group_entry.rb000066400000000000000000000133011276456504500270350ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module ChefServer # Represents an entire policy group. # Server path: /organizations/ORG/policy_groups/GROUP # Repository path: policy_groups\GROUP.json # Format: # { # "policies": { # "a": { "revision_id": "1.0.0" } # } # } class PolicyGroupEntry < RestListEntry # delete is handled normally: # DELETE /organizations/ORG/policy_groups/GROUP # read is handled normally: # GET /organizations/ORG/policy_groups/GROUP # write is different. # For each policy: # - PUT /organizations/ORG/policy_groups/GROUP/policies/POLICY # For each policy on the server but not the client: # - DELETE /organizations/ORG/policy_groups/GROUP/policies/POLICY # If the server has associations for a, b and c, # And the client wants associations for a, x and y, # We must PUT a, x and y # And DELETE b and c def write(file_contents) # Parse the contents to ensure they are valid JSON begin object = Chef::JSONCompat.parse(file_contents) rescue Chef::Exceptions::JSON::ParseError => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}") end if data_handler object = data_handler.normalize_for_put(object, self) data_handler.verify_integrity(object, self) do |error| raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}") end end begin # this should all get carted to PolicyGroupEntry#write. # the server demands the full policy data, but we want users' local policy_group documents to just # have the data you'd get from GET /policy_groups/POLICY_GROUP. so we try to fetch that. # ordinarily this would be POST to the normal URL, but we do PUT to # /organizations/{organization}/policy_groups/{policy_group}/policies/{policy_name} with the full # policy data, for each individual policy. policy_datas = {} object["policies"].each do |policy_name, policy_data| policy_path = "/policies/#{policy_name}/revisions/#{policy_data["revision_id"]}" get_data = begin rest.get(policy_path) rescue Net::HTTPServerException => e raise "Could not find policy '#{policy_name}'' with revision '#{policy_data["revision_id"]}'' on the server" end # GET policy data server_policy_data = Chef::JSONCompat.parse(get_data) # if it comes back 404, raise an Exception with "Policy file X does not exist with revision Y on the server" # otherwise, add it to the list of policyfile datas. policy_datas[policy_name] = server_policy_data end begin existing_group = Chef::JSONCompat.parse(self.read) rescue NotFoundError # It's OK if the group doesn't already exist, just means no existing policies end # now we have the fullpolicy data for each policies, which is what the PUT endpoint demands. policy_datas.each do |policy_name, policy_data| # PUT /organizations/ORG/policy_groups/GROUP/policies/NAME rest.put("#{api_path}/policies/#{policy_name}", policy_data) end # Now we need to remove any policies that are *not* in our current group. if existing_group && existing_group["policies"] (existing_group["policies"].keys - policy_datas.keys).each do |policy_name| rest.delete("#{api_path}/policies/#{policy_name}") end end rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}") rescue Net::HTTPServerException => e # 404 = NotFoundError if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) # 409 = AlreadyExistsError elsif $!.response.code == "409" raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists") # Anything else is unexpected (OperationFailedError) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}") end end self end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb000066400000000000000000000025471276456504500266470ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_dir" require "chef/chef_fs/file_system/chef_server/rest_list_entry" require "chef/chef_fs/file_system/exceptions" require "chef/chef_fs/file_system/chef_server/policy_group_entry" class Chef module ChefFS module FileSystem module ChefServer class PolicyGroupsDir < RestListDir def make_child_entry(name, exists = nil) PolicyGroupEntry.new(name, self, exists) end def create_child(name, file_contents) entry = make_child_entry(name, true) entry.write(file_contents) @children = nil entry end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/policy_revision_entry.rb000066400000000000000000000023421276456504500275420ustar00rootroot00000000000000require "chef/chef_fs/file_system/chef_server/rest_list_entry" require "chef/chef_fs/data_handler/policy_data_handler" class Chef module ChefFS module FileSystem module ChefServer # /policies/NAME-REVISION.json # Represents the actual data at /organizations/ORG/policies/NAME/revisions/REVISION class PolicyRevisionEntry < RestListEntry # /policies/foo-1.0.0.json -> /policies/foo/revisions/1.0.0 def api_path(options = {}) "#{parent.api_path}/#{policy_name}/revisions/#{revision_id}" end def display_path "/policies/#{policy_name}-#{revision_id}.json" end def write(file_contents) raise OperationNotAllowedError.new(:write, self, nil, "cannot be updated: policy revisions are immutable once uploaded. If you want to change the policy, create a new revision with your changes") end def policy_name policy_name, revision_id = data_handler.name_and_revision(name) policy_name end def revision_id policy_name, revision_id = data_handler.name_and_revision(name) revision_id end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/rest_list_dir.rb000066400000000000000000000161071276456504500257560ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_dir" require "chef/chef_fs/file_system/chef_server/rest_list_entry" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module ChefServer class RestListDir < BaseFSDir def initialize(name, parent, api_path = nil, data_handler = nil) super(name, parent) @api_path = api_path || (parent.api_path == "" ? name : "#{parent.api_path}/#{name}") @data_handler = data_handler end attr_reader :api_path attr_reader :data_handler def can_have_child?(name, is_dir) !is_dir end # # When talking to a modern (12.0+) Chef server # knife list / # -> /nodes # -> /policies # -> /policy_groups # -> /roles # # 12.0 or 12.1 will fail when you do this: # knife list / --recursive # Because it thinks /policies exists, and when it tries to list its children # it gets a 404 (indicating it actually doesn't exist). # # With this change, knife list / --recursive will list /policies as a real, empty directory. # # Alternately, we could have done some sort of detection when we listed the top level # and determined which endpoints the server would support, and returned only those. # So you wouldn't see /policies in that case at all. # The issue with that is there's no efficient way to do it because we can't find out # the server version directly, and can't ask the server for a list of the endpoints it supports. # # # Does GET /, assumes the result is of the format: # # { # "foo": "/foo", # "bar": "/bar", # } # # Children are foo.json and bar.json in this case. # def children begin # Grab the names of the children, append json, and make child entries @children ||= root.get_json(api_path).keys.sort.map do |key| make_child_entry(key, true) end rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}") rescue Net::HTTPServerException => e # 404 = NotFoundError if $!.response.code == "404" if parent.is_a?(ChefServerRootDir) # GET /organizations/ORG/ returned 404, but that just might be because # we are talking to an older version of the server that doesn't support policies. # Do GET /organizations/ORG to find out if the org exists at all. # TODO use server API version instead of a second network request. begin root.get_json(parent.api_path) # Return empty list if the organization exists but /policies didn't work [] rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}") end else raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end # Anything else is unexpected (OperationFailedError) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}") end end end # # Does POST with file_contents # def create_child(name, file_contents) # Parse the contents to ensure they are valid JSON begin object = Chef::JSONCompat.parse(file_contents) rescue Chef::Exceptions::JSON::ParseError => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}") end # Create the child entry that will be returned result = make_child_entry(name, true) # Normalize the file_contents before post (add defaults, etc.) if data_handler object = data_handler.normalize_for_post(object, result) data_handler.verify_integrity(object, result) do |error| raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}") end end # POST /api_path with the normalized file_contents begin rest.post(api_path, object) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}") rescue Net::HTTPServerException => e # 404 = NotFoundError if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) # 409 = AlreadyExistsError elsif $!.response.code == "409" raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists") # Anything else is unexpected (OperationFailedError) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}") end end # Clear the cache of children so that if someone asks for children # again, we will get it again @children = nil result end def org parent.org end def environment parent.environment end def rest parent.rest end def make_child_entry(name, exists = nil) @children.find { |child| child.name == name } if @children RestListEntry.new(name, self, exists) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/rest_list_entry.rb000066400000000000000000000147051276456504500263430ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_object" require "chef/chef_fs/file_system/exceptions" require "chef/role" require "chef/node" require "chef/json_compat" class Chef module ChefFS module FileSystem module ChefServer class RestListEntry < BaseFSObject def initialize(name, parent, exists = nil) super(name, parent) @exists = exists end def data_handler parent.data_handler end def api_child_name if %w{ .rb .json }.include? File.extname(name) File.basename(name, ".*") else name end end def api_path "#{parent.api_path}/#{api_child_name}" end def display_path pth = api_path.start_with?("/") ? api_path : "/#{api_path}" File.extname(pth).empty? ? pth + ".json" : pth end alias_method :path_for_printing, :display_path def display_name File.basename(display_path) end def org parent.org end def environment parent.environment end def exists? if @exists.nil? begin @exists = parent.children.any? { |child| child.api_child_name == api_child_name } rescue Chef::ChefFS::FileSystem::NotFoundError @exists = false end end @exists end def delete(recurse) begin rest.delete(api_path) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}") end end end def read Chef::JSONCompat.to_json_pretty(minimize_value(_read_json)) end def _read_json begin # Minimize the value (get rid of defaults) so the results don't look terrible root.get_json(api_path) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}") rescue Net::HTTPServerException => e if $!.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}") end end end def chef_object # REST will inflate the Chef object using json_class data_handler.json_class.from_hash(read) end def minimize_value(value) data_handler.minimize(data_handler.normalize(value, self), self) end def compare_to(other) # TODO this pair of reads can be parallelized # Grab the other value begin other_value_json = other.read rescue Chef::ChefFS::FileSystem::NotFoundError return [ nil, nil, :none ] end # Grab this value begin value = _read_json rescue Chef::ChefFS::FileSystem::NotFoundError return [ false, :none, other_value_json ] end # Minimize (and normalize) both values for easy and beautiful diffs value = minimize_value(value) value_json = Chef::JSONCompat.to_json_pretty(value) begin other_value = Chef::JSONCompat.parse(other_value_json) rescue Chef::Exceptions::JSON::ParseError => e Chef::Log.warn("Parse error reading #{other.path_for_printing} as JSON: #{e}") return [ nil, value_json, other_value_json ] end other_value = minimize_value(other_value) other_value_json = Chef::JSONCompat.to_json_pretty(other_value) [ value == other_value, value_json, other_value_json ] end def rest parent.rest end def write(file_contents) begin object = Chef::JSONCompat.parse(file_contents) rescue Chef::Exceptions::JSON::ParseError => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Parse error reading JSON: #{e}") end if data_handler object = data_handler.normalize_for_put(object, self) data_handler.verify_integrity(object, self) do |error| raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, nil, "#{error}") end end begin rest.put(api_path, object) rescue Timeout::Error => e raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}") rescue Net::HTTPServerException => e if e.response.code == "404" raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) else raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}") end end end def api_error_text(response) begin Chef::JSONCompat.parse(response.body)["error"].join("\n") rescue response.body end end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/versioned_cookbook_dir.rb000066400000000000000000000030201276456504500276200ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/chef_server/cookbook_dir" class Chef module ChefFS module FileSystem module ChefServer class VersionedCookbookDir < CookbookDir # See Erchef code # https://github.com/opscode/chef_objects/blob/968a63344d38fd507f6ace05f73d53e9cd7fb043/src/chef_regex.erl#L94 VALID_VERSIONED_COOKBOOK_NAME = /^([.a-zA-Z0-9_-]+)-(\d+\.\d+\.\d+)$/ def initialize(name, parent, options = {}) super(name, parent) # If the name is apache2-1.0.0 and versioned_cookbooks is on, we know # the actual cookbook_name and version. if name =~ VALID_VERSIONED_COOKBOOK_NAME @cookbook_name = $1 @version = $2 else @exists = false end end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir.rb000066400000000000000000000077441276456504500300240ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/chef_server/cookbooks_dir" require "chef/chef_fs/file_system/chef_server/versioned_cookbook_dir" class Chef module ChefFS module FileSystem module ChefServer # # /cookbooks or /cookbook_artifacts # # Example children of /cookbooks: # # - apache2-1.0.0 # - apache2-1.0.1 # - mysql-2.0.5 # # Example children of /cookbook_artifacts: # # - apache2-ab234098245908ddf324a # - apache2-295387a9823745feff239 # - mysql-1a2b9e1298734dfe90444 # class VersionedCookbooksDir < CookbooksDir def make_child_entry(name) result = @children.find { |child| child.name == name } if @children result || VersionedCookbookDir.new(name, self) end def children @children ||= begin result = [] root.get_json("#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks| cookbooks["versions"].each do |cookbook_version| result << VersionedCookbookDir.new("#{cookbook_name}-#{cookbook_version['version']}", self) end end result.sort_by(&:name) end end # Knife currently does not understand versioned cookbooks # Cookbook Version uploader also requires a lot of refactoring # to make this work. So instead, we make a temporary cookbook # symlinking back to real cookbook, and upload the proxy. def upload_cookbook(other, options) cookbook_name = Chef::ChefFS::FileSystem::Repository::ChefRepositoryFileSystemCookbookDir.canonical_cookbook_name(other.name) Dir.mktmpdir do |temp_cookbooks_path| proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}" # Make a symlink file_class.symlink other.file_path, proxy_cookbook_path # Instantiate a proxy loader using the temporary symlink proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.parent.chefignore) proxy_loader.load_cookbooks cookbook_to_upload = proxy_loader.cookbook_version cookbook_to_upload.freeze_version if options[:freeze] # Instantiate a new uploader based on the proxy loader uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest) with_actual_cookbooks_dir(temp_cookbooks_path) do uploader.upload_cookbooks end # # When the temporary directory is being deleted on # windows, the contents of the symlink under that # directory is also deleted. So explicitly remove # the symlink without removing the original contents if we # are running on windows # if Chef::Platform.windows? Dir.rmdir proxy_cookbook_path end end end def can_have_child?(name, is_dir) is_dir && name =~ Chef::ChefFS::FileSystem::ChefServer::VersionedCookbookDir::VALID_VERSIONED_COOKBOOK_NAME end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/exceptions.rb000066400000000000000000000057611276456504500230020ustar00rootroot00000000000000# # Author:: Thom May () # 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 module ChefFS module FileSystem class FileSystemError < StandardError # @param entry The entry which had an issue. # @param cause The wrapped exception (if any). # @param reason A string describing why this exception happened. def initialize(entry, cause = nil, reason = nil) super(reason) @entry = entry @cause = cause @reason = reason end # The entry which had an issue. attr_reader :entry # The wrapped exception (if any). attr_reader :cause # A string describing why this exception happened. attr_reader :reason end class MustDeleteRecursivelyError < FileSystemError; end class NotFoundError < FileSystemError; end class OperationFailedError < FileSystemError def initialize(operation, entry, cause = nil, reason = nil) super(entry, cause, reason) @operation = operation end def message if cause && cause.is_a?(Net::HTTPExceptions) && cause.response.code == "400" "#{super} cause: #{cause.response.body}" else super end end attr_reader :operation end class OperationNotAllowedError < FileSystemError def initialize(operation, entry, cause = nil, reason = nil) reason ||= case operation when :delete "cannot be deleted" when :write "cannot be updated" when :create_child "cannot have a child created under it" when :read "cannot be read" end super(entry, cause, reason) @operation = operation end attr_reader :operation attr_reader :entry end class AlreadyExistsError < OperationFailedError; end class CookbookFrozenError < AlreadyExistsError; end class RubyFileError < OperationNotAllowedError def reason result = super result + " (can't safely update ruby files)" end end class DefaultEnvironmentCannotBeModifiedError < OperationNotAllowedError def reason result = super result + " (default environment cannot be modified)" end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/memory/000077500000000000000000000000001276456504500215735ustar00rootroot00000000000000chef-12.14.60/lib/chef/chef_fs/file_system/memory/memory_dir.rb000066400000000000000000000025711276456504500242730ustar00rootroot00000000000000require "chef/chef_fs/file_system/base_fs_dir" require "chef/chef_fs/file_system/memory/memory_file" class Chef module ChefFS module FileSystem module Memory class MemoryDir < Chef::ChefFS::FileSystem::BaseFSDir def initialize(name, parent) super(name, parent) @children = [] end attr_reader :children def make_child_entry(name) @children.find { |child| child.name == name } end def add_child(child) @children.push(child) end def can_have_child?(name, is_dir) root.cannot_be_in_regex ? (name !~ root.cannot_be_in_regex) : true end def add_file(path, value) path_parts = path.split("/") dir = add_dir(path_parts[0..-2].join("/")) file = MemoryFile.new(path_parts[-1], dir, value) dir.add_child(file) file end def add_dir(path) path_parts = path.split("/") dir = self path_parts.each do |path_part| subdir = dir.child(path_part) if !subdir.exists? subdir = MemoryDir.new(path_part, dir) dir.add_child(subdir) end dir = subdir end dir end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/memory/memory_file.rb000066400000000000000000000006251276456504500244320ustar00rootroot00000000000000require "chef/chef_fs/file_system/base_fs_object" class Chef module ChefFS module FileSystem module Memory class MemoryFile < Chef::ChefFS::FileSystem::BaseFSObject def initialize(name, parent, value) super(name, parent) @value = value end def read return @value end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/memory/memory_root.rb000066400000000000000000000010001276456504500244620ustar00rootroot00000000000000require "chef/chef_fs/file_system/memory/memory_dir" class Chef module ChefFS module FileSystem module Memory class MemoryRoot < MemoryDir def initialize(pretty_name, cannot_be_in_regex = nil) super("", nil) @pretty_name = pretty_name @cannot_be_in_regex = cannot_be_in_regex end attr_reader :cannot_be_in_regex def path_for_printing @pretty_name end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/multiplexed_dir.rb000066400000000000000000000037051276456504500240070ustar00rootroot00000000000000require "chef/chef_fs/file_system/base_fs_object" require "chef/chef_fs/file_system/nonexistent_fs_object" class Chef module ChefFS module FileSystem class MultiplexedDir < BaseFSDir def initialize(*multiplexed_dirs) @multiplexed_dirs = multiplexed_dirs.flatten super(@multiplexed_dirs[0].name, @multiplexed_dirs[0].parent) end attr_reader :multiplexed_dirs def write_dir multiplexed_dirs[0] end def children begin result = [] seen = {} # If multiple things have the same name, the first one wins. multiplexed_dirs.each do |dir| dir.children.each do |child| if seen[child.name] Chef::Log.warn("Child with name '#{child.name}' found in multiple directories: #{seen[child.name].path_for_printing} and #{child.path_for_printing}") unless seen[child.name].path_for_printing == child.path_for_printing else result << child seen[child.name] = child end end end result end end def make_child_entry(name) result = nil multiplexed_dirs.each do |dir| child_entry = dir.child(name) if child_entry.exists? if result Chef::Log.debug("Child with name '#{child_entry.name}' found in multiple directories: #{result.parent.path_for_printing} and #{child_entry.parent.path_for_printing}") else result = child_entry end end end result end def can_have_child?(name, is_dir) write_dir.can_have_child?(name, is_dir) end def create_child(name, file_contents = nil) @children = nil write_dir.create_child(name, file_contents) end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb000066400000000000000000000016751276456504500252150ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_object" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem class NonexistentFSObject < BaseFSObject def exists? false end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/000077500000000000000000000000001276456504500225025ustar00rootroot00000000000000chef-12.14.60/lib/chef/chef_fs/file_system/repository/acl.rb000066400000000000000000000023671276456504500235760ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/acl_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class Acl < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::AclDataHandler.new super end def bare_name if name == "organization" && parent.kind_of?(AclDir) "organization.json" else name end end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/acls_dir.rb000066400000000000000000000031031276456504500246040ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/acl" require "chef/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/repository/acls_sub_dir" require "chef/chef_fs/file_system/chef_server/acls_dir" require "chef/chef_fs/data_handler/acl_data_handler" class Chef module ChefFS module FileSystem module Repository class AclsDir < Repository::Directory BARE_FILES = %w{ organization.json root } def can_have_child?(name, is_dir) is_dir ? Chef::ChefFS::FileSystem::ChefServer::AclsDir::ENTITY_TYPES.include?(name) : BARE_FILES.include?(name) end protected def make_child_entry(child_name) if BARE_FILES.include? child_name Acl.new(child_name, self) else AclsSubDir.new(child_name, self) end end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/acls_sub_dir.rb000066400000000000000000000021451276456504500254620ustar00rootroot00000000000000# # Author:: Jordan Running () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/acl" require "chef/chef_fs/data_handler/acl_data_handler" require "chef/chef_fs/file_system/repository/directory" class Chef module ChefFS module FileSystem module Repository class AclsSubDir < Repository::Directory protected def make_child_entry(child_name) Acl.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/base_file.rb000066400000000000000000000103101276456504500247330ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/file_system_cache" class Chef module ChefFS module FileSystem module Repository class BaseFile attr_reader :name attr_reader :parent attr_reader :path attr_reader :file_path attr_reader :data_handler alias_method :display_path, :path alias_method :display_name, :name def initialize(name, parent) @parent = parent if %w{ .rb .json }.include? File.extname(name) name = File.basename(name, ".*") end file_path = "#{parent.file_path}/#{name}" Chef::Log.debug "BaseFile: Detecting file extension for #{name}" ext = File.exist?(file_path + ".rb") ? ".rb" : ".json" name += ext file_path += ext Chef::Log.debug "BaseFile: got a file path of #{file_path} for #{name}" @name = name @path = Chef::ChefFS::PathUtils.join(parent.path, name) @file_path = file_path end def dir? false end # Used to compare names on disk to the API, for diffing. def bare_name File.basename(name, ".*") end def is_json_file? File.extname(file_path) == ".json" end def is_ruby_file? File.extname(file_path) == ".rb" end def name_valid? !name.start_with?(".") && (is_json_file? || is_ruby_file?) end def fs_entry_valid? name_valid? && exists? end def create(file_contents) if exists? raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self) else write(file_contents) end end def can_have_child?(name, is_dir) false end attr_writer :write_pretty_json def write_pretty_json @write_pretty_json.nil? ? root.write_pretty_json : @write_pretty_json end def path_for_printing file_path end def delete(_) FileSystemCache.instance.delete!(file_path) File.delete(file_path) rescue Errno::ENOENT raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end def exists? File.file?(file_path) end def minimize(content, entry) object = Chef::JSONCompat.parse(content) object = data_handler.normalize(object, entry) object = data_handler.minimize(object, entry) Chef::JSONCompat.to_json_pretty(object) end def read if is_ruby_file? data_handler.from_ruby(file_path).to_json else File.open(file_path, "rb") { |f| f.read } end rescue Errno::ENOENT raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end def write(content) if is_ruby_file? raise Chef::ChefFS::FileSystem::RubyFileError.new(:write, self) end if content && write_pretty_json && is_json_file? content = minimize(content, self) end File.open(file_path, "wb") do |file| file.write(content) end end def root parent.root end def compare_to(other) nil end end end end end end chef_repository_file_system_cookbook_artifact_dir.rb000066400000000000000000000030011276456504500351520ustar00rootroot00000000000000chef-12.14.60/lib/chef/chef_fs/file_system/repository# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir" class Chef module ChefFS module FileSystem module Repository class ChefRepositoryFileSystemCookbookArtifactDir < ChefRepositoryFileSystemCookbookDir # Override from parent def cookbook_version loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore) cookbook_name, _dash, identifier = name.rpartition("-") # KLUDGE: We shouldn't have to use instance_variable_set loader.instance_variable_set(:@cookbook_name, cookbook_name) loader.load_cookbooks cookbook_version = loader.cookbook_version cookbook_version.identifier = identifier cookbook_version end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb000066400000000000000000000124701276456504500333660ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry" require "chef/chef_fs/file_system/chef_server/cookbook_dir" require "chef/chef_fs/file_system/chef_server/versioned_cookbook_dir" require "chef/chef_fs/file_system/exceptions" require "chef/cookbook/cookbook_version_loader" class Chef module ChefFS module FileSystem module Repository # Represents ROOT/cookbooks/:cookbook class ChefRepositoryFileSystemCookbookDir < ChefRepositoryFileSystemCookbookEntry # API Required by Respository::Directory def fs_entry_valid? return false unless File.directory?(file_path) && name_valid? if can_upload? true else Chef::Log.warn("Cookbook '#{name}' is empty or entirely chefignored at #{path_for_printing}") false end end def name_valid? !name.start_with?(".") end def dir? true end def create(file_contents = nil) if exists? raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self) end begin Dir.mkdir(file_path) rescue Errno::EEXIST raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self) end end def write(cookbook_path, cookbook_version_json, from_fs) # Use the copy/diff algorithm to copy it down so we don't destroy # chefignored data. This is terribly un-thread-safe. Chef::ChefFS::FileSystem.copy_to(Chef::ChefFS::FilePattern.new("/#{cookbook_path}"), from_fs, self, nil, { :purge => true }) # Write out .uploaded-cookbook-version.json # cookbook_file_path = File.join(file_path, cookbook_name) <- this should be the same as self.file_path if !File.exists?(file_path) FileUtils.mkdir_p(file_path) end uploaded_cookbook_version_path = File.join(file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE) File.open(uploaded_cookbook_version_path, "w") do |file| file.write(cookbook_version_json) end end # Customizations of base class def chef_object begin cb = cookbook_version if !cb Chef::Log.error("Cookbook #{file_path} empty.") raise "Cookbook #{file_path} empty." end cb rescue => e Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{e}") Chef::Log.error(e.backtrace.join("\n")) raise end end def children super.select { |entry| !(entry.dir? && entry.children.size == 0 ) } end def can_have_child?(name, is_dir) if is_dir # Only the given directories will be uploaded. return Chef::ChefFS::FileSystem::ChefServer::CookbookDir::COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) && name != "root_files" elsif name == Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE return false end super(name, is_dir) end # Exposed as a class method so that it can be used elsewhere def self.canonical_cookbook_name(entry_name) name_match = Chef::ChefFS::FileSystem::ChefServer::VersionedCookbookDir::VALID_VERSIONED_COOKBOOK_NAME.match(entry_name) return nil if name_match.nil? return name_match[1] end def canonical_cookbook_name(entry_name) self.class.canonical_cookbook_name(entry_name) end def uploaded_cookbook_version_path File.join(file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE) end def can_upload? File.exists?(uploaded_cookbook_version_path) || children.size > 0 end protected def make_child_entry(child_name) segment_info = Chef::ChefFS::FileSystem::ChefServer::CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {} ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive]) end def cookbook_version loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore) loader.load_cookbooks loader.cookbook_version end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry.rb000066400000000000000000000130771276456504500337550ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/file_system_entry" require "chef/chef_fs/file_system/repository/cookbooks_dir" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module Repository # NB: unlike most other things in chef_fs/file_system/repository, this # class represents both files and directories, so it needs to have the # methods/if branches for each. class ChefRepositoryFileSystemCookbookEntry attr_reader :name attr_reader :parent attr_reader :path attr_reader :ruby_only attr_reader :recursive attr_reader :file_path alias_method :display_path, :path alias_method :display_name, :name alias_method :bare_name, :name def initialize(name, parent, file_path = nil, ruby_only = false, recursive = false) @parent = parent @name = name @path = Chef::ChefFS::PathUtils.join(parent.path, name) @ruby_only = ruby_only @recursive = recursive @data_handler = nil @file_path = file_path || "#{parent.file_path}/#{name}" end def children begin entries = Dir.entries(file_path).sort. map { |child_name| make_child_entry(child_name) }. select { |child| child && can_have_child?(child.name, child.dir?) } entries.select { |entry| !(entry.dir? && entry.children.size == 0 ) } rescue Errno::ENOENT raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end end def can_have_child?(name, is_dir) if is_dir return recursive && name != "." && name != ".." elsif ruby_only return false if name[-3..-1] != ".rb" end # Check chefignore ignorer = parent loop do if ignorer.is_a?(CookbooksDir) # Grab the path from entry to child path_to_child = name child = self while child.parent != ignorer path_to_child = PathUtils.join(child.name, path_to_child) child = child.parent end # Check whether that relative path is ignored return !ignorer.chefignore || !ignorer.chefignore.ignored?(path_to_child) end ignorer = ignorer.parent break unless ignorer end true end def write_pretty_json false end def path_for_printing file_path end def create_child(child_name, file_contents = nil) child = make_child_entry(child_name) if child.exists? raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child) end if file_contents child.write(file_contents) else begin Dir.mkdir(child.file_path) rescue Errno::EEXIST raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child) end end child end def dir? File.directory?(file_path) end def delete(recurse) FileSystemCache.instance.delete!(file_path) begin if dir? if !recurse raise MustDeleteRecursivelyError.new(self, $!) end FileUtils.rm_r(file_path) else File.delete(file_path) end rescue Errno::ENOENT raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end end def exists? File.exists?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?)) end def read begin File.open(file_path, "rb") { |f| f.read } rescue Errno::ENOENT raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end end def write(content) File.open(file_path, "wb") do |file| file.write(content) end end def child(name) if can_have_child?(name, true) || can_have_child?(name, false) result = make_child_entry(name) end result || NonexistentFSObject.new(name, self) end def root parent.root end def compare_to(other) nil end protected def make_child_entry(child_name) ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb000066400000000000000000000210121276456504500325330ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_dir" require "chef/chef_fs/file_system/repository/acls_dir" require "chef/chef_fs/file_system/repository/clients_dir" require "chef/chef_fs/file_system/repository/cookbooks_dir" require "chef/chef_fs/file_system/repository/cookbook_artifacts_dir" require "chef/chef_fs/file_system/repository/containers_dir" require "chef/chef_fs/file_system/repository/data_bags_dir" require "chef/chef_fs/file_system/repository/environments_dir" require "chef/chef_fs/file_system/repository/groups_dir" require "chef/chef_fs/file_system/repository/nodes_dir" require "chef/chef_fs/file_system/repository/policy_groups_dir" require "chef/chef_fs/file_system/repository/roles_dir" require "chef/chef_fs/file_system/repository/users_dir" require "chef/chef_fs/file_system/repository/client_keys_dir" require "chef/chef_fs/file_system/repository/file_system_entry" require "chef/chef_fs/file_system/repository/policies_dir" require "chef/chef_fs/file_system/repository/versioned_cookbooks_dir" require "chef/chef_fs/file_system/multiplexed_dir" require "chef/chef_fs/data_handler/client_data_handler" require "chef/chef_fs/data_handler/client_key_data_handler" require "chef/chef_fs/data_handler/environment_data_handler" require "chef/chef_fs/data_handler/node_data_handler" require "chef/chef_fs/data_handler/policy_data_handler" require "chef/chef_fs/data_handler/policy_group_data_handler" require "chef/chef_fs/data_handler/role_data_handler" require "chef/chef_fs/data_handler/user_data_handler" require "chef/chef_fs/data_handler/group_data_handler" require "chef/chef_fs/data_handler/container_data_handler" class Chef module ChefFS module FileSystem module Repository # # Represents the root of a local Chef repository, with directories for # nodes, cookbooks, roles, etc. under it. # class ChefRepositoryFileSystemRootDir < BaseFSDir # # Create a new Chef Repository File System root. # # == Parameters # [child_paths] # A hash of child paths, e.g.: # "nodes" => [ '/var/nodes', '/home/jkeiser/nodes' ], # "roles" => [ '/var/roles' ], # ... # [root_paths] # An array of paths representing the top level, where # +org.json+, +members.json+, and +invites.json+ will be stored. # [chef_config] - a hash of options that looks suspiciously like the ones # stored in Chef::Config, containing at least these keys: # :versioned_cookbooks:: whether to include versions in cookbook names def initialize(child_paths, root_paths = [], chef_config = Chef::Config) super("", nil) @child_paths = child_paths @root_paths = root_paths @versioned_cookbooks = chef_config[:versioned_cookbooks] end attr_accessor :write_pretty_json attr_reader :root_paths attr_reader :child_paths attr_reader :versioned_cookbooks CHILDREN = %w{org.json invitations.json members.json} def children @children ||= begin result = child_paths.keys.sort.map { |name| make_child_entry(name) } result += CHILDREN.map { |name| make_child_entry(name) } result.select { |c| c && c.exists? }.sort_by { |c| c.name } end end def can_have_child?(name, is_dir) if is_dir child_paths.has_key?(name) elsif root_dir CHILDREN.include?(name) else false end end def create_child(name, file_contents = nil) if file_contents child = root_dir.create_child(name, file_contents) else child_paths[name].each do |path| begin Dir.mkdir(path) rescue Errno::EEXIST end end child = make_child_entry(name) end @children = nil child end def json_class nil end # Used to print out a human-readable file system description def fs_description repo_paths = root_paths || [ File.dirname(child_paths["cookbooks"][0]) ] result = "repository at #{repo_paths.join(', ')}\n" if versioned_cookbooks result << " Multiple versions per cookbook\n" else result << " One version per cookbook\n" end child_paths.each_pair do |name, paths| if paths.any? { |path| !repo_paths.include?(File.dirname(path)) } result << " #{name} at #{paths.join(', ')}\n" end end result end private # # A FileSystemEntry representing the root path where invites.json, # members.json and org.json may be found. # def root_dir existing_paths = root_paths.select { |path| File.exists?(path) } if existing_paths.size > 0 MultiplexedDir.new(existing_paths.map do |path| dir = FileSystemEntry.new(name, parent, path) dir.write_pretty_json = !!write_pretty_json dir end) end end # # Create a child entry of the appropriate type: # cookbooks, data_bags, acls, etc. All will be multiplexed (i.e. if # you have multiple paths for cookbooks, the multiplexed dir will grab # cookbooks from all of them when you list or grab them). # def make_child_entry(name) if CHILDREN.include?(name) return nil if !root_dir return root_dir.child(name) end paths = (child_paths[name] || []).select { |path| File.exists?(path) } if paths.size == 0 return NonexistentFSObject.new(name, self) end case name when "acls" dirs = paths.map { |path| AclsDir.new(name, self, path) } when "client_keys" dirs = paths.map { |path| ClientKeysDir.new(name, self, path) } when "clients" dirs = paths.map { |path| ClientsDir.new(name, self, path) } when "containers" dirs = paths.map { |path| ContainersDir.new(name, self, path) } when "cookbooks" if versioned_cookbooks dirs = paths.map { |path| VersionedCookbooksDir.new(name, self, path) } else dirs = paths.map { |path| CookbooksDir.new(name, self, path) } end when "cookbook_artifacts" dirs = paths.map { |path| CookbookArtifactsDir.new(name, self, path) } when "data_bags" dirs = paths.map { |path| DataBagsDir.new(name, self, path) } when "environments" dirs = paths.map { |path| EnvironmentsDir.new(name, self, path) } when "groups" dirs = paths.map { |path| GroupsDir.new(name, self, path) } when "nodes" dirs = paths.map { |path| NodesDir.new(name, self, path) } when "policy_groups" dirs = paths.map { |path| PolicyGroupsDir.new(name, self, path) } when "policies" dirs = paths.map { |path| PoliciesDir.new(name, self, path) } when "roles" dirs = paths.map { |path| RolesDir.new(name, self, path) } when "users" dirs = paths.map { |path| UsersDir.new(name, self, path) } else raise "Unknown top level path #{name}" end MultiplexedDir.new(dirs) end end end end end end chef_repository_file_system_versioned_cookbook_dir.rb000066400000000000000000000033531276456504500353650ustar00rootroot00000000000000chef-12.14.60/lib/chef/chef_fs/file_system/repository# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir" class Chef module ChefFS module FileSystem module Repository class ChefRepositoryFileSystemVersionedCookbookDir < ChefRepositoryFileSystemCookbookDir # Override from parent def cookbook_version loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore) # We need the canonical cookbook name if we are using versioned cookbooks, but we don't # want to spend a lot of time adding code to the main Chef libraries canonical_name = canonical_cookbook_name(File.basename(file_path)) raise "When versioned_cookbooks mode is on, cookbook #{file_path} must match format -x.y.z" unless canonical_name # KLUDGE: We shouldn't have to use instance_variable_set loader.instance_variable_set(:@cookbook_name, canonical_name) loader.load_cookbooks loader.cookbook_version end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/client.rb000066400000000000000000000021031276456504500243010ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/client_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class Client < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::ClientDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/client_key.rb000066400000000000000000000021151276456504500251540ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/client_key_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class ClientKey < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::ClientKeyDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/client_keys_dir.rb000066400000000000000000000023641276456504500262030ustar00rootroot00000000000000# # Author:: Jordan Running () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/client_keys_sub_dir" require "chef/chef_fs/data_handler/client_key_data_handler" require "chef/chef_fs/file_system/repository/directory" class Chef module ChefFS module FileSystem module Repository class ClientKeysDir < Repository::Directory def can_have_child?(name, is_dir) is_dir && !name.start_with?(".") end protected def make_child_entry(child_name) ClientKeysSubDir.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/client_keys_sub_dir.rb000066400000000000000000000021771276456504500270560ustar00rootroot00000000000000# # Author:: Jordan Running () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/client_key" require "chef/chef_fs/data_handler/client_key_data_handler" require "chef/chef_fs/file_system/repository/directory" class Chef module ChefFS module FileSystem module Repository class ClientKeysSubDir < Repository::Directory protected def make_child_entry(child_name) ClientKey.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/clients_dir.rb000066400000000000000000000021741276456504500253320ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # 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 "chef/chef_fs/file_system/repository/client" require "chef/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module Repository class ClientsDir < Repository::Directory def make_child_entry(child_name) Client.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/container.rb000066400000000000000000000021141276456504500250070ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/container_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class Container < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::ContainerDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/containers_dir.rb000066400000000000000000000022051276456504500260310ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # 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 "chef/chef_fs/file_system/repository/container" require "chef/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module Repository class ContainersDir < Repository::Directory def make_child_entry(child_name) Container.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/cookbook_artifacts_dir.rb000066400000000000000000000022451276456504500275360ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/cookbooks_dir" require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir" class Chef module ChefFS module FileSystem module Repository # Represents ROOT/cookbook_artifacts class CookbookArtifactsDir < CookbooksDir def make_child_entry(child_name) ChefRepositoryFileSystemCookbookArtifactDir.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/cookbooks_dir.rb000066400000000000000000000032011276456504500256520ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir" require "chef/cookbook/chefignore" class Chef module ChefFS module FileSystem module Repository class CookbooksDir < Repository::Directory def chefignore @chefignore ||= Chef::Cookbook::Chefignore.new(self.file_path) rescue Errno::EISDIR, Errno::EACCES # Work around a bug in Chefignore when chefignore is a directory end def write_cookbook(cookbook_path, cookbook_version_json, from_fs) cookbook_name = File.basename(cookbook_path) make_child_entry(cookbook_name).write(cookbook_path, cookbook_version_json, from_fs) end protected def make_child_entry(child_name) ChefRepositoryFileSystemCookbookDir.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/data_bag.rb000066400000000000000000000022171276456504500245530ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/repository/data_bag_item" class Chef module ChefFS module FileSystem module Repository # Represents REPO_ROOT/data_bags/data_bag Children of this are data bag # items. class DataBag < Repository::Directory def make_child_entry(child_name) DataBagItem.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/data_bag_item.rb000066400000000000000000000021321276456504500255650ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/data_handler/data_bag_item_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class DataBagItem < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::DataBagItemDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/data_bags_dir.rb000066400000000000000000000022131276456504500255700ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/repository/data_bag" class Chef module ChefFS module FileSystem module Repository # Represents the REPO_ROOT/data_bags directory. Children of this are # data bags. class DataBagsDir < Repository::Directory def make_child_entry(child_name) DataBag.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/directory.rb000066400000000000000000000120501276456504500250310ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system_cache" class Chef module ChefFS module FileSystem module Repository class Directory attr_reader :name attr_reader :parent attr_reader :path attr_reader :file_path alias_method :display_path, :path alias_method :display_name, :name alias_method :bare_name, :name def initialize(name, parent, file_path = nil) @parent = parent @name = name @path = Chef::ChefFS::PathUtils.join(parent.path, name) @file_path = file_path || "#{parent.file_path}/#{name}" end def name_valid? !name.start_with?(".") end # Whether or not the file system entry this object represents is # valid. Mainly used to trim dotfiles/dotdirs and non directories # from the list of children when enumerating items on the filesystem def fs_entry_valid? name_valid? && File.directory?(file_path) end # ChefFS API: # Public API called by multiplexed_dir def can_have_child?(name, is_dir) possible_child = make_child_entry(name) possible_child.dir? == is_dir && possible_child.name_valid? end # Public API called by chef_fs/file_system def dir? true end def path_for_printing file_path end def children return FileSystemCache.instance.children(file_path) if FileSystemCache.instance.exist?(file_path) children = dir_ls.sort. map { |child_name| make_child_entry(child_name) }. select { |new_child| new_child.fs_entry_valid? && can_have_child?(new_child.name, new_child.dir?) } FileSystemCache.instance.set_children(file_path, children) rescue Errno::ENOENT => e raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e) end def create_child(child_name, file_contents = nil) child = make_child_entry(child_name) if child.exists? raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child) end FileSystemCache.instance.delete!(child.file_path) if file_contents child.write(file_contents) else begin Dir.mkdir(child.file_path) rescue Errno::EEXIST raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child) end end child end # An empty children array is an empty dir def empty? children.empty? end # Public API callied by chef_fs/file_system def child(name) possible_child = make_child_entry(name) if possible_child.name_valid? possible_child else NonexistentFSObject.new(name, self) end end def root parent.root end # File system wrappers def create(file_contents = nil) if exists? raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self) end begin FileSystemCache.instance.delete!(file_path) Dir.mkdir(file_path) rescue Errno::EEXIST raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self) end end def dir_ls Dir.entries(file_path).select { |p| !p.start_with?(".") } end def delete(recurse) if exists? if !recurse raise MustDeleteRecursivelyError.new(self, $!) end FileUtils.rm_r(file_path) FileSystemCache.instance.delete!(file_path) else raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end end def exists? File.exists?(file_path) end protected def write(data) raise FileSystemError.new(self, nil, "attempted to write to a directory entry") end def make_child_entry(child_name) raise "Not Implemented" end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/environment.rb000066400000000000000000000021221276456504500253700ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/environment_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class Environment < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::EnvironmentDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/environments_dir.rb000066400000000000000000000022321276456504500264130ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # 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 "chef/chef_fs/file_system/repository/environment" require "chef/chef_fs/data_handler/environment_data_handler" require "chef/chef_fs/file_system/repository/directory" class Chef module ChefFS module FileSystem module Repository class EnvironmentsDir < Repository::Directory def make_child_entry(child_name) Environment.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/file_system_entry.rb000066400000000000000000000113111276456504500265700ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system/base_fs_dir" require "chef/chef_fs/file_system/chef_server/rest_list_dir" require "chef/chef_fs/file_system/exceptions" require "chef/chef_fs/path_utils" require "fileutils" class Chef module ChefFS module FileSystem module Repository class FileSystemEntry < BaseFSDir def initialize(name, parent, file_path = nil, data_handler = nil) super(name, parent) @file_path = file_path || "#{parent.file_path}/#{name}" @data_handler = data_handler end attr_reader :file_path def write_pretty_json=(value) @write_pretty_json = value end def write_pretty_json @write_pretty_json.nil? ? root.write_pretty_json : @write_pretty_json end def data_handler @data_handler || parent.data_handler end def path_for_printing file_path end def chef_object data_handler.chef_object(Chef::JSONCompat.parse(read)) rescue Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}") nil end def can_have_child?(name, is_dir) !is_dir && File.extname(name) == ".json" end def name_valid? !name.start_with?(".") end # basic implementation to support Repository::Directory API def fs_entry_valid? name_valid? && File.exist?(file_path) end def minimize(file_contents, entry) object = Chef::JSONCompat.parse(file_contents) object = data_handler.normalize(object, entry) object = data_handler.minimize(object, entry) Chef::JSONCompat.to_json_pretty(object) end def children # Except cookbooks and data bag dirs, all things must be json files Dir.entries(file_path).sort. map { |child_name| make_child_entry(child_name) }. select { |new_child| new_child.fs_entry_valid? && can_have_child?(new_child.name, new_child.dir?) } rescue Errno::ENOENT raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end def create_child(child_name, file_contents = nil) child = make_child_entry(child_name) if child.exists? raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child) end if file_contents child.write(file_contents) else Dir.mkdir(child.file_path) end child rescue Errno::EEXIST raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child) end def dir? File.directory?(file_path) end def delete(recurse) if dir? if !recurse raise MustDeleteRecursivelyError.new(self, $!) end FileUtils.rm_r(file_path) else File.delete(file_path) end rescue Errno::ENOENT raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end def exists? File.exists?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?)) end def read File.open(file_path, "rb") { |f| f.read } rescue Errno::ENOENT raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!) end def write(file_contents) if file_contents && write_pretty_json && File.extname(name) == ".json" file_contents = minimize(file_contents, self) end File.open(file_path, "wb") do |file| file.write(file_contents) end end alias :create :write protected def make_child_entry(child_name) FileSystemEntry.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/group.rb000066400000000000000000000021001276456504500241540ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/group_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class Group < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::GroupDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/groups_dir.rb000066400000000000000000000021711276456504500252050ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # 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 "chef/chef_fs/file_system/repository/group" require "chef/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module Repository class GroupsDir < Repository::Directory def make_child_entry(child_name) Group.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/node.rb000066400000000000000000000020751276456504500237600ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/node_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class Node < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::NodeDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/nodes_dir.rb000066400000000000000000000021661276456504500250020ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # 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 "chef/chef_fs/file_system/repository/node" require "chef/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module Repository class NodesDir < Repository::Directory def make_child_entry(child_name) Node.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/policies_dir.rb000066400000000000000000000023261276456504500254770ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/policy" require "chef/chef_fs/file_system/repository/directory" require "chef/chef_fs/data_handler/policy_data_handler" class Chef module ChefFS module FileSystem module Repository class PoliciesDir < Repository::Directory def can_have_child?(name, is_dir) !is_dir && name.include?("-") end protected def make_child_entry(child_name) Policy.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/policy.rb000066400000000000000000000021031276456504500243220ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/policy_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class Policy < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::PolicyDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/policy_group.rb000066400000000000000000000021231276456504500255400ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/policy_group_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class PolicyGroup < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::PolicyGroupDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/policy_groups_dir.rb000066400000000000000000000022141276456504500265620ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # 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 "chef/chef_fs/file_system/repository/policy_group" require "chef/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module Repository class PolicyGroupsDir < Repository::Directory def make_child_entry(child_name) PolicyGroup.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/role.rb000066400000000000000000000020751276456504500237740ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/role_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class Role < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::RoleDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/roles_dir.rb000066400000000000000000000021661276456504500250160ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # 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 "chef/chef_fs/file_system/repository/role" require "chef/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module Repository class RolesDir < Repository::Directory def make_child_entry(child_name) Role.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/user.rb000066400000000000000000000020751276456504500240110ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2013-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/chef_fs/data_handler/user_data_handler" require "chef/chef_fs/file_system/repository/base_file" class Chef module ChefFS module FileSystem module Repository class User < BaseFile def initialize(name, parent) @data_handler = Chef::ChefFS::DataHandler::UserDataHandler.new super end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/users_dir.rb000066400000000000000000000021661276456504500250330ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # 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 "chef/chef_fs/file_system/repository/user" require "chef/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/exceptions" class Chef module ChefFS module FileSystem module Repository class UsersDir < Repository::Directory def make_child_entry(child_name) User.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system/repository/versioned_cookbooks_dir.rb000066400000000000000000000021721276456504500277360ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/chef_fs/file_system/repository/cookbooks_dir" require "chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbook_dir" class Chef module ChefFS module FileSystem module Repository class VersionedCookbooksDir < CookbooksDir def make_child_entry(child_name) ChefRepositoryFileSystemVersionedCookbookDir.new(child_name, self) end end end end end end chef-12.14.60/lib/chef/chef_fs/file_system_cache.rb000066400000000000000000000035371276456504500217430ustar00rootroot00000000000000# # 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 "singleton" require "chef/client" class Chef module ChefFS class FileSystemCache include Singleton def initialize @cache = {} Chef::Client.when_run_starts do FileSystemCache.instance.reset! end end def reset! @cache = {} end def exist?(path) @cache.key?(path) end def children(path) @cache[path]["children"] end def set_children(path, val) @cache[path] ||= { "children" => [] } @cache[path]["children"] = val val end def delete!(path) parent = _get_parent(path) Chef::Log.debug("Deleting parent #{parent} and #{path} from FileSystemCache") if @cache.key?(path) @cache.delete(path) end if !parent.nil? && @cache.key?(parent) @cache.delete(parent) end end def fetch(path) if @cache.key?(path) @cache[path] else false end end private def _get_parent(path) parts = ChefFS::PathUtils.split(path) return nil if parts.nil? || parts.length < 2 ChefFS::PathUtils.join(*parts[0..-2]) end end end end chef-12.14.60/lib/chef/chef_fs/knife.rb000066400000000000000000000136121276456504500173640ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/knife" require "pathname" class Chef module ChefFS class Knife < Chef::Knife # Workaround for CHEF-3932 def self.deps super do require "chef/config" require "chef/chef_fs/parallelizer" require "chef/chef_fs/config" require "chef/chef_fs/file_pattern" require "chef/chef_fs/path_utils" yield end end def self.inherited(c) super # Ensure we always get to do our includes, whether subclass calls deps or not c.deps do end c.options.merge!(options) end option :repo_mode, :long => "--repo-mode MODE", :description => "Specifies the local repository layout. Values: static, everything, hosted_everything. Default: everything/hosted_everything" option :chef_repo_path, :long => "--chef-repo-path PATH", :description => "Overrides the location of chef repo. Default is specified by chef_repo_path in the config" option :concurrency, :long => "--concurrency THREADS", :description => "Maximum number of simultaneous requests to send (default: 10)" def configure_chef super Chef::Config[:repo_mode] = config[:repo_mode] if config[:repo_mode] Chef::Config[:concurrency] = config[:concurrency].to_i if config[:concurrency] # --chef-repo-path forcibly overrides all other paths if config[:chef_repo_path] Chef::Config[:chef_repo_path] = config[:chef_repo_path] Chef::ChefFS::Config::INFLECTIONS.each_value do |variable_name| Chef::Config.delete("#{variable_name}_path".to_sym) end end @chef_fs_config = Chef::ChefFS::Config.new(Chef::Config, Dir.pwd, config, ui) Chef::ChefFS::Parallelizer.threads = (Chef::Config[:concurrency] || 10) - 1 end def chef_fs @chef_fs_config.chef_fs end def create_chef_fs @chef_fs_config.create_chef_fs end def local_fs @chef_fs_config.local_fs end def create_local_fs @chef_fs_config.create_local_fs end def pattern_args @pattern_args ||= pattern_args_from(name_args) end def pattern_args_from(args) args.map { |arg| pattern_arg_from(arg) } end def pattern_arg_from(arg) inferred_path = nil if Chef::ChefFS::PathUtils.is_absolute?(arg) # We should be able to use this as-is - but the user might have incorrectly provided # us with a path that is based off of the OS root path instead of the Chef-FS root. # Do a quick and dirty sanity check. if possible_server_path = @chef_fs_config.server_path(arg) ui.warn("The absolute path provided is suspicious: #{arg}") ui.warn("If you wish to refer to a file location, please provide a path that is rooted at the chef-repo.") ui.warn("Consider writing '#{possible_server_path}' instead of '#{arg}'") end # Use the original path because we can't be sure. inferred_path = arg elsif arg[0, 1] == "~" # Let's be nice and fix it if possible - but warn the user. ui.warn("A path relative to a user home directory has been provided: #{arg}") ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.") inferred_path = @chef_fs_config.server_path(arg) ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.") elsif Pathname.new(arg).absolute? # It is definitely a system absolute path (such as C:\ or \\foo\bar) but it cannot be # interpreted as a Chef-FS absolute path. Again attempt to be nice but warn the user. ui.warn("An absolute file system path that isn't a server path was provided: #{arg}") ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.") inferred_path = @chef_fs_config.server_path(arg) ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.") elsif @chef_fs_config.base_path.nil? # These are all relative paths. We can't resolve and root paths unless we are in the # chef repo. ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path.") ui.error("Current working directory is '#{@chef_fs_config.cwd}'.") exit(1) else inferred_path = Chef::ChefFS::PathUtils.join(@chef_fs_config.base_path, arg) end Chef::ChefFS::FilePattern.new(inferred_path) end def format_path(entry) @chef_fs_config.format_path(entry) end def parallelize(inputs, options = {}, &block) Chef::ChefFS::Parallelizer.parallelize(inputs, options, &block) end def discover_repo_dir(dir) %w{.chef cookbooks data_bags environments roles}.each do |subdir| return dir if File.directory?(File.join(dir, subdir)) end # If this isn't it, check the parent parent = File.dirname(dir) if parent && parent != dir discover_repo_dir(parent) else nil end end end end end chef-12.14.60/lib/chef/chef_fs/parallelizer.rb000066400000000000000000000052701276456504500207570ustar00rootroot00000000000000require "thread" require "chef/chef_fs/parallelizer/parallel_enumerable" class Chef module ChefFS # Tries to balance several guarantees, in order of priority: # - don't get deadlocked # - provide results in desired order # - provide results as soon as they are available # - process input as soon as possible class Parallelizer @@parallelizer = nil @@threads = 0 def self.threads=(value) @@threads = value @@parallelizer.resize(value) if @@parallelizer end def self.parallelizer @@parallelizer ||= Parallelizer.new(@@threads) end def self.parallelize(enumerable, options = {}, &block) parallelizer.parallelize(enumerable, options, &block) end def self.parallel_do(enumerable, options = {}, &block) parallelizer.parallel_do(enumerable, options, &block) end def initialize(num_threads) @tasks = Queue.new @threads = [] @stop_thread = {} resize(num_threads) end def num_threads @threads.size end def parallelize(enumerable, options = {}, &block) ParallelEnumerable.new(@tasks, enumerable, options, &block) end def parallel_do(enumerable, options = {}, &block) ParallelEnumerable.new(@tasks, enumerable, options.merge(:ordered => false), &block).wait end def stop(wait = true, timeout = nil) resize(0, wait, timeout) end def resize(to_threads, wait = true, timeout = nil) if to_threads < num_threads threads_to_stop = @threads[to_threads..num_threads - 1] @threads = @threads.slice(0, to_threads) threads_to_stop.each do |thread| @stop_thread[thread] = true end if wait start_time = Time.now threads_to_stop.each do |thread| thread_timeout = timeout ? timeout - (Time.now - start_time) : nil thread.join(thread_timeout) end end else num_threads.upto(to_threads - 1) do |i| @threads[i] = Thread.new(&method(:worker_loop)) end end end def kill @threads.each do |thread| Thread.kill(thread) @stop_thread.delete(thread) end @threads = [] end private def worker_loop begin until @stop_thread[Thread.current] begin task = @tasks.pop task.call rescue puts "ERROR #{$!}" puts $!.backtrace end end ensure @stop_thread.delete(Thread.current) end end end end end chef-12.14.60/lib/chef/chef_fs/parallelizer/000077500000000000000000000000001276456504500204265ustar00rootroot00000000000000chef-12.14.60/lib/chef/chef_fs/parallelizer/flatten_enumerable.rb000066400000000000000000000013541276456504500246120ustar00rootroot00000000000000class Chef module ChefFS class Parallelizer class FlattenEnumerable include Enumerable def initialize(enum, levels = nil) @enum = enum @levels = levels end attr_reader :enum attr_reader :levels def each(&block) enum.each do |value| flatten(value, levels, &block) end end private def flatten(value, levels, &block) if levels != 0 && value.respond_to?(:each) && !value.is_a?(String) value.each do |child| flatten(child, levels.nil? ? levels : levels - 1, &block) end else yield(value) end end end end end end chef-12.14.60/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb000066400000000000000000000220331276456504500247460ustar00rootroot00000000000000require "chef/chef_fs/parallelizer/flatten_enumerable" class Chef module ChefFS class Parallelizer class ParallelEnumerable include Enumerable # options: # :ordered [true|false] - whether the output should stay in the same order # as the input (even though it may not actually be processed in that # order). Default: true # :stop_on_exception [true|false] - if true, when an exception occurs in either # input or output, we wait for any outstanding processing to complete, # but will not process any new inputs. Default: false # :main_thread_processing [true|false] - whether the main thread pulling # on each() is allowed to process inputs. Default: true # NOTE: If you set this to false, parallelizer.kill will stop each() # in its tracks, so you need to know for sure that won't happen. def initialize(parent_task_queue, input_enumerable, options = {}, &block) @parent_task_queue = parent_task_queue @input_enumerable = input_enumerable @options = options @block = block @unconsumed_input = Queue.new @in_process = {} @unconsumed_output = Queue.new end attr_reader :parent_task_queue attr_reader :input_enumerable attr_reader :options attr_reader :block def each each_with_input do |output, index, input, type| yield output end end def each_with_index each_with_input do |output, index, input| yield output, index end end def each_with_input exception = nil each_with_exceptions do |output, index, input, type| if type == :exception if @options[:ordered] == false exception ||= output else raise output end else yield output, index, input end end raise exception if exception end def each_with_exceptions(&block) if @options[:ordered] == false each_with_exceptions_unordered(&block) else each_with_exceptions_ordered(&block) end end def wait exception = nil each_with_exceptions_unordered do |output, index, input, type| exception ||= output if type == :exception end raise exception if exception end # Enumerable methods def restricted_copy(enumerable) ParallelEnumerable.new(@parent_task_queue, enumerable, @options, &@block) end alias :original_count :count def count(*args, &block) if args.size == 0 && block.nil? @input_enumerable.count else original_count(*args, &block) end end def first(n = nil) if n restricted_copy(@input_enumerable.first(n)).to_a else first(1)[0] end end def drop(n) restricted_copy(@input_enumerable.drop(n)).to_a end def flatten(levels = nil) FlattenEnumerable.new(self, levels) end def take(n) restricted_copy(@input_enumerable.take(n)).to_a end if Enumerable.method_defined?(:lazy) class RestrictedLazy def initialize(parallel_enumerable, actual_lazy) @parallel_enumerable = parallel_enumerable @actual_lazy = actual_lazy end def drop(*args, &block) input = @parallel_enumerable.input_enumerable.lazy.drop(*args, &block) @parallel_enumerable.restricted_copy(input) end def take(*args, &block) input = @parallel_enumerable.input_enumerable.lazy.take(*args, &block) @parallel_enumerable.restricted_copy(input) end def method_missing(method, *args, &block) @actual_lazy.send(:method, *args, &block) end end alias :original_lazy :lazy def lazy RestrictedLazy.new(self, original_lazy) end end private def each_with_exceptions_unordered if @each_running raise "each() called on parallel enumerable twice simultaneously! Bad mojo" end @each_running = true begin # Grab all the inputs, yielding any responses during enumeration # in case the enumeration itself takes time begin @input_enumerable.each_with_index do |input, index| @unconsumed_input.push([ input, index ]) @parent_task_queue.push(method(:process_one)) stop_processing_input = false until @unconsumed_output.empty? output, index, input, type = @unconsumed_output.pop yield output, index, input, type if type == :exception && @options[:stop_on_exception] stop_processing_input = true break end end if stop_processing_input break end end rescue # We still want to wait for the rest of the outputs to process @unconsumed_output.push([$!, nil, nil, :exception]) if @options[:stop_on_exception] @unconsumed_input.clear end end until finished? # yield thread to others (for 1.8.7) if @unconsumed_output.empty? sleep(0.01) end yield @unconsumed_output.pop until @unconsumed_output.empty? # If no one is working on our tasks and we're allowed to # work on them in the main thread, process an input to # move things forward. if @in_process.size == 0 && !(@options[:main_thread_processing] == false) process_one end end rescue # If we exited early, perhaps due to any? finding a result, we want # to make sure and throw away any extra results (gracefully) so that # the next enumerator can start over. if !finished? stop end raise ensure @each_running = false end end def each_with_exceptions_ordered next_to_yield = 0 unconsumed = {} each_with_exceptions_unordered do |output, index, input, type| unconsumed[index] = [ output, input, type ] while unconsumed[next_to_yield] input_output = unconsumed.delete(next_to_yield) yield input_output[0], next_to_yield, input_output[1], input_output[2] next_to_yield += 1 end end input_exception = unconsumed.delete(nil) if input_exception yield input_exception[0], next_to_yield, input_exception[1], input_exception[2] end end def stop @unconsumed_input.clear sleep(0.05) while @in_process.size > 0 @unconsumed_output.clear end # # This is thread safe only if called from the main thread pulling on each(). # The order of these checks is important, as well, to be thread safe. # 1. If @unconsumed_input.empty? is true, then we will never have any more # work legitimately picked up. # 2. If @in_process == 0, then there is no work in process, and because ofwhen unconsumed_input is empty, it will never go back up, because # this is called after the input enumerator is finished. Note that switching #2 and #1 # could cause a race, because in_process is incremented *before* consuming input. # 3. If @unconsumed_output.empty? is true, then we are done with outputs. # Thus, 1+2 means no more output will ever show up, and 3 means we've passed all # existing outputs to the user. # def finished? @unconsumed_input.empty? && @in_process.size == 0 && @unconsumed_output.empty? end def process_one @in_process[Thread.current] = true begin begin input, index = @unconsumed_input.pop(true) process_input(input, index) rescue ThreadError end ensure @in_process.delete(Thread.current) end end def process_input(input, index) begin output = @block.call(input) @unconsumed_output.push([ output, index, input, :result ]) rescue StandardError, ScriptError if @options[:stop_on_exception] @unconsumed_input.clear end @unconsumed_output.push([ $!, index, input, :exception ]) end index end end end end end chef-12.14.60/lib/chef/chef_fs/path_utils.rb000066400000000000000000000126361276456504500204510ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs" require "pathname" class Chef module ChefFS class PathUtils # A Chef-FS path is a path in a chef-repository that can be used to address # both files on a local file-system as well as objects on a chef server. # These paths are stricter than file-system paths allowed on various OSes. # Absolute Chef-FS paths begin with "/" (on windows, "\" is acceptable as well). # "/" is used as the path element separator (on windows, "\" is acceptable as well). # No directory/path element may contain a literal "\" character. Any such characters # encountered are either dealt with as separators (on windows) or as escape # characters (on POSIX systems). Relative Chef-FS paths may use ".." or "." but # may never use these to back-out of the root of a Chef-FS path. Any such extraneous # ".."s are ignored. # Chef-FS paths are case sensitive (since the paths on the server are). # On OSes with case insensitive paths, you may be unable to locally deal with two # objects whose server paths only differ by case. OTOH, the case of path segments # that are outside the Chef-FS root (such as when looking at a file-system absolute # path to discover the Chef-FS root path) are handled in accordance to the rules # of the local file-system and OS. def self.join(*parts) return "" if parts.length == 0 # Determine if it started with a slash absolute = parts[0].length == 0 || parts[0].length > 0 && parts[0] =~ /^#{regexp_path_separator}/ # Remove leading and trailing slashes from each part so that the join will work (and the slash at the end will go away) parts = parts.map { |part| part.gsub(/^#{regexp_path_separator}+|#{regexp_path_separator}+$/, "") } # Don't join empty bits result = parts.select { |part| part != "" }.join("/") # Put the / back on absolute ? "/#{result}" : result end def self.split(path) path.split(Regexp.new(regexp_path_separator)) end def self.regexp_path_separator Chef::ChefFS.windows? ? '[\/\\\\]' : "/" end # Given a server path, determines if it is absolute. def self.is_absolute?(path) !!(path =~ /^#{regexp_path_separator}/) end # Given a path which may only be partly real (i.e. /x/y/z when only /x exists, # or /x/y/*/blah when /x/y/z/blah exists), call File.realpath on the biggest # part that actually exists. The paths operated on here are not Chef-FS paths. # These are OS paths that may contain symlinks but may not also fully exist. # # If /x is a symlink to /blarghle, and has no subdirectories, then: # PathUtils.realest_path('/x/y/z') == '/blarghle/y/z' # PathUtils.realest_path('/x/*/z') == '/blarghle/*/z' # PathUtils.realest_path('/*/y/z') == '/*/y/z' # # TODO: Move this to wherever util/path_helper is these days. def self.realest_path(path, cwd = Dir.pwd) path = File.expand_path(path, cwd) parent_path = File.dirname(path) suffix = [] # File.dirname happens to return the path as its own dirname if you're # at the root (such as at \\foo\bar, C:\ or /) until parent_path == path # This can occur if a path such as "C:" is given. Ruby gives the parent as "C:." # for reasons only it knows. raise ArgumentError "Invalid path segment #{path}" if parent_path.length > path.length begin path = File.realpath(path) break rescue Errno::ENOENT suffix << File.basename(path) path = parent_path parent_path = File.dirname(path) end end File.join(path, *suffix.reverse) end # Compares two path fragments according to the case-sentitivity of the host platform. def self.os_path_eq?(left, right) Chef::ChefFS.windows? ? left.casecmp(right) == 0 : left == right end # Given two general OS-dependent file paths, determines the relative path of the # child with respect to the ancestor. Both child and ancestor must exist and be # fully resolved - this is strictly a lexical comparison. No trailing slashes # and other shenanigans are allowed. # # TODO: Move this to util/path_helper. def self.descendant_path(path, ancestor) candidate_fragment = path[0, ancestor.length] return nil unless PathUtils.os_path_eq?(candidate_fragment, ancestor) if ancestor.length == path.length "" elsif path[ancestor.length, 1] =~ /#{PathUtils.regexp_path_separator}/ path[ancestor.length + 1..-1] else nil end end end end end chef-12.14.60/lib/chef/client.rb000066400000000000000000000727061276456504500161620ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: Christopher Brown () # Author:: Tim Hinderliter () # 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/mixin/params_validate" require "chef/mixin/path_sanity" require "chef/log" require "chef/server_api" require "chef/api_client" require "chef/api_client/registration" require "chef/audit/runner" require "chef/node" require "chef/role" require "chef/file_cache" require "chef/run_context" require "chef/runner" require "chef/run_status" require "chef/cookbook/cookbook_collection" require "chef/cookbook/file_vendor" require "chef/cookbook/file_system_file_vendor" require "chef/cookbook/remote_file_vendor" require "chef/event_dispatch/dispatcher" require "chef/event_loggers/base" require "chef/event_loggers/windows_eventlog" require "chef/exceptions" require "chef/formatters/base" 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" require "chef/request_id" require "chef/platform/rebooter" require "chef/mixin/deprecation" require "ohai" require "rbconfig" class Chef # == Chef::Client # The main object in a Chef run. Preps a Chef::Node and Chef::RunContext, # syncs cookbooks if necessary, and triggers convergence. class Client include Chef::Mixin::PathSanity extend Chef::Mixin::Deprecation # # The status of the Chef run. # # @return [Chef::RunStatus] # attr_reader :run_status # # The node represented by this client. # # @return [Chef::Node] # def node run_status.node end def node=(value) run_status.node = value end # # The ohai system used by this client. # # @return [Ohai::System] # attr_reader :ohai # # The rest object used to communicate with the Chef server. # # @return [Chef::ServerAPI] # attr_reader :rest # # A rest object with validate_utf8 set to false. This will not throw exceptions # on non-UTF8 strings in JSON but will sanitize them so that e.g. POSTs will # never fail. Cannot be configured on a request-by-request basis, so we carry # around another rest object for it. # attr_reader :rest_clean # # The runner used to converge. # # @return [Chef::Runner] # attr_accessor :runner # # Extra node attributes that were applied to the node. # # @return [Hash] # attr_reader :json_attribs # # The event dispatcher for the Chef run, including any configured output # formatters and event loggers. # # @return [EventDispatch::Dispatcher] # # @see Chef::Formatters # @see Chef::Config#formatters # @see Chef::Config#stdout # @see Chef::Config#stderr # @see Chef::Config#force_logger # @see Chef::Config#force_formatter # TODO add stdout, stderr, and default formatters to Chef::Config so the # defaults aren't calculated here. Remove force_logger and force_formatter # from this code. # @see Chef::EventLoggers # @see Chef::Config#disable_event_logger # @see Chef::Config#event_loggers # @see Chef::Config#event_handlers # attr_reader :events # # Creates a new Chef::Client. # # @param json_attribs [Hash] Node attributes to layer into the node when it is # fetched. # @param args [Hash] Options: # @option args [Array] :override_runlist A runlist to # use instead of the node's embedded run list. # @option args [Array] :specific_recipes A list of recipe file paths # to load after the run list has been loaded. # def initialize(json_attribs = nil, args = {}) @json_attribs = json_attribs || {} @ohai = Ohai::System.new event_handlers = configure_formatters + configure_event_loggers event_handlers += Array(Chef::Config[:event_handlers]) @events = EventDispatch::Dispatcher.new(*event_handlers) # TODO it seems like a bad idea to be deletin' other peoples' hashes. @override_runlist = args.delete(:override_runlist) @specific_recipes = args.delete(:specific_recipes) @run_status = Chef::RunStatus.new(nil, events) if new_runlist = args.delete(:runlist) @json_attribs["run_list"] = new_runlist end end # # Do a full run for this Chef::Client. # # Locks the run while doing its job. # # Fires run_start before doing anything and fires run_completed or # run_failed when finished. Also notifies client listeners of run_started # at the beginning of Compile, and run_completed_successfully or run_failed # when all is complete. # # Phase 1: Setup # -------------- # Gets information about the system and the run we are doing. # # 1. Run ohai to collect system information. # 2. Register / connect to the Chef server (unless in solo mode). # 3. Retrieve the node (or create a new one). # 4. Merge in json_attribs, Chef::Config.environment, and override_run_list. # # @see #run_ohai # @see #load_node # @see #build_node # @see Chef::Config#lockfile # @see Chef::RunLock#acquire # # Phase 2: Compile # ---------------- # Decides *what* we plan to converge by compiling recipes. # # 1. Sync required cookbooks to the local cache. # 2. Load libraries from all cookbooks. # 3. Load attributes from all cookbooks. # 4. Load LWRPs from all cookbooks. # 5. Load resource definitions from all cookbooks. # 6. Load recipes in the run list. # 7. Load recipes from the command line. # # @see #setup_run_context Syncs and compiles cookbooks. # @see Chef::CookbookCompiler#compile # # Phase 3: Converge # ----------------- # Brings the system up to date. # # 1. Converge the resources built from recipes in Phase 2. # 2. Save the node. # 3. Reboot if we were asked to. # # @see #converge_and_save # @see Chef::Runner # # Phase 4: Audit # -------------- # Runs 'control_group' audits in recipes. This entire section can be enabled or disabled with config. # # 1. 'control_group' DSL collects audits during Phase 2 # 2. Audits are run using RSpec # 3. Errors are collected and reported using the formatters # # @see #run_audits # @see Chef::Audit::Runner#run # # @raise [Chef::Exceptions::RunFailedWrappingError] If converge or audit failed. # # @see Chef::Config#enforce_path_sanity # @see Chef::Config#solo # @see Chef::Config#audit_mode # # @return Always returns true. # def run start_profiling run_error = nil runlock = RunLock.new(Chef::Config.lockfile) # TODO feels like acquire should have its own block arg for this runlock.acquire # don't add code that may fail before entering this section to be sure to release lock begin runlock.save_pid request_id = Chef::RequestID.instance.request_id run_context = nil events.run_start(Chef::VERSION) Chef::Log.info("*** Chef #{Chef::VERSION} ***") Chef::Log.info("Platform: #{RUBY_PLATFORM}") Chef::Log.info "Chef-client pid: #{Process.pid}" Chef::Log.debug("Chef-client request_id: #{request_id}") enforce_path_sanity run_ohai register unless Chef::Config[:solo_legacy_mode] register_data_collector_reporter load_node build_node run_status.run_id = request_id run_status.start_clock Chef::Log.info("Starting Chef Run for #{node.name}") run_started do_windows_admin_check run_context = setup_run_context if Chef::Config[:audit_mode] != :audit_only converge_error = converge_and_save(run_context) end if Chef::Config[:why_run] == true # why_run should probably be renamed to why_converge Chef::Log.debug("Not running controls in 'why-run' mode - this mode is used to see potential converge changes") elsif Chef::Config[:audit_mode] != :disabled audit_error = run_audits(run_context) end # Raise converge_error so run_failed reporters/events are processed. raise converge_error if converge_error run_status.stop_clock Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds") run_completed_successfully events.run_completed(node) # keep this inside the main loop to get exception backtraces end_profiling # rebooting has to be the last thing we do, no exceptions. Chef::Platform::Rebooter.reboot_if_needed!(node) rescue Exception => run_error # CHEF-3336: Send the error first in case something goes wrong below and we don't know why Chef::Log.debug("Re-raising exception: #{run_error.class} - #{run_error.message}\n#{run_error.backtrace.join("\n ")}") # If we failed really early, we may not have a run_status yet. Too early for these to be of much use. if run_status run_status.stop_clock run_status.exception = run_error run_failed end events.run_failed(run_error) ensure Chef::RequestID.instance.reset_request_id @run_status = nil runlock.release end # Raise audit, converge, and other errors here so that we exit # with the proper exit status code and everything gets raised # as a RunFailedWrappingError if run_error || converge_error || audit_error error = if Chef::Config[:audit_mode] == :disabled run_error || converge_error else e = if run_error == converge_error Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error) else Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error) end e.fill_backtrace e end Chef::Application.debug_stacktrace(error) raise error end true end # # Private API # TODO make this stuff protected or private # # @api private def configure_formatters formatters_for_run.map do |formatter_name, output_path| if output_path.nil? Chef::Formatters.new(formatter_name, STDOUT_FD, STDERR_FD) else io = File.open(output_path, "a+") io.sync = true Chef::Formatters.new(formatter_name, io, io) end end end # @api private def formatters_for_run if Chef::Config.formatters.empty? [default_formatter] else Chef::Config.formatters end end # @api private def default_formatter if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter] [:doc] else [:null] end end # @api private def configure_event_loggers if Chef::Config.disable_event_logger [] else Chef::Config.event_loggers.map do |evt_logger| case evt_logger when Symbol Chef::EventLoggers.new(evt_logger) when Class evt_logger.new else end end end end # Rest client for use by API reporters. This rest client will not fail with an exception if # it is fed non-UTF8 data. # # @api private def rest_clean(client_name = node_name, config = Chef::Config) @rest_clean ||= Chef::ServerAPI.new(config[:chef_server_url], client_name: client_name, signing_key_filename: config[:client_key], validate_utf8: false) end # Resource reporters send event information back to the chef server for # processing. Can only be called after we have a @rest object # @api private def register_reporters [ Chef::ResourceReporter.new(rest_clean), Chef::Audit::AuditReporter.new(rest_clean), ].each do |r| events.register(r) end end # # Callback to fire notifications that the Chef run is starting # # @api private # def run_started self.class.run_start_notifications.each do |notification| notification.call(run_status) end events.run_started(run_status) end # # Callback to fire notifications that the run completed successfully # # @api private # def run_completed_successfully success_handlers = self.class.run_completed_successfully_notifications success_handlers.each do |notification| notification.call(run_status) end end # # Callback to fire notifications that the Chef run failed # # @api private # def run_failed failure_handlers = self.class.run_failed_notifications failure_handlers.each do |notification| notification.call(run_status) end end # # Instantiates a Chef::Node object, possibly loading the node's prior state # when using chef-client. Sets Chef.node to the new node. # # @return [Chef::Node] The node object for this Chef run # # @see Chef::PolicyBuilder#load_node # # @api private # def load_node policy_builder.load_node run_status.node = policy_builder.node Chef.set_node(policy_builder.node) node end # # Mutates the `node` object to prepare it for the chef run. # # @return [Chef::Node] The updated node object # # @see Chef::PolicyBuilder#build_node # # @api private # def build_node policy_builder.build_node run_status.node = node node end # # Sync cookbooks to local cache. # # TODO this appears to be unused. # # @see Chef::PolicyBuilder#sync_cookbooks # # @api private # def sync_cookbooks policy_builder.sync_cookbooks end # # Sets up the run context. # # @see Chef::PolicyBuilder#setup_run_context # # @return The newly set up run context # # @api private def setup_run_context run_context = policy_builder.setup_run_context(specific_recipes) assert_cookbook_path_not_empty(run_context) run_status.run_context = run_context run_context end # # The PolicyBuilder strategy for figuring out run list and cookbooks. # # @return [Chef::PolicyBuilder::Policyfile, Chef::PolicyBuilder::ExpandNodeObject] # # @api private # def policy_builder @policy_builder ||= Chef::PolicyBuilder::Dynamic.new(node_name, ohai.data, json_attribs, override_runlist, events) end # # Save the updated node to Chef. # # Does not save if we are in solo mode or using override_runlist. # # @see Chef::Node#save # @see Chef::Config#solo # # @api private # def save_updated_node if Chef::Config[:solo_legacy_mode] # nothing to do elsif policy_builder.temporary_policy? Chef::Log.warn("Skipping final node save because override_runlist was given") else Chef::Log.debug("Saving the current state of node #{node_name}") node.save end end # # Run ohai plugins. Runs all ohai plugins unless minimal_ohai is specified. # # Sends the ohai_completed event when finished. # # @see Chef::EventDispatcher# # @see Chef::Config#minimal_ohai # # @api private # def run_ohai filter = Chef::Config[:minimal_ohai] ? %w{fqdn machinename hostname platform platform_version os os_version} : nil ohai.all_plugins(filter) events.ohai_completed(node) end # # Figure out the node name we are working with. # # It tries these, in order: # - Chef::Config.node_name # - ohai[:fqdn] # - ohai[:machinename] # - ohai[:hostname] # # @raise [Chef::Exceptions::CannotDetermineNodeName] If the node name is not # set and cannot be determined via ohai. # # @see Chef::Config#node_name # # @api private # def node_name name = Chef::Config[:node_name] || ohai[:fqdn] || ohai[:machinename] || ohai[:hostname] Chef::Config[:node_name] = name raise Chef::Exceptions::CannotDetermineNodeName unless name name end # # Determine our private key and set up the connection to the Chef server. # # Skips registration and fires the `skipping_registration` event if # Chef::Config.client_key is unspecified or already exists. # # If Chef::Config.client_key does not exist, we register the client with the # Chef server and fire the registration_start and registration_completed events. # # @return [Chef::ServerAPI] The server connection object. # # @see Chef::Config#chef_server_url # @see Chef::Config#client_key # @see Chef::ApiClient::Registration#run # @see Chef::EventDispatcher#skipping_registration # @see Chef::EventDispatcher#registration_start # @see Chef::EventDispatcher#registration_completed # @see Chef::EventDispatcher#registration_failed # # @api private # def register(client_name = node_name, config = Chef::Config) if !config[:client_key] events.skipping_registration(client_name, config) Chef::Log.debug("Client key is unspecified - skipping registration") elsif File.exists?(config[:client_key]) events.skipping_registration(client_name, config) Chef::Log.debug("Client key #{config[:client_key]} is present - skipping registration") else events.registration_start(node_name, config) Chef::Log.info("Client key #{config[:client_key]} is not present - registering") Chef::ApiClient::Registration.new(node_name, config[:client_key]).run events.registration_completed end # We now have the client key, and should use it from now on. @rest = Chef::ServerAPI.new(config[:chef_server_url], client_name: client_name, signing_key_filename: config[:client_key]) # force initialization of the rest_clean API object rest_clean(client_name, config) register_reporters rescue Exception => e # TODO this should probably only ever fire if we *started* registration. # Move it to the block above. # TODO: munge exception so a semantic failure message can be given to the # user events.registration_failed(client_name, e, config) raise end # # Converges all compiled resources. # # Fires the converge_start, converge_complete and converge_failed events. # # If the exception `:end_client_run_early` is thrown during convergence, it # does not mark the run complete *or* failed, and returns `nil` # # @param run_context The run context. # # @return The thrown exception, if we are in audit mode. `nil` means the # converge was successful or ended early. # # @raise Any converge exception, unless we are in audit mode, in which case # we *return* the exception. # # @see Chef::Runner#converge # @see Chef::Config#audit_mode # @see Chef::EventDispatch#converge_start # @see Chef::EventDispatch#converge_complete # @see Chef::EventDispatch#converge_failed # # @api private # def converge(run_context) converge_exception = nil catch(:end_client_run_early) do begin events.converge_start(run_context) Chef::Log.debug("Converging node #{node_name}") @runner = Chef::Runner.new(run_context) @runner.converge events.converge_complete rescue Exception => e events.converge_failed(e) raise e if Chef::Config[:audit_mode] == :disabled converge_exception = e end end converge_exception end # # Converge the node via and then save it if successful. # # @param run_context The run context. # # @return The thrown exception, if we are in audit mode. `nil` means the # converge was successful or ended early. # # @raise Any converge or node save exception, unless we are in audit mode, # in which case we *return* the exception. # # @see #converge # @see #save_updated_mode # @see Chef::Config#audit_mode # # @api private # # We don't want to change the old API on the `converge` method to have it perform # saving. So we wrap it in this method. # TODO given this seems to be pretty internal stuff, how badly do we need to # split this stuff up? # def converge_and_save(run_context) converge_exception = converge(run_context) unless converge_exception begin save_updated_node rescue Exception => e raise e if Chef::Config[:audit_mode] == :disabled converge_exception = e end end converge_exception end # # Run the audit phase. # # Triggers the audit_phase_start, audit_phase_complete and # audit_phase_failed events. # # @param run_context The run context. # # @return Any thrown exceptions. `nil` if successful. # # @see Chef::Audit::Runner#run # @see Chef::EventDispatch#audit_phase_start # @see Chef::EventDispatch#audit_phase_complete # @see Chef::EventDispatch#audit_phase_failed # # @api private # def run_audits(run_context) begin events.audit_phase_start(run_status) Chef::Log.info("Starting audit phase") auditor = Chef::Audit::Runner.new(run_context) auditor.run if auditor.failed? audit_exception = Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total) @events.audit_phase_failed(audit_exception, Chef::Audit::Logger.read_buffer) else @events.audit_phase_complete(Chef::Audit::Logger.read_buffer) end rescue Exception => e Chef::Log.error("Audit phase failed with error message: #{e.message}") @events.audit_phase_failed(e, Chef::Audit::Logger.read_buffer) audit_exception = e end audit_exception end # # Expands the run list. # # @return [Chef::RunListExpansion] The expanded run list. # # @see Chef::PolicyBuilder#expand_run_list # def expanded_run_list policy_builder.expand_run_list end # # Check if the user has Administrator privileges on windows. # # Throws an error if the user is not an admin, and # `Chef::Config.fatal_windows_admin_check` is true. # # @raise [Chef::Exceptions::WindowsNotAdmin] If the user is not an admin. # # @see Chef::platform#windows? # @see Chef::Config#fatal_windows_admin_check # # @api private # def do_windows_admin_check if Chef::Platform.windows? Chef::Log.debug("Checking for administrator privileges....") if !has_admin_privileges? message = "chef-client doesn't have administrator privileges on node #{node_name}." if Chef::Config[:fatal_windows_admin_check] Chef::Log.fatal(message) Chef::Log.fatal("fatal_windows_admin_check is set to TRUE.") raise Chef::Exceptions::WindowsNotAdmin, message else Chef::Log.warn("#{message} This might cause unexpected resource failures.") end else Chef::Log.debug("chef-client has administrator privileges on node #{node_name}.") end end end # Notification registration class<] # # @api private # def run_start_notifications @run_start_notifications ||= [] end # # Listeners to be run when the client run completes successfully. # # @return [Array] # # @api private # def run_completed_successfully_notifications @run_completed_successfully_notifications ||= [] end # # Listeners to be run when the client run fails. # # @return [Array] # # @api private # def run_failed_notifications @run_failed_notifications ||= [] end end # # IO stream that will be used as 'STDOUT' for formatters. Formatters are # configured during `initialize`, so this provides a convenience for # setting alternative IO stream during tests. # # @api private # STDOUT_FD = STDOUT # # IO stream that will be used as 'STDERR' for formatters. Formatters are # configured during `initialize`, so this provides a convenience for # setting alternative IO stream during tests. # # @api private # STDERR_FD = STDERR # # Deprecated writers # include Chef::Mixin::Deprecation deprecated_attr_writer :ohai, "There is no alternative. Leave ohai alone!" deprecated_attr_writer :rest, "There is no alternative. Leave rest alone!" deprecated_attr :runner, "There is no alternative. Leave runner alone!" private attr_reader :override_runlist attr_reader :specific_recipes def profiling_prereqs! require "ruby-prof" rescue LoadError raise "You must have the ruby-prof gem installed in order to use --profile-ruby" end def start_profiling return unless Chef::Config[:profile_ruby] profiling_prereqs! RubyProf.start end def end_profiling return unless Chef::Config[:profile_ruby] profiling_prereqs! path = Chef::FileCache.create_cache_path("graph_profile.out", false) File.open(path, "w+") do |file| RubyProf::GraphPrinter.new(RubyProf.stop).print(file, {}) end Chef::Log.warn("Ruby execution profile dumped to #{path}") end def empty_directory?(path) !File.exists?(path) || (Dir.entries(path).size <= 2) end def is_last_element?(index, object) object.kind_of?(Array) ? index == object.size - 1 : true end def assert_cookbook_path_not_empty(run_context) if Chef::Config[:solo_legacy_mode] # Check for cookbooks in the path given # Chef::Config[:cookbook_path] can be a string or an array # if it's an array, go through it and check each one, raise error at the last one if no files are found cookbook_paths = Array(Chef::Config[:cookbook_path]) Chef::Log.debug "Loading from cookbook_path: #{cookbook_paths.map { |path| File.expand_path(path) }.join(', ')}" if cookbook_paths.all? { |path| empty_directory?(path) } msg = "None of the cookbook paths set in Chef::Config[:cookbook_path], #{cookbook_paths.inspect}, contain any cookbooks" Chef::Log.fatal(msg) raise Chef::Exceptions::CookbookNotFound, msg end else Chef::Log.warn("Node #{node_name} has an empty run list.") if run_context.node.run_list.empty? end end def has_admin_privileges? require "chef/win32/security" 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 # HACK cannot load this first, but it must be loaded. require "chef/cookbook_loader" require "chef/cookbook_version" require "chef/cookbook/synchronizer" chef-12.14.60/lib/chef/config.rb000066400000000000000000000064771276456504500161530ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Brown () # Author:: AJ Christensen () # Author:: Mark Mzyk () # Author:: Kyle Goodwin () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/log" require "chef-config/logger" # DI our logger into ChefConfig before we load the config. Some defaults are # auto-detected, and this emits log messages on some systems, all of which will # occur at require-time. So we need to set the logger first. ChefConfig.logger = Chef::Log require "chef-config/config" require "chef/platform/query_helpers" # Ohai::Config defines its own log_level and log_location. When loaded, it will # override the default ChefConfig::Config values. We save them here before # loading ohai/config so that we can override them again inside Chef::Config. # # REMOVEME once these configurables are removed from the top level of Ohai. LOG_LEVEL = ChefConfig::Config[:log_level] unless defined? LOG_LEVEL LOG_LOCATION = ChefConfig::Config[:log_location] unless defined? LOG_LOCATION # Load the ohai config into the chef config. We can't have an empty ohai # configuration context because `ohai.plugins_path << some_path` won't work, # and providing default ohai config values here isn't DRY. require "ohai/config" class Chef Config = ChefConfig::Config # We re-open ChefConfig::Config to add additional settings. Generally, # everything should go in chef-config so it's shared with whoever uses that. # We make execeptions to that rule when: # * The functionality isn't likely to be useful outside of Chef # * The functionality makes use of a dependency we don't want to add to chef-config class Config default :event_loggers do evt_loggers = [] if ChefConfig.windows? && !(Chef::Platform.windows_server_2003? || Chef::Platform.windows_nano_server?) evt_loggers << :win_evt end evt_loggers end # Override the default values that were set by Ohai. # # REMOVEME once these configurables are removed from the top level of Ohai. default :log_level, LOG_LEVEL default :log_location, LOG_LOCATION # Ohai::Config[:log_level] is deprecated and warns when set. Unfortunately, # there is no way to distinguish between setting log_level and setting # Ohai::Config[:log_level]. Since log_level and log_location are used by # chef-client and other tools (e.g., knife), we will mute the warnings here # by redefining the config_attr_writer to not warn for these options. # # REMOVEME once the warnings for these configurables are removed from Ohai. [ :log_level, :log_location ].each do |option| config_attr_writer option do |value| value end end end end chef-12.14.60/lib/chef/config_fetcher.rb000066400000000000000000000036641276456504500176460ustar00rootroot00000000000000require "chef/application" require "chef/chef_fs/path_utils" require "chef/http/simple" require "chef/json_compat" class Chef class ConfigFetcher attr_reader :config_location def initialize(config_location) @config_location = config_location end def expanded_path if config_location.nil? || remote_config? config_location else File.expand_path(config_location) end end def fetch_json config_data = read_config begin Chef::JSONCompat.from_json(config_data) rescue Chef::Exceptions::JSON::ParseError => error Chef::Application.fatal!("Could not parse the provided JSON file (#{config_location}): " + error.message, Chef::Exceptions::DeprecatedExitCode.new) end end def read_config if remote_config? fetch_remote_config else read_local_config end end def fetch_remote_config http.get("") rescue SocketError, SystemCallError, Net::HTTPServerException => error Chef::Application.fatal!("Cannot fetch config '#{config_location}': '#{error.class}: #{error.message}", Chef::Exceptions::DeprecatedExitCode.new) end def read_local_config ::File.read(config_location) rescue Errno::ENOENT Chef::Application.fatal!("Cannot load configuration from #{config_location}", Chef::Exceptions::DeprecatedExitCode.new) rescue Errno::EACCES Chef::Application.fatal!("Permissions are incorrect on #{config_location}. Please chmod a+r #{config_location}", Chef::Exceptions::DeprecatedExitCode.new) end def config_missing? return false if remote_config? # Check if the config file exists Pathname.new(config_location).realpath.to_s false rescue Errno::ENOENT return true end def http Chef::HTTP::Simple.new(config_location) end def remote_config? !!(config_location =~ %r{^(http|https)://}) end end end chef-12.14.60/lib/chef/constants.rb000066400000000000000000000014771276456504500167150ustar00rootroot00000000000000# # Author:: John Keiser # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class Chef NOT_PASSED = Object.new def NOT_PASSED.to_s "NOT_PASSED" end def NOT_PASSED.inspect to_s end NOT_PASSED.freeze end chef-12.14.60/lib/chef/cookbook/000077500000000000000000000000001276456504500161515ustar00rootroot00000000000000chef-12.14.60/lib/chef/cookbook/chefignore.rb000066400000000000000000000044351276456504500206150ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Cookbook class Chefignore COMMENTS_AND_WHITESPACE = /^\s*(?:#.*)?$/ attr_reader :ignores def initialize(ignore_file_or_repo) # Check the 'ignore_file_or_repo' path first and then look in the parent directory # to handle both the chef repo cookbook layout and a standalone cookbook @ignore_file = find_ignore_file(ignore_file_or_repo) @ignore_file = find_ignore_file(File.dirname(ignore_file_or_repo)) unless readable_file_or_symlink?(@ignore_file) @ignores = parse_ignore_file end def remove_ignores_from(file_list) Array(file_list).inject([]) do |unignored, file| ignored?(file) ? unignored : unignored << file end end def ignored?(file_name) @ignores.any? { |glob| File.fnmatch?(glob, file_name) } end private def parse_ignore_file ignore_globs = [] if readable_file_or_symlink?(@ignore_file) File.foreach(@ignore_file) do |line| ignore_globs << line.strip unless line =~ COMMENTS_AND_WHITESPACE end else Chef::Log.debug("No chefignore file found at #@ignore_file no files will be ignored") end ignore_globs end def find_ignore_file(path) if File.basename(path) =~ /chefignore/ path else File.join(path, "chefignore") end end def readable_file_or_symlink?(path) File.exist?(@ignore_file) && File.readable?(@ignore_file) && (File.file?(@ignore_file) || File.symlink?(@ignore_file)) end end end end chef-12.14.60/lib/chef/cookbook/cookbook_collection.rb000066400000000000000000000046441276456504500225270ustar00rootroot00000000000000#-- # Author:: Tim Hinderliter () # Author:: Christopher Walters () # Copyright:: Copyright 2010-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/mash" require "chef/cookbook/gem_installer" class Chef # == Chef::CookbookCollection # This class is the consistent interface for a node to obtain its # cookbooks by name. # # This class is basically a glorified Hash, but since there are # several ways this cookbook information is collected, # (e.g. CookbookLoader for solo, hash of auto-vivified Cookbook # objects for lazily-loaded remote cookbooks), it gets transformed # into this. class CookbookCollection < Mash # The input is a mapping of cookbook name to CookbookVersion objects. We # simply extract them def initialize(cookbook_versions = {}) super() do |hash, key| raise Chef::Exceptions::CookbookNotFound, "Cookbook #{key} not found. " << "If you're loading #{key} from another cookbook, make sure you configure the dependency in your metadata" end cookbook_versions.each { |cookbook_name, cookbook_version| self[cookbook_name] = cookbook_version } end # Validates that the cookbook metadata allows it to run on this instance. # # Currently checks chef_version and ohai_version in the cookbook metadata # against the running Chef::VERSION and Ohai::VERSION. # # @raises [Chef::Exceptions::CookbookChefVersionMismatch] if the Chef::VERSION fails validation # @raises [Chef::Exceptions::CookbookOhaiVersionMismatch] if the Ohai::VERSION fails validation def validate! each do |cookbook_name, cookbook_version| cookbook_version.metadata.validate_chef_version! cookbook_version.metadata.validate_ohai_version! end end def install_gems(events) Cookbook::GemInstaller.new(self, events).install end end end chef-12.14.60/lib/chef/cookbook/cookbook_version_loader.rb000066400000000000000000000352001276456504500233770ustar00rootroot00000000000000 require "chef/cookbook_version" require "chef/cookbook/chefignore" require "chef/cookbook/metadata" require "chef/util/path_helper" require "find" class Chef class Cookbook class CookbookVersionLoader FILETYPES_SUBJECT_TO_IGNORE = [ :attribute_filenames, :definition_filenames, :recipe_filenames, :template_filenames, :file_filenames, :library_filenames, :resource_filenames, :provider_filenames] UPLOADED_COOKBOOK_VERSION_FILE = ".uploaded-cookbook-version.json".freeze attr_reader :cookbook_settings attr_reader :cookbook_paths attr_reader :metadata_filenames attr_reader :frozen attr_reader :uploaded_cookbook_version_file attr_reader :cookbook_path # The cookbook's name as inferred from its directory. attr_reader :inferred_cookbook_name attr_reader :metadata_error def initialize(path, chefignore = nil) @cookbook_path = File.expand_path( path ) # cookbook_path from which this was loaded # We keep a list of all cookbook paths that have been merged in @cookbook_paths = [ cookbook_path ] @inferred_cookbook_name = File.basename( path ) @chefignore = chefignore @metadata = nil @relative_path = /#{Regexp.escape(@cookbook_path)}\/(.+)$/ @metadata_loaded = false @cookbook_settings = { :all_files => {}, :attribute_filenames => {}, :definition_filenames => {}, :recipe_filenames => {}, :template_filenames => {}, :file_filenames => {}, :library_filenames => {}, :resource_filenames => {}, :provider_filenames => {}, :root_filenames => {}, } @metadata_filenames = [] @metadata_error = nil end # Load the cookbook. Raises an error if the cookbook_path given to the # constructor doesn't point to a valid cookbook. def load! file_paths_map = load if empty? raise Exceptions::CookbookNotFoundInRepo, "The directory #{cookbook_path} does not contain a cookbook" end file_paths_map end # Load the cookbook. Does not raise an error if given a non-cookbook # directory as the cookbook_path. This behavior is provided for # compatibility, it is recommended to use #load! instead. def load metadata # force lazy evaluation to occur # re-raise any exception that occurred when reading the metadata raise_metadata_error! load_all_files remove_ignored_files load_as(:attribute_filenames, "attributes", "*.rb") load_as(:definition_filenames, "definitions", "*.rb") load_as(:recipe_filenames, "recipes", "*.rb") load_recursively_as(:library_filenames, "libraries", "*") load_recursively_as(:template_filenames, "templates", "*") load_recursively_as(:file_filenames, "files", "*") load_recursively_as(:resource_filenames, "resources", "*.rb") load_recursively_as(:provider_filenames, "providers", "*.rb") load_root_files if empty? Chef::Log.warn "Found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping." end @cookbook_settings end alias :load_cookbooks :load def metadata_filenames return @metadata_filenames unless @metadata_filenames.empty? if File.exists?(File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE)) @uploaded_cookbook_version_file = File.join(cookbook_path, UPLOADED_COOKBOOK_VERSION_FILE) end if File.exists?(File.join(cookbook_path, "metadata.rb")) @metadata_filenames << File.join(cookbook_path, "metadata.rb") elsif File.exists?(File.join(cookbook_path, "metadata.json")) @metadata_filenames << File.join(cookbook_path, "metadata.json") elsif @uploaded_cookbook_version_file @metadata_filenames << @uploaded_cookbook_version_file end # Set frozen based on .uploaded-cookbook-version.json set_frozen @metadata_filenames end def cookbook_version return nil if empty? Chef::CookbookVersion.new(cookbook_name, *cookbook_paths).tap do |c| c.all_files = cookbook_settings[:all_files].values c.attribute_filenames = cookbook_settings[:attribute_filenames].values c.definition_filenames = cookbook_settings[:definition_filenames].values c.recipe_filenames = cookbook_settings[:recipe_filenames].values c.template_filenames = cookbook_settings[:template_filenames].values c.file_filenames = cookbook_settings[:file_filenames].values c.library_filenames = cookbook_settings[:library_filenames].values c.resource_filenames = cookbook_settings[:resource_filenames].values c.provider_filenames = cookbook_settings[:provider_filenames].values c.root_filenames = cookbook_settings[:root_filenames].values c.metadata_filenames = metadata_filenames c.metadata = metadata c.freeze_version if @frozen end end def cookbook_name # The `name` attribute is now required in metadata, so # inferred_cookbook_name generally should not be used. Per CHEF-2923, # we have to not raise errors in cookbook metadata immediately, so that # users can still `knife cookbook upload some-cookbook` when an # unrelated cookbook has an error in its metadata. This situation # could prevent us from reading the `name` attribute from the metadata # entirely, but the name is used as a hash key in CookbookLoader, so we # fall back to the inferred name here. (metadata.name || @inferred_cookbook_name).to_sym end # Generates the Cookbook::Metadata object def metadata return @metadata unless @metadata.nil? @metadata = Chef::Cookbook::Metadata.new metadata_filenames.each do |metadata_file| case metadata_file when /\.rb$/ apply_ruby_metadata(metadata_file) when @uploaded_cookbook_version_file apply_json_cookbook_version_metadata(metadata_file) when /\.json$/ apply_json_metadata(metadata_file) else raise "Invalid metadata file: #{metadata_file} for cookbook: #{cookbook_version}" end end @metadata # Rescue errors so that users can upload cookbooks via `knife cookbook # upload` even if some cookbooks in their chef-repo have errors in # their metadata. We only rescue StandardError because you have to be # doing something *really* terrible to raise an exception that inherits # directly from Exception in your metadata.rb file. rescue StandardError => e @metadata_error = e @metadata end def raise_metadata_error! raise @metadata_error unless @metadata_error.nil? # Metadata won't be valid if the cookbook is empty. If the cookbook is # actually empty, a metadata error here would be misleading, so don't # raise it (if called by #load!, a different error is raised). if !empty? && !metadata.valid? message = "Cookbook loaded at path(s) [#{@cookbook_paths.join(', ')}] has invalid metadata: #{metadata.errors.join('; ')}" raise Exceptions::MetadataNotValid, message end false end def empty? cookbook_settings.values.all? { |files_hash| files_hash.empty? } && metadata_filenames.size == 0 end def merge!(other_cookbook_loader) other_cookbook_settings = other_cookbook_loader.cookbook_settings cookbook_settings.each do |file_type, file_list| file_list.merge!(other_cookbook_settings[file_type]) end metadata_filenames.concat(other_cookbook_loader.metadata_filenames) @cookbook_paths += other_cookbook_loader.cookbook_paths @frozen = true if other_cookbook_loader.frozen @metadata = nil # reset metadata so it gets reloaded and all metadata files applied. self end def chefignore @chefignore ||= Chefignore.new(File.basename(cookbook_path)) end # Enumerate all the files in a cookbook and assign the resulting list to # `cookbook_settings[:all_files]`. In order to behave in a compatible way # with previous implementations, directories at the cookbook's root that # begin with a dot are ignored. dotfiles are generally not ignored, # however if the file is named ".uploaded-cookbook-version.json" it is # assumed to be managed by chef-zero and not part of the cookbook. def load_all_files return unless File.exist?(cookbook_path) # If cookbook_path is a symlink, Find on Windows Ruby 2.3 will not traverse it. # Dir.entries will do so on all platforms, so we iterate the top level using # Dir.entries. Since we have different behavior at the top anyway (hidden # directories at the top level are not included for backcompat), this # actually keeps things a bit cleaner. Dir.entries(cookbook_path).each do |top_filename| # Skip top-level directories starting with "." top_path = File.join(cookbook_path, top_filename) next if File.directory?(top_path) && top_filename.start_with?(".") # Use Find.find because it: # (a) returns any children, recursively # (b) includes top_path as well # (c) skips symlinks, which is backcompat (no judgement on whether it was *right*) Find.find(top_path) do |path| # Only add files, not directories next unless File.file?(path) # Don't add .uploaded-cookbook-version.json next if File.basename(path) == UPLOADED_COOKBOOK_VERSION_FILE relative_path = Chef::Util::PathHelper.relative_path_from(cookbook_path, path) path = Pathname.new(path).cleanpath.to_s cookbook_settings[:all_files][relative_path] = path end end end def load_root_files select_files_by_glob(File.join(Chef::Util::PathHelper.escape_glob_dir(cookbook_path), "*"), File::FNM_DOTMATCH).each do |file| file = Chef::Util::PathHelper.cleanpath(file) next if File.directory?(file) next if File.basename(file) == UPLOADED_COOKBOOK_VERSION_FILE name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file) cookbook_settings[:root_filenames][name] = file end end def load_recursively_as(category, category_dir, glob) glob_pattern = File.join(Chef::Util::PathHelper.escape_glob_dir(cookbook_path, category_dir), "**", glob) select_files_by_glob(glob_pattern, File::FNM_DOTMATCH).each do |file| file = Chef::Util::PathHelper.cleanpath(file) name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file) cookbook_settings[category][name] = file end end def load_as(category, *path_glob) glob_pattern = File.join(Chef::Util::PathHelper.escape_glob_dir(cookbook_path), *path_glob) select_files_by_glob(glob_pattern).each do |file| file = Chef::Util::PathHelper.cleanpath(file) name = Chef::Util::PathHelper.relative_path_from(@cookbook_path, file) cookbook_settings[category][name] = file end end # Mimic Dir.glob inside a cookbook by running `File.fnmatch?` against # `cookbook_settings[:all_files]`. # # @param pattern [String] a glob string passed to `File.fnmatch?` # @param option [Integer] Option flag to control globbing behavior. These # are constants defined on `File`, such as `File::FNM_DOTMATCH`. # `File.fnmatch?` and `Dir.glob` only take one option argument, if you # need to combine options, you must `|` the constants together. To make # `File.fnmatch?` behave like `Dir.glob`, `File::FNM_PATHNAME` is # always enabled. def select_files_by_glob(pattern, option = 0) combined_opts = option | File::FNM_PATHNAME cookbook_settings[:all_files].values.select do |path| File.fnmatch?(pattern, path, combined_opts) end end def remove_ignored_files cookbook_settings[:all_files].reject! do |relative_path, full_path| chefignore.ignored?(relative_path) end end def apply_ruby_metadata(file) begin @metadata.from_file(file) rescue Chef::Exceptions::JSON::ParseError Chef::Log.error("Error evaluating metadata.rb for #@inferred_cookbook_name in " + file) raise end end def apply_json_metadata(file) begin @metadata.from_json(IO.read(file)) rescue Chef::Exceptions::JSON::ParseError Chef::Log.error("Couldn't parse cookbook metadata JSON for #@inferred_cookbook_name in " + file) raise end end def apply_json_cookbook_version_metadata(file) begin data = Chef::JSONCompat.parse(IO.read(file)) @metadata.from_hash(data["metadata"]) # the JSON cookbok metadata file is only used by chef-zero. # The Chef Server API currently does not enforce that the metadata # have a `name` field, but that will cause an error when attempting # to load the cookbook. To keep compatibility, we fake it by setting # the metadata name from the cookbook version object's name. # # This behavior can be removed if/when Chef Server enforces that the # metadata contains a name key. @metadata.name(data["cookbook_name"]) unless data["metadata"].key?("name") rescue Chef::Exceptions::JSON::ParseError Chef::Log.error("Couldn't parse cookbook metadata JSON for #@inferred_cookbook_name in " + file) raise end end def set_frozen if uploaded_cookbook_version_file begin data = Chef::JSONCompat.parse(IO.read(uploaded_cookbook_version_file)) @frozen = data["frozen?"] rescue Chef::Exceptions::JSON::ParseError Chef::Log.error("Couldn't parse cookbook metadata JSON for #@inferred_cookbook_name in #{uploaded_cookbook_version_file}") raise end end end end end end chef-12.14.60/lib/chef/cookbook/file_system_file_vendor.rb000066400000000000000000000042261276456504500234010ustar00rootroot00000000000000#-- # Author:: Christopher Walters () # Author:: Tim Hinderliter () # Copyright:: Copyright 2010-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/cookbook/file_vendor" class Chef class Cookbook # == Chef::Cookbook::FileSystemFileVendor # This FileVendor loads files from Chef::Config.cookbook_path. The # thing that's sort of janky about this FileVendor implementation is # that it basically takes only the cookbook's name from the manifest # and throws the rest away then re-builds the list of files on the # disk. This is due to the manifest not having the on-disk file # locations, since in the chef-client case, that information is # non-sensical. class FileSystemFileVendor < FileVendor attr_reader :cookbook_name attr_reader :repo_paths def initialize(manifest, *repo_paths) @cookbook_name = manifest[:cookbook_name] @repo_paths = repo_paths.flatten raise ArgumentError, "You must specify at least one repo path" if @repo_paths.empty? end # Implements abstract base's requirement. It looks in the # Chef::Config.cookbook_path file hierarchy for the requested # file. def get_filename(filename) location = @repo_paths.inject(nil) do |memo, basepath| candidate_location = File.join(basepath, @cookbook_name, filename) memo = candidate_location if File.exist?(candidate_location) memo end raise "File #{filename} does not exist for cookbook #{@cookbook_name}" unless location location end end end end chef-12.14.60/lib/chef/cookbook/file_vendor.rb000066400000000000000000000046371276456504500210040ustar00rootroot00000000000000# # Author:: Christopher Walters () # Author:: Tim Hinderliter () # Copyright:: Copyright 2010-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 Cookbook # == Chef::Cookbook::FileVendor # This class handles fetching of cookbook files based on specificity. class FileVendor @vendor_class = nil @initialization_options = nil # Configures FileVendor to use the RemoteFileVendor implementation. After # calling this, subsequent calls to create_from_manifest will return a # RemoteFileVendor object initialized with the given http_client def self.fetch_from_remote(http_client) @vendor_class = RemoteFileVendor @initialization_options = http_client end def self.fetch_from_disk(cookbook_paths) @vendor_class = FileSystemFileVendor @initialization_options = cookbook_paths end # Returns the implementation class that is currently configured, or `nil` # if one has not been configured yet. def self.vendor_class @vendor_class end def self.initialization_options @initialization_options end # Factory method that creates the appropriate kind of # Cookbook::FileVendor to serve the contents of the manifest def self.create_from_manifest(manifest) if @vendor_class.nil? raise "Must configure FileVendor to use a specific implementation before creating an instance" end @vendor_class.new(manifest, @initialization_options) end # Gets the on-disk location for the given cookbook file. # # Subclasses are responsible for determining exactly how the # files are obtained and where they are stored. def get_filename(filename) raise NotImplemented, "Subclasses must implement this method" end end end end chef-12.14.60/lib/chef/cookbook/gem_installer.rb000066400000000000000000000052721276456504500213310ustar00rootroot00000000000000#-- # Copyright:: Copyright (c) 2010-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 "tmpdir" require "chef/mixin/shell_out" class Chef class Cookbook class GemInstaller include Chef::Mixin::ShellOut # @return [Chef::EventDispatch::Dispatcher] the client event dispatcher attr_accessor :events # @return [Chef::CookbookCollection] the cookbook collection attr_accessor :cookbook_collection def initialize(cookbook_collection, events) @cookbook_collection = cookbook_collection @events = events end # Installs the gems into the omnibus gemset. # def install cookbook_gems = [] cookbook_collection.each do |cookbook_name, cookbook_version| cookbook_gems += cookbook_version.metadata.gems end events.cookbook_gem_start(cookbook_gems) unless cookbook_gems.empty? begin Dir.mktmpdir("chef-gem-bundle") do |dir| File.open("#{dir}/Gemfile", "w+") do |tf| tf.puts "source '#{Chef::Config[:rubygems_url]}'" cookbook_gems.each do |args| tf.puts "gem(*#{args.inspect})" end tf.close Chef::Log.debug("generated Gemfile contents:") Chef::Log.debug(IO.read(tf.path)) so = shell_out!("bundle install", cwd: dir, env: { "PATH" => path_with_prepended_ruby_bin }) Chef::Log.info(so.stdout) end end Gem.clear_paths rescue Exception => e events.cookbook_gem_failed(e) raise end end events.cookbook_gem_finished end private # path_sanity appends the ruby_bin, but I want the ruby_bin prepended def path_with_prepended_ruby_bin env_path = ENV["PATH"].dup || "" existing_paths = env_path.split(File::PATH_SEPARATOR) existing_paths.unshift(RbConfig::CONFIG["bindir"]) env_path = existing_paths.join(File::PATH_SEPARATOR) env_path.encode("utf-8", invalid: :replace, undef: :replace) end end end end chef-12.14.60/lib/chef/cookbook/metadata.rb000066400000000000000000001037671276456504500202740ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: AJ Christensen () # Author:: Seth Falcon () # 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/exceptions" require "chef/mash" require "chef/mixin/from_file" require "chef/mixin/params_validate" require "chef/log" require "chef/version_class" require "chef/version_constraint" require "chef/version_constraint/platform" require "chef/json_compat" class Chef class Cookbook # == Chef::Cookbook::Metadata # Chef::Cookbook::Metadata provides a convenient DSL for declaring metadata # about Chef Cookbooks. class Metadata NAME = "name".freeze DESCRIPTION = "description".freeze LONG_DESCRIPTION = "long_description".freeze MAINTAINER = "maintainer".freeze MAINTAINER_EMAIL = "maintainer_email".freeze LICENSE = "license".freeze PLATFORMS = "platforms".freeze DEPENDENCIES = "dependencies".freeze RECOMMENDATIONS = "recommendations".freeze SUGGESTIONS = "suggestions".freeze CONFLICTING = "conflicting".freeze PROVIDING = "providing".freeze REPLACING = "replacing".freeze ATTRIBUTES = "attributes".freeze GROUPINGS = "groupings".freeze RECIPES = "recipes".freeze VERSION = "version".freeze SOURCE_URL = "source_url".freeze ISSUES_URL = "issues_url".freeze PRIVACY = "privacy".freeze CHEF_VERSIONS = "chef_versions".freeze OHAI_VERSIONS = "ohai_versions".freeze GEMS = "gems".freeze COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer, :maintainer_email, :license, :platforms, :dependencies, :recommendations, :suggestions, :conflicting, :providing, :replacing, :attributes, :groupings, :recipes, :version, :source_url, :issues_url, :privacy, :chef_versions, :ohai_versions, :gems ] VERSION_CONSTRAINTS = { :depends => DEPENDENCIES, :recommends => RECOMMENDATIONS, :suggests => SUGGESTIONS, :conflicts => CONFLICTING, :provides => PROVIDING, :replaces => REPLACING, :chef_version => CHEF_VERSIONS, :ohai_version => OHAI_VERSIONS } include Chef::Mixin::ParamsValidate include Chef::Mixin::FromFile attr_reader :platforms attr_reader :dependencies attr_reader :recommendations attr_reader :suggestions attr_reader :conflicting attr_reader :providing attr_reader :replacing attr_reader :attributes attr_reader :groupings attr_reader :recipes attr_reader :version # @return [Array] Array of supported Chef versions attr_reader :chef_versions # @return [Array] Array of supported Ohai versions attr_reader :ohai_versions # @return [Array] Array of gems to install with *args as an Array attr_reader :gems # Builds a new Chef::Cookbook::Metadata object. # # === Parameters # cookbook:: An optional cookbook object # maintainer:: An optional maintainer # maintainer_email:: An optional maintainer email # license::An optional license. Default is Apache v2.0 # # === Returns # metadata def initialize @name = nil @description = "" @long_description = "" @license = "All rights reserved" @maintainer = nil @maintainer_email = nil @platforms = Mash.new @dependencies = Mash.new @recommendations = Mash.new @suggestions = Mash.new @conflicting = Mash.new @providing = Mash.new @replacing = Mash.new @attributes = Mash.new @groupings = Mash.new @recipes = Mash.new @version = Version.new("0.0.0") @source_url = "" @issues_url = "" @privacy = false @chef_versions = [] @ohai_versions = [] @gems = [] @errors = [] end def ==(other) COMPARISON_FIELDS.inject(true) do |equal_so_far, field| equal_so_far && other.respond_to?(field) && (other.send(field) == send(field)) end end # Whether this metadata is valid. In order to be valid, all required # fields must be set. Chef's validation implementation checks the content # of a given field when setting (and raises an error if the content does # not meet the criteria), so the content of the fields is not considered # when checking validity. # # === Returns # valid:: Whether this metadata object is valid def valid? run_validation @errors.empty? end # A list of validation errors for this metadata object. See #valid? for # comments about the validation criteria. # # If there are any validation errors, one or more error strings will be # returned. Otherwise an empty array is returned. # # === Returns # error messages:: Whether this metadata object is valid def errors run_validation @errors end # Sets the cookbooks maintainer, or returns it. # # === Parameters # maintainer:: The maintainers name # # === Returns # maintainer:: Returns the current maintainer. def maintainer(arg = nil) set_or_return( :maintainer, arg, :kind_of => [ String ] ) end # Sets the maintainers email address, or returns it. # # === Parameters # maintainer_email:: The maintainers email address # # === Returns # maintainer_email:: Returns the current maintainer email. def maintainer_email(arg = nil) set_or_return( :maintainer_email, arg, :kind_of => [ String ] ) end # Sets the current license, or returns it. # # === Parameters # license:: The current license. # # === Returns # license:: Returns the current license def license(arg = nil) set_or_return( :license, arg, :kind_of => [ String ] ) end # Sets the current description, or returns it. Should be short - one line only! # # === Parameters # description:: The new description # # === Returns # description:: Returns the description def description(arg = nil) set_or_return( :description, arg, :kind_of => [ String ] ) end # Sets the current long description, or returns it. Might come from a README, say. # # === Parameters # long_description:: The new long description # # === Returns # long_description:: Returns the long description def long_description(arg = nil) set_or_return( :long_description, arg, :kind_of => [ String ] ) end # Sets the current cookbook version, or returns it. Can be two or three digits, separated # by dots. ie: '2.1', '1.5.4' or '0.9'. # # === Parameters # version:: The current version, as a string # # === Returns # version:: Returns the current version def version(arg = nil) if arg @version = Chef::Version.new(arg) end @version.to_s end # Sets the name of the cookbook, or returns it. # # === Parameters # name:: The current cookbook name. # # === Returns # name:: Returns the current cookbook name. def name(arg = nil) set_or_return( :name, arg, :kind_of => [ String ] ) end # Adds a supported platform, with version checking strings. # # === Parameters # platform,:: The platform (like :ubuntu or :mac_os_x) # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has # the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def supports(platform, *version_args) version = new_args_format(:supports, platform, version_args) constraint = validate_version_constraint(:supports, platform, version) @platforms[platform] = constraint.to_s @platforms[platform] end # Adds a dependency on another cookbook, with version checking strings. # # === Parameters # cookbook:: The cookbook # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has # the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def depends(cookbook, *version_args) if cookbook == name Chef::Log.warn "Ignoring self-dependency in cookbook #{name}, please remove it (in the future this will be fatal)." else version = new_args_format(:depends, cookbook, version_args) constraint = validate_version_constraint(:depends, cookbook, version) @dependencies[cookbook] = constraint.to_s end @dependencies[cookbook] end # Adds a recommendation for another cookbook, with version checking strings. # # === Parameters # cookbook:: The cookbook # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has # the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def recommends(cookbook, *version_args) version = new_args_format(:recommends, cookbook, version_args) constraint = validate_version_constraint(:recommends, cookbook, version) @recommendations[cookbook] = constraint.to_s @recommendations[cookbook] end # Adds a suggestion for another cookbook, with version checking strings. # # === Parameters # cookbook:: The cookbook # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has the # formx.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def suggests(cookbook, *version_args) version = new_args_format(:suggests, cookbook, version_args) constraint = validate_version_constraint(:suggests, cookbook, version) @suggestions[cookbook] = constraint.to_s @suggestions[cookbook] end # Adds a conflict for another cookbook, with version checking strings. # # === Parameters # cookbook:: The cookbook # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has # the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def conflicts(cookbook, *version_args) version = new_args_format(:conflicts, cookbook, version_args) constraint = validate_version_constraint(:conflicts, cookbook, version) @conflicting[cookbook] = constraint.to_s @conflicting[cookbook] end # Adds a recipe, definition, or resource provided by this cookbook. # # Recipes are specified as normal # Definitions are followed by (), and can include :params for prototyping # Resources are the stringified version (service[apache2]) # # === Parameters # recipe, definition, resource:: The thing we provide # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has # the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def provides(cookbook, *version_args) version = new_args_format(:provides, cookbook, version_args) constraint = validate_version_constraint(:provides, cookbook, version) @providing[cookbook] = constraint.to_s @providing[cookbook] end # Adds a cookbook that is replaced by this one, with version checking strings. # # === Parameters # cookbook:: The cookbook we replace # version:: A version constraint of the form "OP VERSION", # where OP is one of < <= = > >= ~> and VERSION has the form x.y.z or x.y. # # === Returns # versions:: Returns the list of versions for the platform def replaces(cookbook, *version_args) version = new_args_format(:replaces, cookbook, version_args) constraint = validate_version_constraint(:replaces, cookbook, version) @replacing[cookbook] = constraint.to_s @replacing[cookbook] end # Metadata DSL to set a valid chef_version. May be declared multiple times # with the result being 'OR'd such that if any statements match, the version # is considered supported. Uses Gem::Requirement for its implementation. # # @param version_args [Array] Version constraint in String form # @return [Array] Current chef_versions array def chef_version(*version_args) @chef_versions << Gem::Dependency.new("chef", *version_args) unless version_args.empty? @chef_versions end # Metadata DSL to set a valid ohai_version. May be declared multiple times # with the result being 'OR'd such that if any statements match, the version # is considered supported. Uses Gem::Requirement for its implementation. # # @param version_args [Array] Version constraint in String form # @return [Array] Current ohai_versions array def ohai_version(*version_args) @ohai_versions << Gem::Dependency.new("ohai", *version_args) unless version_args.empty? @ohai_versions end # Metadata DSL to set a gem to install from the cookbook metadata. May be declared # multiple times. All the gems from all the cookbooks are combined into one Gemfile # and depsolved together. Uses Bundler's DSL for its implementation. # # @param args [Array] Gem name and options to pass to Bundler's DSL # @return [Array] Array of gem statements as args def gem(*args) @gems << args unless args.empty? @gems end # Adds a description for a recipe. # # === Parameters # recipe:: The recipe # description:: The description of the recipe # # === Returns # description:: Returns the current description def recipe(name, description) @recipes[name] = description end # Sets the cookbook's recipes to the list of recipes in the given # +cookbook+. Any recipe that already has a description (if set by the # #recipe method) will not be updated. # # === Parameters # cookbook:: CookbookVersion object representing the cookbook # description:: The description of the recipe # # === Returns # recipe_unqualified_names:: An array of the recipe names given by the cookbook def recipes_from_cookbook_version(cookbook) cookbook.fully_qualified_recipe_names.map do |recipe_name| unqualified_name = if recipe_name =~ /::default$/ self.name.to_s else recipe_name end @recipes[unqualified_name] ||= "" provides(unqualified_name) unqualified_name end end # Adds an attribute that a user needs to configure for this cookbook. Takes # a name (with the / notation for a nested attribute), followed by any of # these options # # display_name:: What a UI should show for this attribute # description:: A hint as to what this attr is for # choice:: An array of choices to present to the user. # calculated:: If true, the default value is calculated by the recipe and cannot be displayed. # type:: "string" or "array" - default is "string" ("hash" is supported for backwards compatibility) # required:: Whether this attr is 'required', 'recommended' or 'optional' - default 'optional' (true/false values also supported for backwards compatibility) # recipes:: An array of recipes which need this attr set. # default,,:: The default value # # === Parameters # name:: The name of the attribute ('foo', or 'apache2/log_dir') # options:: The description of the options # # === Returns # options:: Returns the current options hash def attribute(name, options) validate( options, { :display_name => { :kind_of => String }, :description => { :kind_of => String }, :choice => { :kind_of => [ Array ], :default => [] }, :calculated => { :equal_to => [ true, false ], :default => false }, :type => { :equal_to => %w{string array hash symbol boolean numeric}, :default => "string" }, :required => { :equal_to => [ "required", "recommended", "optional", true, false ], :default => "optional" }, :recipes => { :kind_of => [ Array ], :default => [] }, :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] }, :source_url => { :kind_of => String }, :issues_url => { :kind_of => String }, :privacy => { :kind_of => [ TrueClass, FalseClass ] }, } ) options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil? validate_choice_array(options) validate_calculated_default_rule(options) validate_choice_default_rule(options) @attributes[name] = options @attributes[name] end def grouping(name, options) validate( options, { :title => { :kind_of => String }, :description => { :kind_of => String }, } ) @groupings[name] = options @groupings[name] end # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to an Array. # # Gem::Dependencey#to_s is not useful, and there is no #to_json defined on it or its component # objets, so we have to write our own rendering method. # # [ Gem::Dependency.new(">= 12.5"), Gem::Dependency.new(">= 11.18.0", "< 12.0") ] # # results in: # # [ [ ">= 12.5" ], [ ">= 11.18.0", "< 12.0" ] ] # # @param deps [Array] Multiple Gem-style version constraints # @return [Array]] Simple object representation of version constraints (for json) def gem_requirements_to_array(*deps) deps.map do |dep| dep.requirement.requirements.map do |op, version| "#{op} #{version}" end.sort end end # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to a hash. # # This is the inverse of #gem_requirements_to_array # # @param what [String] What version constraint we are constructing ('chef' or 'ohai' presently) # @param array [Array]] Simple object representation of version constraints (from json) # @return [Array] Multiple Gem-style version constraints def gem_requirements_from_array(what, array) array.map do |dep| Gem::Dependency.new(what, *dep) end end def to_hash { NAME => self.name, DESCRIPTION => self.description, LONG_DESCRIPTION => self.long_description, MAINTAINER => self.maintainer, MAINTAINER_EMAIL => self.maintainer_email, LICENSE => self.license, PLATFORMS => self.platforms, DEPENDENCIES => self.dependencies, RECOMMENDATIONS => self.recommendations, SUGGESTIONS => self.suggestions, CONFLICTING => self.conflicting, PROVIDING => self.providing, REPLACING => self.replacing, ATTRIBUTES => self.attributes, GROUPINGS => self.groupings, RECIPES => self.recipes, VERSION => self.version, SOURCE_URL => self.source_url, ISSUES_URL => self.issues_url, PRIVACY => self.privacy, CHEF_VERSIONS => gem_requirements_to_array(*self.chef_versions), OHAI_VERSIONS => gem_requirements_to_array(*self.ohai_versions), GEMS => self.gems, } end def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def self.from_hash(o) cm = self.new() cm.from_hash(o) cm end def from_hash(o) @name = o[NAME] if o.has_key?(NAME) @description = o[DESCRIPTION] if o.has_key?(DESCRIPTION) @long_description = o[LONG_DESCRIPTION] if o.has_key?(LONG_DESCRIPTION) @maintainer = o[MAINTAINER] if o.has_key?(MAINTAINER) @maintainer_email = o[MAINTAINER_EMAIL] if o.has_key?(MAINTAINER_EMAIL) @license = o[LICENSE] if o.has_key?(LICENSE) @platforms = o[PLATFORMS] if o.has_key?(PLATFORMS) @dependencies = handle_deprecated_constraints(o[DEPENDENCIES]) if o.has_key?(DEPENDENCIES) @recommendations = handle_deprecated_constraints(o[RECOMMENDATIONS]) if o.has_key?(RECOMMENDATIONS) @suggestions = handle_deprecated_constraints(o[SUGGESTIONS]) if o.has_key?(SUGGESTIONS) @conflicting = handle_deprecated_constraints(o[CONFLICTING]) if o.has_key?(CONFLICTING) @providing = o[PROVIDING] if o.has_key?(PROVIDING) @replacing = handle_deprecated_constraints(o[REPLACING]) if o.has_key?(REPLACING) @attributes = o[ATTRIBUTES] if o.has_key?(ATTRIBUTES) @groupings = o[GROUPINGS] if o.has_key?(GROUPINGS) @recipes = o[RECIPES] if o.has_key?(RECIPES) @version = o[VERSION] if o.has_key?(VERSION) @source_url = o[SOURCE_URL] if o.has_key?(SOURCE_URL) @issues_url = o[ISSUES_URL] if o.has_key?(ISSUES_URL) @privacy = o[PRIVACY] if o.has_key?(PRIVACY) @chef_versions = gem_requirements_from_array("chef", o[CHEF_VERSIONS]) if o.has_key?(CHEF_VERSIONS) @ohai_versions = gem_requirements_from_array("ohai", o[OHAI_VERSIONS]) if o.has_key?(OHAI_VERSIONS) @gems = o[GEMS] if o.has_key?(GEMS) self end def self.from_json(string) o = Chef::JSONCompat.from_json(string) self.from_hash(o) end def self.validate_json(json_str) o = Chef::JSONCompat.from_json(json_str) metadata = new() VERSION_CONSTRAINTS.each do |dependency_type, hash_key| if dependency_group = o[hash_key] dependency_group.each do |cb_name, constraints| if metadata.respond_to?(method_name) metadata.public_send(method_name, cb_name, *Array(constraints)) end end end end true end def from_json(string) o = Chef::JSONCompat.from_json(string) from_hash(o) end # Sets the cookbook's source URL, or returns it. # # === Parameters # maintainer:: The source URL # # === Returns # source_url:: Returns the current source URL. def source_url(arg = nil) set_or_return( :source_url, arg, :kind_of => [ String ] ) end # Sets the cookbook's issues URL, or returns it. # # === Parameters # issues_url:: The issues URL # # === Returns # issues_url:: Returns the current issues URL. def issues_url(arg = nil) set_or_return( :issues_url, arg, :kind_of => [ String ] ) end # # Sets the cookbook's privacy flag, or returns it. # # === Parameters # privacy:: Whether this cookbook is private or not # # === Returns # privacy:: Whether this cookbook is private or not # def privacy(arg = nil) set_or_return( :privacy, arg, :kind_of => [ TrueClass, FalseClass ] ) end # Validates that the Ohai::VERSION of the running chef-client matches one of the # configured ohai_version statements in this cookbooks metadata. # # @raises [Chef::Exceptions::CookbookOhaiVersionMismatch] if the cookbook fails validation def validate_ohai_version! unless gem_dep_matches?("ohai", Gem::Version.new(Ohai::VERSION), *ohai_versions) raise Exceptions::CookbookOhaiVersionMismatch.new(Ohai::VERSION, name, version, *ohai_versions) end end # Validates that the Chef::VERSION of the running chef-client matches one of the # configured chef_version statements in this cookbooks metadata. # # @raises [Chef::Exceptions::CookbookChefVersionMismatch] if the cookbook fails validation def validate_chef_version! unless gem_dep_matches?("chef", Gem::Version.new(Chef::VERSION), *chef_versions) raise Exceptions::CookbookChefVersionMismatch.new(Chef::VERSION, name, version, *chef_versions) end end private # Helper to match a gem style version (ohai_version/chef_version) against a set of # Gem::Dependency version constraints. If none are present, it always matches. if # multiple are present, one must match. Returns false if none matches. # # @param what [String] the name of the constraint (e.g. 'chef' or 'ohai') # @param version [String] the version to compare against the constraints # @param deps [Array] Multiple Gem-style version constraints # @return [Boolean] true if no constraints or a match, false if no match def gem_dep_matches?(what, version, *deps) # always match if we have no chef_version at all return true unless deps.length > 0 # match if we match any of the chef_version lines deps.any? { |dep| dep.match?(what, version) } end def run_validation if name.nil? @errors = ["The `name' attribute is required in cookbook metadata"] end end def new_args_format(caller_name, dep_name, version_constraints) if version_constraints.empty? ">= 0.0.0" elsif version_constraints.size == 1 version_constraints.first else msg = <<-OBSOLETED The dependency specification syntax you are using is no longer valid. You may not specify more than one version constraint for a particular cookbook. Consult https://docs.chef.io/config_rb_metadata.html for the updated syntax. Called by: #{caller_name} '#{dep_name}', #{version_constraints.map { |vc| vc.inspect }.join(", ")} Called from: #{caller[0...5].map { |line| " " + line }.join("\n")} OBSOLETED raise Exceptions::ObsoleteDependencySyntax, msg end end def validate_version_constraint(caller_name, dep_name, constraint_str) Chef::VersionConstraint::Platform.new(constraint_str) rescue Chef::Exceptions::InvalidVersionConstraint => e Log.debug(e) msg = <<-INVALID The version constraint syntax you are using is not valid. If you recently upgraded to Chef 0.10.0, be aware that you no may longer use "<<" and ">>" for 'less than' and 'greater than'; use '<' and '>' instead. Consult https://docs.chef.io/config_rb_metadata.html for more information. Called by: #{caller_name} '#{dep_name}', '#{constraint_str}' Called from: #{caller[0...5].map { |line| " " + line }.join("\n")} INVALID raise Exceptions::InvalidVersionConstraint, msg end # Verify that the given array is an array of strings # # Raise an exception if the members of the array are not Strings # # === Parameters # arry:: An array to be validated def validate_string_array(arry) if arry.kind_of?(Array) arry.each do |choice| validate( { :choice => choice }, { :choice => { :kind_of => String } } ) end end end # Validate the choice of the options hash # # Raise an exception if the members of the array do not match the defaults # === Parameters # opts:: The options hash def validate_choice_array(opts) if opts[:choice].kind_of?(Array) case opts[:type] when "string" validator = [ String ] when "array" validator = [ Array ] when "hash" validator = [ Hash ] when "symbol" validator = [ Symbol ] when "boolean" validator = [ TrueClass, FalseClass ] when "numeric" validator = [ Numeric ] end opts[:choice].each do |choice| validate( { :choice => choice }, { :choice => { :kind_of => validator } } ) end end end # For backwards compatibility, remap Boolean values to String # true is mapped to "required" # false is mapped to "optional" # # === Parameters # required_attr:: The value of options[:required] # # === Returns # required_attr:: "required", "recommended", or "optional" def remap_required_attribute(value) case value when true value = "required" when false value = "optional" end value end def validate_calculated_default_rule(options) calculated_conflict = ((options[:default].is_a?(Array) && !options[:default].empty?) || (options[:default].is_a?(String) && !options[:default] != "")) && options[:calculated] == true raise ArgumentError, "Default cannot be specified if calculated is true!" if calculated_conflict end def validate_choice_default_rule(options) return if !options[:choice].is_a?(Array) || options[:choice].empty? if options[:default].is_a?(String) && options[:default] != "" raise ArgumentError, "Default must be one of your choice values!" if options[:choice].index(options[:default]) == nil end if options[:default].is_a?(Array) && !options[:default].empty? options[:default].each do |val| raise ArgumentError, "Default values must be a subset of your choice values!" if options[:choice].index(val) == nil end end end # This method translates version constraint strings from # cookbooks with the old format. # # Before we began respecting version constraints, we allowed # multiple constraints to be placed on cookbooks, as well as the # << and >> operators, which are now just < and >. For # specifications with more than one constraint, we return an # empty array (otherwise, we're silently abiding only part of # the contract they have specified to us). If there is only one # constraint, we are replacing the old << and >> with the new < # and >. def handle_deprecated_constraints(specification) specification.inject(Mash.new) do |acc, (cb, constraints)| constraints = Array(constraints) acc[cb] = (constraints.empty? || constraints.size > 1) ? [] : constraints.first.gsub(/>>/, ">").gsub(/<) # Copyright:: Copyright 2010-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/cookbook/file_vendor" class Chef class Cookbook # == Chef::Cookbook::RemoteFileVendor # This FileVendor loads files by either fetching them from the local cache, or # if not available, loading them from the remote server. class RemoteFileVendor < FileVendor attr_reader :rest attr_reader :cookbook_name def initialize(manifest, rest) @manifest = manifest @cookbook_name = @manifest[:cookbook_name] || @manifest[:name] @rest = rest end # Implements abstract base's requirement. It looks in the # Chef::Config.cookbook_path file hierarchy for the requested # file. def get_filename(filename) if filename =~ /([^\/]+)\/(.+)$/ segment = $1 else raise "get_filename: Cannot determine segment/filename for incoming filename #{filename}" end raise "No such segment #{segment} in cookbook #{@cookbook_name}" unless @manifest[segment] found_manifest_record = @manifest[segment].find { |manifest_record| manifest_record[:path] == filename } raise "No such file #{filename} in #{@cookbook_name}" unless found_manifest_record cache_filename = File.join("cookbooks", @cookbook_name, found_manifest_record["path"]) # update valid_cache_entries so the upstream cache cleaner knows what # we've used. validate_cached_copy(cache_filename) current_checksum = nil if Chef::FileCache.has_key?(cache_filename) current_checksum = Chef::CookbookVersion.checksum_cookbook_file(Chef::FileCache.load(cache_filename, false)) end # If the checksums are different between on-disk (current) and on-server # (remote, per manifest), do the update. This will also execute if there # is no current checksum. if current_checksum != found_manifest_record["checksum"] raw_file = @rest.streaming_request(found_manifest_record[:url]) Chef::Log.debug("Storing updated #{cache_filename} in the cache.") Chef::FileCache.move_to(raw_file.path, cache_filename) else Chef::Log.debug("Not fetching #{cache_filename}, as the cache is up to date.") Chef::Log.debug("Current checksum: #{current_checksum}; manifest checksum: #{found_manifest_record['checksum']})") end full_path_cache_filename = Chef::FileCache.load(cache_filename, false) # return the filename, not the contents (second argument= false) full_path_cache_filename end def validate_cached_copy(cache_filename) CookbookCacheCleaner.instance.mark_file_as_valid(cache_filename) end end end end chef-12.14.60/lib/chef/cookbook/synchronizer.rb000066400000000000000000000222561276456504500212420ustar00rootroot00000000000000require "chef/client" require "chef/util/threaded_job_queue" require "chef/server_api" require "singleton" class Chef # Keep track of the filenames that we use in both eager cookbook # downloading (during sync_cookbooks) and lazy (during the run # itself, through FileVendor). After the run is over, clean up the # cache. class CookbookCacheCleaner attr_accessor :skip_removal # Setup a notification to clear the valid_cache_entries when a Chef client # run starts Chef::Client.when_run_starts do |run_status| instance.reset! end # Register a notification to cleanup unused files from cookbooks Chef::Client.when_run_completes_successfully do |run_status| instance.cleanup_file_cache end include Singleton def initialize reset! end def reset! @valid_cache_entries = {} end def mark_file_as_valid(cache_path) @valid_cache_entries[cache_path] = true end def cache Chef::FileCache end def cleanup_file_cache unless Chef::Config[:solo_legacy_mode] || skip_removal # Delete each file in the cache that we didn't encounter in the # manifest. cache.find(File.join(%w{cookbooks ** {*,.*}})).each do |cache_filename| unless @valid_cache_entries[cache_filename] Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer needed by chef-client.") cache.delete(cache_filename) end end else Chef::Log.info("Skipping removal of unused files from the cache") end end end # Synchronizes the locally cached copies of cookbooks with the files on the # server. class CookbookSynchronizer CookbookFile = Struct.new(:cookbook, :segment, :manifest_record) attr_accessor :remove_obsoleted_files def initialize(cookbooks_by_name, events) @eager_segments = Chef::CookbookVersion::COOKBOOK_SEGMENTS.dup unless Chef::Config[:no_lazy_load] @eager_segments.delete(:files) @eager_segments.delete(:templates) end @eager_segments.freeze @cookbooks_by_name, @events = cookbooks_by_name, events @cookbook_full_file_paths = {} @remove_obsoleted_files = true end def cache Chef::FileCache end def cookbook_names @cookbooks_by_name.keys end def cookbooks @cookbooks_by_name.values end def cookbook_count @cookbooks_by_name.size end def have_cookbook?(cookbook_name) @cookbooks_by_name.key?(cookbook_name) end def cookbook_segment(cookbook_name, segment) @cookbooks_by_name[cookbook_name].manifest[segment] end def files @files ||= cookbooks.inject([]) do |memo, cookbook| @eager_segments.each do |segment| cookbook.manifest[segment].each do |manifest_record| memo << CookbookFile.new(cookbook, segment, manifest_record) end end memo end end def files_by_cookbook files.group_by { |file| file.cookbook } end def files_remaining_by_cookbook @files_remaining_by_cookbook ||= begin files_by_cookbook.inject({}) do |memo, (cookbook, files)| memo[cookbook] = files.size memo end end end def mark_file_synced(file) files_remaining_by_cookbook[file.cookbook] -= 1 if files_remaining_by_cookbook[file.cookbook] == 0 @events.synchronized_cookbook(file.cookbook.name, file.cookbook) end end # Synchronizes all the cookbooks from the chef-server. #) # === Returns # true:: Always returns true def sync_cookbooks Chef::Log.info("Loading cookbooks [#{cookbooks.map { |ckbk| ckbk.name + '@' + ckbk.version }.join(', ')}]") Chef::Log.debug("Cookbooks detail: #{cookbooks.inspect}") clear_obsoleted_cookbooks queue = Chef::Util::ThreadedJobQueue.new files.each do |file| queue << lambda do |lock| full_file_path = sync_file(file) lock.synchronize do # Save the full_path of the downloaded file to be restored in the manifest later save_full_file_path(file, full_file_path) mark_file_synced(file) end end end @events.cookbook_sync_start(cookbook_count) queue.process(Chef::Config[:cookbook_sync_threads]) # Update the full file paths in the manifest update_cookbook_filenames() rescue Exception => e @events.cookbook_sync_failed(cookbooks, e) raise else @events.cookbook_sync_complete true end # Saves the full_path to the file of the cookbook to be updated # in the manifest later def save_full_file_path(file, full_path) @cookbook_full_file_paths[file.cookbook] ||= {} @cookbook_full_file_paths[file.cookbook][file.segment] ||= [ ] @cookbook_full_file_paths[file.cookbook][file.segment] << full_path end # remove cookbooks that are not referenced in the expanded run_list at all # (if we have an override run_list we may not want to do this) def remove_old_cookbooks cache.find(File.join(%w{cookbooks ** {*,.*}})).each do |cache_file| cache_file =~ /^cookbooks\/([^\/]+)\// unless have_cookbook?($1) Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.") cache.delete(cache_file) @events.removed_cookbook_file(cache_file) end end end # remove deleted files in cookbooks that are being used on the node def remove_deleted_files cache.find(File.join(%w{cookbooks ** {*,.*}})).each do |cache_file| md = cache_file.match(/^cookbooks\/([^\/]+)\/([^\/]+)\/(.*)/) next unless md ( cookbook_name, segment, file ) = md[1..3] if have_cookbook?(cookbook_name) manifest_segment = cookbook_segment(cookbook_name, segment) if manifest_segment.select { |manifest_record| manifest_record["path"] == "#{segment}/#{file}" }.empty? Chef::Log.info("Removing #{cache_file} from the cache; its is no longer in the cookbook manifest.") cache.delete(cache_file) @events.removed_cookbook_file(cache_file) end end end end # Iterates over cached cookbooks' files, removing files belonging to # cookbooks that don't appear in +cookbook_hash+ def clear_obsoleted_cookbooks @events.cookbook_clean_start if remove_obsoleted_files remove_old_cookbooks else Chef::Log.info("Skipping removal of obsoleted cookbooks from the cache") CookbookCacheCleaner.instance.skip_removal = true end remove_deleted_files @events.cookbook_clean_complete end def update_cookbook_filenames @cookbook_full_file_paths.each do |cookbook, file_segments| file_segments.each do |segment, full_paths| cookbook.replace_segment_filenames(segment, full_paths) end end end # Sync an individual file if needed. If there is an up to date copy # locally, nothing is done. Updates +file+'s manifest with the full path to # the cached file. # # === Arguments # file # === Returns # Full path to the cached file as a String def sync_file(file) cache_filename = File.join("cookbooks", file.cookbook.name, file.manifest_record["path"]) mark_cached_file_valid(cache_filename) # If the checksums are different between on-disk (current) and on-server # (remote, per manifest), do the update. This will also execute if there # is no current checksum. if !cached_copy_up_to_date?(cache_filename, file.manifest_record["checksum"]) download_file(file.manifest_record["url"], cache_filename) @events.updated_cookbook_file(file.cookbook.name, cache_filename) else Chef::Log.debug("Not storing #{cache_filename}, as the cache is up to date.") end # Load the file in the cache and return the full file path to the loaded file cache.load(cache_filename, false) end def cached_copy_up_to_date?(local_path, expected_checksum) if Chef::Config[:skip_cookbook_sync] Chef::Log.warn "skipping cookbook synchronization! DO NOT LEAVE THIS ENABLED IN PRODUCTION!!!" return true end if cache.has_key?(local_path) current_checksum = CookbookVersion.checksum_cookbook_file(cache.load(local_path, false)) expected_checksum == current_checksum else false end end # Unconditionally download the file from the given URL. File will be # downloaded to the path +destination+ which is relative to the Chef file # cache root. def download_file(url, destination) raw_file = server_api.streaming_request(url) Chef::Log.info("Storing updated #{destination} in the cache.") cache.move_to(raw_file.path, destination) end # Marks the given file as valid (non-stale). def mark_cached_file_valid(cache_filename) CookbookCacheCleaner.instance.mark_file_as_valid(cache_filename) end def server_api Thread.current[:server_api] ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], keepalives: true) end end end chef-12.14.60/lib/chef/cookbook/syntax_check.rb000066400000000000000000000212601276456504500211620ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 "pathname" require "stringio" require "erubis" require "chef/mixin/shell_out" require "chef/mixin/checksum" require "chef/util/path_helper" class Chef class Cookbook # == Chef::Cookbook::SyntaxCheck # Encapsulates the process of validating the ruby syntax of files in Chef # cookbooks. class SyntaxCheck # == Chef::Cookbook::SyntaxCheck::PersistentSet # Implements set behavior with disk-based persistence. Objects in the set # are expected to be strings containing only characters that are valid in # filenames. # # This class is used to track which files have been syntax checked so # that known good files are not rechecked. class PersistentSet attr_reader :cache_path # Create a new PersistentSet. Values in the set are persisted by # creating a file in the +cache_path+ directory. def initialize(cache_path = Chef::Config[:syntax_check_cache_path]) @cache_path = cache_path @cache_path_created = false end # Adds +value+ to the set's collection. def add(value) ensure_cache_path_created FileUtils.touch(File.join(cache_path, value)) end # Returns true if the set includes +value+ def include?(value) File.exist?(File.join(cache_path, value)) end private def ensure_cache_path_created return true if @cache_path_created FileUtils.mkdir_p(cache_path) @cache_path_created = true end end include Chef::Mixin::ShellOut include Chef::Mixin::Checksum attr_reader :cookbook_path # A PersistentSet object that tracks which files have already been # validated. attr_reader :validated_files attr_reader :chefignore # Creates a new SyntaxCheck given the +cookbook_name+ and a +cookbook_path+. # If no +cookbook_path+ is given, +Chef::Config.cookbook_path+ is used. def self.for_cookbook(cookbook_name, cookbook_path = nil) cookbook_path ||= Chef::Config.cookbook_path unless cookbook_path raise ArgumentError, "Cannot find cookbook #{cookbook_name} unless Chef::Config.cookbook_path is set or an explicit cookbook path is given" end new(File.join(cookbook_path, cookbook_name.to_s)) end # Create a new SyntaxCheck object # === Arguments # cookbook_path::: the (on disk) path to the cookbook def initialize(cookbook_path) @cookbook_path = cookbook_path @chefignore ||= Chefignore.new(cookbook_path) @validated_files = PersistentSet.new end def remove_ignored_files(file_list) return file_list if chefignore.ignores.empty? file_list.reject do |full_path| relative_pn = Chef::Util::PathHelper.relative_path_from(cookbook_path, full_path) chefignore.ignored?(relative_pn.to_s) end end def remove_uninteresting_ruby_files(file_list) file_list.reject { |f| f =~ %r{#{cookbook_path}/(files|templates)/} } end def ruby_files path = Chef::Util::PathHelper.escape_glob_dir(cookbook_path) files = Dir[File.join(path, "**", "*.rb")] files = remove_ignored_files(files) files = remove_uninteresting_ruby_files(files) files end def untested_ruby_files ruby_files.reject do |file| if validated?(file) Chef::Log.debug("Ruby file #{file} is unchanged, skipping syntax check") true else false end end end def template_files remove_ignored_files Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(cookbook_path), "**/templates/**", "*.erb")] end def untested_template_files template_files.reject do |file| if validated?(file) Chef::Log.debug("Template #{file} is unchanged, skipping syntax check") true else false end end end def validated?(file) validated_files.include?(checksum(file)) end def validated(file) validated_files.add(checksum(file)) end def validate_ruby_files untested_ruby_files.each do |ruby_file| return false unless validate_ruby_file(ruby_file) validated(ruby_file) end end def validate_templates untested_template_files.each do |template| return false unless validate_template(template) validated(template) end end def validate_template(erb_file) Chef::Log.debug("Testing template #{erb_file} for syntax errors...") validate_erb_file_inline(erb_file) end def validate_ruby_file(ruby_file) Chef::Log.debug("Testing #{ruby_file} for syntax errors...") validate_ruby_file_inline(ruby_file) end # Validate the ruby code in an erb template. Uses RubyVM to do syntax # checking. def validate_erb_file_inline(erb_file) old_stderr = $stderr engine = Erubis::Eruby.new engine.convert!(IO.read(erb_file)) ruby_code = engine.src # Even when we're compiling the code w/ RubyVM, syntax errors just # print to $stderr. We want to capture this and handle the printing # ourselves, so we must temporarily swap $stderr to capture the output. tmp_stderr = $stderr = StringIO.new abs_path = File.expand_path(erb_file) RubyVM::InstructionSequence.new(ruby_code, erb_file, abs_path, 0) true rescue SyntaxError $stderr = old_stderr invalid_erb_file(erb_file, tmp_stderr.string) false ensure # be paranoid about setting stderr back to the old value. $stderr = old_stderr if defined?(old_stderr) && old_stderr end # Debug a syntax error in a template. def invalid_erb_file(erb_file, error_message) file_relative_path = erb_file[/^#{Regexp.escape(cookbook_path + File::Separator)}(.*)/, 1] Chef::Log.fatal("Erb template #{file_relative_path} has a syntax error:") error_message.each_line { |l| Chef::Log.fatal(l.chomp) } nil end # Validate the syntax of a ruby file. Uses (Ruby 1.9+ only) RubyVM to # compile the code without evaluating it or spawning a new process. def validate_ruby_file_inline(ruby_file) # Even when we're compiling the code w/ RubyVM, syntax errors just # print to $stderr. We want to capture this and handle the printing # ourselves, so we must temporarily swap $stderr to capture the output. old_stderr = $stderr tmp_stderr = $stderr = StringIO.new abs_path = File.expand_path(ruby_file) file_content = IO.read(abs_path) # We have to wrap this in a block so the user code evaluates in a # similar context as what Chef does normally. Otherwise RubyVM # will reject some common idioms, like using `return` to end evaluation # of a recipe. See also CHEF-5199 wrapped_content = "Object.new.instance_eval do\n#{file_content}\nend\n" RubyVM::InstructionSequence.new(wrapped_content, ruby_file, abs_path, 0) true rescue SyntaxError $stderr = old_stderr invalid_ruby_file(ruby_file, tmp_stderr.string) false ensure # be paranoid about setting stderr back to the old value. $stderr = old_stderr if defined?(old_stderr) && old_stderr end # Debugs ruby syntax errors by printing the path to the file and any # diagnostic info given in +error_message+ def invalid_ruby_file(ruby_file, error_message) file_relative_path = ruby_file[/^#{Regexp.escape(cookbook_path + File::Separator)}(.*)/, 1] Chef::Log.fatal("Cookbook file #{file_relative_path} has a ruby syntax error:") error_message.each_line { |l| Chef::Log.fatal(l.chomp) } false end # Returns the full path to the running ruby. def ruby Gem.ruby end end end end chef-12.14.60/lib/chef/cookbook_loader.rb000066400000000000000000000142051276456504500200260ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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/exceptions" require "chef/cookbook/cookbook_version_loader" require "chef/cookbook_version" require "chef/cookbook/chefignore" require "chef/cookbook/metadata" # # CookbookLoader class loads the cookbooks lazily as read # class Chef class CookbookLoader attr_reader :cookbooks_by_name attr_reader :merged_cookbooks attr_reader :cookbook_paths attr_reader :metadata include Enumerable def initialize(*repo_paths) repo_paths = repo_paths.flatten raise ArgumentError, "You must specify at least one cookbook repo path" if repo_paths.empty? @cookbooks_by_name = Mash.new @loaded_cookbooks = {} @metadata = Mash.new @cookbooks_paths = Hash.new { |h, k| h[k] = [] } # for deprecation warnings @chefignores = {} @repo_paths = repo_paths.map do |repo_path| File.expand_path(repo_path) end @preloaded_cookbooks = false @loaders_by_name = {} # Used to track which cookbooks appear in multiple places in the cookbook repos # and are merged in to a single cookbook by file shadowing. This behavior is # deprecated, so users of this class may issue warnings to the user by checking # this variable @merged_cookbooks = [] end def merged_cookbook_paths # for deprecation warnings merged_cookbook_paths = {} @merged_cookbooks.each { |c| merged_cookbook_paths[c] = @cookbooks_paths[c] } merged_cookbook_paths end def warn_about_cookbook_shadowing unless merged_cookbooks.empty? Chef::Log.deprecation "The cookbook(s): #{merged_cookbooks.join(', ')} exist in multiple places in your cookbook_path. " + "A composite version has been compiled. This has been deprecated since 0.10.4, in Chef 13 this behavior will be REMOVED." end end # Will be removed when cookbook shadowing is removed, do NOT create new consumers of this API. # # @api private def load_cookbooks_without_shadow_warning preload_cookbooks @loaders_by_name.each do |cookbook_name, _loaders| load_cookbook(cookbook_name) end @cookbooks_by_name end def load_cookbooks ret = load_cookbooks_without_shadow_warning warn_about_cookbook_shadowing ret end def load_cookbook(cookbook_name) preload_cookbooks return @cookbooks_by_name[cookbook_name] if @cookbooks_by_name.has_key?(cookbook_name) return nil unless @loaders_by_name.key?(cookbook_name.to_s) cookbook_loaders_for(cookbook_name).each do |loader| loader.load next if loader.empty? @cookbooks_paths[cookbook_name] << loader.cookbook_path # for deprecation warnings if @loaded_cookbooks.key?(cookbook_name) @merged_cookbooks << cookbook_name # for deprecation warnings @loaded_cookbooks[cookbook_name].merge!(loader) else @loaded_cookbooks[cookbook_name] = loader end end if @loaded_cookbooks.has_key?(cookbook_name) cookbook_version = @loaded_cookbooks[cookbook_name].cookbook_version @cookbooks_by_name[cookbook_name] = cookbook_version @metadata[cookbook_name] = cookbook_version.metadata end @cookbooks_by_name[cookbook_name] end def [](cookbook) if @cookbooks_by_name.has_key?(cookbook.to_sym) || load_cookbook(cookbook.to_sym) @cookbooks_by_name[cookbook.to_sym] else raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook}; did you forget to add metadata to a cookbook? (https://docs.chef.io/config_rb_metadata.html)" end end alias :fetch :[] def has_key?(cookbook_name) not self[cookbook_name.to_sym].nil? end alias :cookbook_exists? :has_key? alias :key? :has_key? def each @cookbooks_by_name.keys.sort { |a, b| a.to_s <=> b.to_s }.each do |cname| yield(cname, @cookbooks_by_name[cname]) end end def cookbook_names @cookbooks_by_name.keys.sort end def values @cookbooks_by_name.values end alias :cookbooks :values private def preload_cookbooks return false if @preloaded_cookbooks all_directories_in_repo_paths.each do |cookbook_path| preload_cookbook(cookbook_path) end @preloaded_cookbooks = true true end def preload_cookbook(cookbook_path) repo_path = File.dirname(cookbook_path) @chefignores[repo_path] ||= Cookbook::Chefignore.new(repo_path) loader = Cookbook::CookbookVersionLoader.new(cookbook_path, @chefignores[repo_path]) add_cookbook_loader(loader) end def all_directories_in_repo_paths @all_directories_in_repo_paths ||= all_files_in_repo_paths.select { |path| File.directory?(path) } end def all_files_in_repo_paths @all_files_in_repo_paths ||= begin @repo_paths.inject([]) do |all_children, repo_path| all_children + Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(repo_path), "*")] end end end def add_cookbook_loader(loader) cookbook_name = loader.cookbook_name @loaders_by_name[cookbook_name.to_s] ||= [] @loaders_by_name[cookbook_name.to_s] << loader loader end def cookbook_loaders_for(cookbook_name) @loaders_by_name[cookbook_name.to_s] end end end chef-12.14.60/lib/chef/cookbook_manifest.rb000066400000000000000000000232741276456504500203740ustar00rootroot00000000000000# Author:: Daniel DeLeo () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "forwardable" require "chef/util/path_helper" require "chef/log" class Chef # Handles the details of representing a cookbook in JSON form for uploading # to a Chef Server. class CookbookManifest # Duplicates the same constant in CookbookVersion. We cannot remove it # there because it is treated by other code as part of CookbookVersion's # public API (also used in some deprecated methods). COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ].freeze extend Forwardable attr_reader :cookbook_version def_delegator :@cookbook_version, :root_paths def_delegator :@cookbook_version, :segment_filenames def_delegator :@cookbook_version, :name def_delegator :@cookbook_version, :identifier def_delegator :@cookbook_version, :metadata def_delegator :@cookbook_version, :full_name def_delegator :@cookbook_version, :version def_delegator :@cookbook_version, :frozen_version? # Create a new CookbookManifest object for the given `cookbook_version`. # You can subsequently call #to_hash to get a Hash representation of the # cookbook_version in the "manifest" format, or #to_json to get a JSON # representation of the cookbook_version. # # The inferface for this behavior is expected to change as we implement new # manifest formats. The entire class should be considered a private API for # now. # # @api private # @param policy_mode [Boolean] whether to convert cookbooks to Hash/JSON in # the format used by the `cookbook_artifacts` endpoint (for policyfiles). # Setting this option also changes the behavior of #save_url and # #force_save_url such that CookbookVersions will be uploaded to the new # `cookbook_artifacts` API. This endpoint is currently under active # development and the format is expected to change frequently, therefore # the result of #manifest, #to_hash, and #to_json will not be stable when # `policy_mode` is enabled. def initialize(cookbook_version, policy_mode: false) @cookbook_version = cookbook_version @policy_mode = !!policy_mode reset! end # Resets all lazily computed values. def reset! @manifest = nil @checksums = nil @manifest_records_by_path = nil true end # Returns a 'manifest' data structure that can be uploaded to a Chef # Server. # # The format is as follows: # # { # :cookbook_name => name, # String # :metadata => metadata, # Chef::Cookbook::Metadata # :version => version, # Chef::Version # :name => full_name, # String of "#{name}-#{version}" # # :recipes => Array, # :definitions => Array, # :libraries => Array, # :attributes => Array, # :files => Array, # :templates => Array, # :resources => Array, # :providers => Array, # :root_files => Array # } # # Where a `FileSpec` is a Hash of the form: # # { # :name => file_name, # :path => path, # :checksum => csum, # :specificity => specificity # } # def manifest @manifest || generate_manifest @manifest end def checksums @manifest || generate_manifest @checksums end def manifest_records_by_path @manifest || generate_manifest @manifest_records_by_path end def policy_mode? @policy_mode end def to_hash result = manifest.dup result["frozen?"] = frozen_version? result["chef_type"] = "cookbook_version" result.to_hash end def to_json(*a) result = to_hash result["json_class"] = "Chef::CookbookVersion" Chef::JSONCompat.to_json(result, *a) end # Return the URL to save (PUT) this object to the server via the # REST api. If there is an existing document on the server and it # is marked frozen, a PUT will result in a 409 Conflict. def save_url if policy_mode? "#{named_cookbook_url}/#{identifier}" else "#{named_cookbook_url}/#{version}" end end def named_cookbook_url "#{cookbook_url_path}/#{name}" end # Adds the `force=true` parameter to the upload URL. This allows # the user to overwrite a frozen cookbook (a PUT against the # normal #save_url raises a 409 Conflict in this case). def force_save_url "#{save_url}?force=true" end # Update this CookbookManifest from the contents of another manifest, and # make the corresponding changes to the cookbook_version object. Required # to provide backward compatibility with CookbookVersion#manifest= method. def update_from(new_manifest) @manifest = Mash.new new_manifest @checksums = extract_checksums_from_manifest(@manifest) @manifest_records_by_path = extract_manifest_records_by_path(@manifest) COOKBOOK_SEGMENTS.each do |segment| next unless @manifest.has_key?(segment) filenames = @manifest[segment].map { |manifest_record| manifest_record["name"] } cookbook_version.replace_segment_filenames(segment, filenames) end end private def cookbook_url_path policy_mode? ? "cookbook_artifacts" : "cookbooks" end # See #manifest for a description of the manifest return value. # See #preferred_manifest_record for a description an individual manifest record. def generate_manifest manifest = Mash.new({ :recipes => Array.new, :definitions => Array.new, :libraries => Array.new, :attributes => Array.new, :files => Array.new, :templates => Array.new, :resources => Array.new, :providers => Array.new, :root_files => Array.new, }) @checksums = {} if !root_paths || root_paths.size == 0 Chef::Log.error("Cookbook #{name} does not have root_paths! Cannot generate manifest.") raise "Cookbook #{name} does not have root_paths! Cannot generate manifest." end COOKBOOK_SEGMENTS.each do |segment| segment_filenames(segment).each do |segment_file| next if File.directory?(segment_file) path, specificity = parse_segment_file_from_root_paths(segment, segment_file) file_name = File.basename(path) csum = checksum_cookbook_file(segment_file) @checksums[csum] = segment_file rs = Mash.new({ :name => file_name, :path => path, :checksum => csum, :specificity => specificity, }) manifest[segment] << rs end end manifest[:metadata] = metadata manifest[:version] = metadata.version if policy_mode? manifest[:name] = name.to_s manifest[:identifier] = identifier else manifest[:name] = full_name manifest[:cookbook_name] = name.to_s end @manifest_records_by_path = extract_manifest_records_by_path(manifest) @manifest = manifest end def parse_segment_file_from_root_paths(segment, segment_file) root_paths.each do |root_path| pathname = Chef::Util::PathHelper.relative_path_from(root_path, segment_file) parts = pathname.each_filename.take(2) # Check if path is actually under root_path next if parts[0] == ".." if segment == :templates || segment == :files # Check if pathname looks like files/foo or templates/foo (unscoped) if pathname.each_filename.to_a.length == 2 # Use root_default in case the same path exists at root_default and default return [ pathname.to_s, "root_default" ] else return [ pathname.to_s, parts[1] ] end else return [ pathname.to_s, "default" ] end end Chef::Log.error("Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}.") raise "Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}." end def extract_checksums_from_manifest(manifest) checksums = {} COOKBOOK_SEGMENTS.each do |segment| next unless manifest.has_key?(segment) manifest[segment].each do |manifest_record| checksums[manifest_record[:checksum]] = nil end end checksums end def checksum_cookbook_file(filepath) CookbookVersion.checksum_cookbook_file(filepath) end def extract_manifest_records_by_path(manifest) manifest_records_by_path = {} COOKBOOK_SEGMENTS.each do |segment| next unless manifest.has_key?(segment) manifest[segment].each do |manifest_record| manifest_records_by_path[manifest_record[:path]] = manifest_record end end manifest_records_by_path end end end chef-12.14.60/lib/chef/cookbook_site_streaming_uploader.rb000066400000000000000000000210341276456504500234660ustar00rootroot00000000000000# # Author:: Stanislav Vitvitskiy # Author:: Nuo Yan (nuo@chef.io) # Author:: Christopher Walters () # Copyright:: Copyright 2009-2016, 2010-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 "net/http" require "mixlib/authentication/signedheaderauth" require "openssl" class Chef # == Chef::CookbookSiteStreamingUploader # A streaming multipart HTTP upload implementation. Used to upload cookbooks # (in tarball form) to https://supermarket.chef.io # # inspired by http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html class CookbookSiteStreamingUploader DefaultHeaders = { "accept" => "application/json", "x-chef-version" => ::Chef::VERSION } # rubocop:disable Style/ConstantName class << self def create_build_dir(cookbook) tmp_cookbook_path = Tempfile.new("chef-#{cookbook.name}-build") tmp_cookbook_path.close tmp_cookbook_dir = tmp_cookbook_path.path File.unlink(tmp_cookbook_dir) FileUtils.mkdir_p(tmp_cookbook_dir) Chef::Log.debug("Staging at #{tmp_cookbook_dir}") checksums_to_on_disk_paths = cookbook.checksums Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment| cookbook.manifest[segment].each do |manifest_record| path_in_cookbook = manifest_record[:path] on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]] dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook) FileUtils.mkdir_p(File.dirname(dest)) Chef::Log.debug("Staging #{on_disk_path} to #{dest}") FileUtils.cp(on_disk_path, dest) end end # First, generate metadata Chef::Log.debug("Generating metadata") kcm = Chef::Knife::CookbookMetadata.new kcm.config[:cookbook_path] = [ tmp_cookbook_dir ] kcm.name_args = [ cookbook.name.to_s ] kcm.run tmp_cookbook_dir end def post(to_url, user_id, secret_key_filename, params = {}, headers = {}) make_request(:post, to_url, user_id, secret_key_filename, params, headers) end def put(to_url, user_id, secret_key_filename, params = {}, headers = {}) make_request(:put, to_url, user_id, secret_key_filename, params, headers) end def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {}) boundary = "----RubyMultipartClient" + rand(1000000).to_s + "ZZZZZ" parts = [] content_file = nil secret_key = OpenSSL::PKey::RSA.new(File.read(secret_key_filename)) unless params.nil? || params.empty? params.each do |key, value| if value.kind_of?(File) content_file = value filepath = value.path filename = File.basename(filepath) parts << StringPart.new( "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + key.to_s + "\"; filename=\"" + filename + "\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n") parts << StreamPart.new(value, File.size(filepath)) parts << StringPart.new("\r\n") else parts << StringPart.new( "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + key.to_s + "\"\r\n\r\n") parts << StringPart.new(value.to_s + "\r\n") end end parts << StringPart.new("--" + boundary + "--\r\n") end body_stream = MultipartStream.new(parts) timestamp = Time.now.utc.iso8601 url = URI.parse(to_url) Chef::Log.logger.debug("Signing: method: #{http_verb}, url: #{url}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}") # We use the body for signing the request if the file parameter # wasn't a valid file or wasn't included. Extract the body (with # multi-part delimiters intact) to sign the request. # TODO: tim: 2009-12-28: It'd be nice to remove this special case, and # always hash the entire request body. In the file case it would just be # expanded multipart text - the entire body of the POST. content_body = parts.inject("") { |result, part| result + part.read(0, part.size) } content_file.rewind if content_file # we consumed the file for the above operation, so rewind it. signing_options = { :http_method => http_verb, :path => url.path, :user_id => user_id, :timestamp => timestamp } (content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || "")) headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object(signing_options).sign(secret_key)) content_file.rewind if content_file # net/http doesn't like symbols for header keys, so we'll to_s each one just in case headers = DefaultHeaders.merge(Hash[*headers.map { |k, v| [k.to_s, v] }.flatten]) req = case http_verb when :put Net::HTTP::Put.new(url.path, headers) when :post Net::HTTP::Post.new(url.path, headers) end req.content_length = body_stream.size req.content_type = "multipart/form-data; boundary=" + boundary unless parts.empty? req.body_stream = body_stream http = Chef::HTTP::BasicClient.new(url).http_client res = http.request(req) # alias status to code and to_s to body for test purposes # TODO: stop the following madness! class << res alias :to_s :body # BUGBUG this makes the response compatible with what respsonse_steps expects to test headers (response.headers[] -> response[]) def headers # rubocop:disable Lint/NestedMethodDefinition self end def status # rubocop:disable Lint/NestedMethodDefinition code.to_i end end res end end class StreamPart def initialize(stream, size) @stream, @size = stream, size end def size @size end # read the specified amount from the stream def read(offset, how_much) @stream.read(how_much) end end class StringPart def initialize(str) @str = str end def size @str.length end # read the specified amount from the string startiung at the offset def read(offset, how_much) @str[offset, how_much] end end class MultipartStream def initialize(parts) @parts = parts @part_no = 0 @part_offset = 0 end def size @parts.inject(0) { |size, part| size + part.size } end def read(how_much, dst_buf = nil) if @part_no >= @parts.size dst_buf.replace("") if dst_buf return dst_buf end how_much_current_part = @parts[@part_no].size - @part_offset how_much_current_part = if how_much_current_part > how_much how_much else how_much_current_part end how_much_next_part = how_much - how_much_current_part current_part = @parts[@part_no].read(@part_offset, how_much_current_part) # recurse into the next part if the current one was not large enough if how_much_next_part > 0 @part_no += 1 @part_offset = 0 next_part = read(how_much_next_part) result = current_part + if next_part next_part else "" end else @part_offset += how_much_current_part result = current_part end dst_buf ? dst_buf.replace(result || "") : result end end end end chef-12.14.60/lib/chef/cookbook_uploader.rb000066400000000000000000000126241276456504500203760ustar00rootroot00000000000000 require "set" require "chef/exceptions" require "chef/knife/cookbook_metadata" require "chef/digester" require "chef/cookbook_manifest" require "chef/cookbook_version" require "chef/cookbook/syntax_check" require "chef/cookbook/file_system_file_vendor" require "chef/util/threaded_job_queue" require "chef/sandbox" require "chef/server_api" class Chef class CookbookUploader attr_reader :cookbooks attr_reader :path attr_reader :opts attr_reader :rest attr_reader :concurrency # Creates a new CookbookUploader. # ===Arguments: # * cookbooks::: A Chef::CookbookVersion or array of them describing the # cookbook(s) to be uploaded # * path::: A String or Array of Strings representing the base paths to the # cookbook repositories. # * opts::: (optional) An options Hash # ===Options: # * :force indicates that the uploader should set the force option when # uploading the cookbook. This allows frozen CookbookVersion # documents on the server to be overwritten (otherwise a 409 is # returned by the server) # * :rest A Chef::ServerAPI object that you have configured the way you like it. # If you don't provide this, one will be created using the values # in Chef::Config. # * :concurrency An integer that decided how many threads will be used to # perform concurrent uploads def initialize(cookbooks, opts = {}) @opts = opts @cookbooks = Array(cookbooks) @rest = opts[:rest] || Chef::ServerAPI.new(Chef::Config[:chef_server_url]) @concurrency = opts[:concurrency] || 10 @policy_mode = opts[:policy_mode] || false end def upload_cookbooks # Syntax Check validate_cookbooks # generate checksums of cookbook files and create a sandbox checksum_files = {} cookbooks.each do |cb| Chef::Log.info("Saving #{cb.name}") checksum_files.merge!(cb.checksums) end checksums = checksum_files.inject({}) { |memo, elt| memo[elt.first] = nil; memo } new_sandbox = rest.post("sandboxes", { :checksums => checksums }) Chef::Log.info("Uploading files") queue = Chef::Util::ThreadedJobQueue.new checksums_to_upload = Set.new # upload the new checksums and commit the sandbox new_sandbox["checksums"].each do |checksum, info| if info["needs_upload"] == true checksums_to_upload << checksum Chef::Log.info("Uploading #{checksum_files[checksum]} (checksum hex = #{checksum}) to #{info['url']}") queue << uploader_function_for(checksum_files[checksum], checksum, info["url"], checksums_to_upload) else Chef::Log.debug("#{checksum_files[checksum]} has not changed") end end queue.process(@concurrency) sandbox_url = new_sandbox["uri"] Chef::Log.debug("Committing sandbox") # Retry if S3 is claims a checksum doesn't exist (the eventual # in eventual consistency) retries = 0 begin rest.put(sandbox_url, { :is_completed => true }) rescue Net::HTTPServerException => e if e.message =~ /^400/ && (retries += 1) <= 5 sleep 2 retry else raise end end # files are uploaded, so save the manifest cookbooks.each do |cb| manifest = Chef::CookbookManifest.new(cb, policy_mode: policy_mode?) save_url = opts[:force] ? manifest.force_save_url : manifest.save_url begin rest.put(save_url, manifest) rescue Net::HTTPServerException => e case e.response.code when "409" raise Chef::Exceptions::CookbookFrozen, "Version #{cb.version} of cookbook #{cb.name} is frozen. Use --force to override." else raise end end end Chef::Log.info("Upload complete!") end def uploader_function_for(file, checksum, url, checksums_to_upload) lambda do # Checksum is the hexadecimal representation of the md5, # but we need the base64 encoding for the content-md5 # header checksum64 = Base64.encode64([checksum].pack("H*")).strip file_contents = File.open(file, "rb") { |f| f.read } # Custom headers. 'content-type' disables JSON serialization of the request body. headers = { "content-type" => "application/x-binary", "content-md5" => checksum64, "accept" => "application/json" } begin rest.put(url, file_contents, headers) checksums_to_upload.delete(checksum) rescue Net::HTTPServerException, Net::HTTPFatalError, Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError => e error_message = "Failed to upload #{file} (#{checksum}) to #{url} : #{e.message}" error_message << "\n#{e.response.body}" if e.respond_to?(:response) Chef::Knife.ui.error(error_message) raise end end end def validate_cookbooks cookbooks.each do |cb| syntax_checker = Chef::Cookbook::SyntaxCheck.for_cookbook(cb.name) Chef::Log.info("Validating ruby files") exit(1) unless syntax_checker.validate_ruby_files Chef::Log.info("Validating templates") exit(1) unless syntax_checker.validate_templates Chef::Log.info("Syntax OK") true end end def policy_mode? @policy_mode end end end chef-12.14.60/lib/chef/cookbook_version.rb000066400000000000000000000542041276456504500202500ustar00rootroot00000000000000# Author:: Adam Jacob () # Author:: Nuo Yan () # Author:: Christopher Walters () # Author:: Tim Hinderliter () # Author:: Seth Falcon () # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/log" require "chef/cookbook/file_vendor" require "chef/cookbook/metadata" require "chef/version_class" require "chef/digester" require "chef/cookbook_manifest" require "chef/server_api" class Chef # == Chef::CookbookVersion # CookbookVersion is a model object encapsulating the data about a Chef # cookbook. Chef supports maintaining multiple versions of a cookbook on a # single server; each version is represented by a distinct instance of this # class. class CookbookVersion include Comparable COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ] attr_accessor :all_files attr_accessor :root_paths attr_accessor :definition_filenames attr_accessor :template_filenames attr_accessor :file_filenames attr_accessor :library_filenames attr_accessor :resource_filenames attr_accessor :provider_filenames attr_accessor :root_filenames attr_accessor :name attr_accessor :metadata_filenames def status=(new_status) Chef.log_deprecation("Deprecated method `status' called. This method will be removed.") @status = new_status end def status Chef.log_deprecation("Deprecated method `status' called. This method will be removed.") @status end # A Chef::Cookbook::Metadata object. It has a setter that fixes up the # metadata to add descriptions of the recipes contained in this # CookbookVersion. attr_reader :metadata # attribute_filenames also has a setter that has non-default # functionality. attr_reader :attribute_filenames # recipe_filenames also has a setter that has non-default # functionality. attr_reader :recipe_filenames attr_reader :recipe_filenames_by_name attr_reader :attribute_filenames_by_short_filename attr_accessor :chef_server_rest # The `identifier` field is used for cookbook_artifacts, which are # organized on the chef server according to their content. If the # policy_mode option to CookbookManifest is set to true it will include # this field in the manifest Hash and in the upload URL. # # This field may be removed or have different behavior in the future, don't # use it in 3rd party code. # @api private attr_accessor :identifier # The first root path is the primary cookbook dir, from which metadata is loaded def root_dir root_paths[0] end # This is the one and only method that knows how cookbook files' # checksums are generated. def self.checksum_cookbook_file(filepath) Chef::Digester.generate_md5_checksum_for_file(filepath) rescue Errno::ENOENT Chef::Log.debug("File #{filepath} does not exist, so there is no checksum to generate") nil end def self.cache Chef::FileCache end # Creates a new Chef::CookbookVersion object. # # === Returns # object:: Duh. :) def initialize(name, *root_paths, chef_server_rest: nil) @name = name @root_paths = root_paths @frozen = false @attribute_filenames = Array.new @definition_filenames = Array.new @template_filenames = Array.new @file_filenames = Array.new @recipe_filenames = Array.new @recipe_filenames_by_name = Hash.new @library_filenames = Array.new @resource_filenames = Array.new @provider_filenames = Array.new @metadata_filenames = Array.new @root_filenames = Array.new @all_files = Array.new # deprecated @status = :ready @file_vendor = nil @metadata = Chef::Cookbook::Metadata.new @chef_server_rest = chef_server_rest end def version metadata.version end # Indicates if this version is frozen or not. Freezing a coobkook version # indicates that a new cookbook with the same name and version number # shoule def frozen_version? @frozen end def freeze_version @frozen = true end def version=(new_version) cookbook_manifest.reset! metadata.version(new_version) end def full_name "#{name}-#{version}" end def attribute_filenames=(*filenames) @attribute_filenames = filenames.flatten @attribute_filenames_by_short_filename = filenames_by_name(attribute_filenames) attribute_filenames end def metadata=(metadata) @metadata = metadata @metadata.recipes_from_cookbook_version(self) @metadata end ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]## alias :attribute_files :attribute_filenames alias :attribute_files= :attribute_filenames= def manifest cookbook_manifest.manifest end # Returns a hash of checksums to either nil or the on disk path (which is # done by generate_manifest). def checksums cookbook_manifest.checksums end def manifest_records_by_path cookbook_manifest.manifest_records_by_path end def manifest=(new_manifest) cookbook_manifest.update_from(new_manifest) end # Return recipe names in the form of cookbook_name::recipe_name def fully_qualified_recipe_names results = Array.new recipe_filenames_by_name.each_key do |rname| results << "#{name}::#{rname}" end results end def recipe_filenames=(*filenames) @recipe_filenames = filenames.flatten @recipe_filenames_by_name = filenames_by_name(recipe_filenames) recipe_filenames end ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]## alias :recipe_files :recipe_filenames alias :recipe_files= :recipe_filenames= # called from DSL def load_recipe(recipe_name, run_context) unless recipe_filenames_by_name.has_key?(recipe_name) raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}" end Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{name}") recipe = Chef::Recipe.new(name, recipe_name, run_context) recipe_filename = recipe_filenames_by_name[recipe_name] unless recipe_filename raise Chef::Exceptions::RecipeNotFound, "could not find #{recipe_name} files for cookbook #{name}" end recipe.from_file(recipe_filename) recipe end def segment_filenames(segment) unless COOKBOOK_SEGMENTS.include?(segment) raise ArgumentError, "invalid segment #{segment}: must be one of #{COOKBOOK_SEGMENTS.join(', ')}" end case segment.to_sym when :resources @resource_filenames when :providers @provider_filenames when :recipes @recipe_filenames when :libraries @library_filenames when :definitions @definition_filenames when :attributes @attribute_filenames when :files @file_filenames when :templates @template_filenames when :root_files @root_filenames end end def replace_segment_filenames(segment, filenames) case segment.to_sym when :recipes self.recipe_filenames = filenames when :attributes self.attribute_filenames = filenames else segment_filenames(segment).replace(filenames) end end # Query whether a template file +template_filename+ is available. File # specificity for the given +node+ is obeyed in the lookup. def has_template_for_node?(node, template_filename) !!find_preferred_manifest_record(node, :templates, template_filename) end # Query whether a cookbook_file file +cookbook_filename+ is available. File # specificity for the given +node+ is obeyed in the lookup. def has_cookbook_file_for_node?(node, cookbook_filename) !!find_preferred_manifest_record(node, :files, cookbook_filename) end # Determine the most specific manifest record for the given # segment/filename, given information in the node. Throws # FileNotFound if there is no such segment and filename in the # manifest. # # A manifest record is a Mash that follows the following form: # { # :name => "example.rb", # :path => "files/default/example.rb", # :specificity => "default", # :checksum => "1234" # } def preferred_manifest_record(node, segment, filename) found_pref = find_preferred_manifest_record(node, segment, filename) if found_pref manifest_records_by_path[found_pref] else if segment == :files || segment == :templates error_message = "Cookbook '#{name}' (#{version}) does not contain a file at any of these locations:\n" error_locations = if filename.is_a?(Array) filename.map { |name| " #{File.join(segment.to_s, name)}" } else [ " #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}", " #{segment}/#{node[:platform]}/#{filename}", " #{segment}/default/#{filename}", " #{segment}/#{filename}", ] end error_message << error_locations.join("\n") existing_files = segment_filenames(segment) # Strip the root_dir prefix off all files for readability pretty_existing_files = existing_files.map do |path| if root_dir path[root_dir.length + 1..-1] else path end end # Show the files that the cookbook does have. If the user made a typo, # hopefully they'll see it here. unless pretty_existing_files.empty? error_message << "\n\nThis cookbook _does_ contain: ['#{pretty_existing_files.join("','")}']" end raise Chef::Exceptions::FileNotFound, error_message else raise Chef::Exceptions::FileNotFound, "cookbook #{name} does not contain file #{segment}/#{filename}" end end end def preferred_filename_on_disk_location(node, segment, filename, current_filepath = nil) manifest_record = preferred_manifest_record(node, segment, filename) if current_filepath && (manifest_record["checksum"] == self.class.checksum_cookbook_file(current_filepath)) nil else file_vendor.get_filename(manifest_record["path"]) end end def relative_filenames_in_preferred_directory(node, segment, dirname) preferences = preferences_for_path(node, segment, dirname) filenames_by_pref = Hash.new preferences.each { |pref| filenames_by_pref[pref] = Array.new } manifest[segment].each do |manifest_record| manifest_record_path = manifest_record[:path] # find the NON SPECIFIC filenames, but prefer them by filespecificity. # For example, if we have a file: # 'files/default/somedir/somefile.conf' we only keep # 'somedir/somefile.conf'. If there is also # 'files/$hostspecific/somedir/otherfiles' that matches the requested # hostname specificity, that directory will win, as it is more specific. # # This is clearly ugly b/c the use case is for remote directory, where # we're just going to make cookbook_files out of these and make the # cookbook find them by filespecificity again. but it's the shortest # path to "success" for now. if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/ specificity_dirname = $1 non_specific_path = manifest_record_path[/#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)}\/(.+)$/, 1] # Record the specificity_dirname only if it's in the list of # valid preferences if filenames_by_pref[specificity_dirname] filenames_by_pref[specificity_dirname] << non_specific_path end end end best_pref = preferences.find { |pref| !filenames_by_pref[pref].empty? } raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/default/#{dirname}" unless best_pref filenames_by_pref[best_pref] end # Determine the manifest records from the most specific directory # for the given node. See #preferred_manifest_record for a # description of entries of the returned Array. def preferred_manifest_records_for_directory(node, segment, dirname) preferences = preferences_for_path(node, segment, dirname) records_by_pref = Hash.new preferences.each { |pref| records_by_pref[pref] = Array.new } manifest[segment].each do |manifest_record| manifest_record_path = manifest_record[:path] # extract the preference part from the path. if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/ # Note the specificy_dirname includes the segment and # dirname argument as above, which is what # preferences_for_path returns. It could be # "files/ubuntu-9.10/dirname", for example. specificity_dirname = $1 # Record the specificity_dirname only if it's in the list of # valid preferences if records_by_pref[specificity_dirname] records_by_pref[specificity_dirname] << manifest_record end end end best_pref = preferences.find { |pref| !records_by_pref[pref].empty? } raise Chef::Exceptions::FileNotFound, "cookbook #{name} (#{version}) has no directory #{segment}/default/#{dirname}" unless best_pref records_by_pref[best_pref] end # Given a node, segment and path (filename or directory name), # return the priority-ordered list of preference locations to # look. def preferences_for_path(node, segment, path) # only files and templates can be platform-specific if segment.to_sym == :files || segment.to_sym == :templates relative_search_path = if path.is_a?(Array) path else begin platform, version = Chef::Platform.find_platform_and_version(node) rescue ArgumentError => e # Skip platform/version if they were not found by find_platform_and_version if e.message =~ /Cannot find a (?:platform|version)/ platform = "/unknown_platform/" version = "/unknown_platform_version/" else raise end end fqdn = node[:fqdn] # Break version into components, eg: "5.7.1" => [ "5.7.1", "5.7", "5" ] search_versions = [] parts = version.to_s.split(".") parts.size.times do search_versions << parts.join(".") parts.pop end # Most specific to least specific places to find the path search_path = [ File.join("host-#{fqdn}", path) ] search_versions.each do |v| search_path << File.join("#{platform}-#{v}", path) end search_path << File.join(platform.to_s, path) search_path << File.join("default", path) search_path << path search_path end relative_search_path.map { |relative_path| File.join(segment.to_s, relative_path) } else if segment.to_sym == :root_files [path] else [File.join(segment, path)] end end end private :preferences_for_path def self.from_hash(o) cookbook_version = new(o["cookbook_name"] || o["name"]) # We want the Chef::Cookbook::Metadata object to always be inflated cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"]) cookbook_version.manifest = o cookbook_version.identifier = o["identifier"] if o.key?("identifier") # We don't need the following step when we decide to stop supporting deprecated operators in the metadata (e.g. <<, >>) cookbook_version.manifest["metadata"] = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(cookbook_version.metadata)) cookbook_version.freeze_version if o["frozen?"] cookbook_version end def self.json_create(o) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::CookbookVersion#from_hash") from_hash(o) end def self.from_cb_artifact_data(o) from_hash(o) end # @deprecated This method was used by the Ruby Chef Server and is no longer # needed. There is no replacement. def generate_manifest_with_urls Chef.log_deprecation("Deprecated method #generate_manifest_with_urls.") rendered_manifest = manifest.dup COOKBOOK_SEGMENTS.each do |segment| if rendered_manifest.has_key?(segment) rendered_manifest[segment].each do |manifest_record| url_options = { :cookbook_name => name.to_s, :cookbook_version => version, :checksum => manifest_record["checksum"] } manifest_record["url"] = yield(url_options) end end end rendered_manifest end def to_hash # TODO: this should become deprecated when the API for CookbookManifest becomes stable cookbook_manifest.to_hash end def to_json(*a) # TODO: this should become deprecated when the API for CookbookManifest becomes stable cookbook_manifest.to_json end def metadata_json_file File.join(root_paths[0], "metadata.json") end def metadata_rb_file File.join(root_paths[0], "metadata.rb") end def reload_metadata! if File.exists?(metadata_json_file) metadata.from_json(IO.read(metadata_json_file)) end end ## # REST API ## def save_url # TODO: this should become deprecated when the API for CookbookManifest becomes stable cookbook_manifest.save_url end def force_save_url # TODO: this should become deprecated when the API for CookbookManifest becomes stable cookbook_manifest.force_save_url end def chef_server_rest @chef_server_rest ||= self.chef_server_rest end def self.chef_server_rest Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def destroy chef_server_rest.delete("cookbooks/#{name}/#{version}") self end def self.load(name, version = "_latest") version = "_latest" if version == "latest" from_hash(chef_server_rest.get("cookbooks/#{name}/#{version}")) end # The API returns only a single version of each cookbook in the result from the cookbooks method def self.list chef_server_rest.get("cookbooks") end # Alias latest_cookbooks as list class << self alias :latest_cookbooks :list end def self.list_all_versions chef_server_rest.get("cookbooks?num_versions=all") end ## # Given a +cookbook_name+, get a list of all versions that exist on the # server. # ===Returns # [String]:: Array of cookbook versions, which are strings like 'x.y.z' # nil:: if the cookbook doesn't exist. an error will also be logged. def self.available_versions(cookbook_name) chef_server_rest.get("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map do |cb| cb["version"] end rescue Net::HTTPServerException => e if e.to_s =~ /^404/ Chef::Log.error("Cannot find a cookbook named #{cookbook_name}") nil else raise end end def <=>(other) raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != other.name # FIXME: can we change the interface to the Metadata class such # that metadata.version returns a Chef::Version instance instead # of a string? Chef::Version.new(self.version) <=> Chef::Version.new(other.version) end private def cookbook_manifest @cookbook_manifest ||= CookbookManifest.new(self) end def find_preferred_manifest_record(node, segment, filename) preferences = preferences_for_path(node, segment, filename) # in order of prefernce, look for the filename in the manifest preferences.find { |preferred_filename| manifest_records_by_path[preferred_filename] } end # For each filename, produce a mapping of base filename (i.e. recipe name # or attribute file) to on disk location def filenames_by_name(filenames) filenames.select { |filename| filename =~ /\.rb$/ }.inject({}) { |memo, filename| memo[File.basename(filename, ".rb")] = filename; memo } end def file_vendor unless @file_vendor @file_vendor = Chef::Cookbook::FileVendor.create_from_manifest(manifest) end @file_vendor end end end chef-12.14.60/lib/chef/daemon.rb000066400000000000000000000103231276456504500161320ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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. # I love you Merb (lib/merb-core/server.rb) require "chef/config" require "chef/run_lock" require "etc" class Chef class Daemon class << self attr_accessor :name attr_accessor :runlock # Daemonize the current process, managing pidfiles and process uid/gid # # === Parameters # name:: The name to be used for the pid file # def daemonize(name) @name = name @runlock = RunLock.new(pid_file) if runlock.test # We've acquired the daemon lock. Now daemonize. Chef::Log.info("Daemonizing..") begin exit if fork Process.setsid exit if fork Chef::Log.info("Forked, in #{Process.pid}. Privileges: #{Process.euid} #{Process.egid}") File.umask Chef::Config[:umask] $stdin.reopen("/dev/null") $stdout.reopen("/dev/null", "a") $stderr.reopen($stdout) runlock.save_pid rescue NotImplementedError => e Chef::Application.fatal!("There is no fork: #{e.message}") end else Chef::Application.fatal!("Chef is already running pid #{pid_from_file}") end end # Gets the pid file for @name # ==== Returns # String:: # Location of the pid file for @name def pid_file Chef::Config[:pid_file] || "/tmp/#{@name}.pid" end # Suck the pid out of pid_file # ==== Returns # Integer:: # The PID from pid_file # nil:: # Returned if the pid_file does not exist. # def pid_from_file File.read(pid_file).chomp.to_i rescue Errno::ENOENT, Errno::EACCES nil end # Change process user/group to those specified in Chef::Config # def change_privilege Dir.chdir("/") if Chef::Config[:user] && Chef::Config[:group] Chef::Log.info("About to change privilege to #{Chef::Config[:user]}:#{Chef::Config[:group]}") _change_privilege(Chef::Config[:user], Chef::Config[:group]) elsif Chef::Config[:user] Chef::Log.info("About to change privilege to #{Chef::Config[:user]}") _change_privilege(Chef::Config[:user]) end end # Change privileges of the process to be the specified user and group # # ==== Parameters # user:: The user to change the process to. # group:: The group to change the process to. # # ==== Alternatives # If group is left out, the user will be used (changing to user:user) # def _change_privilege(user, group = user) uid, gid = Process.euid, Process.egid begin target_uid = Etc.getpwnam(user).uid rescue ArgumentError => e Chef::Application.fatal!("Failed to get UID for user #{user}, does it exist? #{e.message}") return false end begin target_gid = Etc.getgrnam(group).gid rescue ArgumentError => e Chef::Application.fatal!("Failed to get GID for group #{group}, does it exist? #{e.message}") return false end if (uid != target_uid) || (gid != target_gid) Process.initgroups(user, target_gid) Process::GID.change_privilege(target_gid) Process::UID.change_privilege(target_uid) end true rescue Errno::EPERM => e Chef::Application.fatal!("Permission denied when trying to change #{uid}:#{gid} to #{target_uid}:#{target_gid}. #{e.message}") end end end end chef-12.14.60/lib/chef/data_bag.rb000066400000000000000000000117151276456504500164170ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Nuo Yan () # Author:: Christopher Brown () # Copyright:: Copyright 2009-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/mixin/params_validate" require "chef/mixin/from_file" require "chef/data_bag_item" require "chef/mash" require "chef/json_compat" require "chef/server_api" class Chef class DataBag include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate VALID_NAME = /^[\.\-[:alnum:]_]+$/ attr_accessor :chef_server_rest def self.validate_name!(name) unless name =~ VALID_NAME raise Exceptions::InvalidDataBagName, "DataBags must have a name matching #{VALID_NAME.inspect}, you gave #{name.inspect}" end end # Create a new Chef::DataBag def initialize(chef_server_rest: nil) @name = "" @chef_server_rest = chef_server_rest end def name(arg = nil) set_or_return( :name, arg, :regex => VALID_NAME ) end def to_hash result = { "name" => @name, "json_class" => self.class.name, "chef_type" => "data_bag", } result end # Serialize this object as a hash def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def chef_server_rest @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def self.chef_server_rest Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end # Create a Chef::Role from JSON def self.json_create(o) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::DataBag#from_hash") from_hash(o) end def self.from_hash(o) bag = new bag.name(o["name"]) bag end def self.list(inflate = false) if Chef::Config[:solo_legacy_mode] paths = Array(Chef::Config[:data_bag_path]) names = [] paths.each do |path| unless File.directory?(path) raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{path}' is invalid" end names += Dir.glob(File.join( Chef::Util::PathHelper.escape_glob_dir(path), "*")).map { |f| File.basename(f) }.sort end names.inject({}) { |h, n| h[n] = n; h } else if inflate # Can't search for all data bags like other objects, fall back to N+1 :( list(false).inject({}) do |response, bag_and_uri| response[bag_and_uri.first] = load(bag_and_uri.first) response end else Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data") end end end # Load a Data Bag by name via either the RESTful API or local data_bag_path if run in solo mode def self.load(name) if Chef::Config[:solo_legacy_mode] paths = Array(Chef::Config[:data_bag_path]) data_bag = {} paths.each do |path| unless File.directory?(path) raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{path}' is invalid" end Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(path, name.to_s), "*.json")).inject({}) do |bag, f| item = Chef::JSONCompat.parse(IO.read(f)) # Check if we have multiple items with similar names (ids) and raise if their content differs if data_bag.has_key?(item["id"]) && data_bag[item["id"]] != item raise Chef::Exceptions::DuplicateDataBagItem, "Data bag '#{name}' has items with the same name '#{item["id"]}' but different content." else data_bag[item["id"]] = item end end end return data_bag else Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data/#{name}") end end def destroy chef_server_rest.delete("data/#{@name}") end # Save the Data Bag via RESTful API def save begin if Chef::Config[:why_run] Chef::Log.warn("In why-run mode, so NOT performing data bag save.") else create end rescue Net::HTTPServerException => e raise e unless e.response.code == "409" end self end #create a data bag via RESTful API def create chef_server_rest.post("data", self) self end # As a string def to_s "data_bag[#{@name}]" end end end chef-12.14.60/lib/chef/data_bag_item.rb000066400000000000000000000135511276456504500174350ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Nuo Yan () # Author:: Christopher Brown () # Copyright:: Copyright 2009-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 "forwardable" require "chef/config" require "chef/mixin/params_validate" require "chef/mixin/from_file" require "chef/data_bag" require "chef/mash" require "chef/server_api" require "chef/json_compat" class Chef class DataBagItem extend Forwardable include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate VALID_ID = /^[\.\-[:alnum:]_]+$/ attr_accessor :chef_server_rest def self.validate_id!(id_str) if id_str.nil? || ( id_str !~ VALID_ID ) raise Exceptions::InvalidDataBagItemID, "Data Bag items must have an id matching #{VALID_ID.inspect}, you gave: #{id_str.inspect}" end end # Define all Hash's instance methods as delegating to @raw_data def_delegators(:@raw_data, *(Hash.instance_methods - Object.instance_methods)) attr_reader :raw_data # Create a new Chef::DataBagItem def initialize(chef_server_rest: nil) @data_bag = nil @raw_data = Mash.new @chef_server_rest = chef_server_rest end def chef_server_rest @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def self.chef_server_rest Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def raw_data @raw_data end def validate_id!(id_str) self.class.validate_id!(id_str) end def raw_data=(new_data) unless new_data.respond_to?(:[]) && new_data.respond_to?(:keys) raise Exceptions::ValidationFailed, "Data Bag Items must contain a Hash or Mash!" end validate_id!(new_data["id"]) @raw_data = new_data end def data_bag(arg = nil) set_or_return( :data_bag, arg, :regex => /^[\-[:alnum:]_]+$/ ) end def name object_name end def object_name raise Exceptions::ValidationFailed, "You must have an 'id' or :id key in the raw data" unless raw_data.has_key?("id") raise Exceptions::ValidationFailed, "You must have declared what bag this item belongs to!" unless data_bag id = raw_data["id"] "data_bag_item_#{data_bag}_#{id}" end def self.object_name(data_bag_name, id) "data_bag_item_#{data_bag_name}_#{id}" end def to_hash result = self.raw_data.dup result["chef_type"] = "data_bag_item" result["data_bag"] = self.data_bag.to_s result end # Serialize this object as a hash def to_json(*a) result = { "name" => object_name, "json_class" => self.class.name, "chef_type" => "data_bag_item", "data_bag" => data_bag, "raw_data" => raw_data, } Chef::JSONCompat.to_json(result, *a) end def self.from_hash(h) h.delete("chef_type") h.delete("json_class") item = new item.data_bag(h.delete("data_bag")) if h.key?("data_bag") if h.key?("raw_data") item.raw_data = Mash.new(h["raw_data"]) else item.raw_data = h end item end # Create a Chef::DataBagItem from JSON def self.json_create(o) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::DataBagItem#from_hash") from_hash(o) end # Load a Data Bag Item by name via either the RESTful API or local data_bag_path if run in solo mode def self.load(data_bag, name) if Chef::Config[:solo_legacy_mode] bag = Chef::DataBag.load(data_bag) raise Exceptions::InvalidDataBagItemID, "Item #{name} not found in data bag #{data_bag}. Other items found: #{bag.keys.join(", ")}" unless bag.include?(name) item = bag[name] else item = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data/#{data_bag}/#{name}") end if item.kind_of?(DataBagItem) item else item = from_hash(item) item.data_bag(data_bag) item end end def destroy(data_bag = self.data_bag(), databag_item = name) chef_server_rest.delete("data/#{data_bag}/#{databag_item}") end # Save this Data Bag Item via RESTful API def save(item_id = @raw_data["id"]) r = chef_server_rest begin if Chef::Config[:why_run] Chef::Log.warn("In why-run mode, so NOT performing data bag item save.") else r.put("data/#{data_bag}/#{item_id}", self) end rescue Net::HTTPServerException => e raise e unless e.response.code == "404" r.post("data/#{data_bag}", self) end self end # Create this Data Bag Item via RESTful API def create chef_server_rest.post("data/#{data_bag}", self) self end def ==(other) other.respond_to?(:to_hash) && other.respond_to?(:data_bag) && (other.to_hash == to_hash) && (other.data_bag.to_s == data_bag.to_s) end # As a string def to_s "data_bag_item[#{id}]" end def inspect "data_bag_item[#{data_bag.inspect}, #{raw_data['id'].inspect}, #{raw_data.inspect}]" end def pretty_print(pretty_printer) pretty_printer.pp({ "data_bag_item('#{data_bag}', '#{id}')" => self.to_hash }) end def id @raw_data["id"] end end end chef-12.14.60/lib/chef/data_collector.rb000066400000000000000000000344421276456504500176560ustar00rootroot00000000000000# # Author:: Adam Leff () # Author:: Ryan Cragun () # # 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" require "ostruct" 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 :all_resource_reports, :status, :exception, :error_descriptions, :expanded_run_list, :run_context, :run_status, :http, :current_resource_report, :enabled def initialize validate_data_collector_server_url! @all_resource_reports = [] @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#converge_start # Upon receipt, we stash the run_context for use at the # end of the run in order to determine what resource+action # combinations have not yet fired so we can report on # unprocessed resources. def converge_start(run_context) @run_context = run_context end # see EventDispatch::Base#converge_complete # At the end of the converge, we add any unprocessed resources # to our report list. def converge_complete detect_unprocessed_resources end # see EventDispatch::Base#converge_failed # At the end of the converge, we add any unprocessed resources # to our report list def converge_failed(exception) detect_unprocessed_resources 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(create_resource_report(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) resource_report = create_resource_report(new_resource, action) resource_report.skipped(conditional) update_current_resource_report(resource_report) 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). # 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_resource_report(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, Errno::EHOSTDOWN => 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.run_end_message( run_status: run_status, expanded_run_list: expanded_run_list, resources: all_resource_reports, 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_resource_report(resource_report) @all_resource_reports << OpenStruct.new( resource: resource_report.new_resource, action: resource_report.action, report_data: resource_report.to_hash ) 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 def create_resource_report(new_resource, action, current_resource = nil) Chef::DataCollector::ResourceReport.new( new_resource, action, current_resource ) end def detect_unprocessed_resources # create a Set containing all resource+action combinations from # the Resource Collection collection_resources = Set.new run_context.resource_collection.all_resources.each do |resource| Array(resource.action).each do |action| collection_resources.add([resource, action]) end end # Delete from the Set any resource+action combination we have # already processed. all_resource_reports.each do |report| collection_resources.delete([report.resource, report.action]) end # The items remaining in the Set are unprocessed resource+actions, # so we'll create new resource reports for them which default to # a state of "unprocessed". collection_resources.each do |resource, action| add_resource_report(create_resource_report(resource, action)) end 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 def validate_data_collector_server_url! raise Chef::Exceptions::ConfigurationError, "Chef::Config[:data_collector][:server_url] is empty. Please supply a valid URL." if data_collector_server_url.empty? begin uri = URI(data_collector_server_url) rescue URI::InvalidURIError raise Chef::Exceptions::ConfigurationError, "Chef::Config[:data_collector][:server_url] (#{data_collector_server_url}) is not a valid URI." end raise Chef::Exceptions::ConfigurationError, "Chef::Config[:data_collector][:server_url] (#{data_collector_server_url}) is a URI with no host. Please supply a valid URL." if uri.host.nil? end end end end chef-12.14.60/lib/chef/data_collector/000077500000000000000000000000001276456504500173225ustar00rootroot00000000000000chef-12.14.60/lib/chef/data_collector/messages.rb000066400000000000000000000072121276456504500214600ustar00rootroot00000000000000# # Author:: Adam Leff () # # 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" => run_status.node, "node_name" => run_status.node.name, "organization_name" => organization, "resources" => reporter_data[:resources].map(&:report_data), "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[:resources].count, "updated_resource_count" => reporter_data[:resources].select { |r| r.report_data["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 end end end chef-12.14.60/lib/chef/data_collector/messages/000077500000000000000000000000001276456504500211315ustar00rootroot00000000000000chef-12.14.60/lib/chef/data_collector/messages/helpers.rb000066400000000000000000000125111276456504500231200ustar00rootroot00000000000000# # Author:: Adam Leff () # # 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) updated_metadata = metadata.tap { |x| x[key] = value } Chef::FileCache.store(metadata_filename, updated_metadata.to_json, 0644) end def metadata_filename "data_collector_metadata.json" end end end end end chef-12.14.60/lib/chef/data_collector/resource_report.rb000066400000000000000000000057151276456504500231010ustar00rootroot00000000000000# # Author:: Adam Leff () # Author:: Ryan Cragun () # # 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, :elapsed_time, :new_resource, :status attr_accessor :conditional, :current_resource, :exception def initialize(new_resource, action, current_resource = nil) @new_resource = new_resource @action = action @current_resource = current_resource @status = "unprocessed" 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 elapsed_time_in_milliseconds elapsed_time.nil? ? nil : (elapsed_time * 1000).to_i end def potentially_changed? %w{updated failed}.include?(status) 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_in_milliseconds.to_s, "delta" => new_resource.respond_to?(:diff) && potentially_changed? ? new_resource.diff : "", "ignore_failure" => new_resource.ignore_failure, "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 hash["recipe_name"] = new_resource.recipe_name 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 chef-12.14.60/lib/chef/decorator.rb000066400000000000000000000045671276456504500166660ustar00rootroot00000000000000#-- # 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 "delegate" class Chef class Decorator < SimpleDelegator NULL = ::Object.new def initialize(obj = NULL) @__defined_methods__ = [] super unless obj.equal?(NULL) end # if we wrap a nil then decorator.nil? should be true def nil? __getobj__.nil? end # if we wrap a Hash then decorator.is_a?(Hash) should be true def is_a?(klass) __getobj__.is_a?(klass) || super end # if we wrap a Hash then decorator.kind_of?(Hash) should be true def kind_of?(klass) __getobj__.kind_of?(klass) || super end # reset our methods on the instance if the object changes under us (this also # clears out the closure over the target we create in method_missing below) def __setobj__(obj) __reset_methods__ super end # this is the ruby 2.2/2.3 implementation of Delegator#method_missing() with # adding the define_singleton_method call and @__defined_methods__ tracking def method_missing(m, *args, &block) r = true target = self.__getobj__ { r = false } if r && target.respond_to?(m) # these next 4 lines are the patched code define_singleton_method(m) do |*args, &block| target.__send__(m, *args, &block) end @__defined_methods__.push(m) target.__send__(m, *args, &block) elsif ::Kernel.respond_to?(m, true) ::Kernel.instance_method(m).bind(self).call(*args, &block) else super(m, *args, &block) end end private # used by __setobj__ to clear the methods we've built on the instance. def __reset_methods__ @__defined_methods__.each do |m| singleton_class.send(:undef_method, m) end @__defined_methods__ = [] end end end chef-12.14.60/lib/chef/decorator/000077500000000000000000000000001276456504500163255ustar00rootroot00000000000000chef-12.14.60/lib/chef/decorator/lazy.rb000066400000000000000000000026111276456504500176310ustar00rootroot00000000000000#-- # 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/decorator" class Chef class Decorator # Lazy wrapper to delay construction of an object until a method is # called against the object. # # @example # # def foo # puts "allocated" # "value" # end # # a = Chef::Decorator::Lazy.new { foo } # # puts "started" # a # puts "still lazy" # puts a # # outputs: # # started # still lazy # allocated # value # # @since 12.10.x class Lazy < Decorator def initialize(&block) super @block = block end def __getobj__ __setobj__(@block.call) unless defined?(@delegate_sd_obj) super end end end end chef-12.14.60/lib/chef/decorator/lazy_array.rb000066400000000000000000000032211276456504500210250ustar00rootroot00000000000000#-- # 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/decorator/lazy" class Chef class Decorator # Lazy Array around Lazy Objects # # This only lazys access through `#[]`. In order to implement #each we need to # know how many items we have and what their indexes are, so we'd have to evalute # the proc which makes that impossible. You can call methods like #each and the # decorator will forward the method, but item access will not be lazy. # # #at() and #fetch() are not implemented but technically could be. # # @example # def foo # puts "allocated" # "value" # end # # a = Chef::Decorator::LazyArray.new { [ foo ] } # # puts "started" # a[0] # puts "still lazy" # puts a[0] # # outputs: # # started # still lazy # allocated # value # # @since 12.10.x class LazyArray < Lazy def [](idx) block = @block Lazy.new { block.call[idx] } end end end end chef-12.14.60/lib/chef/decorator/unchain.rb000066400000000000000000000041071276456504500203010ustar00rootroot00000000000000class Chef class Decorator < SimpleDelegator # # This decorator unchains method call chains and turns them into method calls # with variable args. So this: # # node.set_unless["foo"]["bar"] = "baz" # # Can become: # # node.set_unless("foo", "bar", "baz") # # While this is a decorator it is not a Decorator and does not inherit because # it deliberately does not need or want the method_missing magic. It is not legal # to call anything on the intermediate values and only supports method chaining with # #[] until the chain comes to an end with #[]=, so does not behave like a hash or # array... e.g. # # node.default['foo'].keys is legal # node.set_unless['foo'].keys is not legal now or ever # class Unchain attr_accessor :__path__ attr_accessor :__method__ def initialize(obj, method) @__path__ = [] @__method__ = method @delegate_sd_obj = obj end def [](key) __path__.push(key) self end def []=(key, value) __path__.push(key) @delegate_sd_obj.public_send(__method__, *__path__, value) end # unfortunately we have to support method_missing for node.set_unless.foo.bar = 'baz' notation def method_missing(symbol, *args) if symbol == :to_ary merged_attributes.send(symbol, *args) elsif args.empty? Chef.log_deprecation %q{method access to node attributes (node.foo.bar) is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]["bar"])} self[symbol] elsif symbol.to_s =~ /=$/ Chef.log_deprecation %q{method setting of node attributes (node.foo="bar") is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]="bar")} key_to_set = symbol.to_s[/^(.+)=$/, 1] self[key_to_set] = (args.length == 1 ? args[0] : args) else raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'" end end end end end chef-12.14.60/lib/chef/delayed_evaluator.rb000066400000000000000000000013411276456504500203600ustar00rootroot00000000000000# # Author:: John Keiser # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class Chef class DelayedEvaluator < Proc end end chef-12.14.60/lib/chef/deprecation/000077500000000000000000000000001276456504500166405ustar00rootroot00000000000000chef-12.14.60/lib/chef/deprecation/mixin/000077500000000000000000000000001276456504500177645ustar00rootroot00000000000000chef-12.14.60/lib/chef/deprecation/mixin/template.rb000066400000000000000000000027571276456504500221370ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-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 "tempfile" require "erubis" class Chef module Deprecation module Mixin # == Deprecation::Provider::Mixin::Template # This module contains the deprecated functions of # Chef::Mixin::Template. These functions are refactored to different # components. They are frozen and will be removed in Chef 13. # module Template def render_template(template, context) begin eruby = Erubis::Eruby.new(template) output = eruby.evaluate(context) rescue Object => e raise TemplateError.new(e, template, context) end Tempfile.open("chef-rendered-template") do |tempfile| tempfile.print(output) tempfile.close yield tempfile end end end end end end chef-12.14.60/lib/chef/deprecation/provider/000077500000000000000000000000001276456504500204725ustar00rootroot00000000000000chef-12.14.60/lib/chef/deprecation/provider/cookbook_file.rb000066400000000000000000000032561276456504500236320ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Deprecation module Provider # == Deprecation::Provider::CookbookFile # This module contains the deprecated functions of # Chef::Provider::CookbookFile. These functions are refactored to # different components. They are frozen and will be removed in Chef 13. # module CookbookFile def file_cache_location @file_cache_location ||= begin cookbook = run_context.cookbook_collection[resource_cookbook] cookbook.preferred_filename_on_disk_location(node, :files, @new_resource.source, @new_resource.path) end end def resource_cookbook @new_resource.cookbook || @new_resource.cookbook_name end def content_stale? ( ! ::File.exist?(@new_resource.path)) || ( ! compare_content) end def backup_new_resource if ::File.exists?(@new_resource.path) backup @new_resource.path end end end end end end chef-12.14.60/lib/chef/deprecation/provider/file.rb000066400000000000000000000176701276456504500217510ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-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/util/path_helper" class Chef module Deprecation module Provider # == Deprecation::Provider::File # This module contains the deprecated functions of # Chef::Provider::File. These functions are refactored to different # components. They are frozen and will be removed in Chef 13. # module File def diff_current_from_content(new_content) result = nil Tempfile.open("chef-diff") do |file| file.write new_content file.close result = diff_current file.path end result end def is_binary?(path) ::File.open(path) do |file| buff = file.read(Chef::Config[:diff_filesize_threshold]) buff = "" if buff.nil? return buff !~ /^[\r[:print:]]*$/ end end def diff_current(temp_path) suppress_resource_reporting = false return [ "(diff output suppressed by config)" ] if Chef::Config[:diff_disabled] return [ "(no temp file with new content, diff output suppressed)" ] unless ::File.exists?(temp_path) # should never happen? # solaris does not support diff -N, so create tempfile to diff against if we are creating a new file target_path = if ::File.exists?(@current_resource.path) @current_resource.path else suppress_resource_reporting = true # suppress big diffs going to resource reporting service tempfile = Tempfile.new("chef-tempfile") tempfile.path end diff_filesize_threshold = Chef::Config[:diff_filesize_threshold] diff_output_threshold = Chef::Config[:diff_output_threshold] if ::File.size(target_path) > diff_filesize_threshold || ::File.size(temp_path) > diff_filesize_threshold return [ "(file sizes exceed #{diff_filesize_threshold} bytes, diff output suppressed)" ] end # MacOSX(BSD?) diff will *sometimes* happily spit out nasty binary diffs return [ "(current file is binary, diff output suppressed)"] if is_binary?(target_path) return [ "(new content is binary, diff output suppressed)"] if is_binary?(temp_path) begin # -u: Unified diff format result = shell_out("diff -u #{target_path} #{temp_path}" ) rescue Exception => e # Should *not* receive this, but in some circumstances it seems that # an exception can be thrown even using shell_out instead of shell_out! return [ "Could not determine diff. Error: #{e.message}" ] end # diff will set a non-zero return code even when there's # valid stdout results, if it encounters something unexpected # So as long as we have output, we'll show it. if not result.stdout.empty? if result.stdout.length > diff_output_threshold [ "(long diff of over #{diff_output_threshold} characters, diff output suppressed)" ] else val = result.stdout.split("\n") val.delete("\\ No newline at end of file") @new_resource.diff(val.join("\\n")) unless suppress_resource_reporting val end elsif not result.stderr.empty? [ "Could not determine diff. Error: #{result.stderr}" ] else [ "(no diff)" ] end end def setup_acl return if Chef::Platform.windows? acl_scanner = ScanAccessControl.new(@new_resource, @current_resource) acl_scanner.set_all! end def compare_content checksum(@current_resource.path) == new_resource_content_checksum end def set_content unless compare_content description = [] description << "update content in file #{@new_resource.path} from #{short_cksum(@current_resource.checksum)} to #{short_cksum(new_resource_content_checksum)}" description << diff_current_from_content(@new_resource.content) converge_by(description) do backup @new_resource.path if ::File.exists?(@new_resource.path) ::File.open(@new_resource.path, "w") { |f| f.write @new_resource.content } Chef::Log.info("#{@new_resource} contents updated") end end end def update_new_file_state(path = @new_resource.path) if !::File.directory?(path) @new_resource.checksum(checksum(path)) end if Chef::Platform.windows? # TODO: To work around CHEF-3554, add support for Windows # equivalent, or implicit resource reporting won't work for # Windows. return end acl_scanner = ScanAccessControl.new(@new_resource, @new_resource) acl_scanner.set_all! end def set_all_access_controls if access_controls.requires_changes? converge_by(access_controls.describe_changes) do access_controls.set_all #Update file state with new access values update_new_file_state end end end def deploy_tempfile Tempfile.open(::File.basename(@new_resource.name)) do |tempfile| yield tempfile temp_res = Chef::Resource::CookbookFile.new(@new_resource.name) temp_res.path(tempfile.path) ac = Chef::FileAccessControl.new(temp_res, @new_resource, self) ac.set_all! FileUtils.mv(tempfile.path, @new_resource.path) end end def backup(file = nil) file ||= @new_resource.path if @new_resource.backup != false && @new_resource.backup > 0 && ::File.exist?(file) time = Time.now savetime = time.strftime("%Y%m%d%H%M%S") backup_filename = "#{@new_resource.path}.chef-#{savetime}" backup_filename = backup_filename.sub(/^([A-Za-z]:)/, "") #strip drive letter on Windows # if :file_backup_path is nil, we fallback to the old behavior of # keeping the backup in the same directory. We also need to to_s it # so we don't get a type error around implicit to_str conversions. prefix = Chef::Config[:file_backup_path].to_s backup_path = ::File.join(prefix, backup_filename) FileUtils.mkdir_p(::File.dirname(backup_path)) if Chef::Config[:file_backup_path] FileUtils.cp(file, backup_path, :preserve => true) Chef::Log.info("#{@new_resource} backed up to #{backup_path}") # Clean up after the number of backups slice_number = @new_resource.backup backup_files = Dir[Chef::Util::PathHelper.escape_glob_dir(prefix, ".#{@new_resource.path}") + ".chef-*"].sort { |a, b| b <=> a } if backup_files.length >= @new_resource.backup remainder = backup_files.slice(slice_number..-1) remainder.each do |backup_to_delete| FileUtils.rm(backup_to_delete) Chef::Log.info("#{@new_resource} removed backup at #{backup_to_delete}") end end end end end end end end chef-12.14.60/lib/chef/deprecation/provider/remote_directory.rb000066400000000000000000000034061276456504500244010ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Deprecation module Provider module RemoteDirectory def directory_root_in_cookbook_cache Chef.log_deprecation "the Chef::Provider::RemoteDirectory#directory_root_in_cookbook_cache method is deprecated" @directory_root_in_cookbook_cache ||= begin cookbook = run_context.cookbook_collection[resource_cookbook] cookbook.preferred_filename_on_disk_location(node, :files, source, path) end end # List all excluding . and .. def ls(path) files = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob_dir(path), "**", "*"), ::File::FNM_DOTMATCH) # Remove current directory and previous directory files = files.reject do |name| basename = Pathname.new(name).basename().to_s [".", ".."].include?(basename) end # Clean all the paths... this is required because of the join files.map { |f| Chef::Util::PathHelper.cleanpath(f) } end end end end end chef-12.14.60/lib/chef/deprecation/provider/remote_file.rb000066400000000000000000000063741276456504500233230ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Deprecation module Provider # == Deprecation::Provider::RemoteFile # This module contains the deprecated functions of # Chef::Provider::RemoteFile. These functions are refactored to different # components. They are frozen and will be removed in Chef 13. # module RemoteFile def current_resource_matches_target_checksum? @new_resource.checksum && @current_resource.checksum && @current_resource.checksum =~ /^#{Regexp.escape(@new_resource.checksum)}/ end def matches_current_checksum?(candidate_file) Chef::Log.debug "#{@new_resource} checking for file existence of #{@new_resource.path}" if ::File.exists?(@new_resource.path) Chef::Log.debug "#{@new_resource} file exists at #{@new_resource.path}" @new_resource.checksum(checksum(candidate_file.path)) Chef::Log.debug "#{@new_resource} target checksum: #{@current_resource.checksum}" Chef::Log.debug "#{@new_resource} source checksum: #{@new_resource.checksum}" @new_resource.checksum == @current_resource.checksum else Chef::Log.debug "#{@new_resource} creating #{@new_resource.path}" false end end def backup_new_resource if ::File.exists?(@new_resource.path) Chef::Log.debug "#{@new_resource} checksum changed from #{@current_resource.checksum} to #{@new_resource.checksum}" backup @new_resource.path end end def source_file(source, current_checksum, &block) if absolute_uri?(source) fetch_from_uri(source, &block) elsif !Chef::Config[:solo_legacy_mode] fetch_from_chef_server(source, current_checksum, &block) else fetch_from_local_cookbook(source, &block) end end def http_client_opts(source) opts = {} # CHEF-3140 # 1. If it's already compressed, trying to compress it more will # probably be counter-productive. # 2. Some servers are misconfigured so that you GET $URL/file.tgz but # they respond with content type of tar and content encoding of gzip, # which tricks Chef::REST into decompressing the response body. In this # case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz, # which is not what you wanted. if @new_resource.path =~ /gz$/ || source =~ /gz$/ opts[:disable_gzip] = true end opts end end end end end chef-12.14.60/lib/chef/deprecation/provider/template.rb000066400000000000000000000040001276456504500226240ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-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/deprecation/mixin/template" class Chef module Deprecation module Provider # == Deprecation::Provider::Template # This module contains the deprecated functions of # Chef::Provider::Template. These functions are refactored to different # components. They are frozen and will be removed in Chef 13. # module Template include Chef::Deprecation::Mixin::Template def template_finder @template_finder ||= begin Chef::Provider::TemplateFinder.new(run_context, cookbook_name, node) end end def template_location @template_file_cache_location ||= begin template_finder.find(@new_resource.source, :local => @new_resource.local, :cookbook => @new_resource.cookbook) end end def resource_cookbook @new_resource.cookbook || @new_resource.cookbook_name end def rendered(rendered_template) @new_resource.checksum(checksum(rendered_template.path)) Chef::Log.debug("Current content's checksum: #{@current_resource.checksum}") Chef::Log.debug("Rendered content's checksum: #{@new_resource.checksum}") end def content_matches? @current_resource.checksum == @new_resource.checksum end end end end end chef-12.14.60/lib/chef/deprecation/warnings.rb000066400000000000000000000022501276456504500210140ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Deprecation module Warnings def add_deprecation_warnings_for(method_names) method_names.each do |name| define_method(name) do |*args| message = [] message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 13." message << "Please update your cookbooks accordingly." Chef.log_deprecation(message) super(*args) end end end end end end chef-12.14.60/lib/chef/digester.rb000066400000000000000000000035441276456504500165040ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Chef Software Inc. # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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 "openssl" require "singleton" class Chef class Digester include Singleton def self.checksum_for_file(*args) instance.checksum_for_file(*args) end def validate_checksum(*args) self.class.validate_checksum(*args) end def checksum_for_file(file) generate_checksum(file) end def generate_checksum(file) if file.is_a?(StringIO) checksum_io(file, OpenSSL::Digest::SHA256.new) else checksum_file(file, OpenSSL::Digest::SHA256.new) end end def self.generate_md5_checksum_for_file(*args) instance.generate_md5_checksum_for_file(*args) end def generate_md5_checksum_for_file(file) checksum_file(file, OpenSSL::Digest::MD5.new) end def generate_md5_checksum(io) checksum_io(io, OpenSSL::Digest::MD5.new) end private def checksum_file(file, digest) File.open(file, "rb") { |f| checksum_io(f, digest) } end def checksum_io(io, digest) while chunk = io.read(1024 * 8) digest.update(chunk) end digest.hexdigest end end end chef-12.14.60/lib/chef/dsl.rb000066400000000000000000000003141276456504500154500ustar00rootroot00000000000000require "chef/dsl/recipe" require "chef/dsl/platform_introspection" require "chef/dsl/data_query" require "chef/dsl/include_recipe" require "chef/dsl/include_attribute" require "chef/dsl/registry_helper" chef-12.14.60/lib/chef/dsl/000077500000000000000000000000001276456504500151255ustar00rootroot00000000000000chef-12.14.60/lib/chef/dsl/audit.rb000066400000000000000000000034161276456504500165640ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/exceptions" class Chef module DSL module Audit # Can encompass tests in a `control` block or `describe` block # Adds the controls group and block (containing controls to execute) to the runner's list of pending examples def control_group(*args, &block) raise Chef::Exceptions::NoAuditsProvided unless block name = args[0] if name.nil? || name.empty? raise Chef::Exceptions::AuditNameMissing elsif run_context.audits.has_key?(name) raise Chef::Exceptions::AuditControlGroupDuplicate.new(name) end # This DSL will only work in the Recipe class because that exposes the cookbook_name cookbook_name = self.cookbook_name metadata = { cookbook_name: cookbook_name, cookbook_version: self.run_context.cookbook_collection[cookbook_name].version, recipe_name: self.recipe_name, line_number: block.source_location[1], } run_context.audits[name] = Struct.new(:args, :block, :metadata).new(args, block, metadata) end end end end chef-12.14.60/lib/chef/dsl/chef_provisioning.rb000066400000000000000000000036071276456504500211730ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module DSL # Lazy activation for the chef-provisioning gem. Specifically, we set up methods for # each resource and DSL method in chef-provisioning which, when invoked, will # require 'chef-provisioning' (which will define the actual method) and then call the # method chef-provisioning defined. module ChefProvisioning %w{ add_machine_options current_image_options current_machine_options load_balancer machine_batch machine_execute machine_file machine_image machine with_driver with_image_options with_machine_options }.each do |method_name| eval(<<-EOM, binding, __FILE__, __LINE__ + 1) def #{method_name}(*args, &block) Chef::DSL::ChefProvisioning.load_chef_provisioning self.#{method_name}(*args, &block) end EOM end def self.load_chef_provisioning # Remove all chef-provisioning methods; they will be added back in by chef-provisioning public_instance_methods(false).each do |method_name| remove_method(method_name) end require "chef/provisioning" end end end end chef-12.14.60/lib/chef/dsl/cheffish.rb000066400000000000000000000037311276456504500172350ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module DSL # Lazy activation for the cheffish gem. Specifically, we set up methods for # each resource and DSL method in cheffish which, when invoked, will # require 'cheffish' (which will define the actual method) and then call the # method cheffish defined. module Cheffish %w{ chef_acl chef_client chef_container chef_data_bag_item chef_data_bag chef_environment chef_group chef_mirror chef_node chef_organization chef_role chef_user private_key public_key with_chef_data_bag with_chef_environment with_chef_data_bag_item_encryption with_chef_server with_chef_local_server get_private_key }.each do |method_name| eval(<<-EOM, binding, __FILE__, __LINE__ + 1) def #{method_name}(*args, &block) Chef::DSL::Cheffish.load_cheffish self.#{method_name}(*args, &block) end EOM end def self.load_cheffish # Remove all cheffish methods; they will be added back in by cheffish public_instance_methods(false).each do |method_name| remove_method(method_name) end require "cheffish" end end end end chef-12.14.60/lib/chef/dsl/core.rb000066400000000000000000000042041276456504500164020ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Christopher Walters () # Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/dsl/declare_resource" require "chef/dsl/universal" require "chef/mixin/notifying_block" require "chef/mixin/lazy_module_include" class Chef module DSL # Part of a family of DSL mixins. # # Chef::DSL::Recipe mixes into Recipes and LWRP Providers. # - this does not target core chef resources and providers. # - this is restricted to recipe/resource/provider context where a resource collection exists. # - cookbook authors should typically include modules into here. # # Chef::DSL::Core mixes into Recipes, LWRP Providers and Core Providers # - this adds cores providers on top of the Recipe DSL. # - this is restricted to recipe/resource/provider context where a resource collection exists. # - core chef authors should typically include modules into here. # # Chef::DSL::Universal mixes into Recipes, LWRP Resources+Providers, Core Resources+Providers, and Attributes files. # - this adds resources and attributes files. # - do not add helpers which manipulate the resource collection. # - this is for general-purpose stuff that is useful nearly everywhere. # - it also pollutes the namespace of nearly every context, watch out. # module Core include Chef::DSL::Universal include Chef::DSL::DeclareResource include Chef::Mixin::NotifyingBlock extend Chef::Mixin::LazyModuleInclude end end end chef-12.14.60/lib/chef/dsl/data_query.rb000066400000000000000000000054711276456504500176170ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/search/query" require "chef/data_bag" require "chef/data_bag_item" require "chef/encrypted_data_bag_item" require "chef/encrypted_data_bag_item/check_encrypted" class Chef module DSL # ==Chef::DSL::DataQuery # Provides DSL for querying data from the chef-server via search or data # bag. module DataQuery include Chef::EncryptedDataBagItem::CheckEncrypted def search(*args, &block) # If you pass a block, or have at least the start argument, do raw result parsing # # Otherwise, do the iteration for the end user if Kernel.block_given? || args.length >= 4 Chef::Search::Query.new.search(*args, &block) else results = Array.new Chef::Search::Query.new.search(*args) do |o| results << o end results end end def data_bag(bag) DataBag.validate_name!(bag.to_s) rbag = DataBag.load(bag) rbag.keys rescue Exception Log.error("Failed to list data bag items in data bag: #{bag.inspect}") raise end def data_bag_item(bag, item, secret = nil) DataBag.validate_name!(bag.to_s) DataBagItem.validate_id!(item) item = DataBagItem.load(bag, item) if encrypted?(item.raw_data) Log.debug("Data bag item looks encrypted: #{bag.inspect} #{item.inspect}") # Try to load the data bag item secret, if secret is not provided. # Chef::EncryptedDataBagItem.load_secret may throw a variety of errors. begin secret ||= EncryptedDataBagItem.load_secret item = EncryptedDataBagItem.new(item.raw_data, secret) rescue Exception Log.error("Failed to load secret for encrypted data bag item: #{bag.inspect} #{item.inspect}") raise end end item rescue Exception Log.error("Failed to load data bag item: #{bag.inspect} #{item.inspect}") raise end end end end # **DEPRECATED** # This used to be part of chef/mixin/language. Load the file to activate the deprecation code. require "chef/mixin/language" chef-12.14.60/lib/chef/dsl/declare_resource.rb000066400000000000000000000314121276456504500207610ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Christopher Walters # Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/exceptions" class Chef module DSL module DeclareResource # Helper for switching run_contexts. Allows for using :parent or :root in place of # passing the run_context. Executes the block in the run_context. Returns the return # value of the passed block. # # @param rc [Chef::RunContext,Symbol] Either :root, :parent or a Chef::RunContext # # @return return value of the block # # @example # # creates/returns a 'service[foo]' resource in the root run_context # resource = with_run_context(:root) # edit_resource(:service, "foo") do # action :nothing # end # end # def with_run_context(rc) raise ArgumentError, "with_run_context is useless without a block" unless block_given? old_run_context = @run_context @run_context = case rc when Chef::RunContext rc when :root Chef.run_context when :parent run_context.parent_run_context else raise ArgumentError, "bad argument to run_context helper, must be :root, :parent, or a Chef::RunContext" end yield ensure @run_context = old_run_context end # Lookup a resource in the resource collection by name and delete it. This # will raise Chef::Exceptions::ResourceNotFound if the resource is not found. # # @param type [Symbol] The type of resource (e.g. `:file` or `:package`) # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2') # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on # # @return [Chef::Resource] The resource # # @example # delete_resource!(:template, '/x/y.txy') # def delete_resource!(type, name, run_context: self.run_context) run_context.resource_collection.delete("#{type}[#{name}]").tap do |resource| # Purge any pending notifications too. This will not raise an exception # if there are no notifications. if resource run_context.before_notification_collection.delete(resource.declared_key) run_context.immediate_notification_collection.delete(resource.declared_key) run_context.delayed_notification_collection.delete(resource.declared_key) end end end # Lookup a resource in the resource collection by name and delete it. Returns # nil if the resource is not found and should not fail. # # @param type [Symbol] The type of resource (e.g. `:file` or `:package`) # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2') # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on # # @return [Chef::Resource] The resource # # @example # delete_resource(:template, '/x/y.txy') # def delete_resource(type, name, run_context: self.run_context) delete_resource!(type, name, run_context: run_context) rescue Chef::Exceptions::ResourceNotFound nil end # Lookup a resource in the resource collection by name and edit the resource. If the resource is not # found this will raise Chef::Exceptions::ResourceNotFound. This is the correct API to use for # "chef_rewind" functionality. # # @param type [Symbol] The type of resource (e.g. `:file` or `:package`) # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2') # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on # @param resource_attrs_block A block that lets you set attributes of the # resource (it is instance_eval'd on the resource instance). # # @return [Chef::Resource] The updated resource # # @example # edit_resource!(:template, '/x/y.txy') do # cookbook_name: cookbook_name # end # def edit_resource!(type, name, created_at = nil, run_context: self.run_context, &resource_attrs_block) resource = find_resource!(type, name, run_context: run_context) resource.instance_eval(&resource_attrs_block) if block_given? resource end # Lookup a resource in the resource collection by name. If it exists, # return it. If it does not exist, create it. This is a useful function # for accumulator patterns. In CRUD terminology this is an "upsert" operation and is # used to assert that the resource must exist with the specified properties. # # @param type [Symbol] The type of resource (e.g. `:file` or `:package`) # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2') # @param created_at [String] The caller of the resource. Use `caller[0]` # to get the caller of your function. Defaults to the caller of this # function. # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on # @param resource_attrs_block A block that lets you set attributes of the # resource (it is instance_eval'd on the resource instance). # # @return [Chef::Resource] The updated or created resource # # @example # resource = edit_resource(:template, '/x/y.txy') do # source "y.txy.erb" # variables {} # end # resource.variables.merge!({ home: "/home/klowns" }) # def edit_resource(type, name, created_at = nil, run_context: self.run_context, &resource_attrs_block) edit_resource!(type, name, created_at, run_context: run_context, &resource_attrs_block) rescue Chef::Exceptions::ResourceNotFound declare_resource(type, name, created_at, run_context: run_context, &resource_attrs_block) end # Lookup a resource in the resource collection by name. If the resource is not # found this will raise Chef::Exceptions::ResourceNotFound. This API is identical to the # resources() call and while it is a synonym it is not intended to deprecate that call. # # @param type [Symbol] The type of resource (e.g. `:file` or `:package`) # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2') # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on # # @return [Chef::Resource] The updated resource # # @example # resource = find_resource!(:template, '/x/y.txy') # def find_resource!(type, name, run_context: self.run_context) raise ArgumentError, "find_resource! does not take a block" if block_given? run_context.resource_collection.find(type => name) end # Lookup a resource in the resource collection by name. If the resource is not found # the will be no exception raised and the call will return nil. If a block is given and # no resource is found it will create the resource using the block, if the resource is # found then the block will not be applied. The block version is similar to create_if_missing # # @param type [Symbol] The type of resource (e.g. `:file` or `:package`) # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2') # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on # # @return [Chef::Resource] The updated resource # # @example # if ( find_resource(:template, '/x/y.txy') ) # # do something # else # # don't worry about the error # end # # @example # # this API can be used to return a resource from an outer run context, and will only create # # an action :nothing service if one does not already exist. # resource = with_run_context(:root) do # find_resource(:service, 'whatever') do # action :nothing # end # end # def find_resource(type, name, created_at: nil, run_context: self.run_context, &resource_attrs_block) find_resource!(type, name, run_context: run_context) rescue Chef::Exceptions::ResourceNotFound if block_given? declare_resource(type, name, created_at, run_context: run_context, &resource_attrs_block) end # returns nil otherwise end # Instantiates a resource (via #build_resource), then adds it to the # resource collection. Note that resource classes are looked up directly, # so this will create the resource you intended even if the method name # corresponding to that resource has been overridden. # # @param type [Symbol] The type of resource (e.g. `:file` or `:package`) # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2') # @param created_at [String] The caller of the resource. Use `caller[0]` # to get the caller of your function. Defaults to the caller of this # function. # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on # @param resource_attrs_block A block that lets you set attributes of the # resource (it is instance_eval'd on the resource instance). # # @return [Chef::Resource] The new resource. # # @example # declare_resource(:file, '/x/y.txy', caller[0]) do # action :delete # end # # Equivalent to # file '/x/y.txt' do # action :delete # end # def declare_resource(type, name, created_at = nil, run_context: self.run_context, create_if_missing: false, &resource_attrs_block) created_at ||= caller[0] if create_if_missing Chef::Log.deprecation "build_resource with a create_if_missing flag is deprecated, use edit_resource instead" # midly goofy since we call edit_resource only to re-call ourselves, but that's why its deprecated... return edit_resource(type, name, created_at, run_context: run_context, &resource_attrs_block) end resource = build_resource(type, name, created_at, &resource_attrs_block) run_context.resource_collection.insert(resource, resource_type: type, instance_name: name) resource end # Instantiate a resource of the given +type+ with the given +name+ and # attributes as given in the +resource_attrs_block+. # # The resource is NOT added to the resource collection. # # @param type [Symbol] The type of resource (e.g. `:file` or `:package`) # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2') # @param created_at [String] The caller of the resource. Use `caller[0]` # to get the caller of your function. Defaults to the caller of this # function. # @param run_context [Chef::RunContext] the run_context of the resource collection to operate on # @param resource_attrs_block A block that lets you set attributes of the # resource (it is instance_eval'd on the resource instance). # # @return [Chef::Resource] The new resource. # # @example # build_resource(:file, '/x/y.txy', caller[0]) do # action :delete # end # def build_resource(type, name, created_at = nil, run_context: self.run_context, &resource_attrs_block) created_at ||= caller[0] # this needs to be lazy in order to avoid circular dependencies since ResourceBuilder # will requires the entire provider+resolver universe require "chef/resource_builder" unless defined?(Chef::ResourceBuilder) Chef::ResourceBuilder.new( type: type, name: name, created_at: created_at, params: @params, run_context: run_context, cookbook_name: cookbook_name, recipe_name: recipe_name, enclosing_provider: self.is_a?(Chef::Provider) ? self : nil ).build(&resource_attrs_block) end end end end chef-12.14.60/lib/chef/dsl/definitions.rb000066400000000000000000000024401276456504500177650ustar00rootroot00000000000000class Chef module DSL # # Module containing a method for each declared definition # # Depends on declare_resource(name, created_at, &block) # # @api private # module Definitions def self.add_definition(dsl_name) module_eval <<-EOM, __FILE__, __LINE__ + 1 def #{dsl_name}(*args, &block) evaluate_resource_definition(#{dsl_name.inspect}, *args, &block) end EOM end # @api private def has_resource_definition?(name) run_context.definitions.has_key?(name) end # Processes the arguments and block as a resource definition. # # @api private def evaluate_resource_definition(definition_name, *args, &block) # This dupes the high level object, but we still need to dup the params new_def = run_context.definitions[definition_name].dup new_def.params = new_def.params.dup new_def.node = run_context.node # This sets up the parameter overrides new_def.instance_eval(&block) if block new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context) new_recipe.params = new_def.params new_recipe.params[:name] = args[0] new_recipe.instance_eval(&new_def.recipe) end end end end chef-12.14.60/lib/chef/dsl/include_attribute.rb000066400000000000000000000043051276456504500211620ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" class Chef module DSL module IncludeAttribute # Loads the attribute file specified by the short name of the # file, e.g., loads specified cookbook's # "attributes/mailservers.rb" # if passed # "mailservers" def include_attribute(*attr_file_specs) attr_file_specs.flatten.each do |attr_file_spec| cookbook_name, attr_file = parse_attribute_file_spec(attr_file_spec) if run_context.loaded_fully_qualified_attribute?(cookbook_name, attr_file) Chef::Log.debug("I am not loading attribute file #{cookbook_name}::#{attr_file}, because I have already seen it.") else Chef::Log.debug("Loading Attribute #{cookbook_name}::#{attr_file}") run_context.loaded_attribute(cookbook_name, attr_file) attr_file_path = run_context.resolve_attribute(cookbook_name, attr_file) node.from_file(attr_file_path) end end true end # Takes a attribute file specification, like "apache2" or "mysql::server" # and converts it to a 2 element array of [cookbook_name, attribute_file_name] def parse_attribute_file_spec(file_spec) if match = file_spec.match(/(.+?)::(.+)/) [match[1], match[2]] else [file_spec, "default"] end end end end end # **DEPRECATED** # This used to be part of chef/mixin/language_include_attribute. Load the file to activate the deprecation code. require "chef/mixin/language_include_attribute" chef-12.14.60/lib/chef/dsl/include_recipe.rb000066400000000000000000000025661276456504500204350ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" class Chef module DSL module IncludeRecipe def include_recipe(*recipe_names) run_context.include_recipe(*recipe_names, current_cookbook: cookbook_name) end def load_recipe(recipe_name) run_context.load_recipe(recipe_name, current_cookbook: cookbook_name) end def require_recipe(*args) Chef::Log.warn("require_recipe is deprecated and will be removed in a future release, please use include_recipe") include_recipe(*args) end end end end # **DEPRECATED** # This used to be part of chef/mixin/language_include_recipe. Load the file to activate the deprecation code. require "chef/mixin/language_include_recipe" chef-12.14.60/lib/chef/dsl/method_missing.rb000066400000000000000000000057461276456504500204770ustar00rootroot00000000000000#-- # Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module DSL # @deprecated scheduled to die in a Chef 13 fire module MethodMissing def describe_self_for_error if respond_to?(:name) %Q{`#{self.class} "#{name}"'} elsif respond_to?(:recipe_name) %Q{`#{self.class} "#{recipe_name}"'} else to_s end end # DEPRECATED: # method_missing must live for backcompat purposes until Chef 13. def method_missing(method_symbol, *args, &block) # # If there is already DSL for this, someone must have called # method_missing manually. Not a fan. Not. A. Fan. # if respond_to?(method_symbol) Chef.log_deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13. Use public_send() or send() instead.") return send(method_symbol, *args, &block) end # # If a definition exists, then Chef::DSL::Definitions.add_definition was # never called. DEPRECATED. # if run_context.definitions.has_key?(method_symbol.to_sym) Chef.log_deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.") Chef::DSL::Definitions.add_definition(method_symbol) return send(method_symbol, *args, &block) end # # See if the resource exists anyway. If the user had set # Chef::Resource::Blah = , a deprecation warning will be # emitted and the DSL method 'blah' will be added to the DSL. # resource_class = Chef::ResourceResolver.resolve(method_symbol, node: run_context ? run_context.node : nil) if resource_class Chef::DSL::Resources.add_resource_dsl(method_symbol) return send(method_symbol, *args, &block) end begin super rescue NoMethodError raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}" rescue NameError raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}" end end end end end chef-12.14.60/lib/chef/dsl/platform_introspection.rb000066400000000000000000000250521276456504500222620ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module DSL # == Chef::DSL::PlatformIntrospection # Provides the DSL for platform-dependent switch logic, such as # #value_for_platform. module PlatformIntrospection # Implementation class for determining platform dependent values class PlatformDependentValue # Create a platform dependent value object. # === Arguments # platform_hash (Hash) a hash of the same structure as Chef::Platform, # like this: # { # :debian => {:default => 'the value for all debian'} # [:centos, :redhat, :fedora] => {:default => "value for all EL variants"} # :ubuntu => { :default => "default for ubuntu", '10.04' => "value for 10.04 only"}, # :default => "the default when nothing else matches" # } # * platforms can be specified as Symbols or Strings # * multiple platforms can be grouped by using an Array as the key # * values for platforms need to be Hashes of the form: # {platform_version => value_for_that_version} # * the exception to the above is the default value, which is given as # :default => default_value def initialize(platform_hash) @values = {} platform_hash.each { |platforms, value| set(platforms, value) } end def value_for_node(node) platform, version = node[:platform].to_s, node[:platform_version].to_s # Check if we match a version constraint via Chef::VersionConstraint::Platform and Chef::Version::Platform matched_value = match_versions(node) if @values.key?(platform) && @values[platform].key?(version) @values[platform][version] elsif matched_value matched_value elsif @values.key?(platform) && @values[platform].key?("default") @values[platform]["default"] elsif @values.key?("default") @values["default"] else nil end end private def match_versions(node) begin platform, version = node[:platform].to_s, node[:platform_version].to_s return nil unless @values.key?(platform) node_version = Chef::Version::Platform.new(version) key_matches = [] keys = @values[platform].keys keys.each do |k| begin if Chef::VersionConstraint::Platform.new(k).include?(node_version) key_matches << k end rescue Chef::Exceptions::InvalidVersionConstraint => e Chef::Log.debug "Caught InvalidVersionConstraint. This means that a key in value_for_platform cannot be interpreted as a Chef::VersionConstraint::Platform." Chef::Log.debug(e) end end return @values[platform][version] if key_matches.include?(version) case key_matches.length when 0 return nil when 1 return @values[platform][key_matches.first] else raise "Multiple matches detected for #{platform} with values #{@values}. The matches are: #{key_matches}" end rescue Chef::Exceptions::InvalidCookbookVersion => e # Lets not break because someone passes a weird string like 'default' :) Chef::Log.debug(e) Chef::Log.debug "InvalidCookbookVersion exceptions are common and expected here: the generic constraint matcher attempted to match something which is not a constraint. Moving on to next version or constraint" return nil rescue Chef::Exceptions::InvalidPlatformVersion => e Chef::Log.debug "Caught InvalidPlatformVersion, this means that Chef::Version::Platform does not know how to turn #{node_version} into an x.y.z format" Chef::Log.debug(e) return nil end end def set(platforms, value) if platforms.to_s == "default" @values["default"] = value else assert_valid_platform_values!(platforms, value) Array(platforms).each { |platform| @values[platform.to_s] = normalize_keys(value) } value end end def normalize_keys(hash) hash.inject({}) do |h, key_value| keys, value = *key_value Array(keys).each do |key| h[key.to_s] = value end h end end def assert_valid_platform_values!(platforms, value) unless value.kind_of?(Hash) msg = "platform dependent values must be specified in the format :platform => {:version => value} " msg << "you gave a value #{value.inspect} for platform(s) #{platforms}" raise ArgumentError, msg end end end # Given a hash similar to the one we use for Platforms, select a value from the hash. Supports # per platform defaults, along with a single base default. Arrays may be passed as hash keys and # will be expanded. # # === Parameters # platform_hash:: A platform-style hash. # # === Returns # value:: Whatever the most specific value of the hash is. def value_for_platform(platform_hash) PlatformDependentValue.new(platform_hash).value_for_node(node) end # Given a list of platforms, returns true if the current recipe is being run on a node with # that platform, false otherwise. # # === Parameters # args:: A list of platforms. Each platform can be in string or symbol format. # # === Returns # true:: If the current platform is in the list # false:: If the current platform is not in the list def platform?(*args) has_platform = false args.flatten.each do |platform| has_platform = true if platform.to_s == node[:platform] end has_platform end # Implementation class for determining platform family dependent values class PlatformFamilyDependentValue # Create a platform family dependent value object. # === Arguments # platform_family_hash (Hash) a map of platform families to values. # like this: # { # :rhel => "value for all EL variants" # :fedora => "value for fedora variants fedora and amazon" , # [:fedora, :rhel] => "value for all known redhat variants" # :debian => "value for debian variants including debian, ubuntu, mint" , # :default => "the default when nothing else matches" # } # * platform families can be specified as Symbols or Strings # * multiple platform families can be grouped by using an Array as the key # * values for platform families can be any object, with no restrictions. Some examples: # - [:stop, :start] # - "mysql-devel" # - { :key => "value" } def initialize(platform_family_hash) @values = {} @values["default"] = nil platform_family_hash.each { |platform_families, value| set(platform_families, value) } end def value_for_node(node) if node.key?(:platform_family) platform_family = node[:platform_family].to_s if @values.key?(platform_family) @values[platform_family] else @values["default"] end else @values["default"] end end private def set(platform_family, value) if platform_family.to_s == "default" @values["default"] = value else Array(platform_family).each { |family| @values[family.to_s] = value } value end end end # Given a hash mapping platform families to values, select a value from the hash. Supports a single # base default if platform family is not in the map. Arrays may be passed as hash keys and will be # expanded # # === Parameters # platform_family_hash:: A hash in the form { platform_family_name => value } # # === Returns # value:: Whatever the most specific value of the hash is. def value_for_platform_family(platform_family_hash) PlatformFamilyDependentValue.new(platform_family_hash).value_for_node(node) end # Given a list of platform families, returns true if the current recipe is being run on a # node within that platform family, false otherwise. # # === Parameters # args:: A list of platform families. Each platform family can be in string or symbol format. # # === Returns # true:: if the current node platform family is in the list. # false:: if the current node platform family is not in the list. def platform_family?(*args) args.flatten.any? do |platform_family| platform_family.to_s == node[:platform_family] 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 # **DEPRECATED** # This used to be part of chef/mixin/language. Load the file to activate the deprecation code. require "chef/mixin/language" chef-12.14.60/lib/chef/dsl/powershell.rb000066400000000000000000000017231276456504500176410ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/util/powershell/ps_credential" class Chef module DSL module Powershell def ps_credential(username = "placeholder", password) # rubocop:disable Style/OptionalArguments Chef::Util::Powershell::PSCredential.new(username, password) end end end end chef-12.14.60/lib/chef/dsl/reboot_pending.rb000066400000000000000000000062541276456504500204570ustar00rootroot00000000000000# Author:: Bryan McLellan # Author:: Seth Chisamore # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/dsl/platform_introspection" require "chef/dsl/registry_helper" class Chef module DSL module RebootPending include Chef::DSL::RegistryHelper include Chef::DSL::PlatformIntrospection # Returns true if the system needs a reboot or is expected to reboot # Note that we will silently miss any other platform-specific reboot notices besides Windows+Ubuntu. def reboot_pending? # don't break when used as a mixin in contexts without #node (e.g. specs). if self.respond_to?(:node, true) && node.run_context.reboot_requested? true elsif platform?("windows") # PendingFileRenameOperations contains pairs (REG_MULTI_SZ) of filenames that cannot be updated # due to a file being in use (usually a temporary file and a system file) # \??\c:\temp\test.sys!\??\c:\winnt\system32\test.sys # http://technet.microsoft.com/en-us/library/cc960241.aspx registry_value_exists?('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => "PendingFileRenameOperations" }) || # RebootRequired key contains Update IDs with a value of 1 if they require a reboot. # The existence of RebootRequired alone is sufficient on my Windows 8.1 workstation in Windows Update registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') || # Vista + Server 2008 and newer may have reboots pending from CBS registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') || # The mere existence of the UpdateExeVolatile key should indicate a pending restart for certain updates # http://support.microsoft.com/kb/832475 Chef::Platform.windows_server_2003? && (registry_key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') && !registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0].nil? && [1, 2, 3].include?(registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0][:data])) elsif platform?("ubuntu") # This should work for Debian as well if update-notifier-common happens to be installed. We need an API for that. File.exists?("/var/run/reboot-required") else false end end end end end chef-12.14.60/lib/chef/dsl/recipe.rb000066400000000000000000000071621276456504500167270ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Christopher Walters () # Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/exceptions" require "chef/dsl/resources" require "chef/dsl/definitions" require "chef/dsl/data_query" require "chef/dsl/include_recipe" require "chef/dsl/registry_helper" require "chef/dsl/reboot_pending" require "chef/dsl/audit" require "chef/dsl/powershell" require "chef/dsl/core" require "chef/dsl/method_missing" require "chef/mixin/lazy_module_include" class Chef module DSL # Part of a family of DSL mixins. # # Chef::DSL::Recipe mixes into Recipes and LWRP Providers. # - this does not target core chef resources and providers. # - this is restricted to recipe/resource/provider context where a resource collection exists. # - cookbook authors should typically include modules into here. # # Chef::DSL::Core mixes into Recipes, LWRP Providers and Core Providers # - this adds cores providers on top of the Recipe DSL. # - this is restricted to recipe/resource/provider context where a resource collection exists. # - core chef authors should typically include modules into here. # # Chef::DSL::Universal mixes into Recipes, LWRP Resources+Providers, Core Resources+Providers, and Attributes files. # - this adds resources and attributes files. # - do not add helpers which manipulate the resource collection. # - this is for general-purpose stuff that is useful nearly everywhere. # - it also pollutes the namespace of nearly every context, watch out. # module Recipe include Chef::DSL::Core include Chef::DSL::DataQuery include Chef::DSL::IncludeRecipe include Chef::DSL::RegistryHelper include Chef::DSL::RebootPending include Chef::DSL::Audit include Chef::DSL::Powershell include Chef::DSL::Resources include Chef::DSL::Definitions # method_missing will disappear in Chef 13 include Chef::DSL::MethodMissing extend Chef::Mixin::LazyModuleInclude def resource_class_for(snake_case_name) Chef::Resource.resource_for_node(snake_case_name, run_context.node) end def have_resource_class_for?(snake_case_name) not resource_class_for(snake_case_name).nil? rescue NameError false end def exec(args) raise Chef::Exceptions::ResourceNotFound, "exec was called, but you probably meant to use an execute resource. If not, please call Kernel#exec explicitly. The exec block called was \"#{args}\"" end # @deprecated Use Chef::DSL::Recipe instead, will be removed in Chef 13 module FullDSL include Chef::DSL::Recipe extend Chef::Mixin::LazyModuleInclude end end end end # Avoid circular references for things that are only used in instance methods require "chef/resource" # **DEPRECATED** # This used to be part of chef/mixin/recipe_definition_dsl_core. Load the file to activate the deprecation code. require "chef/mixin/recipe_definition_dsl_core" chef-12.14.60/lib/chef/dsl/registry_helper.rb000066400000000000000000000045671276456504500206750ustar00rootroot00000000000000# # Author:: Lamont Granquist () # 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. # # # Helper functions to access the windows registry from within recipes and # the not_if/only_if blocks in resources. This only exposes the methods # in the chef/win32/registry class which are reasonably side-effect-free. # The actual modification of the registry should be done via the registry_key # resource in a more idempotent way. # # class Chef module DSL module RegistryHelper # the registry instance is cheap to build and throwing it away ensures we # don't carry any state (e.g. magic 32-bit/64-bit settings) between calls def registry_key_exists?(key_path, architecture = :machine) registry = Chef::Win32::Registry.new(run_context, architecture) registry.key_exists?(key_path) end def registry_get_values(key_path, architecture = :machine) registry = Chef::Win32::Registry.new(run_context, architecture) registry.get_values(key_path) end def registry_has_subkeys?(key_path, architecture = :machine) registry = Chef::Win32::Registry.new(run_context, architecture) registry.has_subkeys?(key_path) end def registry_get_subkeys(key_path, architecture = :machine) registry = Chef::Win32::Registry.new(run_context, architecture) registry.get_subkeys(key_path) end def registry_value_exists?(key_path, value, architecture = :machine) registry = Chef::Win32::Registry.new(run_context, architecture) registry.value_exists?(key_path, value) end def registry_data_exists?(key_path, value, architecture = :machine) registry = Chef::Win32::Registry.new(run_context, architecture) registry.data_exists?(key_path, value) end end end end chef-12.14.60/lib/chef/dsl/resources.rb000066400000000000000000000044111276456504500174640ustar00rootroot00000000000000# # Author:: John Keiser # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/dsl/cheffish" require "chef/dsl/chef_provisioning" class Chef module DSL # # Module containing a method for each globally declared Resource # # Depends on declare_resource(name, created_at, &block) # # @api private module Resources # Include the lazy loaders for cheffish and chef-provisioning, so that the # resource DSL is there but the gems aren't activated yet. include Chef::DSL::Cheffish include Chef::DSL::ChefProvisioning def self.add_resource_dsl(dsl_name) begin module_eval(<<-EOM, __FILE__, __LINE__ + 1) def #{dsl_name}(*args, &block) Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1 declare_resource(#{dsl_name.inspect}, args[0], caller[0], &block) end EOM rescue SyntaxError # Handle the case where dsl_name has spaces, etc. define_method(dsl_name.to_sym) do |*args, &block| Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1 declare_resource(dsl_name, args[0], caller[0], &block) end end end def self.remove_resource_dsl(dsl_name) remove_method(dsl_name) rescue NameError end end end end chef-12.14.60/lib/chef/dsl/universal.rb000066400000000000000000000041011276456504500174560ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Christopher Walters () # Copyright:: Copyright 2008-2016, 2009-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/dsl/platform_introspection" require "chef/mixin/powershell_out" require "chef/mixin/shell_out" class Chef module DSL # Part of a family of DSL mixins. # # Chef::DSL::Recipe mixes into Recipes and LWRP Providers. # - this does not target core chef resources and providers. # - this is restricted to recipe/resource/provider context where a resource collection exists. # - cookbook authors should typically include modules into here. # # Chef::DSL::Core mixes into Recipes, LWRP Providers and Core Providers # - this adds cores providers on top of the Recipe DSL. # - this is restricted to recipe/resource/provider context where a resource collection exists. # - core chef authors should typically include modules into here. # # Chef::DSL::Universal mixes into Recipes, LWRP Resources+Providers, Core Resources+Providers, and Attributes files. # - this adds resources and attributes files. # - do not add helpers which manipulate the resource collection. # - this is for general-purpose stuff that is useful nearly everywhere. # - it also pollutes the namespace of nearly every context, watch out. # module Universal include Chef::DSL::PlatformIntrospection include Chef::Mixin::PowershellOut include Chef::Mixin::ShellOut end end end chef-12.14.60/lib/chef/encrypted_data_bag_item.rb000066400000000000000000000112311276456504500215030ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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/data_bag_item" require "chef/encrypted_data_bag_item/decryptor" require "chef/encrypted_data_bag_item/encryptor" require "open-uri" # An EncryptedDataBagItem represents a read-only data bag item where # all values, except for the value associated with the id key, have # been encrypted. # # EncryptedDataBagItem can be used in recipes to decrypt data bag item # members. # # Data bag item values are assumed to have been encrypted using the # default symmetric encryption provided by Encryptor.encrypt where # values are converted to YAML prior to encryption. # # If the shared secret is not specified at initialization or load, # then the contents of the file referred to in # Chef::Config[:encrypted_data_bag_secret] will be used as the # secret. The default path is /etc/chef/encrypted_data_bag_secret # # EncryptedDataBagItem is intended to provide a means to avoid storing # data bag items in the clear on the Chef server. This provides some # protection against a breach of the Chef server or of Chef server # backup data. Because the secret must be stored in the clear on any # node needing access to an EncryptedDataBagItem, this approach # provides no protection of data bag items from actors with access to # such nodes in the infrastructure. # class Chef::EncryptedDataBagItem ALGORITHM = "aes-256-cbc" AEAD_ALGORITHM = "aes-256-gcm" # # === Synopsis # # EncryptedDataBagItem.new(hash, secret) # # === Args # # +enc_hash+:: # The encrypted hash to be decrypted # +secret+:: # The raw secret key # # === Description # # Create a new encrypted data bag item for reading (decryption) # def initialize(enc_hash, secret) @enc_hash = enc_hash @secret = secret end def [](key) value = @enc_hash[key] if key == "id" || value.nil? value else Decryptor.for(value, @secret).for_decrypted_item end end def []=(key, value) raise ArgumentError, "assignment not supported for #{self.class}" end def to_hash @enc_hash.keys.inject({}) { |hash, key| hash[key] = self[key]; hash } end def self.encrypt_data_bag_item(plain_hash, secret) plain_hash.inject({}) do |h, (key, val)| h[key] = if key != "id" Encryptor.new(val, secret).for_encrypted_item else val end h end end # # === Synopsis # # EncryptedDataBagItem.load(data_bag, name, secret = nil) # # === Args # # +data_bag+:: # The name of the data bag to fetch # +name+:: # The name of the data bag item to fetch # +secret+:: # The raw secret key. If the +secret+ is nil, the value of the file at # +Chef::Config[:encrypted_data_bag_secret]+ is loaded. See +load_secret+ # for more information. # # === Description # # Loads and decrypts the data bag item with the given name. # def self.load(data_bag, name, secret = nil) raw_hash = Chef::DataBagItem.load(data_bag, name) secret = secret || self.load_secret self.new(raw_hash, secret) end def self.load_secret(path = nil) path ||= Chef::Config[:encrypted_data_bag_secret] if !path raise ArgumentError, "No secret specified and no secret found at #{Chef::Config.platform_specific_path('/etc/chef/encrypted_data_bag_secret')}" end secret = case path when /^\w+:\/\// # We have a remote key begin Kernel.open(path).read.strip rescue Errno::ECONNREFUSED raise ArgumentError, "Remote key not available from '#{path}'" rescue OpenURI::HTTPError raise ArgumentError, "Remote key not found at '#{path}'" end else if !File.exist?(path) raise Errno::ENOENT, "file not found '#{path}'" end IO.read(path).strip end if secret.size < 1 raise ArgumentError, "invalid zero length secret in '#{path}'" end secret end end chef-12.14.60/lib/chef/encrypted_data_bag_item/000077500000000000000000000000001276456504500211605ustar00rootroot00000000000000chef-12.14.60/lib/chef/encrypted_data_bag_item/assertions.rb000066400000000000000000000041311276456504500236760ustar00rootroot00000000000000# # Author:: Xabier de Zuazo () # Copyright:: Copyright 2014-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format" require "chef/encrypted_data_bag_item/unsupported_cipher" class Chef::EncryptedDataBagItem class EncryptedDataBagRequirementsFailure < StandardError end module Assertions def assert_format_version_acceptable!(format_version) unless format_version.kind_of?(Integer) && format_version >= Chef::Config[:data_bag_decrypt_minimum_version] raise UnacceptableEncryptedDataBagItemFormat, "The encrypted data bag item has format version `#{format_version}', " + "but the config setting 'data_bag_decrypt_minimum_version' requires version `#{Chef::Config[:data_bag_decrypt_minimum_version]}'" end end def assert_valid_cipher!(requested_cipher, algorithm) # In the future, chef may support configurable ciphers. For now, only # aes-256-cbc and aes-256-gcm are supported. unless requested_cipher == algorithm raise UnsupportedCipher, "Cipher '#{requested_cipher}' is not supported by this version of Chef. Available ciphers: ['#{ALGORITHM}', '#{AEAD_ALGORITHM}']" end end def assert_aead_requirements_met!(algorithm) unless OpenSSL::Cipher.ciphers.include?(algorithm) raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires an OpenSSL version with \"#{algorithm}\" algorithm support" end end end end chef-12.14.60/lib/chef/encrypted_data_bag_item/check_encrypted.rb000066400000000000000000000043751276456504500246500ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2010-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/encrypted_data_bag_item/encryptor" class Chef::EncryptedDataBagItem # Common code for checking if a data bag appears encrypted module CheckEncrypted # Tries to autodetect if the item's raw hash appears to be encrypted. def encrypted?(raw_data) data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key. # Assume hashes containing only the "id" key are not encrypted. # Otherwise, remove the keys that don't appear to be encrypted and compare # the result with the hash. If some entry has been removed, then some entry # doesn't appear to be encrypted and we assume the entire hash is not encrypted. data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data end private # Checks if data looks like it has been encrypted by # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns # true only when there is an exact match between the VersionXEncryptor # keys and the hash's keys. def looks_like_encrypted?(data) return false unless data.is_a?(Hash) && data.has_key?("version") case data["version"] when 1 Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort when 2 Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort when 3 Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort else false # version means something else... assume not encrypted. end end end end chef-12.14.60/lib/chef/encrypted_data_bag_item/decryption_failure.rb000066400000000000000000000014021276456504500253710ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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::EncryptedDataBagItem class DecryptionFailure < StandardError end end chef-12.14.60/lib/chef/encrypted_data_bag_item/decryptor.rb000066400000000000000000000163541276456504500235310ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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 "yaml" require "chef/json_compat" require "openssl" require "base64" require "digest/sha2" require "chef/encrypted_data_bag_item" require "chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format" require "chef/encrypted_data_bag_item/decryption_failure" require "chef/encrypted_data_bag_item/assertions" class Chef::EncryptedDataBagItem #=== Decryptor # For backwards compatibility, Chef implements decryption/deserialization for # older encrypted data bag item formats in addition to the current version. # Each decryption/deserialization strategy is implemented as a class in this # namespace. For convenience the factory method +Decryptor.for()+ can be used # to create an instance of the appropriate strategy for the given encrypted # data bag value. module Decryptor extend Chef::EncryptedDataBagItem::Assertions # Detects the encrypted data bag item format version and instantiates a # decryptor object for that version. Call #for_decrypted_item on the # resulting object to decrypt and deserialize it. def self.for(encrypted_value, key) format_version = format_version_of(encrypted_value) assert_format_version_acceptable!(format_version) case format_version when 3 Version3Decryptor.new(encrypted_value, key) when 2 Version2Decryptor.new(encrypted_value, key) when 1 Version1Decryptor.new(encrypted_value, key) when 0 Version0Decryptor.new(encrypted_value, key) else raise UnsupportedEncryptedDataBagItemFormat, "This version of chef does not support encrypted data bag item format version '#{format_version}'" end end def self.format_version_of(encrypted_value) if encrypted_value.respond_to?(:key?) encrypted_value["version"] else 0 end end class Version0Decryptor include Chef::EncryptedDataBagItem::Assertions attr_reader :encrypted_data attr_reader :key def initialize(encrypted_data, key) @encrypted_data = encrypted_data @key = key end # Returns the used decryption algorithm def algorithm ALGORITHM end def for_decrypted_item YAML.load(decrypted_data) end def decrypted_data @decrypted_data ||= begin plaintext = openssl_decryptor.update(encrypted_bytes) plaintext << openssl_decryptor.final rescue OpenSSL::Cipher::CipherError => e # if the key length is less than 255 characters, and it contains slashes, we think it may be a path. raise DecryptionFailure, "Error decrypting data bag value: '#{e.message}'. Most likely the provided key is incorrect. #{ (@key.length < 255 && @key.include?('/')) ? 'You may need to use --secret-file rather than --secret.' : '' }" end end def encrypted_bytes Base64.decode64(@encrypted_data) end def openssl_decryptor @openssl_decryptor ||= begin d = OpenSSL::Cipher.new(algorithm) d.decrypt d.pkcs5_keyivgen(key) d end end end class Version1Decryptor < Version0Decryptor attr_reader :encrypted_data attr_reader :key def initialize(encrypted_data, key) @encrypted_data = encrypted_data @key = key end def for_decrypted_item Chef::JSONCompat.parse(decrypted_data)["json_wrapper"] rescue Chef::Exceptions::JSON::ParseError # convert to a DecryptionFailure error because the most likely scenario # here is that the decryption step was unsuccessful but returned bad # data rather than raising an error. raise DecryptionFailure, "Error decrypting data bag value. Most likely the provided key is incorrect" end def encrypted_bytes Base64.decode64(@encrypted_data["encrypted_data"]) end def iv Base64.decode64(@encrypted_data["iv"]) end def decrypted_data @decrypted_data ||= begin plaintext = openssl_decryptor.update(encrypted_bytes) plaintext << openssl_decryptor.final rescue OpenSSL::Cipher::CipherError => e # if the key length is less than 255 characters, and it contains slashes, we think it may be a path. raise DecryptionFailure, "Error decrypting data bag value: '#{e.message}'. Most likely the provided key is incorrect. #{ ( @key.length < 255 && @key.include?('/')) ? 'You may need to use --secret-file rather than --secret.' : '' }" end end def openssl_decryptor @openssl_decryptor ||= begin assert_valid_cipher!(@encrypted_data["cipher"], algorithm) d = OpenSSL::Cipher.new(algorithm) d.decrypt # We must set key before iv: https://bugs.ruby-lang.org/issues/8221 d.key = OpenSSL::Digest::SHA256.digest(key) d.iv = iv d end end end class Version2Decryptor < Version1Decryptor def decrypted_data validate_hmac! unless @decrypted_data super end def validate_hmac! digest = OpenSSL::Digest.new("sha256") raw_hmac = OpenSSL::HMAC.digest(digest, key, @encrypted_data["encrypted_data"]) if candidate_hmac_matches?(raw_hmac) true else raise DecryptionFailure, "Error decrypting data bag value: invalid hmac. Most likely the provided key is incorrect" end end private def candidate_hmac_matches?(expected_hmac) return false unless @encrypted_data["hmac"] expected_bytes = expected_hmac.bytes.to_a candidate_hmac_bytes = Base64.decode64(@encrypted_data["hmac"]).bytes.to_a valid = expected_bytes.size ^ candidate_hmac_bytes.size expected_bytes.zip(candidate_hmac_bytes) { |x, y| valid |= x ^ y.to_i } valid == 0 end end class Version3Decryptor < Version1Decryptor def initialize(encrypted_data, key) super assert_aead_requirements_met!(algorithm) end # Returns the used decryption algorithm def algorithm AEAD_ALGORITHM end def auth_tag auth_tag_b64 = @encrypted_data["auth_tag"] if auth_tag_b64.nil? raise DecryptionFailure, "Error decrypting data bag value: invalid authentication tag. Most likely the data is corrupted" end Base64.decode64(auth_tag_b64) end def openssl_decryptor @openssl_decryptor ||= begin d = super d.auth_tag = auth_tag d.auth_data = "" d end end end end end chef-12.14.60/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb000066400000000000000000000023651276456504500306020ustar00rootroot00000000000000# # Author:: Xabier de Zuazo () # Copyright:: Copyright 2014-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef::EncryptedDataBagItem class EncryptedDataBagRequirementsFailure < StandardError end module Assertions def assert_requirements_met! unless OpenSSL::Cipher.method_defined?(:auth_data=) raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires Ruby >= 2.0" end unless OpenSSL::Cipher.ciphers.include?(algorithm) raise EncryptedDataBagRequirementsFailure, "The used Encrypted Data Bags version requires an OpenSSL version with \"#{algorithm}\" algorithm support" end end end end chef-12.14.60/lib/chef/encrypted_data_bag_item/encryption_failure.rb000066400000000000000000000014071276456504500254100ustar00rootroot00000000000000# # Author:: Xabier de Zuazo () # Copyright:: Copyright 2014-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef::EncryptedDataBagItem class EncryptionFailure < StandardError end end chef-12.14.60/lib/chef/encrypted_data_bag_item/encryptor.rb000066400000000000000000000161021276456504500235320ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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 "base64" require "digest/sha2" require "openssl" require "ffi_yajl" require "chef/encrypted_data_bag_item" require "chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format" require "chef/encrypted_data_bag_item/encryption_failure" require "chef/encrypted_data_bag_item/assertions" class Chef::EncryptedDataBagItem # Implementation class for converting plaintext data bag item values to an # encrypted value, including any necessary wrappers and metadata. module Encryptor # "factory" method that creates an encryptor object with the proper class # for the desired encrypted data bag format version. # # +Chef::Config[:data_bag_encrypt_version]+ determines which version is used. def self.new(value, secret, iv = nil) format_version = Chef::Config[:data_bag_encrypt_version] case format_version when 1 Version1Encryptor.new(value, secret, iv) when 2 Version2Encryptor.new(value, secret, iv) when 3 Version3Encryptor.new(value, secret, iv) else raise UnsupportedEncryptedDataBagItemFormat, "Invalid encrypted data bag format version `#{format_version}'. Supported versions are '1', '2', '3'" end end class Version1Encryptor attr_reader :key attr_reader :plaintext_data include Chef::EncryptedDataBagItem::Assertions # Create a new Encryptor for +data+, which will be encrypted with the given # +key+. # # === Arguments: # * data: An object of any type that can be serialized to json # * key: A String representing the desired passphrase # * iv: The optional +iv+ parameter is intended for testing use only. When # *not* supplied, Encryptor will use OpenSSL to generate a secure random # IV, which is what you want. def initialize(plaintext_data, key, iv = nil) @plaintext_data = plaintext_data @key = key @iv = iv && Base64.decode64(iv) end # Returns the used encryption algorithm def algorithm ALGORITHM end # Returns a wrapped and encrypted version of +plaintext_data+ suitable for # using as the value in an encrypted data bag item. def for_encrypted_item { "encrypted_data" => encrypted_data, "iv" => Base64.encode64(iv), "version" => 1, "cipher" => algorithm, } end # Generates or returns the IV. def iv # Generated IV comes from OpenSSL::Cipher#random_iv # This gets generated when +openssl_encryptor+ gets created. openssl_encryptor if @iv.nil? @iv end # Generates (and memoizes) an OpenSSL::Cipher object and configures # it for the specified iv and encryption key. def openssl_encryptor @openssl_encryptor ||= begin encryptor = OpenSSL::Cipher.new(algorithm) encryptor.encrypt # We must set key before iv: https://bugs.ruby-lang.org/issues/8221 encryptor.key = OpenSSL::Digest::SHA256.digest(key) @iv ||= encryptor.random_iv encryptor.iv = @iv encryptor end end # Encrypts and Base64 encodes +serialized_data+ def encrypted_data @encrypted_data ||= begin enc_data = openssl_encryptor.update(serialized_data) enc_data << openssl_encryptor.final Base64.encode64(enc_data) end end # Wraps the data in a single key Hash (JSON Object) and converts to JSON. # The wrapper is required because we accept values (such as Integers or # Strings) that do not produce valid JSON when serialized without the # wrapper. def serialized_data FFI_Yajl::Encoder.encode(:json_wrapper => plaintext_data) end def self.encryptor_keys %w{ encrypted_data iv version cipher } end end class Version2Encryptor < Version1Encryptor # Returns a wrapped and encrypted version of +plaintext_data+ suitable for # using as the value in an encrypted data bag item. def for_encrypted_item { "encrypted_data" => encrypted_data, "hmac" => hmac, "iv" => Base64.encode64(iv), "version" => 2, "cipher" => algorithm, } end # Generates an HMAC-SHA2-256 of the encrypted data (encrypt-then-mac) def hmac @hmac ||= begin digest = OpenSSL::Digest.new("sha256") raw_hmac = OpenSSL::HMAC.digest(digest, key, encrypted_data) Base64.encode64(raw_hmac) end end def self.encryptor_keys super + %w{ hmac } end end class Version3Encryptor < Version1Encryptor include Chef::EncryptedDataBagItem::Assertions def initialize(plaintext_data, key, iv = nil) super assert_aead_requirements_met!(algorithm) @auth_tag = nil end # Returns a wrapped and encrypted version of +plaintext_data+ suitable for # using as the value in an encrypted data bag item. def for_encrypted_item { "encrypted_data" => encrypted_data, "iv" => Base64.encode64(iv), "auth_tag" => Base64.encode64(auth_tag), "version" => 3, "cipher" => algorithm, } end # Returns the used encryption algorithm def algorithm AEAD_ALGORITHM end # Returns a wrapped and encrypted version of +plaintext_data+ suitable for # Returns the auth_tag. def auth_tag # Generated auth_tag comes from OpenSSL::Cipher#auth_tag # This must be generated after the data is encrypted if @auth_tag.nil? raise EncryptionFailure, "Internal Error: GCM authentication tag read before encryption" end @auth_tag end # Generates (and memoizes) an OpenSSL::Cipher object and configures # it for the specified iv and encryption key using AEAD def openssl_encryptor @openssl_encryptor ||= begin encryptor = super encryptor.auth_data = "" encryptor end end # Encrypts, Base64 encodes +serialized_data+ and gets the authentication tag def encrypted_data @encrypted_data ||= begin enc_data_b64 = super @auth_tag = openssl_encryptor.auth_tag enc_data_b64 end end def self.encryptor_keys super + %w{ auth_tag } end end end end chef-12.14.60/lib/chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format.rb000066400000000000000000000014271276456504500324040ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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::EncryptedDataBagItem class UnacceptableEncryptedDataBagItemFormat < StandardError end end chef-12.14.60/lib/chef/encrypted_data_bag_item/unsupported_cipher.rb000066400000000000000000000014021276456504500254240ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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::EncryptedDataBagItem class UnsupportedCipher < StandardError end end chef-12.14.60/lib/chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format.rb000066400000000000000000000014261276456504500323450ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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::EncryptedDataBagItem class UnsupportedEncryptedDataBagItemFormat < StandardError end end chef-12.14.60/lib/chef/environment.rb000066400000000000000000000223231276456504500172360ustar00rootroot00000000000000# # Author:: Stephen Delano () # Author:: Seth Falcon () # Author:: John Keiser () # Author:: Kyle Goodwin () # Copyright:: Copyright 2010-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/mash" require "chef/mixin/params_validate" require "chef/mixin/from_file" require "chef/version_constraint" require "chef/server_api" class Chef class Environment DEFAULT = "default" include Chef::Mixin::ParamsValidate include Chef::Mixin::FromFile attr_accessor :chef_server_rest COMBINED_COOKBOOK_CONSTRAINT = /(.+)(?:[\s]+)((?:#{Chef::VersionConstraint::OPS.join('|')})(?:[\s]+).+)$/ def initialize(chef_server_rest: nil) @name = "" @description = "" @default_attributes = Mash.new @override_attributes = Mash.new @cookbook_versions = Hash.new @chef_server_rest = chef_server_rest end def chef_server_rest @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def self.chef_server_rest Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def name(arg = nil) set_or_return( :name, arg, { :regex => /^[\-[:alnum:]_]+$/, :kind_of => String } ) end def description(arg = nil) set_or_return( :description, arg, :kind_of => String ) end def default_attributes(arg = nil) set_or_return( :default_attributes, arg, :kind_of => Hash ) end def default_attributes=(attrs) default_attributes(attrs) end def override_attributes(arg = nil) set_or_return( :override_attributes, arg, :kind_of => Hash ) end def override_attributes=(attrs) override_attributes(attrs) end def cookbook_versions(arg = nil) set_or_return( :cookbook_versions, arg, { :kind_of => Hash, :callbacks => { "should be a valid set of cookbook version requirements" => lambda { |cv| Chef::Environment.validate_cookbook_versions(cv) }, }, } ) end def cookbook(cookbook, version) validate({ :version => version, }, { :version => { :callbacks => { "should be a valid version requirement" => lambda { |v| Chef::Environment.validate_cookbook_version(v) } }, }, }) @cookbook_versions[cookbook] = version end def to_hash result = { "name" => @name, "description" => @description, "cookbook_versions" => @cookbook_versions, "json_class" => self.class.name, "chef_type" => "environment", "default_attributes" => @default_attributes, "override_attributes" => @override_attributes, } result end def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def update_from!(o) description(o.description) cookbook_versions(o.cookbook_versions) default_attributes(o.default_attributes) override_attributes(o.override_attributes) self end def update_attributes_from_params(params) unless params[:default_attributes].nil? || params[:default_attributes].size == 0 default_attributes(Chef::JSONCompat.from_json(params[:default_attributes])) end unless params[:override_attributes].nil? || params[:override_attributes].size == 0 override_attributes(Chef::JSONCompat.from_json(params[:override_attributes])) end end def update_from_params(params) # reset because everything we need will be in the params, this is necessary because certain constraints # may have been removed in the params and need to be removed from cookbook_versions as well. bkup_cb_versions = cookbook_versions cookbook_versions(Hash.new) valid = true begin name(params[:name]) rescue Chef::Exceptions::ValidationFailed => e invalid_fields[:name] = e.message valid = false end description(params[:description]) unless params[:cookbook_version].nil? params[:cookbook_version].each do |index, cookbook_constraint_spec| unless cookbook_constraint_spec.nil? || cookbook_constraint_spec.size == 0 valid = valid && update_cookbook_constraint_from_param(index, cookbook_constraint_spec) end end end update_attributes_from_params(params) valid = validate_required_attrs_present && valid cookbook_versions(bkup_cb_versions) unless valid # restore the old cookbook_versions if valid is false valid end def update_cookbook_constraint_from_param(index, cookbook_constraint_spec) valid = true md = cookbook_constraint_spec.match(COMBINED_COOKBOOK_CONSTRAINT) if md.nil? || md[2].nil? valid = false add_cookbook_constraint_error(index, cookbook_constraint_spec) elsif self.class.validate_cookbook_version(md[2]) cookbook_versions[md[1]] = md[2] else valid = false add_cookbook_constraint_error(index, cookbook_constraint_spec) end valid end def add_cookbook_constraint_error(index, cookbook_constraint_spec) invalid_fields[:cookbook_version] ||= {} invalid_fields[:cookbook_version][index] = "#{cookbook_constraint_spec} is not a valid cookbook constraint" end def invalid_fields @invalid_fields ||= {} end def validate_required_attrs_present if name.nil? || name.size == 0 invalid_fields[:name] ||= "name cannot be empty" false else true end end def self.json_create(o) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::Environment#from_hash") from_hash(o) end def self.from_hash(o) environment = new environment.name(o["name"]) environment.description(o["description"]) environment.cookbook_versions(o["cookbook_versions"]) environment.default_attributes(o["default_attributes"]) environment.override_attributes(o["override_attributes"]) environment end def self.list(inflate = false) if inflate response = Hash.new Chef::Search::Query.new.search(:environment) do |e| response[e.name] = e unless e.nil? end response else chef_server_rest.get("environments") end end def self.load(name) if Chef::Config[:solo_legacy_mode] load_from_file(name) else self.from_hash(chef_server_rest.get("environments/#{name}")) end end def self.load_from_file(name) unless File.directory?(Chef::Config[:environment_path]) raise Chef::Exceptions::InvalidEnvironmentPath, "Environment path '#{Chef::Config[:environment_path]}' is invalid" end js_file = File.join(Chef::Config[:environment_path], "#{name}.json") rb_file = File.join(Chef::Config[:environment_path], "#{name}.rb") if File.exists?(js_file) # from_json returns object.class => json_class in the JSON. hash = Chef::JSONCompat.parse(IO.read(js_file)) from_hash(hash) elsif File.exists?(rb_file) environment = Chef::Environment.new environment.name(name) environment.from_file(rb_file) environment else raise Chef::Exceptions::EnvironmentNotFound, "Environment '#{name}' could not be loaded from disk" end end def destroy chef_server_rest.delete("environments/#{@name}") end def save begin chef_server_rest.put("environments/#{@name}", self) rescue Net::HTTPServerException => e raise e unless e.response.code == "404" chef_server_rest.post("environments", self) end self end def create chef_server_rest.post("environments", self) self end def self.load_filtered_recipe_list(environment) chef_server_rest.get("environments/#{environment}/recipes") end def to_s @name end def self.validate_cookbook_versions(cv) return false unless cv.kind_of?(Hash) cv.each do |cookbook, version| return false unless Chef::Environment.validate_cookbook_version(version) end true end def self.validate_cookbook_version(version) begin if Chef::Config[:solo_legacy_mode] raise Chef::Exceptions::IllegalVersionConstraint, "Environment cookbook version constraints not allowed in chef-solo" else Chef::VersionConstraint.new version true end rescue ArgumentError false end end end end chef-12.14.60/lib/chef/event_dispatch/000077500000000000000000000000001276456504500173435ustar00rootroot00000000000000chef-12.14.60/lib/chef/event_dispatch/base.rb000066400000000000000000000330671276456504500206130ustar00rootroot00000000000000class Chef # ==EventDispatch # Classes in EventDispatch deal with collecting, distributing, and handling # information in response to events that occur during a chef-client run. # # EventDispatch uses a simple publishing system where data from all events # are forwarded to all subscribers unconditionally. # # EventDispatch is used to implement custom console output formatters so that # users may have more control over the formatting and verbosity of Chef # client output and client-side data collection for server-side client # history storage and reporting. # # === API Stability Status # The EventDispatch API is intended to become a stable, public API upon which # end-users can implement their own custom output formatters, reporting # integration libraries, and more. This is a new feature, however, so # breaking changes may be required as it "bakes" in order to provide a clean, # coherent and supportable API in the long term. Therefore, developers should # consider the feature "beta" for now and be prepared for possible breaking # changes in point releases. module EventDispatch # == EventDispatch::Base # EventDispatch::Base is a completely abstract base class that defines the # API used by both the classes that collect event information and those # that process them. class Base # Called at the very start of a Chef Run def run_start(version) end def run_started(run_status) end # Called at the end a successful Chef run. def run_completed(node) end # Called at the end of a failed Chef run. def run_failed(exception) end # Called right after ohai runs. def ohai_completed(node) end # Announce that we're not going to register the client. Generally because # we already have the private key, or because we're deliberately not using # a key. def skipping_registration(node_name, config) end # About to attempt to create a private key registered to the server with # client +node_name+. def registration_start(node_name, config) end # Successfully created the private key and registered this client with the # server. def registration_completed end # Failed to register this client with the server. def registration_failed(node_name, exception, config) end # Called before Chef client loads the node data from the server def node_load_start(node_name, config) end # TODO: def node_run_list_overridden(*args) # Failed to load node data from the server def node_load_failed(node_name, exception, config) end # Error expanding the run list def run_list_expand_failed(node, exception) end # Called after Chef client has loaded the node data. # Default and override attrs from roles have been computed, but not yet applied. # Normal attrs from JSON have been added to the node. def node_load_completed(node, expanded_run_list, config) end # Called after the Policyfile was loaded. This event only occurs when # chef is in policyfile mode. def policyfile_loaded(policy) end # Called before the cookbook collection is fetched from the server. def cookbook_resolution_start(expanded_run_list) end # Called when there is an error getting the cookbook collection from the # server. def cookbook_resolution_failed(expanded_run_list, exception) end # Called when the cookbook collection is returned from the server. def cookbook_resolution_complete(cookbook_collection) end # Called before unneeded cookbooks are removed def cookbook_clean_start end # Called after the file at +path+ is removed. It may be removed if the # cookbook containing it was removed from the run list, or if the file was # removed from the cookbook. def removed_cookbook_file(path) end # Called when cookbook cleaning is finished. def cookbook_clean_complete end # Called before cookbook sync starts def cookbook_sync_start(cookbook_count) end # Called when cookbook +cookbook+ has been sync'd def synchronized_cookbook(cookbook_name, cookbook) end # Called when an individual file in a cookbook has been updated def updated_cookbook_file(cookbook_name, path) end # Called when an error occurs during cookbook sync def cookbook_sync_failed(cookbooks, exception) end # Called after all cookbooks have been sync'd. def cookbook_sync_complete end # Called when starting to collect gems from the cookbooks def cookbook_gem_start(gems) end # Called when the result of installing the bundle is to install the gem def cookbook_gem_installing(gem, version) end # Called when the result of installing the bundle is to use the gem def cookbook_gem_using(gem, version) end # Called when finished installing cookbook gems def cookbook_gem_finished end # Called when cookbook gem installation fails def cookbook_gem_failed(exception) end ## TODO: add cookbook name to the API for file load callbacks ## TODO: add callbacks for overall cookbook eval start and complete. # Called when library file loading starts def library_load_start(file_count) end # Called when library file has been loaded def library_file_loaded(path) end # Called when a library file has an error on load. def library_file_load_failed(path, exception) end # Called when library file loading has finished def library_load_complete end # Called when LWRP loading starts def lwrp_load_start(lwrp_file_count) end # Called after a LWR or LWP has been loaded def lwrp_file_loaded(path) end # Called after a LWR or LWP file errors on load def lwrp_file_load_failed(path, exception) end # Called when LWRPs are finished loading def lwrp_load_complete end # Called before attribute files are loaded def attribute_load_start(attribute_file_count) end # Called after the attribute file is loaded def attribute_file_loaded(path) end # Called when an attribute file fails to load. def attribute_file_load_failed(path, exception) end # Called when attribute file loading is finished def attribute_load_complete end # Called before resource definitions are loaded def definition_load_start(definition_file_count) end # Called when a resource definition has been loaded def definition_file_loaded(path) end # Called when a resource definition file fails to load def definition_file_load_failed(path, exception) end # Called when resource definitions are done loading def definition_load_complete end # Called before recipes are loaded def recipe_load_start(recipe_count) end # Called after the recipe has been loaded def recipe_file_loaded(path, recipe) end # Called after a recipe file fails to load def recipe_file_load_failed(path, exception, recipe) end # Called when a recipe cannot be resolved def recipe_not_found(exception) end # Called when recipes have been loaded. def recipe_load_complete end # Called before convergence starts def converge_start(run_context) end # Called when the converge phase is finished. def converge_complete end # Called if the converge phase fails def converge_failed(exception) end ################################## # Audit Mode Events # This phase is currently experimental and these event APIs are subject to change ################################## # Called before audit phase starts def audit_phase_start(run_status) end # Called when audit phase successfully finishes def audit_phase_complete(audit_output) end # Called if there is an uncaught exception during the audit phase. The audit runner should # be catching and handling errors from the examples, so this is only uncaught errors (like # bugs in our handling code) def audit_phase_failed(exception, audit_output) end # Signifies the start of a `control_group` block with a defined name def control_group_started(name) end # An example in a `control_group` block completed successfully def control_example_success(control_group_name, example_data) end # An example in a `control_group` block failed with the provided error def control_example_failure(control_group_name, example_data, error) end # TODO: need events for notification resolve? # def notifications_resolved # end # # Resource events and ordering: # # 1. Start the action # - resource_action_start # 2. Check the guard # - resource_skipped: (goto 7) if only_if/not_if say to skip # 3. Load the current resource # - resource_current_state_loaded # - resource_current_state_load_bypassed (if not why-run safe) # 4. Check if why-run safe # - resource_bypassed: (goto 7) if not why-run safe # 5. During processing: # - resource_update_applied: For each actual change (many per action) # 6. Processing complete status: # - resource_failed if the resource threw an exception while running # - resource_failed_retriable: (goto 3) if resource failed and will be retried # - resource_updated if the resource was updated (resource_update_applied will have been called) # - resource_up_to_date if the resource was up to date (no resource_update_applied) # 7. Processing complete: # - resource_completed # # Called before action is executed on a resource. def resource_action_start(resource, action, notification_type = nil, notifier = nil) end # Called when a resource action has been skipped b/c of a conditional def resource_skipped(resource, action, conditional) end # Called after #load_current_resource has run. def resource_current_state_loaded(resource, action, current_resource) end # Called when resource current state load is skipped due to the provider # not supporting whyrun mode. def resource_current_state_load_bypassed(resource, action, current_resource) end # Called when evaluating a resource that does not support whyrun in whyrun mode def resource_bypassed(resource, action, current_resource) end # Called when a change has been made to a resource. May be called multiple # times per resource, e.g., a file may have its content updated, and then # its permissions updated. def resource_update_applied(resource, action, update) end # Called when a progress notification should be sent to the user to # indicate the overall progress of a long running operation, such as # a large file download. def resource_update_progress(resource, current, total, interval) end # Called when a resource fails, but will retry. def resource_failed_retriable(resource, action, retry_count, exception) end # Called when a resource fails and will not be retried. def resource_failed(resource, action, exception) end # Called after a resource has been completely converged, but only if # modifications were made. def resource_updated(resource, action) end # Called when a resource has no converge actions, e.g., it was already correct. def resource_up_to_date(resource, action) end # Called when a resource action has been completed def resource_completed(resource) end # A stream has opened. def stream_opened(stream, options = {}) end # A stream has closed. def stream_closed(stream, options = {}) end # A chunk of data from a stream. The stream is managed by "stream," which # can be any tag whatsoever. Data in different "streams" may not be placed # on the same line or even sent to the same console. def stream_output(stream, output, options = {}) end # Called before handlers run def handlers_start(handler_count) end # Called after an individual handler has run def handler_executed(handler) end # Called after all handlers have executed def handlers_completed end # Called when an assertion declared by a provider fails def provider_requirement_failed(action, resource, exception, message) end # Called when a provider makes an assumption after a failed assertion # in whyrun mode, in order to allow execution to continue def whyrun_assumption(action, resource, message) end # Emit a message about something being deprecated. def deprecation(message, location = caller(2..2)[0]) end def run_list_expanded(run_list_expansion) end # An uncategorized message. This supports the case that a user needs to # pass output that doesn't fit into one of the callbacks above. Note that # there's no semantic information about the content or importance of the # message. That means that if you're using this too often, you should add a # callback for it. def msg(message) end end end end chef-12.14.60/lib/chef/event_dispatch/dispatcher.rb000066400000000000000000000034751276456504500220270ustar00rootroot00000000000000require "chef/event_dispatch/base" class Chef module EventDispatch # == EventDispatch::Dispatcher # The Dispatcher handles receiving event data from the sources # (Chef::Client, Resources and Providers, etc.) and publishing the data to # the registered subscribers. class Dispatcher < Base attr_reader :subscribers def initialize(*subscribers) @subscribers = subscribers end # Add a new subscriber to the list of registered subscribers def register(subscriber) @subscribers << subscriber end # Check to see if we are dispatching to a formatter def formatter? @subscribers.any? { |s| s.respond_to?(:is_formatter?) && s.is_formatter? } end #### # All messages are unconditionally forwarded to all subscribers, so just # define the forwarding in one go: # def call_subscribers(method_name, *args) @subscribers.each do |s| # Skip new/unsupported event names. next if !s.respond_to?(method_name) mth = s.method(method_name) # Trim arguments to match what the subscriber expects to allow # adding new arguments without breaking compat. if mth.arity < args.size && mth.arity >= 0 mth.call(*args.take(mth.arity)) else mth.call(*args) end end end (Base.instance_methods - Object.instance_methods).each do |method_name| class_eval <<-EOM def #{method_name}(*args) call_subscribers(#{method_name.inspect}, *args) end EOM end # Special case deprecation, since it needs to know its caller def deprecation(message, location = caller(2..2)[0]) call_subscribers(:deprecation, message, location) end end end end chef-12.14.60/lib/chef/event_dispatch/dsl.rb000066400000000000000000000043171276456504500204570ustar00rootroot00000000000000# # Author:: Ranjib Dey () # Copyright:: Copyright 2015-2016, Ranjib Dey # 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/event_dispatch/base" require "chef/exceptions" require "chef/config" class Chef module EventDispatch class DSL attr_reader :handler def initialize(name) klass = Class.new(Chef::EventDispatch::Base) do attr_reader :name end @handler = klass.new @handler.instance_variable_set(:@name, name) # Use event.register API to add anonymous handler if Chef.run_context # and associated event dispatcher is set, else fallback to # Chef::Config[:event_handlers] if Chef.run_context && Chef.run_context.events Chef::Log.debug("Registering handler '#{name}' using events api") Chef.run_context.events.register(handler) else Chef::Log.debug("Registering handler '#{name}' using global config") Chef::Config[:event_handlers] << handler end end # Adds a new event handler derived from base handler # with user defined block against a chef event # # @return [Chef::EventDispatch::Base] a base handler object def on(event_type, &block) validate!(event_type) handler.define_singleton_method(event_type) do |*args| instance_exec(*args, &block) end end private def validate!(event_type) all_event_types = (Chef::EventDispatch::Base.instance_methods - Object.instance_methods) raise Chef::Exceptions::InvalidEventType, "Invalid event type: #{event_type}" unless all_event_types.include?(event_type) end end end end chef-12.14.60/lib/chef/event_dispatch/events_output_stream.rb000066400000000000000000000016001276456504500241640ustar00rootroot00000000000000class Chef module EventDispatch class EventsOutputStream # This is a fake stream that connects to events. # # == Arguments # events: the EventDispatch object to send data to (run_context.events) # options is a hash with these possible options: # - name: a string that identifies the stream to the user. Preferably short. def initialize(events, options = {}) @events = events @options = options events.stream_opened(self, options) end attr_reader :options attr_reader :events def print(str) events.stream_output(self, str, options) end def <<(str) events.stream_output(self, str, options) end def write(str) events.stream_output(self, str, options) end def close events.stream_closed(self, options) end end end end chef-12.14.60/lib/chef/event_loggers/000077500000000000000000000000001276456504500172065ustar00rootroot00000000000000chef-12.14.60/lib/chef/event_loggers/base.rb000066400000000000000000000034151276456504500204500ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # 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/event_dispatch/base" class Chef module EventLoggers class UnknownEventLogger < StandardError; end class UnavailableEventLogger < StandardError; end def self.event_loggers_by_name @event_loggers_by_name ||= {} end def self.register(name, logger) event_loggers_by_name[name.to_s] = logger end def self.by_name(name) event_loggers_by_name[name] end def self.available_event_loggers event_loggers_by_name.select do |key, val| val.available? end.keys end def self.new(name) event_logger_class = by_name(name.to_s) raise UnknownEventLogger, "No event logger found for #{name} (available: #{available_event_loggers.join(', ')})" unless event_logger_class raise UnavailableEventLogger unless available_event_loggers.include? name.to_s event_logger_class.new end class Base < EventDispatch::Base def self.short_name(name) Chef::EventLoggers.register(name, self) end # Returns true if this implementation of EventLoggers can be used def self.available? false end end end end chef-12.14.60/lib/chef/event_loggers/windows_eventlog.rb000066400000000000000000000055031276456504500231330ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # 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/event_loggers/base" require "chef/platform/query_helpers" require "chef/win32/eventlog" class Chef module EventLoggers class WindowsEventLogger < EventLoggers::Base short_name(:win_evt) # These must match those that are defined in the manifest file RUN_START_EVENT_ID = 10000 RUN_STARTED_EVENT_ID = 10001 RUN_COMPLETED_EVENT_ID = 10002 RUN_FAILED_EVENT_ID = 10003 EVENT_CATEGORY_ID = 11000 LOG_CATEGORY_ID = 11001 # Since we must install the event logger, this is not really configurable SOURCE = "Chef" def self.available? return Chef::Platform.windows? end def initialize @eventlog = ::Win32::EventLog.open("Application") end def run_start(version) @eventlog.report_event( :event_type => ::Win32::EventLog::INFO_TYPE, :source => SOURCE, :event_id => RUN_START_EVENT_ID, :data => [version] ) end def run_started(run_status) @run_status = run_status @eventlog.report_event( :event_type => ::Win32::EventLog::INFO_TYPE, :source => SOURCE, :event_id => RUN_STARTED_EVENT_ID, :data => [run_status.run_id] ) end def run_completed(node) @eventlog.report_event( :event_type => ::Win32::EventLog::INFO_TYPE, :source => SOURCE, :event_id => RUN_COMPLETED_EVENT_ID, :data => [@run_status.run_id, @run_status.elapsed_time.to_s] ) end #Failed chef-client run %1 in %2 seconds. #Exception type: %3 #Exception message: %4 #Exception backtrace: %5 def run_failed(e) data = if @run_status [@run_status.run_id, @run_status.elapsed_time.to_s] else %w{UNKNOWN UNKNOWN} end @eventlog.report_event( :event_type => ::Win32::EventLog::ERROR_TYPE, :source => SOURCE, :event_id => RUN_FAILED_EVENT_ID, :data => data + [e.class.name, e.message, e.backtrace.join("\n")] ) end end end end chef-12.14.60/lib/chef/exceptions.rb000066400000000000000000000501461276456504500170570ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Falcon () # Author:: Kyle Goodwin () # 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/exceptions" class Chef # == Chef::Exceptions # Chef's custom exceptions are all contained within the Chef::Exceptions # namespace. class Exceptions ConfigurationError = ChefConfig::ConfigurationError # Backcompat with Chef::ShellOut code: require "mixlib/shellout/exceptions" def self.const_missing(const_name) if const_name == :ShellCommandFailed Chef::Log.warn("Chef::Exceptions::ShellCommandFailed is deprecated, use Mixlib::ShellOut::ShellCommandFailed") called_from = caller[0..3].inject("Called from:\n") { |msg, trace_line| msg << " #{trace_line}\n" } Chef::Log.warn(called_from) Mixlib::ShellOut::ShellCommandFailed else super end 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 class ErlCall < RuntimeError; end class FileNotFound < RuntimeError; end class Package < RuntimeError; end class Service < RuntimeError; end class Script < RuntimeError; end class Route < RuntimeError; end class SearchIndex < RuntimeError; end class Override < RuntimeError; end 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" end end class User < RuntimeError; end 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 class DuplicateRole < RuntimeError; end class ValidationFailed < ArgumentError; end class CannotValidateStaticallyError < ArgumentError; end class InvalidPrivateKey < ArgumentError; end class MissingKeyAttribute < ArgumentError; end class KeyCommandInputError < ArgumentError; end class BootstrapCommandInputError < ArgumentError def initialize super "You cannot pass both --json-attributes and --json-attribute-file. Please pass one or none." end end class InvalidKeyArgument < ArgumentError; end class InvalidKeyAttribute < ArgumentError; end class InvalidUserAttribute < ArgumentError; end class InvalidClientAttribute < ArgumentError; end class RedirectLimitExceeded < RuntimeError; end class AmbiguousRunlistSpecification < ArgumentError; end class CookbookFrozen < ArgumentError; end class CookbookNotFound < RuntimeError; end class OnlyApiVersion0SupportedForAction < RuntimeError; end # Cookbook loader used to raise an argument error when cookbook not found. # for back compat, need to raise an error that inherits from ArgumentError class CookbookNotFoundInRepo < ArgumentError; end class RecipeNotFound < ArgumentError; end # AttributeNotFound really means the attribute file could not be found class AttributeNotFound < RuntimeError; end # NoSuchAttribute is raised on access by node.read!("foo", "bar") when node["foo"]["bar"] does not exist. class NoSuchAttribute < RuntimeError; end # AttributeTypeMismatch is raised by node.write!("foo", "bar", "baz") when e.g. node["foo"] = "bar" (overwriting String with Hash) class AttributeTypeMismatch < RuntimeError; end class MissingCookbookDependency < StandardError; end # CHEF-5120 class InvalidCommandOption < RuntimeError; end class CommandTimeout < RuntimeError; end class RequestedUIDUnavailable < RuntimeError; end class InvalidHomeDirectory < ArgumentError; end class DsclCommandFailed < RuntimeError; end class PlistUtilCommandFailed < RuntimeError; end class UserIDNotFound < ArgumentError; end class GroupIDNotFound < ArgumentError; end class ConflictingMembersInGroup < ArgumentError; end class InvalidResourceReference < RuntimeError; end class ResourceNotFound < RuntimeError; end class ProviderNotFound < RuntimeError; end NoProviderAvailable = ProviderNotFound class VerificationNotFound < RuntimeError; end class InvalidEventType < ArgumentError; end class MultipleIdentityError < RuntimeError; end # Used in Resource::ActionClass#load_current_resource to denote that # the resource doesn't actually exist (for example, the file does not exist) class CurrentValueDoesNotExist < RuntimeError; end # Can't find a Resource of this type that is valid on this platform. class NoSuchResourceType < NameError def initialize(short_name, node) super "Cannot find a resource for #{short_name} on #{node[:platform]} version #{node[:platform_version]}" end end class InvalidPolicybuilderCall < ArgumentError; end class InvalidResourceSpecification < ArgumentError; end class SolrConnectionError < RuntimeError; end class IllegalChecksumRevert < RuntimeError; end class CookbookVersionNameMismatch < ArgumentError; end class MissingParentDirectory < RuntimeError; end class UnresolvableGitReference < RuntimeError; end class InvalidRemoteGitReference < RuntimeError; end class InvalidEnvironmentRunListSpecification < ArgumentError; end class InvalidDataBagItemID < ArgumentError; end class InvalidDataBagName < ArgumentError; end class EnclosingDirectoryDoesNotExist < ArgumentError; end # Errors originating from calls to the Win32 API class Win32APIError < RuntimeError; end # Thrown when Win32 API layer binds to non-existent Win32 function. Occurs # when older versions of Windows don't support newer Win32 API functions. class Win32APIFunctionNotImplemented < NotImplementedError; end # Attempting to run windows code on a not-windows node class Win32NotWindows < RuntimeError; end class WindowsNotAdmin < RuntimeError; end # Attempting to access a 64-bit only resource on a 32-bit Windows system class Win32ArchitectureIncorrect < RuntimeError; end class ObsoleteDependencySyntax < ArgumentError; end class InvalidDataBagPath < ArgumentError; end class DuplicateDataBagItem < RuntimeError; end class PowershellCmdletException < RuntimeError; end class LCMParser < RuntimeError; end class CannotDetermineHomebrewOwner < Package; end class CannotDetermineWindowsInstallerType < Package; end class NoWindowsPackageSource < Package; end # Can not create staging file during file deployment class FileContentStagingError < RuntimeError def initialize(errors) super "Staging tempfile can not be created during file deployment.\n Errors: #{errors.join('\n')}!" end end # A different version of a cookbook was added to a # VersionedRecipeList than the one already there. class CookbookVersionConflict < ArgumentError; end # does not follow X.Y.Z format. ArgumentError? class InvalidPlatformVersion < ArgumentError; end class InvalidCookbookVersion < ArgumentError; end # version constraint should be a string or array, or it doesn't # match OP VERSION. ArgumentError? class InvalidVersionConstraint < ArgumentError; end # Version constraints are not allowed in chef-solo class IllegalVersionConstraint < NotImplementedError; end class MetadataNotValid < StandardError; end class MetadataNotFound < StandardError attr_reader :install_path attr_reader :cookbook_name def initialize(install_path, cookbook_name) @install_path = install_path @cookbook_name = cookbook_name super "No metadata.rb or metadata.json found for cookbook #{@cookbook_name} in #{@install_path}" end end # File operation attempted but no permissions to perform it class InsufficientPermissions < RuntimeError; end # Ifconfig failed class Ifconfig < RuntimeError; end # Invalid "source" parameter to a remote_file resource class InvalidRemoteFileURI < ArgumentError; end # Node::Attribute computes the merged version of of attributes # and makes it read-only. Attempting to modify a read-only # attribute will cause this error. class ImmutableAttributeModification < NoMethodError def initialize super "Node attributes are read-only when you do not specify which precedence level to set. " + %q{To set an attribute use code like `node.default["key"] = "value"'} end end # Merged node attributes are invalidated when the component # attributes are updated. Attempting to read from a stale copy # of merged attributes will trigger this error. class StaleAttributeRead < StandardError; end # Registry Helper throws the following errors class Win32RegArchitectureIncorrect < Win32ArchitectureIncorrect; end class Win32RegHiveMissing < ArgumentError; end class Win32RegKeyMissing < RuntimeError; end class Win32RegValueMissing < RuntimeError; end class Win32RegDataMissing < RuntimeError; end class Win32RegValueExists < RuntimeError; end class Win32RegNoRecursive < ArgumentError; end class Win32RegTypeDoesNotExist < ArgumentError; end class Win32RegBadType < ArgumentError; end class Win32RegBadValueSize < ArgumentError; end class Win32RegTypesMismatch < ArgumentError; end class InvalidEnvironmentPath < ArgumentError; end class EnvironmentNotFound < RuntimeError; end # File-like resource found a non-file (socket, pipe, directory, etc) at its destination class FileTypeMismatch < RuntimeError; end # File (or descendent) resource configured to manage symlink source, but # the symlink that is there either loops or points to a nonexistent file class InvalidSymlink < RuntimeError; end class ChildConvergeError < RuntimeError; end class DeprecatedFeatureError < RuntimeError; def initalize(message) super("#{message} (raising error due to treat_deprecation_warnings_as_errors being set)") end end class MissingRole < RuntimeError NULL = Object.new attr_reader :expansion def initialize(message_or_expansion = NULL) @expansion = nil case message_or_expansion when NULL super() when String super when RunList::RunListExpansion @expansion = message_or_expansion missing_roles = @expansion.errors.join(", ") super("The expanded run list includes nonexistent roles: #{missing_roles}") end end end # Exception class for collecting multiple failures. Used when running # delayed notifications so that chef can process each delayed # notification even if chef client or other notifications fail. class MultipleFailures < StandardError def initialize(*args) super @all_failures = [] end def message base = "Multiple failures occurred:\n" @all_failures.inject(base) do |message, (location, error)| message << "* #{error.class} occurred in #{location}: #{error.message}\n" end end def client_run_failure(exception) set_backtrace(exception.backtrace) @all_failures << [ "chef run", exception ] end def notification_failure(exception) @all_failures << [ "delayed notification", exception ] end def raise! unless empty? raise self.for_raise end end def empty? @all_failures.empty? end def for_raise if @all_failures.size == 1 @all_failures[0][1] else self end end end class CookbookVersionSelection # Compound exception: In run_list expansion and resolution, # run_list items referred to cookbooks that don't exist and/or # have no versions available. class InvalidRunListItems < StandardError attr_reader :non_existent_cookbooks attr_reader :cookbooks_with_no_matching_versions def initialize(message, non_existent_cookbooks, cookbooks_with_no_matching_versions) super(message) @non_existent_cookbooks = non_existent_cookbooks @cookbooks_with_no_matching_versions = cookbooks_with_no_matching_versions end def to_json(*a) result = { "message" => message, "non_existent_cookbooks" => non_existent_cookbooks, "cookbooks_with_no_versions" => cookbooks_with_no_matching_versions, } Chef::JSONCompat.to_json(result, *a) end end # In run_list expansion and resolution, a constraint was # unsatisfiable. # # This exception may not be the complete error report. If you # resolve the misconfiguration represented by this exception and # re-solve, you may get another exception class UnsatisfiableRunListItem < StandardError attr_reader :run_list_item attr_reader :non_existent_cookbooks, :most_constrained_cookbooks # most_constrained_cookbooks: if I were to remove constraints # regarding these cookbooks, I would get a solution or move on # to the next error (deeper in the graph). An item in this list # may be unsatisfiable, but when resolved may also reveal # further unsatisfiable constraints; this condition would not be # reported. def initialize(message, run_list_item, non_existent_cookbooks, most_constrained_cookbooks) super(message) @run_list_item = run_list_item @non_existent_cookbooks = non_existent_cookbooks @most_constrained_cookbooks = most_constrained_cookbooks end def to_json(*a) result = { "message" => message, "unsatisfiable_run_list_item" => run_list_item, "non_existent_cookbooks" => non_existent_cookbooks, "most_constrained_cookbooks" => most_constrained_cookbooks, } Chef::JSONCompat.to_json(result, *a) end end end # CookbookVersionSelection # When the server sends a redirect, RFC 2616 states a user-agent should # not follow it with a method other than GET or HEAD, unless a specific # action is taken by the user. A redirect received as response to a # non-GET and non-HEAD request will thus raise an InvalidRedirect. class InvalidRedirect < StandardError; end # Raised when the content length of a download does not match the content # length declared in the http response. class ContentLengthMismatch < RuntimeError def initialize(response_length, content_length) super <<-EOF Response body length #{response_length} does not match HTTP Content-Length header #{content_length}. This error is most often caused by network issues (proxies, etc) outside of chef-client. EOF end end class UnsupportedPlatform < RuntimeError def initialize(platform) super "This functionality is not supported on platform #{platform}." end end # Raised when Chef::Config[:run_lock_timeout] is set and some other client run fails # to release the run lock becure Chef::Config[:run_lock_timeout] seconds pass. class RunLockTimeout < RuntimeError def initialize(duration, blocking_pid) super "Unable to acquire lock. Waited #{duration} seconds for #{blocking_pid} to release." end end class ChecksumMismatch < RuntimeError def initialize(res_cksum, cont_cksum) super "Checksum on resource (#{res_cksum}) does not match checksum on content (#{cont_cksum})" end end class BadProxyURI < RuntimeError; end # Raised by Chef::JSONCompat class JSON class EncodeError < RuntimeError; end class ParseError < RuntimeError; end end class InvalidSearchQuery < ArgumentError; end # Raised by Chef::ProviderResolver class AmbiguousProviderResolution < RuntimeError def initialize(resource, classes) super "Found more than one provider for #{resource.resource_name} resource: #{classes}" end end class AuditError < RuntimeError; end class AuditControlGroupDuplicate < AuditError def initialize(name) super "Control group with name '#{name}' has already been defined" end end class AuditNameMissing < AuditError; end class NoAuditsProvided < AuditError def initialize super "You must provide a block with controls" end end class AuditsFailed < AuditError def initialize(num_failed, num_total) super "Audit phase found failures - #{num_failed}/#{num_total} controls failed" end end # If a converge or audit fails, we want to wrap the output from those errors into 1 error so we can # see both issues in the output. It is possible that nil will be provided. You must call `fill_backtrace` # to correctly populate the backtrace with the wrapped backtraces. class RunFailedWrappingError < RuntimeError attr_reader :wrapped_errors def initialize(*errors) errors = errors.select { |e| !e.nil? } output = "Found #{errors.size} errors, they are stored in the backtrace" @wrapped_errors = errors super output end def fill_backtrace backtrace = [] wrapped_errors.each_with_index do |e, i| backtrace << "#{i + 1}) #{e.class} - #{e.message}" backtrace += e.backtrace if e.backtrace backtrace << "" unless i == wrapped_errors.length - 1 end set_backtrace(backtrace) end end class PIDFileLockfileMatch < RuntimeError def initialize super "PID file and lockfile are not permitted to match. Specify a different location with --pid or --lockfile" end end class CookbookChefVersionMismatch < RuntimeError def initialize(chef_version, cookbook_name, cookbook_version, *constraints) constraint_str = constraints.map { |c| c.requirement.as_list.to_s }.join(", ") super "Cookbook '#{cookbook_name}' version '#{cookbook_version}' depends on chef version #{constraint_str}, but the running chef version is #{chef_version}" end end class CookbookOhaiVersionMismatch < RuntimeError def initialize(ohai_version, cookbook_name, cookbook_version, *constraints) constraint_str = constraints.map { |c| c.requirement.as_list.to_s }.join(", ") super "Cookbook '#{cookbook_name}' version '#{cookbook_version}' depends on ohai version #{constraint_str}, but the running ohai version is #{ohai_version}" end end class MultipleDscResourcesFound < RuntimeError attr_reader :resources_found def initialize(resources_found) @resources_found = resources_found matches_info = @resources_found.each do |r| if r["Module"].nil? "Resource #{r['Name']} was found in #{r['Module']['Name']}" else "Resource #{r['Name']} is a binary resource" end end super "Found multiple matching resources. #{matches_info.join("\n")}" end end end end chef-12.14.60/lib/chef/file_access_control.rb000066400000000000000000000045061276456504500206750ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" class Chef # == Chef::FileAccessControl # FileAccessControl objects set the owner, group and mode of +file+ to # the values specified by a value object, usually a Chef::Resource. class FileAccessControl if RUBY_PLATFORM =~ /mswin|mingw|windows/ require "chef/file_access_control/windows" include FileAccessControl::Windows else require "chef/file_access_control/unix" include FileAccessControl::Unix end attr_reader :current_resource attr_reader :resource attr_reader :provider attr_reader :file # FileAccessControl objects set the owner, group and mode of +file+ to # the values specified by +resource+. +file+ is completely independent # of any file or path attribute on +resource+, so it is possible to set # access control settings on a tempfile (for example). # === Arguments: # resource: probably a Chef::Resource::File object (or subclass), but # this is not required. Must respond to +owner+, +group+, # and +mode+ # file: The file whose access control settings you wish to modify, # given as a String. # # TODO requiring current_resource will break cookbook_file template_file def initialize(current_resource, new_resource, provider) @current_resource, @resource, @provider = current_resource, new_resource, provider @file = @current_resource.path @modified = false end def modified? @modified end private def modified @modified = true end def log_string @resource || @file end end end chef-12.14.60/lib/chef/file_access_control/000077500000000000000000000000001276456504500203435ustar00rootroot00000000000000chef-12.14.60/lib/chef/file_access_control/unix.rb000066400000000000000000000234251276456504500216610ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # Author:: Seth Chisamore () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" class Chef class FileAccessControl module Unix UINT = (1 << 32) UID_MAX = (1 << 32) - 10 module ClassMethods # We want to mix these in as class methods def writable?(path) ::File.writable?(path) end end def self.included(base) # When this file is mixed in, make sure we also add the class methods base.send :extend, ClassMethods end def set_all! set_owner! set_group! set_mode! end def set_all set_owner set_group set_mode end # TODO factor this up def requires_changes? should_update_mode? || should_update_owner? || should_update_group? end def define_resource_requirements uid_from_resource(resource) gid_from_resource(resource) end def describe_changes changes = [] changes << "change mode from '#{mode_to_s(current_mode)}' to '#{mode_to_s(target_mode)}'" if should_update_mode? changes << "change owner from '#{current_resource.owner}' to '#{resource.owner}'" if should_update_owner? changes << "change group from '#{current_resource.group}' to '#{resource.group}'" if should_update_group? changes end def target_uid uid_from_resource(resource) end def current_uid uid_from_resource(current_resource) end def should_update_owner? if target_uid.nil? # the user has not specified a permission on the new resource, so we never manage it with FAC Chef::Log.debug("Found target_uid == nil, so no owner was specified on resource, not managing owner") return false elsif current_uid.nil? # the user has specified a permission, and we are creating a file, so always enforce permissions Chef::Log.debug("Found current_uid == nil, so we are creating a new file, updating owner") return true elsif target_uid != current_uid # the user has specified a permission, and it does not match the file, so fix the permission Chef::Log.debug("Found target_uid != current_uid, updating owner") return true else Chef::Log.debug("Found target_uid == current_uid, not updating owner") # the user has specified a permission, but it matches the file, so behave idempotently return false end end def set_owner! unless target_uid.nil? chown(target_uid, nil, file) Chef::Log.info("#{log_string} owner changed to #{target_uid}") modified end end def set_owner set_owner! if should_update_owner? end def target_gid gid_from_resource(resource) end def current_gid gid_from_resource(current_resource) end def gid_from_resource(resource) return nil if resource == nil || resource.group.nil? if resource.group.kind_of?(String) diminished_radix_complement( Etc.getgrnam(resource.group).gid ) elsif resource.group.kind_of?(Integer) resource.group else Chef::Log.error("The `group` parameter of the #@resource resource is set to an invalid value (#{resource.owner.inspect})") raise ArgumentError, "cannot resolve #{resource.group.inspect} to gid, group must be a string or integer" end rescue ArgumentError provider.requirements.assert(:create, :create_if_missing, :touch) do |a| a.assertion { false } a.failure_message(Chef::Exceptions::GroupIDNotFound, "cannot determine group id for '#{resource.group}', does the group exist on this system?") a.whyrun("Assuming group #{resource.group} would have been created") end return nil end def should_update_group? if target_gid.nil? # the user has not specified a permission on the new resource, so we never manage it with FAC Chef::Log.debug("Found target_gid == nil, so no group was specified on resource, not managing group") return false elsif current_gid.nil? # the user has specified a permission, and we are creating a file, so always enforce permissions Chef::Log.debug("Found current_gid == nil, so we are creating a new file, updating group") return true elsif target_gid != current_gid # the user has specified a permission, and it does not match the file, so fix the permission Chef::Log.debug("Found target_gid != current_gid, updating group") return true else Chef::Log.debug("Found target_gid == current_gid, not updating group") # the user has specified a permission, but it matches the file, so behave idempotently return false end end def set_group! unless target_gid.nil? chown(nil, target_gid, file) Chef::Log.info("#{log_string} group changed to #{target_gid}") modified end end def set_group set_group! if should_update_group? end def mode_from_resource(res) return nil if res == nil || res.mode.nil? (res.mode.respond_to?(:oct) ? res.mode.oct : res.mode.to_i) & 007777 end def target_mode mode_from_resource(resource) end def mode_to_s(mode) mode.nil? ? "" : "0#{mode.to_s(8)}" end def current_mode mode_from_resource(current_resource) end def should_update_mode? if target_mode.nil? # the user has not specified a permission on the new resource, so we never manage it with FAC Chef::Log.debug("Found target_mode == nil, so no mode was specified on resource, not managing mode") return false elsif current_mode.nil? # the user has specified a permission, and we are creating a file, so always enforce permissions Chef::Log.debug("Found current_mode == nil, so we are creating a new file, updating mode") return true elsif target_mode != current_mode # the user has specified a permission, and it does not match the file, so fix the permission Chef::Log.debug("Found target_mode != current_mode, updating mode") return true elsif suid_bit_set? && (should_update_group? || should_update_owner?) return true else Chef::Log.debug("Found target_mode == current_mode, not updating mode") # the user has specified a permission, but it matches the file, so behave idempotently return false end end def set_mode! unless target_mode.nil? chmod(target_mode, file) Chef::Log.info("#{log_string} mode changed to #{target_mode.to_s(8)}") modified end end def set_mode set_mode! if should_update_mode? end def stat if manage_symlink_attrs? @stat ||= File.lstat(file) else @stat ||= File.stat(file) end end def manage_symlink_attrs? @provider.manage_symlink_access? end private def chmod(mode, file) if manage_symlink_attrs? begin File.lchmod(mode, file) rescue NotImplementedError Chef::Log.warn("#{file} mode not changed: File.lchmod is unimplemented on this OS and Ruby version") end else File.chmod(mode, file) end end def chown(uid, gid, file) if manage_symlink_attrs? File.lchown(uid, gid, file) else File.chown(uid, gid, file) end end # Workaround the fact that Ruby's Etc module doesn't believe in negative # uids, so negative uids show up as the diminished radix complement of # a uint. For example, a uid of -2 is reported as 4294967294 def diminished_radix_complement(int) if int > UID_MAX int - UINT else int end end def uid_from_resource(resource) return nil if resource == nil || resource.owner.nil? if resource.owner.kind_of?(String) diminished_radix_complement( Etc.getpwnam(resource.owner).uid ) elsif resource.owner.kind_of?(Integer) resource.owner else Chef::Log.error("The `owner` parameter of the #@resource resource is set to an invalid value (#{resource.owner.inspect})") raise ArgumentError, "cannot resolve #{resource.owner.inspect} to uid, owner must be a string or integer" end rescue ArgumentError provider.requirements.assert(:create, :create_if_missing, :touch) do |a| a.assertion { false } a.failure_message(Chef::Exceptions::UserIDNotFound, "cannot determine user id for '#{resource.owner}', does the user exist on this system?") a.whyrun("Assuming user #{resource.owner} would have been created") end return nil end def suid_bit_set? return target_mode & 04000 > 0 end end end end chef-12.14.60/lib/chef/file_access_control/windows.rb000066400000000000000000000244661276456504500223760ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/security" require "chef/win32/file" class Chef class FileAccessControl module Windows include Chef::ReservedNames::Win32::API::Security Security = Chef::ReservedNames::Win32::Security ACL = Security::ACL ACE = Security::ACE SID = Security::SID module ClassMethods # We want to mix these in as class methods def writable?(path) ::File.exists?(path) && Chef::ReservedNames::Win32::File.file_access_check( path, Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE) end end def self.included(base) # When this file is mixed in, make sure we also add the class methods base.send :extend, ClassMethods end def set_all! set_owner! set_group! set_dacl end def set_all set_owner set_group set_dacl end def define_resource_requirements # windows FAC has no assertions end def requires_changes? should_update_dacl? || should_update_owner? || should_update_group? end def describe_changes # FIXME: describe what these are changing from and to changes = [] changes << "change dacl" if should_update_dacl? changes << "change owner" if should_update_owner? changes << "change group" if should_update_group? changes end private # Compare the actual ACL on a resource with the ACL we want. This # ignores explicit ACLs on the target, and does mask prediction (if you # set GENERIC_WRITE, Windows will flip on a whole bunch of other rights # on the file when you save the ACL) def acls_equal(target_acl, actual_acl) if actual_acl.nil? return target_acl.nil? end actual_acl = actual_acl.select { |ace| !ace.inherited? } # When ACLs apply to children, Windows splits them on the file system into two ACLs: # one specific applying to this container, and one generic applying to children. new_target_acl = [] target_acl.each do |target_ace| if target_ace.flags & INHERIT_ONLY_ACE == 0 self_ace = target_ace.dup self_ace.flags = 0 self_ace.mask = securable_object.predict_rights_mask(target_ace.mask) new_target_acl << self_ace end if target_ace.flags & (CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE) != 0 children_ace = target_ace.dup children_ace.flags |= INHERIT_ONLY_ACE new_target_acl << children_ace end end return actual_acl == new_target_acl end def existing_descriptor securable_object.security_descriptor end def get_sid(value) if value.kind_of?(String) SID.from_account(value) elsif value.kind_of?(SID) value else raise "Must specify username, group or SID: #{value}" end end def securable_object @securable_object ||= begin if file.kind_of?(String) so = Chef::ReservedNames::Win32::Security::SecurableObject.new(file.dup) end raise ArgumentError, "'file' must be a valid path or object of type 'Chef::ReservedNames::Win32::Security::SecurableObject'" unless so.kind_of? Chef::ReservedNames::Win32::Security::SecurableObject so end end def should_update_dacl? return true unless ::File.exists?(file) || ::File.symlink?(file) dacl = target_dacl existing_dacl = existing_descriptor.dacl inherits = target_inherits ( ! inherits.nil? && inherits != existing_descriptor.dacl_inherits? ) || ( dacl && !acls_equal(dacl, existing_dacl) ) end def set_dacl! set_dacl end def set_dacl dacl = target_dacl existing_dacl = existing_descriptor.dacl inherits = target_inherits if ! inherits.nil? && inherits != existing_descriptor.dacl_inherits? # We have to set DACL along with inherits. If rights were not # specified, we need to change only inherited ACLs and leave # explicit ACLs alone. if dacl.nil? && !existing_dacl.nil? dacl = ACL.create(existing_dacl.select { |ace| !ace.inherited? }) end securable_object.set_dacl(dacl, inherits) Chef::Log.info("#{log_string} permissions changed to #{dacl} with inherits of #{inherits}") modified elsif dacl && !acls_equal(dacl, existing_dacl) securable_object.dacl = dacl Chef::Log.info("#{log_string} permissions changed to #{dacl}") modified end end def should_update_group? return true unless ::File.exists?(file) || ::File.symlink?(file) (group = target_group) && (group != existing_descriptor.group) end def set_group! if (group = target_group) Chef::Log.info("#{log_string} group changed to #{group}") securable_object.group = group modified end end def set_group if (group = target_group) && (group != existing_descriptor.group) set_group! end end def should_update_owner? return true unless ::File.exists?(file) || ::File.symlink?(file) (owner = target_owner) && (owner != existing_descriptor.owner) end def set_owner! if owner = target_owner Chef::Log.info("#{log_string} owner changed to #{owner}") securable_object.owner = owner modified end end def set_owner if (owner = target_owner) && (owner != existing_descriptor.owner) set_owner! end end def mode_ace(sid, mode) mask = 0 mask |= GENERIC_READ if mode & 4 != 0 mask |= (GENERIC_WRITE | DELETE) if mode & 2 != 0 mask |= GENERIC_EXECUTE if mode & 1 != 0 return [] if mask == 0 [ ACE.access_allowed(sid, mask) ] end def calculate_mask(permissions) mask = 0 [ permissions ].flatten.each do |permission| case permission when :full_control mask |= GENERIC_ALL when :modify mask |= GENERIC_WRITE | GENERIC_READ | GENERIC_EXECUTE | DELETE when :read mask |= GENERIC_READ when :read_execute mask |= GENERIC_READ | GENERIC_EXECUTE when :write mask |= GENERIC_WRITE else # Otherwise, assume it's an integer specifying the actual flags mask |= permission end end mask end def calculate_flags(rights) # Handle inheritance flags flags = 0 # # Configure child inheritence only if the resource is some # type of a directory. # if resource.is_a? Chef::Resource::Directory case rights[:applies_to_children] when :containers_only flags |= CONTAINER_INHERIT_ACE when :objects_only flags |= OBJECT_INHERIT_ACE when true flags |= CONTAINER_INHERIT_ACE flags |= OBJECT_INHERIT_ACE when nil flags |= CONTAINER_INHERIT_ACE flags |= OBJECT_INHERIT_ACE end end if rights[:applies_to_self] == false flags |= INHERIT_ONLY_ACE end if rights[:one_level_deep] flags |= NO_PROPAGATE_INHERIT_ACE end flags end def target_dacl return nil if resource.rights.nil? && resource.deny_rights.nil? && resource.mode.nil? acls = nil if !resource.deny_rights.nil? acls = [] if acls.nil? resource.deny_rights.each do |rights| mask = calculate_mask(rights[:permissions]) [ rights[:principals] ].flatten.each do |principal| sid = get_sid(principal) flags = calculate_flags(rights) acls.push ACE.access_denied(sid, mask, flags) end end end if !resource.rights.nil? acls = [] if acls.nil? resource.rights.each do |rights| mask = calculate_mask(rights[:permissions]) [ rights[:principals] ].flatten.each do |principal| sid = get_sid(principal) flags = calculate_flags(rights) acls.push ACE.access_allowed(sid, mask, flags) end end end if !resource.mode.nil? acls = [] if acls.nil? mode = (resource.mode.respond_to?(:oct) ? resource.mode.oct : resource.mode.to_i) & 0777 owner = target_owner if owner acls += mode_ace(owner, (mode & 0700) >> 6) elsif mode & 0700 != 0 Chef::Log.warn("Mode #{sprintf("%03o", mode)} includes bits for the owner, but owner is not specified") end group = target_group if group acls += mode_ace(group, (mode & 070) >> 3) elsif mode & 070 != 0 Chef::Log.warn("Mode #{sprintf("%03o", mode)} includes bits for the group, but group is not specified") end acls += mode_ace(SID.Everyone, (mode & 07)) end acls.nil? ? nil : Chef::ReservedNames::Win32::Security::ACL.create(acls) end def target_group return nil if resource.group.nil? get_sid(resource.group) end def target_inherits resource.inherits end def target_owner return nil if resource.owner.nil? get_sid(resource.owner) end end end end chef-12.14.60/lib/chef/file_cache.rb000066400000000000000000000144751276456504500167450ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/mixin/params_validate" require "chef/mixin/create_path" require "chef/exceptions" require "chef/json_compat" require "fileutils" require "chef/util/path_helper" class Chef class FileCache class << self include Chef::Mixin::ParamsValidate include Chef::Mixin::CreatePath # Write a file to the File Cache. # # === Parameters # path:: The path to the file you want to put in the cache - should # be relative to file_cache_path # contents:: A string with the contents you want written to the file # perm:: Sets file permission bits. Permission bits are platform # dependent; on Unix systems, see open(2) for details. # # === Returns # true def store(path, contents, perm = 0640) validate( { :path => path, :contents => contents, }, { :path => { :kind_of => String }, :contents => { :kind_of => String }, } ) file_path_array = File.split(path) file_name = file_path_array.pop cache_path = create_cache_path(File.join(file_path_array)) File.open(File.join(cache_path, file_name), "w", perm) do |io| io.print(contents) end true end # Move a file into the cache. Useful with the REST raw file output. # # === Parameters # file:: The path to the file you want in the cache # path:: The relative name you want the new file to use def move_to(file, path) validate( { :file => file, :path => path, }, { :file => { :kind_of => String }, :path => { :kind_of => String }, } ) file_path_array = File.split(path) file_name = file_path_array.pop if File.exists?(file) && File.writable?(file) FileUtils.mv( file, File.join(create_cache_path(File.join(file_path_array), true), file_name) ) else raise "Cannot move #{file} to #{path}!" end end # Read a file from the File Cache # # === Parameters # path:: The path to the file you want to load - should # be relative to file_cache_path # read:: Whether to return the file contents, or the path. # Defaults to true. # # === Returns # String:: A string with the file contents, or the path to the file. # # === Raises # Chef::Exceptions::FileNotFound:: If it cannot find the file in the cache def load(path, read = true) validate( { :path => path, }, { :path => { :kind_of => String }, } ) cache_path = create_cache_path(path, false) raise Chef::Exceptions::FileNotFound, "Cannot find #{cache_path} for #{path}!" unless File.exists?(cache_path) if read File.read(cache_path) else cache_path end end # Delete a file from the File Cache # # === Parameters # path:: The path to the file you want to delete - should # be relative to file_cache_path # # === Returns # true def delete(path) validate( { :path => path, }, { :path => { :kind_of => String }, } ) cache_path = create_cache_path(path, false) if File.exists?(cache_path) File.unlink(cache_path) end true end # List all the files in the Cache # # === Returns # Array:: An array of files in the cache, suitable for use with load, delete and store def list find("**#{File::Separator}*") end ## # Find files in the cache by +glob_pattern+ # === Returns # [String] - An array of file cache keys matching the glob def find(glob_pattern) keys = Array.new Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(file_cache_path), glob_pattern)].each do |f| if File.file?(f) keys << f[/^#{Regexp.escape(Dir[Chef::Util::PathHelper.escape_glob_dir(file_cache_path)].first) + File::Separator}(.+)/, 1] end end keys end # Whether or not this file exists in the Cache # # === Parameters # path:: The path to the file you want to check - is relative # to file_cache_path # # === Returns # True:: If the file exists # False:: If it does not def has_key?(path) validate( { :path => path, }, { :path => { :kind_of => String }, } ) full_path = create_cache_path(path, false) if File.exists?(full_path) true else false end end # Create a full path to a given file in the cache. By default, # also creates the path if it does not exist. # # === Parameters # path:: The path to create, relative to file_cache_path # create_if_missing:: True by default - whether to create the path if it does not exist # # === Returns # String:: The fully expanded path def create_cache_path(path, create_if_missing = true) cache_dir = File.expand_path(File.join(file_cache_path, path)) if create_if_missing create_path(cache_dir) else cache_dir end end private def file_cache_path Chef::Config[:file_cache_path] end end end end chef-12.14.60/lib/chef/file_content_management/000077500000000000000000000000001276456504500212105ustar00rootroot00000000000000chef-12.14.60/lib/chef/file_content_management/content_base.rb000066400000000000000000000031511276456504500242010ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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 FileContentManagement class ContentBase attr_reader :run_context attr_reader :new_resource attr_reader :current_resource def initialize(new_resource, current_resource, run_context) @new_resource = new_resource @current_resource = current_resource @run_context = run_context @tempfile_loaded = false end def tempfile # tempfile may be nil, so we cannot use ||= here if @tempfile_loaded @tempfile else @tempfile_loaded = true @tempfile = file_for_provider end end private # # Return something that looks like a File or Tempfile and # you must assume the provider will unlink this file. Copy # the contents to a Tempfile if you need to. # def file_for_provider raise "class must implement file_for_provider!" end end end end chef-12.14.60/lib/chef/file_content_management/deploy.rb000066400000000000000000000021731276456504500230340ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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/file_content_management/deploy/cp" require "chef/file_content_management/deploy/mv_unix" if Chef::Platform.windows? require "chef/file_content_management/deploy/mv_windows" end class Chef class FileContentManagement class Deploy def self.strategy(atomic_update) if atomic_update Chef::Platform.windows? ? MvWindows.new() : MvUnix.new() else Cp.new() end end end end end chef-12.14.60/lib/chef/file_content_management/deploy/000077500000000000000000000000001276456504500225045ustar00rootroot00000000000000chef-12.14.60/lib/chef/file_content_management/deploy/cp.rb000066400000000000000000000033451276456504500234400ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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. # # # PURPOSE: This strategy preserves the inode, and will preserve modes + ownership # even if the user running chef cannot create that ownership (but has # rights to the file). It is vulnerable to crashes in the middle of # writing the file which could result in corruption or zero-length files. # class Chef class FileContentManagement class Deploy # # PURPOSE: This strategy preserves the inode, and will preserve modes + ownership # even if the user running chef cannot create that ownership (but has # rights to the file). It is vulnerable to crashes in the middle of # writing the file which could result in corruption or zero-length files. # class Cp def create(file) Chef::Log.debug("Touching #{file} to create it") FileUtils.touch(file) end def deploy(src, dst) Chef::Log.debug("Copying temporary file #{src} into place at #{dst}") FileUtils.cp(src, dst) end end end end end chef-12.14.60/lib/chef/file_content_management/deploy/mv_unix.rb000066400000000000000000000062031276456504500245170ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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 FileContentManagement class Deploy # # PURPOSE: this strategy is atomic, and attempts to preserve file modes # # NOTE: there is no preserve flag to FileUtils.mv, and we want to preserve the dst file # modes rather than the src file modes (preserve = true is what mv does already, we # would like preserve = false which is tricky). # class MvUnix def create(file) # this is very simple, but it ensures that ownership and file modes take # good defaults, in particular mode needs to obey umask on create Chef::Log.debug("Touching #{file} to create it") FileUtils.touch(file) end def deploy(src, dst) # we are only responsible for content so restore the dst files perms Chef::Log.debug("Reading modes from #{dst} file") stat = ::File.stat(dst) mode = stat.mode & 07777 uid = stat.uid gid = stat.gid Chef::Log.debug("Applying mode = #{mode.to_s(8)}, uid = #{uid}, gid = #{gid} to #{src}") # i own the inode, so should be able to at least chmod it ::File.chmod(mode, src) # we may be running as non-root in which case because we are doing an mv we cannot preserve # the file modes. after the mv we have a different inode and if we don't have rights to # chown/chgrp on the inode then we can't fix the ownership. # # in the case where i'm running chef-solo on my homedir as myself and some root-shell # work has caused dotfiles of mine to change to root-owned, i'm fine with this not being # exceptional, and i think most use cases will consider this to not be exceptional, and # the right thing is to fix the ownership of the file to the user running the commmand # (which requires write perms to the directory, or mv will throw an exception) begin ::File.chown(uid, nil, src) rescue Errno::EPERM Chef::Log.warn("Could not set uid = #{uid} on #{src}, file modes not preserved") end begin ::File.chown(nil, gid, src) rescue Errno::EPERM Chef::Log.warn("Could not set gid = #{gid} on #{src}, file modes not preserved") end Chef::Log.debug("Moving temporary file #{src} into place at #{dst}") FileUtils.mv(src, dst) end end end end end chef-12.14.60/lib/chef/file_content_management/deploy/mv_windows.rb000066400000000000000000000063061276456504500252320ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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. # # # We update the contents of the file, using mv for atomicity, while maintaining all the # ACL information on the dst file. # require "chef/platform/query_helpers" if Chef::Platform.windows? require "chef/win32/security" end class Chef class FileContentManagement class Deploy class MvWindows Security = Chef::ReservedNames::Win32::Security ACL = Security::ACL def create(file) Chef::Log.debug("Touching #{file} to create it") FileUtils.touch(file) end def deploy(src, dst) # # At the time of deploy ACLs are correctly configured on the # dst. This would be a simple atomic move operations in # windows was not converting inherited ACLs of src to # non-inherited ACLs in certain cases.See: # http://blogs.msdn.com/b/oldnewthing/archive/2006/08/24/717181.aspx # # # First cache the ACLs of dst file # dst_so = Security::SecurableObject.new(dst) begin # get the sd with the SACL dst_sd = dst_so.security_descriptor(true) rescue Chef::Exceptions::Win32APIError # Catch and raise if the user is not elevated enough. # At this point we can't configure the file as expected so # we're failing action on the resource. raise Chef::Exceptions::WindowsNotAdmin, "can not get the security information for '#{dst}' due to missing Administrator privileges." end dacl_present = dst_sd.dacl_present? if dacl_present if dst_sd.dacl.nil? apply_dacl = nil else apply_dacl = ACL.create(dst_sd.dacl.select { |ace| !ace.inherited? }) end end sacl_present = dst_sd.sacl_present? if sacl_present if dst_sd.sacl.nil? apply_sacl = nil else apply_sacl = ACL.create(dst_sd.sacl.select { |ace| !ace.inherited? }) end end # # Then deploy the file # FileUtils.mv(src, dst) # # Then apply the cached acls to the new dst file # dst_so = Security::SecurableObject.new(dst) dst_so.group = dst_sd.group dst_so.owner = dst_sd.owner dst_so.set_dacl(apply_dacl, dst_sd.dacl_inherits?) if dacl_present dst_so.set_sacl(apply_sacl, dst_sd.sacl_inherits?) if sacl_present end end end end end chef-12.14.60/lib/chef/file_content_management/tempfile.rb000066400000000000000000000062501276456504500233450ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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 "tempfile" class Chef class FileContentManagement class Tempfile attr_reader :new_resource def initialize(new_resource) @new_resource = new_resource end def tempfile @tempfile ||= tempfile_open end private def tempfile_open tf = nil errors = [ ] tempfile_dirnames.each do |tempfile_dirname| begin tf = ::Tempfile.open(tempfile_basename, tempfile_dirname) break rescue SystemCallError => e message = "Creating temp file under '#{tempfile_dirname}' failed with: '#{e.message}'" Chef::Log.debug(message) errors << message end end raise Chef::Exceptions::FileContentStagingError, errors if tf.nil? # We always process the tempfile in binmode so that we # preserve the line endings of the content. tf.binmode tf end # # These are important for windows to get permissions right, and may # be useful for SELinux and other ACL approaches. Please use them # as the arguments to Tempfile.new() consistently. # def tempfile_basename basename = ::File.basename(@new_resource.name) basename.insert 0, "chef-" basename.insert 0, "." unless Chef::Platform.windows? # dotfile if we're not on windows basename end # Returns the possible directories for the tempfile to be created in. def tempfile_dirnames # in why-run mode we need to create a Tempfile to compare against, which we will never # wind up deploying, but our enclosing directory for the destdir may not exist yet, so # instead we can reliably always create a Tempfile to compare against in Dir::tmpdir if Chef::Config[:why_run] [ Dir.tmpdir ] else case Chef::Config[:file_staging_uses_destdir] when :auto # In auto mode we try the destination directory first and fallback to ENV['TMP'] if # that doesn't work. [ ::File.dirname(@new_resource.path), Dir.tmpdir ] when true [ ::File.dirname(@new_resource.path) ] when false [ Dir.tmpdir ] else raise Chef::Exceptions::ConfigurationError, "Unknown setting '#{Chef::Config[:file_staging_uses_destdir]}' for Chef::Config[:file_staging_uses_destdir]. Possible values are :auto, true or false." end end end end end end chef-12.14.60/lib/chef/formatters/000077500000000000000000000000001276456504500165315ustar00rootroot00000000000000chef-12.14.60/lib/chef/formatters/base.rb000066400000000000000000000154441276456504500200000ustar00rootroot00000000000000# # Author:: Tyler Cloke () # # 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 "chef/event_dispatch/base" require "chef/formatters/error_inspectors" require "chef/formatters/error_description" require "chef/formatters/error_mapper" require "chef/formatters/indentable_output_stream" class Chef # == Chef::Formatters # Formatters handle printing output about the progress/status of a chef # client run to the user's screen. module Formatters class UnknownFormatter < StandardError; end def self.formatters_by_name @formatters_by_name ||= {} end def self.register(name, formatter) formatters_by_name[name.to_s] = formatter end def self.by_name(name) formatters_by_name[name] end def self.available_formatters formatters_by_name.keys end #-- # TODO: is it too clever to be defining new() on a module like this? def self.new(name, out, err) formatter_class = by_name(name.to_s) raise UnknownFormatter, "No output formatter found for #{name} (available: #{available_formatters.join(', ')})" unless formatter_class formatter_class.new(out, err) end # == Formatters::Base # Base class that all formatters should inherit from. class Base < EventDispatch::Base include ErrorMapper def self.cli_name(name) Chef::Formatters.register(name, self) end attr_reader :out attr_reader :err attr_reader :output def initialize(out, err) @output = IndentableOutputStream.new(out, err) end def puts(*args) @output.puts(*args) end def print(*args) @output.print(*args) end def puts_line(*args) @output.puts_line(*args) end def start_line(*args) @output.start_line(*args) end def indent_by(amount) @output.indent += amount if @output.indent < 0 # This is left commented out for now. We need to uncomment it and fix at least one bug in # the formatter, and then leave this line uncommented in the future. #Chef::Log.warn "Internal Formatter Error -- Attempt to indent by negative number of spaces" @output.indent = 0 end @output.indent end # Input: a Formatters::ErrorDescription object. # Outputs error to STDOUT. def display_error(description) puts("") description.display(output) end def registration_failed(node_name, exception, config) #A Formatters::ErrorDescription object description = ErrorMapper.registration_failed(node_name, exception, config) display_error(description) end def node_load_failed(node_name, exception, config) description = ErrorMapper.node_load_failed(node_name, exception, config) display_error(description) end def run_list_expand_failed(node, exception) description = ErrorMapper.run_list_expand_failed(node, exception) display_error(description) end def cookbook_resolution_failed(expanded_run_list, exception) description = ErrorMapper.cookbook_resolution_failed(expanded_run_list, exception) display_error(description) end def cookbook_sync_failed(cookbooks, exception) description = ErrorMapper.cookbook_sync_failed(cookbooks, exception) display_error(description) end def resource_failed(resource, action, exception) description = ErrorMapper.resource_failed(resource, action, exception) display_error(description) end # Generic callback for any attribute/library/lwrp/recipe file in a # cookbook getting loaded. The per-filetype callbacks for file load are # overriden so that they call this instead. This means that a subclass of # Formatters::Base can implement #file_loaded to do the same thing for # every kind of file that Chef loads from a recipe instead of # implementing all the per-filetype callbacks. def file_loaded(path) end # Generic callback for any attribute/library/lwrp/recipe file throwing an # exception when loaded. Default behavior is to use CompileErrorInspector # to print contextual info about the failure. def file_load_failed(path, exception) description = ErrorMapper.file_load_failed(path, exception) display_error(description) end def recipe_not_found(exception) description = ErrorMapper.file_load_failed(nil, exception) display_error(description) end # Delegates to #file_loaded def library_file_loaded(path) file_loaded(path) end # Delegates to #file_load_failed def library_file_load_failed(path, exception) file_load_failed(path, exception) end # Delegates to #file_loaded def lwrp_file_loaded(path) file_loaded(path) end # Delegates to #file_load_failed def lwrp_file_load_failed(path, exception) file_load_failed(path, exception) end # Delegates to #file_loaded def attribute_file_loaded(path) file_loaded(path) end # Delegates to #file_load_failed def attribute_file_load_failed(path, exception) file_load_failed(path, exception) end # Delegates to #file_loaded def definition_file_loaded(path) file_loaded(path) end # Delegates to #file_load_failed def definition_file_load_failed(path, exception) file_load_failed(path, exception) end # Delegates to #file_loaded def recipe_file_loaded(path, recipe) file_loaded(path) end # Delegates to #file_load_failed def recipe_file_load_failed(path, exception, recipe) file_load_failed(path, exception) end def deprecation(message, location = caller(2..2)[0]) Chef::Log.deprecation("#{message} at #{location}") end def is_formatter? true end end # == NullFormatter # Formatter that doesn't actually produce any output. You can use this to # disable the use of output formatters. class NullFormatter < Base cli_name(:null) def is_formatter? false end end end end chef-12.14.60/lib/chef/formatters/doc.rb000066400000000000000000000321671276456504500176340ustar00rootroot00000000000000require "chef/formatters/base" require "chef/config" class Chef module Formatters # Formatter similar to RSpec's documentation formatter. Uses indentation to # show context. class Doc < Formatters::Base attr_reader :start_time, :end_time, :successful_audits, :failed_audits private :successful_audits, :failed_audits cli_name(:doc) def initialize(out, err) super @updated_resources = 0 @up_to_date_resources = 0 @successful_audits = 0 @failed_audits = 0 @start_time = Time.now @end_time = @start_time @skipped_resources = 0 @progress = {} end def elapsed_time end_time - start_time end def pretty_elapsed_time time = elapsed_time if time < 60 message = Time.at(time).utc.strftime("%S seconds") elsif time < 3600 message = Time.at(time).utc.strftime("%M minutes %S seconds") else message = Time.at(time).utc.strftime("%H hours %M minutes %S seconds") end message end def run_start(version) puts_line "Starting Chef Client, version #{version}" puts_line "OpenSSL FIPS 140 mode enabled" if Chef::Config[:fips] end def total_resources @up_to_date_resources + @updated_resources + @skipped_resources end def total_audits successful_audits + failed_audits end def run_completed(node) @end_time = Time.now # Print out deprecations. if !deprecations.empty? puts_line "" puts_line "Deprecated features used!" deprecations.each do |message, locations| if locations.size == 1 puts_line " #{message} at #{locations.size} location:" else puts_line " #{message} at #{locations.size} locations:" end locations.each do |location| prefix = " - " Array(location).each do |line| puts_line "#{prefix}#{line}" prefix = " " end end end puts_line "" end if Chef::Config[:why_run] puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources would have been updated" else puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{pretty_elapsed_time}" if total_audits > 0 puts_line " #{successful_audits}/#{total_audits} controls succeeded" end end end def run_failed(exception) @end_time = Time.now if Chef::Config[:why_run] puts_line "Chef Client failed. #{@updated_resources} resources would have been updated" else puts_line "Chef Client failed. #{@updated_resources} resources updated in #{pretty_elapsed_time}" if total_audits > 0 puts_line " #{successful_audits} controls succeeded" end end end # Called right after ohai runs. def ohai_completed(node) end # Already have a client key, assuming this node has registered. def skipping_registration(node_name, config) end # About to attempt to register as +node_name+ def registration_start(node_name, config) puts_line "Creating a new client identity for #{node_name} using the validator key." end def registration_completed end def node_load_start(node_name, config) end # Failed to load node data from the server def node_load_failed(node_name, exception, config) super end # Default and override attrs from roles have been computed, but not yet applied. # Normal attrs from JSON have been added to the node. def node_load_completed(node, expanded_run_list, config) end def policyfile_loaded(policy) puts_line "Using policy '#{policy["name"]}' at revision '#{policy["revision_id"]}'" end # Called before the cookbook collection is fetched from the server. def cookbook_resolution_start(expanded_run_list) puts_line "resolving cookbooks for run list: #{expanded_run_list.inspect}" end # Called when there is an error getting the cookbook collection from the # server. def cookbook_resolution_failed(expanded_run_list, exception) super end # Called when the cookbook collection is returned from the server. def cookbook_resolution_complete(cookbook_collection) end # Called before unneeded cookbooks are removed def cookbook_clean_start end # Called after the file at +path+ is removed. It may be removed if the # cookbook containing it was removed from the run list, or if the file was # removed from the cookbook. def removed_cookbook_file(path) end # Called when cookbook cleaning is finished. def cookbook_clean_complete end # Called before cookbook sync starts def cookbook_sync_start(cookbook_count) puts_line "Synchronizing Cookbooks:" indent end # Called when cookbook +cookbook+ has been sync'd def synchronized_cookbook(cookbook_name, cookbook) puts_line "- #{cookbook.name} (#{cookbook.version})" end # Called when an individual file in a cookbook has been updated def updated_cookbook_file(cookbook_name, path) end # Called after all cookbooks have been sync'd. def cookbook_sync_complete unindent end # Called when starting to collect gems from the cookbooks def cookbook_gem_start(gems) puts_line "Installing Cookbook Gems:" indent end # Called when the result of installing the bundle is to install the gem def cookbook_gem_installing(gem, version) puts_line "- Installing #{gem} #{version}", :green end # Called when the result of installing the bundle is to use the gem def cookbook_gem_using(gem, version) puts_line "- Using #{gem} #{version}" end # Called when finished installing cookbook gems def cookbook_gem_finished unindent end # Called when cookbook gem installation fails def cookbook_gem_failed(exception) unindent end # Called when cookbook loading starts. def library_load_start(file_count) puts_line "Compiling Cookbooks..." end # Called after a file in a cookbook is loaded. def file_loaded(path) end # Called when recipes have been loaded. def recipe_load_complete end # Called before convergence starts def converge_start(run_context) puts_line "Converging #{run_context.resource_collection.all_resources.size} resources" end # Called when the converge phase is finished. def converge_complete unindent if @current_recipe end def converge_failed(e) # Currently a failed converge is handled the same way as a successful converge converge_complete end # Called before audit phase starts def audit_phase_start(run_status) puts_line "Starting audit phase" end def audit_phase_complete(audit_output) puts_line audit_output puts_line "Auditing complete" end def audit_phase_failed(error, audit_output) puts_line audit_output puts_line "" puts_line "Audit phase exception:" indent puts_line "#{error.message}" if error.backtrace error.backtrace.each do |l| puts_line l end end end def control_example_success(control_group_name, example_data) @successful_audits += 1 end def control_example_failure(control_group_name, example_data, error) @failed_audits += 1 end # Called before action is executed on a resource. def resource_action_start(resource, action, notification_type = nil, notifier = nil) if resource.cookbook_name && resource.recipe_name resource_recipe = "#{resource.cookbook_name}::#{resource.recipe_name}" else resource_recipe = "" end if resource_recipe != @current_recipe && !resource.enclosing_provider unindent if @current_recipe puts_line "Recipe: #{resource_recipe}" @current_recipe = resource_recipe indent end # TODO: info about notifies start_line "* #{resource} action #{action}", :stream => resource indent end def resource_update_progress(resource, current, total, interval) @progress[resource] ||= 0 percent_complete = (current.to_f / total.to_f * 100).to_i if percent_complete > @progress[resource] @progress[resource] = percent_complete if percent_complete % interval == 0 start_line " - Progress: #{percent_complete}%", :green end end end # Called when a resource fails, but will retry. def resource_failed_retriable(resource, action, retry_count, exception) end # Called when a resource fails and will not be retried. def resource_failed(resource, action, exception) super unindent end # Called when a resource action has been skipped b/c of a conditional def resource_skipped(resource, action, conditional) @skipped_resources += 1 # TODO: more info about conditional puts " (skipped due to #{conditional.short_description})", :stream => resource unindent end # Called after #load_current_resource has run. def resource_current_state_loaded(resource, action, current_resource) end # Called when a resource has no converge actions, e.g., it was already correct. def resource_up_to_date(resource, action) @up_to_date_resources += 1 puts " (up to date)", :stream => resource unindent end def resource_bypassed(resource, action, provider) puts " (Skipped: whyrun not supported by provider #{provider.class.name})", :stream => resource unindent end def output_record(line) end # Called when a change has been made to a resource. May be called multiple # times per resource, e.g., a file may have its content updated, and then # its permissions updated. def resource_update_applied(resource, action, update) prefix = Chef::Config[:why_run] ? "Would " : "" Array(update).each do |line| next if line.nil? output_record line if line.kind_of? String start_line "- #{prefix}#{line}", :green elsif line.kind_of? Array # Expanded output - delta # @todo should we have a resource_update_delta callback? line.each do |detail| start_line detail, :white end end end end # Called after a resource has been completely converged. def resource_updated(resource, action) @updated_resources += 1 unindent puts "\n" end # Called when resource current state load is skipped due to the provider # not supporting whyrun mode. def resource_current_state_load_bypassed(resource, action, current_resource) puts_line("* Whyrun not supported for #{resource}, bypassing load.", :yellow) end def stream_output(stream, output, options = {}) print(output, { :stream => stream }.merge(options)) end # Called before handlers run def handlers_start(handler_count) puts "" puts "Running handlers:" indent end # Called after an individual handler has run def handler_executed(handler) puts_line "- #{handler.class.name}" end # Called after all handlers have executed def handlers_completed unindent puts_line "Running handlers complete\n" end # Called when a provider makes an assumption after a failed assertion # in whyrun mode, in order to allow execution to continue def whyrun_assumption(action, resource, message) return unless message [ message ].flatten.each do |line| start_line("* #{line}", :yellow) end end # Called when an assertion declared by a provider fails def provider_requirement_failed(action, resource, exception, message) return unless message color = Chef::Config[:why_run] ? :yellow : :red [ message ].flatten.each do |line| start_line("* #{line}", color) end end def deprecation(message, location = caller(2..2)[0]) if Chef::Config[:treat_deprecation_warnings_as_errors] super end # Save deprecations to the screen until the end deprecations[message] ||= Set.new deprecations[message] << location end def indent indent_by(2) end def unindent indent_by(-2) end protected def deprecations @deprecations ||= {} end end end end chef-12.14.60/lib/chef/formatters/error_description.rb000066400000000000000000000032371276456504500226170ustar00rootroot00000000000000# # Author:: Tyler Cloke () # # 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 module Formatters # == Formatters::ErrorDescription # Class for displaying errors on STDOUT. class ErrorDescription attr_reader :sections def initialize(title) @title = title @sections = [] end def section(heading, text) @sections << { heading => (text || "") } end def display(out) out.puts "=" * 80 out.puts @title, :red out.puts "=" * 80 out.puts "\n" sections.each do |section| section.each do |heading, text| display_section(heading, text, out) end end display_section("Platform:", RUBY_PLATFORM, out) end def for_json() { "title" => @title, "sections" => @sections, } end private def display_section(heading, text, out) out.puts heading out.puts "-" * heading.size out.puts text out.puts "\n" end end end end chef-12.14.60/lib/chef/formatters/error_inspectors.rb000066400000000000000000000014121276456504500224560ustar00rootroot00000000000000require "chef/formatters/error_inspectors/node_load_error_inspector" require "chef/formatters/error_inspectors/registration_error_inspector" require "chef/formatters/error_inspectors/compile_error_inspector" require "chef/formatters/error_inspectors/resource_failure_inspector" require "chef/formatters/error_inspectors/run_list_expansion_error_inspector" require "chef/formatters/error_inspectors/cookbook_resolve_error_inspector" require "chef/formatters/error_inspectors/cookbook_sync_error_inspector" class Chef module Formatters # == ErrorInspectors # Error inspectors wrap exceptions and contextual information. They # generate diagnostic messages about possible causes of the error for user # consumption. module ErrorInspectors end end end chef-12.14.60/lib/chef/formatters/error_inspectors/000077500000000000000000000000001276456504500221335ustar00rootroot00000000000000chef-12.14.60/lib/chef/formatters/error_inspectors/api_error_formatting.rb000066400000000000000000000150551276456504500267020ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # 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 "chef/http/authenticator" class Chef module Formatters module APIErrorFormatting NETWORK_ERROR_CLASSES = [Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError] def describe_network_errors(error_description) error_description.section("Networking Error:", <<-E) #{exception.message} Your chef_server_url may be misconfigured, or the network could be down. E error_description.section("Relevant Config Settings:", <<-E) chef_server_url "#{server_url}" E end def describe_eof_error(error_description) error_description.section("Authentication Error:", <<-E) Received an EOF on transport socket. This almost always indicates a network error external to chef-client. Some causes include: - Blocking ICMP Dest Unreachable (breaking Path MTU Discovery) - IPsec or VPN tunnelling / TCP Encapsulation MTU issues - Jumbo frames configured only on one side (breaking Path MTU) - Jumbo frames configured on a LAN that does not support them - Proxies or Load Balancers breaking large POSTs - Broken TCP offload in network drivers/hardware Try sending large pings to the destination: windows: ping server.example.com -f -l 9999 unix: ping server.example.com -s 9999 Try sending large POSTs to the destination (any HTTP code returned is success): e.g.: curl http://server.example.com/`printf '%*s' 9999 '' | tr ' ' 'a'` Try disabling TCP Offload Engines (TOE) in your ethernet drivers. windows: Disable-NetAdapterChecksumOffload * -TcpIPv4 -UdpIPv4 -IpIPv4 -NoRestart Disable-NetAdapterLso * -IPv4 -NoRestart Set-NetAdapterAdvancedProperty * -DisplayName "Large Receive Offload (IPv4)" -DisplayValue Disabled –NoRestart Restart-NetAdapter * unix(bash): for i in rx tx sg tso ufo gso gro lro rxvlan txvlan rxhash; do /sbin/ethtool -K eth0 $i off; done In some cases the underlying virtualization layer (Xen, VMware, KVM, Hyper-V, etc) may have broken virtual networking code. E end def describe_401_error(error_description) if clock_skew? error_description.section("Authentication Error:", <<-E) Failed to authenticate to the chef server (http 401). The request failed because your clock has drifted by more than 15 minutes. Syncing your clock to an NTP Time source should resolve the issue. E else error_description.section("Authentication Error:", <<-E) Failed to authenticate to the chef server (http 401). E error_description.section("Server Response:", format_rest_error) error_description.section("Relevant Config Settings:", <<-E) chef_server_url "#{server_url}" node_name "#{username}" client_key "#{api_key}" If these settings are correct, your client_key may be invalid, or you may have a chef user with the same client name as this node. E end end def describe_400_error(error_description) error_description.section("Invalid Request Data:", <<-E) The data in your request was invalid (HTTP 400). E error_description.section("Server Response:", format_rest_error) end def describe_406_error(error_description, response) if response["x-ops-server-api-version"] version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"]) client_api_version = version_header["request_version"] min_server_version = version_header["min_version"] max_server_version = version_header["max_version"] error_description.section("Incompatible server API version:", <<-E) This version of the API that this Chef request specified is not supported by the Chef server you sent this request to. The server supports a min API version of #{min_server_version} and a max API version of #{max_server_version}. Chef just made a request with an API version of #{client_api_version}. Please either update your Chef client or server to be a compatible set. E else describe_http_error(error_description) end end def describe_500_error(error_description) error_description.section("Unknown Server Error:", <<-E) The server had a fatal error attempting to load the node data. E error_description.section("Server Response:", format_rest_error) end def describe_503_error(error_description) error_description.section("Server Unavailable", "The Chef Server is temporarily unavailable") error_description.section("Server Response:", format_rest_error) end # Fallback for unexpected/uncommon http errors def describe_http_error(error_description) error_description.section("Unexpected API Request Failure:", format_rest_error) end # Parses JSON from the error response sent by Chef Server and returns the # error message def format_rest_error Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join("; ") rescue Exception safe_format_rest_error end def username config[:node_name] end def api_key config[:client_key] end def server_url config[:chef_server_url] end def clock_skew? exception.response.body =~ /synchronize the clock/i end def safe_format_rest_error # When we get 504 from the server, sometimes the response body is non-readable. # # Stack trace: # # NoMethodError: undefined method `closed?' for nil:NilClass # .../lib/ruby/1.9.1/net/http.rb:2789:in `stream_check' # .../lib/ruby/1.9.1/net/http.rb:2709:in `read_body' # .../lib/ruby/1.9.1/net/http.rb:2736:in `body' # .../lib/chef/formatters/error_inspectors/api_error_formatting.rb:91:in `rescue in format_rest_error' begin exception.response.body rescue Exception "Cannot fetch the contents of the response." end end end end end chef-12.14.60/lib/chef/formatters/error_inspectors/compile_error_inspector.rb000066400000000000000000000125001276456504500274050ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # 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 module Formatters module ErrorInspectors # == CompileErrorInspector # Wraps exceptions that occur during the compile phase of a Chef run and # tries to find the code responsible for the error. class CompileErrorInspector attr_reader :path attr_reader :exception def initialize(path, exception) @path, @exception = path, exception @backtrace_lines_in_cookbooks = nil @file_lines = nil @culprit_backtrace_entry = nil @culprit_line = nil end def add_explanation(error_description) error_description.section(exception.class.name, exception.message) if found_error_in_cookbooks? traceback = filtered_bt.map { |line| " #{line}" }.join("\n") error_description.section("Cookbook Trace:", traceback) error_description.section("Relevant File Content:", context) end if exception_message_modifying_frozen? msg = <<-MESSAGE Ruby objects are often frozen to prevent further modifications when they would negatively impact the process (e.g. values inside Ruby's ENV class) or to prevent polluting other objects when default values are passed by reference to many instances of an object (e.g. the empty Array as a Chef resource default, passed by reference to every instance of the resource). Chef uses Object#freeze to ensure the default values of properties inside Chef resources are not modified, so that when a new instance of a Chef resource is created, and Object#dup copies values by reference, the new resource is not receiving a default value that has been by a previous instance of that resource. Instead of modifying an object that contains a default value for all instances of a Chef resource, create a new object and assign it to the resource's parameter, e.g.: fruit_basket = resource(:fruit_basket, 'default') # BAD: modifies 'contents' object for all new fruit_basket instances fruit_basket.contents << 'apple' # GOOD: allocates new array only owned by this fruit_basket instance fruit_basket.contents %w(apple) MESSAGE error_description.section("Additional information:", msg.gsub(/^ {6}/, "")) end end def context context_lines = [] context_lines << "#{culprit_file}:\n\n" Range.new(display_lower_bound, display_upper_bound).each do |i| line_nr = (i + 1).to_s.rjust(3) indicator = (i + 1) == culprit_line ? ">> " : ": " context_lines << "#{line_nr}#{indicator}#{file_lines[i]}" end context_lines.join("") end def display_lower_bound lower = (culprit_line - 8) lower = 0 if lower < 0 lower end def display_upper_bound upper = (culprit_line + 8) upper = file_lines.size if upper > file_lines.size upper end def file_lines @file_lines ||= IO.readlines(culprit_file) end def culprit_backtrace_entry @culprit_backtrace_entry ||= begin bt_entry = filtered_bt.first Chef::Log.debug("Backtrace entry for compile error: '#{bt_entry}'") bt_entry end end def culprit_line @culprit_line ||= begin line_number = culprit_backtrace_entry[/^(?:.\:)?[^:]+:([\d]+)/, 1].to_i Chef::Log.debug("Line number of compile error: '#{line_number}'") line_number end end def culprit_file @culprit_file ||= culprit_backtrace_entry[/^((?:.\:)?[^:]+):([\d]+)/, 1] end def filtered_bt backtrace_lines_in_cookbooks.count > 0 ? backtrace_lines_in_cookbooks : exception.backtrace end def found_error_in_cookbooks? !backtrace_lines_in_cookbooks.empty? end def backtrace_lines_in_cookbooks @backtrace_lines_in_cookbooks ||= begin filters = Array(Chef::Config.cookbook_path).map { |p| /^#{Regexp.escape(p)}/i } r = exception.backtrace.select { |line| filters.any? { |filter| line =~ filter } } Chef::Log.debug("Filtered backtrace of compile error: #{r.join(",")}") r end end def exception_message_modifying_frozen? exception.message.include?("can't modify frozen") end end end end end chef-12.14.60/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb000066400000000000000000000151071276456504500313300ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # 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 "chef/formatters/error_inspectors/api_error_formatting" class Chef module Formatters module ErrorInspectors class CookbookResolveErrorInspector attr_reader :exception attr_reader :expanded_run_list include APIErrorFormatting def initialize(expanded_run_list, exception) @expanded_run_list = expanded_run_list @exception = exception end def add_explanation(error_description) case exception when Net::HTTPServerException, Net::HTTPFatalError humanize_http_exception(error_description) when EOFError describe_eof_error(error_description) when *NETWORK_ERROR_CLASSES describe_network_errors(error_description) else error_description.section("Unexpected Error:", "#{exception.class.name}: #{exception.message}") end end def humanize_http_exception(error_description) response = exception.response case response when Net::HTTPUnauthorized # TODO: this is where you'd see conflicts b/c of username/clientname stuff describe_401_error(error_description) when Net::HTTPForbidden # TODO: we're rescuing errors from Node.find_or_create # * could be no write on nodes container # * could be no read on the node error_description.section("Authorization Error", <<-E) This client is not authorized to read some of the information required to access its cookbooks (HTTP 403). To access its cookbooks, a client needs to be able to read its environment and all of the cookbooks in its expanded run list. E error_description.section("Expanded Run List:", expanded_run_list_ul) error_description.section("Server Response:", format_rest_error) when Net::HTTPPreconditionFailed describe_412_error(error_description) when Net::HTTPBadRequest describe_400_error(error_description) when Net::HTTPNotFound when Net::HTTPInternalServerError describe_500_error(error_description) when Net::HTTPBadGateway, Net::HTTPServiceUnavailable describe_503_error(error_description) when Net::HTTPNotAcceptable describe_406_error(error_description, response) else describe_http_error(error_description) end end def describe_412_error(error_description) explanation = "" error_reasons = extract_412_error_message # Prepare the error message if there is detailed information # about individual cookbooks. if !error_reasons.respond_to?(:key?) explanation << error_reasons.to_s else if error_reasons.key?("non_existent_cookbooks") && !Array(error_reasons["non_existent_cookbooks"]).empty? explanation << "The following cookbooks are required by the client but don't exist on the server:\n" Array(error_reasons["non_existent_cookbooks"]).each do |cookbook| explanation << "* #{cookbook}\n" end explanation << "\n" end if error_reasons.key?("cookbooks_with_no_versions") && !Array(error_reasons["cookbooks_with_no_versions"]).empty? explanation << "The following cookbooks exist on the server, but there is no version that meets\nthe version constraints in this environment:\n" Array(error_reasons["cookbooks_with_no_versions"]).each do |cookbook| explanation << "* #{cookbook}\n" end explanation << "\n" end end if !explanation.empty? error_description.section("Missing Cookbooks:", explanation) else # If we don't have any cookbook details print a more # generic error message. if error_reasons.respond_to?(:key?) && error_reasons["message"] explanation << "Error message: #{error_reasons["message"]}\n" end explanation << < ["nope"], # "cookbooks_with_no_versions" => [], # "message" => "Run list contains invalid items: no such cookbook nope."} def extract_412_error_message # Example: # "{\"error\":[\"{\\\"non_existent_cookbooks\\\":[\\\"nope\\\"],\\\"cookbooks_with_no_versions\\\":[],\\\"message\\\":\\\"Run list contains invalid items: no such cookbook nope.\\\"}\"]}" wrapped_error_message = attempt_json_parse(exception.response.body) unless wrapped_error_message.kind_of?(Hash) && wrapped_error_message.key?("error") return wrapped_error_message.to_s end error_description = Array(wrapped_error_message["error"]).first if error_description.kind_of?(Hash) return error_description end attempt_json_parse(error_description) end private def attempt_json_parse(maybe_json_string) Chef::JSONCompat.from_json(maybe_json_string) rescue Exception maybe_json_string end end end end end chef-12.14.60/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb000066400000000000000000000053011276456504500306200ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # 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 "chef/formatters/error_inspectors/api_error_formatting" class Chef module Formatters module ErrorInspectors # == CookbookSyncErrorInspector # Generates human-friendly explanations for errors encountered during # cookbook sync. #-- # TODO: Not sure what errors are commonly seen during cookbook sync, so # the messaging is kinda generic. class CookbookSyncErrorInspector include APIErrorFormatting attr_reader :exception attr_reader :cookbooks def initialize(cookbooks, exception) @cookbooks, @exception = cookbooks, exception end def add_explanation(error_description) case exception when Net::HTTPServerException, Net::HTTPFatalError humanize_http_exception(error_description) when EOFError describe_eof_error(error_description) when *NETWORK_ERROR_CLASSES describe_network_errors(error_description) else error_description.section("Unexpected Error:", "#{exception.class.name}: #{exception.message}") end end def config Chef::Config end def humanize_http_exception(error_description) response = exception.response case response when Net::HTTPUnauthorized # TODO: this is where you'd see conflicts b/c of username/clientname stuff describe_401_error(error_description) when Net::HTTPBadRequest describe_400_error(error_description) when Net::HTTPNotFound when Net::HTTPInternalServerError describe_500_error(error_description) when Net::HTTPBadGateway, Net::HTTPServiceUnavailable, Net::HTTPGatewayTimeOut describe_503_error(error_description) when Net::HTTPNotAcceptable describe_406_error(error_description, response) else describe_http_error(error_description) end end end end end end chef-12.14.60/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb000066400000000000000000000104411276456504500277030ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # 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 "chef/formatters/error_inspectors/api_error_formatting" class Chef module Formatters module ErrorInspectors # == APIErrorInspector # Wraps exceptions caused by API calls to the server. class NodeLoadErrorInspector include APIErrorFormatting attr_reader :exception attr_reader :node_name attr_reader :config def initialize(node_name, exception, config) @node_name = node_name @exception = exception @config = config end def add_explanation(error_description) case exception when Net::HTTPServerException, Net::HTTPFatalError humanize_http_exception(error_description) when Chef::Exceptions::PrivateKeyMissing error_description.section("Private Key Not Found:", <<-E) Your private key could not be loaded. If the key file exists, ensure that it is readable by chef-client. E error_description.section("Relevant Config Settings:", <<-E) client_key "#{api_key}" E when EOFError describe_eof_error(error_description) when *NETWORK_ERROR_CLASSES describe_network_errors(error_description) else error_description.section("Unexpected Error:", "#{exception.class.name}: #{exception.message}") end end def humanize_http_exception(error_description) response = exception.response case response when Net::HTTPUnauthorized # TODO: this is where you'd see conflicts b/c of username/clientname stuff describe_401_error(error_description) when Net::HTTPForbidden # TODO: we're rescuing errors from Node.find_or_create # * could be no write on nodes container # * could be no read on the node error_description.section("Authorization Error", <<-E) Your client is not authorized to load the node data (HTTP 403). E error_description.section("Server Response:", format_rest_error) error_description.section("Possible Causes:", <<-E) * Your client (#{username}) may have misconfigured authorization permissions. E when Net::HTTPBadRequest describe_400_error(error_description) when Net::HTTPNotFound describe_404_error(error_description) when Net::HTTPInternalServerError describe_500_error(error_description) when Net::HTTPBadGateway, Net::HTTPServiceUnavailable describe_503_error(error_description) when Net::HTTPNotAcceptable describe_406_error(error_description, response) else describe_http_error(error_description) end end # Custom 404 error messaging. Users sometimes see 404s when they have # misconfigured server URLs, and the wrong one redirects to the new # one, e.g., PUT http://wrong.url/nodes/node-name becomes a GET after a # redirect. def describe_404_error(error_description) error_description.section("Resource Not Found:", <<-E) The server returned a HTTP 404. This usually indicates that your chef_server_url is incorrect. E error_description.section("Relevant Config Settings:", <<-E) chef_server_url "#{server_url}" E end def username config[:node_name] end def api_key config[:client_key] end def server_url config[:chef_server_url] end def clock_skew? exception.response.body =~ /synchronize the clock/i end end end end end chef-12.14.60/lib/chef/formatters/error_inspectors/registration_error_inspector.rb000066400000000000000000000124161276456504500304750ustar00rootroot00000000000000class Chef module Formatters module ErrorInspectors # == RegistrationErrorInspector # Wraps exceptions that occur during the client registration process and # suggests possible causes. #-- # TODO: Lots of duplication with the node_load_error_inspector, just # slightly tweaked to talk about validation keys instead of other keys. class RegistrationErrorInspector include APIErrorFormatting attr_reader :exception attr_reader :node_name attr_reader :config def initialize(node_name, exception, config) @node_name = node_name @exception = exception @config = config end def add_explanation(error_description) case exception when Net::HTTPServerException, Net::HTTPFatalError humanize_http_exception(error_description) when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError error_description.section("Network Error:", <<-E) There was a network error connecting to the Chef Server: #{exception.message} E error_description.section("Relevant Config Settings:", <<-E) chef_server_url "#{server_url}" If your chef_server_url is correct, your network could be down. E when Chef::Exceptions::PrivateKeyMissing error_description.section("Private Key Not Found:", <<-E) Your private key could not be loaded. If the key file exists, ensure that it is readable by chef-client. E error_description.section("Relevant Config Settings:", <<-E) validation_key "#{api_key}" E when Chef::Exceptions::InvalidRedirect error_description.section("Invalid Redirect:", <<-E) Change your server location in client.rb to the server's FQDN to avoid unwanted redirections. E when EOFError describe_eof_error(error_description) else "#{exception.class.name}: #{exception.message}" end end def humanize_http_exception(error_description) response = exception.response case response when Net::HTTPUnauthorized if clock_skew? error_description.section("Authentication Error:", <<-E) Failed to authenticate to the chef server (http 401). The request failed because your clock has drifted by more than 15 minutes. Syncing your clock to an NTP Time source should resolve the issue. E else error_description.section("Authentication Error:", <<-E) Failed to authenticate to the chef server (http 401). E error_description.section("Server Response:", format_rest_error) error_description.section("Relevant Config Settings:", <<-E) chef_server_url "#{server_url}" validation_client_name "#{username}" validation_key "#{api_key}" If these settings are correct, your validation_key may be invalid. E end when Net::HTTPForbidden error_description.section("Authorization Error:", <<-E) Your validation client is not authorized to create the client for this node (HTTP 403). E error_description.section("Possible Causes:", <<-E) * There may already be a client named "#{config[:node_name]}" * Your validation client (#{username}) may have misconfigured authorization permissions. E when Net::HTTPBadRequest error_description.section("Invalid Request Data:", <<-E) The data in your request was invalid (HTTP 400). E error_description.section("Server Response:", format_rest_error) when Net::HTTPNotFound error_description.section("Resource Not Found:", <<-E) The server returned a HTTP 404. This usually indicates that your chef_server_url is incorrect. E error_description.section("Relevant Config Settings:", <<-E) chef_server_url "#{server_url}" E when Net::HTTPNotAcceptable describe_406_error(error_description, response) when Net::HTTPInternalServerError error_description.section("Unknown Server Error:", <<-E) The server had a fatal error attempting to load the node data. E error_description.section("Server Response:", format_rest_error) when Net::HTTPBadGateway, Net::HTTPServiceUnavailable error_description.section("Server Unavailable", "The Chef Server is temporarily unavailable") error_description.section("Server Response:", format_rest_error) else error_description.section("Unexpected API Request Failure:", format_rest_error) end end def username #config[:node_name] config[:validation_client_name] end def api_key config[:validation_key] #config[:client_key] end def server_url config[:chef_server_url] end def clock_skew? exception.response.body =~ /synchronize the clock/i end # Parses JSON from the error response sent by Chef Server and returns the # error message #-- # TODO: this code belongs in Chef::REST def format_rest_error Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join("; ") rescue Exception exception.response.body end end end end end chef-12.14.60/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb000066400000000000000000000102001276456504500300750ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Author:: Tyler Cloke () # 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 module Formatters module ErrorInspectors class ResourceFailureInspector attr_reader :resource attr_reader :action attr_reader :exception def initialize(resource, action, exception) @resource = resource @action = action @exception = exception end def add_explanation(error_description) error_description.section(exception.class.name, exception.message) unless filtered_bt.empty? error_description.section("Cookbook Trace:", filtered_bt.join("\n")) end unless dynamic_resource? error_description.section("Resource Declaration:", resource.sensitive ? "suppressed sensitive resource output" : recipe_snippet) end error_description.section("Compiled Resource:", "#{resource.to_text}") # Template errors get wrapped in an exception class that can show the relevant template code, # so add them to the error output. if exception.respond_to?(:source_listing) error_description.section("Template Context:", "#{exception.source_location}\n#{exception.source_listing}") end if Chef::Platform.windows? require "chef/win32/security" if !Chef::ReservedNames::Win32::Security.has_admin_privileges? error_description.section("Missing Windows Admin Privileges", "chef-client doesn't have administrator privileges. This can be a possible reason for the resource failure.") end end end def recipe_snippet return nil if dynamic_resource? @snippet ||= begin if (file = parse_source) && (line = parse_line(file)) return nil unless ::File.exists?(file) lines = IO.readlines(file) relevant_lines = ["# In #{file}\n\n"] current_line = line - 1 current_line = 0 if current_line < 0 nesting = 0 loop do # low rent parser. try to gracefully handle nested blocks in resources nesting += 1 if lines[current_line] =~ /[\s]+do[\s]*/ nesting -= 1 if lines[current_line] =~ /end[\s]*$/ relevant_lines << format_line(current_line, lines[current_line]) break if lines[current_line + 1].nil? break if current_line >= (line + 50) break if nesting <= 0 current_line += 1 end relevant_lines << format_line(current_line + 1, lines[current_line + 1]) if lines[current_line + 1] relevant_lines.join("") end end end def dynamic_resource? !resource.source_line end def filtered_bt filters = Array(Chef::Config.cookbook_path).map { |p| /^#{Regexp.escape(p)}/ } exception.backtrace.select { |line| filters.any? { |filter| line =~ filter } } end private def format_line(line_nr, line) # Print line number as 1-indexed not zero line_nr_string = (line_nr + 1).to_s.rjust(3) + ": " line_nr_string + line end def parse_source resource.source_line[/^(([\w]:)?[^:]+):([\d]+)/, 1] end def parse_line(source) resource.source_line[/^#{Regexp.escape(source)}:([\d]+)/, 1].to_i end end end end end chef-12.14.60/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb000066400000000000000000000105001276456504500316760ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Author:: Tyler Cloke () # 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 "chef/formatters/error_inspectors/api_error_formatting" class Chef module Formatters module ErrorInspectors class RunListExpansionErrorInspector include APIErrorFormatting attr_reader :exception attr_reader :node def initialize(node, exception) @node, @exception = node, exception end def add_explanation(error_description) case exception when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError error_description.section("Networking Error:", <<-E) #{exception.message} Your chef_server_url may be misconfigured, or the network could be down. E when Net::HTTPServerException, Net::HTTPFatalError humanize_http_exception(error_description) when Chef::Exceptions::MissingRole describe_missing_role(error_description) when EOFError describe_eof_error(error_description) else error_description.section("Unexpected Error:", "#{exception.class.name}: #{exception.message}") end end def describe_missing_role(error_description) error_description.section("Missing Role(s) in Run List:", missing_roles_explained) original_run_list = node.run_list.map { |item| "* #{item}" }.join("\n") error_description.section("Original Run List", original_run_list) end def missing_roles_explained run_list_expansion.missing_roles_with_including_role.map do |role, includer| "* #{role} included by '#{includer}'" end.join("\n") end def run_list_expansion exception.expansion end def config Chef::Config end def humanize_http_exception(error_description) response = exception.response case response when Net::HTTPUnauthorized error_description.section("Authentication Error:", <<-E) Failed to authenticate to the chef server (http 401). E error_description.section("Server Response:", format_rest_error) error_description.section("Relevant Config Settings:", <<-E) chef_server_url "#{server_url}" node_name "#{username}" client_key "#{api_key}" If these settings are correct, your client_key may be invalid. E when Net::HTTPForbidden # TODO: we're rescuing errors from Node.find_or_create # * could be no write on nodes container # * could be no read on the node error_description.section("Authorization Error", <<-E) Your client is not authorized to load one or more of your roles (HTTP 403). E error_description.section("Server Response:", format_rest_error) error_description.section("Possible Causes:", <<-E) * Your client (#{username}) may have misconfigured authorization permissions. E when Net::HTTPNotAcceptable describe_406_error(error_description, response) when Net::HTTPInternalServerError error_description.section("Unknown Server Error:", <<-E) The server had a fatal error attempting to load a role. E error_description.section("Server Response:", format_rest_error) when Net::HTTPBadGateway, Net::HTTPServiceUnavailable error_description.section("Server Unavailable", "The Chef Server is temporarily unavailable") error_description.section("Server Response:", format_rest_error) else error_description.section("Unexpected API Request Failure:", format_rest_error) end end end end end end chef-12.14.60/lib/chef/formatters/error_mapper.rb000066400000000000000000000070601276456504500215560ustar00rootroot00000000000000#-- # Author:: Tyler Cloke () # 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 module Formatters # == Formatters::ErrorMapper # Collection of methods for creating and returning # Formatters::ErrorDescription objects based on node, # exception, and configuration information. module ErrorMapper # Failed to register this client with the server. def self.registration_failed(node_name, exception, config) error_inspector = ErrorInspectors::RegistrationErrorInspector.new(node_name, exception, config) headline = "Chef encountered an error attempting to create the client \"#{node_name}\"" description = ErrorDescription.new(headline) error_inspector.add_explanation(description) return description end def self.node_load_failed(node_name, exception, config) error_inspector = ErrorInspectors::NodeLoadErrorInspector.new(node_name, exception, config) headline = "Chef encountered an error attempting to load the node data for \"#{node_name}\"" description = ErrorDescription.new(headline) error_inspector.add_explanation(description) return description end def self.run_list_expand_failed(node, exception) error_inspector = ErrorInspectors::RunListExpansionErrorInspector.new(node, exception) headline = "Error expanding the run_list:" description = ErrorDescription.new(headline) error_inspector.add_explanation(description) return description end def self.cookbook_resolution_failed(expanded_run_list, exception) error_inspector = ErrorInspectors::CookbookResolveErrorInspector.new(expanded_run_list, exception) headline = "Error Resolving Cookbooks for Run List:" description = ErrorDescription.new(headline) error_inspector.add_explanation(description) return description end def self.cookbook_sync_failed(cookbooks, exception) error_inspector = ErrorInspectors::CookbookSyncErrorInspector.new(cookbooks, exception) headline = "Error Syncing Cookbooks:" description = ErrorDescription.new(headline) error_inspector.add_explanation(description) return description end def self.resource_failed(resource, action, exception) error_inspector = ErrorInspectors::ResourceFailureInspector.new(resource, action, exception) headline = "Error executing action `#{action}` on resource '#{resource}'" description = ErrorDescription.new(headline) error_inspector.add_explanation(description) return description end def self.file_load_failed(path, exception) error_inspector = ErrorInspectors::CompileErrorInspector.new(path, exception) headline = "Recipe Compile Error" + ( path ? " in #{path}" : "" ) description = ErrorDescription.new(headline) error_inspector.add_explanation(description) description end end end end chef-12.14.60/lib/chef/formatters/indentable_output_stream.rb000066400000000000000000000115411276456504500241600ustar00rootroot00000000000000class Chef module Formatters # Handles basic indentation and colorization tasks class IndentableOutputStream attr_reader :out attr_reader :err attr_accessor :indent attr_reader :line_started attr_accessor :current_stream attr_reader :semaphore def initialize(out, err) @out, @err = out, err @indent = 0 @line_started = false @semaphore = Mutex.new end def highline @highline ||= begin require "highline" HighLine.new end end # Print text. This will start a new line and indent if necessary # but will not terminate the line (future print and puts statements # will start off where this print left off). def color(string, *args) print(string, from_args(args)) end # Print the start of a new line. This will terminate any existing lines and # cause indentation but will not move to the next line yet (future 'print' # and 'puts' statements will stay on this line). def start_line(string, *args) print(string, from_args(args, :start_line => true)) end # Print a line. This will continue from the last start_line or print, # or start a new line and indent if necessary. def puts(string, *args) print(string, from_args(args, :end_line => true)) end # Print an entire line from start to end. This will terminate any existing # lines and cause indentation. def puts_line(string, *args) print(string, from_args(args, :start_line => true, :end_line => true)) end # Print a raw chunk def <<(obj) print(obj) end # Print a string. # # == Arguments # string: string to print. # options: a hash with these possible options: # - :stream => OBJ: unique identifier for a stream. If two prints have # different streams, they will print on separate lines. # Otherwise, they will stay together. # - :start_line => BOOLEAN: if true, print will begin on a blank (indented) line. # - :end_line => BOOLEAN: if true, current line will be ended. # - :name => STRING: a name to prefix in front of a stream. It will be printed # once (with the first line of the stream) and subsequent lines # will be indented to match. # # == Alternative # # You may also call print('string', :red) (a list of colors a la Highline.color) def print(string, *args) options = from_args(args) # Make sure each line stays a unit even with threads sending output semaphore.synchronize do if should_start_line?(options) move_to_next_line end print_string(string, options) if should_end_line?(options) move_to_next_line end end end private def should_start_line?(options) options[:start_line] || @current_stream != options[:stream] end def should_end_line?(options) options[:end_line] && @line_started end def from_args(colors, merge_options = {}) if colors.size == 1 && colors[0].kind_of?(Hash) merge_options.merge(colors[0]) else merge_options.merge({ :colors => colors }) end end def print_string(string, options) if string.empty? if options[:end_line] print_line("", options) end else string.lines.each do |line| print_line(line, options) end end end def print_line(line, options) indent_line(options) # Note that the next line will need to be started if line[-1..-1] == "\n" @line_started = false end if Chef::Config[:color] && options[:colors] @out.print highline.color(line, *options[:colors]) else @out.print line end end def move_to_next_line if @line_started @out.puts "" @line_started = false end end def indent_line(options) if !@line_started # Print indents. If there is a stream name, either print it (if we're # switching streams) or print enough blanks to match # the indents. if options[:name] if @current_stream != options[:stream] @out.print "#{(' ' * indent)}[#{options[:name]}] " else @out.print " " * (indent + 3 + options[:name].size) end else # Otherwise, just print indents. @out.print " " * indent end if @current_stream != options[:stream] @current_stream = options[:stream] end @line_started = true end end end end end chef-12.14.60/lib/chef/formatters/minimal.rb000066400000000000000000000157701276456504500205160ustar00rootroot00000000000000require "chef/formatters/base" class Chef module Formatters # == Formatters::Minimal # Shows the progress of the chef run by printing single characters, and # displays a summary of updates at the conclusion of the run. For events # that don't have meaningful status information (loading a file, syncing a # cookbook) a dot is printed. For resources, a dot, 'S' or 'U' is printed # if the resource is up to date, skipped by not_if/only_if, or updated, # respectively. class Minimal < Formatters::Base cli_name(:minimal) cli_name(:min) attr_reader :updated_resources attr_reader :updates_by_resource def initialize(out, err) super @updated_resources = [] @updates_by_resource = Hash.new { |h, k| h[k] = [] } end # Called at the very start of a Chef Run def run_start(version) puts_line "Starting Chef Client, version #{version}" puts_line "OpenSSL FIPS 140 mode enabled" if Chef::Config[:fips] end # Called at the end of the Chef run. def run_completed(node) puts "chef client finished, #{@updated_resources.size} resources updated" end # called at the end of a failed run def run_failed(exception) puts "chef client failed. #{@updated_resources.size} resources updated" end # Called right after ohai runs. def ohai_completed(node) end # Already have a client key, assuming this node has registered. def skipping_registration(node_name, config) end # About to attempt to register as +node_name+ def registration_start(node_name, config) end def registration_completed end # Failed to register this client with the server. def registration_failed(node_name, exception, config) super end def node_load_start(node_name, config) end # Failed to load node data from the server def node_load_failed(node_name, exception, config) end # Default and override attrs from roles have been computed, but not yet applied. # Normal attrs from JSON have been added to the node. def node_load_completed(node, expanded_run_list, config) end # Called before the cookbook collection is fetched from the server. def cookbook_resolution_start(expanded_run_list) puts "resolving cookbooks for run list: #{expanded_run_list.inspect}" end # Called when there is an error getting the cookbook collection from the # server. def cookbook_resolution_failed(expanded_run_list, exception) end # Called when the cookbook collection is returned from the server. def cookbook_resolution_complete(cookbook_collection) end # Called before unneeded cookbooks are removed #-- # TODO: Should be called in CookbookVersion.sync_cookbooks def cookbook_clean_start end # Called after the file at +path+ is removed. It may be removed if the # cookbook containing it was removed from the run list, or if the file was # removed from the cookbook. def removed_cookbook_file(path) end # Called when cookbook cleaning is finished. def cookbook_clean_complete end # Called before cookbook sync starts def cookbook_sync_start(cookbook_count) puts "Synchronizing cookbooks" end # Called when cookbook +cookbook+ has been sync'd def synchronized_cookbook(cookbook_name, cookbook) print "." end # Called when an individual file in a cookbook has been updated def updated_cookbook_file(cookbook_name, path) end # Called after all cookbooks have been sync'd. def cookbook_sync_complete puts "done." end # Called when cookbook loading starts. def library_load_start(file_count) puts "Compiling cookbooks" end # Called after a file in a cookbook is loaded. def file_loaded(path) print "." end def file_load_failed(path, exception) super end # Called when recipes have been loaded. def recipe_load_complete puts "done." end # Called before convergence starts def converge_start(run_context) puts "Converging #{run_context.resource_collection.all_resources.size} resources" end # Called when the converge phase is finished. def converge_complete puts "\n" puts "System converged." if updated_resources.empty? puts "no resources updated" else puts "\n" puts "resources updated this run:" updated_resources.each do |resource| puts "* #{resource}" updates_by_resource[resource.name].flatten.each do |update| puts " - #{update}" end puts "\n" end end end # Called before action is executed on a resource. def resource_action_start(resource, action, notification_type = nil, notifier = nil) end # Called when a resource fails, but will retry. def resource_failed_retriable(resource, action, retry_count, exception) end # Called when a resource fails and will not be retried. def resource_failed(resource, action, exception) end # Called when a resource action has been skipped b/c of a conditional def resource_skipped(resource, action, conditional) print "S" end # Called after #load_current_resource has run. def resource_current_state_loaded(resource, action, current_resource) end # Called when a resource has no converge actions, e.g., it was already correct. def resource_up_to_date(resource, action) print "." end ## TODO: callback for assertion failures ## TODO: callback for assertion fallback in why run # Called when a change has been made to a resource. May be called multiple # times per resource, e.g., a file may have its content updated, and then # its permissions updated. def resource_update_applied(resource, action, update) @updates_by_resource[resource.name] << Array(update)[0] end # Called after a resource has been completely converged. def resource_updated(resource, action) updated_resources << resource print "U" end # Called before handlers run def handlers_start(handler_count) end # Called after an individual handler has run def handler_executed(handler) end # Called after all handlers have executed def handlers_completed end # An uncategorized message. This supports the case that a user needs to # pass output that doesn't fit into one of the callbacks above. Note that # there's no semantic information about the content or importance of the # message. That means that if you're using this too often, you should add a # callback for it. def msg(message) end end end end chef-12.14.60/lib/chef/guard_interpreter.rb000066400000000000000000000022171276456504500204170ustar00rootroot00000000000000# # Author:: Steven Danna () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/guard_interpreter/default_guard_interpreter" require "chef/guard_interpreter/resource_guard_interpreter" class Chef class GuardInterpreter def self.for_resource(resource, command, command_opts) if resource.guard_interpreter == :default Chef::GuardInterpreter::DefaultGuardInterpreter.new(command, command_opts) else Chef::GuardInterpreter::ResourceGuardInterpreter.new(resource, command, command_opts) end end end end chef-12.14.60/lib/chef/guard_interpreter/000077500000000000000000000000001276456504500200705ustar00rootroot00000000000000chef-12.14.60/lib/chef/guard_interpreter/default_guard_interpreter.rb000066400000000000000000000022411276456504500256450ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/shell_out" class Chef class GuardInterpreter class DefaultGuardInterpreter include Chef::Mixin::ShellOut protected def initialize(command, opts) @command = command @command_opts = opts end public def evaluate shell_out(@command, @command_opts).status.success? rescue Chef::Exceptions::CommandTimeout Chef::Log.warn "Command '#{@command}' timed out" false end end end end chef-12.14.60/lib/chef/guard_interpreter/resource_guard_interpreter.rb000066400000000000000000000125041276456504500260530ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/guard_interpreter" class Chef class GuardInterpreter class ResourceGuardInterpreter < DefaultGuardInterpreter def initialize(parent_resource, command, opts) super(command, opts) @parent_resource = parent_resource @resource = get_interpreter_resource(parent_resource) end def evaluate # Add attributes inherited from the parent class # to the resource merge_inherited_attributes # Only execute and script resources and use guard attributes. # The command to be executed on them are passed via different attributes. # Script resources use code attribute and execute resources use # command attribute. Moreover script resources are also execute # resources. Here we make sure @command is assigned to the right # attribute by checking the type of the resources. # We need to make sure we check for Script first because any resource # that can get to here is an Execute resource. if @resource.is_a? Chef::Resource::Script block_attributes = @command_opts.merge({ :code => @command }) else block_attributes = @command_opts.merge({ :command => @command }) end # Handles cases like powershell_script where default # attributes are different when used in a guard vs. not. For # powershell_script in particular, this will go away when # the one attribue that causes this changes its default to be # the same after some period to prepare for deprecation if @resource.class.respond_to?(:get_default_attributes) block_attributes = @resource.class.send(:get_default_attributes, @command_opts).merge(block_attributes) end resource_block = block_from_attributes(block_attributes) evaluate_action(nil, &resource_block) end protected def evaluate_action(action = nil, &block) @resource.instance_eval(&block) run_action = action || @resource.action begin # Coerce to an array to be safe. This could happen with a legacy # resource or something overriding the default_action code in a # subclass. Array(run_action).each { |action_to_run| @resource.run_action(action_to_run) } resource_updated = @resource.updated rescue Mixlib::ShellOut::ShellCommandFailed resource_updated = nil end resource_updated end def get_interpreter_resource(parent_resource) if parent_resource.nil? || parent_resource.node.nil? raise ArgumentError, "Node for guard resource parent must not be nil" end resource_class = Chef::Resource.resource_for_node(parent_resource.guard_interpreter, parent_resource.node) if resource_class.nil? raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter} unknown for this platform" end if ! resource_class.ancestors.include?(Chef::Resource::Execute) raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Execute resource" end # Duplicate the node below because the new RunContext # overwrites the state of Node instances passed to it. # See https://github.com/chef/chef/issues/3485. empty_events = Chef::EventDispatch::Dispatcher.new anonymous_run_context = Chef::RunContext.new(parent_resource.node.dup, {}, empty_events) interpreter_resource = resource_class.new("Guard resource", anonymous_run_context) interpreter_resource.is_guard_interpreter = true interpreter_resource end def block_from_attributes(attributes) Proc.new do attributes.keys.each do |attribute_name| send(attribute_name, attributes[attribute_name]) if respond_to?(attribute_name) end end end def merge_inherited_attributes inherited_attributes = [] if @parent_resource.class.respond_to?(:guard_inherited_attributes) inherited_attributes = @parent_resource.class.send(:guard_inherited_attributes) end if inherited_attributes && !inherited_attributes.empty? inherited_attributes.each do |attribute| if @parent_resource.respond_to?(attribute) && @resource.respond_to?(attribute) parent_value = @parent_resource.send(attribute) child_value = @resource.send(attribute) if parent_value || child_value @resource.send(attribute, parent_value) end end end end end end end end chef-12.14.60/lib/chef/handler.rb000066400000000000000000000201521276456504500163050ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Copyright:: Copyright 2010-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/client" require "forwardable" class Chef # == Chef::Handler # The base class for an Exception or Notification Handler. Create your own # handler by subclassing Chef::Handler. When a Chef run fails with an # uncaught Exception, Chef will set the +run_status+ on your handler and call # +report+ # # ===Example: # # require 'net/smtp' # # module MyOrg # class OhNoes < Chef::Handler # # def report # # Create the email message # message = "From: Your Name \n" # message << "To: Destination Address \n" # message << "Subject: Chef Run Failure\n" # message << "Date: #{Time.now.rfc2822}\n\n" # # # The Node is available as +node+ # message << "Chef run failed on #{node.name}\n" # # +run_status+ is a value object with all of the run status data # message << "#{run_status.formatted_exception}\n" # # Join the backtrace lines. Coerce to an array just in case. # message << Array(backtrace).join("\n") # # # Send the email # Net::SMTP.start('your.smtp.server', 25) do |smtp| # smtp.send_message message, 'from@address', 'to@address' # end # end # # end # end # class Handler def self.handler_for(*args) if args.include?(:start) Chef::Config[:start_handlers] ||= [] Chef::Config[:start_handlers] |= [self] end if args.include?(:report) Chef::Config[:report_handlers] ||= [] Chef::Config[:report_handlers] |= [self] end if args.include?(:exception) Chef::Config[:exception_handlers] ||= [] Chef::Config[:exception_handlers] |= [self] end end # The list of currently configured start handlers def self.start_handlers Array(Chef::Config[:start_handlers]) end def self.resolve_handler_instance(handler) if handler.is_a?(Class) if handler.respond_to?(:instance) # support retrieving a Singleton reporting object handler.instance else # just a class with no way to insert data handler.new end else # the Chef::Config array contains an instance, not a class handler end end # Run the start handlers. This will usually be called by a notification # from Chef::Client def self.run_start_handlers(run_status) Chef::Log.info("Running start handlers") start_handlers.each do |handler| handler = resolve_handler_instance(handler) handler.run_report_safely(run_status) end Chef::Log.info("Start handlers complete.") end # Wire up a notification to run the start handlers when the chef run # starts. Chef::Client.when_run_starts do |run_status| run_start_handlers(run_status) end # The list of currently configured report handlers def self.report_handlers Array(Chef::Config[:report_handlers]) end # Run the report handlers. This will usually be called by a notification # from Chef::Client def self.run_report_handlers(run_status) events = run_status.events events.handlers_start(report_handlers.size) Chef::Log.info("Running report handlers") report_handlers.each do |handler| handler = resolve_handler_instance(handler) handler.run_report_safely(run_status) events.handler_executed(handler) end events.handlers_completed Chef::Log.info("Report handlers complete") end # Wire up a notification to run the report handlers if the chef run # succeeds. Chef::Client.when_run_completes_successfully do |run_status| run_report_handlers(run_status) end # The list of currently configured exception handlers def self.exception_handlers Array(Chef::Config[:exception_handlers]) end # Run the exception handlers. Usually will be called by a notification # from Chef::Client when the run fails. def self.run_exception_handlers(run_status) events = run_status.events events.handlers_start(exception_handlers.size) Chef::Log.error("Running exception handlers") exception_handlers.each do |handler| handler = resolve_handler_instance(handler) handler.run_report_safely(run_status) events.handler_executed(handler) end events.handlers_completed Chef::Log.error("Exception handlers complete") end # Wire up a notification to run the exception handlers if the chef run fails. Chef::Client.when_run_fails do |run_status| run_exception_handlers(run_status) end extend Forwardable # The Chef::RunStatus object containing data about the Chef run. attr_reader :run_status ## # :method: start_time # # The time the chef run started def_delegator :@run_status, :start_time ## # :method: end_time # # The time the chef run ended def_delegator :@run_status, :end_time ## # :method: elapsed_time # # The time elapsed between the start and finish of the chef run def_delegator :@run_status, :elapsed_time ## # :method: run_context # # The Chef::RunContext object used by the chef run def_delegator :@run_status, :run_context ## # :method: exception # # The uncaught Exception that terminated the chef run, or nil if the run # completed successfully def_delegator :@run_status, :exception ## # :method: backtrace # # The backtrace captured by the uncaught exception that terminated the chef # run, or nil if the run completed successfully def_delegator :@run_status, :backtrace ## # :method: node # # The Chef::Node for this client run def_delegator :@run_status, :node ## # :method: all_resources # # An Array containing all resources in the chef run's resource_collection def_delegator :@run_status, :all_resources ## # :method: updated_resources # # An Array containing all resources that were updated during the chef run def_delegator :@run_status, :updated_resources ## # :method: success? # # Was the chef run successful? True if the chef run did not raise an # uncaught exception def_delegator :@run_status, :success? ## # :method: failed? # # Did the chef run fail? True if the chef run raised an uncaught exception def_delegator :@run_status, :failed? # The main entry point for report handling. Subclasses should override this # method with their own report handling logic. def report end # Runs the report handler, rescuing and logging any errors it may cause. # This ensures that all handlers get a chance to run even if one fails. # This method should not be overridden by subclasses unless you know what # you're doing. def run_report_safely(run_status) run_report_unsafe(run_status) rescue Exception => e Chef::Log.error("Report handler #{self.class.name} raised #{e.inspect}") Array(e.backtrace).each { |line| Chef::Log.error(line) } ensure @run_status = nil end # Runs the report handler without any error handling. This method should # not be used directly except in testing. def run_report_unsafe(run_status) @run_status = run_status report end # Return the Hash representation of the run_status def data @run_status.to_hash end end end chef-12.14.60/lib/chef/handler/000077500000000000000000000000001276456504500157605ustar00rootroot00000000000000chef-12.14.60/lib/chef/handler/error_report.rb000066400000000000000000000020501276456504500210260ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2010-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/handler" require "chef/resource/directory" class Chef class Handler class ErrorReport < ::Chef::Handler def report Chef::FileCache.store("failed-run-data.json", Chef::JSONCompat.to_json_pretty(data), 0640) Chef::Log.fatal("Saving node information to #{Chef::FileCache.load("failed-run-data.json", false)}") end end end end chef-12.14.60/lib/chef/handler/json_file.rb000066400000000000000000000035221276456504500202570ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2010-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/handler" require "chef/resource/directory" class Chef class Handler class JsonFile < ::Chef::Handler attr_reader :config def initialize(config = {}) @config = config @config[:path] ||= "/var/chef/reports" @config end def report if exception Chef::Log.error("Creating JSON exception report") else Chef::Log.info("Creating JSON run report") end build_report_dir savetime = Time.now.strftime("%Y%m%d%H%M%S") File.open(File.join(config[:path], "chef-run-report-#{savetime}.json"), "w") do |file| #ensure start time and end time are output in the json properly in the event activesupport happens to be on the system run_data = data run_data[:start_time] = run_data[:start_time].to_s run_data[:end_time] = run_data[:end_time].to_s file.puts Chef::JSONCompat.to_json_pretty(run_data) end end def build_report_dir unless File.exists?(config[:path]) FileUtils.mkdir_p(config[:path]) File.chmod(00700, config[:path]) end end end end end chef-12.14.60/lib/chef/http.rb000066400000000000000000000414331276456504500156540ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Thom May () # Author:: Nuo Yan () # Author:: Christopher Brown () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, 2013-2015 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 "tempfile" require "net/https" require "uri" require "chef/http/basic_client" require "chef/monkey_patches/net_http" require "chef/config" require "chef/platform/query_helpers" require "chef/exceptions" class Chef # == Chef::HTTP # Basic HTTP client, with support for adding features via middleware class HTTP # Class for applying middleware behaviors to streaming # responses. Collects stream handlers (if any) from each # middleware. When #handle_chunk is called, the chunk gets # passed to all handlers in turn for processing. class StreamHandler def initialize(middlewares, response) middlewares = middlewares.flatten @stream_handlers = [] middlewares.each do |middleware| stream_handler = middleware.stream_response_handler(response) @stream_handlers << stream_handler unless stream_handler.nil? end end def handle_chunk(next_chunk) # stream handlers handle responses so must be applied in reverse order # (same as #apply_stream_complete_middleware or #apply_response_midddleware) @stream_handlers.reverse.inject(next_chunk) do |chunk, handler| Chef::Log.debug("Chef::HTTP::StreamHandler calling #{handler.class}#handle_chunk") handler.handle_chunk(chunk) end end end def self.middlewares @middlewares ||= [] end def self.use(middleware_class) middlewares << middleware_class end attr_reader :url attr_reader :sign_on_redirect attr_reader :redirect_limit attr_reader :options attr_reader :middlewares # [Boolean] if we're doing keepalives or not attr_reader :keepalives # Create a HTTP client object. The supplied +url+ is used as the base for # all subsequent requests. For example, when initialized with a base url # http://localhost:4000, a call to +get+ with 'nodes' will make an # HTTP GET request to http://localhost:4000/nodes def initialize(url, options = {}) @url = url @default_headers = options[:headers] || {} @sign_on_redirect = true @redirects_followed = 0 @redirect_limit = 10 @keepalives = options[:keepalives] || false @options = options @middlewares = [] self.class.middlewares.each do |middleware_class| @middlewares << middleware_class.new(options) end end # Send an HTTP HEAD request to the path # # === Parameters # path:: path part of the request URL def head(path, headers = {}) request(:HEAD, path, headers) end # Send an HTTP GET request to the path # # === Parameters # path:: The path to GET def get(path, headers = {}) request(:GET, path, headers) end # Send an HTTP PUT request to the path # # === Parameters # path:: path part of the request URL def put(path, json, headers = {}) request(:PUT, path, headers, json) end # Send an HTTP POST request to the path # # === Parameters # path:: path part of the request URL def post(path, json, headers = {}) request(:POST, path, headers, json) end # Send an HTTP DELETE request to the path # # === Parameters # path:: path part of the request URL def delete(path, headers = {}) request(:DELETE, path, headers) end # Makes an HTTP request to +path+ with the given +method+, +headers+, and # +data+ (if applicable). def request(method, path, headers = {}, data = false) url = create_url(path) method, url, headers, data = apply_request_middleware(method, url, headers, data) response, rest_request, return_value = send_http_request(method, url, headers, data) response, rest_request, return_value = apply_response_middleware(response, rest_request, return_value) response.error! unless success_response?(response) return_value rescue Exception => exception log_failed_request(response, return_value) unless response.nil? if exception.respond_to?(:chef_rest_request=) exception.chef_rest_request = rest_request end raise end def streaming_request_with_progress(path, headers = {}, &progress_block) url = create_url(path) response, rest_request, return_value = nil, nil, nil tempfile = nil method = :GET method, url, headers, data = apply_request_middleware(method, url, headers, data) response, rest_request, return_value = send_http_request(method, url, headers, data) do |http_response| if http_response.kind_of?(Net::HTTPSuccess) tempfile = stream_to_tempfile(url, http_response, &progress_block) end apply_stream_complete_middleware(http_response, rest_request, return_value) end return nil if response.kind_of?(Net::HTTPRedirection) unless response.kind_of?(Net::HTTPSuccess) response.error! end tempfile rescue Exception => e log_failed_request(response, return_value) unless response.nil? if e.respond_to?(:chef_rest_request=) e.chef_rest_request = rest_request end raise end # Makes a streaming download request, streaming the response body to a # tempfile. If a block is given, the tempfile is passed to the block and # the tempfile will automatically be unlinked after the block is executed. # # If no block is given, the tempfile is returned, which means it's up to # you to unlink the tempfile when you're done with it. # # @yield [tempfile] block to process the tempfile # @yieldparams [tempfile] tempfile def streaming_request(path, headers = {}) url = create_url(path) response, rest_request, return_value = nil, nil, nil tempfile = nil method = :GET method, url, headers, data = apply_request_middleware(method, url, headers, data) response, rest_request, return_value = send_http_request(method, url, headers, data) do |http_response| if http_response.kind_of?(Net::HTTPSuccess) tempfile = stream_to_tempfile(url, http_response) end apply_stream_complete_middleware(http_response, rest_request, return_value) end return nil if response.kind_of?(Net::HTTPRedirection) unless response.kind_of?(Net::HTTPSuccess) response.error! end if block_given? begin yield tempfile ensure tempfile && tempfile.close! end end tempfile rescue Exception => e log_failed_request(response, return_value) unless response.nil? if e.respond_to?(:chef_rest_request=) e.chef_rest_request = rest_request end raise end def http_client(base_url = nil) base_url ||= url if keepalives && !base_url.nil? # only reuse the http_client if we want keepalives and have a base_url @http_client ||= {} # the per-host per-port cache here gets peristent connections correct when # redirecting to different servers if base_url.is_a?(String) # sigh, this kind of abuse can't happen with strongly typed languages @http_client[base_url] ||= build_http_client(base_url) else @http_client[base_url.host] ||= {} @http_client[base_url.host][base_url.port] ||= build_http_client(base_url) end else build_http_client(base_url) end end # DEPRECATED: This is only kept around to provide access to cache control data in # lib/chef/provider/remote_file/http.rb # FIXME: Find a better API. def last_response @last_response end private # @api private def build_http_client(base_url) if chef_zero_uri?(base_url) # PERFORMANCE CRITICAL: *MUST* lazy require here otherwise we load up webrick # via chef-zero and that hits DNS (at *require* time) which may timeout, # when for most knife/chef-client work we never need/want this loaded. unless defined?(SocketlessChefZeroClient) require "chef/http/socketless_chef_zero_client" end SocketlessChefZeroClient.new(base_url) else BasicClient.new(base_url, ssl_policy: Chef::HTTP::APISSLPolicy, keepalives: keepalives) end end # @api private def create_url(path) return path if path.is_a?(URI) if path =~ /^(http|https|chefzero):\/\//i URI.parse(path) elsif path.nil? || path.empty? URI.parse(@url) else # The regular expressions used here are to make sure '@url' does not have # any trailing slashes and 'path' does not have any leading slashes. This # way they are always joined correctly using just one slash. URI.parse(@url.gsub(%r{/+$}, "") + "/" + path.gsub(%r{^/+}, "")) end end # @api private def apply_request_middleware(method, url, headers, data) middlewares.inject([method, url, headers, data]) do |req_data, middleware| Chef::Log.debug("Chef::HTTP calling #{middleware.class}#handle_request") middleware.handle_request(*req_data) end end # @api private def apply_response_middleware(response, rest_request, return_value) middlewares.reverse.inject([response, rest_request, return_value]) do |res_data, middleware| Chef::Log.debug("Chef::HTTP calling #{middleware.class}#handle_response") middleware.handle_response(*res_data) end end # @api private def apply_stream_complete_middleware(response, rest_request, return_value) middlewares.reverse.inject([response, rest_request, return_value]) do |res_data, middleware| Chef::Log.debug("Chef::HTTP calling #{middleware.class}#handle_stream_complete") middleware.handle_stream_complete(*res_data) end end # @api private def log_failed_request(response, return_value) return_value ||= {} error_message = "HTTP Request Returned #{response.code} #{response.message}: " error_message << (return_value["error"].respond_to?(:join) ? return_value["error"].join(", ") : return_value["error"].to_s) Chef::Log.info(error_message) end # @api private def success_response?(response) response.kind_of?(Net::HTTPSuccess) || response.kind_of?(Net::HTTPRedirection) end # Runs a synchronous HTTP request, with no middleware applied (use #request # to have the middleware applied). The entire response will be loaded into memory. # @api private def send_http_request(method, url, headers, body, &response_handler) headers = build_headers(method, url, headers, body) retrying_http_errors(url) do client = http_client(url) return_value = nil if block_given? request, response = client.request(method, url, body, headers, &response_handler) else request, response = client.request(method, url, body, headers) { |r| r.read_body } return_value = response.read_body end @last_response = response if response.kind_of?(Net::HTTPSuccess) [response, request, return_value] elsif response.kind_of?(Net::HTTPNotModified) # Must be tested before Net::HTTPRedirection because it's subclass. [response, request, false] elsif redirect_location = redirected_to(response) if [:GET, :HEAD].include?(method) follow_redirect do send_http_request(method, url + redirect_location, headers, body, &response_handler) end else raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects." end else [response, request, nil] end end end # Wraps an HTTP request with retry logic. # === Arguments # url:: URL of the request, used for error messages # @api private def retrying_http_errors(url) http_attempts = 0 begin loop do http_attempts += 1 response, request, return_value = yield # handle HTTP 50X Error if response.kind_of?(Net::HTTPServerError) && !Chef::Config.local_mode if http_retry_count - http_attempts + 1 > 0 sleep_time = 1 + (2**http_attempts) + rand(2**http_attempts) Chef::Log.error("Server returned error #{response.code} for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s") sleep(sleep_time) redo end end return [response, request, return_value] end rescue SocketError, Errno::ETIMEDOUT, Errno::ECONNRESET => e if http_retry_count - http_attempts + 1 > 0 Chef::Log.error("Error connecting to #{url}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end e.message.replace "Error connecting to #{url} - #{e.message}" raise e rescue Errno::ECONNREFUSED if http_retry_count - http_attempts + 1 > 0 Chef::Log.error("Connection refused connecting to #{url}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end raise Errno::ECONNREFUSED, "Connection refused connecting to #{url}, giving up" rescue Timeout::Error if http_retry_count - http_attempts + 1 > 0 Chef::Log.error("Timeout connecting to #{url}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end raise Timeout::Error, "Timeout connecting to #{url}, giving up" rescue OpenSSL::SSL::SSLError => e if (http_retry_count - http_attempts + 1 > 0) && !e.message.include?("certificate verify failed") Chef::Log.error("SSL Error connecting to #{url}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end raise OpenSSL::SSL::SSLError, "SSL Error connecting to #{url} - #{e.message}" end end # @api private def http_retry_delay config[:http_retry_delay] end # @api private def http_retry_count config[:http_retry_count] end # @api private def config Chef::Config end # @api private def follow_redirect raise Chef::Exceptions::RedirectLimitExceeded if @redirects_followed >= redirect_limit @redirects_followed += 1 Chef::Log.debug("Following redirect #{@redirects_followed}/#{redirect_limit}") yield ensure @redirects_followed = 0 end # @api private def chef_zero_uri?(uri) uri = URI.parse(uri) unless uri.respond_to?(:scheme) uri.scheme == "chefzero" end # @api private def redirected_to(response) return nil unless response.kind_of?(Net::HTTPRedirection) # Net::HTTPNotModified is undesired subclass of Net::HTTPRedirection so test for this return nil if response.kind_of?(Net::HTTPNotModified) response["location"] end # @api private def build_headers(method, url, headers = {}, json_body = false) headers = @default_headers.merge(headers) headers["Content-Length"] = json_body.bytesize.to_s if json_body headers.merge!(Chef::Config[:custom_http_headers]) if Chef::Config[:custom_http_headers] headers end # @api private def stream_to_tempfile(url, response, &progress_block) content_length = response["Content-Length"] tf = Tempfile.open("chef-rest") if Chef::Platform.windows? tf.binmode # required for binary files on Windows platforms end Chef::Log.debug("Streaming download from #{url} to tempfile #{tf.path}") # Stolen from http://www.ruby-forum.com/topic/166423 # Kudos to _why! stream_handler = StreamHandler.new(middlewares, response) response.read_body do |chunk| tf.write(stream_handler.handle_chunk(chunk)) yield tf.size, content_length if block_given? end tf.close tf rescue Exception tf.close! if tf raise end end end chef-12.14.60/lib/chef/http/000077500000000000000000000000001276456504500153225ustar00rootroot00000000000000chef-12.14.60/lib/chef/http/auth_credentials.rb000066400000000000000000000041471276456504500211730ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Thom May () # Author:: Nuo Yan () # Author:: Christopher Brown () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "mixlib/authentication/signedheaderauth" class Chef class HTTP class AuthCredentials attr_reader :client_name, :key def initialize(client_name = nil, key = nil) @client_name, @key = client_name, key end def sign_requests? !!key end def signature_headers(request_params = {}) raise ArgumentError, "Cannot sign the request without a client name, check that :node_name is assigned" if client_name.nil? Chef::Log.debug("Signing the request as #{client_name}") # params_in = {:http_method => :GET, :path => "/clients", :body => "", :host => "localhost"} request_params = request_params.dup request_params[:timestamp] = Time.now.utc.iso8601 request_params[:user_id] = client_name request_params[:proto_version] = Chef::Config[:authentication_protocol_version] host = request_params.delete(:host) || "localhost" sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(request_params) signed = sign_obj.sign(key).merge({ :host => host }) signed.inject({}) { |memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1]; memo } end end end end chef-12.14.60/lib/chef/http/authenticator.rb000066400000000000000000000067011276456504500205250ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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/http/auth_credentials" require "chef/exceptions" require "openssl" class Chef class HTTP class Authenticator DEFAULT_SERVER_API_VERSION = "1" attr_reader :signing_key_filename attr_reader :raw_key attr_reader :attr_names attr_reader :auth_credentials attr_accessor :sign_request def initialize(opts = {}) @raw_key = nil @sign_request = true @signing_key_filename = opts[:signing_key_filename] @key = load_signing_key(opts[:signing_key_filename], opts[:raw_key]) @auth_credentials = AuthCredentials.new(opts[:client_name], @key) if opts[:api_version] @api_version = opts[:api_version] else @api_version = DEFAULT_SERVER_API_VERSION end end def handle_request(method, url, headers = {}, data = false) headers["X-Ops-Server-API-Version"] = @api_version headers.merge!(authentication_headers(method, url, data, headers)) if sign_requests? [method, url, headers, data] end def handle_response(http_response, rest_request, return_value) [http_response, rest_request, return_value] end def stream_response_handler(response) nil end def handle_stream_complete(http_response, rest_request, return_value) [http_response, rest_request, return_value] end def sign_requests? auth_credentials.sign_requests? && @sign_request end def client_name @auth_credentials.client_name end def load_signing_key(key_file, raw_key = nil) if !!key_file @raw_key = IO.read(key_file).strip elsif !!raw_key @raw_key = raw_key.strip else return nil end @key = OpenSSL::PKey::RSA.new(@raw_key) rescue SystemCallError, IOError => e Chef::Log.warn "Failed to read the private key #{key_file}: #{e.inspect}" raise Chef::Exceptions::PrivateKeyMissing, "I cannot read #{key_file}, which you told me to use to sign requests!" rescue OpenSSL::PKey::RSAError msg = "The file #{key_file} or :raw_key option does not contain a correctly formatted private key.\n" msg << "The key file should begin with '-----BEGIN RSA PRIVATE KEY-----' and end with '-----END RSA PRIVATE KEY-----'" raise Chef::Exceptions::InvalidPrivateKey, msg end def authentication_headers(method, url, json_body = nil, headers = nil) request_params = { :http_method => method, :path => url.path, :body => json_body, :host => "#{url.host}:#{url.port}", :headers => headers, } request_params[:body] ||= "" auth_credentials.signature_headers(request_params) end end end end chef-12.14.60/lib/chef/http/basic_client.rb000066400000000000000000000120471276456504500202720ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Thom May () # Author:: Nuo Yan () # Author:: Christopher Brown () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-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 "net/http" require "chef/http/ssl_policies" require "chef/http/http_request" class Chef class HTTP class BasicClient HTTPS = "https".freeze attr_reader :url attr_reader :http_client attr_reader :ssl_policy attr_reader :keepalives # Instantiate a BasicClient. # === Arguments: # url:: An URI for the remote server. # === Options: # ssl_policy:: The SSL Policy to use, defaults to DefaultSSLPolicy def initialize(url, opts = {}) @url = url @ssl_policy = opts[:ssl_policy] || DefaultSSLPolicy @keepalives = opts[:keepalives] || false end def http_client @http_client ||= build_http_client end def host @url.hostname end def port @url.port end def request(method, url, req_body, base_headers = {}) http_request = HTTPRequest.new(method, url, req_body, base_headers).http_request Chef::Log.debug("Initiating #{method} to #{url}") Chef::Log.debug("---- HTTP Request Header Data: ----") base_headers.each do |name, value| Chef::Log.debug("#{name}: #{value}") end Chef::Log.debug("---- End HTTP Request Header Data ----") http_client.request(http_request) do |response| Chef::Log.debug("---- HTTP Status and Header Data: ----") Chef::Log.debug("HTTP #{response.http_version} #{response.code} #{response.msg}") response.each do |header, value| Chef::Log.debug("#{header}: #{value}") end Chef::Log.debug("---- End HTTP Status/Header Data ----") # For non-400's, log the request and response bodies if !response.code || !response.code.start_with?("2") if response.body Chef::Log.debug("---- HTTP Response Body ----") Chef::Log.debug(response.body) Chef::Log.debug("---- End HTTP Response Body -----") end if req_body Chef::Log.debug("---- HTTP Request Body ----") Chef::Log.debug(req_body) Chef::Log.debug("---- End HTTP Request Body ----") end end yield response if block_given? # http_client.request may not have the return signature we want, so # force the issue: return [http_request, response] end rescue OpenSSL::SSL::SSLError => e Chef::Log.error("SSL Validation failure connecting to host: #{host} - #{e.message}") raise end def proxy_uri @proxy_uri ||= Chef::Config.proxy_uri(url.scheme, host, port) end def build_http_client # Note: the last nil in the new below forces Net::HTTP to ignore the # no_proxy environment variable. This is a workaround for limitations # in Net::HTTP use of the no_proxy environment variable. We internally # match no_proxy with a fuzzy matcher, rather than letting Net::HTTP # do it. http_client = http_client_builder.new(host, port, nil) http_client.proxy_port = nil if http_client.proxy_address == nil if url.scheme == HTTPS configure_ssl(http_client) end http_client.read_timeout = config[:rest_timeout] http_client.open_timeout = config[:rest_timeout] if keepalives http_client.start else http_client end end def config Chef::Config end def http_client_builder if proxy_uri.nil? Net::HTTP else Chef::Log.debug("Using #{proxy_uri.host}:#{proxy_uri.port} for proxy") Net::HTTP.Proxy(proxy_uri.host, proxy_uri.port, http_proxy_user(proxy_uri), http_proxy_pass(proxy_uri)) end end def http_proxy_user(proxy_uri) proxy_uri.user || Chef::Config["#{proxy_uri.scheme}_proxy_user"] end def http_proxy_pass(proxy_uri) proxy_uri.password || Chef::Config["#{proxy_uri.scheme}_proxy_pass"] end def configure_ssl(http_client) http_client.use_ssl = true ssl_policy.apply_to(http_client) end end end end chef-12.14.60/lib/chef/http/cookie_jar.rb000066400000000000000000000017661276456504500177660ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Thom May () # Author:: Nuo Yan () # Author:: Christopher Brown () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-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 "singleton" class Chef class HTTP class CookieJar < Hash include Singleton end end end chef-12.14.60/lib/chef/http/cookie_manager.rb000066400000000000000000000035371276456504500206220ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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/http/cookie_jar" class Chef class HTTP # An HTTP middleware to manage storing/sending cookies in HTTP requests. # Most HTTP communication in Chef does not need cookies, it was originally # implemented to support OpenID, but it's not known who might be relying on # it, so it's included with Chef::REST class CookieManager def initialize(options = {}) @cookies = CookieJar.instance end def handle_request(method, url, headers = {}, data = false) @host, @port = url.host, url.port if @cookies.has_key?("#{@host}:#{@port}") headers["Cookie"] = @cookies["#{@host}:#{@port}"] end [method, url, headers, data] end def handle_response(http_response, rest_request, return_value) if http_response["set-cookie"] @cookies["#{@host}:#{@port}"] = http_response["set-cookie"] end [http_response, rest_request, return_value] end def stream_response_handler(response) nil end def handle_stream_complete(http_response, rest_request, return_value) [http_response, rest_request, return_value] end end end end chef-12.14.60/lib/chef/http/decompressor.rb000066400000000000000000000111771276456504500203630ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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 "zlib" require "chef/http/http_request" class Chef class HTTP # Middleware-esque class for handling compression in HTTP responses. class Decompressor class NoopInflater def inflate(chunk) chunk end alias :handle_chunk :inflate end class GzipInflater < Zlib::Inflate def initialize super(Zlib::MAX_WBITS + 16) end alias :handle_chunk :inflate end class DeflateInflater < Zlib::Inflate def initialize super end alias :handle_chunk :inflate end CONTENT_ENCODING = "content-encoding".freeze GZIP = "gzip".freeze DEFLATE = "deflate".freeze IDENTITY = "identity".freeze def initialize(opts = {}) @disable_gzip = false handle_options(opts) end def handle_request(method, url, headers = {}, data = false) headers[HTTPRequest::ACCEPT_ENCODING] = HTTPRequest::ENCODING_GZIP_DEFLATE unless gzip_disabled? [method, url, headers, data] end def handle_response(http_response, rest_request, return_value) # temporary hack, skip processing if return_value is false # needed to keep conditional get stuff working correctly. return [http_response, rest_request, return_value] if return_value == false response_body = decompress_body(http_response) http_response.body.replace(response_body) if http_response.body.respond_to?(:replace) [http_response, rest_request, return_value] end def handle_stream_complete(http_response, rest_request, return_value) [http_response, rest_request, return_value] end def decompress_body(response) if gzip_disabled? || response.body.nil? response.body else case response[CONTENT_ENCODING] when GZIP Chef::Log.debug "Decompressing gzip response" Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body) when DEFLATE Chef::Log.debug "Decompressing deflate response" Zlib::Inflate.inflate(response.body) else response.body end end end # This isn't used when this class is used as middleware; it returns an # object you can use to unzip/inflate a streaming response. def stream_response_handler(response) if gzip_disabled? Chef::Log.debug "disable_gzip is set. \ Not using #{response[CONTENT_ENCODING]} \ and initializing noop stream deflator." NoopInflater.new else case response[CONTENT_ENCODING] when GZIP Chef::Log.debug "Initializing gzip stream deflator" GzipInflater.new when DEFLATE Chef::Log.debug "Initializing deflate stream deflator" DeflateInflater.new else Chef::Log.debug "content_encoding = '#{response[CONTENT_ENCODING]}' \ initializing noop stream deflator." NoopInflater.new end end end # gzip is disabled using the disable_gzip => true option in the # constructor. When gzip is disabled, no 'Accept-Encoding' header will be # set, and the response will not be decompressed, no matter what the # Content-Encoding header of the response is. The intended use case for # this is to work around situations where you request +file.tar.gz+, but # the server responds with a content type of tar and a content encoding of # gzip, tricking the client into decompressing the response so you end up # with a tar archive (no gzip) named file.tar.gz def gzip_disabled? @disable_gzip end private def handle_options(opts) opts.each do |name, value| case name.to_s when "disable_gzip" @disable_gzip = value end end end end end end chef-12.14.60/lib/chef/http/http_request.rb000066400000000000000000000132171276456504500204020ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Thom May () # Author:: Nuo Yan () # Author:: Christopher Brown () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, 2010-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 "net/http" # To load faster, we only want ohai's version string. # However, in ohai before 0.6.0, the version is defined # in ohai, not ohai/version begin require "ohai/version" #used in user agent string. rescue LoadError require "ohai" end require "chef/version" class Chef class HTTP class HTTPRequest engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby" UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +https://chef.io)" DEFAULT_UA = "Chef Client" << UA_COMMON USER_AGENT = "User-Agent".freeze ACCEPT_ENCODING = "Accept-Encoding".freeze ENCODING_GZIP_DEFLATE = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3".freeze GET = "get".freeze PUT = "put".freeze POST = "post".freeze DELETE = "delete".freeze HEAD = "head".freeze HTTPS = "https".freeze SLASH = "/".freeze HOST_LOWER = "host".freeze URI_SCHEME_DEFAULT_PORT = { "http" => 80, "https" => 443 }.freeze def self.user_agent=(ua) @user_agent = ua end def self.user_agent @user_agent ||= DEFAULT_UA end attr_reader :method, :url, :headers, :http_client, :http_request def initialize(method, url, req_body, base_headers = {}) @method, @url = method, url @request_body = nil build_headers(base_headers) configure_http_request(req_body) end def host @url.hostname end def uri_safe_host @url.host end def port @url.port end def query @url.query end def path @url.path.empty? ? SLASH : @url.path end # DEPRECATED. Call request on an HTTP client object instead. def call hide_net_http_bug do http_client.request(http_request) do |response| yield response if block_given? response end end end def config Chef::Config end # DEPRECATED. Call request on an HTTP client object instead. def http_client @http_client ||= BasicClient.new(url).http_client end private def hide_net_http_bug yield rescue NoMethodError => e # http://redmine.ruby-lang.org/issues/show/2708 # http://redmine.ruby-lang.org/issues/show/2758 if e.to_s =~ /#{Regexp.escape(%q{undefined method `closed?' for nil:NilClass})}/ Chef::Log.debug("Rescued error in http connect, re-raising as Errno::ECONNREFUSED to hide bug in net/http") Chef::Log.debug("#{e.class.name}: #{e}") Chef::Log.debug(e.backtrace.join("\n")) raise Errno::ECONNREFUSED, "Connection refused attempting to contact #{url.scheme}://#{host}:#{port}" else raise end end def build_headers(headers) @headers = headers.dup # No response compression unless we asked for it explicitly: @headers[HTTPRequest::ACCEPT_ENCODING] ||= "identity" @headers["X-Chef-Version"] = ::Chef::VERSION # Only include port in Host header when it is not the default port # for the url scheme (80;443) - Fixes CHEF-5355 host_header = uri_safe_host.dup host_header << ":#{port}" unless URI_SCHEME_DEFAULT_PORT[@url.scheme] == port.to_i @headers["Host"] = host_header unless @headers.keys.any? { |k| k.downcase.to_s == HOST_LOWER } @headers end def configure_http_request(request_body = nil) req_path = "#{path}" req_path << "?#{query}" if query @http_request = case method.to_s.downcase when GET Net::HTTP::Get.new(req_path, headers) when POST Net::HTTP::Post.new(req_path, headers) when PUT Net::HTTP::Put.new(req_path, headers) when DELETE Net::HTTP::Delete.new(req_path, headers) when HEAD Net::HTTP::Head.new(req_path, headers) else raise ArgumentError, "You must provide :GET, :PUT, :POST, :DELETE or :HEAD as the method" end @http_request.body = request_body if request_body && @http_request.request_body_permitted? # Optionally handle HTTP Basic Authentication if url.user user = URI.unescape(url.user) password = URI.unescape(url.password) if url.password @http_request.basic_auth(user, password) end # Overwrite default UA @http_request[USER_AGENT] = self.class.user_agent end end end end chef-12.14.60/lib/chef/http/json_input.rb000066400000000000000000000047111276456504500200420ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Author:: John Keiser () # Copyright:: Copyright 2013-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/json_compat" class Chef class HTTP # Middleware that takes json input and turns it into raw text class JSONInput attr_accessor :opts def initialize(opts = {}) @opts = opts end def handle_request(method, url, headers = {}, data = false) if data && should_encode_as_json?(headers) headers.delete_if { |key, _value| key.casecmp("content-type").zero? } headers["Content-Type"] = "application/json" json_opts = {} json_opts[:validate_utf8] = opts[:validate_utf8] if opts.has_key?(:validate_utf8) data = Chef::JSONCompat.to_json(data, json_opts) # Force encoding to binary to fix SSL related EOFErrors # cf. http://tickets.opscode.com/browse/CHEF-2363 # http://redmine.ruby-lang.org/issues/5233 data.force_encoding(Encoding::BINARY) if data.respond_to?(:force_encoding) end [method, url, headers, data] end def handle_response(http_response, rest_request, return_value) [http_response, rest_request, return_value] end def stream_response_handler(response) nil end def handle_stream_complete(http_response, rest_request, return_value) [http_response, rest_request, return_value] end private def should_encode_as_json?(headers) # ruby/Net::HTTP don't enforce capitalized headers (it normalizes them # for you before sending the request), so we have to account for all # the variations we might find requested_content_type = headers.find { |k, v| k.casecmp("content-type").zero? } requested_content_type.nil? || requested_content_type.last.include?("json") end end end end chef-12.14.60/lib/chef/http/json_output.rb000066400000000000000000000052511276456504500202430ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Author:: John Keiser () # Copyright:: Copyright 2013-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/json_compat" require "chef/log" class Chef class HTTP # Middleware that takes an HTTP response, parses it as JSON if possible. class JSONOutput attr_accessor :raw_output attr_accessor :inflate_json_class def initialize(opts = {}) @raw_output = opts[:raw_output] @inflate_json_class = opts[:inflate_json_class] end def handle_request(method, url, headers = {}, data = false) # Ideally this should always set Accept to application/json, but # Chef::REST is sometimes used to make non-JSON requests, so it sets # Accept to the desired value before middlewares get called. headers["Accept"] ||= "application/json" [method, url, headers, data] end def handle_response(http_response, rest_request, return_value) # temporary hack, skip processing if return_value is false # needed to keep conditional get stuff working correctly. return [http_response, rest_request, return_value] if return_value == false if http_response["content-type"] =~ /json/ if http_response.body.nil? return_value = nil elsif raw_output return_value = http_response.body.to_s else if inflate_json_class return_value = Chef::JSONCompat.from_json(http_response.body.chomp) else return_value = Chef::JSONCompat.parse(http_response.body.chomp) end end [http_response, rest_request, return_value] else Chef::Log.debug("Expected JSON response, but got content-type '#{http_response['content-type']}'") return [http_response, rest_request, http_response.body.to_s] end end def handle_stream_complete(http_response, rest_request, return_value) [http_response, rest_request, return_value] end def stream_response_handler(response) nil end end end end chef-12.14.60/lib/chef/http/json_to_model_output.rb000066400000000000000000000021451276456504500221240ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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/http/json_output" class Chef class HTTP # A Middleware-ish thing that takes an HTTP response, parses it as JSON if # possible, and converts it into an appropriate model object if it contains # a `json_class` key. class JSONToModelOutput < JSONOutput def initialize(opts = {}) opts[:inflate_json_class] = true if !opts.has_key?(:inflate_json_class) super end end end end chef-12.14.60/lib/chef/http/remote_request_id.rb000066400000000000000000000025201276456504500213650ustar00rootroot00000000000000# Author:: Prajakta Purohit () # Copyright:: Copyright 2009-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/request_id" class Chef class HTTP class RemoteRequestID def initialize(opts = {}) end def handle_request(method, url, headers = {}, data = false) headers["X-REMOTE-REQUEST-ID"] = Chef::RequestID.instance.request_id [method, url, headers, data] end def handle_response(http_response, rest_request, return_value) [http_response, rest_request, return_value] end def stream_response_handler(response) nil end def handle_stream_complete(http_response, rest_request, return_value) [http_response, rest_request, return_value] end end end end chef-12.14.60/lib/chef/http/simple.rb000066400000000000000000000021751276456504500171450ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/http" require "chef/http/authenticator" require "chef/http/decompressor" require "chef/http/cookie_manager" require "chef/http/validate_content_length" class Chef class HTTP class Simple < HTTP use Decompressor use CookieManager # ValidateContentLength should come after Decompressor # because the order of middlewares is reversed when handling # responses. use ValidateContentLength end end end chef-12.14.60/lib/chef/http/simple_json.rb000066400000000000000000000023011276456504500201650ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/http" require "chef/http/authenticator" require "chef/http/decompressor" require "chef/http/cookie_manager" require "chef/http/validate_content_length" class Chef class HTTP class SimpleJSON < HTTP use JSONInput use JSONOutput use CookieManager use Decompressor use RemoteRequestID # ValidateContentLength should come after Decompressor # because the order of middlewares is reversed when handling # responses. use ValidateContentLength end end end chef-12.14.60/lib/chef/http/socketless_chef_zero_client.rb000066400000000000000000000155321276456504500234160ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # --- # Some portions of the code in this file are verbatim copies of code from the # fakeweb project: https://github.com/chrisk/fakeweb # # fakeweb is distributed under the MIT license, which is copied below: # --- # # Copyright 2006-2016, Blaine Cook, Chris Kampmeier, and other contributors # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. require "chef_zero/server" class Chef class HTTP # HTTP Client class that talks directly to Zero via the Rack interface. class SocketlessChefZeroClient # This module is extended into Net::HTTP Response objects created from # Socketless Chef Zero responses. module ResponseExts # Net::HTTP raises an error if #read_body is called with a block or # file argument after the body has already been read from the network. # # Since we always set the body to the string response from Chef Zero # and set the `@read` indicator variable, we have to patch this method # or else streaming-style responses won't work. def read_body(dest = nil, &block) if dest raise "responses from socketless chef zero can't be written to specific destination" end if block_given? yield(@body) else super end end end attr_reader :url # copied verbatim from webrick (2-clause BSD License) # # HTTP status codes and descriptions STATUS_MESSAGE = { 100 => "Continue", 101 => "Switching Protocols", 200 => "OK", 201 => "Created", 202 => "Accepted", 203 => "Non-Authoritative Information", 204 => "No Content", 205 => "Reset Content", 206 => "Partial Content", 207 => "Multi-Status", 300 => "Multiple Choices", 301 => "Moved Permanently", 302 => "Found", 303 => "See Other", 304 => "Not Modified", 305 => "Use Proxy", 307 => "Temporary Redirect", 400 => "Bad Request", 401 => "Unauthorized", 402 => "Payment Required", 403 => "Forbidden", 404 => "Not Found", 405 => "Method Not Allowed", 406 => "Not Acceptable", 407 => "Proxy Authentication Required", 408 => "Request Timeout", 409 => "Conflict", 410 => "Gone", 411 => "Length Required", 412 => "Precondition Failed", 413 => "Request Entity Too Large", 414 => "Request-URI Too Large", 415 => "Unsupported Media Type", 416 => "Request Range Not Satisfiable", 417 => "Expectation Failed", 422 => "Unprocessable Entity", 423 => "Locked", 424 => "Failed Dependency", 426 => "Upgrade Required", 428 => "Precondition Required", 429 => "Too Many Requests", 431 => "Request Header Fields Too Large", 500 => "Internal Server Error", 501 => "Not Implemented", 502 => "Bad Gateway", 503 => "Service Unavailable", 504 => "Gateway Timeout", 505 => "HTTP Version Not Supported", 507 => "Insufficient Storage", 511 => "Network Authentication Required", } STATUS_MESSAGE.values.each { |v| v.freeze } STATUS_MESSAGE.freeze def initialize(base_url) @url = base_url end def host @url.hostname end def port @url.port end # FIXME: yard with @yield def request(method, url, body, headers) request = req_to_rack(method, url, body, headers) res = ChefZero::SocketlessServerMap.request(port, request) net_http_response = to_net_http(res[0], res[1], res[2]) yield net_http_response if block_given? [self, net_http_response] end def req_to_rack(method, url, body, headers) body_str = body || "" { "SCRIPT_NAME" => "", "SERVER_NAME" => "localhost", "REQUEST_METHOD" => method.to_s.upcase, "PATH_INFO" => url.path, "QUERY_STRING" => url.query, "SERVER_PORT" => url.port, "HTTP_HOST" => "localhost:#{url.port}", "rack.url_scheme" => "chefzero", "rack.input" => StringIO.new(body_str), } end def to_net_http(code, headers, chunked_body) body = chunked_body.join("") msg = STATUS_MESSAGE[code] raise "Cannot determine HTTP status message for code #{code}" unless msg response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg) response.instance_variable_set(:@body, body) headers.each do |name, value| if value.respond_to?(:each) value.each { |v| response.add_field(name, v) } else response[name] = value end end response.instance_variable_set(:@read, true) response.extend(ResponseExts) response end private def headers_extracted_from_options options.reject { |name, _| KNOWN_OPTIONS.include?(name) }.map do |name, value| [name.to_s.split("_").map { |segment| segment.capitalize }.join("-"), value] end end end end end chef-12.14.60/lib/chef/http/ssl_policies.rb000066400000000000000000000104761276456504500203470ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Thom May () # Author:: Nuo Yan () # Author:: Christopher Brown () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-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 "openssl" require "chef/util/path_helper" class Chef class HTTP # == Chef::HTTP::DefaultSSLPolicy # Configures SSL behavior on an HTTP object via visitor pattern. class DefaultSSLPolicy def self.apply_to(http_client) new(http_client).apply http_client end attr_reader :http_client def initialize(http_client) @http_client = http_client end def apply set_verify_mode set_ca_store set_custom_certs set_client_credentials end def set_verify_mode if config[:ssl_verify_mode] == :verify_none http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE elsif config[:ssl_verify_mode] == :verify_peer http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER end end def set_ca_store if config[:ssl_ca_path] unless ::File.exist?(config[:ssl_ca_path]) raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_path #{config[:ssl_ca_path]} does not exist" end http_client.ca_path = config[:ssl_ca_path] elsif config[:ssl_ca_file] unless ::File.exist?(config[:ssl_ca_file]) raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{config[:ssl_ca_file]} does not exist" end http_client.ca_file = config[:ssl_ca_file] end end def set_custom_certs unless http_client.cert_store http_client.cert_store = OpenSSL::X509::Store.new http_client.cert_store.set_default_paths end if config.trusted_certs_dir certs = Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(config.trusted_certs_dir), "*.{crt,pem}")) certs.each do |cert_file| cert = OpenSSL::X509::Certificate.new(File.read(cert_file)) add_trusted_cert(cert) end end end def set_client_credentials if config[:ssl_client_cert] || config[:ssl_client_key] unless config[:ssl_client_cert] && config[:ssl_client_key] raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together" end unless ::File.exists?(config[:ssl_client_cert]) raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist" end unless ::File.exists?(config[:ssl_client_key]) raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist" end http_client.cert = OpenSSL::X509::Certificate.new(::File.read(config[:ssl_client_cert])) http_client.key = OpenSSL::PKey::RSA.new(::File.read(config[:ssl_client_key])) end end def config Chef::Config end private def add_trusted_cert(cert) http_client.cert_store.add_cert(cert) rescue OpenSSL::X509::StoreError => e raise e unless e.message == "cert already in hash table" end end class APISSLPolicy < DefaultSSLPolicy def set_verify_mode if config[:ssl_verify_mode] == :verify_peer || config[:verify_api_cert] http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER elsif config[:ssl_verify_mode] == :verify_none http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE end end end end end chef-12.14.60/lib/chef/http/validate_content_length.rb000066400000000000000000000075071276456504500225440ustar00rootroot00000000000000#-- # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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 "pp" require "chef/log" class Chef class HTTP # Middleware that validates the Content-Length header against the downloaded number of bytes. # # This must run before the decompressor middleware, since otherwise we will count the uncompressed # streamed bytes, rather than the on-the-wire compressed bytes. class ValidateContentLength class ContentLengthCounter attr_accessor :content_length def initialize @content_length = 0 end def handle_chunk(chunk) @content_length += chunk.bytesize chunk end end def initialize(opts = {}) end def handle_request(method, url, headers = {}, data = false) [method, url, headers, data] end def handle_response(http_response, rest_request, return_value) validate(http_response, http_response.body.bytesize) if http_response && http_response.body return [http_response, rest_request, return_value] end def handle_stream_complete(http_response, rest_request, return_value) if @content_length_counter.nil? Chef::Log.debug("No content-length information collected for the streamed download, cannot identify streamed download.") else validate(http_response, @content_length_counter.content_length) end # Make sure the counter is reset since this object might get used # again. See CHEF-5100 @content_length_counter = nil return [http_response, rest_request, return_value] end def stream_response_handler(response) @content_length_counter = ContentLengthCounter.new end private def response_content_length(response) return nil if response["content-length"].nil? if response["content-length"].is_a?(Array) response["content-length"].first.to_i else response["content-length"].to_i end end def validate(http_response, response_length) content_length = response_content_length(http_response) transfer_encoding = http_response["transfer-encoding"] if content_length.nil? Chef::Log.debug "HTTP server did not include a Content-Length header in response, cannot identify truncated downloads." return true end if content_length < 0 Chef::Log.debug "HTTP server responded with a negative Content-Length header (#{content_length}), cannot identify truncated downloads." return true end # if Transfer-Encoding is set the RFC states that we must ignore the Content-Length field # CHEF-5041: some proxies uncompress gzip content, leave the incorrect content-length, but set the transfer-encoding field unless transfer_encoding.nil? Chef::Log.debug "Transfer-Encoding header is set, skipping Content-Length check." return true end if response_length != content_length raise Chef::Exceptions::ContentLengthMismatch.new(response_length, content_length) end Chef::Log.debug "Content-Length validated correctly." true end end end end chef-12.14.60/lib/chef/json_compat.rb000066400000000000000000000134001276456504500172020ustar00rootroot00000000000000# # Author:: Tim Hinderliter () # Copyright:: Copyright 2010-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. # Wrapper class for interacting with JSON. require "ffi_yajl" require "chef/exceptions" # We're requiring this to prevent breaking consumers using Hash.to_json require "json" class Chef class JSONCompat JSON_MAX_NESTING = 1000 JSON_CLASS = "json_class".freeze CHEF_APICLIENT = "Chef::ApiClient".freeze CHEF_CHECKSUM = "Chef::Checksum".freeze CHEF_COOKBOOKVERSION = "Chef::CookbookVersion".freeze CHEF_DATABAG = "Chef::DataBag".freeze CHEF_DATABAGITEM = "Chef::DataBagItem".freeze CHEF_ENVIRONMENT = "Chef::Environment".freeze CHEF_NODE = "Chef::Node".freeze CHEF_ROLE = "Chef::Role".freeze CHEF_SANDBOX = "Chef::Sandbox".freeze CHEF_RESOURCE = "Chef::Resource".freeze CHEF_RESOURCECOLLECTION = "Chef::ResourceCollection".freeze CHEF_RESOURCESET = "Chef::ResourceCollection::ResourceSet".freeze CHEF_RESOURCELIST = "Chef::ResourceCollection::ResourceList".freeze CHEF_RUNLISTEXPANSION = "Chef::RunListExpansion".freeze class < e raise Chef::Exceptions::JSON::ParseError, e.message end end # Just call the JSON gem's parse method with a modified :max_nesting field def from_json(source, opts = {}) obj = parse(source, opts) # JSON gem requires top level object to be a Hash or Array (otherwise # you get the "must contain two octets" error). Yajl doesn't impose the # same limitation. For compatibility, we re-impose this condition. unless obj.kind_of?(Hash) || obj.kind_of?(Array) raise Chef::Exceptions::JSON::ParseError, "Top level JSON object must be a Hash or Array. (actual: #{obj.class})" end # The old default in the json gem (which we are mimicing because we # sadly rely on this misfeature) is to "create additions" i.e., convert # JSON objects into ruby objects. Explicit :create_additions => false # is required to turn it off. if opts[:create_additions].nil? || opts[:create_additions] map_to_rb_obj(obj) else obj end end # Look at an object that's a basic type (from json parse) and convert it # to an instance of Chef classes if desired. def map_to_rb_obj(json_obj) case json_obj when Hash mapped_hash = map_hash_to_rb_obj(json_obj) if json_obj.has_key?(JSON_CLASS) && (class_to_inflate = class_for_json_class(json_obj[JSON_CLASS])) class_to_inflate.json_create(mapped_hash) else mapped_hash end when Array json_obj.map { |e| map_to_rb_obj(e) } else json_obj end end def map_hash_to_rb_obj(json_hash) json_hash.each do |key, value| json_hash[key] = map_to_rb_obj(value) end json_hash end def to_json(obj, opts = nil) begin FFI_Yajl::Encoder.encode(obj, opts) rescue FFI_Yajl::EncodeError => e raise Chef::Exceptions::JSON::EncodeError, e.message end end def to_json_pretty(obj, opts = nil) opts ||= {} options_map = {} options_map[:pretty] = true options_map[:indent] = opts[:indent] if opts.has_key?(:indent) to_json(obj, options_map).chomp end # Map +json_class+ to a Class object. We use a +case+ instead of a Hash # assigned to a constant because otherwise this file could not be loaded # until all the constants were defined, which means you'd have to load # the world to get json, which would make knife very slow. def class_for_json_class(json_class) case json_class when CHEF_APICLIENT Chef::ApiClient when CHEF_CHECKSUM Chef::Checksum when CHEF_COOKBOOKVERSION Chef::CookbookVersion when CHEF_DATABAG Chef::DataBag when CHEF_DATABAGITEM Chef::DataBagItem when CHEF_ENVIRONMENT Chef::Environment when CHEF_NODE Chef::Node when CHEF_ROLE Chef::Role when CHEF_SANDBOX # a falsey return here will disable object inflation/"create # additions" in the caller. In Chef 11 this is correct, we just have # a dummy Chef::Sandbox class for compat with Chef 10 servers. false when CHEF_RESOURCE Chef::Resource when CHEF_RESOURCECOLLECTION Chef::ResourceCollection when CHEF_RESOURCESET Chef::ResourceCollection::ResourceSet when CHEF_RESOURCELIST Chef::ResourceCollection::ResourceList when /^Chef::Resource/ Chef::Resource.find_descendants_by_name(json_class) else raise Chef::Exceptions::JSON::ParseError, "Unsupported `json_class` type '#{json_class}'" end end end end end chef-12.14.60/lib/chef/key.rb000066400000000000000000000231071276456504500154630ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/json_compat" require "chef/mixin/params_validate" require "chef/exceptions" require "chef/server_api" class Chef # Class for interacting with a chef key object. Can be used to create new keys, # save to server, load keys from server, list keys, delete keys, etc. # # @author Tyler Cloke # # @attr [String] actor the name of the client or user that this key is for # @attr [String] name the name of the key # @attr [String] public_key the RSA string of this key # @attr [String] private_key the RSA string of the private key if returned via a POST or PUT # @attr [String] expiration_date the ISO formatted string YYYY-MM-DDTHH:MM:SSZ, i.e. 2020-12-24T21:00:00Z # @attr [String] rest Chef::ServerAPI object, initialized and cached via chef_rest method # @attr [string] api_base either "users" or "clients", initialized and cached via api_base method # # @attr_reader [String] actor_field_name must be either 'client' or 'user' class Key include Chef::Mixin::ParamsValidate attr_reader :actor_field_name def initialize(actor, actor_field_name) # Actor that the key is for, either a client or a user. @actor = actor unless actor_field_name == "user" || actor_field_name == "client" raise Chef::Exceptions::InvalidKeyArgument, "the second argument to initialize must be either 'user' or 'client'" end @actor_field_name = actor_field_name @name = nil @public_key = nil @private_key = nil @expiration_date = nil @create_key = nil end def chef_rest @rest ||= if @actor_field_name == "user" Chef::ServerAPI.new(Chef::Config[:chef_server_root]) else Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end end def api_base @api_base ||= if @actor_field_name == "user" "users" else "clients" end end def actor(arg = nil) set_or_return(:actor, arg, :regex => /^[a-z0-9\-_]+$/) end def name(arg = nil) set_or_return(:name, arg, :kind_of => String) end def public_key(arg = nil) raise Chef::Exceptions::InvalidKeyAttribute, "you cannot set the public_key if create_key is true" if !arg.nil? && @create_key set_or_return(:public_key, arg, :kind_of => String) end def private_key(arg = nil) set_or_return(:private_key, arg, :kind_of => String) end def delete_public_key @public_key = nil end def delete_create_key @create_key = nil end def create_key(arg = nil) raise Chef::Exceptions::InvalidKeyAttribute, "you cannot set create_key to true if the public_key field exists" if arg == true && !@public_key.nil? set_or_return(:create_key, arg, :kind_of => [TrueClass, FalseClass]) end def expiration_date(arg = nil) set_or_return(:expiration_date, arg, :regex => /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|infinity)$/) end def to_hash result = { @actor_field_name => @actor, } result["name"] = @name if @name result["public_key"] = @public_key if @public_key result["private_key"] = @private_key if @private_key result["expiration_date"] = @expiration_date if @expiration_date result["create_key"] = @create_key if @create_key result end def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def create # if public_key is undefined and create_key is false, we cannot create if @public_key.nil? && !@create_key raise Chef::Exceptions::MissingKeyAttribute, "either public_key must be defined or create_key must be true" end # defaults the key name to the fingerprint of the key if @name.nil? # if they didn't pass a public_key, #then they must supply a name because we can't generate a fingerprint unless @public_key.nil? @name = fingerprint else raise Chef::Exceptions::MissingKeyAttribute, "a name cannot be auto-generated if no public key passed, either pass a public key or supply a name" end end payload = { "name" => @name } payload["public_key"] = @public_key unless @public_key.nil? payload["create_key"] = @create_key if @create_key payload["expiration_date"] = @expiration_date unless @expiration_date.nil? result = chef_rest.post("#{api_base}/#{@actor}/keys", payload) # append the private key to the current key if the server returned one, # since the POST endpoint just returns uri and private_key if needed. new_key = self.to_hash new_key["private_key"] = result["private_key"] if result["private_key"] Chef::Key.from_hash(new_key) end def fingerprint self.class.generate_fingerprint(@public_key) end # set @name and pass put_name if you wish to update the name of an existing key put_name to @name def update(put_name = nil) if @name.nil? && put_name.nil? raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated or you must pass a name to update when update is called" end # If no name was passed, fall back to using @name in the PUT URL, otherwise # use the put_name passed. This will update the a key by the name put_name # to @name. put_name = @name if put_name.nil? new_key = chef_rest.put("#{api_base}/#{@actor}/keys/#{put_name}", to_hash) # if the server returned a public_key, remove the create_key field, as we now have a key if new_key["public_key"] self.delete_create_key end Chef::Key.from_hash(self.to_hash.merge(new_key)) end def save create rescue Net::HTTPServerException => e if e.response.code == "409" update else raise e end end def destroy if @name.nil? raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated when delete is called" end chef_rest.delete("#{api_base}/#{@actor}/keys/#{@name}") end class << self def from_hash(key_hash) if key_hash.has_key?("user") key = Chef::Key.new(key_hash["user"], "user") elsif key_hash.has_key?("client") key = Chef::Key.new(key_hash["client"], "client") else raise Chef::Exceptions::MissingKeyAttribute, "The hash passed to from_hash does not contain the key 'user' or 'client'. Please pass a hash that defines one of those keys." end key.name key_hash["name"] if key_hash.key?("name") key.public_key key_hash["public_key"] if key_hash.key?("public_key") key.private_key key_hash["private_key"] if key_hash.key?("private_key") key.create_key key_hash["create_key"] if key_hash.key?("create_key") key.expiration_date key_hash["expiration_date"] if key_hash.key?("expiration_date") key end def from_json(json) Chef::Key.from_hash(Chef::JSONCompat.from_json(json)) end def json_create(json) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::Key#from_json or one of the load_by methods.") Chef::Key.from_json(json) end def list_by_user(actor, inflate = false) keys = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("users/#{actor}/keys") self.list(keys, actor, :load_by_user, inflate) end def list_by_client(actor, inflate = false) keys = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("clients/#{actor}/keys") self.list(keys, actor, :load_by_client, inflate) end def load_by_user(actor, key_name) response = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("users/#{actor}/keys/#{key_name}") Chef::Key.from_hash(response.merge({ "user" => actor })) end def load_by_client(actor, key_name) response = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("clients/#{actor}/keys/#{key_name}") Chef::Key.from_hash(response.merge({ "client" => actor })) end def generate_fingerprint(public_key) openssl_key_object = OpenSSL::PKey::RSA.new(public_key) data_string = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer.new(openssl_key_object.public_key.n), OpenSSL::ASN1::Integer.new(openssl_key_object.public_key.e), ]) OpenSSL::Digest::SHA1.hexdigest(data_string.to_der).scan(/../).join(":") end def list(keys, actor, load_method_symbol, inflate) if inflate keys.inject({}) do |key_map, result| name = result["name"] key_map[name] = Chef::Key.send(load_method_symbol, actor, name) key_map end else keys end end end end end chef-12.14.60/lib/chef/knife.rb000066400000000000000000000511761276456504500157760ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Brown () # Copyright:: Copyright 2009-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 "forwardable" require "chef/version" require "mixlib/cli" require "chef/workstation_config_loader" require "chef/mixin/convert_to_class_name" require "chef/mixin/path_sanity" require "chef/knife/core/subcommand_loader" require "chef/knife/core/ui" require "chef/local_mode" require "chef/server_api" require "chef/http/authenticator" require "chef/http/http_request" require "chef/http" require "pp" class Chef class Knife Chef::HTTP::HTTPRequest.user_agent = "Chef Knife#{Chef::HTTP::HTTPRequest::UA_COMMON}" include Mixlib::CLI include Chef::Mixin::PathSanity extend Chef::Mixin::ConvertToClassName extend Forwardable # Backwards Compat: # Ideally, we should not vomit all of these methods into this base class; # instead, they should be accessed by hitting the ui object directly. def_delegator :@ui, :stdout def_delegator :@ui, :stderr def_delegator :@ui, :stdin def_delegator :@ui, :msg def_delegator :@ui, :ask_question def_delegator :@ui, :pretty_print def_delegator :@ui, :output def_delegator :@ui, :format_list_for_display def_delegator :@ui, :format_for_display def_delegator :@ui, :format_cookbook_list_for_display def_delegator :@ui, :edit_data def_delegator :@ui, :edit_hash def_delegator :@ui, :edit_object def_delegator :@ui, :confirm attr_accessor :name_args attr_accessor :ui # Configure mixlib-cli to always separate defaults from user-supplied CLI options def self.use_separate_defaults? true end def self.ui @ui ||= Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {}) end def self.msg(msg = "") ui.msg(msg) end def self.reset_config_loader! @@chef_config_dir = nil @config_loader = nil end def self.reset_subcommands! @@subcommands = {} @subcommands_by_category = nil end def self.inherited(subclass) unless subclass.unnamed? subcommands[subclass.snake_case_name] = subclass subcommand_files[subclass.snake_case_name] += if subclass.superclass.to_s == "Chef::ChefFS::Knife" # ChefFS-based commands have a superclass that defines an # inhereited method which calls super. This means that the # top of the call stack is not the class definition for # our subcommand. Try the second entry in the call stack. [path_from_caller(caller[1])] else [path_from_caller(caller[0])] end end end # Explicitly set the category for the current command to +new_category+ # The category is normally determined from the first word of the command # name, but some commands make more sense using two or more words # ===Arguments # new_category::: A String to set the category to (see examples) # ===Examples: # Data bag commands would be in the 'data' category by default. To put them # in the 'data bag' category: # category('data bag') def self.category(new_category) @category = new_category end def self.subcommand_category @category || snake_case_name.split("_").first unless unnamed? end def self.snake_case_name convert_to_snake_case(name.split("::").last) unless unnamed? end def self.common_name snake_case_name.split("_").join(" ") end # Does this class have a name? (Classes created via Class.new don't) def self.unnamed? name.nil? || name.empty? end def self.subcommand_loader @subcommand_loader ||= Chef::Knife::SubcommandLoader.for_config(chef_config_dir) end def self.load_commands @commands_loaded ||= subcommand_loader.load_commands end def self.guess_category(args) subcommand_loader.guess_category(args) end def self.subcommand_class_from(args) if args.size == 1 && args[0].strip.casecmp("rehash").zero? # To prevent issues with the rehash file not pointing to the correct plugins, # we always use the glob loader when regenerating the rehash file @subcommand_loader = Chef::Knife::SubcommandLoader.gem_glob_loader(chef_config_dir) end subcommand_loader.command_class_from(args) || subcommand_not_found!(args) end def self.subcommands @@subcommands ||= {} end def self.subcommand_files @@subcommand_files ||= Hash.new([]) end def self.subcommands_by_category unless @subcommands_by_category @subcommands_by_category = Hash.new { |hash, key| hash[key] = [] } subcommands.each do |snake_cased, klass| @subcommands_by_category[klass.subcommand_category] << snake_cased end end @subcommands_by_category end # Shared with subclasses @@chef_config_dir = nil def self.config_loader @config_loader ||= WorkstationConfigLoader.new(nil, Chef::Log) end def self.load_config(explicit_config_file) config_loader.explicit_config_file = explicit_config_file config_loader.load ui.warn("No knife configuration file found") if config_loader.no_config_found? config_loader rescue Exceptions::ConfigurationError => e ui.error(ui.color("CONFIGURATION ERROR:", :red) + e.message) exit 1 end def self.chef_config_dir @@chef_config_dir ||= config_loader.chef_config_dir end # Run knife for the given +args+ (ARGV), adding +options+ to the list of # CLI options that the subcommand knows how to handle. # ===Arguments # args::: usually ARGV # options::: A Mixlib::CLI option parser hash. These +options+ are how # subcommands know about global knife CLI options def self.run(args, options = {}) # Fallback debug logging. Normally the logger isn't configured until we # read the config, but this means any logging that happens before the # config file is read may be lost. If the KNIFE_DEBUG variable is set, we # setup the logger for debug logging to stderr immediately to catch info # from early in the setup process. if ENV["KNIFE_DEBUG"] Chef::Log.init($stderr) Chef::Log.level(:debug) end subcommand_class = subcommand_class_from(args) subcommand_class.options = options.merge!(subcommand_class.options) subcommand_class.load_deps instance = subcommand_class.new(args) instance.configure_chef instance.run_with_pretty_exceptions end def self.dependency_loaders @dependency_loaders ||= [] end def self.deps(&block) dependency_loaders << block end def self.load_deps dependency_loaders.each do |dep_loader| dep_loader.call end end OFFICIAL_PLUGINS = %w{ec2 rackspace windows openstack terremark bluebox} class << self private # @api private def path_from_caller(caller_line) caller_line.split(/:\d+/).first end # :nodoc: # Error out and print usage. probably because the arguments given by the # user could not be resolved to a subcommand. # @api private def subcommand_not_found!(args) ui.fatal("Cannot find subcommand for: '#{args.join(' ')}'") # Mention rehash when the subcommands cache(plugin_manifest.json) is used if subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::HashedCommandLoader) || subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::CustomManifestLoader) ui.info("If this is a recently installed plugin, please run 'knife rehash' to update the subcommands cache.") end if category_commands = guess_category(args) list_commands(category_commands) elsif missing_plugin = ( OFFICIAL_PLUGINS.find { |plugin| plugin == args[0] } ) ui.info("The #{missing_plugin} commands were moved to plugins in Chef 0.10") ui.info("You can install the plugin with `(sudo) gem install knife-#{missing_plugin}`") ui.info("Use `chef gem install knife-#{missing_plugin}` instead if using ChefDK") else list_commands end exit 10 end # @api private def list_commands(preferred_category = nil) category_desc = preferred_category ? preferred_category + " " : "" msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n" subcommand_loader.list_commands(preferred_category).sort.each do |category, commands| next if category =~ /deprecated/i msg "** #{category.upcase} COMMANDS **" commands.sort.each do |command| subcommand_loader.load_command(command) msg subcommands[command].banner if subcommands[command] end msg end end # @api private def reset_config_path! @@chef_config_dir = nil end end reset_config_path! # Create a new instance of the current class configured for the given # arguments and options def initialize(argv = []) super() # having to call super in initialize is the most annoying anti-pattern :( @ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, config) command_name_words = self.class.snake_case_name.split("_") # Mixlib::CLI ignores the embedded name_args @name_args = parse_options(argv) @name_args.delete(command_name_words.join("-")) @name_args.reject! { |name_arg| command_name_words.delete(name_arg) } # knife node run_list add requires that we have extra logic to handle # the case that command name words could be joined by an underscore :/ command_name_words = command_name_words.join("_") @name_args.reject! { |name_arg| command_name_words == name_arg } if config[:help] msg opt_parser exit 1 end # copy Mixlib::CLI over so that it can be configured in knife.rb # config file Chef::Config[:verbosity] = config[:verbosity] if config[:verbosity] end def parse_options(args) super rescue OptionParser::InvalidOption => e puts "Error: " + e.to_s show_usage exit(1) end # keys from mixlib-cli options def cli_keys self.class.options.keys end # extracts the settings from the Chef::Config[:knife] sub-hash that correspond # to knife cli options -- in preparation for merging config values with cli values # # NOTE: due to weirdness in mixlib-config #has_key? is only true if the value has # been set by the user -- the Chef::Config defaults return #has_key?() of false and # this code DEPENDS on that functionality since applying the default values in # Chef::Config[:knife] would break the defaults in the cli that we would otherwise # overwrite. def config_file_settings cli_keys.each_with_object({}) do |key, memo| memo[key] = Chef::Config[:knife][key] if Chef::Config[:knife].has_key?(key) end end # config is merged in this order (inverse of precedence) # default_config - mixlib-cli defaults (accessor from the mixin) # config_file_settings - Chef::Config[:knife] sub-hash # config - mixlib-cli settings (accessor from the mixin) def merge_configs # other code may have a handle to the config object, so use Hash#replace to deliberately # update-in-place. config.replace( default_config.merge(config_file_settings).merge(config) ) end # Catch-all method that does any massaging needed for various config # components, such as expanding file paths and converting verbosity level # into log level. def apply_computed_config Chef::Config[:color] = config[:color] case Chef::Config[:verbosity] when 0, nil Chef::Config[:log_level] = :warn when 1 Chef::Config[:log_level] = :info else Chef::Config[:log_level] = :debug end Chef::Config[:log_level] = :debug if ENV["KNIFE_DEBUG"] Chef::Config[:node_name] = config[:node_name] if config[:node_name] Chef::Config[:client_key] = config[:client_key] if config[:client_key] Chef::Config[:chef_server_url] = config[:chef_server_url] if config[:chef_server_url] Chef::Config[:environment] = config[:environment] if config[:environment] Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode) Chef::Config.listen = config[:listen] if config.has_key?(:listen) if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path) Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd) end Chef::Config.chef_zero.host = config[:chef_zero_host] if config[:chef_zero_host] Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port] # Expand a relative path from the config directory. Config from command # line should already be expanded, and absolute paths will be unchanged. if Chef::Config[:client_key] && config[:config_file] Chef::Config[:client_key] = File.expand_path(Chef::Config[:client_key], File.dirname(config[:config_file])) end Mixlib::Log::Formatter.show_time = false Chef::Log.init(Chef::Config[:log_location]) Chef::Log.level(Chef::Config[:log_level] || :error) end def configure_chef # knife needs to send logger output to STDERR by default Chef::Config[:log_location] = STDERR config_loader = self.class.load_config(config[:config_file]) config[:config_file] = config_loader.config_location merge_configs apply_computed_config Chef::Config.export_proxies # This has to be after apply_computed_config so that Mixlib::Log is configured Chef::Log.info("Using configuration from #{config[:config_file]}") if config[:config_file] end def show_usage stdout.puts("USAGE: " + self.opt_parser.to_s) end def run_with_pretty_exceptions(raise_exception = false) unless self.respond_to?(:run) ui.error "You need to add a #run method to your knife command before you can use it" end enforce_path_sanity maybe_setup_fips Chef::LocalMode.with_server_connectivity do run end rescue Exception => e raise if raise_exception || Chef::Config[:verbosity] == 2 humanize_exception(e) exit 100 end def humanize_exception(e) case e when SystemExit raise # make sure exit passes through. when Net::HTTPServerException, Net::HTTPFatalError humanize_http_exception(e) when OpenSSL::SSL::SSLError ui.error "Could not establish a secure connection to the server." ui.info "Use `knife ssl check` to troubleshoot your SSL configuration." ui.info "If your Chef Server uses a self-signed certificate, you can use" ui.info "`knife ssl fetch` to make knife trust the server's certificates." ui.info "" ui.info "Original Exception: #{e.class.name}: #{e.message}" when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError ui.error "Network Error: #{e.message}" ui.info "Check your knife configuration and network settings" when NameError, NoMethodError ui.error "knife encountered an unexpected error" ui.info "This may be a bug in the '#{self.class.common_name}' knife command or plugin" ui.info "Please collect the output of this command with the `-VV` option before filing a bug report." ui.info "Exception: #{e.class.name}: #{e.message}" when Chef::Exceptions::PrivateKeyMissing ui.error "Your private key could not be loaded from #{api_key}" ui.info "Check your configuration file and ensure that your private key is readable" when Chef::Exceptions::InvalidRedirect ui.error "Invalid Redirect: #{e.message}" ui.info "Change your server location in knife.rb to the server's FQDN to avoid unwanted redirections." else ui.error "#{e.class.name}: #{e.message}" end end def humanize_http_exception(e) response = e.response case response when Net::HTTPUnauthorized ui.error "Failed to authenticate to #{server_url} as #{username} with key #{api_key}" ui.info "Response: #{format_rest_error(response)}" when Net::HTTPForbidden ui.error "You authenticated successfully to #{server_url} as #{username} but you are not authorized for this action" ui.info "Response: #{format_rest_error(response)}" when Net::HTTPBadRequest ui.error "The data in your request was invalid" ui.info "Response: #{format_rest_error(response)}" when Net::HTTPNotFound ui.error "The object you are looking for could not be found" ui.info "Response: #{format_rest_error(response)}" when Net::HTTPInternalServerError ui.error "internal server error" ui.info "Response: #{format_rest_error(response)}" when Net::HTTPBadGateway ui.error "bad gateway" ui.info "Response: #{format_rest_error(response)}" when Net::HTTPServiceUnavailable ui.error "Service temporarily unavailable" ui.info "Response: #{format_rest_error(response)}" when Net::HTTPNotAcceptable version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"]) client_api_version = version_header["request_version"] min_server_version = version_header["min_version"] max_server_version = version_header["max_version"] ui.error "The version of Chef that Knife is using is not supported by the Chef server you sent this request to" ui.info "The request that Knife sent was using API version #{client_api_version}" ui.info "The Chef server you sent the request to supports a min API verson of #{min_server_version} and a max API version of #{max_server_version}" ui.info "Please either update your Chef client or server to be a compatible set" else ui.error response.message ui.info "Response: #{format_rest_error(response)}" end end def username Chef::Config[:node_name] end def api_key Chef::Config[:client_key] end # Parses JSON from the error response sent by Chef Server and returns the # error message #-- # TODO: this code belongs in Chef::REST def format_rest_error(response) Array(Chef::JSONCompat.from_json(response.body)["error"]).join("; ") rescue Exception response.body end # FIXME: yard with @yield def create_object(object, pretty_name = nil, object_class: nil) output = edit_data(object, object_class: object_class) if Kernel.block_given? output = yield(output) else output.save end pretty_name ||= output self.msg("Created #{pretty_name}") output(output) if config[:print_after] end # FIXME: yard with @yield def delete_object(klass, name, delete_name = nil) confirm("Do you really want to delete #{name}") if Kernel.block_given? object = yield else object = klass.load(name) object.destroy end output(format_for_display(object)) if config[:print_after] obj_name = delete_name ? "#{delete_name}[#{name}]" : object self.msg("Deleted #{obj_name}") end # helper method for testing if a field exists # and returning the usage and proper error if not def test_mandatory_field(field, fieldname) if field.nil? show_usage ui.fatal("You must specify a #{fieldname}") exit 1 end end def rest @rest ||= begin require "chef/server_api" Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end end def noauth_rest @rest ||= begin require "chef/http/simple_json" Chef::HTTP::SimpleJSON.new(Chef::Config[:chef_server_url]) end end def server_url Chef::Config[:chef_server_url] end def maybe_setup_fips if !config[:fips].nil? Chef::Config[:fips] = config[:fips] end Chef::Config.init_openssl end end end chef-12.14.60/lib/chef/knife/000077500000000000000000000000001276456504500154375ustar00rootroot00000000000000chef-12.14.60/lib/chef/knife/bootstrap.rb000066400000000000000000000434331276456504500200100ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/data_bag_secret_options" require "erubis" require "chef/knife/bootstrap/chef_vault_handler" require "chef/knife/bootstrap/client_builder" require "chef/util/path_helper" class Chef class Knife class Bootstrap < Knife include DataBagSecretOptions attr_accessor :client_builder attr_accessor :chef_vault_handler deps do require "chef/knife/core/bootstrap_context" require "chef/json_compat" require "tempfile" require "highline" require "net/ssh" require "net/ssh/multi" require "chef/knife/ssh" Chef::Knife::Ssh.load_deps end banner "knife bootstrap [SSH_USER@]FQDN (options)" option :ssh_user, :short => "-x USERNAME", :long => "--ssh-user USERNAME", :description => "The ssh username", :default => "root" option :ssh_password, :short => "-P PASSWORD", :long => "--ssh-password PASSWORD", :description => "The ssh password" option :ssh_port, :short => "-p PORT", :long => "--ssh-port PORT", :description => "The ssh port", :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key } option :ssh_gateway, :short => "-G GATEWAY", :long => "--ssh-gateway GATEWAY", :description => "The ssh gateway", :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key } option :forward_agent, :short => "-A", :long => "--forward-agent", :description => "Enable SSH agent forwarding", :boolean => true option :identity_file, :long => "--identity-file IDENTITY_FILE", :description => "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead." option :ssh_identity_file, :short => "-i IDENTITY_FILE", :long => "--ssh-identity-file IDENTITY_FILE", :description => "The SSH identity file used for authentication" option :chef_node_name, :short => "-N NAME", :long => "--node-name NAME", :description => "The Chef node name for your new node" option :prerelease, :long => "--prerelease", :description => "Install the pre-release chef gems" option :bootstrap_version, :long => "--bootstrap-version VERSION", :description => "The version of Chef to install", :proc => lambda { |v| Chef::Config[:knife][:bootstrap_version] = v } option :bootstrap_proxy, :long => "--bootstrap-proxy PROXY_URL", :description => "The proxy server for the node being bootstrapped", :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p } option :bootstrap_proxy_user, :long => "--bootstrap-proxy-user PROXY_USER", :description => "The proxy authentication username for the node being bootstrapped" option :bootstrap_proxy_pass, :long => "--bootstrap-proxy-pass PROXY_PASS", :description => "The proxy authentication password for the node being bootstrapped" option :bootstrap_no_proxy, :long => "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]", :description => "Do not proxy locations for the node being bootstrapped; this option is used internally by Opscode", :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np } # DEPR: Remove this option in Chef 13 option :distro, :short => "-d DISTRO", :long => "--distro DISTRO", :description => "Bootstrap a distro using a template. [DEPRECATED] Use -t / --bootstrap-template option instead.", :proc => Proc.new { |v| Chef::Log.warn("[DEPRECATED] -d / --distro option is deprecated. Use -t / --bootstrap-template option instead.") v } option :bootstrap_template, :short => "-t TEMPLATE", :long => "--bootstrap-template TEMPLATE", :description => "Bootstrap Chef using a built-in or custom template. Set to the full path of an erb template or use one of the built-in templates." option :use_sudo, :long => "--sudo", :description => "Execute the bootstrap via sudo", :boolean => true option :preserve_home, :long => "--sudo-preserve-home", :description => "Preserve non-root user HOME environment variable with sudo", :boolean => true option :use_sudo_password, :long => "--use-sudo-password", :description => "Execute the bootstrap via sudo with password", :boolean => false # DEPR: Remove this option in Chef 13 option :template_file, :long => "--template-file TEMPLATE", :description => "Full path to location of template to use. [DEPRECATED] Use -t / --bootstrap-template option instead.", :proc => Proc.new { |v| Chef::Log.warn("[DEPRECATED] --template-file option is deprecated. Use -t / --bootstrap-template option instead.") v } option :run_list, :short => "-r RUN_LIST", :long => "--run-list RUN_LIST", :description => "Comma separated list of roles/recipes to apply", :proc => lambda { |o| o.split(/[\s,]+/) }, :default => [] option :policy_name, :long => "--policy-name POLICY_NAME", :description => "Policyfile name to use (--policy-group must also be given)", :default => nil option :policy_group, :long => "--policy-group POLICY_GROUP", :description => "Policy group name to use (--policy-name must also be given)", :default => nil option :tags, :long => "--tags TAGS", :description => "Comma separated list of tags to apply to the node", :proc => lambda { |o| o.split(/[\s,]+/) }, :default => [] option :first_boot_attributes, :short => "-j JSON_ATTRIBS", :long => "--json-attributes", :description => "A JSON string to be added to the first run of chef-client", :proc => lambda { |o| Chef::JSONCompat.parse(o) }, :default => nil option :first_boot_attributes_from_file, :long => "--json-attribute-file FILE", :description => "A JSON file to be used to the first run of chef-client", :proc => lambda { |o| Chef::JSONCompat.parse(File.read(o)) }, :default => nil option :host_key_verify, :long => "--[no-]host-key-verify", :description => "Verify host key, enabled by default.", :boolean => true, :default => true option :hint, :long => "--hint HINT_NAME[=HINT_FILE]", :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.", :proc => Proc.new { |h| Chef::Config[:knife][:hints] ||= Hash.new name, path = h.split("=") Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new } option :bootstrap_url, :long => "--bootstrap-url URL", :description => "URL to a custom installation script", :proc => Proc.new { |u| Chef::Config[:knife][:bootstrap_url] = u } option :bootstrap_install_command, :long => "--bootstrap-install-command COMMANDS", :description => "Custom command to install chef-client", :proc => Proc.new { |ic| Chef::Config[:knife][:bootstrap_install_command] = ic } option :bootstrap_wget_options, :long => "--bootstrap-wget-options OPTIONS", :description => "Add options to wget when installing chef-client", :proc => Proc.new { |wo| Chef::Config[:knife][:bootstrap_wget_options] = wo } option :bootstrap_curl_options, :long => "--bootstrap-curl-options OPTIONS", :description => "Add options to curl when install chef-client", :proc => Proc.new { |co| Chef::Config[:knife][:bootstrap_curl_options] = co } option :node_ssl_verify_mode, :long => "--node-ssl-verify-mode [peer|none]", :description => "Whether or not to verify the SSL cert for all HTTPS requests.", :proc => Proc.new { |v| valid_values = %w{none peer} unless valid_values.include?(v) raise "Invalid value '#{v}' for --node-ssl-verify-mode. Valid values are: #{valid_values.join(", ")}" end v } option :node_verify_api_cert, :long => "--[no-]node-verify-api-cert", :description => "Verify the SSL cert for HTTPS requests to the Chef server API.", :boolean => true option :bootstrap_vault_file, :long => "--bootstrap-vault-file VAULT_FILE", :description => "A JSON file with a list of vault(s) and item(s) to be updated" option :bootstrap_vault_json, :long => "--bootstrap-vault-json VAULT_JSON", :description => "A JSON string with the vault(s) and item(s) to be updated" option :bootstrap_vault_item, :long => "--bootstrap-vault-item VAULT_ITEM", :description => 'A single vault and item to update as "vault:item"', :proc => Proc.new { |i| (vault, item) = i.split(/:/) Chef::Config[:knife][:bootstrap_vault_item] ||= {} Chef::Config[:knife][:bootstrap_vault_item][vault] ||= [] Chef::Config[:knife][:bootstrap_vault_item][vault].push(item) Chef::Config[:knife][:bootstrap_vault_item] } def initialize(argv = []) super @client_builder = Chef::Knife::Bootstrap::ClientBuilder.new( chef_config: Chef::Config, knife_config: config, ui: ui ) @chef_vault_handler = Chef::Knife::Bootstrap::ChefVaultHandler.new( knife_config: config, ui: ui ) end # The default bootstrap template to use to bootstrap a server This is a public API hook # which knife plugins use or inherit and override. # # @return [String] Default bootstrap template def default_bootstrap_template "chef-full" end def host_descriptor Array(@name_args).first end # The server_name is the DNS or IP we are going to connect to, it is not necessarily # the node name, the fqdn, or the hostname of the server. This is a public API hook # which knife plugins use or inherit and override. # # @return [String] The DNS or IP that bootstrap will connect to def server_name if host_descriptor @server_name ||= host_descriptor.split("@").reverse[0] end end def user_name if host_descriptor @user_name ||= host_descriptor.split("@").reverse[1] end end def bootstrap_template # The order here is important. We want to check if we have the new Chef 12 option is set first. # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at # the end. config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template end def find_template template = bootstrap_template # Use the template directly if it's a path to an actual file if File.exists?(template) Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}") return template end # Otherwise search the template directories until we find the right one bootstrap_files = [] bootstrap_files << File.join(File.dirname(__FILE__), "bootstrap/templates", "#{template}.erb") bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir Chef::Util::PathHelper.home(".chef", "bootstrap", "#{template}.erb") { |p| bootstrap_files << p } bootstrap_files << Gem.find_files(File.join("chef", "knife", "bootstrap", "#{template}.erb")) bootstrap_files.flatten! template_file = Array(bootstrap_files).find do |bootstrap_template| Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}") File.exists?(bootstrap_template) end unless template_file ui.info("Can not find bootstrap definition for #{template}") raise Errno::ENOENT end Chef::Log.debug("Found bootstrap template in #{File.dirname(template_file)}") template_file end def secret @secret ||= encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil end def bootstrap_context @bootstrap_context ||= Knife::Core::BootstrapContext.new( config, config[:run_list], Chef::Config, secret ) end def first_boot_attributes @config[:first_boot_attributes] || @config[:first_boot_attributes_from_file] || {} end def render_template @config[:first_boot_attributes] = first_boot_attributes template_file = find_template template = IO.read(template_file).chomp Erubis::Eruby.new(template).evaluate(bootstrap_context) end def run if @config[:first_boot_attributes] && @config[:first_boot_attributes_from_file] raise Chef::Exceptions::BootstrapCommandInputError end validate_name_args! validate_options! $stdout.sync = true # chef-vault integration must use the new client-side hawtness, otherwise to use the # new client-side hawtness, just delete your validation key. if chef_vault_handler.doing_chef_vault? || (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key]))) unless config[:chef_node_name] ui.error("You must pass a node name with -N when bootstrapping with user credentials") exit 1 end client_builder.run chef_vault_handler.run(client_builder.client) bootstrap_context.client_pem = client_builder.client_path else ui.info("Doing old-style registration with the validation key at #{Chef::Config[:validation_key]}...") ui.info("Delete your validation key in order to use your user credentials instead") ui.info("") end ui.info("Connecting to #{ui.color(server_name, :bold)}") begin knife_ssh.run rescue Net::SSH::AuthenticationFailed if config[:ssh_password] raise else ui.info("Failed to authenticate #{knife_ssh.config[:ssh_user]} - trying password auth") knife_ssh_with_password_auth.run end end end def validate_name_args! if server_name.nil? ui.error("Must pass an FQDN or ip to bootstrap") exit 1 elsif server_name == "windows" # catches "knife bootstrap windows" when that command is not installed ui.warn("Hostname containing 'windows' specified. Please install 'knife-windows' if you are attempting to bootstrap a Windows node via WinRM.") end end def validate_options! if incomplete_policyfile_options? ui.error("--policy-name and --policy-group must be specified together") exit 1 elsif policyfile_and_run_list_given? ui.error("Policyfile options and --run-list are exclusive") exit 1 end true end def knife_ssh ssh = Chef::Knife::Ssh.new ssh.ui = ui ssh.name_args = [ server_name, ssh_command ] ssh.config[:ssh_user] = user_name || config[:ssh_user] ssh.config[:ssh_password] = config[:ssh_password] ssh.config[:ssh_port] = config[:ssh_port] ssh.config[:ssh_gateway] = config[:ssh_gateway] ssh.config[:forward_agent] = config[:forward_agent] ssh.config[:ssh_identity_file] = config[:ssh_identity_file] || config[:identity_file] ssh.config[:manual] = true ssh.config[:host_key_verify] = config[:host_key_verify] ssh.config[:on_error] = :raise ssh end def knife_ssh_with_password_auth ssh = knife_ssh ssh.config[:ssh_identity_file] = nil ssh.config[:ssh_password] = ssh.get_password ssh end def ssh_command command = render_template if config[:use_sudo] sudo_prefix = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -S " : "sudo " command = config[:preserve_home] ? "#{sudo_prefix} #{command}" : "#{sudo_prefix} -H #{command}" end command end private # True if policy_name and run_list are both given def policyfile_and_run_list_given? run_list_given? && policyfile_options_given? end def run_list_given? !config[:run_list].nil? && !config[:run_list].empty? end def policyfile_options_given? !!config[:policy_name] end # True if one of policy_name or policy_group was given, but not both def incomplete_policyfile_options? (!!config[:policy_name] ^ config[:policy_group]) end end end end chef-12.14.60/lib/chef/knife/bootstrap/000077500000000000000000000000001276456504500174545ustar00rootroot00000000000000chef-12.14.60/lib/chef/knife/bootstrap/chef_vault_handler.rb000066400000000000000000000123041276456504500236160ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife/bootstrap" class Chef class Knife class Bootstrap < Knife class ChefVaultHandler # @return [Hash] knife merged config, typically @config attr_accessor :knife_config # @return [Chef::Knife::UI] ui object for output attr_accessor :ui # @return [Chef::ApiClient] vault client attr_reader :client # @param knife_config [Hash] knife merged config, typically @config # @param ui [Chef::Knife::UI] ui object for output def initialize(knife_config: {}, ui: nil) @knife_config = knife_config @ui = ui end # Updates the chef vault items for the newly created client. # # @param client [Chef::ApiClient] vault client def run(client) return unless doing_chef_vault? sanity_check @client = client update_bootstrap_vault_json! end # Iterate through all the vault items to update. Items may be either a String # or an Array of Strings: # # { # "vault1": "item", # "vault2": [ "item1", "item2", "item2" ] # } # def update_bootstrap_vault_json! vault_json.each do |vault, items| [ items ].flatten.each do |item| update_vault(vault, item) end end end # @return [Boolean] if we've got chef vault options to act on or not def doing_chef_vault? !!(bootstrap_vault_json || bootstrap_vault_file || bootstrap_vault_item) end private # warn if the user has given mutual conflicting options def sanity_check if bootstrap_vault_item && (bootstrap_vault_json || bootstrap_vault_file) ui.warn "--vault-item given with --vault-list or --vault-file, ignoring the latter" end if bootstrap_vault_json && bootstrap_vault_file ui.warn "--vault-list given with --vault-file, ignoring the latter" end end # @return [String] string with serialized JSON representing the chef vault items def bootstrap_vault_json knife_config[:bootstrap_vault_json] end # @return [String] JSON text in a file representing the chef vault items def bootstrap_vault_file knife_config[:bootstrap_vault_file] end # @return [Hash] Ruby object representing the chef vault items to create def bootstrap_vault_item knife_config[:bootstrap_vault_item] end # Helper to return a ruby object represeting all the data bags and items # to update via chef-vault. # # @return [Hash] deserialized ruby hash with all the vault items def vault_json @vault_json ||= begin if bootstrap_vault_item bootstrap_vault_item else json = bootstrap_vault_json ? bootstrap_vault_json : File.read(bootstrap_vault_file) Chef::JSONCompat.from_json(json) end end end # Update an individual vault item and save it # # @param vault [String] name of the chef-vault encrypted data bag # @param item [String] name of the chef-vault encrypted item def update_vault(vault, item) require_chef_vault! bootstrap_vault_item = load_chef_bootstrap_vault_item(vault, item) bootstrap_vault_item.clients(client) bootstrap_vault_item.save end # Hook to stub out ChefVault # # @param vault [String] name of the chef-vault encrypted data bag # @param item [String] name of the chef-vault encrypted item # @returns [ChefVault::Item] ChefVault::Item object def load_chef_bootstrap_vault_item(vault, item) ChefVault::Item.load(vault, item) end public :load_chef_bootstrap_vault_item # for stubbing # Helper to very lazily require the chef-vault gem def require_chef_vault! @require_chef_vault ||= begin error_message = "Knife bootstrap requires version 2.6.0 or higher of the chef-vault gem to configure chef vault items" require "chef-vault" if Gem::Version.new(ChefVault::VERSION) < Gem::Version.new("2.6.0") raise error_message end true rescue LoadError raise error_message end end end end end end chef-12.14.60/lib/chef/knife/bootstrap/client_builder.rb000066400000000000000000000163121276456504500227700ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/node" require "chef/server_api" require "chef/api_client/registration" require "chef/api_client" require "chef/knife/bootstrap" require "tmpdir" class Chef class Knife class Bootstrap < Knife class ClientBuilder # @return [Hash] knife merged config, typically @config attr_accessor :knife_config # @return [Hash] chef config object attr_accessor :chef_config # @return [Chef::Knife::UI] ui object for output attr_accessor :ui # @return [Chef::ApiClient] client saved on run attr_reader :client # @param knife_config [Hash] Hash of knife config settings # @param chef_config [Hash] Hash of chef config settings # @param ui [Chef::Knife::UI] UI object for output def initialize(knife_config: {}, chef_config: {}, ui: nil) @knife_config = knife_config @chef_config = chef_config @ui = ui end # Main entry. Prompt the user to clean up any old client or node objects. Then create # the new client, then create the new node. def run sanity_check ui.info("Creating new client for #{node_name}") @client = create_client! ui.info("Creating new node for #{node_name}") create_node! end # Tempfile to use to write newly created client credentials to. # # This method is public so that the knife bootstrapper can read then and pass the value into # the handler for chef vault which needs the client cert we create here. # # We hang onto the tmpdir as an ivar as well so that it will not get GC'd and removed # # @return [String] path to the generated client.pem def client_path @client_path ||= begin @tmpdir = Dir.mktmpdir File.join(@tmpdir, "#{node_name}.pem") end end private # @return [String] node name from the knife_config def node_name knife_config[:chef_node_name] end # @return [String] enviroment from the knife_config def environment knife_config[:environment] end # @return [String] run_list from the knife_config def run_list knife_config[:run_list] end # @return [String] policy_name from the knife_config def policy_name knife_config[:policy_name] end # @return [String] policy_group from the knife_config def policy_group knife_config[:policy_group] end # @return [Hash,Array] Object representation of json first-boot attributes from the knife_config def first_boot_attributes knife_config[:first_boot_attributes] end # @return [String] chef server url from the Chef::Config def chef_server_url chef_config[:chef_server_url] end # Accesses the run_list and coerces it into an Array, changing nils into # the empty Array, and splitting strings representations of run_lists into # Arrays. # # @return [Array] run_list coerced into an array def normalized_run_list case run_list when nil [] when String run_list.split(/\s*,\s*/) when Array run_list end end # Create the client object and save it to the Chef API def create_client! Chef::ApiClient::Registration.new(node_name, client_path, http_api: rest).run end # Create the node object (via the lazy accessor) and save it to the Chef API def create_node! node.save end # Create a new Chef::Node. Supports creating the node with its name, run_list, attributes # and environment. This injects a rest object into the Chef::Node which uses the client key # for authentication so that the client creates the node and therefore we get the acls setup # correctly. # # @return [Chef::Node] new chef node to create def node @node ||= begin node = Chef::Node.new(chef_server_rest: client_rest) node.name(node_name) node.run_list(normalized_run_list) node.normal_attrs = first_boot_attributes if first_boot_attributes node.environment(environment) if environment node.policy_name = policy_name if policy_name node.policy_group = policy_group if policy_group (knife_config[:tags] || []).each do |tag| node.tags << tag end node end end # Check for the existence of a node and/or client already on the server. If the node # already exists, we must delete it in order to proceed so that we can create a new node # object with the permissions of the new client. There is a use case for creating a new # client and wiring it up to a precreated node object, but we do currently support that. # # We prompt the user about what to do and will fail hard if we do not get confirmation to # delete any prior node/client objects. def sanity_check if resource_exists?("nodes/#{node_name}") ui.confirm("Node #{node_name} exists, overwrite it") rest.delete("nodes/#{node_name}") end if resource_exists?("clients/#{node_name}") ui.confirm("Client #{node_name} exists, overwrite it") rest.delete("clients/#{node_name}") end end # Check if an relative path exists on the chef server # # @param relative_path [String] URI path relative to the chef organization # @return [Boolean] if the relative path exists or returns a 404 def resource_exists?(relative_path) rest.get(relative_path) true rescue Net::HTTPServerException => e raise unless e.response.code == "404" false end # @return [Chef::ServerAPI] REST client using the client credentials def client_rest @client_rest ||= Chef::ServerAPI.new(chef_server_url, :client_name => node_name, :signing_key_filename => client_path) end # @return [Chef::ServerAPI] REST client using the cli user's knife credentials # this uses the users's credentials def rest @rest ||= Chef::ServerAPI.new(chef_server_url) end end end end end chef-12.14.60/lib/chef/knife/bootstrap/templates/000077500000000000000000000000001276456504500214525ustar00rootroot00000000000000chef-12.14.60/lib/chef/knife/bootstrap/templates/README.md000066400000000000000000000013321276456504500227300ustar00rootroot00000000000000This directory contains bootstrap templates which can be used with the -d flag to 'knife bootstrap' to install Chef in different ways. To simplify installation, and reduce the matrix of common installation patterns to support, we have standardized on the [Omnibus](https://github.com/chef/omnibus) built installation packages. The 'chef-full' template downloads a script which is used to determine the correct Omnibus package for this system from the [Omnitruck](https://docs.chef.io/api_omnitruck.html) API. You can still utilize custom bootstrap templates on your system if your installation needs are unique. Additional information can be found on the [docs site](https://docs.chef.io/knife_bootstrap.html#custom-templates). chef-12.14.60/lib/chef/knife/bootstrap/templates/chef-full.erb000066400000000000000000000126271276456504500240210ustar00rootroot00000000000000sh -c ' <%= "export https_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%> if test "x$TMPDIR" = "x"; then tmp="/tmp" else tmp=$TMPDIR fi # secure-ish temp dir creation without having mktemp available (DDoS-able but not exploitable) tmp_dir="$tmp/install.sh.$$" (umask 077 && mkdir $tmp_dir) || exit 1 exists() { if command -v $1 >/dev/null 2>&1 then return 0 else return 1 fi } http_404_error() { echo "ERROR 404: Could not retrieve a valid install.sh!" exit 1 } capture_tmp_stderr() { # spool up /tmp/stderr from all the commands we called if test -f "$tmp_dir/stderr"; then output=`cat $tmp_dir/stderr` stderr_results="${stderr_results}\nSTDERR from $1:\n\n$output\n" rm $tmp_dir/stderr fi } # do_wget URL FILENAME do_wget() { echo "trying wget..." wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> -O "$2" "$1" 2>$tmp_dir/stderr rc=$? # check for 404 grep "ERROR 404" $tmp_dir/stderr 2>&1 >/dev/null if test $? -eq 0; then http_404_error fi # check for bad return status or empty output if test $rc -ne 0 || test ! -s "$2"; then capture_tmp_stderr "wget" return 1 fi return 0 } # do_curl URL FILENAME do_curl() { echo "trying curl..." curl -sL <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> -D $tmp_dir/stderr -o "$2" "$1" 2>$tmp_dir/stderr rc=$? # check for 404 grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null if test $? -eq 0; then http_404_error fi # check for bad return status or empty output if test $rc -ne 0 || test ! -s "$2"; then capture_tmp_stderr "curl" return 1 fi return 0 } # do_fetch URL FILENAME do_fetch() { echo "trying fetch..." fetch -o "$2" "$1" 2>$tmp_dir/stderr # check for bad return status test $? -ne 0 && return 1 return 0 } # do_perl URL FILENAME do_perl() { echo "trying perl..." perl -e "use LWP::Simple; getprint(shift @ARGV);" "$1" > "$2" 2>$tmp_dir/stderr rc=$? # check for 404 grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null if test $? -eq 0; then http_404_error fi # check for bad return status or empty output if test $rc -ne 0 || test ! -s "$2"; then capture_tmp_stderr "perl" return 1 fi return 0 } # do_python URL FILENAME do_python() { echo "trying python..." python -c "import sys,urllib2 ; sys.stdout.write(urllib2.urlopen(sys.argv[1]).read())" "$1" > "$2" 2>$tmp_dir/stderr rc=$? # check for 404 grep "HTTP Error 404" $tmp_dir/stderr 2>&1 >/dev/null if test $? -eq 0; then http_404_error fi # check for bad return status or empty output if test $rc -ne 0 || test ! -s "$2"; then capture_tmp_stderr "python" return 1 fi return 0 } # do_download URL FILENAME do_download() { PATH=/opt/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sfw/bin:/sbin:/bin:/usr/sbin:/usr/bin export PATH echo "downloading $1" echo " to file $2" # we try all of these until we get success. # perl, in particular may be present but LWP::Simple may not be installed if exists wget; then do_wget $1 $2 && return 0 fi if exists curl; then do_curl $1 $2 && return 0 fi if exists fetch; then do_fetch $1 $2 && return 0 fi if exists perl; then do_perl $1 $2 && return 0 fi if exists python; then do_python $1 $2 && return 0 fi echo ">>>>>> wget, curl, fetch, perl, or python not found on this instance." if test "x$stderr_results" != "x"; then echo "\nDEBUG OUTPUT FOLLOWS:\n$stderr_results" fi return 16 } <% if knife_config[:bootstrap_install_command] %> <%= knife_config[:bootstrap_install_command] %> <% else %> install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://omnitruck-direct.chef.io/chef/install.sh" %>" if test -f /usr/bin/chef-client; then echo "-----> Existing Chef installation detected" else echo "-----> Installing Chef Omnibus (<%= latest_current_chef_version_string %>)" do_download ${install_sh} $tmp_dir/install.sh sh $tmp_dir/install.sh -P chef <%= latest_current_chef_version_string %> fi <% end %> if test "x$tmp_dir" != "x"; then rm -r "$tmp_dir" fi mkdir -p /etc/chef <% if client_pem -%> cat > /etc/chef/client.pem < EOP chmod 0600 /etc/chef/client.pem <% end -%> <% if validation_key -%> cat > /etc/chef/validation.pem < EOP chmod 0600 /etc/chef/validation.pem <% end -%> <% if encrypted_data_bag_secret -%> cat > /etc/chef/encrypted_data_bag_secret < EOP chmod 0600 /etc/chef/encrypted_data_bag_secret <% end -%> <% unless trusted_certs.empty? -%> mkdir -p /etc/chef/trusted_certs <%= trusted_certs %> <% end -%> <%# Generate Ohai Hints -%> <% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> mkdir -p /etc/chef/ohai/hints <% @chef_config[:knife][:hints].each do |name, hash| -%> cat > /etc/chef/ohai/hints/<%= name %>.json < EOP <% end -%> <% end -%> cat > /etc/chef/client.rb < EOP cat > /etc/chef/first-boot.json < EOP <% unless client_d.empty? -%> mkdir -p /etc/chef/client.d <%= client_d %> <% end -%> echo "Starting the first Chef Client run..." <%= start_chef %>' chef-12.14.60/lib/chef/knife/client_bulk_delete.rb000066400000000000000000000062231276456504500216040ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class ClientBulkDelete < Knife deps do require "chef/api_client_v1" require "chef/json_compat" end option :delete_validators, :short => "-D", :long => "--delete-validators", :description => "Force deletion of clients if they're validators" banner "knife client bulk delete REGEX (options)" def run if name_args.length < 1 ui.fatal("You must supply a regular expression to match the results against") exit 42 end all_clients = Chef::ApiClientV1.list(true) matcher = /#{name_args[0]}/ clients_to_delete = {} validators_to_delete = {} all_clients.each do |name, client| next unless name =~ matcher if client.validator validators_to_delete[client.name] = client else clients_to_delete[client.name] = client end end if clients_to_delete.empty? && validators_to_delete.empty? ui.info "No clients match the expression /#{name_args[0]}/" exit 0 end check_and_delete_validators(validators_to_delete) check_and_delete_clients(clients_to_delete) end def check_and_delete_validators(validators) unless validators.empty? unless config[:delete_validators] ui.msg("The following clients are validators and will not be deleted:") print_clients(validators) ui.msg("You must specify --delete-validators to delete the validator clients") else ui.msg("The following validators will be deleted:") print_clients(validators) if ui.confirm_without_exit("Are you sure you want to delete these validators") destroy_clients(validators) end end end end def check_and_delete_clients(clients) unless clients.empty? ui.msg("The following clients will be deleted:") print_clients(clients) ui.confirm("Are you sure you want to delete these clients") destroy_clients(clients) end end def destroy_clients(clients) clients.sort.each do |name, client| client.destroy ui.msg("Deleted client #{name}") end end def print_clients(clients) ui.msg("") ui.msg(ui.list(clients.keys.sort, :columns_down)) ui.msg("") end end end end chef-12.14.60/lib/chef/knife/client_create.rb000066400000000000000000000063731276456504500205760ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class ClientCreate < Knife deps do require "chef/api_client_v1" require "chef/json_compat" end option :file, :short => "-f FILE", :long => "--file FILE", :description => "Write the private key to a file if the server generated one." option :admin, :short => "-a", :long => "--admin", :description => "Open Source Chef Server 11 only. Create the client as an admin.", :boolean => true option :validator, :long => "--validator", :description => "Create the client as a validator.", :boolean => true option :public_key, :short => "-p FILE", :long => "--public-key", :description => "Set the initial default key for the client from a file on disk (cannot pass with --prevent-keygen)." option :prevent_keygen, :short => "-k", :long => "--prevent-keygen", :description => "API V1 (Chef Server 12.1+) only. Prevent server from generating a default key pair for you. Cannot be passed with --public-key.", :boolean => true banner "knife client create CLIENTNAME (options)" def client @client_field ||= Chef::ApiClientV1.new end def create_client(client) # should not be using save :( bad behavior Chef::ApiClientV1.from_hash(client).save end def run test_mandatory_field(@name_args[0], "client name") client.name @name_args[0] if config[:public_key] && config[:prevent_keygen] show_usage ui.fatal("You cannot pass --public-key and --prevent-keygen") exit 1 end if !config[:prevent_keygen] && !config[:public_key] client.create_key(true) end if config[:admin] client.admin(true) end if config[:validator] client.validator(true) end if config[:public_key] client.public_key File.read(File.expand_path(config[:public_key])) end output = edit_hash(client) final_client = create_client(output) ui.info("Created #{final_client}") # output private_key if one if final_client.private_key if config[:file] File.open(config[:file], "w") do |f| f.print(final_client.private_key) end else puts final_client.private_key end end end end end end chef-12.14.60/lib/chef/knife/client_delete.rb000066400000000000000000000032331276456504500205650ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class ClientDelete < Knife deps do require "chef/api_client_v1" require "chef/json_compat" end option :delete_validators, :short => "-D", :long => "--delete-validators", :description => "Force deletion of client if it's a validator" banner "knife client delete CLIENT (options)" def run @client_name = @name_args[0] if @client_name.nil? show_usage ui.fatal("You must specify a client name") exit 1 end delete_object(Chef::ApiClientV1, @client_name, "client") do object = Chef::ApiClientV1.load(@client_name) if object.validator unless config[:delete_validators] ui.fatal("You must specify --delete-validators to delete the validator client #{@client_name}") exit 2 end end object.destroy end end end end end chef-12.14.60/lib/chef/knife/client_edit.rb000066400000000000000000000027061276456504500202540ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class ClientEdit < Knife deps do require "chef/api_client_v1" require "chef/json_compat" end banner "knife client edit CLIENT (options)" def run @client_name = @name_args[0] if @client_name.nil? show_usage ui.fatal("You must specify a client name") exit 1 end original_data = Chef::ApiClientV1.load(@client_name).to_hash edited_client = edit_hash(original_data) if original_data != edited_client client = Chef::ApiClientV1.from_hash(edited_client) client.save ui.msg("Saved #{client}.") else ui.msg("Client unchanged, not saving.") end end end end end chef-12.14.60/lib/chef/knife/client_key_create.rb000066400000000000000000000032751276456504500214440ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/key_create_base" class Chef class Knife # Implements knife user key create using Chef::Knife::KeyCreate # as a service class. # # @author Tyler Cloke # # @attr_reader [String] actor the name of the client that this key is for class ClientKeyCreate < Knife include Chef::Knife::KeyCreateBase attr_reader :actor def initialize(argv = []) super(argv) @service_object = nil end def run apply_params!(@name_args) service_object.run end def actor_field_name "client" end def service_object @service_object ||= Chef::Knife::KeyCreate.new(@actor, actor_field_name, ui, config) end def actor_missing_error "You must specify a client name" end def apply_params!(params) @actor = params[0] if @actor.nil? show_usage ui.fatal(actor_missing_error) exit 1 end end end end end chef-12.14.60/lib/chef/knife/client_key_delete.rb000066400000000000000000000036151276456504500214410ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife # Implements knife client key delete using Chef::Knife::KeyDelete # as a service class. # # @author Tyler Cloke # # @attr_reader [String] actor the name of the client that this key is for class ClientKeyDelete < Knife banner "knife client key delete CLIENT KEYNAME (options)" attr_reader :actor def initialize(argv = []) super(argv) @service_object = nil end def run apply_params!(@name_args) service_object.run end def actor_field_name "client" end def actor_missing_error "You must specify a client name" end def keyname_missing_error "You must specify a key name" end def service_object @service_object ||= Chef::Knife::KeyDelete.new(@name, @actor, actor_field_name, ui) end def apply_params!(params) @actor = params[0] if @actor.nil? show_usage ui.fatal(actor_missing_error) exit 1 end @name = params[1] if @name.nil? show_usage ui.fatal(keyname_missing_error) exit 1 end end end end end chef-12.14.60/lib/chef/knife/client_key_edit.rb000066400000000000000000000037261276456504500211270ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/key_edit_base" class Chef class Knife # Implements knife client key edit using Chef::Knife::KeyEdit # as a service class. # # @author Tyler Cloke # # @attr_reader [String] actor the name of the client that this key is for class ClientKeyEdit < Knife include Chef::Knife::KeyEditBase banner "knife client key edit CLIENT KEYNAME (options)" attr_reader :actor def initialize(argv = []) super(argv) @service_object = nil end def run apply_params!(@name_args) service_object.run end def actor_field_name "client" end def service_object @service_object ||= Chef::Knife::KeyEdit.new(@name, @actor, actor_field_name, ui, config) end def actor_missing_error "You must specify a client name" end def keyname_missing_error "You must specify a key name" end def apply_params!(params) @actor = params[0] if @actor.nil? show_usage ui.fatal(actor_missing_error) exit 1 end @name = params[1] if @name.nil? show_usage ui.fatal(keyname_missing_error) exit 1 end end end end end chef-12.14.60/lib/chef/knife/client_key_list.rb000066400000000000000000000033451276456504500211520ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/key_list_base" class Chef class Knife # Implements knife user key list using Chef::Knife::KeyList # as a service class. # # @author Tyler Cloke # # @attr_reader [String] actor the name of the client that this key is for class ClientKeyList < Knife include Chef::Knife::KeyListBase banner "knife client key list CLIENT (options)" attr_reader :actor def initialize(argv = []) super(argv) @service_object = nil end def run apply_params!(@name_args) service_object.run end def list_method :list_by_client end def actor_missing_error "You must specify a client name" end def service_object @service_object ||= Chef::Knife::KeyList.new(@actor, list_method, ui, config) end def apply_params!(params) @actor = params[0] if @actor.nil? show_usage ui.fatal(actor_missing_error) exit 1 end end end end end chef-12.14.60/lib/chef/knife/client_key_show.rb000066400000000000000000000036001276456504500211510ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife # Implements knife client key show using Chef::Knife::KeyShow # as a service class. # # @author Tyler Cloke # # @attr_reader [String] actor the name of the client that this key is for class ClientKeyShow < Knife banner "knife client key show CLIENT KEYNAME (options)" attr_reader :actor def initialize(argv = []) super(argv) @service_object = nil end def run apply_params!(@name_args) service_object.run end def load_method :load_by_client end def actor_missing_error "You must specify a client name" end def keyname_missing_error "You must specify a key name" end def service_object @service_object ||= Chef::Knife::KeyShow.new(@name, @actor, load_method, ui) end def apply_params!(params) @actor = params[0] if @actor.nil? show_usage ui.fatal(actor_missing_error) exit 1 end @name = params[1] if @name.nil? show_usage ui.fatal(keyname_missing_error) exit 1 end end end end end chef-12.14.60/lib/chef/knife/client_list.rb000066400000000000000000000021621276456504500202760ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class ClientList < Knife deps do require "chef/api_client_v1" require "chef/json_compat" end banner "knife client list (options)" option :with_uri, :short => "-w", :long => "--with-uri", :description => "Show corresponding URIs" def run output(format_list_for_display(Chef::ApiClientV1.list)) end end end end chef-12.14.60/lib/chef/knife/client_reregister.rb000066400000000000000000000030571276456504500215020ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class ClientReregister < Knife deps do require "chef/api_client_v1" require "chef/json_compat" end banner "knife client reregister CLIENT (options)" option :file, :short => "-f FILE", :long => "--file FILE", :description => "Write the key to a file" def run @client_name = @name_args[0] if @client_name.nil? show_usage ui.fatal("You must specify a client name") exit 1 end client = Chef::ApiClientV1.reregister(@client_name) Chef::Log.debug("Updated client data: #{client.inspect}") key = client.private_key if config[:file] File.open(config[:file], "w") do |f| f.print(key) end else ui.msg key end end end end end chef-12.14.60/lib/chef/knife/client_show.rb000066400000000000000000000023731276456504500203070ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class ClientShow < Knife include Knife::Core::MultiAttributeReturnOption deps do require "chef/api_client_v1" require "chef/json_compat" end banner "knife client show CLIENT (options)" def run @client_name = @name_args[0] if @client_name.nil? show_usage ui.fatal("You must specify a client name") exit 1 end client = Chef::ApiClientV1.load(@client_name) output(format_for_display(client)) end end end end chef-12.14.60/lib/chef/knife/configure.rb000066400000000000000000000154561276456504500177600ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class Configure < Knife attr_reader :chef_server, :new_client_name, :admin_client_name, :admin_client_key attr_reader :chef_repo, :new_client_key, :validation_client_name, :validation_key deps do require "ohai" Chef::Knife::ClientCreate.load_deps Chef::Knife::UserCreate.load_deps end banner "knife configure (options)" option :repository, :short => "-r REPO", :long => "--repository REPO", :description => "The path to the chef-repo" option :initial, :short => "-i", :long => "--initial", :boolean => true, :description => "Use to create a API client, typically an administrator client on a freshly-installed server" option :admin_client_name, :long => "--admin-client-name NAME", :description => "The name of the client, typically the name of the admin client" option :admin_client_key, :long => "--admin-client-key PATH", :description => "The path to the private key used by the client, typically a file named admin.pem" option :validation_client_name, :long => "--validation-client-name NAME", :description => "The name of the validation client, typically a client named chef-validator" option :validation_key, :long => "--validation-key PATH", :description => "The path to the validation key used by the client, typically a file named validation.pem" def configure_chef # We are just faking out the system so that you can do this without a key specified Chef::Config[:node_name] = "woot" super Chef::Config[:node_name] = nil end def run ask_user_for_config_path FileUtils.mkdir_p(chef_config_path) ask_user_for_config ::File.open(config[:config_file], "w") do |f| f.puts <<-EOH log_level :info log_location STDOUT node_name '#{new_client_name}' client_key '#{new_client_key}' validation_client_name '#{validation_client_name}' validation_key '#{validation_key}' chef_server_url '#{chef_server}' syntax_check_cache_path '#{File.join(chef_config_path, "syntax_check_cache")}' EOH unless chef_repo.empty? f.puts "cookbook_path [ '#{chef_repo}/cookbooks' ]" end end if config[:initial] ui.msg("Creating initial API user...") Chef::Config[:chef_server_url] = chef_server Chef::Config[:node_name] = admin_client_name Chef::Config[:client_key] = admin_client_key user_create = Chef::Knife::UserCreate.new user_create.name_args = [ new_client_name ] user_create.config[:user_password] = config[:user_password] || ui.ask("Please enter a password for the new user: ") { |q| q.echo = false } user_create.config[:admin] = true user_create.config[:file] = new_client_key user_create.config[:yes] = true user_create.config[:disable_editing] = true user_create.run else ui.msg("*****") ui.msg("") ui.msg("You must place your client key in:") ui.msg(" #{new_client_key}") ui.msg("Before running commands with Knife") ui.msg("") ui.msg("*****") ui.msg("") ui.msg("You must place your validation key in:") ui.msg(" #{validation_key}") ui.msg("Before generating instance data with Knife") ui.msg("") ui.msg("*****") end ui.msg("Configuration file written to #{config[:config_file]}") end def ask_user_for_config_path config[:config_file] ||= ask_question("Where should I put the config file? ", :default => "#{Chef::Config[:user_home]}/.chef/knife.rb") # have to use expand path to expand the tilde character to the user's home config[:config_file] = File.expand_path(config[:config_file]) if File.exists?(config[:config_file]) confirm("Overwrite #{config[:config_file]}") end end def ask_user_for_config server_name = guess_servername @chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", :default => "https://#{server_name}:443") if config[:initial] @new_client_name = config[:node_name] || ask_question("Please enter a name for the new user: ", :default => Etc.getlogin) @admin_client_name = config[:admin_client_name] || ask_question("Please enter the existing admin name: ", :default => "admin") @admin_client_key = config[:admin_client_key] || ask_question("Please enter the location of the existing admin's private key: ", :default => "/etc/chef-server/admin.pem") @admin_client_key = File.expand_path(@admin_client_key) else @new_client_name = config[:node_name] || ask_question("Please enter an existing username or clientname for the API: ", :default => Etc.getlogin) end @validation_client_name = config[:validation_client_name] || ask_question("Please enter the validation clientname: ", :default => "chef-validator") @validation_key = config[:validation_key] || ask_question("Please enter the location of the validation key: ", :default => "/etc/chef-server/chef-validator.pem") @validation_key = File.expand_path(@validation_key) @chef_repo = config[:repository] || ask_question("Please enter the path to a chef repository (or leave blank): ") @new_client_key = config[:client_key] || File.join(chef_config_path, "#{@new_client_name}.pem") @new_client_key = File.expand_path(@new_client_key) end def guess_servername o = Ohai::System.new o.load_plugins o.require_plugin "os" o.require_plugin "hostname" o[:fqdn] || o[:machinename] || o[:hostname] || "localhost" end def config_file config[:config_file] end def chef_config_path File.dirname(config_file) end end end end chef-12.14.60/lib/chef/knife/configure_client.rb000066400000000000000000000032321276456504500213030ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class ConfigureClient < Knife banner "knife configure client DIRECTORY" def run unless @config_dir = @name_args[0] ui.fatal "You must provide the directory to put the files in" show_usage exit(1) end ui.info("Creating client configuration") FileUtils.mkdir_p(@config_dir) ui.info("Writing client.rb") File.open(File.join(@config_dir, "client.rb"), "w") do |file| file.puts("log_level :info") file.puts("log_location STDOUT") file.puts("chef_server_url '#{Chef::Config[:chef_server_url]}'") file.puts("validation_client_name '#{Chef::Config[:validation_client_name]}'") end ui.info("Writing validation.pem") File.open(File.join(@config_dir, "validation.pem"), "w") do |validation| validation.puts(IO.read(Chef::Config[:validation_key])) end end end end end chef-12.14.60/lib/chef/knife/cookbook_bulk_delete.rb000066400000000000000000000050261276456504500221340ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookBulkDelete < Knife deps do require "chef/knife/cookbook_delete" require "chef/cookbook_version" end option :purge, :short => "-p", :long => "--purge", :boolean => true, :description => "Permanently remove files from backing data store" banner "knife cookbook bulk delete REGEX (options)" def run unless regex_str = @name_args.first ui.fatal("You must supply a regular expression to match the results against") exit 42 end regex = Regexp.new(regex_str) all_cookbooks = Chef::CookbookVersion.list cookbooks_names = all_cookbooks.keys.grep(regex) cookbooks_to_delete = cookbooks_names.inject({}) { |hash, name| hash[name] = all_cookbooks[name]; hash } ui.msg "All versions of the following cookbooks will be deleted:" ui.msg "" ui.msg ui.list(cookbooks_to_delete.keys.sort, :columns_down) ui.msg "" unless config[:yes] ui.confirm("Do you really want to delete these cookbooks") if config[:purge] ui.msg("Files that are common to multiple cookbooks are shared, so purging the files may break other cookbooks.") ui.confirm("Are you sure you want to purge files instead of just deleting the cookbooks") end ui.msg "" end cookbooks_names.each do |cookbook_name| versions = rest.get("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map { |v| v["version"] }.flatten versions.each do |version| rest.delete("cookbooks/#{cookbook_name}/#{version}#{config[:purge] ? "?purge=true" : ""}") ui.info("Deleted cookbook #{cookbook_name.ljust(25)} [#{version}]") end end end end end end chef-12.14.60/lib/chef/knife/cookbook_create.rb000066400000000000000000000352131276456504500211210ustar00rootroot00000000000000# # Author:: Nuo Yan () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookCreate < Knife deps do require "chef/json_compat" require "uri" require "fileutils" end banner "knife cookbook create COOKBOOK (options)" option :cookbook_path, :short => "-o PATH", :long => "--cookbook-path PATH", :description => "The directory where the cookbook will be created" option :readme_format, :short => "-r FORMAT", :long => "--readme-format FORMAT", :description => "Format of the README file, supported formats are 'md' (markdown) and 'rdoc' (rdoc)" option :cookbook_license, :short => "-I LICENSE", :long => "--license LICENSE", :description => "License for cookbook, apachev2, gplv2, gplv3, mit or none" option :cookbook_copyright, :short => "-C COPYRIGHT", :long => "--copyright COPYRIGHT", :description => "Name of copyright holder" option :cookbook_email, :short => "-m EMAIL", :long => "--email EMAIL", :description => "Email address of cookbook maintainer" def run Chef::Log.deprecation <. # EOH when "mit" file.puts <<-EOH # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # EOH when "none" file.puts <<-EOH # All rights reserved - Do Not Redistribute # EOH end end end end def create_changelog(dir, cookbook_name) msg("** Creating CHANGELOG for cookbook: #{cookbook_name}") unless File.exists?(File.join(dir, cookbook_name, "CHANGELOG.md")) open(File.join(dir, cookbook_name, "CHANGELOG.md"), "w") do |file| file.puts <<-EOH # #{cookbook_name} CHANGELOG This file is used to list changes made in each version of the #{cookbook_name} cookbook. ## 0.1.0 - [your_name] - Initial release of #{cookbook_name} - - - Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown. The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown. EOH end end end def create_readme(dir, cookbook_name, readme_format) msg("** Creating README for cookbook: #{cookbook_name}") unless File.exist?(File.join(dir, cookbook_name, "README.#{readme_format}")) open(File.join(dir, cookbook_name, "README.#{readme_format}"), "w") do |file| case readme_format when "rdoc" file.puts <<-EOH = #{cookbook_name} Cookbook TODO: Enter the cookbook description here. e.g. This cookbook makes your favorite breakfast sandwich. == Requirements TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. e.g. ==== packages - +toaster+ - #{cookbook_name} needs toaster to brown your bagel. == Attributes TODO: List your cookbook attributes here. e.g. ==== #{cookbook_name}::default
Key Type Description Default
['#{cookbook_name}']['bacon'] Boolean whether to include bacon true
== Usage ==== #{cookbook_name}::default TODO: Write usage instructions for each cookbook. e.g. Just include +#{cookbook_name}+ in your node's +run_list+: { "name":"my_node", "run_list": [ "recipe[#{cookbook_name}]" ] } == Contributing TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. e.g. 1. Fork the repository on Github 2. Create a named feature branch (like `add_component_x`) 3. Write your change 4. Write tests for your change (if applicable) 5. Run the tests, ensuring they all pass 6. Submit a Pull Request using Github == License and Authors Authors: TODO: List authors EOH when "md", "mkd", "txt" file.puts <<-EOH # #{cookbook_name} Cookbook TODO: Enter the cookbook description here. e.g. This cookbook makes your favorite breakfast sandwich. ## Requirements TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. e.g. ### Platforms - SandwichOS ### Chef - Chef 12.0 or later ### Cookbooks - `toaster` - #{cookbook_name} needs toaster to brown your bagel. ## Attributes TODO: List your cookbook attributes here. e.g. ### #{cookbook_name}::default
Key Type Description Default
['#{cookbook_name}']['bacon'] Boolean whether to include bacon true
## Usage ### #{cookbook_name}::default TODO: Write usage instructions for each cookbook. e.g. Just include `#{cookbook_name}` in your node's `run_list`: ```json { "name":"my_node", "run_list": [ "recipe[#{cookbook_name}]" ] } ``` ## Contributing TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. e.g. 1. Fork the repository on Github 2. Create a named feature branch (like `add_component_x`) 3. Write your change 4. Write tests for your change (if applicable) 5. Run the tests, ensuring they all pass 6. Submit a Pull Request using Github ## License and Authors Authors: TODO: List authors EOH else file.puts <<-EOH #{cookbook_name} Cookbook #{'=' * "#{cookbook_name} Cookbook".length} TODO: Enter the cookbook description here. e.g. This cookbook makes your favorite breakfast sandwich. Requirements TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc. e.g. toaster #{cookbook_name} needs toaster to brown your bagel. Attributes TODO: List your cookbook attributes here. #{cookbook_name} Key Type Description Default ['#{cookbook_name}']['bacon'] Boolean whether to include bacon true Usage #{cookbook_name} TODO: Write usage instructions for each cookbook. e.g. Just include `#{cookbook_name}` in your node's `run_list`: [code] { "name":"my_node", "run_list": [ "recipe[#{cookbook_name}]" ] } [/code] Contributing TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section. e.g. 1. Fork the repository on Github 2. Create a named feature branch (like `add_component_x`) 3. Write your change 4. Write tests for your change (if applicable) 5. Run the tests, ensuring they all pass 6. Submit a Pull Request using Github License and Authors Authors: TODO: List authors EOH end end end end def create_metadata(dir, cookbook_name, copyright, email, license, readme_format) msg("** Creating metadata for cookbook: #{cookbook_name}") license_name = case license when "apachev2" "Apache 2.0" when "gplv2" "GNU Public License 2.0" when "gplv3" "GNU Public License 3.0" when "mit" "MIT" when "none" "All rights reserved" end unless File.exist?(File.join(dir, cookbook_name, "metadata.rb")) open(File.join(dir, cookbook_name, "metadata.rb"), "w") do |file| if File.exist?(File.join(dir, cookbook_name, "README.#{readme_format}")) long_description = "long_description IO.read(File.join(File.dirname(__FILE__), 'README.#{readme_format}'))" end file.puts <<-EOH name '#{cookbook_name}' maintainer '#{copyright}' maintainer_email '#{email}' license '#{license_name}' description 'Installs/Configures #{cookbook_name}' #{long_description} version '0.1.0' EOH end end end private def default_cookbook_path_empty? Chef::Config[:cookbook_path].nil? || Chef::Config[:cookbook_path].empty? end def parameter_empty?(parameter) parameter.nil? || parameter.empty? end end end end chef-12.14.60/lib/chef/knife/cookbook_delete.rb000066400000000000000000000121141276456504500211130ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookDelete < Knife attr_accessor :cookbook_name, :version deps do require "chef/cookbook_version" end option :all, :short => "-a", :long => "--all", :boolean => true, :description => "delete all versions" option :purge, :short => "-p", :long => "--purge", :boolean => true, :description => "Permanently remove files from backing data store" banner "knife cookbook delete COOKBOOK VERSION (options)" def run confirm("Files that are common to multiple cookbooks are shared, so purging the files may disable other cookbooks. Are you sure you want to purge files instead of just deleting the cookbook") if config[:purge] @cookbook_name, @version = name_args if @cookbook_name && @version delete_explicit_version elsif @cookbook_name && config[:all] delete_all_versions elsif @cookbook_name && @version.nil? delete_without_explicit_version elsif @cookbook_name.nil? show_usage ui.fatal("You must provide the name of the cookbook to delete") exit(1) end end def delete_explicit_version delete_object(Chef::CookbookVersion, "#{@cookbook_name} version #{@version}", "cookbook") do delete_request("cookbooks/#{@cookbook_name}/#{@version}") end end def delete_all_versions confirm("Do you really want to delete all versions of #{@cookbook_name}") delete_all_without_confirmation end def delete_all_without_confirmation # look up the available versions again just in case the user # got to the list of versions to delete and selected 'all' # and also a specific version @available_versions = nil Array(available_versions).each do |version| delete_version_without_confirmation(version) end end def delete_without_explicit_version if available_versions.nil? # we already logged an error or 2 about it, so just bail exit(1) elsif available_versions.size == 1 @version = available_versions.first delete_explicit_version else versions_to_delete = ask_which_versions_to_delete delete_versions_without_confirmation(versions_to_delete) end end def available_versions @available_versions ||= rest.get("cookbooks/#{@cookbook_name}").map do |name, url_and_version| url_and_version["versions"].map { |url_by_version| url_by_version["version"] } end.flatten rescue Net::HTTPServerException => e if e.to_s =~ /^404/ ui.error("Cannot find a cookbook named #{@cookbook_name} to delete") nil else raise end end def ask_which_versions_to_delete question = "Which version(s) do you want to delete?\n" valid_responses = {} available_versions.each_with_index do |version, index| valid_responses[(index + 1).to_s] = version question << "#{index + 1}. #{@cookbook_name} #{version}\n" end valid_responses[(available_versions.size + 1).to_s] = :all question << "#{available_versions.size + 1}. All versions\n\n" responses = ask_question(question).split(",").map { |response| response.strip } if responses.empty? ui.error("No versions specified, exiting") exit(1) end versions = responses.map do |response| if version = valid_responses[response] version else ui.error("#{response} is not a valid choice, skipping it") end end versions.compact end def delete_version_without_confirmation(version) object = delete_request("cookbooks/#{@cookbook_name}/#{version}") output(format_for_display(object)) if config[:print_after] ui.info("Deleted cookbook[#{@cookbook_name}][#{version}]") end def delete_versions_without_confirmation(versions) versions.each do |version| if version == :all delete_all_without_confirmation break else delete_version_without_confirmation(version) end end end private def delete_request(path) path += "?purge=true" if config[:purge] rest.delete(path) end end end end chef-12.14.60/lib/chef/knife/cookbook_download.rb000066400000000000000000000106321276456504500214630ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookDownload < Knife attr_reader :version attr_accessor :cookbook_name deps do require "chef/cookbook_version" end banner "knife cookbook download COOKBOOK [VERSION] (options)" option :latest, :short => "-N", :long => "--latest", :description => "The version of the cookbook to download", :boolean => true option :download_directory, :short => "-d DOWNLOAD_DIRECTORY", :long => "--dir DOWNLOAD_DIRECTORY", :description => "The directory to download the cookbook into", :default => Dir.pwd option :force, :short => "-f", :long => "--force", :description => "Force download over the download directory if it exists" # TODO: tim/cw: 5-23-2010: need to implement knife-side # specificity for downloads - need to implement --platform and # --fqdn here def run @cookbook_name, @version = @name_args if @cookbook_name.nil? show_usage ui.fatal("You must specify a cookbook name") exit 1 elsif @version.nil? @version = determine_version if @version.nil? ui.fatal("No such cookbook found") exit 1 end end ui.info("Downloading #{@cookbook_name} cookbook version #{@version}") cookbook = Chef::CookbookVersion.load(@cookbook_name, @version) manifest = cookbook.manifest basedir = File.join(config[:download_directory], "#{@cookbook_name}-#{cookbook.version}") if File.exists?(basedir) if config[:force] Chef::Log.debug("Deleting #{basedir}") FileUtils.rm_rf(basedir) else ui.fatal("Directory #{basedir} exists, use --force to overwrite") exit end end Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment| next unless manifest.has_key?(segment) ui.info("Downloading #{segment}") manifest[segment].each do |segment_file| dest = File.join(basedir, segment_file["path"].gsub("/", File::SEPARATOR)) Chef::Log.debug("Downloading #{segment_file['path']} to #{dest}") FileUtils.mkdir_p(File.dirname(dest)) tempfile = rest.streaming_request(segment_file["url"]) FileUtils.mv(tempfile.path, dest) end end ui.info("Cookbook downloaded to #{basedir}") end def determine_version if available_versions.nil? nil elsif available_versions.size == 1 @version = available_versions.first elsif config[:latest] @version = available_versions.last else ask_which_version end end def available_versions @available_versions ||= begin versions = Chef::CookbookVersion.available_versions(@cookbook_name) unless versions.nil? versions.map! { |version| Chef::Version.new(version) } versions.sort! end versions end @available_versions end def ask_which_version question = "Which version do you want to download?\n" valid_responses = {} available_versions.each_with_index do |version, index| valid_responses[(index + 1).to_s] = version question << "#{index + 1}. #{@cookbook_name} #{version}\n" end question += "\n" response = ask_question(question).strip unless @version = valid_responses[response] ui.error("'#{response}' is not a valid value.") exit(1) end @version end end end end chef-12.14.60/lib/chef/knife/cookbook_list.rb000066400000000000000000000027461276456504500206360ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Nuo Yan () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookList < Knife banner "knife cookbook list (options)" option :with_uri, :short => "-w", :long => "--with-uri", :description => "Show corresponding URIs" option :all_versions, :short => "-a", :long => "--all", :description => "Show all available versions." def run env = config[:environment] num_versions = config[:all_versions] ? "num_versions=all" : "num_versions=1" api_endpoint = env ? "/environments/#{env}/cookbooks?#{num_versions}" : "/cookbooks?#{num_versions}" cookbook_versions = rest.get(api_endpoint) ui.output(format_cookbook_list_for_display(cookbook_versions)) end end end end chef-12.14.60/lib/chef/knife/cookbook_metadata.rb000066400000000000000000000070511276456504500214350ustar00rootroot00000000000000# # # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookMetadata < Knife deps do require "chef/cookbook_loader" require "chef/cookbook/metadata" end banner "knife cookbook metadata COOKBOOK (options)" option :cookbook_path, :short => "-o PATH:PATH", :long => "--cookbook-path PATH:PATH", :description => "A colon-separated path to look for cookbooks in", :proc => lambda { |o| o.split(":") } option :all, :short => "-a", :long => "--all", :description => "Generate metadata for all cookbooks, rather than just a single cookbook" def run config[:cookbook_path] ||= Chef::Config[:cookbook_path] if config[:all] cl = Chef::CookbookLoader.new(config[:cookbook_path]) cl.load_cookbooks cl.each do |cname, cookbook| generate_metadata(cname.to_s) end else cookbook_name = @name_args[0] if cookbook_name.nil? || cookbook_name.empty? ui.error "You must specify the cookbook to generate metadata for, or use the --all option." exit 1 end generate_metadata(cookbook_name) end end def generate_metadata(cookbook) Array(config[:cookbook_path]).reverse_each do |path| file = File.expand_path(File.join(path, cookbook, "metadata.rb")) if File.exists?(file) generate_metadata_from_file(cookbook, file) else validate_metadata_json(path, cookbook) end end end def generate_metadata_from_file(cookbook, file) ui.info("Generating metadata for #{cookbook} from #{file}") md = Chef::Cookbook::Metadata.new md.name(cookbook) md.from_file(file) json_file = File.join(File.dirname(file), "metadata.json") File.open(json_file, "w") do |f| f.write(Chef::JSONCompat.to_json_pretty(md)) end Chef::Log.debug("Generated #{json_file}") rescue Exceptions::ObsoleteDependencySyntax, Exceptions::InvalidVersionConstraint => e ui.stderr.puts "ERROR: The cookbook '#{cookbook}' contains invalid or obsolete metadata syntax." ui.stderr.puts "in #{file}:" ui.stderr.puts ui.stderr.puts e.message exit 1 end def validate_metadata_json(path, cookbook) json_file = File.join(path, cookbook, "metadata.json") if File.exist?(json_file) Chef::Cookbook::Metadata.validate_json(IO.read(json_file)) end rescue Exceptions::ObsoleteDependencySyntax, Exceptions::InvalidVersionConstraint => e ui.stderr.puts "ERROR: The cookbook '#{cookbook}' contains invalid or obsolete metadata syntax." ui.stderr.puts "in #{json_file}:" ui.stderr.puts ui.stderr.puts e.message exit 1 end end end end chef-12.14.60/lib/chef/knife/cookbook_metadata_from_file.rb000066400000000000000000000023301276456504500234520ustar00rootroot00000000000000# # # Author:: Adam Jacob () # Author:: Matthew Kent () # Copyright:: Copyright 2009-2016, Chef Software Inc. # Copyright:: Copyright 2010-2016, Matthew Kent # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookMetadataFromFile < Knife deps do require "chef/cookbook/metadata" end banner "knife cookbook metadata from FILE (options)" def run file = @name_args[0] cookbook = File.basename(File.dirname(file)) @metadata = Chef::Knife::CookbookMetadata.new @metadata.generate_metadata_from_file(cookbook, file) end end end end chef-12.14.60/lib/chef/knife/cookbook_show.rb000066400000000000000000000062141276456504500206350ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookShow < Knife deps do require "chef/json_compat" require "uri" require "chef/cookbook_version" end banner "knife cookbook show COOKBOOK [VERSION] [PART] [FILENAME] (options)" option :fqdn, :short => "-f FQDN", :long => "--fqdn FQDN", :description => "The FQDN of the host to see the file for" option :platform, :short => "-p PLATFORM", :long => "--platform PLATFORM", :description => "The platform to see the file for" option :platform_version, :short => "-V VERSION", :long => "--platform-version VERSION", :description => "The platform version to see the file for" option :with_uri, :short => "-w", :long => "--with-uri", :description => "Show corresponding URIs" def run cookbook_name, cookbook_version, segment, filename = @name_args cookbook = Chef::CookbookVersion.load(cookbook_name, cookbook_version) unless cookbook_version.nil? case @name_args.length when 4 # We are showing a specific file node = Hash.new node[:fqdn] = config[:fqdn] if config.has_key?(:fqdn) node[:platform] = config[:platform] if config.has_key?(:platform) node[:platform_version] = config[:platform_version] if config.has_key?(:platform_version) class << node def attribute?(name) # rubocop:disable Lint/NestedMethodDefinition has_key?(name) end end manifest_entry = cookbook.preferred_manifest_record(node, segment, filename) temp_file = rest.streaming_request(manifest_entry[:url]) # the temp file is cleaned up elsewhere temp_file.open if temp_file.closed? pretty_print(temp_file.read) when 3 # We are showing a specific part of the cookbook output(cookbook.manifest[segment]) when 2 # We are showing the whole cookbook data output(cookbook) when 1 # We are showing the cookbook versions (all of them) env = config[:environment] api_endpoint = env ? "environments/#{env}/cookbooks/#{cookbook_name}" : "cookbooks/#{cookbook_name}" output(format_cookbook_list_for_display(rest.get(api_endpoint))) when 0 show_usage ui.fatal("You must specify a cookbook name") exit 1 end end end end end chef-12.14.60/lib/chef/knife/cookbook_site_download.rb000066400000000000000000000071011276456504500225040ustar00rootroot00000000000000# Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookSiteDownload < Knife deps do require "fileutils" end banner "knife cookbook site download COOKBOOK [VERSION] (options)" category "cookbook site" option :file, :short => "-f FILE", :long => "--file FILE", :description => "The filename to write to" option :force, :long => "--force", :description => "Force download deprecated version" option :supermarket_site, :short => "-m SUPERMARKET_SITE", :long => "--supermarket-site SUPERMARKET_SITE", :description => "Supermarket Site", :default => "https://supermarket.chef.io", :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } def run if current_cookbook_deprecated? message = "DEPRECATION: This cookbook has been deprecated. " message << "It has been replaced by #{replacement_cookbook}." ui.warn message unless config[:force] ui.warn "Use --force to force download deprecated cookbook." return end end download_cookbook end def version @version = desired_cookbook_data["version"] end private def cookbooks_api_url "#{config[:supermarket_site]}/api/v1/cookbooks" end def current_cookbook_data @current_cookbook_data ||= begin noauth_rest.get "#{cookbooks_api_url}/#{@name_args[0]}" end end def current_cookbook_deprecated? current_cookbook_data["deprecated"] == true end def desired_cookbook_data @desired_cookbook_data ||= begin uri = if @name_args.length == 1 current_cookbook_data["latest_version"] else specific_cookbook_version_url end noauth_rest.get uri end end def download_cookbook ui.info "Downloading #{@name_args[0]} from Supermarket at version #{version} to #{download_location}" tf = noauth_rest.streaming_request(desired_cookbook_data["file"]) ::FileUtils.cp tf.path, download_location ui.info "Cookbook saved: #{download_location}" end def download_location config[:file] ||= File.join Dir.pwd, "#{@name_args[0]}-#{version}.tar.gz" config[:file] end def replacement_cookbook File.basename(current_cookbook_data["replacement"]) end def specific_cookbook_version_url "#{cookbooks_api_url}/#{@name_args[0]}/versions/#{@name_args[1].tr('.', '_')}" end end end end chef-12.14.60/lib/chef/knife/cookbook_site_install.rb000066400000000000000000000145301276456504500223470ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/exceptions" require "shellwords" require "mixlib/archive" class Chef class Knife class CookbookSiteInstall < Knife deps do require "chef/mixin/shell_out" require "chef/knife/core/cookbook_scm_repo" require "chef/cookbook/metadata" end banner "knife cookbook site install COOKBOOK [VERSION] (options)" category "cookbook site" option :no_deps, :short => "-D", :long => "--skip-dependencies", :boolean => true, :default => false, :description => "Skips automatic dependency installation." option :cookbook_path, :short => "-o PATH:PATH", :long => "--cookbook-path PATH:PATH", :description => "A colon-separated path to look for cookbooks in", :proc => lambda { |o| o.split(":") } option :default_branch, :short => "-B BRANCH", :long => "--branch BRANCH", :description => "Default branch to work with", :default => "master" option :use_current_branch, :short => "-b", :long => "--use-current-branch", :description => "Use the current branch", :boolean => true, :default => false option :supermarket_site, :short => "-m SUPERMARKET_SITE", :long => "--supermarket-site SUPERMARKET_SITE", :description => "Supermarket Site", :default => "https://supermarket.chef.io", :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } attr_reader :cookbook_name attr_reader :vendor_path def run extend Chef::Mixin::ShellOut if config[:cookbook_path] Chef::Config[:cookbook_path] = config[:cookbook_path] else config[:cookbook_path] = Chef::Config[:cookbook_path] end @cookbook_name = parse_name_args! # Check to ensure we have a valid source of cookbooks before continuing # @install_path = File.expand_path(Array(config[:cookbook_path]).first) ui.info "Installing #@cookbook_name to #{@install_path}" @repo = CookbookSCMRepo.new(@install_path, ui, config) #cookbook_path = File.join(vendor_path, name_args[0]) upstream_file = File.join(@install_path, "#{@cookbook_name}.tar.gz") @repo.sanity_check unless config[:use_current_branch] @repo.reset_to_default_state @repo.prepare_to_import(@cookbook_name) end downloader = download_cookbook_to(upstream_file) clear_existing_files(File.join(@install_path, @cookbook_name)) extract_cookbook(upstream_file, downloader.version) # TODO: it'd be better to store these outside the cookbook repo and # keep them around, e.g., in ~/Library/Caches on OS X. ui.info("Removing downloaded tarball") File.unlink(upstream_file) if @repo.finalize_updates_to(@cookbook_name, downloader.version) unless config[:use_current_branch] @repo.reset_to_default_state end @repo.merge_updates_from(@cookbook_name, downloader.version) else unless config[:use_current_branch] @repo.reset_to_default_state end end unless config[:no_deps] preferred_metadata.dependencies.each do |cookbook, version_list| # Doesn't do versions.. yet nv = self.class.new nv.config = config nv.name_args = [ cookbook ] nv.run end end end def parse_name_args! if name_args.empty? ui.error("Please specify a cookbook to download and install.") exit 1 elsif name_args.size >= 2 unless name_args.last.match(/^(\d+)(\.\d+){1,2}$/) && name_args.size == 2 ui.error("Installing multiple cookbooks at once is not supported.") exit 1 end end name_args.first end def download_cookbook_to(download_path) downloader = Chef::Knife::CookbookSiteDownload.new downloader.config[:file] = download_path downloader.config[:supermarket_site] = config[:supermarket_site] downloader.name_args = name_args downloader.run downloader end def extract_cookbook(upstream_file, version) ui.info("Uncompressing #{@cookbook_name} version #{version}.") Mixlib::Archive.new(convert_path(upstream_file)).extract(@install_path, perms: false) end def clear_existing_files(cookbook_path) ui.info("Removing pre-existing version.") FileUtils.rmtree(cookbook_path) if File.directory?(cookbook_path) end def convert_path(upstream_file) # converts a Windows path (C:\foo) to a mingw path (/c/foo) if ENV["MSYSTEM"] == "MINGW32" return upstream_file.sub(/^([[:alpha:]]):/, '/\1') else return Shellwords.escape upstream_file end end # Get the preferred metadata path on disk. Chef prefers the metadata.rb # over the metadata.json. # # @raise if there is no metadata in the cookbook # # @return [Chef::Cookbook::Metadata] def preferred_metadata md = Chef::Cookbook::Metadata.new rb = File.join(@install_path, @cookbook_name, "metadata.rb") if File.exist?(rb) md.from_file(rb) return md end json = File.join(@install_path, @cookbook_name, "metadata.json") if File.exist?(json) json = IO.read(json) md.from_json(json) return md end raise Chef::Exceptions::MetadataNotFound.new(@install_path, @cookbook_name) end end end end chef-12.14.60/lib/chef/knife/cookbook_site_list.rb000066400000000000000000000042041276456504500216510ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookSiteList < Knife banner "knife cookbook site list (options)" category "cookbook site" option :with_uri, :short => "-w", :long => "--with-uri", :description => "Show corresponding URIs" option :supermarket_site, :short => "-m SUPERMARKET_SITE", :long => "--supermarket-site SUPERMARKET_SITE", :description => "Supermarket Site", :default => "https://supermarket.chef.io", :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } def run if config[:with_uri] cookbooks = Hash.new get_cookbook_list.each { |k, v| cookbooks[k] = v["cookbook"] } ui.output(format_for_display(cookbooks)) else ui.msg(ui.list(get_cookbook_list.keys.sort, :columns_down)) end end def get_cookbook_list(items = 10, start = 0, cookbook_collection = {}) cookbooks_url = "#{config[:supermarket_site]}/api/v1/cookbooks?items=#{items}&start=#{start}" cr = noauth_rest.get(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook end new_start = start + cr["items"].length if new_start < cr["total"] get_cookbook_list(items, new_start, cookbook_collection) else cookbook_collection end end end end end chef-12.14.60/lib/chef/knife/cookbook_site_search.rb000066400000000000000000000034631276456504500221510ustar00rootroot00000000000000# Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookSiteSearch < Knife banner "knife cookbook site search QUERY (options)" category "cookbook site" option :supermarket_site, :short => "-m SUPERMARKET_SITE", :long => "--supermarket-site SUPERMARKET_SITE", :description => "Supermarket Site", :default => "https://supermarket.chef.io", :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } def run output(search_cookbook(name_args[0])) end def search_cookbook(query, items = 10, start = 0, cookbook_collection = {}) cookbooks_url = "#{config[:supermarket_site]}/api/v1/search?q=#{query}&items=#{items}&start=#{start}" cr = noauth_rest.get(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook end new_start = start + cr["items"].length if new_start < cr["total"] search_cookbook(query, items, new_start, cookbook_collection) else cookbook_collection end end end end end chef-12.14.60/lib/chef/knife/cookbook_site_share.rb000066400000000000000000000140601276456504500220010ustar00rootroot00000000000000# Author:: Nuo Yan () # Author:: Tim Hinderliter () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/mixin/shell_out" class Chef class Knife class CookbookSiteShare < Knife include Chef::Mixin::ShellOut deps do require "chef/cookbook_loader" require "chef/cookbook_uploader" require "chef/cookbook_site_streaming_uploader" require "mixlib/shellout" end include Chef::Mixin::ShellOut banner "knife cookbook site share COOKBOOK [CATEGORY] (options)" category "cookbook site" option :cookbook_path, :short => "-o PATH:PATH", :long => "--cookbook-path PATH:PATH", :description => "A colon-separated path to look for cookbooks in", :proc => lambda { |o| Chef::Config.cookbook_path = o.split(":") } option :dry_run, :long => "--dry-run", :short => "-n", :boolean => true, :default => false, :description => "Don't take action, only print what files will be uploaded to Supermarket." option :supermarket_site, :short => "-m SUPERMARKET_SITE", :long => "--supermarket-site SUPERMARKET_SITE", :description => "Supermarket Site", :default => "https://supermarket.chef.io", :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } def run config[:cookbook_path] ||= Chef::Config[:cookbook_path] if @name_args.length < 1 show_usage ui.fatal("You must specify the cookbook name.") exit(1) elsif @name_args.length < 2 cookbook_name = @name_args[0] category = get_category(cookbook_name) else cookbook_name = @name_args[0] category = @name_args[1] end cl = Chef::CookbookLoader.new(config[:cookbook_path]) if cl.cookbook_exists?(cookbook_name) cookbook = cl[cookbook_name] Chef::CookbookUploader.new(cookbook).validate_cookbooks tmp_cookbook_dir = Chef::CookbookSiteStreamingUploader.create_build_dir(cookbook) begin Chef::Log.debug("Temp cookbook directory is #{tmp_cookbook_dir.inspect}") ui.info("Making tarball #{cookbook_name}.tgz") shell_out!("#{tar_cmd} -czf #{cookbook_name}.tgz #{cookbook_name}", :cwd => tmp_cookbook_dir) rescue => e ui.error("Error making tarball #{cookbook_name}.tgz: #{e.message}. Increase log verbosity (-VV) for more information.") Chef::Log.debug("\n#{e.backtrace.join("\n")}") exit(1) end if config[:dry_run] ui.info("Not uploading #{cookbook_name}.tgz due to --dry-run flag.") result = shell_out!("#{tar_cmd} -tzf #{cookbook_name}.tgz", :cwd => tmp_cookbook_dir) ui.info(result.stdout) FileUtils.rm_rf tmp_cookbook_dir return end begin do_upload("#{tmp_cookbook_dir}/#{cookbook_name}.tgz", category, Chef::Config[:node_name], Chef::Config[:client_key]) ui.info("Upload complete") Chef::Log.debug("Removing local staging directory at #{tmp_cookbook_dir}") FileUtils.rm_rf tmp_cookbook_dir rescue => e ui.error("Error uploading cookbook #{cookbook_name} to Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.") Chef::Log.debug("\n#{e.backtrace.join("\n")}") exit(1) end else ui.error("Could not find cookbook #{cookbook_name} in your cookbook path.") exit(1) end end def get_category(cookbook_name) data = noauth_rest.get("#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}") data["category"] rescue => e return "Other" if e.kind_of?(Net::HTTPServerException) && e.response.code == "404" ui.fatal("Unable to reach Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.") Chef::Log.debug("\n#{e.backtrace.join("\n")}") exit(1) end def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename) uri = "#{config[:supermarket_site]}/api/v1/cookbooks" category_string = Chef::JSONCompat.to_json({ "category" => cookbook_category }) http_resp = Chef::CookbookSiteStreamingUploader.post(uri, user_id, user_secret_filename, { :tarball => File.open(cookbook_filename), :cookbook => category_string, }) res = Chef::JSONCompat.from_json(http_resp.body) if http_resp.code.to_i != 201 if res["error_messages"] if res["error_messages"][0] =~ /Version already exists/ ui.error "The same version of this cookbook already exists on Supermarket." exit(1) else ui.error "#{res['error_messages'][0]}" exit(1) end else ui.error "Unknown error while sharing cookbook" ui.error "Server response: #{http_resp.body}" exit(1) end end res end def tar_cmd if !@tar_cmd @tar_cmd = "tar" begin # Unix and Mac only - prefer gnutar if shell_out("which gnutar").exitstatus.equal?(0) @tar_cmd = "gnutar" end rescue Errno::ENOENT end end @tar_cmd end end end end chef-12.14.60/lib/chef/knife/cookbook_site_show.rb000066400000000000000000000042401276456504500216560ustar00rootroot00000000000000# Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookSiteShow < Knife banner "knife cookbook site show COOKBOOK [VERSION] (options)" category "cookbook site" option :supermarket_site, :short => "-m SUPERMARKET_SITE", :long => "--supermarket-site SUPERMARKET_SITE", :description => "Supermarket Site", :default => "https://supermarket.chef.io", :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } def run output(format_for_display(get_cookbook_data)) end def supermarket_uri "#{config[:supermarket_site]}/api/v1" end def get_cookbook_data case @name_args.length when 1 noauth_rest.get("#{supermarket_uri}/cookbooks/#{@name_args[0]}") when 2 noauth_rest.get("#{supermarket_uri}/cookbooks/#{@name_args[0]}/versions/#{name_args[1].tr('.', '_')}") end end def get_cookbook_list(items = 10, start = 0, cookbook_collection = {}) cookbooks_url = "#{supermarket_uri}/cookbooks?items=#{items}&start=#{start}" cr = noauth_rest.get(cookbooks_url) cr["items"].each do |cookbook| cookbook_collection[cookbook["cookbook_name"]] = cookbook end new_start = start + cr["items"].length if new_start < cr["total"] get_cookbook_list(items, new_start, cookbook_collection) else cookbook_collection end end end end end chef-12.14.60/lib/chef/knife/cookbook_site_unshare.rb000066400000000000000000000037401276456504500223470ustar00rootroot00000000000000# # Author:: Stephen Delano () # Author:: Tim Hinderliter () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookSiteUnshare < Knife deps do require "chef/json_compat" end banner "knife cookbook site unshare COOKBOOK" category "cookbook site" option :supermarket_site, :short => "-m SUPERMARKET_SITE", :long => "--supermarket-site SUPERMARKET_SITE", :description => "Supermarket Site", :default => "https://supermarket.chef.io", :proc => Proc.new { |supermarket| Chef::Config[:knife][:supermarket_site] = supermarket } def run @cookbook_name = @name_args[0] if @cookbook_name.nil? show_usage ui.fatal "You must provide the name of the cookbook to unshare" exit 1 end confirm "Do you really want to unshare all versions of the cookbook #{@cookbook_name}" begin rest.delete "#{config[:supermarket_site]}/api/v1/cookbooks/#{@name_args[0]}" rescue Net::HTTPServerException => e raise e unless e.message =~ /Forbidden/ ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it." exit 1 end ui.info "Unshared all versions of the cookbook #{@cookbook_name}" end end end end chef-12.14.60/lib/chef/knife/cookbook_site_vendor.rb000066400000000000000000000023011276456504500221670ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/cookbook_site_install" class Chef::Knife::CookbookSiteVendor < Chef::Knife::CookbookSiteInstall def self.load_deps superclass.load_deps end def self.options=(new_opts) superclass.options = new_opts end def self.options superclass.options end banner(<<-B) ************************************************* DEPRECATED: please use knife cookbook site install ************************************************* #{superclass.banner} B category "deprecated" end chef-12.14.60/lib/chef/knife/cookbook_test.rb000066400000000000000000000056711276456504500206420ustar00rootroot00000000000000# # # Author:: Adam Jacob () # Author:: Matthew Kent () # Copyright:: Copyright 2009-2016, Chef Software Inc. # Copyright:: Copyright 2010-2016, Matthew Kent # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class CookbookTest < Knife deps do require "chef/cookbook_loader" require "chef/cookbook/syntax_check" end banner "knife cookbook test [COOKBOOKS...] (options)" option :cookbook_path, :short => "-o PATH:PATH", :long => "--cookbook-path PATH:PATH", :description => "A colon-separated path to look for cookbooks in", :proc => lambda { |o| o.split(":") } option :all, :short => "-a", :long => "--all", :description => "Test all cookbooks, rather than just a single cookbook" def run ui.warn("DEPRECATED: Please use ChefSpec or Rubocop to syntax-check cookbooks.") config[:cookbook_path] ||= Chef::Config[:cookbook_path] checked_a_cookbook = false if config[:all] cl = cookbook_loader cl.load_cookbooks cl.each do |key, cookbook| checked_a_cookbook = true test_cookbook(key) end else @name_args.each do |cb| ui.info "checking #{cb}" next unless cookbook_loader.cookbook_exists?(cb) checked_a_cookbook = true test_cookbook(cb) end end unless checked_a_cookbook ui.warn("No cookbooks to test in #{Array(config[:cookbook_path]).join(',')} - is your cookbook path misconfigured?") end end def test_cookbook(cookbook) ui.info("Running syntax check on #{cookbook}") Array(config[:cookbook_path]).reverse_each do |path| syntax_checker = Chef::Cookbook::SyntaxCheck.for_cookbook(cookbook, path) test_ruby(syntax_checker) test_templates(syntax_checker) end end def test_ruby(syntax_checker) ui.info("Validating ruby files") exit(1) unless syntax_checker.validate_ruby_files end def test_templates(syntax_checker) ui.info("Validating templates") exit(1) unless syntax_checker.validate_templates end def cookbook_loader @cookbook_loader ||= Chef::CookbookLoader.new(config[:cookbook_path]) end end end end chef-12.14.60/lib/chef/knife/cookbook_upload.rb000066400000000000000000000276261276456504500211530ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: Nuo Yan () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/cookbook_uploader" class Chef class Knife class CookbookUpload < Knife CHECKSUM = "checksum" MATCH_CHECKSUM = /[0-9a-f]{32,}/ deps do require "chef/exceptions" require "chef/cookbook_loader" require "chef/cookbook_uploader" end banner "knife cookbook upload [COOKBOOKS...] (options)" option :cookbook_path, :short => "-o PATH:PATH", :long => "--cookbook-path PATH:PATH", :description => "A colon-separated path to look for cookbooks in", :proc => lambda { |o| o.split(":") } option :freeze, :long => "--freeze", :description => "Freeze this version of the cookbook so that it cannot be overwritten", :boolean => true option :all, :short => "-a", :long => "--all", :description => "Upload all cookbooks, rather than just a single cookbook" option :force, :long => "--force", :boolean => true, :description => "Update cookbook versions even if they have been frozen" option :concurrency, :long => "--concurrency NUMBER_OF_THREADS", :description => "How many concurrent threads will be used", :default => 10, :proc => lambda { |o| o.to_i } option :environment, :short => "-E", :long => "--environment ENVIRONMENT", :description => "Set ENVIRONMENT's version dependency match the version you're uploading.", :default => nil option :depends, :short => "-d", :long => "--include-dependencies", :description => "Also upload cookbook dependencies" def run # Sanity check before we load anything from the server unless config[:all] if @name_args.empty? show_usage ui.fatal("You must specify the --all flag or at least one cookbook name") exit 1 end end config[:cookbook_path] ||= Chef::Config[:cookbook_path] if @name_args.empty? && ! config[:all] show_usage ui.fatal("You must specify the --all flag or at least one cookbook name") exit 1 end assert_environment_valid! warn_about_cookbook_shadowing version_constraints_to_update = {} upload_failures = 0 upload_ok = 0 # Get a list of cookbooks and their versions from the server # to check for the existence of a cookbook's dependencies. @server_side_cookbooks = Chef::CookbookVersion.list_all_versions justify_width = @server_side_cookbooks.map { |name| name.size }.max.to_i + 2 if config[:all] cookbook_repo.load_cookbooks_without_shadow_warning cookbooks_for_upload = [] cookbook_repo.each do |cookbook_name, cookbook| cookbooks_for_upload << cookbook cookbook.freeze_version if config[:freeze] version_constraints_to_update[cookbook_name] = cookbook.version end if cookbooks_for_upload.any? begin upload(cookbooks_for_upload, justify_width) rescue Exceptions::CookbookFrozen ui.warn("Not updating version constraints for some cookbooks in the environment as the cookbook is frozen.") end ui.info("Uploaded all cookbooks.") else cookbook_path = config[:cookbook_path].respond_to?(:join) ? config[:cookbook_path].join(", ") : config[:cookbook_path] ui.warn("Could not find any cookbooks in your cookbook path: #{cookbook_path}. Use --cookbook-path to specify the desired path.") end else if @name_args.empty? show_usage ui.error("You must specify the --all flag or at least one cookbook name") exit 1 end cookbooks_to_upload.each do |cookbook_name, cookbook| cookbook.freeze_version if config[:freeze] begin upload([cookbook], justify_width) upload_ok += 1 version_constraints_to_update[cookbook_name] = cookbook.version rescue Exceptions::CookbookNotFoundInRepo => e upload_failures += 1 ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it") Log.debug(e) upload_failures += 1 rescue Exceptions::CookbookFrozen ui.warn("Not updating version constraints for #{cookbook_name} in the environment as the cookbook is frozen.") upload_failures += 1 end end if upload_failures == 0 ui.info "Uploaded #{upload_ok} cookbook#{upload_ok > 1 ? "s" : ""}." elsif upload_failures > 0 && upload_ok > 0 ui.warn "Uploaded #{upload_ok} cookbook#{upload_ok > 1 ? "s" : ""} ok but #{upload_failures} " + "cookbook#{upload_failures > 1 ? "s" : ""} upload failed." elsif upload_failures > 0 && upload_ok == 0 ui.error "Failed to upload #{upload_failures} cookbook#{upload_failures > 1 ? "s" : ""}." exit 1 end end unless version_constraints_to_update.empty? update_version_constraints(version_constraints_to_update) if config[:environment] end end def cookbooks_to_upload @cookbooks_to_upload ||= if config[:all] cookbook_repo.load_cookbooks_without_shadow_warning else upload_set = {} @name_args.each do |cookbook_name| begin if ! upload_set.has_key?(cookbook_name) upload_set[cookbook_name] = cookbook_repo[cookbook_name] if config[:depends] upload_set[cookbook_name].metadata.dependencies.each { |dep, ver| @name_args << dep } end end rescue Exceptions::CookbookNotFoundInRepo => e ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it") Log.debug(e) end end upload_set end end def cookbook_repo @cookbook_loader ||= begin Chef::Cookbook::FileVendor.fetch_from_disk(config[:cookbook_path]) Chef::CookbookLoader.new(config[:cookbook_path]) end end def update_version_constraints(new_version_constraints) new_version_constraints.each do |cookbook_name, version| environment.cookbook_versions[cookbook_name] = "= #{version}" end environment.save end def environment @environment ||= config[:environment] ? Environment.load(config[:environment]) : nil end def warn_about_cookbook_shadowing # because cookbooks are lazy-loaded, we have to force the loader # to load the cookbooks the user intends to upload here: cookbooks_to_upload unless cookbook_repo.merged_cookbooks.empty? ui.warn "* " * 40 ui.warn(<<-WARNING) The cookbooks: #{cookbook_repo.merged_cookbooks.join(', ')} exist in multiple places in your cookbook_path. A composite version of these cookbooks has been compiled for uploading. #{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this behavior will be removed and you will no longer be able to have the same version of a cookbook in multiple places in your cookbook_path. WARNING ui.warn "The affected cookbooks are located:" ui.output ui.format_for_display(cookbook_repo.merged_cookbook_paths) ui.warn "* " * 40 end end private def assert_environment_valid! environment rescue Net::HTTPServerException => e if e.response.code.to_s == "404" ui.error "The environment #{config[:environment]} does not exist on the server, aborting." Log.debug(e) exit 1 else raise end end def upload(cookbooks, justify_width) cookbooks.each do |cb| ui.info("Uploading #{cb.name.to_s.ljust(justify_width + 10)} [#{cb.version}]") check_for_broken_links!(cb) check_for_dependencies!(cb) end Chef::CookbookUploader.new(cookbooks, :force => config[:force], :concurrency => config[:concurrency]).upload_cookbooks rescue Chef::Exceptions::CookbookFrozen => e ui.error e raise end def check_for_broken_links!(cookbook) # MUST!! dup the cookbook version object--it memoizes its # manifest object, but the manifest becomes invalid when you # regenerate the metadata broken_files = cookbook.dup.manifest_records_by_path.select do |path, info| info[CHECKSUM].nil? || info[CHECKSUM] !~ MATCH_CHECKSUM end unless broken_files.empty? broken_filenames = Array(broken_files).map { |path, info| path } ui.error "The cookbook #{cookbook.name} has one or more broken files" ui.error "This is probably caused by broken symlinks in the cookbook directory" ui.error "The broken file(s) are: #{broken_filenames.join(' ')}" exit 1 end end def check_for_dependencies!(cookbook) # for all dependencies, check if the version is on the server, or # the version is in the cookbooks being uploaded. If not, exit and warn the user. missing_dependencies = cookbook.metadata.dependencies.reject do |cookbook_name, version| check_server_side_cookbooks(cookbook_name, version) || check_uploading_cookbooks(cookbook_name, version) end unless missing_dependencies.empty? missing_cookbook_names = missing_dependencies.map { |cookbook_name, version| "'#{cookbook_name}' version '#{version}'" } ui.error "Cookbook #{cookbook.name} depends on cookbooks which are not currently" ui.error "being uploaded and cannot be found on the server." ui.error "The missing cookbook(s) are: #{missing_cookbook_names.join(', ')}" exit 1 end end def check_server_side_cookbooks(cookbook_name, version) if @server_side_cookbooks[cookbook_name].nil? false else versions = @server_side_cookbooks[cookbook_name]["versions"].collect { |versions| versions["version"] } Log.debug "Versions of cookbook '#{cookbook_name}' returned by the server: #{versions.join(", ")}" @server_side_cookbooks[cookbook_name]["versions"].each do |versions_hash| if Chef::VersionConstraint.new(version).include?(versions_hash["version"]) Log.debug "Matched cookbook '#{cookbook_name}' with constraint '#{version}' to cookbook version '#{versions_hash['version']}' on the server" return true end end false end end def check_uploading_cookbooks(cookbook_name, version) if (! cookbooks_to_upload[cookbook_name].nil?) && Chef::VersionConstraint.new(version).include?(cookbooks_to_upload[cookbook_name].version) Log.debug "Matched cookbook '#{cookbook_name}' with constraint '#{version}' to a local cookbook." return true end false end end end end chef-12.14.60/lib/chef/knife/core/000077500000000000000000000000001276456504500163675ustar00rootroot00000000000000chef-12.14.60/lib/chef/knife/core/bootstrap_context.rb000066400000000000000000000210631276456504500224770ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/run_list" require "chef/util/path_helper" require "pathname" class Chef class Knife module Core # Instances of BootstrapContext are the context objects (i.e., +self+) for # bootstrap templates. For backwards compatibility, they +must+ set the # following instance variables: # * @config - a hash of knife's config values # * @run_list - the run list for the node to boostrap # class BootstrapContext attr_accessor :client_pem def initialize(config, run_list, chef_config, secret = nil) @config = config @run_list = run_list @chef_config = chef_config @secret = secret end def bootstrap_environment @config[:environment] end def validation_key if @chef_config.has_key?(:validation_key) && File.exist?(File.expand_path(@chef_config[:validation_key])) IO.read(File.expand_path(@chef_config[:validation_key])) else false end end def client_d @client_d ||= client_d_content end def encrypted_data_bag_secret @secret end # Contains commands and content, see trusted_certs_content # TODO: Rename to trusted_certs_script def trusted_certs @trusted_certs ||= trusted_certs_content end def config_content client_rb = <<-CONFIG log_location STDOUT chef_server_url "#{@chef_config[:chef_server_url]}" validation_client_name "#{@chef_config[:validation_client_name]}" CONFIG if @config[:chef_node_name] client_rb << %Q{node_name "#{@config[:chef_node_name]}"\n} else client_rb << "# Using default node name (fqdn)\n" end # We configure :verify_api_cert only when it's overridden on the CLI # or when specified in the knife config. if !@config[:node_verify_api_cert].nil? || knife_config.has_key?(:verify_api_cert) value = @config[:node_verify_api_cert].nil? ? knife_config[:verify_api_cert] : @config[:node_verify_api_cert] client_rb << %Q{verify_api_cert #{value}\n} end # We configure :ssl_verify_mode only when it's overridden on the CLI # or when specified in the knife config. if @config[:node_ssl_verify_mode] || knife_config.has_key?(:ssl_verify_mode) value = case @config[:node_ssl_verify_mode] when "peer" :verify_peer when "none" :verify_none when nil knife_config[:ssl_verify_mode] else nil end if value client_rb << %Q{ssl_verify_mode :#{value}\n} end end if @config[:ssl_verify_mode] client_rb << %Q{ssl_verify_mode :#{knife_config[:ssl_verify_mode]}\n} end if knife_config[:bootstrap_proxy] client_rb << %Q{http_proxy "#{knife_config[:bootstrap_proxy]}"\n} client_rb << %Q{https_proxy "#{knife_config[:bootstrap_proxy]}"\n} end if knife_config[:bootstrap_proxy_user] client_rb << %Q{http_proxy_user "#{knife_config[:bootstrap_proxy_user]}"\n} client_rb << %Q{https_proxy_user "#{knife_config[:bootstrap_proxy_user]}"\n} end if knife_config[:bootstrap_proxy_pass] client_rb << %Q{http_proxy_pass "#{knife_config[:bootstrap_proxy_pass]}"\n} client_rb << %Q{https_proxy_pass "#{knife_config[:bootstrap_proxy_pass]}"\n} end if knife_config[:bootstrap_no_proxy] client_rb << %Q{no_proxy "#{knife_config[:bootstrap_no_proxy]}"\n} end if encrypted_data_bag_secret client_rb << %Q{encrypted_data_bag_secret "/etc/chef/encrypted_data_bag_secret"\n} end unless trusted_certs.empty? client_rb << %Q{trusted_certs_dir "/etc/chef/trusted_certs"\n} end if Chef::Config[:fips] client_rb << <<-CONFIG fips true chef_version = ::Chef::VERSION.split(".") unless chef_version[0].to_i > 12 || (chef_version[0].to_i == 12 && chef_version[1].to_i >= 8) raise "FIPS Mode requested but not supported by this client" end CONFIG end client_rb end def start_chef # If the user doesn't have a client path configure, let bash use the PATH for what it was designed for client_path = @chef_config[:chef_client_path] || "chef-client" s = "#{client_path} -j /etc/chef/first-boot.json" s << " -l debug" if @config[:verbosity] && @config[:verbosity] >= 2 s << " -E #{bootstrap_environment}" unless bootstrap_environment.nil? s << " --no-color" unless @config[:color] s end def knife_config @chef_config.key?(:knife) ? @chef_config[:knife] : {} end # # chef version string to fetch the latest current version from omnitruck # If user is on X.Y.Z bootstrap will use the latest X release # X here can be 10 or 11 def latest_current_chef_version_string installer_version_string = nil if @config[:prerelease] installer_version_string = ["-p"] else chef_version_string = if knife_config[:bootstrap_version] knife_config[:bootstrap_version] else Chef::VERSION.split(".").first end installer_version_string = ["-v", chef_version_string] # If bootstrapping a pre-release version add -p to the installer string if chef_version_string.split(".").length > 3 installer_version_string << "-p" end end installer_version_string.join(" ") end def first_boot (@config[:first_boot_attributes] || {}).tap do |attributes| if @config[:policy_name] && @config[:policy_group] attributes[:policy_name] = @config[:policy_name] attributes[:policy_group] = @config[:policy_group] else attributes[:run_list] = @run_list end attributes.merge!(:tags => @config[:tags]) if @config[:tags] && !@config[:tags].empty? end end private # Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped # This string should contain both the commands necessary to both create the files, as well as their content def trusted_certs_content content = "" if @chef_config[:trusted_certs_dir] Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(@chef_config[:trusted_certs_dir]), "*.{crt,pem}")).each do |cert| content << "cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'\n" + IO.read(File.expand_path(cert)) + "\nEOP\n" end end content end def client_d_content content = "" if @chef_config[:client_d_dir] && File.exist?(@chef_config[:client_d_dir]) root = Pathname(@chef_config[:client_d_dir]) root.find do |f| relative = f.relative_path_from(root) if f != root file_on_node = "/etc/chef/client.d/#{relative}" if f.directory? content << "mkdir #{file_on_node}\n" else content << "cat > #{file_on_node} <<'EOP'\n" + f.read + "\nEOP\n" end end end end content end end end end end chef-12.14.60/lib/chef/knife/core/cookbook_scm_repo.rb000066400000000000000000000117641276456504500224220ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/shell_out" class Chef class Knife class CookbookSCMRepo DIRTY_REPO = /^[\s]+M/ include Chef::Mixin::ShellOut attr_reader :repo_path attr_reader :default_branch attr_reader :use_current_branch attr_reader :ui def initialize(repo_path, ui, opts = {}) @repo_path = repo_path @ui = ui @default_branch = "master" @use_current_branch = false apply_opts(opts) end def sanity_check unless ::File.directory?(repo_path) ui.error("The cookbook repo path #{repo_path} does not exist or is not a directory") exit 1 end unless git_repo?(repo_path) ui.error "The cookbook repo #{repo_path} is not a git repository." ui.info("Use `git init` to initialize a git repo") exit 1 end if use_current_branch @default_branch = get_current_branch() end unless branch_exists?(default_branch) ui.error "The default branch '#{default_branch}' does not exist" ui.info "If this is a new git repo, make sure you have at least one commit before installing cookbooks" exit 1 end cmd = git("status --porcelain") if cmd.stdout =~ DIRTY_REPO ui.error "You have uncommitted changes to your cookbook repo (#{repo_path}):" ui.msg cmd.stdout ui.info "Commit or stash your changes before importing cookbooks" exit 1 end # TODO: any untracked files in the cookbook directory will get nuked later # make this an error condition also. true end def reset_to_default_state ui.info("Checking out the #{default_branch} branch.") git("checkout #{default_branch}") end def prepare_to_import(cookbook_name) branch = "chef-vendor-#{cookbook_name}" if branch_exists?(branch) ui.info("Pristine copy branch (#{branch}) exists, switching to it.") git("checkout #{branch}") else ui.info("Creating pristine copy branch #{branch}") git("checkout -b #{branch}") end end def finalize_updates_to(cookbook_name, version) if update_count = updated?(cookbook_name) ui.info "#{update_count} files updated, committing changes" git("add #{cookbook_name}") git("commit -m \"Import #{cookbook_name} version #{version}\" -- #{cookbook_name}") ui.info("Creating tag cookbook-site-imported-#{cookbook_name}-#{version}") git("tag -f cookbook-site-imported-#{cookbook_name}-#{version}") true else ui.info("No changes made to #{cookbook_name}") false end end def merge_updates_from(cookbook_name, version) branch = "chef-vendor-#{cookbook_name}" Dir.chdir(repo_path) do if system("git merge #{branch}") ui.info("Cookbook #{cookbook_name} version #{version} successfully installed") else ui.error("You have merge conflicts - please resolve manually") ui.info("Merge status (cd #{repo_path}; git status):") system("git status") exit 3 end end end def updated?(cookbook_name) update_count = git("status --porcelain -- #{cookbook_name}").stdout.strip.lines.count update_count == 0 ? nil : update_count end def branch_exists?(branch_name) git("branch --no-color").stdout.lines.any? { |l| l =~ /\s#{Regexp.escape(branch_name)}(?:\s|$)/ } end def get_current_branch() ref = git("symbolic-ref HEAD").stdout ref.chomp.split("/")[2] end private def git_repo?(directory) if File.directory?(File.join(directory, ".git")) return true elsif File.dirname(directory) == directory return false else git_repo?(File.dirname(directory)) end end def apply_opts(opts) opts.each do |option, value| case option.to_s when "default_branch" @default_branch = value when "use_current_branch" @use_current_branch = value end end end def git(command) shell_out!("git #{command}", :cwd => repo_path) end end end end chef-12.14.60/lib/chef/knife/core/custom_manifest_loader.rb000066400000000000000000000050151276456504500234430ustar00rootroot00000000000000# Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/version" class Chef class Knife class SubcommandLoader # # Load a subcommand from a user-supplied # manifest file # class CustomManifestLoader < Chef::Knife::SubcommandLoader attr_accessor :manifest def initialize(chef_config_dir, plugin_manifest) super(chef_config_dir) @manifest = plugin_manifest end # If the user has created a ~/.chef/plugin_manifest.json file, we'll use # that instead of inspecting the on-system gems to find the plugins. The # file format is expected to look like: # # { "plugins": { # "knife-ec2": { # "paths": [ # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb", # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb" # ] # } # } # } # # Extraneous content in this file is ignored. This is intentional so that we # can adapt the file format for potential behavior changes to knife in # the future. def find_subcommands_via_manifest # Format of subcommand_files is "relative_path" (something you can # Kernel.require()) => full_path. The relative path isn't used # currently, so we just map full_path => full_path. subcommand_files = {} manifest["plugins"].each do |plugin_name, plugin_manifest| plugin_manifest["paths"].each do |cmd_path| subcommand_files[cmd_path] = cmd_path end end subcommand_files.merge(find_subcommands_via_dirglob) end def subcommand_files @subcommand_files ||= (find_subcommands_via_manifest.values + site_subcommands).flatten.uniq end end end end end chef-12.14.60/lib/chef/knife/core/gem_glob_loader.rb000066400000000000000000000121721276456504500220200ustar00rootroot00000000000000# Author:: Christopher Brown () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/version" require "chef/util/path_helper" class Chef class Knife class SubcommandLoader class GemGlobLoader < Chef::Knife::SubcommandLoader MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+} MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/} def subcommand_files @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq end # Returns a Hash of paths to knife commands built-in to chef, or installed via gem. # If rubygems is not installed, falls back to globbing the knife directory. # The Hash is of the form {"relative/path" => "/absolute/path"} #-- # Note: the "right" way to load the plugins is to require the relative path, i.e., # require 'chef/knife/command' # but we're getting frustrated by bugs at every turn, and it's slow besides. So # subcommand loader has been modified to load the plugins by using Kernel.load # with the absolute path. def gem_and_builtin_subcommands require "rubygems" find_subcommands_via_rubygems rescue LoadError find_subcommands_via_dirglob end def find_subcommands_via_dirglob # The "require paths" of the core knife subcommands bundled with chef files = Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(File.expand_path("../../../knife", __FILE__)), "*.rb")] subcommand_files = {} files.each do |knife_file| rel_path = knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/, 1] subcommand_files[rel_path] = knife_file end subcommand_files end def find_subcommands_via_rubygems files = find_files_latest_gems "chef/knife/*.rb" subcommand_files = {} files.each do |file| rel_path = file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1] # When not installed as a gem (ChefDK/appbundler in particular), AND # a different version of Chef is installed via gems, `files` will # include some files from the 'other' Chef install. If this contains # a knife command that doesn't exist in this version of Chef, we will # get a LoadError later when we try to require it. next if from_different_chef_version?(file) subcommand_files[rel_path] = file end subcommand_files.merge(find_subcommands_via_dirglob) end private def find_files_latest_gems(glob, check_load_path = true) files = [] if check_load_path files = $LOAD_PATH.map do |load_path| Dir["#{File.expand_path glob, Chef::Util::PathHelper.escape_glob_dir(load_path)}#{Gem.suffix_pattern}"] end.flatten.select { |file| File.file? file.untaint } end gem_files = latest_gem_specs.map do |spec| # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8 if spec.respond_to? :matches_for_glob spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}") else check_spec_for_glob(spec, glob) end end.flatten files.concat gem_files files.uniq! if check_load_path return files end def latest_gem_specs @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs Gem::Specification.latest_specs(true) # find prerelease gems else Gem.source_index.latest_specs(true) end end def check_spec_for_glob(spec, glob) dirs = if spec.require_paths.size > 1 "{#{spec.require_paths.join(',')}}" else spec.require_paths.first end glob = File.join(Chef::Util::PathHelper.escape_glob_dir(spec.full_gem_path, dirs), glob) Dir[glob].map { |f| f.untaint } end def from_different_chef_version?(path) matches_any_chef_gem?(path) && !matches_this_chef_gem?(path) end def matches_any_chef_gem?(path) path =~ MATCHES_CHEF_GEM end def matches_this_chef_gem?(path) path =~ MATCHES_THIS_CHEF_GEM end end end end end chef-12.14.60/lib/chef/knife/core/generic_presenter.rb000066400000000000000000000173071276456504500224270ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife/core/text_formatter" class Chef class Knife module Core # Allows includer knife commands to return multiple attributes # @brief knife node show NAME -a ATTR1 -a ATTR2 module MultiAttributeReturnOption # :nodoc: def self.included(includer) includer.class_eval do @attrs_to_show = [] option :attribute, :short => "-a ATTR1 [-a ATTR2]", :long => "--attribute ATTR1 [--attribute ATTR2] ", :proc => lambda { |val| @attrs_to_show << val }, :description => "Show one or more attributes" end end end #==Chef::Knife::Core::GenericPresenter # The base presenter class for displaying structured data in knife commands. # This is not an abstract base class, and it is suitable for displaying # most kinds of objects that knife needs to display. class GenericPresenter attr_reader :ui attr_reader :config # Instaniates a new GenericPresenter. This is generally handled by the # Chef::Knife::UI object, though you need to match the signature of this # method if you intend to use your own presenter instead. def initialize(ui, config) @ui, @config = ui, config end # Is the selected output format a data interchange format? # Returns true if the selected output format is json or yaml, false # otherwise. Knife search uses this to adjust its data output so as not # to produce invalid JSON output. def interchange? case parse_format_option when :json, :yaml true else false end end # Returns a String representation of +data+ that is suitable for output # to a terminal or perhaps for data interchange with another program. # The representation of the +data+ depends on the value of the # `config[:format]` setting. def format(data) case parse_format_option when :summary summarize(data) when :text text_format(data) when :json Chef::JSONCompat.to_json_pretty(data) when :yaml require "yaml" YAML.dump(data) when :pp require "stringio" # If you were looking for some attribute and there is only one match # just dump the attribute value if config[:attribute] && data.length == 1 data.values[0] else out = StringIO.new PP.pp(data, out) out.string end end end # Converts the user-supplied value of `config[:format]` to a Symbol # representing the desired output format. # ===Returns # returns one of :summary, :text, :json, :yaml, or :pp # ===Raises # Raises an ArgumentError if the desired output format could not be # determined from the value of `config[:format]` def parse_format_option case config[:format] when "summary", /^s/, nil :summary when "text", /^t/ :text when "json", /^j/ :json when "yaml", /^y/ :yaml when "pp", /^p/ :pp else raise ArgumentError, "Unknown output format #{config[:format]}" end end # Summarize the data. Defaults to text format output, # which may not be very summary-like def summarize(data) text_format(data) end # Converts the +data+ to a String in the text format. Uses # Chef::Knife::Core::TextFormatter def text_format(data) TextFormatter.new(data, ui).formatted_data end def format_list_for_display(list) config[:with_uri] ? list : list.keys.sort { |a, b| a <=> b } end def format_for_display(data) if formatting_subset_of_data? format_data_subset_for_display(data) elsif config[:id_only] name_or_id_for(data) elsif config[:environment] && data.respond_to?(:chef_environment) { "chef_environment" => data.chef_environment } else data end end def format_data_subset_for_display(data) subset = if config[:attribute] result = {} Array(config[:attribute]).each do |nested_value_spec| nested_value = extract_nested_value(data, nested_value_spec) result[nested_value_spec] = nested_value end result elsif config[:run_list] run_list = data.run_list.run_list { "run_list" => run_list } else raise ArgumentError, "format_data_subset_for_display requires attribute, run_list, or id_only config option to be set" end { name_or_id_for(data) => subset } end def name_or_id_for(data) data.respond_to?(:name) ? data.name : data["id"] end def formatting_subset_of_data? config[:attribute] || config[:run_list] end def extract_nested_value(data, nested_value_spec) nested_value_spec.split(".").each do |attr| if data.nil? nil # don't get no method error on nil # Must check :[] before attr because spec can include # `keys` - want the key named `keys`, not a list of # available keys. elsif data.respond_to?(:[]) && data.has_key?(attr) data = data[attr] elsif data.respond_to?(attr.to_sym) data = data.send(attr.to_sym) else data = begin data.send(attr.to_sym) rescue NoMethodError nil end end end ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data end def format_cookbook_list_for_display(item) if config[:with_uri] item.inject({}) do |collected, (cookbook, versions)| collected[cookbook] = Hash.new versions["versions"].each do |ver| collected[cookbook][ver["version"]] = ver["url"] end collected end else versions_by_cookbook = item.inject({}) do |collected, ( cookbook, versions )| collected[cookbook] = versions["versions"].map { |v| v["version"] } collected end key_length = versions_by_cookbook.empty? ? 0 : versions_by_cookbook.keys.map { |name| name.size }.max + 2 versions_by_cookbook.sort.map do |cookbook, versions| "#{cookbook.ljust(key_length)} #{versions.join(' ')}" end end end end end end end chef-12.14.60/lib/chef/knife/core/hashed_command_loader.rb000066400000000000000000000067501276456504500232040ustar00rootroot00000000000000# Author:: Steven Danna () # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/version" class Chef class Knife class SubcommandLoader # # Load a subcommand from a pre-computed path # for the given command. # class HashedCommandLoader < Chef::Knife::SubcommandLoader KEY = "_autogenerated_command_paths" attr_accessor :manifest def initialize(chef_config_dir, plugin_manifest) super(chef_config_dir) @manifest = plugin_manifest end def guess_category(args) category_words = positional_arguments(args) category_words.map! { |w| w.split("-") }.flatten! find_longest_key(manifest[KEY]["plugins_by_category"], category_words, " ") end def list_commands(pref_category = nil) if pref_category || manifest[KEY]["plugins_by_category"].key?(pref_category) commands = { pref_category => manifest[KEY]["plugins_by_category"][pref_category] } else commands = manifest[KEY]["plugins_by_category"] end # If any of the specified plugins in the manifest dont have a valid path we will # eventually get an error and the user will need to rehash - instead, lets just # print out 1 error here telling them to rehash errors = {} commands.collect { |k, v| v }.flatten.each do |command| paths = manifest[KEY]["plugins_paths"][command] if paths && paths.is_a?(Array) # It is only an error if all the paths don't exist if paths.all? { |sc| !File.exists?(sc) } errors[command] = paths end end end if errors.empty? commands else Chef::Log.error "There are files specified in the manifest that are missing. Please rehash to update the subcommands cache. If you see this error after rehashing delete the cache at #{Chef::Knife::SubcommandLoader.plugin_manifest_path}" Chef::Log.error "Missing files:\n\t#{errors.values.flatten.join("\n\t")}" {} end end def subcommand_files manifest[KEY]["plugins_paths"].values.flatten end def load_command(args) paths = manifest[KEY]["plugins_paths"][subcommand_for_args(args)] if paths.nil? || paths.empty? || (! paths.is_a? Array) false else paths.each do |sc| if File.exists?(sc) Kernel.load sc else return false end end true end end def subcommand_for_args(args) if manifest[KEY]["plugins_paths"].key?(args) args else find_longest_key(manifest[KEY]["plugins_paths"], args, "_") end end end end end end chef-12.14.60/lib/chef/knife/core/node_editor.rb000066400000000000000000000073721276456504500212200ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Jordan Running () # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/json_compat" require "chef/node" class Chef class Knife class NodeEditor attr_reader :node, :ui, :config private :node, :ui, :config # @param node [Chef::Node] # @param ui [Chef::Knife::UI] # @param config [Hash] def initialize(node, ui, config) @node, @ui, @config = node, ui, config end # Opens the node data (as JSON) in the user's editor and returns a new # {Chef::Node} reflecting the user's changes. # # @return [Chef::Node] def edit_node abort "You specified the --disable_editing option, nothing to edit" if config[:disable_editing] assert_editor_set! updated_node_data = ui.edit_hash(view) apply_updates(updated_node_data) @updated_node end # Returns an array of the names of properties that have been changed or # +false+ if none were changed. # # @return [Array] if any properties have been changed. # @return [false] if no properties have been changed. def updated? return false if @updated_node.nil? pristine_copy = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(node)) updated_copy = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(@updated_node)) updated_properties = %w{ name chef_environment automatic default normal override policy_name policy_group run_list }.reject do |key| pristine_copy[key] == updated_copy[key] end updated_properties.any? && updated_properties end # @api private def view result = { "name" => node.name, "chef_environment" => node.chef_environment, "normal" => node.normal_attrs, "policy_name" => node.policy_name, "policy_group" => node.policy_group, "run_list" => node.run_list, } if config[:all_attributes] result["default"] = node.default_attrs result["override"] = node.override_attrs result["automatic"] = node.automatic_attrs end result end # @api private def apply_updates(updated_data) if node.name && node.name != updated_data["name"] ui.warn "Changing the name of a node results in a new node being created, #{node.name} will not be modified or removed." ui.confirm "Proceed with creation of new node" end data = updated_data.dup unless config[:all_attributes] data["automatic"] = node.automatic_attrs data["default"] = node.default_attrs data["override"] = node.override_attrs end @updated_node = Node.from_hash(data) end private def abort(message) ui.error(message) exit 1 end def assert_editor_set! unless config[:editor] abort "You must set your EDITOR environment variable or configure your editor via knife.rb" end end end end end chef-12.14.60/lib/chef/knife/core/node_presenter.rb000066400000000000000000000120051276456504500217260ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife/core/text_formatter" require "chef/knife/core/generic_presenter" class Chef class Knife module Core # This module may be included into a knife subcommand class to automatically # add configuration options used by the NodePresenter module NodeFormattingOptions # :nodoc: # Would prefer to do this in a rational way, but can't be done b/c of # Mixlib::CLI's design :( def self.included(includer) includer.class_eval do option :medium_output, :short => "-m", :long => "--medium", :boolean => true, :default => false, :description => "Include normal attributes in the output" option :long_output, :short => "-l", :long => "--long", :boolean => true, :default => false, :description => "Include all attributes in the output" end end end #==Chef::Knife::Core::NodePresenter # A customized presenter for Chef::Node objects. Supports variable-length # output formats for displaying node data class NodePresenter < GenericPresenter def format(data) if parse_format_option == :json summarize_json(data) else super end end def summarize_json(data) if data.kind_of?(Chef::Node) node = data result = {} result["name"] = node.name if node.policy_name.nil? && node.policy_group.nil? result["chef_environment"] = node.chef_environment else result["policy_name"] = node.policy_name result["policy_group"] = node.policy_group end result["run_list"] = node.run_list result["normal"] = node.normal_attrs if config[:long_output] result["default"] = node.default_attrs result["override"] = node.override_attrs result["automatic"] = node.automatic_attrs end Chef::JSONCompat.to_json_pretty(result) else Chef::JSONCompat.to_json_pretty(data) end end # Converts a Chef::Node object to a string suitable for output to a # terminal. If config[:medium_output] or config[:long_output] are set # the volume of output is adjusted accordingly. Uses colors if enabled # in the ui object. def summarize(data) if data.kind_of?(Chef::Node) node = data # special case ec2 with their split horizon whatsis. ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress] summarized = <<-SUMMARY #{ui.color('Node Name:', :bold)} #{ui.color(node.name, :bold)} SUMMARY show_policy = !(node.policy_name.nil? && node.policy_group.nil?) if show_policy summarized << <<-POLICY #{key('Policy Name:')} #{node.policy_name} #{key('Policy Group:')} #{node.policy_group} POLICY else summarized << <<-ENV #{key('Environment:')} #{node.chef_environment} ENV end summarized << <<-SUMMARY #{key('FQDN:')} #{node[:fqdn]} #{key('IP:')} #{ip} #{key('Run List:')} #{node.run_list} SUMMARY unless show_policy summarized << <<-ROLES #{key('Roles:')} #{Array(node[:roles]).join(', ')} ROLES end summarized << <<-SUMMARY #{key('Recipes:')} #{Array(node[:recipes]).join(', ')} #{key('Platform:')} #{node[:platform]} #{node[:platform_version]} #{key('Tags:')} #{node.tags.join(', ')} SUMMARY if config[:medium_output] || config[:long_output] summarized += <<-MORE #{key('Attributes:')} #{text_format(node.normal_attrs)} MORE end if config[:long_output] summarized += <<-MOST #{key('Default Attributes:')} #{text_format(node.default_attrs)} #{key('Override Attributes:')} #{text_format(node.override_attrs)} #{key('Automatic Attributes (Ohai Data):')} #{text_format(node.automatic_attrs)} MOST end summarized else super end end def key(key_text) ui.color(key_text, :cyan) end end end end end chef-12.14.60/lib/chef/knife/core/object_loader.rb000066400000000000000000000067441276456504500215230ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "ffi_yajl" require "chef/util/path_helper" require "chef/data_bag_item" class Chef class Knife module Core class ObjectLoader attr_reader :ui attr_reader :klass class ObjectType FILE = 1 FOLDER = 2 end def initialize(klass, ui) @klass = klass @ui = ui end def load_from(repo_location, *components) unless object_file = find_file(repo_location, *components) ui.error "Could not find or open file '#{components.last}' in current directory or in '#{repo_location}/#{components.join('/')}'" exit 1 end object_from_file(object_file) end # When someone makes this awesome, please update the above error message. def find_file(repo_location, *components) if file_exists_and_is_readable?(File.expand_path( components.last )) File.expand_path( components.last ) else relative_path = File.join(Dir.pwd, repo_location, *components) if file_exists_and_is_readable?(relative_path) relative_path else nil end end end # Find all objects in the given location # If the object type is File it will look for all *.{json,rb} # files, otherwise it will lookup for folders only (useful for # data_bags) # # @param [String] path - base look up location # # @return [Array] basenames of the found objects # # @api public def find_all_objects(path) path = File.join(Chef::Util::PathHelper.escape_glob_dir(File.expand_path(path)), "*") path << ".{json,rb}" objects = Dir.glob(path) objects.map { |o| File.basename(o) } end def find_all_object_dirs(path) path = File.join(Chef::Util::PathHelper.escape_glob_dir(File.expand_path(path)), "*") objects = Dir.glob(path) objects.delete_if { |o| !File.directory?(o) } objects.map { |o| File.basename(o) } end def object_from_file(filename) case filename when /\.(js|json)$/ r = FFI_Yajl::Parser.parse(IO.read(filename)) # Chef::DataBagItem doesn't work well with the json_create method if @klass == Chef::DataBagItem r else @klass.from_hash(r) end when /\.rb$/ r = klass.new r.from_file(filename) r else ui.fatal("File must end in .js, .json, or .rb") exit 30 end end def file_exists_and_is_readable?(file) File.exist?(file) && File.readable?(file) end end end end end chef-12.14.60/lib/chef/knife/core/status_presenter.rb000066400000000000000000000126161276456504500223340ustar00rootroot00000000000000# # Author:: Nicolas DUPEUX () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife/core/text_formatter" require "chef/knife/core/generic_presenter" class Chef class Knife module Core # This module may be included into a knife subcommand class to automatically # add configuration options used by the StatusPresenter module StatusFormattingOptions # :nodoc: # Would prefer to do this in a rational way, but can't be done b/c of # Mixlib::CLI's design :( def self.included(includer) includer.class_eval do option :medium_output, :short => "-m", :long => "--medium", :boolean => true, :default => false, :description => "Include normal attributes in the output" option :long_output, :short => "-l", :long => "--long", :boolean => true, :default => false, :description => "Include all attributes in the output" end end end #==Chef::Knife::Core::StatusPresenter # A customized presenter for Chef::Node objects. Supports variable-length # output formats for displaying node data class StatusPresenter < GenericPresenter def format(data) if parse_format_option == :json summarize_json(data) else super end end def summarize_json(list) result_list = [] list.each do |node| result = {} result["name"] = node["name"] || node.name result["chef_environment"] = node["chef_environment"] ip = (node["ec2"] && node["ec2"]["public_ipv4"]) || node["ipaddress"] fqdn = (node["ec2"] && node["ec2"]["public_hostname"]) || node["fqdn"] result["ip"] = ip if ip result["fqdn"] = fqdn if fqdn result["run_list"] = node.run_list if config["run_list"] result["ohai_time"] = node["ohai_time"] result["platform"] = node["platform"] if node["platform"] result["platform_version"] = node["platform_version"] if node["platform_version"] if config[:long_output] result["default"] = node.default_attrs result["override"] = node.override_attrs result["automatic"] = node.automatic_attrs end result_list << result end Chef::JSONCompat.to_json_pretty(result_list) end # Converts a Chef::Node object to a string suitable for output to a # terminal. If config[:medium_output] or config[:long_output] are set # the volume of output is adjusted accordingly. Uses colors if enabled # in the ui object. def summarize(list) summarized = "" list.each do |data| node = data # special case ec2 with their split horizon whatsis. ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress] fqdn = (node[:ec2] && node[:ec2][:public_hostname]) || node[:fqdn] name = node["name"] || node.name hours, minutes, = time_difference_in_hms(node["ohai_time"]) hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}" minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}" run_list = "#{node['run_list']}" if config[:run_list] if hours > 24 color = :red text = hours_text elsif hours >= 1 color = :yellow text = hours_text else color = :green text = minutes_text end line_parts = Array.new line_parts << @ui.color(text, color) + " ago" << name line_parts << fqdn if fqdn line_parts << ip if ip line_parts << run_list if run_list if node["platform"] platform = node["platform"] if node["platform_version"] platform << " #{node['platform_version']}" end line_parts << platform end summarized = summarized + line_parts.join(", ") + ".\n" end summarized end def key(key_text) ui.color(key_text, :cyan) end # :nodoc: # TODO: this is duplicated from StatusHelper in the Webui. dedup. def time_difference_in_hms(unix_time) now = Time.now.to_i difference = now - unix_time.to_i hours = (difference / 3600).to_i difference = difference % 3600 minutes = (difference / 60).to_i seconds = (difference % 60) return [hours, minutes, seconds] end end end end end chef-12.14.60/lib/chef/knife/core/subcommand_loader.rb000066400000000000000000000175651276456504500224100ustar00rootroot00000000000000# Author:: Christopher Brown () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/version" require "chef/util/path_helper" require "chef/knife/core/gem_glob_loader" require "chef/knife/core/hashed_command_loader" require "chef/knife/core/custom_manifest_loader" class Chef class Knife # # Public Methods of a Subcommand Loader # # load_commands - loads all available subcommands # load_command(args) - loads subcommands for the given args # list_commands(args) - lists all available subcommands, # optionally filtering by category # subcommand_files - returns an array of all subcommand files # that could be loaded # commnad_class_from(args) - returns the subcommand class for the # user-requested command # class SubcommandLoader attr_reader :chef_config_dir attr_reader :env # A small factory method. Eventually, this is the only place # where SubcommandLoader should know about its subclasses, but # to maintain backwards compatibility many of the instance # methods in this base class contain default implementations # of the functions sub classes should otherwise provide # or directly instantiate the appropriate subclass def self.for_config(chef_config_dir) if autogenerated_manifest? Chef::Log.debug("Using autogenerated hashed command manifest #{plugin_manifest_path}") Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest) elsif custom_manifest? Chef.log_deprecation("Using custom manifest #{plugin_manifest_path} is deprecated. Please use a `knife rehash` autogenerated manifest instead.") Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, plugin_manifest) else Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir) end end # There are certain situations where we want to shortcut the loader selection # in self.for_config and force using the GemGlobLoader def self.gem_glob_loader(chef_config_dir) Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir) end def self.plugin_manifest? plugin_manifest_path && File.exist?(plugin_manifest_path) end def self.autogenerated_manifest? plugin_manifest? && plugin_manifest.key?(HashedCommandLoader::KEY) end def self.custom_manifest? plugin_manifest? && plugin_manifest.key?("plugins") end def self.plugin_manifest Chef::JSONCompat.from_json(File.read(plugin_manifest_path)) end def self.plugin_manifest_path Chef::Util::PathHelper.home(".chef", "plugin_manifest.json") end def initialize(chef_config_dir, env = nil) @chef_config_dir = chef_config_dir # Deprecated and un-used instance variable. @env = env unless env.nil? Chef.log_deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.") end end # Load all the sub-commands def load_commands return true if @loaded subcommand_files.each { |subcommand| Kernel.load subcommand } @loaded = true end def force_load @loaded = false load_commands end def load_command(_command_args) load_commands end def list_commands(pref_cat = nil) load_commands if pref_cat && Chef::Knife.subcommands_by_category.key?(pref_cat) { pref_cat => Chef::Knife.subcommands_by_category[pref_cat] } else Chef::Knife.subcommands_by_category end end def command_class_from(args) cmd_words = positional_arguments(args) load_command(cmd_words) result = Chef::Knife.subcommands[find_longest_key(Chef::Knife.subcommands, cmd_words, "_")] result || Chef::Knife.subcommands[args.first.tr("-", "_")] end def guess_category(args) category_words = positional_arguments(args) category_words.map! { |w| w.split("-") }.flatten! find_longest_key(Chef::Knife.subcommands_by_category, category_words, " ") end # # This is shared between the custom_manifest_loader and the gem_glob_loader # def find_subcommands_via_dirglob # The "require paths" of the core knife subcommands bundled with chef files = Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(File.expand_path("../../../knife", __FILE__)), "*.rb")] subcommand_files = {} files.each do |knife_file| rel_path = knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/, 1] subcommand_files[rel_path] = knife_file end subcommand_files end # # Subclassses should define this themselves. Eventually, this will raise a # NotImplemented error, but for now, we mimic the behavior the user was likely # to get in the past. # def subcommand_files Chef.log_deprecation "Using Chef::Knife::SubcommandLoader directly is deprecated. Please use Chef::Knife::SubcommandLoader.for_config(chef_config_dir, env)" @subcommand_files ||= if Chef::Knife::SubcommandLoader.plugin_manifest? Chef::Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, env).subcommand_files else Chef::Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir, env).subcommand_files end end # # Utility function for finding an element in a hash given an array # of words and a separator. We find the the longest key in the # hash composed of the given words joined by the separator. # def find_longest_key(hash, words, sep = "_") match = nil until match || words.empty? candidate = words.join(sep) if hash.key?(candidate) match = candidate else words.pop end end match end # # The positional arguments from the argument list provided by the # users. Used to search for subcommands and categories. # # @return [Array] # def positional_arguments(args) args.select { |arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ } end # Returns an Array of paths to knife commands located in # chef_config_dir/plugins/knife/ and ~/.chef/plugins/knife/ def site_subcommands user_specific_files = [] if chef_config_dir user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", Chef::Util::PathHelper.escape_glob_dir(chef_config_dir))) end # finally search ~/.chef/plugins/knife/*.rb Chef::Util::PathHelper.home(".chef", "plugins", "knife") do |p| user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(p), "*.rb")) end user_specific_files end end end end chef-12.14.60/lib/chef/knife/core/text_formatter.rb000066400000000000000000000053541276456504500217720ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Knife module Core class TextFormatter attr_reader :data attr_reader :ui def initialize(data, ui) @ui = ui @data = if data.respond_to?(:display_hash) data.display_hash elsif data.kind_of?(Array) data elsif data.respond_to?(:to_hash) data.to_hash else data end end def formatted_data @formatted_data ||= text_format(data) end def text_format(data) buffer = "" if data.respond_to?(:keys) justify_width = data.keys.map { |k| k.to_s.size }.max.to_i + 1 data.sort.each do |key, value| # key: ['value'] should be printed as key: value if value.kind_of?(Array) && value.size == 1 && is_singleton(value[0]) value = value[0] end if is_singleton(value) # Strings are printed as key: value. justified_key = ui.color("#{key}:".ljust(justify_width), :cyan) buffer << "#{justified_key} #{value}\n" else # Arrays and hashes get indented on their own lines. buffer << ui.color("#{key}:\n", :cyan) lines = text_format(value).split("\n") lines.each { |line| buffer << " #{line}\n" } end end elsif data.kind_of?(Array) data.each_index do |index| item = data[index] buffer << text_format(data[index]) # Separate items with newlines if it's an array of hashes or an # array of arrays buffer << "\n" if !is_singleton(data[index]) && index != data.size - 1 end else buffer << "#{data}\n" end buffer end def is_singleton(value) !(value.kind_of?(Array) || value.respond_to?(:keys)) end end end end end chef-12.14.60/lib/chef/knife/core/ui.rb000066400000000000000000000206301276456504500173320ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Brown () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-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 "forwardable" require "chef/platform/query_helpers" require "chef/knife/core/generic_presenter" require "tempfile" class Chef class Knife #==Chef::Knife::UI # The User Interaction class used by knife. class UI extend Forwardable attr_reader :stdout attr_reader :stderr attr_reader :stdin attr_reader :config attr_reader :presenter def_delegator :@presenter, :format_list_for_display def_delegator :@presenter, :format_for_display def_delegator :@presenter, :format_cookbook_list_for_display def initialize(stdout, stderr, stdin, config) @stdout, @stderr, @stdin, @config = stdout, stderr, stdin, config @presenter = Chef::Knife::Core::GenericPresenter.new(self, config) end # Creates a new +presenter_class+ object and uses it to format structured # data for display. By default, a Chef::Knife::Core::GenericPresenter # object is used. def use_presenter(presenter_class) @presenter = presenter_class.new(self, config) end def highline @highline ||= begin require "highline" HighLine.new end end # Prints a message to stdout. Aliased as +info+ for compatibility with # the logger API. def msg(message) begin stdout.puts message rescue Errno::EPIPE => e raise e if @config[:verbosity] >= 2 exit 0 end end # Prints a msg to stderr. Used for info, warn, error, and fatal. def log(message) begin stderr.puts message rescue Errno::EPIPE => e raise e if @config[:verbosity] >= 2 exit 0 end end alias :info :log alias :err :log # Print a warning message def warn(message) log("#{color('WARNING:', :yellow, :bold)} #{message}") end # Print an error message def error(message) log("#{color('ERROR:', :red, :bold)} #{message}") end # Print a message describing a fatal error. def fatal(message) log("#{color('FATAL:', :red, :bold)} #{message}") end def color(string, *colors) if color? highline.color(string, *colors) else string end end # Should colored output be used? For output to a terminal, this is # determined by the value of `config[:color]`. When output is not to a # terminal, colored output is never used def color? Chef::Config[:color] && stdout.tty? end def ask(*args, &block) highline.ask(*args, &block) end def list(*args) highline.list(*args) end # Formats +data+ using the configured presenter and outputs the result # via +msg+. Formatting can be customized by configuring a different # presenter. See +use_presenter+ def output(data) msg @presenter.format(data) end # Determines if the output format is a data interchange format, i.e., # JSON or YAML def interchange? @presenter.interchange? end def ask_question(question, opts = {}) question = question + "[#{opts[:default]}] " if opts[:default] if opts[:default] && config[:defaults] opts[:default] else stdout.print question a = stdin.readline.strip if opts[:default] a.empty? ? opts[:default] : a else a end end end def pretty_print(data) begin stdout.puts data rescue Errno::EPIPE => e raise e if @config[:verbosity] >= 2 exit 0 end end # Hash -> Hash # Works the same as edit_data but # returns a hash rather than a JSON string/Fully inflated object def edit_hash(hash) raw = edit_data(hash, false) Chef::JSONCompat.parse(raw) end def edit_data(data, parse_output = true, object_class: nil) output = Chef::JSONCompat.to_json_pretty(data) if !config[:disable_editing] Tempfile.open([ "knife-edit-", ".json" ]) do |tf| tf.sync = true tf.puts output tf.close raise "Please set EDITOR environment variable" unless system("#{config[:editor]} #{tf.path}") output = IO.read(tf.path) end end if parse_output if object_class.nil? Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please pass in the class to inflate or use #edit_hash") Chef::JSONCompat.from_json(output) else object_class.from_hash(Chef::JSONCompat.parse(output)) end else output end end def edit_object(klass, name) object = klass.load(name) output = edit_data(object, object_class: klass) # Only make the save if the user changed the object. # # Output JSON for the original (object) and edited (output), then parse # them without reconstituting the objects into real classes # (create_additions=false). Then, compare the resulting simple objects, # which will be Array/Hash/String/etc. # # We wouldn't have to do these shenanigans if all the editable objects # implemented to_hash, or if to_json against a hash returned a string # with stable key order. object_parsed_again = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(object)) output_parsed_again = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(output)) if object_parsed_again != output_parsed_again output.save self.msg("Saved #{output}") else self.msg("Object unchanged, not saving") end output(format_for_display(object)) if config[:print_after] end def confirmation_instructions(default_choice) case default_choice when true "? (Y/n) " when false "? (y/N) " else "? (Y/N) " end end # See confirm method for argument information def confirm_without_exit(question, append_instructions = true, default_choice = nil) return true if config[:yes] stdout.print question stdout.print confirmation_instructions(default_choice) if append_instructions answer = stdin.readline answer.chomp! case answer when "Y", "y" true when "N", "n" self.msg("You said no, so I'm done here.") false when "" unless default_choice.nil? default_choice else self.msg("I have no idea what to do with '#{answer}'") self.msg("Just say Y or N, please.") confirm_without_exit(question, append_instructions, default_choice) end else self.msg("I have no idea what to do with '#{answer}'") self.msg("Just say Y or N, please.") confirm_without_exit(question, append_instructions, default_choice) end end # # Not the ideal signature for a function but we need to stick with this # for now until we get a chance to break our API in Chef 12. # # question => Question to print before asking for confirmation # append_instructions => Should print '? (Y/N)' as instructions # default_choice => Set to true for 'Y', and false for 'N' as default answer # def confirm(question, append_instructions = true, default_choice = nil) unless confirm_without_exit(question, append_instructions, default_choice) exit 3 end true end end end end chef-12.14.60/lib/chef/knife/data_bag_create.rb000066400000000000000000000045241276456504500210360ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Falcon () # Copyright:: Copyright 2009-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/data_bag_secret_options" class Chef class Knife class DataBagCreate < Knife include DataBagSecretOptions deps do require "chef/data_bag" require "chef/encrypted_data_bag_item" end banner "knife data bag create BAG [ITEM] (options)" category "data bag" def run @data_bag_name, @data_bag_item_name = @name_args if @data_bag_name.nil? show_usage ui.fatal("You must specify a data bag name") exit 1 end begin Chef::DataBag.validate_name!(@data_bag_name) rescue Chef::Exceptions::InvalidDataBagName => e ui.fatal(e.message) exit(1) end # create the data bag begin rest.post("data", { "name" => @data_bag_name }) ui.info("Created data_bag[#{@data_bag_name}]") rescue Net::HTTPServerException => e raise unless e.to_s =~ /^409/ ui.info("Data bag #{@data_bag_name} already exists") end # if an item is specified, create it, as well if @data_bag_item_name create_object({ "id" => @data_bag_item_name }, "data_bag_item[#{@data_bag_item_name}]") do |output| item = Chef::DataBagItem.from_hash( if encryption_secret_provided? Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret) else output end ) item.data_bag(@data_bag_name) rest.post("data/#{@data_bag_name}", item) end end end end end end chef-12.14.60/lib/chef/knife/data_bag_delete.rb000066400000000000000000000026341276456504500210350ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class DataBagDelete < Knife deps do require "chef/data_bag" end banner "knife data bag delete BAG [ITEM] (options)" category "data bag" def run if @name_args.length == 2 delete_object(Chef::DataBagItem, @name_args[1], "data_bag_item") do rest.delete("data/#{@name_args[0]}/#{@name_args[1]}") end elsif @name_args.length == 1 delete_object(Chef::DataBag, @name_args[0], "data_bag") do rest.delete("data/#{@name_args[0]}") end else show_usage ui.fatal("You must specify at least a data bag name") exit 1 end end end end end chef-12.14.60/lib/chef/knife/data_bag_edit.rb000066400000000000000000000046061276456504500205210ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Falcon () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/data_bag_secret_options" class Chef class Knife class DataBagEdit < Knife include DataBagSecretOptions deps do require "chef/data_bag_item" require "chef/encrypted_data_bag_item" end banner "knife data bag edit BAG ITEM (options)" category "data bag" def load_item(bag, item_name) item = Chef::DataBagItem.load(bag, item_name) if encrypted?(item.raw_data) if encryption_secret_provided_ignore_encrypt_flag? return Chef::EncryptedDataBagItem.new(item, read_secret).to_hash, true else ui.fatal("You cannot edit an encrypted data bag without providing the secret.") exit(1) end else return item, false end end def run if @name_args.length != 2 stdout.puts "You must supply the data bag and an item to edit" stdout.puts opt_parser exit 1 end item, was_encrypted = load_item(@name_args[0], @name_args[1]) edited_item = edit_hash(item) if was_encrypted || encryption_secret_provided? ui.info("Encrypting data bag using provided secret.") item_to_save = Chef::EncryptedDataBagItem.encrypt_data_bag_item(edited_item, read_secret) else ui.info("Saving data bag unencrypted. To encrypt it, provide an appropriate secret.") item_to_save = edited_item end rest.put("data/#{@name_args[0]}/#{@name_args[1]}", item_to_save) stdout.puts("Saved data_bag_item[#{@name_args[1]}]") ui.output(edited_item) if config[:print_after] end end end end chef-12.14.60/lib/chef/knife/data_bag_from_file.rb000066400000000000000000000064341276456504500215370ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Falcon () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/util/path_helper" require "chef/knife/data_bag_secret_options" class Chef class Knife class DataBagFromFile < Knife include DataBagSecretOptions deps do require "chef/data_bag" require "chef/data_bag_item" require "chef/knife/core/object_loader" require "chef/json_compat" require "chef/encrypted_data_bag_item" end banner "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)" category "data bag" option :all, :short => "-a", :long => "--all", :description => "Upload all data bags or all items for specified data bags" def loader @loader ||= Knife::Core::ObjectLoader.new(DataBagItem, ui) end def run if config[:all] == true load_all_data_bags(@name_args) else if @name_args.size < 2 ui.msg(opt_parser) exit(1) end @data_bag = @name_args.shift load_data_bag_items(@data_bag, @name_args) end end private def data_bags_path @data_bag_path ||= "data_bags" end def find_all_data_bags loader.find_all_object_dirs("./#{data_bags_path}") end def find_all_data_bag_items(data_bag) loader.find_all_objects("./#{data_bags_path}/#{data_bag}") end def load_all_data_bags(args) data_bags = args.empty? ? find_all_data_bags : [args.shift] data_bags.each do |data_bag| load_data_bag_items(data_bag) end end def load_data_bag_items(data_bag, items = nil) items ||= find_all_data_bag_items(data_bag) item_paths = normalize_item_paths(items) item_paths.each do |item_path| item = loader.load_from("#{data_bags_path}", data_bag, item_path) item = if encryption_secret_provided? Chef::EncryptedDataBagItem.encrypt_data_bag_item(item, read_secret) else item end dbag = Chef::DataBagItem.new dbag.data_bag(data_bag) dbag.raw_data = item dbag.save ui.info("Updated data_bag_item[#{dbag.data_bag}::#{dbag.id}]") end end def normalize_item_paths(args) paths = Array.new args.each do |path| if File.directory?(path) paths.concat(Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(path), "*.json"))) else paths << path end end paths end end end end chef-12.14.60/lib/chef/knife/data_bag_list.rb000066400000000000000000000021431276456504500205410ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class DataBagList < Knife deps do require "chef/data_bag" end banner "knife data bag list (options)" category "data bag" option :with_uri, :short => "-w", :long => "--with-uri", :description => "Show corresponding URIs" def run output(format_list_for_display(Chef::DataBag.list)) end end end end chef-12.14.60/lib/chef/knife/data_bag_secret_options.rb000066400000000000000000000121111276456504500226220ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "mixlib/cli" require "chef/config" require "chef/encrypted_data_bag_item/check_encrypted" class Chef class Knife module DataBagSecretOptions include Mixlib::CLI include Chef::EncryptedDataBagItem::CheckEncrypted # The config object is populated by knife#merge_configs with knife.rb `knife[:*]` config values, but they do # not overwrite the command line properties. It does mean, however, that `knife[:secret]` and `--secret-file` # passed at the same time populate both `config[:secret]` and `config[:secret_file]`. We cannot differentiate # the valid case (`knife[:secret]` in config file and `--secret-file` on CL) and the invalid case (`--secret` # and `--secret-file` on the CL) - thats why I'm storing the CL options in a different config key if they # are provided. def self.included(base) base.option :secret, :short => "-s SECRET", :long => "--secret ", :description => "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'", # Need to store value from command line in separate variable - knife#merge_configs populates same keys # on config object from :proc => Proc.new { |s| set_cl_secret(s) } base.option :secret_file, :long => "--secret-file SECRET_FILE", :description => "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret_file'", :proc => Proc.new { |sf| set_cl_secret_file(sf) } base.option :encrypt, :long => "--encrypt", :description => "If 'secret' or 'secret_file' is present in your config, then encrypt data bags using it", :boolean => true, :default => false end def encryption_secret_provided? base_encryption_secret_provided? end def encryption_secret_provided_ignore_encrypt_flag? base_encryption_secret_provided?(false) end def read_secret # Moving the non 'compile-time' requires into here to speed up knife command loading # IE, if we are not running 'knife data bag *' we don't need to load 'chef/encrypted_data_bag_item' require "chef/encrypted_data_bag_item" if has_cl_secret? config[:secret] elsif has_cl_secret_file? Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) elsif secret = knife_config[:secret] secret else secret_file = knife_config[:secret_file] Chef::EncryptedDataBagItem.load_secret(secret_file) end end def validate_secrets if has_cl_secret? && has_cl_secret_file? ui.fatal("Please specify only one of --secret, --secret-file") exit(1) end if knife_config[:secret] && knife_config[:secret_file] ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config file") exit(1) end end private ## # Determine if the user has specified an appropriate secret for encrypting data bag items. # @returns boolean def base_encryption_secret_provided?(need_encrypt_flag = true) validate_secrets return true if has_cl_secret? || has_cl_secret_file? if need_encrypt_flag if config[:encrypt] unless knife_config[:secret] || knife_config[:secret_file] ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") exit(1) end return true end return false elsif knife_config[:secret] || knife_config[:secret_file] # Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret return true end return false end def has_cl_secret? Chef::Config[:knife].has_key?(:cl_secret) end def self.set_cl_secret(s) Chef::Config[:knife][:cl_secret] = s end def has_cl_secret_file? Chef::Config[:knife].has_key?(:cl_secret_file) end def self.set_cl_secret_file(sf) Chef::Config[:knife][:cl_secret_file] = sf end def knife_config Chef::Config.key?(:knife) ? Chef::Config[:knife] : {} end end end end chef-12.14.60/lib/chef/knife/data_bag_show.rb000066400000000000000000000051621276456504500205520ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Falcon () # Copyright:: Copyright 2009-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/data_bag_secret_options" class Chef class Knife class DataBagShow < Knife include DataBagSecretOptions deps do require "chef/data_bag" require "chef/encrypted_data_bag_item" end banner "knife data bag show BAG [ITEM] (options)" category "data bag" def run display = case @name_args.length when 2 # Bag and Item names provided secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data encrypted = encrypted?(raw_data) if encrypted && secret # Users do not need to pass --encrypt to read data, we simply try to use the provided secret ui.info("Encrypted data bag detected, decrypting with provided secret.") raw = Chef::EncryptedDataBagItem.load(@name_args[0], @name_args[1], secret) format_for_display(raw.to_hash) elsif encrypted && !secret ui.warn("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.") format_for_display(raw_data) else ui.warn("Unencrypted data bag detected, ignoring any provided secret options.") format_for_display(raw_data) end when 1 # Only Bag name provided format_list_for_display(Chef::DataBag.load(@name_args[0])) else stdout.puts opt_parser exit(1) end output(display) end end end end chef-12.14.60/lib/chef/knife/delete.rb000066400000000000000000000062721276456504500172350ustar00rootroot00000000000000require "chef/chef_fs/knife" class Chef class Knife class Delete < Chef::ChefFS::Knife banner "knife delete [PATTERN1 ... PATTERNn]" category "path-based" deps do require "chef/chef_fs/file_system" end option :recurse, :short => "-r", :long => "--[no-]recurse", :boolean => true, :default => false, :description => "Delete directories recursively." option :both, :long => "--both", :boolean => true, :default => false, :description => "Delete both the local and remote copies." option :local, :long => "--local", :boolean => true, :default => false, :description => "Delete the local copy (leave the remote copy)." def run if name_args.length == 0 show_usage ui.fatal("You must specify at least one argument. If you want to delete everything in this directory, run \"knife delete --recurse .\"") exit 1 end # Get the matches (recursively) error = false if config[:local] pattern_args.each do |pattern| Chef::ChefFS::FileSystem.list(local_fs, pattern).each do |result| if delete_result(result) error = true end end end elsif config[:both] pattern_args.each do |pattern| Chef::ChefFS::FileSystem.list_pairs(pattern, chef_fs, local_fs).each do |chef_result, local_result| if delete_result(chef_result, local_result) error = true end end end else # Remote only pattern_args.each do |pattern| Chef::ChefFS::FileSystem.list(chef_fs, pattern).each do |result| if delete_result(result) error = true end end end end if error exit 1 end end def format_path_with_root(entry) root = entry.root == chef_fs ? " (remote)" : " (local)" "#{format_path(entry)}#{root}" end def delete_result(*results) deleted_any = false found_any = false error = false results.each do |result| begin result.delete(config[:recurse]) deleted_any = true found_any = true rescue Chef::ChefFS::FileSystem::NotFoundError # This is not an error unless *all* of them were not found rescue Chef::ChefFS::FileSystem::MustDeleteRecursivelyError => e ui.error "#{format_path_with_root(e.entry)} must be deleted recursively! Pass -r to knife delete." found_any = true error = true rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e ui.error "#{format_path_with_root(e.entry)} #{e.reason}." found_any = true error = true end end if deleted_any output("Deleted #{format_path(results[0])}") elsif !found_any ui.error "#{format_path(results[0])}: No such file or directory" error = true end error end end end end chef-12.14.60/lib/chef/knife/deps.rb000066400000000000000000000112151276456504500167170ustar00rootroot00000000000000require "chef/chef_fs/knife" class Chef class Knife class Deps < Chef::ChefFS::Knife banner "knife deps PATTERN1 [PATTERNn]" category "path-based" deps do require "chef/chef_fs/file_system" require "chef/run_list" end option :recurse, :long => "--[no-]recurse", :boolean => true, :description => "List dependencies recursively (default: true). Only works with --tree." option :tree, :long => "--tree", :boolean => true, :description => "Show dependencies in a visual tree. May show duplicates." option :remote, :long => "--remote", :boolean => true, :description => "List dependencies on the server instead of the local filesystem" attr_accessor :exit_code def run if config[:recurse] == false && !config[:tree] ui.error "--no-recurse requires --tree" exit(1) end config[:recurse] = true if config[:recurse].nil? @root = config[:remote] ? chef_fs : local_fs dependencies = {} pattern_args.each do |pattern| Chef::ChefFS::FileSystem.list(@root, pattern).each do |entry| if config[:tree] print_dependencies_tree(entry, dependencies) else print_flattened_dependencies(entry, dependencies) end end end exit exit_code if exit_code end def print_flattened_dependencies(entry, dependencies) if !dependencies[entry.path] dependencies[entry.path] = get_dependencies(entry) dependencies[entry.path].each do |child| child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child) print_flattened_dependencies(child_entry, dependencies) end output format_path(entry) end end def print_dependencies_tree(entry, dependencies, printed = {}, depth = 0) dependencies[entry.path] = get_dependencies(entry) if !dependencies[entry.path] output "#{' ' * depth}#{format_path(entry)}" if !printed[entry.path] && (config[:recurse] || depth == 0) printed[entry.path] = true dependencies[entry.path].each do |child| child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child) print_dependencies_tree(child_entry, dependencies, printed, depth + 1) end end end def get_dependencies(entry) begin if entry.parent && entry.parent.path == "/cookbooks" return entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" } elsif entry.parent && entry.parent.path == "/nodes" node = Chef::JSONCompat.parse(entry.read) result = [] if node["chef_environment"] && node["chef_environment"] != "_default" result << "/environments/#{node['chef_environment']}.json" end if node["run_list"] result += dependencies_from_runlist(node["run_list"]) end result elsif entry.parent && entry.parent.path == "/roles" role = Chef::JSONCompat.parse(entry.read) result = [] if role["run_list"] dependencies_from_runlist(role["run_list"]).each do |dependency| result << dependency if !result.include?(dependency) end end if role["env_run_lists"] role["env_run_lists"].each_pair do |env, run_list| dependencies_from_runlist(run_list).each do |dependency| result << dependency if !result.include?(dependency) end end end result elsif !entry.exists? raise Chef::ChefFS::FileSystem::NotFoundError.new(entry) else [] end rescue Chef::ChefFS::FileSystem::NotFoundError => e ui.error "#{format_path(e.entry)}: No such file or directory" self.exit_code = 2 [] end end def dependencies_from_runlist(run_list) chef_run_list = Chef::RunList.new chef_run_list.reset!(run_list) chef_run_list.map do |run_list_item| case run_list_item.type when :role "/roles/#{run_list_item.name}.json" when :recipe if run_list_item.name =~ /(.+)::[^:]*/ "/cookbooks/#{$1}" else "/cookbooks/#{run_list_item.name}" end else raise "Unknown run list item type #{run_list_item.type}" end end end end end end chef-12.14.60/lib/chef/knife/diff.rb000066400000000000000000000045531276456504500167030ustar00rootroot00000000000000require "chef/chef_fs/knife" class Chef class Knife class Diff < Chef::ChefFS::Knife banner "knife diff PATTERNS" category "path-based" deps do require "chef/chef_fs/command_line" end option :recurse, :long => "--[no-]recurse", :boolean => true, :default => true, :description => "List directories recursively." option :name_only, :long => "--name-only", :boolean => true, :description => "Only show names of modified files." option :name_status, :long => "--name-status", :boolean => true, :description => "Only show names and statuses of modified files: Added, Deleted, Modified, and Type Changed." option :diff_filter, :long => "--diff-filter=[(A|D|M|T)...[*]]", :description => "Select only files that are Added (A), Deleted (D), Modified (M), or have their type (i.e. regular file, directory) changed (T). Any combination of the filter characters (including none) can be used. When * (All-or-none) is added to the combination, all paths are selected if there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected." option :cookbook_version, :long => "--cookbook-version VERSION", :description => "Version of cookbook to download (if there are multiple versions and cookbook_versions is false)" def run if config[:name_only] output_mode = :name_only end if config[:name_status] output_mode = :name_status end patterns = pattern_args_from(name_args.length > 0 ? name_args : [ "" ]) # Get the matches (recursively) error = false begin patterns.each do |pattern| found_error = Chef::ChefFS::CommandLine.diff_print(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, output_mode, proc { |entry| format_path(entry) }, config[:diff_filter], ui ) do |diff| stdout.print diff end error = true if found_error end rescue Chef::ChefFS::FileSystem::OperationFailedError => e ui.error "Failed on #{format_path(e.entry)} in #{e.operation}: #{e.message}" error = true end if error exit 1 end end end end end chef-12.14.60/lib/chef/knife/download.rb000066400000000000000000000040071276456504500175740ustar00rootroot00000000000000require "chef/chef_fs/knife" class Chef class Knife class Download < Chef::ChefFS::Knife banner "knife download PATTERNS" category "path-based" deps do require "chef/chef_fs/command_line" end option :recurse, :long => "--[no-]recurse", :boolean => true, :default => true, :description => "List directories recursively." option :purge, :long => "--[no-]purge", :boolean => true, :default => false, :description => "Delete matching local files and directories that do not exist remotely." option :force, :long => "--[no-]force", :boolean => true, :default => false, :description => "Force upload of files even if they match (quicker and harmless, but doesn't print out what it changed)" option :dry_run, :long => "--dry-run", :short => "-n", :boolean => true, :default => false, :description => "Don't take action, only print what would happen" option :diff, :long => "--[no-]diff", :boolean => true, :default => true, :description => "Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff" option :cookbook_version, :long => "--cookbook-version VERSION", :description => "Version of cookbook to download (if there are multiple versions and cookbook_versions is false)" def run if name_args.length == 0 show_usage ui.fatal("You must specify at least one argument. If you want to download everything in this directory, run \"knife download .\"") exit 1 end error = false pattern_args.each do |pattern| if Chef::ChefFS::FileSystem.copy_to(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, config, ui, proc { |entry| format_path(entry) }) error = true end end if error exit 1 end end end end end chef-12.14.60/lib/chef/knife/edit.rb000066400000000000000000000042751276456504500167210ustar00rootroot00000000000000require "chef/chef_fs/knife" class Chef class Knife class Edit < Chef::ChefFS::Knife banner "knife edit [PATTERN1 ... PATTERNn]" category "path-based" deps do require "chef/chef_fs/file_system" require "chef/chef_fs/file_system/not_found_error" end option :local, :long => "--local", :boolean => true, :description => "Show local files instead of remote" def run # Get the matches (recursively) error = false pattern_args.each do |pattern| Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result| if result.dir? ui.error "#{format_path(result)}: is a directory" if pattern.exact_path error = true else begin new_value = edit_text(result.read, File.extname(result.name)) if new_value result.write(new_value) output "Updated #{format_path(result)}" else output "#{format_path(result)} unchanged" end rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e ui.error "#{format_path(e.entry)}: #{e.reason}." error = true rescue Chef::ChefFS::FileSystem::NotFoundError => e ui.error "#{format_path(e.entry)}: No such file or directory" error = true end end end end if error exit 1 end end def edit_text(text, extension) if !config[:disable_editing] Tempfile.open([ "knife-edit-", extension ]) do |file| # Write the text to a temporary file file.write(text) file.close # Let the user edit the temporary file if !system("#{config[:editor]} #{file.path}") raise "Please set EDITOR environment variable. See https://docs.chef.io/knife_using.html for details." end result_text = IO.read(file.path) return result_text if result_text != text end end end end end end chef-12.14.60/lib/chef/knife/environment_compare.rb000066400000000000000000000073241276456504500220440ustar00rootroot00000000000000# # Author:: Sander Botman () # Copyright:: Copyright 2013-2016, Sander Botman. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class EnvironmentCompare < Knife deps do require "chef/environment" end banner "knife environment compare [ENVIRONMENT..] (options)" option :all, :short => "-a", :long => "--all", :description => "Show all cookbooks", :boolean => true option :mismatch, :short => "-m", :long => "--mismatch", :description => "Only show mismatching versions", :boolean => true def run # Get the commandline environments or all if none are provided. environments = environment_list # Get a list of all cookbooks that have constraints and their environment. constraints = constraint_list(environments) # Get the total list of cookbooks that have constraints cookbooks = cookbook_list(constraints) # If we cannot find any cookbooks, we can stop here. if cookbooks.nil? || cookbooks.empty? ui.error "Cannot find any environment cookbook constraints" exit 1 end # Get all cookbooks so we can compare them all cookbooks = rest.get("/cookbooks?num_versions=1") if config[:all] # display matrix view of in the requested format. if config[:format] == "summary" matrix = matrix_output(cookbooks, constraints) ui.output(matrix) else ui.output(constraints) end end private def environment_list environments = [] unless @name_args.nil? || @name_args.empty? @name_args.each { |name| environments << name } else environments = Chef::Environment.list end end def constraint_list(environments) constraints = {} environments.each do |env, url| # Because you cannot modify the default environment I filter it out here. unless env == "_default" envdata = Chef::Environment.load(env) ver = envdata.cookbook_versions constraints[env] = ver end end constraints end def cookbook_list(constraints) result = {} constraints.each { |env, cb| result.merge!(cb) } result end def matrix_output(cookbooks, constraints) rows = [ "" ] environments = [] constraints.each { |e, v| environments << e.to_s } columns = environments.count + 1 environments.each { |env| rows << ui.color(env, :bold) } cookbooks.each do |c, v| total = [] environments.each { |n| total << constraints[n][c] } if total.uniq.count == 1 next if config[:mismatch] color = :white else color = :yellow end rows << ui.color(c, :bold) environments.each do |e| tag = constraints[e][c] || "latest" rows << ui.color(tag, color) end end ui.list(rows, :uneven_columns_across, columns) end end end end chef-12.14.60/lib/chef/knife/environment_create.rb000066400000000000000000000027351276456504500216620ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class EnvironmentCreate < Knife deps do require "chef/environment" require "chef/json_compat" end banner "knife environment create ENVIRONMENT (options)" option :description, :short => "-d DESCRIPTION", :long => "--description DESCRIPTION", :description => "The environment description" def run env_name = @name_args[0] if env_name.nil? show_usage ui.fatal("You must specify an environment name") exit 1 end env = Chef::Environment.new env.name(env_name) env.description(config[:description]) if config[:description] create_object(env, object_class: Chef::Environment) end end end end chef-12.14.60/lib/chef/knife/environment_delete.rb000066400000000000000000000022531276456504500216540ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class EnvironmentDelete < Knife deps do require "chef/environment" require "chef/json_compat" end banner "knife environment delete ENVIRONMENT (options)" def run env_name = @name_args[0] if env_name.nil? show_usage ui.fatal("You must specify an environment name") exit 1 end delete_object(Chef::Environment, env_name) end end end end chef-12.14.60/lib/chef/knife/environment_edit.rb000066400000000000000000000022451276456504500213400ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class EnvironmentEdit < Knife deps do require "chef/environment" require "chef/json_compat" end banner "knife environment edit ENVIRONMENT (options)" def run env_name = @name_args[0] if env_name.nil? show_usage ui.fatal("You must specify an environment name") exit 1 end edit_object(Chef::Environment, env_name) end end end end chef-12.14.60/lib/chef/knife/environment_from_file.rb000066400000000000000000000043111276456504500223510ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-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 Knife class EnvironmentFromFile < Knife deps do require "chef/environment" require "chef/knife/core/object_loader" end banner "knife environment from file FILE [FILE..] (options)" option :all, :short => "-a", :long => "--all", :description => "Upload all environments" def loader @loader ||= Knife::Core::ObjectLoader.new(Chef::Environment, ui) end def environments_path @environments_path ||= "environments" end def find_all_environments loader.find_all_objects("./#{environments_path}/") end def load_all_environments environments = find_all_environments if environments.empty? ui.fatal("Unable to find any environment files in '#{environments_path}'") exit(1) end environments.each do |env| load_environment(env) end end def load_environment(env) updated = loader.load_from("environments", env) updated.save output(format_for_display(updated)) if config[:print_after] ui.info("Updated Environment #{updated.name}") end def run if config[:all] == true load_all_environments else if @name_args[0].nil? show_usage ui.fatal("You must specify a file to load") exit 1 end @name_args.each do |arg| load_environment(arg) end end end end end end chef-12.14.60/lib/chef/knife/environment_list.rb000066400000000000000000000022011276456504500213560ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class EnvironmentList < Knife deps do require "chef/environment" require "chef/json_compat" end banner "knife environment list (options)" option :with_uri, :short => "-w", :long => "--with-uri", :description => "Show corresponding URIs" def run output(format_list_for_display(Chef::Environment.list)) end end end end chef-12.14.60/lib/chef/knife/environment_show.rb000066400000000000000000000024021276456504500213660ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class EnvironmentShow < Knife include Knife::Core::MultiAttributeReturnOption deps do require "chef/environment" require "chef/json_compat" end banner "knife environment show ENVIRONMENT (options)" def run env_name = @name_args[0] if env_name.nil? show_usage ui.fatal("You must specify an environment name") exit 1 end env = Chef::Environment.load(env_name) output(format_for_display(env)) end end end end chef-12.14.60/lib/chef/knife/exec.rb000066400000000000000000000052331276456504500167130ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo ( "-E CODE", :long => "--exec CODE", :description => "a string of Chef code to execute" option :script_path, :short => "-p PATH:PATH", :long => "--script-path PATH:PATH", :description => "A colon-separated path to look for scripts in", :proc => lambda { |o| o.split(":") } deps do require "chef/shell/ext" end def run config[:script_path] ||= Array(Chef::Config[:script_path]) # Default script paths are chef-repo/.chef/scripts and ~/.chef/scripts config[:script_path] << File.join(Chef::Knife.chef_config_dir, "scripts") if Chef::Knife.chef_config_dir Chef::Util::PathHelper.home(".chef", "scripts") { |p| config[:script_path] << p } scripts = Array(name_args) context = Object.new Shell::Extensions.extend_context_object(context) if config[:exec] context.instance_eval(config[:exec], "-E Argument", 0) elsif !scripts.empty? scripts.each do |script| file = find_script(script) context.instance_eval(IO.read(file), file, 0) end else script = STDIN.read context.instance_eval(script, "STDIN", 0) end end def find_script(x) # Try to find a script. First try expanding the path given. script = File.expand_path(x) return script if File.exists?(script) # Failing that, try searching the script path. If we can't find # anything, fail gracefully. Chef::Log.debug("Searching script_path: #{config[:script_path].inspect}") config[:script_path].each do |path| path = File.expand_path(path) test = File.join(path, x) Chef::Log.debug("Testing: #{test}") if File.exists?(test) script = test Chef::Log.debug("Found: #{test}") return script end end ui.error("\"#{x}\" not found in current directory or script_path, giving up.") exit(1) end end chef-12.14.60/lib/chef/knife/help.rb000066400000000000000000000063541276456504500167240ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Knife class Help < Chef::Knife banner "knife help [list|TOPIC]" def run if name_args.empty? ui.info "Usage: knife SUBCOMMAND (options)" ui.msg "" # This command is atypical, the user is likely not interested in usage of # this command, but knife in general. So hack the banner. opt_parser.banner = "General Knife Options:" ui.msg opt_parser.to_s ui.msg "" ui.info "For further help:" ui.info(<<-MOAR_HELP) knife help list list help topics knife help knife show general knife help knife help TOPIC display the manual for TOPIC knife SUBCOMMAND --help show the options for a command MOAR_HELP exit 1 else @query = name_args.join("-") end case @query when "topics", "list" print_help_topics exit 1 when "intro", "knife" @topic = "knife" else @topic = find_manpages_for_query(@query) end manpage_path = find_manpage_path(@topic) exec "man #{manpage_path}" end def help_topics # The list of help topics is generated by a rake task from the available man pages # This constant is provided in help_topics.rb which is automatically required/loaded by the knife subcommand loader. HELP_TOPICS end def print_help_topics ui.info "Available help topics are: " help_topics.collect { |t| t.gsub(/knife-/, "") }.sort.each do |topic| ui.msg " #{topic}" end end def find_manpages_for_query(query) possibilities = help_topics.select do |manpage| ::File.fnmatch("knife-#{query}*", manpage) || ::File.fnmatch("#{query}*", manpage) end if possibilities.empty? ui.error "No help found for '#{query}'" ui.msg "" print_help_topics exit 1 elsif possibilities.size == 1 possibilities.first else ui.info "Multiple help topics match your query. Pick one:" ui.highline.choose(*possibilities) end end def find_manpage_path(topic) if ::File.exists?(::File.expand_path("../distro/common/man/man1/#{topic}.1", CHEF_ROOT)) # If we've provided the man page in the gem, give that return ::File.expand_path("../distro/common/man/man1/#{topic}.1", CHEF_ROOT) else # Otherwise, we'll just be using MANPATH topic end end end end end chef-12.14.60/lib/chef/knife/help_topics.rb000066400000000000000000000011231276456504500202720ustar00rootroot00000000000000# Do not edit this file by hand # This file is autogenerated by the docs:list rake task from the available manpages HELP_TOPICS = ["chef-shell", "knife-bootstrap", "knife-client", "knife-configure", "knife-cookbook-site", "knife-cookbook", "knife-data-bag", "knife-delete", "knife-deps", "knife-diff", "knife-download", "knife-edit", "knife-environment", "knife-exec", "knife-index-rebuild", "knife-list", "knife-node", "knife-raw", "knife-recipe-list", "knife-role", "knife-search", "knife-show", "knife-ssh", "knife-status", "knife-tag", "knife-upload", "knife-user", "knife-xargs", "knife"] chef-12.14.60/lib/chef/knife/index_rebuild.rb000066400000000000000000000103361276456504500206040ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class IndexRebuild < Knife banner "knife index rebuild (options)" option :yes, :short => "-y", :long => "--yes", :boolean => true, :description => "don't bother to ask if I'm sure" def run api_info = grab_api_info if unsupported_version?(api_info) unsupported_server_message(api_info) exit 1 else deprecated_server_message nag output rest.post("/search/reindex", {}) end end def grab_api_info # Since we don't yet have any endpoints that implement an # OPTIONS handler, we need to get our version header # information in a more roundabout way. We'll try to query # for a node we know won't exist; the 404 response that comes # back will give us what we want dummy_node = "knife_index_rebuild_test_#{rand(1000000)}" rest.get("/nodes/#{dummy_node}") rescue Net::HTTPServerException => exception r = exception.response parse_api_info(r) end # Only Chef 11+ servers will have version information in their # headers, and only those servers will lack an API endpoint for # index rebuilding. def unsupported_version?(api_info) !!api_info["version"] end def unsupported_server_message(api_info) ui.error("Rebuilding the index is not available via knife for #{server_type(api_info)}s version 11.0.0 and above.") ui.info("Instead, run the '#{ctl_command(api_info)} reindex' command on the server itself.") end def deprecated_server_message ui.warn("'knife index rebuild' has been removed for Chef 11+ servers. It will continue to work for prior versions, however.") end def nag ui.info("This operation is destructive. Rebuilding the index may take some time.") ui.confirm("Continue") end # Chef 11 (and above) servers return various pieces of # information about the server in an +x-ops-api-info+ header. # This is a +;+ delimited string of key / value pairs, separated # by +=+. # # Given a Net::HTTPResponse object, this method extracts this # information (if present), and returns it as a hash. If no # such header is found, an empty hash is returned. def parse_api_info(response) value = response["x-ops-api-info"] if value kv = value.split(";") kv.inject({}) do |acc, pair| k, v = pair.split("=") acc[k] = v acc end else {} end end # Given an API info hash (see +#parse_api_info(response)+), # return a string describing the kind of server we're # interacting with (based on the +flavor+ field) def server_type(api_info) case api_info["flavor"] when "osc" "Open Source Chef Server" when "opc" "Private Chef Server" else # Generic fallback "Chef Server" end end # Given an API info hash (see +#parse_api_info(response)+), # return the name of the "server-ctl" command for the kind of # server we're interacting with (based on the +flavor+ field) def ctl_command(api_info) case api_info["flavor"] when "osc" "chef-server-ctl" when "opc" "private-chef-ctl" else # Generic fallback "chef-server-ctl" end end end end end chef-12.14.60/lib/chef/knife/key_create.rb000066400000000000000000000060611276456504500201020ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/key" require "chef/json_compat" require "chef/exceptions" class Chef class Knife # Service class for UserKeyCreate and ClientKeyCreate, # Implements common functionality of knife [user | org client] key create. # # @author Tyler Cloke # # @attr_accessor [Hash] cli input, see UserKeyCreate and ClientKeyCreate for what could populate it class KeyCreate attr_accessor :config def initialize(actor, actor_field_name, ui, config) @actor = actor @actor_field_name = actor_field_name @ui = ui @config = config end def public_key_or_key_name_error_msg <) # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Knife # Extendable module that class_eval's common options into UserKeyCreate and ClientKeyCreate # # @author Tyler Cloke module KeyCreateBase def self.included(includer) includer.class_eval do option :public_key, :short => "-p FILENAME", :long => "--public-key FILENAME", :description => "Public key for newly created key. If not passed, the server will create a key pair for you, but you must pass --key-name NAME in that case." option :file, :short => "-f FILE", :long => "--file FILE", :description => "Write the private key to a file, if you requested the server to create one." option :key_name, :short => "-k NAME", :long => "--key-name NAME", :description => "The name for your key. If you do not pass a name, you must pass --public-key, and the name will default to the fingerprint of the public key passed." option :expiration_date, :short => "-e DATE", :long => "--expiration-date DATE", :description => "Optionally pass the expiration date for the key in ISO 8601 fomatted string: YYYY-MM-DDTHH:MM:SSZ e.g. 2013-12-24T21:00:00Z. Defaults to infinity if not passed. UTC timezone assumed." end end end end end chef-12.14.60/lib/chef/knife/key_delete.rb000066400000000000000000000032301276456504500200740ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/key" class Chef class Knife # Service class for UserKeyDelete and ClientKeyDelete, used to delete keys. # Implements common functionality of knife [user | org client] key delete. # # @author Tyler Cloke # # @attr_accessor [Hash] cli input, see UserKeyDelete and ClientKeyDelete for what could populate it class KeyDelete def initialize(name, actor, actor_field_name, ui) @name = name @actor = actor @actor_field_name = actor_field_name @ui = ui end def confirm! @ui.confirm("Do you really want to delete the key named #{@name} for the #{@actor_field_name} named #{@actor}") end def print_destroyed @ui.info("Deleted key named #{@name} for the #{@actor_field_name} named #{@actor}") end def run key = Chef::Key.new(@actor, @actor_field_name) key.name(@name) confirm! key.destroy print_destroyed end end end end chef-12.14.60/lib/chef/knife/key_edit.rb000066400000000000000000000066151276456504500175710ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/key" require "chef/json_compat" require "chef/exceptions" class Chef class Knife # Service class for UserKeyEdit and ClientKeyEdit, # Implements common functionality of knife [user | org client] key edit. # # @author Tyler Cloke # # @attr_accessor [Hash] cli input, see UserKeyEdit and ClientKeyEdit for what could populate it class KeyEdit attr_accessor :config def initialize(original_name, actor, actor_field_name, ui, config) @original_name = original_name @actor = actor @actor_field_name = actor_field_name @ui = ui @config = config end def public_key_and_create_key_error_msg <) # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Knife # Extendable module that class_eval's common options into UserKeyEdit and ClientKeyEdit # # @author Tyler Cloke module KeyEditBase def self.included(includer) includer.class_eval do option :public_key, :short => "-p FILENAME", :long => "--public-key FILENAME", :description => "Replace the public_key field from a file on disk. If not passed, the public_key field will not change." option :create_key, :short => "-c", :long => "--create-key", :description => "Replace the public_key field with a key generated by the server. The private key will be returned." option :file, :short => "-f FILE", :long => "--file FILE", :description => "Write the private key to a file, if you requested the server to create one via --create-key." option :key_name, :short => "-k NAME", :long => "--key-name NAME", :description => "The new name for your key. Pass if you wish to update the name field of your key." option :expiration_date, :short => "-e DATE", :long => "--expiration-date DATE", :description => "Updates the expiration_date field of your key if passed. Pass in ISO 8601 fomatted string: YYYY-MM-DDTHH:MM:SSZ e.g. 2013-12-24T21:00:00Z or infinity. UTC timezone assumed." end end end end end chef-12.14.60/lib/chef/knife/key_list.rb000066400000000000000000000051641276456504500176150ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/key" require "chef/json_compat" require "chef/exceptions" class Chef class Knife # Service class for UserKeyList and ClientKeyList, used to list keys. # Implements common functionality of knife [user | org client] key list. # # @author Tyler Cloke # # @attr_accessor [Hash] cli input, see UserKeyList and ClientKeyList for what could populate it class KeyList attr_accessor :config def initialize(actor, list_method, ui, config) @actor = actor @list_method = list_method @ui = ui @config = config end def expired_and_non_expired_msg < max_length end keys.each do |key| next if !key["expired"] && @config[:only_expired] next if key["expired"] && @config[:only_non_expired] display = "#{colorize(key['name'].ljust(max_length))} #{key['uri']}" display = "#{display} (expired)" if key["expired"] display_info(display) end else keys.each do |key| next if !key["expired"] && @config[:only_expired] next if key["expired"] && @config[:only_non_expired] display_info(key["name"]) end end end end end end chef-12.14.60/lib/chef/knife/key_list_base.rb000066400000000000000000000027621276456504500206100ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Knife # Extendable module that class_eval's common options into UserKeyList and ClientKeyList # # @author Tyler Cloke module KeyListBase def self.included(includer) includer.class_eval do option :with_details, :short => "-w", :long => "--with-details", :description => "Show corresponding URIs and whether the key has expired or not." option :only_expired, :short => "-e", :long => "--only-expired", :description => "Only show expired keys." option :only_non_expired, :short => "-n", :long => "--only-non-expired", :description => "Only show non-expired keys." end end end end end chef-12.14.60/lib/chef/knife/key_show.rb000066400000000000000000000030061276456504500176130ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/key" require "chef/json_compat" require "chef/exceptions" class Chef class Knife # Service class for UserKeyShow and ClientKeyShow, used to show keys. # Implements common functionality of knife [user | org client] key show. # # @author Tyler Cloke # # @attr_accessor [Hash] cli input, see UserKeyShow and ClientKeyShow for what could populate it class KeyShow attr_accessor :config def initialize(name, actor, load_method, ui) @name = name @actor = actor @load_method = load_method @ui = ui end def display_output(key) @ui.output(@ui.format_for_display(key)) end def run key = Chef::Key.send(@load_method, @actor, @name) key.public_key(key.public_key.strip) display_output(key) end end end end chef-12.14.60/lib/chef/knife/list.rb000066400000000000000000000115351276456504500167440ustar00rootroot00000000000000require "chef/chef_fs/knife" class Chef class Knife class List < Chef::ChefFS::Knife banner "knife list [-dfR1p] [PATTERN1 ... PATTERNn]" category "path-based" deps do require "chef/chef_fs/file_system" require "highline" end option :recursive, :short => "-R", :boolean => true, :description => "List directories recursively" option :bare_directories, :short => "-d", :boolean => true, :description => "When directories match the pattern, do not show the directories' children" option :local, :long => "--local", :boolean => true, :description => "List local directory instead of remote" option :flat, :short => "-f", :long => "--flat", :boolean => true, :description => "Show a list of filenames rather than the prettified ls-like output normally produced" option :one_column, :short => "-1", :boolean => true, :description => "Show only one column of results" option :trailing_slashes, :short => "-p", :boolean => true, :description => "Show trailing slashes after directories" attr_accessor :exit_code def run patterns = name_args.length == 0 ? [""] : name_args # Get the top-level matches all_results = parallelize(pattern_args_from(patterns)) do |pattern| pattern_results = Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).to_a if pattern_results.first && !pattern_results.first.exists? && pattern.exact_path ui.error "#{format_path(pattern_results.first)}: No such file or directory" self.exit_code = 1 end pattern_results end.flatten(1).to_a # Process directories if !config[:bare_directories] dir_results = parallelize(all_results.select { |result| result.dir? }) do |result| add_dir_result(result) end.flatten(1) else dir_results = [] end # Process all other results results = all_results.select { |result| result.exists? && (!result.dir? || config[:bare_directories]) }.to_a # Flatten out directory results if necessary if config[:flat] dir_results.each do |result, children| results += children end dir_results = [] end # Sort by path for happy output results = results.sort_by { |result| result.path } dir_results = dir_results.sort_by { |result| result[0].path } # Print! if results.length == 0 && dir_results.length == 1 results = dir_results[0][1] dir_results = [] end print_result_paths results printed_something = results.length > 0 dir_results.each do |result, children| if printed_something output "" else printed_something = true end output "#{format_path(result)}:" print_results(children.map { |result| maybe_add_slash(result.display_name, result.dir?) }.sort, "") end exit self.exit_code if self.exit_code end def add_dir_result(result) begin children = result.children.sort_by { |child| child.name } rescue Chef::ChefFS::FileSystem::NotFoundError => e ui.error "#{format_path(e.entry)}: No such file or directory" return [] end result = [ [ result, children ] ] if config[:recursive] child_dirs = children.select { |child| child.dir? } result += parallelize(child_dirs) { |child| add_dir_result(child) }.flatten(1).to_a end result end def print_result_paths(results, indent = "") print_results(results.map { |result| maybe_add_slash(format_path(result), result.dir?) }, indent) end def print_results(results, indent) return if results.length == 0 print_space = results.map { |result| result.length }.max + 2 if config[:one_column] || !stdout.isatty columns = 0 else columns = HighLine::SystemExtensions.terminal_size[0] end current_line = "" results.each do |result| if current_line.length > 0 && current_line.length + print_space > columns output current_line.rstrip current_line = "" end if current_line.length == 0 current_line << indent end current_line << result current_line << (" " * (print_space - result.length)) end output current_line.rstrip if current_line.length > 0 end def maybe_add_slash(path, is_dir) if config[:trailing_slashes] && is_dir "#{path}/" else path end end end end end chef-12.14.60/lib/chef/knife/node_bulk_delete.rb000066400000000000000000000037571276456504500212640ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class NodeBulkDelete < Knife deps do require "chef/node" require "chef/json_compat" end banner "knife node bulk delete REGEX (options)" def run if name_args.length < 1 ui.fatal("You must supply a regular expression to match the results against") exit 42 end nodes_to_delete = {} matcher = /#{name_args[0]}/ all_nodes.each do |name, node| next unless name =~ matcher nodes_to_delete[name] = node end if nodes_to_delete.empty? ui.msg "No nodes match the expression /#{name_args[0]}/" exit 0 end ui.msg("The following nodes will be deleted:") ui.msg("") ui.msg(ui.list(nodes_to_delete.keys.sort, :columns_down)) ui.msg("") ui.confirm("Are you sure you want to delete these nodes") nodes_to_delete.sort.each do |name, node| node.destroy ui.msg("Deleted node #{name}") end end def all_nodes node_uris_by_name = Chef::Node.list node_uris_by_name.keys.inject({}) do |nodes_by_name, name| nodes_by_name[name] = Chef::Node.new.tap { |n| n.name(name) } nodes_by_name end end end end end chef-12.14.60/lib/chef/knife/node_create.rb000066400000000000000000000023031276456504500202320ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class NodeCreate < Knife deps do require "chef/node" require "chef/json_compat" end banner "knife node create NODE (options)" def run @node_name = @name_args[0] if @node_name.nil? show_usage ui.fatal("You must specify a node name") exit 1 end node = Chef::Node.new node.name(@node_name) create_object(node, object_class: Chef::Node) end end end end chef-12.14.60/lib/chef/knife/node_delete.rb000066400000000000000000000022001276456504500202250ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class NodeDelete < Knife deps do require "chef/node" require "chef/json_compat" end banner "knife node delete NODE (options)" def run @node_name = @name_args[0] if @node_name.nil? show_usage ui.fatal("You must specify a node name") exit 1 end delete_object(Chef::Node, @node_name) end end end end chef-12.14.60/lib/chef/knife/node_edit.rb000066400000000000000000000034171276456504500177230ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class NodeEdit < Knife deps do require "chef/node" require "chef/json_compat" require "chef/knife/core/node_editor" end banner "knife node edit NODE (options)" option :all_attributes, :short => "-a", :long => "--all", :boolean => true, :description => "Display all attributes when editing" def run if node_name.nil? show_usage ui.fatal("You must specify a node name") exit 1 end updated_node = node_editor.edit_node if updated_values = node_editor.updated? ui.info "Saving updated #{updated_values.join(', ')} on node #{node.name}" updated_node.save else ui.info "Node not updated, skipping node save" end end def node_name @node_name ||= @name_args[0] end def node_editor @node_editor ||= Knife::NodeEditor.new(node, ui, config) end def node @node ||= Chef::Node.load(node_name) end end end end chef-12.14.60/lib/chef/knife/node_environment_set.rb000066400000000000000000000025361276456504500222160ustar00rootroot00000000000000# # Author:: Jimmy McCrory () # Copyright:: Copyright 2014-2016, Jimmy McCrory # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class NodeEnvironmentSet < Knife deps do require "chef/node" end banner "knife node environment set NODE ENVIRONMENT" def run if @name_args.size < 2 ui.fatal "You must specify a node name and an environment." show_usage exit 1 else @node_name = @name_args[0] @environment = @name_args[1] end node = Chef::Node.load(@node_name) node.chef_environment = @environment node.save config[:attribute] = "chef_environment" output(format_for_display(node)) end end end end chef-12.14.60/lib/chef/knife/node_from_file.rb000066400000000000000000000024621276456504500207370ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class NodeFromFile < Knife deps do require "chef/node" require "chef/json_compat" require "chef/knife/core/object_loader" end banner "knife node from file FILE (options)" def loader @loader ||= Knife::Core::ObjectLoader.new(Chef::Node, ui) end def run @name_args.each do |arg| updated = loader.load_from("nodes", arg) updated.save output(format_for_display(updated)) if config[:print_after] ui.info("Updated Node #{updated.name}") end end end end end chef-12.14.60/lib/chef/knife/node_list.rb000066400000000000000000000022661276456504500177520ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class NodeList < Knife deps do require "chef/node" require "chef/json_compat" end banner "knife node list (options)" option :with_uri, :short => "-w", :long => "--with-uri", :description => "Show corresponding URIs" def run env = Chef::Config[:environment] output(format_list_for_display( env ? Chef::Node.list_by_environment(env) : Chef::Node.list )) end end end end chef-12.14.60/lib/chef/knife/node_run_list_add.rb000066400000000000000000000055211276456504500214430ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class NodeRunListAdd < Knife deps do require "chef/node" require "chef/json_compat" end banner "knife node run_list add [NODE] [ENTRY[,ENTRY]] (options)" option :after, :short => "-a ITEM", :long => "--after ITEM", :description => "Place the ENTRY in the run list after ITEM" option :before, :short => "-b ITEM", :long => "--before ITEM", :description => "Place the ENTRY in the run list before ITEM" def run node = Chef::Node.load(@name_args[0]) if @name_args.size > 2 # Check for nested lists and create a single plain one entries = @name_args[1..-1].map do |entry| entry.split(",").map { |e| e.strip } end.flatten else # Convert to array and remove the extra spaces entries = @name_args[1].split(",").map { |e| e.strip } end if config[:after] && config[:before] ui.fatal("You cannot specify both --before and --after!") exit 1 end if config[:after] add_to_run_list_after(node, entries, config[:after]) elsif config[:before] add_to_run_list_before(node, entries, config[:before]) else add_to_run_list_after(node, entries) end node.save config[:run_list] = true output(format_for_display(node)) end private def add_to_run_list_after(node, entries, after = nil) if after nlist = [] node.run_list.each do |entry| nlist << entry if entry == after entries.each { |e| nlist << e } end end node.run_list.reset!(nlist) else entries.each { |e| node.run_list << e } end end def add_to_run_list_before(node, entries, before) nlist = [] node.run_list.each do |entry| if entry == before entries.each { |e| nlist << e } end nlist << entry end node.run_list.reset!(nlist) end end end end chef-12.14.60/lib/chef/knife/node_run_list_remove.rb000066400000000000000000000036061276456504500222120ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class NodeRunListRemove < Knife deps do require "chef/node" require "chef/json_compat" end banner "knife node run_list remove [NODE] [ENTRY[,ENTRY]] (options)" def run node = Chef::Node.load(@name_args[0]) if @name_args.size > 2 # Check for nested lists and create a single plain one entries = @name_args[1..-1].map do |entry| entry.split(",").map { |e| e.strip } end.flatten else # Convert to array and remove the extra spaces entries = @name_args[1].split(",").map { |e| e.strip } end # iterate over the list of things to remove, # warning if one of them was not found entries.each do |e| if node.run_list.find { |rli| e == rli.to_s } node.run_list.remove(e) else ui.warn "#{e} is not in the run list" unless e =~ /^(recipe|role)\[/ ui.warn "(did you forget recipe[] or role[] around it?)" end end end node.save config[:run_list] = true output(format_for_display(node)) end end end end chef-12.14.60/lib/chef/knife/node_run_list_set.rb000066400000000000000000000035341276456504500215100ustar00rootroot00000000000000# # Author:: Mike Fiedler () # Copyright:: Copyright 2013-2016, Mike Fiedler # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class NodeRunListSet < Knife deps do require "chef/node" require "chef/json_compat" end banner "knife node run_list set NODE ENTRIES (options)" def run if @name_args.size < 2 ui.fatal "You must supply both a node name and a run list." show_usage exit 1 elsif @name_args.size > 2 # Check for nested lists and create a single plain one entries = @name_args[1..-1].map do |entry| entry.split(",").map { |e| e.strip } end.flatten else # Convert to array and remove the extra spaces entries = @name_args[1].split(",").map { |e| e.strip } end node = Chef::Node.load(@name_args[0]) set_run_list(node, entries) node.save config[:run_list] = true output(format_for_display(node)) end # Clears out any existing run_list_items and sets them to the # specified entries def set_run_list(node, entries) node.run_list.run_list_items.clear entries.each { |e| node.run_list << e } end end end end chef-12.14.60/lib/chef/knife/node_show.rb000066400000000000000000000033731276456504500177570ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/core/node_presenter" class Chef class Knife class NodeShow < Knife include Knife::Core::NodeFormattingOptions include Knife::Core::MultiAttributeReturnOption deps do require "chef/node" require "chef/json_compat" end banner "knife node show NODE (options)" option :run_list, :short => "-r", :long => "--run-list", :description => "Show only the run list" option :environment, :short => "-E", :long => "--environment", :description => "Show only the Chef environment" def run ui.use_presenter Knife::Core::NodePresenter @node_name = @name_args[0] if @node_name.nil? show_usage ui.fatal("You must specify a node name") exit 1 end node = Chef::Node.load(@node_name) output(format_for_display(node)) self.class.attrs_to_show = [] end def self.attrs_to_show=(attrs) @attrs_to_show = attrs end end end end chef-12.14.60/lib/chef/knife/null.rb000066400000000000000000000001731276456504500167370ustar00rootroot00000000000000class Chef class Knife class Null < Chef::Knife banner "knife null" def run end end end end chef-12.14.60/lib/chef/knife/osc_user_create.rb000066400000000000000000000052731276456504500211400ustar00rootroot00000000000000# # Author:: Steven Danna () # 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 "chef/knife" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_create.rb. class Chef class Knife class OscUserCreate < Knife deps do require "chef/user" require "chef/json_compat" end option :file, :short => "-f FILE", :long => "--file FILE", :description => "Write the private key to a file" option :admin, :short => "-a", :long => "--admin", :description => "Create the user as an admin", :boolean => true option :user_password, :short => "-p PASSWORD", :long => "--password PASSWORD", :description => "Password for newly created user", :default => "" option :user_key, :long => "--user-key FILENAME", :description => "Public key for newly created user. By default a key will be created for you." banner "knife osc_user create USER (options)" def run @user_name = @name_args[0] if @user_name.nil? show_usage ui.fatal("You must specify a user name") exit 1 end if config[:user_password].length == 0 show_usage ui.fatal("You must specify a non-blank password") exit 1 end user = Chef::User.new user.name(@user_name) user.admin(config[:admin]) user.password config[:user_password] if config[:user_key] user.public_key File.read(File.expand_path(config[:user_key])) end output = edit_hash(user) user = Chef::User.from_hash(output).create ui.info("Created #{user}") if user.private_key if config[:file] File.open(config[:file], "w") do |f| f.print(user.private_key) end else ui.msg user.private_key end end end end end end chef-12.14.60/lib/chef/knife/osc_user_delete.rb000066400000000000000000000025441276456504500211350ustar00rootroot00000000000000# # Author:: Steven Danna () # 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 "chef/knife" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in the user_delete.rb. class Chef class Knife class OscUserDelete < Knife deps do require "chef/user" require "chef/json_compat" end banner "knife osc_user delete USER (options)" def run @user_name = @name_args[0] if @user_name.nil? show_usage ui.fatal("You must specify a user name") exit 1 end delete_object(Chef::User, @user_name) end end end end chef-12.14.60/lib/chef/knife/osc_user_edit.rb000066400000000000000000000031631276456504500206160ustar00rootroot00000000000000# # Author:: Steven Danna () # 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 "chef/knife" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_edit.rb. class Chef class Knife class OscUserEdit < Knife deps do require "chef/user" require "chef/json_compat" end banner "knife osc_user edit USER (options)" def run @user_name = @name_args[0] if @user_name.nil? show_usage ui.fatal("You must specify a user name") exit 1 end original_user = Chef::User.load(@user_name).to_hash edited_user = edit_hash(original_user) if original_user != edited_user user = Chef::User.from_hash(edited_user) user.update ui.msg("Saved #{user}.") else ui.msg("User unchanged, not saving.") end end end end end chef-12.14.60/lib/chef/knife/osc_user_list.rb000066400000000000000000000024741276456504500206500ustar00rootroot00000000000000# # Author:: Steven Danna () # 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 "chef/knife" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_list.rb. class Chef class Knife class OscUserList < Knife deps do require "chef/user" require "chef/json_compat" end banner "knife osc_user list (options)" option :with_uri, :short => "-w", :long => "--with-uri", :description => "Show corresponding URIs" def run output(format_list_for_display(Chef::User.list)) end end end end chef-12.14.60/lib/chef/knife/osc_user_reregister.rb000066400000000000000000000033721276456504500220460ustar00rootroot00000000000000# # Author:: Steven Danna () # 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 "chef/knife" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_reregister.rb. class Chef class Knife class OscUserReregister < Knife deps do require "chef/user" require "chef/json_compat" end banner "knife osc_user reregister USER (options)" option :file, :short => "-f FILE", :long => "--file FILE", :description => "Write the private key to a file" def run @user_name = @name_args[0] if @user_name.nil? show_usage ui.fatal("You must specify a user name") exit 1 end user = Chef::User.load(@user_name).reregister Chef::Log.debug("Updated user data: #{user.inspect}") key = user.private_key if config[:file] File.open(config[:file], "w") do |f| f.print(key) end else ui.msg key end end end end end chef-12.14.60/lib/chef/knife/osc_user_show.rb000066400000000000000000000026671276456504500206610ustar00rootroot00000000000000# # Author:: Steven Danna () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_show.rb. class Chef class Knife class OscUserShow < Knife include Knife::Core::MultiAttributeReturnOption deps do require "chef/user" require "chef/json_compat" end banner "knife osc_user show USER (options)" def run @user_name = @name_args[0] if @user_name.nil? show_usage ui.fatal("You must specify a user name") exit 1 end user = Chef::User.load(@user_name) output(format_for_display(user)) end end end end chef-12.14.60/lib/chef/knife/raw.rb000066400000000000000000000061021276456504500165540ustar00rootroot00000000000000require "chef/knife" require "chef/http" class Chef class Knife class Raw < Chef::Knife banner "knife raw REQUEST_PATH" deps do require "chef/json_compat" require "chef/config" require "chef/http" require "chef/http/authenticator" require "chef/http/cookie_manager" require "chef/http/decompressor" require "chef/http/json_output" end option :method, :long => "--method METHOD", :short => "-m METHOD", :default => "GET", :description => "Request method (GET, POST, PUT or DELETE). Default: GET" option :pretty, :long => "--[no-]pretty", :boolean => true, :default => true, :description => "Pretty-print JSON output. Default: true" option :input, :long => "--input FILE", :short => "-i FILE", :description => "Name of file to use for PUT or POST" option :proxy_auth, :long => "--proxy-auth", :boolean => true, :default => false, :description => "Use webui proxy authentication. Client key must be the webui key." class RawInputServerAPI < Chef::HTTP def initialize(options = {}) options[:client_name] ||= Chef::Config[:node_name] options[:signing_key_filename] ||= Chef::Config[:client_key] super(Chef::Config[:chef_server_url], options) end use Chef::HTTP::JSONOutput use Chef::HTTP::CookieManager use Chef::HTTP::Decompressor use Chef::HTTP::Authenticator use Chef::HTTP::RemoteRequestID end def run if name_args.length == 0 show_usage ui.fatal("You must provide the path you want to hit on the server") exit(1) elsif name_args.length > 1 show_usage ui.fatal("You must specify only a single path") exit(1) end path = name_args[0] data = false if config[:input] data = IO.read(config[:input]) end begin method = config[:method].to_sym headers = { "Content-Type" => "application/json" } if config[:proxy_auth] headers["x-ops-request-source"] = "web" end if config[:pretty] chef_rest = RawInputServerAPI.new result = chef_rest.request(method, name_args[0], headers, data) unless result.is_a?(String) result = Chef::JSONCompat.to_json_pretty(result) end else chef_rest = RawInputServerAPI.new(:raw_output => true) result = chef_rest.request(method, name_args[0], headers, data) end output result rescue Timeout::Error => e ui.error "Server timeout" exit 1 rescue Net::HTTPServerException => e ui.error "Server responded with error #{e.response.code} \"#{e.response.message}\"" ui.error "Error Body: #{e.response.body}" if e.response.body && e.response.body != "" exit 1 end end end # class Raw end end chef-12.14.60/lib/chef/knife/recipe_list.rb000066400000000000000000000017111276456504500202660ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef::Knife::RecipeList < Chef::Knife banner "knife recipe list [PATTERN]" def run recipes = rest.get("cookbooks/_recipes") if pattern = @name_args.first recipes = recipes.grep(Regexp.new(pattern)) end output(recipes) end end chef-12.14.60/lib/chef/knife/rehash.rb000066400000000000000000000053171276456504500172440ustar00rootroot00000000000000# # Author:: Steven Danna # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/core/subcommand_loader" class Chef class Knife class Rehash < Chef::Knife banner "knife rehash" def run if ! Chef::Knife::SubcommandLoader.autogenerated_manifest? ui.msg "Using knife-rehash will speed up knife's load time by caching the location of subcommands on disk." ui.msg "However, you will need to update the cache by running `knife rehash` anytime you install a new knife plugin." else reload_plugins end write_hash(generate_hash) end def reload_plugins # The subcommand_loader for this knife command should _always_ be the GemGlobLoader. The GemGlobLoader loads # plugins from disc and ensures the hash we write is always correct. By this point it should also already have # loaded plugins and `load_commands` shouldn't have an effect. Chef::Knife.subcommand_loader.load_commands end def generate_hash output = if Chef::Knife::SubcommandLoader.plugin_manifest? Chef::Knife::SubcommandLoader.plugin_manifest else { Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY => {} } end output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]["plugins_paths"] = Chef::Knife.subcommand_files output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]["plugins_by_category"] = Chef::Knife.subcommands_by_category output end def write_hash(data) plugin_manifest_dir = File.expand_path("..", Chef::Knife::SubcommandLoader.plugin_manifest_path) FileUtils.mkdir_p(plugin_manifest_dir) unless File.directory?(plugin_manifest_dir) File.open(Chef::Knife::SubcommandLoader.plugin_manifest_path, "w") do |f| f.write(Chef::JSONCompat.to_json_pretty(data)) ui.msg "Knife subcommands are cached in #{Chef::Knife::SubcommandLoader.plugin_manifest_path}. Delete this file to disable the caching." end end end end end chef-12.14.60/lib/chef/knife/role_bulk_delete.rb000066400000000000000000000034451276456504500212720ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleBulkDelete < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role bulk delete REGEX (options)" def run if @name_args.length < 1 ui.error("You must supply a regular expression to match the results against") exit 1 end all_roles = Chef::Role.list(true) matcher = /#{@name_args[0]}/ roles_to_delete = {} all_roles.each do |name, role| next unless name =~ matcher roles_to_delete[role.name] = role end if roles_to_delete.empty? ui.info "No roles match the expression /#{@name_args[0]}/" exit 0 end ui.msg("The following roles will be deleted:") ui.msg("") ui.msg(ui.list(roles_to_delete.keys.sort, :columns_down)) ui.msg("") ui.confirm("Are you sure you want to delete these roles") roles_to_delete.sort.each do |name, role| role.destroy ui.msg("Deleted role #{name}") end end end end end chef-12.14.60/lib/chef/knife/role_create.rb000066400000000000000000000026311276456504500202520ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleCreate < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role create ROLE (options)" option :description, :short => "-d DESC", :long => "--description DESC", :description => "The role description" def run @role_name = @name_args[0] if @role_name.nil? show_usage ui.fatal("You must specify a role name") exit 1 end role = Chef::Role.new role.name(@role_name) role.description(config[:description]) if config[:description] create_object(role, object_class: Chef::Role) end end end end chef-12.14.60/lib/chef/knife/role_delete.rb000066400000000000000000000022001276456504500202410ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleDelete < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role delete ROLE (options)" def run @role_name = @name_args[0] if @role_name.nil? show_usage ui.fatal("You must specify a role name") exit 1 end delete_object(Chef::Role, @role_name) end end end end chef-12.14.60/lib/chef/knife/role_edit.rb000066400000000000000000000021741276456504500177360ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleEdit < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role edit ROLE (options)" def run @role_name = @name_args[0] if @role_name.nil? show_usage ui.fatal("You must specify a role name") exit 1 end ui.edit_object(Chef::Role, @role_name) end end end end chef-12.14.60/lib/chef/knife/role_env_run_list_add.rb000066400000000000000000000052141276456504500223260ustar00rootroot00000000000000# Author:: Adam Jacob () # Author:: William Albenzi () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleEnvRunListAdd < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role env_run_list add [ROLE] [ENVIRONMENT] [ENTRY[,ENTRY]] (options)" option :after, :short => "-a ITEM", :long => "--after ITEM", :description => "Place the ENTRY in the run list after ITEM" def add_to_env_run_list(role, environment, entries, after = nil) if after nlist = [] unless role.env_run_lists.key?(environment) role.env_run_lists_add(environment => nlist) end role.run_list_for(environment).each do |entry| nlist << entry if entry == after entries.each { |e| nlist << e } end end role.env_run_lists_add(environment => nlist) else nlist = [] unless role.env_run_lists.key?(environment) role.env_run_lists_add(environment => nlist) end role.run_list_for(environment).each do |entry| nlist << entry end entries.each { |e| nlist << e } role.env_run_lists_add(environment => nlist) end end def run role = Chef::Role.load(@name_args[0]) role.name(@name_args[0]) environment = @name_args[1] if @name_args.size > 2 # Check for nested lists and create a single plain one entries = @name_args[2..-1].map do |entry| entry.split(",").map { |e| e.strip } end.flatten else # Convert to array and remove the extra spaces entries = @name_args[2].split(",").map { |e| e.strip } end add_to_env_run_list(role, environment, entries, config[:after]) role.save config[:env_run_list] = true output(format_for_display(role)) end end end end chef-12.14.60/lib/chef/knife/role_env_run_list_clear.rb000066400000000000000000000030251276456504500226620ustar00rootroot00000000000000# # Author:: Mike Fiedler () # Author:: William Albenzi () # Copyright:: Copyright 2013-2016, Mike Fiedler # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleEnvRunListClear < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role env_run_list clear [ROLE] [ENVIRONMENT]" def clear_env_run_list(role, environment) nlist = [] role.env_run_lists_add(environment => nlist) end def run if @name_args.size > 2 ui.fatal "You must not supply an environment run list." show_usage exit 1 end role = Chef::Role.load(@name_args[0]) role.name(@name_args[0]) environment = @name_args[1] clear_env_run_list(role, environment) role.save config[:env_run_list] = true output(format_for_display(role)) end end end end chef-12.14.60/lib/chef/knife/role_env_run_list_remove.rb000066400000000000000000000031731276456504500230750ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleEnvRunListRemove < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role env_run_list remove [ROLE] [ENVIRONMENT] [ENTRIES]" def remove_from_env_run_list(role, environment, item_to_remove) nlist = [] role.run_list_for(environment).each do |entry| nlist << entry unless entry == item_to_remove #unless entry == @name_args[2] # nlist << entry #end end role.env_run_lists_add(environment => nlist) end def run role = Chef::Role.load(@name_args[0]) role.name(@name_args[0]) environment = @name_args[1] item_to_remove = @name_args[2] remove_from_env_run_list(role, environment, item_to_remove) role.save config[:env_run_list] = true output(format_for_display(role)) end end end end chef-12.14.60/lib/chef/knife/role_env_run_list_replace.rb000066400000000000000000000033161276456504500232120ustar00rootroot00000000000000# Author:: Adam Jacob () # Author:: William Albenzi () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleEnvRunListReplace < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role env_run_list replace [ROLE] [ENVIRONMENT] [OLD_ENTRY] [NEW_ENTRY] " def replace_in_env_run_list(role, environment, old_entry, new_entry) nlist = [] role.run_list_for(environment).each do |entry| if entry == old_entry nlist << new_entry else nlist << entry end end role.env_run_lists_add(environment => nlist) end def run role = Chef::Role.load(@name_args[0]) role.name(@name_args[0]) environment = @name_args[1] old_entry = @name_args[2] new_entry = @name_args[3] replace_in_env_run_list(role, environment, old_entry, new_entry) role.save config[:env_run_list] = true output(format_for_display(role)) end end end end chef-12.14.60/lib/chef/knife/role_env_run_list_set.rb000066400000000000000000000042431276456504500223720ustar00rootroot00000000000000# # Author:: Mike Fiedler () # Author:: William Albenzi () # Copyright:: Copyright 2013-2016, Mike Fiedler # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleEnvRunListSet < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role env_run_list set [ROLE] [ENVIRONMENT] [ENTRIES]" # Clears out any existing env_run_list_items and sets them to the # specified entries def set_env_run_list(role, environment, entries) nlist = [] unless role.env_run_lists.key?(environment) role.env_run_lists_add(environment => nlist) end entries.each { |e| nlist << e } role.env_run_lists_add(environment => nlist) end def run role = Chef::Role.load(@name_args[0]) role.name(@name_args[0]) environment = @name_args[1] if @name_args.size < 2 ui.fatal "You must supply both a role name and an environment run list." show_usage exit 1 elsif @name_args.size > 2 # Check for nested lists and create a single plain one entries = @name_args[2..-1].map do |entry| entry.split(",").map { |e| e.strip } end.flatten else # Convert to array and remove the extra spaces entries = @name_args[2].split(",").map { |e| e.strip } end set_env_run_list(role, environment, entries ) role.save config[:env_run_list] = true output(format_for_display(role)) end end end end chef-12.14.60/lib/chef/knife/role_from_file.rb000066400000000000000000000024731276456504500207550ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleFromFile < Knife deps do require "chef/role" require "chef/knife/core/object_loader" require "chef/json_compat" end banner "knife role from file FILE [FILE..] (options)" def loader @loader ||= Knife::Core::ObjectLoader.new(Chef::Role, ui) end def run @name_args.each do |arg| updated = loader.load_from("roles", arg) updated.save output(format_for_display(updated)) if config[:print_after] ui.info("Updated Role #{updated.name}") end end end end end chef-12.14.60/lib/chef/knife/role_list.rb000066400000000000000000000021361276456504500177620ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleList < Knife deps do require "chef/node" require "chef/json_compat" end banner "knife role list (options)" option :with_uri, :short => "-w", :long => "--with-uri", :description => "Show corresponding URIs" def run output(format_list_for_display(Chef::Role.list)) end end end end chef-12.14.60/lib/chef/knife/role_run_list_add.rb000066400000000000000000000051641276456504500214620ustar00rootroot00000000000000# Author:: Adam Jacob () # Author:: William Albenzi () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleRunListAdd < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role run_list add [ROLE] [ENTRY[,ENTRY]] (options)" option :after, :short => "-a ITEM", :long => "--after ITEM", :description => "Place the ENTRY in the run list after ITEM" def add_to_env_run_list(role, environment, entries, after = nil) if after nlist = [] unless role.env_run_lists.key?(environment) role.env_run_lists_add(environment => nlist) end role.run_list_for(environment).each do |entry| nlist << entry if entry == after entries.each { |e| nlist << e } end end role.env_run_lists_add(environment => nlist) else nlist = [] unless role.env_run_lists.key?(environment) role.env_run_lists_add(environment => nlist) end role.run_list_for(environment).each do |entry| nlist << entry end entries.each { |e| nlist << e } role.env_run_lists_add(environment => nlist) end end def run role = Chef::Role.load(@name_args[0]) role.name(@name_args[0]) environment = "_default" if @name_args.size > 1 # Check for nested lists and create a single plain one entries = @name_args[1..-1].map do |entry| entry.split(",").map { |e| e.strip } end.flatten else # Convert to array and remove the extra spaces entries = @name_args[1].split(",").map { |e| e.strip } end add_to_env_run_list(role, environment, entries, config[:after]) role.save config[:env_run_list] = true output(format_for_display(role)) end end end end chef-12.14.60/lib/chef/knife/role_run_list_clear.rb000066400000000000000000000027751276456504500220250ustar00rootroot00000000000000# # Author:: Mike Fiedler () # Author:: William Albenzi () # Copyright:: Copyright 2013-2016, Mike Fiedler # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleRunListClear < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role run_list clear [ROLE]" def clear_env_run_list(role, environment) nlist = [] role.env_run_lists_add(environment => nlist) end def run if @name_args.size > 2 ui.fatal "You must not supply an environment run list." show_usage exit 1 end role = Chef::Role.load(@name_args[0]) role.name(@name_args[0]) environment = "_default" clear_env_run_list(role, environment) role.save config[:env_run_list] = true output(format_for_display(role)) end end end end chef-12.14.60/lib/chef/knife/role_run_list_remove.rb000066400000000000000000000031411276456504500222200ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleRunListRemove < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role run_list remove [ROLE] [ENTRY]" def remove_from_env_run_list(role, environment, item_to_remove) nlist = [] role.run_list_for(environment).each do |entry| nlist << entry unless entry == item_to_remove #unless entry == @name_args[2] # nlist << entry #end end role.env_run_lists_add(environment => nlist) end def run role = Chef::Role.load(@name_args[0]) role.name(@name_args[0]) environment = "_default" item_to_remove = @name_args[1] remove_from_env_run_list(role, environment, item_to_remove) role.save config[:env_run_list] = true output(format_for_display(role)) end end end end chef-12.14.60/lib/chef/knife/role_run_list_replace.rb000066400000000000000000000032661276456504500223460ustar00rootroot00000000000000# Author:: Adam Jacob () # Author:: William Albenzi () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleRunListReplace < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role run_list replace [ROLE] [OLD_ENTRY] [NEW_ENTRY] " def replace_in_env_run_list(role, environment, old_entry, new_entry) nlist = [] role.run_list_for(environment).each do |entry| if entry == old_entry nlist << new_entry else nlist << entry end end role.env_run_lists_add(environment => nlist) end def run role = Chef::Role.load(@name_args[0]) role.name(@name_args[0]) environment = "_default" old_entry = @name_args[1] new_entry = @name_args[2] replace_in_env_run_list(role, environment, old_entry, new_entry) role.save config[:env_run_list] = true output(format_for_display(role)) end end end end chef-12.14.60/lib/chef/knife/role_run_list_set.rb000066400000000000000000000042131276456504500215170ustar00rootroot00000000000000# # Author:: Mike Fiedler () # Author:: William Albenzi () # Copyright:: Copyright 2013-2016, Mike Fiedler # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleRunListSet < Knife deps do require "chef/role" require "chef/json_compat" end banner "knife role run_list set [ROLE] [ENTRIES]" # Clears out any existing env_run_list_items and sets them to the # specified entries def set_env_run_list(role, environment, entries) nlist = [] unless role.env_run_lists.key?(environment) role.env_run_lists_add(environment => nlist) end entries.each { |e| nlist << e } role.env_run_lists_add(environment => nlist) end def run role = Chef::Role.load(@name_args[0]) role.name(@name_args[0]) environment = "_default" if @name_args.size < 1 ui.fatal "You must supply both a role name and an environment run list." show_usage exit 1 elsif @name_args.size > 1 # Check for nested lists and create a single plain one entries = @name_args[1..-1].map do |entry| entry.split(",").map { |e| e.strip } end.flatten else # Convert to array and remove the extra spaces entries = @name_args[1].split(",").map { |e| e.strip } end set_env_run_list(role, environment, entries ) role.save config[:env_run_list] = true output(format_for_display(role)) end end end end chef-12.14.60/lib/chef/knife/role_show.rb000066400000000000000000000024311276456504500177650ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class RoleShow < Knife include Knife::Core::MultiAttributeReturnOption deps do require "chef/node" require "chef/json_compat" end banner "knife role show ROLE (options)" def run @role_name = @name_args[0] if @role_name.nil? show_usage ui.fatal("You must specify a role name") exit 1 end role = Chef::Role.load(@role_name) output(format_for_display(config[:environment] ? role.environment(config[:environment]) : role)) end end end end chef-12.14.60/lib/chef/knife/search.rb000066400000000000000000000146111276456504500172340ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/core/node_presenter" require "addressable/uri" class Chef class Knife class Search < Knife include Knife::Core::MultiAttributeReturnOption deps do require "chef/node" require "chef/environment" require "chef/api_client" require "chef/search/query" end include Knife::Core::NodeFormattingOptions banner "knife search INDEX QUERY (options)" option :sort, :short => "-o SORT", :long => "--sort SORT", :description => "The order to sort the results in", :default => nil option :start, :short => "-b ROW", :long => "--start ROW", :description => "The row to start returning results at", :default => 0, :proc => lambda { |i| i.to_i } option :rows, :short => "-R INT", :long => "--rows INT", :description => "The number of rows to return", :default => nil, :proc => lambda { |i| i.to_i } option :run_list, :short => "-r", :long => "--run-list", :description => "Show only the run list" option :id_only, :short => "-i", :long => "--id-only", :description => "Show only the ID of matching objects" option :query, :short => "-q QUERY", :long => "--query QUERY", :description => "The search query; useful to protect queries starting with -" option :filter_result, :short => "-f FILTER", :long => "--filter-result FILTER", :description => "Only return specific attributes of the matching objects; for example: \"ServerName=name, Kernel=kernel.version\"" def run read_cli_args fuzzify_query if @type == "node" ui.use_presenter Knife::Core::NodePresenter end q = Chef::Search::Query.new escaped_query = Addressable::URI.encode_component(@query, Addressable::URI::CharacterClasses::QUERY) result_items = [] result_count = 0 search_args = Hash.new search_args[:sort] = config[:sort] if config[:sort] search_args[:start] = config[:start] if config[:start] search_args[:rows] = config[:rows] if config[:rows] if config[:filter_result] search_args[:filter_result] = create_result_filter(config[:filter_result]) elsif (not ui.config[:attribute].nil?) && (not ui.config[:attribute].empty?) search_args[:filter_result] = create_result_filter_from_attributes(ui.config[:attribute]) end begin q.search(@type, escaped_query, search_args) do |item| formatted_item = Hash.new if item.is_a?(Hash) # doing a little magic here to set the correct name formatted_item[item["__display_name"]] = item.reject { |k| k == "__display_name" } else formatted_item = format_for_display(item) end result_items << formatted_item result_count += 1 end rescue Net::HTTPServerException => e msg = Chef::JSONCompat.from_json(e.response.body)["error"].first ui.error("knife search failed: #{msg}") exit 1 end if ui.interchange? output({ :results => result_count, :rows => result_items }) else ui.log "#{result_count} items found" ui.log("\n") result_items.each do |item| output(item) unless config[:id_only] ui.msg("\n") end end end end def read_cli_args if config[:query] if @name_args[1] ui.error "Please specify query as an argument or an option via -q, not both" ui.msg opt_parser exit 1 end @type = name_args[0] @query = config[:query] else case name_args.size when 0 ui.error "No query specified" ui.msg opt_parser exit 1 when 1 @type = "node" @query = name_args[0] when 2 @type = name_args[0] @query = name_args[1] end end end def fuzzify_query if @query !~ /:/ @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}* OR policy_name:*#{@query}* OR policy_group:*#{@query}*" end end # This method turns a set of key value pairs in a string into the appropriate data structure that the # chef-server search api is expecting. # expected input is in the form of: # -f "return_var1=path.to.attribute, return_var2=shorter.path" # # a more concrete example might be: # -f "env=chef_environment, ruby_platform=languages.ruby.platform" # # The end result is a hash where the key is a symbol in the hash (the return variable) # and the path is an array with the path elements as strings (in order) # See lib/chef/search/query.rb for more examples of this. def create_result_filter(filter_string) final_filter = Hash.new filter_string.delete!(" ") filters = filter_string.split(",") filters.each do |f| return_id, attr_path = f.split("=") final_filter[return_id.to_sym] = attr_path.split(".") end return final_filter end def create_result_filter_from_attributes(filter_array) final_filter = Hash.new filter_array.each do |f| final_filter[f] = f.split(".") end # adding magic filter so we can actually pull the name as before final_filter["__display_name"] = [ "name" ] return final_filter end end end end chef-12.14.60/lib/chef/knife/serve.rb000066400000000000000000000032111276456504500171050ustar00rootroot00000000000000require "chef/knife" require "chef/local_mode" class Chef class Knife class Serve < Knife banner "knife serve (options)" option :repo_mode, :long => "--repo-mode MODE", :description => "Specifies the local repository layout. Values: static (only environments/roles/data_bags/cookbooks), everything (includes nodes/clients/users), hosted_everything (includes acls/groups/etc. for Enterprise/Hosted Chef). Default: everything/hosted_everything" option :chef_repo_path, :long => "--chef-repo-path PATH", :description => "Overrides the location of chef repo. Default is specified by chef_repo_path in the config" option :chef_zero_host, :long => "--chef-zero-host IP", :description => "Overrides the host upon which chef-zero listens. Default is 127.0.0.1." def configure_chef super Chef::Config.local_mode = true Chef::Config[:repo_mode] = config[:repo_mode] if config[:repo_mode] # --chef-repo-path forcibly overrides all other paths if config[:chef_repo_path] Chef::Config.chef_repo_path = config[:chef_repo_path] %w{acl client cookbook container data_bag environment group node role user}.each do |variable_name| Chef::Config.delete("#{variable_name}_path".to_sym) end end end def run server = Chef::LocalMode.chef_zero_server begin output "Serving files from:\n#{Chef::LocalMode.chef_fs.fs_description}" server.stop server.start(stdout) # to print header ensure server.stop end end end end end chef-12.14.60/lib/chef/knife/show.rb000066400000000000000000000031201276456504500167400ustar00rootroot00000000000000require "chef/chef_fs/knife" class Chef class Knife class Show < Chef::ChefFS::Knife banner "knife show [PATTERN1 ... PATTERNn]" category "path-based" deps do require "chef/chef_fs/file_system" require "chef/chef_fs/file_system/exceptions" end option :local, :long => "--local", :boolean => true, :description => "Show local files instead of remote" def run # Get the matches (recursively) error = false entry_values = parallelize(pattern_args) do |pattern| parallelize(Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern)) do |entry| if entry.dir? ui.error "#{format_path(entry)}: is a directory" if pattern.exact_path error = true nil else begin [entry, entry.read] rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e ui.error "#{format_path(e.entry)}: #{e.reason}." error = true nil rescue Chef::ChefFS::FileSystem::NotFoundError => e ui.error "#{format_path(e.entry)}: No such file or directory" error = true nil end end end end.flatten(1) entry_values.each do |entry, value| if entry output "#{format_path(entry)}:" output(format_for_display(value)) end end if error exit 1 end end end end end chef-12.14.60/lib/chef/knife/ssh.rb000066400000000000000000000514761276456504500165760ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/shell_out" require "chef/knife" class Chef class Knife class Ssh < Knife deps do require "net/ssh" require "net/ssh/multi" require "chef/monkey_patches/net-ssh-multi" require "readline" require "chef/exceptions" require "chef/search/query" require "chef/util/path_helper" require "mixlib/shellout" end include Chef::Mixin::ShellOut attr_writer :password banner "knife ssh QUERY COMMAND (options)" option :concurrency, :short => "-C NUM", :long => "--concurrency NUM", :description => "The number of concurrent connections", :default => nil, :proc => lambda { |o| o.to_i } option :attribute, :short => "-a ATTR", :long => "--attribute ATTR", :description => "The attribute to use for opening the connection - default depends on the context", :proc => Proc.new { |key| Chef::Config[:knife][:ssh_attribute] = key.strip } option :manual, :short => "-m", :long => "--manual-list", :boolean => true, :description => "QUERY is a space separated list of servers", :default => false option :ssh_user, :short => "-x USERNAME", :long => "--ssh-user USERNAME", :description => "The ssh username" option :ssh_password_ng, :short => "-P [PASSWORD]", :long => "--ssh-password [PASSWORD]", :description => "The ssh password - will prompt if flag is specified but no password is given", # default to a value that can not be a password (boolean) # so we can effectively test if this parameter was specified # without a value :default => false option :ssh_port, :short => "-p PORT", :long => "--ssh-port PORT", :description => "The ssh port", :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key.strip } option :ssh_timeout, :short => "-t SECONDS", :long => "--ssh-timeout SECONDS", :description => "The ssh connection timeout", :proc => Proc.new { |key| Chef::Config[:knife][:ssh_timeout] = key.strip.to_i }, :default => 120 option :ssh_gateway, :short => "-G GATEWAY", :long => "--ssh-gateway GATEWAY", :description => "The ssh gateway", :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key.strip } option :forward_agent, :short => "-A", :long => "--forward-agent", :description => "Enable SSH agent forwarding", :boolean => true option :identity_file, :long => "--identity-file IDENTITY_FILE", :description => "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead." option :ssh_identity_file, :short => "-i IDENTITY_FILE", :long => "--ssh-identity-file IDENTITY_FILE", :description => "The SSH identity file used for authentication" option :host_key_verify, :long => "--[no-]host-key-verify", :description => "Verify host key, enabled by default.", :boolean => true, :default => true option :on_error, :short => "-e", :long => "--exit-on-error", :description => "Immediately exit if an error is encountered", :boolean => true, :proc => Proc.new { :raise } option :tmux_split, :long => "--tmux-split", :description => "Split tmux window.", :boolean => true, :default => false def session config[:on_error] ||= :skip ssh_error_handler = Proc.new do |server| case config[:on_error] when :skip ui.warn "Failed to connect to #{server.host} -- #{$!.class.name}: #{$!.message}" $!.backtrace.each { |l| Chef::Log.debug(l) } when :raise #Net::SSH::Multi magic to force exception to be re-raised. throw :go, :raise end end @session ||= Net::SSH::Multi.start(:concurrent_connections => config[:concurrency], :on_error => ssh_error_handler) end def configure_gateway config[:ssh_gateway] ||= Chef::Config[:knife][:ssh_gateway] if config[:ssh_gateway] gw_host, gw_user = config[:ssh_gateway].split("@").reverse gw_host, gw_port = gw_host.split(":") gw_opts = session_options(gw_host, gw_port, gw_user) user = gw_opts.delete(:user) begin # Try to connect with a key. session.via(gw_host, user, gw_opts) rescue Net::SSH::AuthenticationFailed prompt = "Enter the password for #{user}@#{gw_host}: " gw_opts[:password] = prompt_for_password(prompt) # Try again with a password. session.via(gw_host, user, gw_opts) end end end def configure_session list = config[:manual] ? @name_args[0].split(" ") : search_nodes if list.length == 0 if @action_nodes.length == 0 ui.fatal("No nodes returned from search") else ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes" : "node"} found, " + "but does not have the required attribute to establish the connection. " + "Try setting another attribute to open the connection using --attribute.") end exit 10 end session_from_list(list) end def get_ssh_attribute(node) # Order of precedence for ssh target # 1) command line attribute # 2) configuration file # 3) cloud attribute # 4) fqdn if config[:attribute] Chef::Log.debug("Using node attribute '#{config[:attribute]}' as the ssh target") attribute = config[:attribute] elsif Chef::Config[:knife][:ssh_attribute] Chef::Log.debug("Using node attribute #{Chef::Config[:knife][:ssh_attribute]}") attribute = Chef::Config[:knife][:ssh_attribute] elsif node[:cloud] && node[:cloud][:public_hostname] && !node[:cloud][:public_hostname].empty? Chef::Log.debug("Using node attribute 'cloud[:public_hostname]' automatically as the ssh target") attribute = "cloud.public_hostname" else # falling back to default of fqdn Chef::Log.debug("Using node attribute 'fqdn' as the ssh target") attribute = "fqdn" end attribute end def search_nodes list = Array.new query = Chef::Search::Query.new @action_nodes = query.search(:node, @name_args[0])[0] @action_nodes.each do |item| # we should skip the loop to next iteration if the item # returned by the search is nil next if item.nil? # next if we couldn't find the specified attribute in the # returned node object host = extract_nested_value(item, get_ssh_attribute(item)) next if host.nil? ssh_port = item[:cloud].nil? ? nil : item[:cloud][:public_ssh_port] srv = [host, ssh_port] list.push(srv) end list end # Net::SSH session options hash for global options. These should be # options that will apply to the gateway connection in addition to the # main one. # # @since 12.5.0 # @param host [String] Hostname for this session. # @param port [String] SSH port for this session. # @param user [String] Optional username for this session. # @return [Hash] def session_options(host, port, user = nil) ssh_config = Net::SSH.configuration_for(host) {}.tap do |opts| # Chef::Config[:knife][:ssh_user] is parsed in #configure_user and written to config[:ssh_user] opts[:user] = user || config[:ssh_user] || ssh_config[:user] if config[:ssh_identity_file] opts[:keys] = File.expand_path(config[:ssh_identity_file]) opts[:keys_only] = true elsif config[:ssh_password] opts[:password] = config[:ssh_password] end # Don't set the keys to nil if we don't have them. forward_agent = config[:forward_agent] || ssh_config[:forward_agent] opts[:forward_agent] = forward_agent unless forward_agent.nil? port ||= ssh_config[:port] opts[:port] = port unless port.nil? opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug if !config[:host_key_verify] opts[:paranoid] = false opts[:user_known_hosts_file] = "/dev/null" end end end def session_from_list(list) list.each do |item| host, ssh_port = item Chef::Log.debug("Adding #{host}") session_opts = session_options(host, ssh_port) # Handle port overrides for the main connection. session_opts[:port] = Chef::Config[:knife][:ssh_port] if Chef::Config[:knife][:ssh_port] session_opts[:port] = config[:ssh_port] if config[:ssh_port] # Handle connection timeout session_opts[:timeout] = Chef::Config[:knife][:ssh_timeout] if Chef::Config[:knife][:ssh_timeout] session_opts[:timeout] = config[:ssh_timeout] if config[:ssh_timeout] # Create the hostspec. hostspec = session_opts[:user] ? "#{session_opts.delete(:user)}@#{host}" : host # Connect a new session on the multi. session.use(hostspec, session_opts) @longest = host.length if host.length > @longest end session end def fixup_sudo(command) command.sub(/^sudo/, 'sudo -p \'knife sudo password: \'') end def print_data(host, data) @buffers ||= {} if leftover = @buffers[host] @buffers[host] = nil print_data(host, leftover + data) else if newline_index = data.index("\n") line = data.slice!(0...newline_index) data.slice!(0) print_line(host, line) print_data(host, data) else @buffers[host] = data end end end def print_line(host, data) padding = @longest - host.length str = ui.color(host, :cyan) + (" " * (padding + 1)) + data ui.msg(str) end def ssh_command(command, subsession = nil) exit_status = 0 subsession ||= session command = fixup_sudo(command) command.force_encoding("binary") if command.respond_to?(:force_encoding) subsession.open_channel do |ch| ch.request_pty ch.exec command do |ch, success| raise ArgumentError, "Cannot execute #{command}" unless success ch.on_data do |ichannel, data| print_data(ichannel[:host], data) if data =~ /^knife sudo password: / print_data(ichannel[:host], "\n") ichannel.send_data("#{get_password}\n") end end ch.on_request "exit-status" do |ichannel, data| exit_status = [exit_status, data.read_long].max end end end session.loop exit_status end def get_password @password ||= prompt_for_password end def prompt_for_password(prompt = "Enter your password: ") ui.ask(prompt) { |q| q.echo = false } end # Present the prompt and read a single line from the console. It also # detects ^D and returns "exit" in that case. Adds the input to the # history, unless the input is empty. Loops repeatedly until a non-empty # line is input. def read_line loop do command = reader.readline("#{ui.color('knife-ssh>', :bold)} ", true) if command.nil? command = "exit" puts(command) else command.strip! end unless command.empty? return command end end end def reader Readline end def interactive puts "Connected to #{ui.list(session.servers_for.collect { |s| ui.color(s.host, :cyan) }, :inline, " and ")}" puts puts "To run a command on a list of servers, do:" puts " on SERVER1 SERVER2 SERVER3; COMMAND" puts " Example: on latte foamy; echo foobar" puts puts "To exit interactive mode, use 'quit!'" puts loop do command = read_line case command when "quit!" puts "Bye!" break when /^on (.+?); (.+)$/ raw_list = $1.split(" ") server_list = Array.new session.servers.each do |session_server| server_list << session_server if raw_list.include?(session_server.host) end command = $2 ssh_command(command, session.on(*server_list)) else ssh_command(command) end end end def screen tf = Tempfile.new("knife-ssh-screen") Chef::Util::PathHelper.home(".screenrc") do |screenrc_path| if File.exist? screenrc_path tf.puts("source #{screenrc_path}") end end tf.puts("caption always '%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<'") tf.puts("hardstatus alwayslastline 'knife ssh #{@name_args[0]}'") window = 0 session.servers_for.each do |server| tf.print("screen -t \"#{server.host}\" #{window} ssh ") tf.print("-i #{config[:ssh_identity_file]} ") if config[:ssh_identity_file] server.user ? tf.puts("#{server.user}@#{server.host}") : tf.puts(server.host) window += 1 end tf.close exec("screen -c #{tf.path}") end def tmux ssh_dest = lambda do |server| identity = "-i #{config[:ssh_identity_file]} " if config[:ssh_identity_file] prefix = server.user ? "#{server.user}@" : "" "'ssh #{identity}#{prefix}#{server.host}'" end new_window_cmds = lambda do if session.servers_for.size > 1 [""] + session.servers_for[1..-1].map do |server| if config[:tmux_split] "split-window #{ssh_dest.call(server)}; tmux select-layout tiled" else "new-window -a -n '#{server.host}' #{ssh_dest.call(server)}" end end else [] end.join(" \\; ") end tmux_name = "'knife ssh #{@name_args[0].tr(':', '=')}'" begin server = session.servers_for.first cmd = ["tmux new-session -d -s #{tmux_name}", "-n '#{server.host}'", ssh_dest.call(server), new_window_cmds.call].join(" ") shell_out!(cmd) exec("tmux attach-session -t #{tmux_name}") rescue Chef::Exceptions::Exec end end def macterm begin require "appscript" rescue LoadError STDERR.puts "You need the rb-appscript gem to use knife ssh macterm. `(sudo) gem install rb-appscript` to install" raise end Appscript.app("/Applications/Utilities/Terminal.app").windows.first.activate Appscript.app("System Events").application_processes["Terminal.app"].keystroke("n", :using => :command_down) term = Appscript.app("Terminal") window = term.windows.first.get (session.servers_for.size - 1).times do |i| window.activate Appscript.app("System Events").application_processes["Terminal.app"].keystroke("t", :using => :command_down) end session.servers_for.each_with_index do |server, tab_number| cmd = "unset PROMPT_COMMAND; echo -e \"\\033]0;#{server.host}\\007\"; ssh #{server.user ? "#{server.user}@#{server.host}" : server.host}" Appscript.app("Terminal").do_script(cmd, :in => window.tabs[tab_number + 1].get) end end def cssh cssh_cmd = nil %w{csshX cssh}.each do |cmd| begin # Unix and Mac only cssh_cmd = shell_out!("which #{cmd}").stdout.strip break rescue Mixlib::ShellOut::ShellCommandFailed end end raise Chef::Exceptions::Exec, "no command found for cssh" unless cssh_cmd # pass in the consolidated identity file option to cssh(X) if config[:ssh_identity_file] cssh_cmd << " --ssh_args '-i #{File.expand_path(config[:ssh_identity_file])}'" end session.servers_for.each do |server| cssh_cmd << " #{server.user ? "#{server.user}@#{server.host}" : server.host}" end Chef::Log.debug("Starting cssh session with command: #{cssh_cmd}") exec(cssh_cmd) end def get_stripped_unfrozen_value(value) return nil if value.nil? value.strip end def configure_user config[:ssh_user] = get_stripped_unfrozen_value(config[:ssh_user] || Chef::Config[:knife][:ssh_user]) end # This is a bit overly complicated because of the way we want knife ssh to work with -P causing a password prompt for # the user, but we have to be conscious that this code gets included in knife bootstrap and knife * server create as # well. We want to change the semantics so that the default is false and 'nil' means -P without an argument on the # command line. But the other utilities expect nil to be the default and we can't prompt in that case. So we effectively # use ssh_password_ng to determine if we're coming from knife ssh or from the other utilities. The other utilties can # also be patched to use ssh_password_ng easily as long they follow the convention that the default is false. def configure_password if config.has_key?(:ssh_password_ng) && config[:ssh_password_ng].nil? # If the parameter is called on the command line with no value # it will set :ssh_password_ng = nil # This is where we want to trigger a prompt for password config[:ssh_password] = get_password else # if ssh_password_ng is false then it has not been set at all, and we may be in knife ec2 and still # using an old config[:ssh_password]. this is backwards compatibility. all knife cloud plugins should # be updated to use ssh_password_ng with a default of false and ssh_password should be retired, (but # we'll still need to use the ssh_password out of knife.rb if we find that). ssh_password = config.has_key?(:ssh_password_ng) ? config[:ssh_password_ng] : config[:ssh_password] # Otherwise, the password has either been specified on the command line, # in knife.rb, or key based auth will be attempted config[:ssh_password] = get_stripped_unfrozen_value(ssh_password || Chef::Config[:knife][:ssh_password]) end end def configure_ssh_identity_file # config[:identity_file] is DEPRECATED in favor of :ssh_identity_file config[:ssh_identity_file] = get_stripped_unfrozen_value(config[:ssh_identity_file] || config[:identity_file] || Chef::Config[:knife][:ssh_identity_file]) end def extract_nested_value(data_structure, path_spec) ui.presenter.extract_nested_value(data_structure, path_spec) end def run @longest = 0 configure_user configure_password configure_ssh_identity_file configure_gateway configure_session exit_status = case @name_args[1] when "interactive" interactive when "screen" screen when "tmux" tmux when "macterm" macterm when "cssh" cssh when "csshx" Chef::Log.warn("knife ssh csshx will be deprecated in a future release") Chef::Log.warn("please use knife ssh cssh instead") cssh else ssh_command(@name_args[1..-1].join(" ")) end session.close if exit_status != 0 exit exit_status else exit_status end end private :search_nodes end end end chef-12.14.60/lib/chef/knife/ssl_check.rb000066400000000000000000000205471276456504500177320ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/config" class Chef class Knife class SslCheck < Chef::Knife deps do require "pp" require "socket" require "uri" require "chef/http/ssl_policies" require "openssl" require "chef/mixin/proxified_socket" include Chef::Mixin::ProxifiedSocket end banner "knife ssl check [URL] (options)" def initialize(*args) @host = nil @verify_peer_socket = nil @ssl_policy = HTTP::DefaultSSLPolicy super end def uri @uri ||= begin Chef::Log.debug("Checking SSL cert on #{given_uri}") URI.parse(given_uri) end end def given_uri (name_args[0] || Chef::Config.chef_server_url) end def host uri.host end def port uri.port end def validate_uri unless host && port invalid_uri! end rescue URI::Error invalid_uri! end def invalid_uri! ui.error("Given URI: `#{given_uri}' is invalid") show_usage exit 1 end def verify_peer_socket @verify_peer_socket ||= begin tcp_connection = proxified_socket(host, port) ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context) ssl_client.hostname = host ssl_client end end def verify_peer_ssl_context @verify_peer_ssl_context ||= begin verify_peer_context = OpenSSL::SSL::SSLContext.new @ssl_policy.apply_to(verify_peer_context) verify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_PEER verify_peer_context end end def noverify_socket @noverify_socket ||= begin tcp_connection = proxified_socket(host, port) OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context) end end def noverify_peer_ssl_context @noverify_peer_ssl_context ||= begin noverify_peer_context = OpenSSL::SSL::SSLContext.new @ssl_policy.apply_to(noverify_peer_context) noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE noverify_peer_context end end def verify_X509 cert_debug_msg = "" trusted_certificates.each do |cert_name| message = check_X509_certificate(cert_name) unless message.nil? cert_debug_msg << File.expand_path(cert_name) + ": " + message + "\n" end end unless cert_debug_msg.empty? debug_invalid_X509(cert_debug_msg) end true # Maybe the bad certs won't hurt... end def verify_cert ui.msg("Connecting to host #{host}:#{port}") verify_peer_socket.connect true rescue OpenSSL::SSL::SSLError => e ui.error "The SSL certificate of #{host} could not be verified" Chef::Log.debug e.message debug_invalid_cert false end def verify_cert_host verify_peer_socket.post_connection_check(host) true rescue OpenSSL::SSL::SSLError => e ui.error "The SSL cert is signed by a trusted authority but is not valid for the given hostname" Chef::Log.debug(e) debug_invalid_host false end def debug_invalid_X509(cert_debug_msg) ui.msg("\n#{ui.color("Configuration Info:", :bold)}\n\n") debug_ssl_settings debug_chef_ssl_config ui.warn(<<-BAD_CERTS) There are invalid certificates in your trusted_certs_dir. OpenSSL will not use the following certificates when verifying SSL connections: #{cert_debug_msg} #{ui.color("TO FIX THESE WARNINGS:", :bold)} We are working on documentation for resolving common issues uncovered here. * If the certificate is generated by the server, you may try redownloading the server's certificate. By default, the certificate is stored in the following location on the host where your chef-server runs: /var/opt/opscode/nginx/ca/SERVER_HOSTNAME.crt Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir}) using SSH/SCP or some other secure method, then re-run this command to confirm that the server's certificate is now trusted. BAD_CERTS # @TODO: ^ needs URL once documentation is posted. end def debug_invalid_cert noverify_socket.connect issuer_info = noverify_socket.peer_cert.issuer ui.msg("Certificate issuer data: #{issuer_info}") ui.msg("\n#{ui.color("Configuration Info:", :bold)}\n\n") debug_ssl_settings debug_chef_ssl_config ui.err(<<-ADVICE) #{ui.color("TO FIX THIS ERROR:", :bold)} If the server you are connecting to uses a self-signed certificate, you must configure chef to trust that server's certificate. By default, the certificate is stored in the following location on the host where your chef-server runs: /var/opt/opscode/nginx/ca/SERVER_HOSTNAME.crt Copy that file to your trusted_certs_dir (currently: #{configuration.trusted_certs_dir}) using SSH/SCP or some other secure method, then re-run this command to confirm that the server's certificate is now trusted. ADVICE end def debug_invalid_host noverify_socket.connect subject = noverify_socket.peer_cert.subject cn_field_tuple = subject.to_a.find { |field| field[0] == "CN" } cn = cn_field_tuple[1] ui.error("You are attempting to connect to: '#{host}'") ui.error("The server's certificate belongs to '#{cn}'") ui.err(<<-ADVICE) #{ui.color("TO FIX THIS ERROR:", :bold)} The solution for this issue depends on your networking configuration. If you are able to connect to this server using the hostname #{cn} instead of #{host}, then you can resolve this issue by updating chef_server_url in your configuration file. If you are not able to connect to the server using the hostname #{cn} you will have to update the certificate on the server to use the correct hostname. ADVICE end def debug_ssl_settings ui.err "OpenSSL Configuration:" ui.err "* Version: #{OpenSSL::OPENSSL_VERSION}" ui.err "* Certificate file: #{OpenSSL::X509::DEFAULT_CERT_FILE}" ui.err "* Certificate directory: #{OpenSSL::X509::DEFAULT_CERT_DIR}" end def debug_chef_ssl_config ui.err "Chef SSL Configuration:" ui.err "* ssl_ca_path: #{configuration.ssl_ca_path.inspect}" ui.err "* ssl_ca_file: #{configuration.ssl_ca_file.inspect}" ui.err "* trusted_certs_dir: #{configuration.trusted_certs_dir.inspect}" end def configuration Chef::Config end def run validate_uri if verify_X509 && verify_cert && verify_cert_host ui.msg "Successfully verified certificates from `#{host}'" else exit 1 end end private def trusted_certificates if configuration.trusted_certs_dir && Dir.exist?(configuration.trusted_certs_dir) glob_dir = ChefConfig::PathHelper.escape_glob_dir(configuration.trusted_certs_dir) Dir.glob(File.join(glob_dir, "*.{crt,pem}")) else [] end end def check_X509_certificate(cert_file) store = OpenSSL::X509::Store.new cert = OpenSSL::X509::Certificate.new(IO.read(File.expand_path(cert_file))) begin store.add_cert(cert) # test if the store can verify the cert we just added unless store.verify(cert) # true if verified, false if not return store.error_string end rescue OpenSSL::X509::StoreError => e return e.message end return nil end end end end chef-12.14.60/lib/chef/knife/ssl_fetch.rb000066400000000000000000000110431276456504500177350ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/config" class Chef class Knife class SslFetch < Chef::Knife deps do require "pp" require "socket" require "uri" require "openssl" require "chef/mixin/proxified_socket" include Chef::Mixin::ProxifiedSocket end banner "knife ssl fetch [URL] (options)" def initialize(*args) super @uri = nil end def uri @uri ||= begin Chef::Log.debug("Checking SSL cert on #{given_uri}") URI.parse(given_uri) end end def given_uri (name_args[0] || Chef::Config.chef_server_url) end def host uri.host end def port uri.port end def validate_uri unless host && port invalid_uri! end rescue URI::Error invalid_uri! end def invalid_uri! ui.error("Given URI: `#{given_uri}' is invalid") show_usage exit 1 end def remote_cert_chain tcp_connection = proxified_socket(host, port) shady_ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context) shady_ssl_connection.connect shady_ssl_connection.peer_cert_chain end def noverify_peer_ssl_context @noverify_peer_ssl_context ||= begin noverify_peer_context = OpenSSL::SSL::SSLContext.new noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE noverify_peer_context end end def cn_of(certificate) subject = certificate.subject cn_field_tuple = subject.to_a.find { |field| field[0] == "CN" } cn_field_tuple[1] end # Convert the CN of a certificate into something that will work well as a # filename. To do so, all `*` characters are converted to the string # "wildcard" and then all characters other than alphanumeric and hypen # characters are converted to underscores. # NOTE: There is some confustion about what the CN will contain when # using internationalized domain names. RFC 6125 mandates that the ascii # representation be used, but it is not clear whether this is followed in # practice. # https://tools.ietf.org/html/rfc6125#section-6.4.2 def normalize_cn(cn) cn.gsub("*", "wildcard").gsub(/[^[:alnum:]\-]/, "_") end def configuration Chef::Config end def trusted_certs_dir configuration.trusted_certs_dir end def write_cert(cert) FileUtils.mkdir_p(trusted_certs_dir) cn = cn_of(cert) filename = File.join(trusted_certs_dir, "#{normalize_cn(cn)}.crt") ui.msg("Adding certificate for #{cn} in #{filename}") File.open(filename, File::CREAT | File::TRUNC | File::RDWR, 0644) do |f| f.print(cert.to_s) end end def run validate_uri ui.warn(<<-TRUST_TRUST) Certificates from #{host} will be fetched and placed in your trusted_cert directory (#{trusted_certs_dir}). Knife has no means to verify these are the correct certificates. You should verify the authenticity of these certificates after downloading. TRUST_TRUST remote_cert_chain.each do |cert| write_cert(cert) end rescue OpenSSL::SSL::SSLError => e # 'unknown protocol' usually means you tried to connect to a non-ssl # service. We handle that specially here, any other error we let bubble # up (probably a bug of some sort). raise unless e.message.include?("unknown protocol") ui.error("The service at the given URI (#{uri}) does not accept SSL connections") if uri.scheme == "http" https_uri = uri.to_s.sub(/^http/, "https") ui.error("Perhaps you meant to connect to '#{https_uri}'?") end exit 1 end end end end chef-12.14.60/lib/chef/knife/status.rb000066400000000000000000000070401276456504500173100ustar00rootroot00000000000000# # Author:: Ian Meyer () # Copyright:: Copyright 2010-2016, Ian Meyer # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/core/status_presenter" require "chef/knife/core/node_presenter" class Chef class Knife class Status < Knife include Knife::Core::NodeFormattingOptions deps do require "chef/search/query" end banner "knife status QUERY (options)" option :run_list, :short => "-r", :long => "--run-list", :description => "Show the run list" option :sort_reverse, :short => "-s", :long => "--sort-reverse", :description => "Sort the status list by last run time descending" option :hide_healthy, :short => "-H", :long => "--hide-healthy", :description => "Hide nodes that have run chef in the last hour. [DEPRECATED] Use --hide-by-mins MINS instead" option :hide_by_mins, :long => "--hide-by-mins MINS", :description => "Hide nodes that have run chef in the last MINS minutes" def append_to_query(term) @query << " AND " unless @query.empty? @query << term end def run ui.use_presenter Knife::Core::StatusPresenter if config[:long_output] opts = {} else opts = { filter_result: { name: ["name"], ipaddress: ["ipaddress"], ohai_time: ["ohai_time"], ec2: ["ec2"], run_list: ["run_list"], platform: ["platform"], platform_version: ["platform_version"], chef_environment: ["chef_environment"] } } end @query ||= "" append_to_query(@name_args[0]) if @name_args[0] append_to_query("chef_environment:#{config[:environment]}") if config[:environment] if config[:hide_healthy] ui.warn("-H / --hide-healthy is deprecated. Use --hide-by-mins MINS instead") time = Time.now.to_i # AND NOT is not valid lucene syntax, so don't use append_to_query @query << " " unless @query.empty? @query << "NOT ohai_time:[#{(time - 60 * 60)} TO #{time}]" end if config[:hide_by_mins] hidemins = config[:hide_by_mins].to_i time = Time.now.to_i # AND NOT is not valid lucene syntax, so don't use append_to_query @query << " " unless @query.empty? @query << "NOT ohai_time:[#{(time - hidemins * 60)} TO #{time}]" end @query = @query.empty? ? "*:*" : @query all_nodes = [] q = Chef::Search::Query.new Chef::Log.info("Sending query: #{@query}") q.search(:node, @query, opts) do |node| all_nodes << node end output(all_nodes.sort do |n1, n2| if config[:sort_reverse] || Chef::Config[:knife][:sort_status_reverse] (n2["ohai_time"] || 0) <=> (n1["ohai_time"] || 0) else (n1["ohai_time"] || 0) <=> (n2["ohai_time"] || 0) end end) end end end end chef-12.14.60/lib/chef/knife/supermarket_download.rb000066400000000000000000000021461276456504500222200ustar00rootroot00000000000000# # Author:: Christopher Webber () # Copyright:: Copyright (c) 2014 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/cookbook_site_download" class Chef class Knife class SupermarketDownload < Knife::CookbookSiteDownload # Handle the subclassing (knife doesn't do this :() dependency_loaders.concat(superclass.dependency_loaders) options.merge!(superclass.options) banner "knife supermarket download COOKBOOK [VERSION] (options)" category "supermarket" end end end chef-12.14.60/lib/chef/knife/supermarket_install.rb000066400000000000000000000021421276456504500220530ustar00rootroot00000000000000# # Author:: Christopher Webber () # Copyright:: Copyright (c) 2014 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/cookbook_site_install" class Chef class Knife class SupermarketInstall < Knife::CookbookSiteInstall # Handle the subclassing (knife doesn't do this :() dependency_loaders.concat(superclass.dependency_loaders) options.merge!(superclass.options) banner "knife supermarket install COOKBOOK [VERSION] (options)" category "supermarket" end end end chef-12.14.60/lib/chef/knife/supermarket_list.rb000066400000000000000000000021031276456504500213550ustar00rootroot00000000000000# # Author:: Christopher Webber () # Copyright:: Copyright (c) 2014 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/cookbook_site_list" class Chef class Knife class SupermarketList < Knife::CookbookSiteList # Handle the subclassing (knife doesn't do this :() dependency_loaders.concat(superclass.dependency_loaders) options.merge!(superclass.options) banner "knife supermarket list (options)" category "supermarket" end end end chef-12.14.60/lib/chef/knife/supermarket_search.rb000066400000000000000000000021211276456504500216470ustar00rootroot00000000000000# # Author:: Christopher Webber () # Copyright:: Copyright (c) 2014 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/cookbook_site_search" class Chef class Knife class SupermarketSearch < Knife::CookbookSiteSearch # Handle the subclassing (knife doesn't do this :() dependency_loaders.concat(superclass.dependency_loaders) options.merge!(superclass.options) banner "knife supermarket search QUERY (options)" category "supermarket" end end end chef-12.14.60/lib/chef/knife/supermarket_share.rb000066400000000000000000000021331276456504500215070ustar00rootroot00000000000000# # Author:: Christopher Webber () # Copyright:: Copyright (c) 2014 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/cookbook_site_share" class Chef class Knife class SupermarketShare < Knife::CookbookSiteShare # Handle the subclassing (knife doesn't do this :() dependency_loaders.concat(superclass.dependency_loaders) options.merge!(superclass.options) banner "knife supermarket share COOKBOOK [CATEGORY] (options)" category "supermarket" end end end chef-12.14.60/lib/chef/knife/supermarket_show.rb000066400000000000000000000021261276456504500213670ustar00rootroot00000000000000# # Author:: Christopher Webber () # Copyright:: Copyright (c) 2014 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/cookbook_site_show" class Chef class Knife class SupermarketShow < Knife::CookbookSiteShow # Handle the subclassing (knife doesn't do this :() dependency_loaders.concat(superclass.dependency_loaders) options.merge!(superclass.options) banner "knife supermarket show COOKBOOK [VERSION] (options)" category "supermarket" end end end chef-12.14.60/lib/chef/knife/supermarket_unshare.rb000066400000000000000000000021301276456504500220470ustar00rootroot00000000000000# # Author:: Christopher Webber () # Copyright:: Copyright (c) 2014 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/cookbook_site_unshare" class Chef class Knife class SupermarketUnshare < Knife::CookbookSiteUnshare # Handle the subclassing (knife doesn't do this :() dependency_loaders.concat(superclass.dependency_loaders) options.merge!(superclass.options) banner "knife supermarket unshare COOKBOOK (options)" category "supermarket" end end end chef-12.14.60/lib/chef/knife/tag_create.rb000066400000000000000000000026271276456504500200710ustar00rootroot00000000000000# # Author:: Ryan Davis () # Author:: Daniel DeLeo () # Author:: Nuo Yan () # Copyright:: Copyright 2011-2016, Ryan Davis and Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class TagCreate < Knife deps do require "chef/node" end banner "knife tag create NODE TAG ..." def run name = @name_args[0] tags = @name_args[1..-1] if name.nil? || tags.nil? || tags.empty? show_usage ui.fatal("You must specify a node name and at least one tag.") exit 1 end node = Chef::Node.load name tags.each do |tag| (node.tags << tag).uniq! end node.save ui.info("Created tags #{tags.join(", ")} for node #{name}.") end end end end chef-12.14.60/lib/chef/knife/tag_delete.rb000066400000000000000000000033031276456504500200600ustar00rootroot00000000000000# # Author:: Ryan Davis () # Author:: Daniel DeLeo () # Author:: Nuo Yan () # Copyright:: Copyright 2011-2016, Ryan Davis and Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class TagDelete < Knife deps do require "chef/node" end banner "knife tag delete NODE TAG ..." def run name = @name_args[0] tags = @name_args[1..-1] if name.nil? || tags.nil? || tags.empty? show_usage ui.fatal("You must specify a node name and at least one tag.") exit 1 end node = Chef::Node.load name deleted_tags = Array.new tags.each do |tag| unless node.tags.delete(tag).nil? deleted_tags << tag end end node.save message = if deleted_tags.empty? "Nothing has changed. The tags requested to be deleted do not exist." else "Deleted tags #{deleted_tags.join(", ")} for node #{name}." end ui.info(message) end end end end chef-12.14.60/lib/chef/knife/tag_list.rb000066400000000000000000000022631276456504500175750ustar00rootroot00000000000000# # Author:: Ryan Davis () # Author:: Daniel DeLeo () # Author:: Nuo Yan () # Copyright:: Copyright 2011-2016, Ryan Davis and Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class TagList < Knife deps do require "chef/node" end banner "knife tag list NODE" def run name = @name_args[0] if name.nil? show_usage ui.fatal("You must specify a node name.") exit 1 end node = Chef::Node.load(name) output(node.tags) end end end end chef-12.14.60/lib/chef/knife/upload.rb000066400000000000000000000037361276456504500172610ustar00rootroot00000000000000require "chef/chef_fs/knife" class Chef class Knife class Upload < Chef::ChefFS::Knife banner "knife upload PATTERNS" category "path-based" deps do require "chef/chef_fs/command_line" end option :recurse, :long => "--[no-]recurse", :boolean => true, :default => true, :description => "List directories recursively." option :purge, :long => "--[no-]purge", :boolean => true, :default => false, :description => "Delete matching local files and directories that do not exist remotely." option :force, :long => "--[no-]force", :boolean => true, :default => false, :description => "Force upload of files even if they match (quicker for many files). Will overwrite frozen cookbooks." option :freeze, :long => "--[no-]freeze", :boolean => true, :default => false, :description => "Freeze cookbooks that get uploaded." option :dry_run, :long => "--dry-run", :short => "-n", :boolean => true, :default => false, :description => "Don't take action, only print what would happen" option :diff, :long => "--[no-]diff", :boolean => true, :default => true, :description => "Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff" def run if name_args.length == 0 show_usage ui.fatal("You must specify at least one argument. If you want to upload everything in this directory, run \"knife upload .\"") exit 1 end error = false pattern_args.each do |pattern| if Chef::ChefFS::FileSystem.copy_to(pattern, local_fs, chef_fs, config[:recurse] ? nil : 1, config, ui, proc { |entry| format_path(entry) }) error = true end end if error exit 1 end end end end end chef-12.14.60/lib/chef/knife/user_create.rb000066400000000000000000000115021276456504500202640ustar00rootroot00000000000000# # Author:: Steven Danna () # Author:: Tyler Cloke () # 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 "chef/knife" require "chef/knife/osc_user_create" class Chef class Knife class UserCreate < Knife attr_accessor :user_field deps do require "chef/user_v1" require "chef/json_compat" end option :file, :short => "-f FILE", :long => "--file FILE", :description => "Write the private key to a file if the server generated one." option :user_key, :long => "--user-key FILENAME", :description => "Set the initial default key for the user from a file on disk (cannot pass with --prevent-keygen)." option :prevent_keygen, :short => "-k", :long => "--prevent-keygen", :description => "API V1 (Chef Server 12.1+) only. Prevent server from generating a default key pair for you. Cannot be passed with --user-key.", :boolean => true option :admin, :short => "-a", :long => "--admin", :description => "DEPRECATED: Open Source Chef 11 only. Create the user as an admin.", :boolean => true option :user_password, :short => "-p PASSWORD", :long => "--password PASSWORD", :description => "DEPRECATED: Open Source Chef 11 only. Password for newly created user.", :default => "" banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)" def user @user_field ||= Chef::UserV1.new end def create_user_from_hash(hash) Chef::UserV1.from_hash(hash).create end def osc_11_warning <<-EOF IF YOU ARE USING CHEF SERVER 12+, PLEASE FOLLOW THE INSTRUCTIONS UNDER knife user create --help. You only passed a single argument to knife user create. For backwards compatibility, when only a single argument is passed, knife user create assumes you want Open Source 11 Server user creation. knife user create for Open Source 11 Server is being deprecated. Open Source 11 Server user commands now live under the knife osc_user namespace. For backwards compatibility, we will forward this request to knife osc_user create. If you are using an Open Source 11 Server, please use that command to avoid this warning. EOF end def run_osc_11_user_create # run osc_user_create with our input ARGV.delete("user") ARGV.unshift("osc_user") Chef::Knife.run(ARGV, Chef::Application::Knife.options) end def run # DEPRECATION NOTE # Remove this if statement and corrosponding code post OSC 11 support. # # If only 1 arg is passed, assume OSC 11 case. if @name_args.length == 1 ui.warn(osc_11_warning) run_osc_11_user_create else # EC / CS 12 user create test_mandatory_field(@name_args[0], "username") user.username @name_args[0] test_mandatory_field(@name_args[1], "display name") user.display_name @name_args[1] test_mandatory_field(@name_args[2], "first name") user.first_name @name_args[2] test_mandatory_field(@name_args[3], "last name") user.last_name @name_args[3] test_mandatory_field(@name_args[4], "email") user.email @name_args[4] test_mandatory_field(@name_args[5], "password") user.password @name_args[5] if config[:user_key] && config[:prevent_keygen] show_usage ui.fatal("You cannot pass --user-key and --prevent-keygen") exit 1 end if !config[:prevent_keygen] && !config[:user_key] user.create_key(true) end if config[:user_key] user.public_key File.read(File.expand_path(config[:user_key])) end output = edit_hash(user) final_user = create_user_from_hash(output) ui.info("Created #{user}") if final_user.private_key if config[:file] File.open(config[:file], "w") do |f| f.print(final_user.private_key) end else ui.msg final_user.private_key end end end end end end end chef-12.14.60/lib/chef/knife/user_delete.rb000066400000000000000000000054621276456504500202730ustar00rootroot00000000000000# # Author:: Steven Danna () # 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 "chef/knife" class Chef class Knife class UserDelete < Knife deps do require "chef/user_v1" require "chef/json_compat" end banner "knife user delete USER (options)" def osc_11_warning <<-EOF The Chef Server you are using does not support the username field. This means it is an Open Source 11 Server. knife user delete for Open Source 11 Server is being deprecated. Open Source 11 Server user commands now live under the knife osc_user namespace. For backwards compatibility, we will forward this request to knife osc_user delete. If you are using an Open Source 11 Server, please use that command to avoid this warning. EOF end def run_osc_11_user_delete # run osc_user_delete with our input ARGV.delete("user") ARGV.unshift("osc_user") Chef::Knife.run(ARGV, Chef::Application::Knife.options) end # DEPRECATION NOTE # Delete this override method after OSC 11 support is dropped def delete_object(user_name) confirm("Do you really want to delete #{user_name}") if Kernel.block_given? object = block.call else object = Chef::UserV1.load(user_name) object.destroy end output(format_for_display(object)) if config[:print_after] self.msg("Deleted #{user_name}") end def run @user_name = @name_args[0] if @user_name.nil? show_usage ui.fatal("You must specify a user name") exit 1 end # DEPRECATION NOTE # # Below is modification of Chef::Knife.delete_object to detect OSC 11 server. # When OSC 11 is deprecated, simply delete all this and go back to: # # delete_object(Chef::UserV1, @user_name) # # Also delete our override of delete_object above object = Chef::UserV1.load(@user_name) # OSC 11 case if object.username.nil? ui.warn(osc_11_warning) run_osc_11_user_delete else # proceed with EC / CS delete delete_object(@user_name) end end end end end chef-12.14.60/lib/chef/knife/user_edit.rb000066400000000000000000000047251276456504500177570ustar00rootroot00000000000000# # Author:: Steven Danna () # 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 "chef/knife" class Chef class Knife class UserEdit < Knife deps do require "chef/user_v1" require "chef/json_compat" end banner "knife user edit USER (options)" def osc_11_warning <<-EOF The Chef Server you are using does not support the username field. This means it is an Open Source 11 Server. knife user edit for Open Source 11 Server is being deprecated. Open Source 11 Server user commands now live under the knife oc_user namespace. For backwards compatibility, we will forward this request to knife osc_user edit. If you are using an Open Source 11 Server, please use that command to avoid this warning. EOF end def run_osc_11_user_edit # run osc_user_create with our input ARGV.delete("user") ARGV.unshift("osc_user") Chef::Knife.run(ARGV, Chef::Application::Knife.options) end def run @user_name = @name_args[0] if @user_name.nil? show_usage ui.fatal("You must specify a user name") exit 1 end original_user = Chef::UserV1.load(@user_name).to_hash # DEPRECATION NOTE # Remove this if statement and corrosponding code post OSC 11 support. # # if username is nil, we are in the OSC 11 case, # forward to deprecated command if original_user["username"].nil? ui.warn(osc_11_warning) run_osc_11_user_edit else # EC / CS 12 user create edited_user = edit_hash(original_user) if original_user != edited_user user = Chef::UserV1.from_hash(edited_user) user.update ui.msg("Saved #{user}.") else ui.msg("User unchanged, not saving.") end end end end end end chef-12.14.60/lib/chef/knife/user_key_create.rb000066400000000000000000000033521276456504500211400ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/key_create_base" class Chef class Knife # Implements knife user key create using Chef::Knife::KeyCreate # as a service class. # # @author Tyler Cloke # # @attr_reader [String] actor the name of the user that this key is for class UserKeyCreate < Knife include Chef::Knife::KeyCreateBase banner "knife user key create USER (options)" attr_reader :actor def initialize(argv = []) super(argv) @service_object = nil end def run apply_params!(@name_args) service_object.run end def actor_field_name "user" end def service_object @service_object ||= Chef::Knife::KeyCreate.new(@actor, actor_field_name, ui, config) end def actor_missing_error "You must specify a user name" end def apply_params!(params) @actor = params[0] if @actor.nil? show_usage ui.fatal(actor_missing_error) exit 1 end end end end end chef-12.14.60/lib/chef/knife/user_key_delete.rb000066400000000000000000000036011276456504500211340ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife # Implements knife user key delete using Chef::Knife::KeyDelete # as a service class. # # @author Tyler Cloke # # @attr_reader [String] actor the name of the client that this key is for class UserKeyDelete < Knife banner "knife user key delete USER KEYNAME (options)" attr_reader :actor def initialize(argv = []) super(argv) @service_object = nil end def run apply_params!(@name_args) service_object.run end def actor_field_name "user" end def actor_missing_error "You must specify a user name" end def keyname_missing_error "You must specify a key name" end def service_object @service_object ||= Chef::Knife::KeyDelete.new(@name, @actor, actor_field_name, ui) end def apply_params!(params) @actor = params[0] if @actor.nil? show_usage ui.fatal(actor_missing_error) exit 1 end @name = params[1] if @name.nil? show_usage ui.fatal(keyname_missing_error) exit 1 end end end end end chef-12.14.60/lib/chef/knife/user_key_edit.rb000066400000000000000000000037101276456504500206200ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/key_edit_base" class Chef class Knife # Implements knife user key edit using Chef::Knife::KeyEdit # as a service class. # # @author Tyler Cloke # # @attr_reader [String] actor the name of the user that this key is for class UserKeyEdit < Knife include Chef::Knife::KeyEditBase banner "knife user key edit USER KEYNAME (options)" attr_reader :actor def initialize(argv = []) super(argv) @service_object = nil end def run apply_params!(@name_args) service_object.run end def actor_field_name "user" end def service_object @service_object ||= Chef::Knife::KeyEdit.new(@name, @actor, actor_field_name, ui, config) end def actor_missing_error "You must specify a user name" end def keyname_missing_error "You must specify a key name" end def apply_params!(params) @actor = params[0] if @actor.nil? show_usage ui.fatal(actor_missing_error) exit 1 end @name = params[1] if @name.nil? show_usage ui.fatal(keyname_missing_error) exit 1 end end end end end chef-12.14.60/lib/chef/knife/user_key_list.rb000066400000000000000000000033331276456504500206470ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/knife/key_list_base" class Chef class Knife # Implements knife user key list using Chef::Knife::KeyList # as a service class. # # @author Tyler Cloke # # @attr_reader [String] actor the name of the client that this key is for class UserKeyList < Knife include Chef::Knife::KeyListBase banner "knife user key list USER (options)" attr_reader :actor def initialize(argv = []) super(argv) @service_object = nil end def run apply_params!(@name_args) service_object.run end def list_method :list_by_user end def actor_missing_error "You must specify a user name" end def service_object @service_object ||= Chef::Knife::KeyList.new(@actor, list_method, ui, config) end def apply_params!(params) @actor = params[0] if @actor.nil? show_usage ui.fatal(actor_missing_error) exit 1 end end end end end chef-12.14.60/lib/chef/knife/user_key_show.rb000066400000000000000000000035641276456504500206620ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife # Implements knife user key show using Chef::Knife::KeyShow # as a service class. # # @author Tyler Cloke # # @attr_reader [String] actor the name of the client that this key is for class UserKeyShow < Knife banner "knife user key show USER KEYNAME (options)" attr_reader :actor def initialize(argv = []) super(argv) @service_object = nil end def run apply_params!(@name_args) service_object.run end def load_method :load_by_user end def actor_missing_error "You must specify a user name" end def keyname_missing_error "You must specify a key name" end def service_object @service_object ||= Chef::Knife::KeyShow.new(@name, @actor, load_method, ui) end def apply_params!(params) @actor = params[0] if @actor.nil? show_usage ui.fatal(actor_missing_error) exit 1 end @name = params[1] if @name.nil? show_usage ui.fatal(keyname_missing_error) exit 1 end end end end end chef-12.14.60/lib/chef/knife/user_list.rb000066400000000000000000000023351276456504500200000ustar00rootroot00000000000000# # Author:: Steven Danna () # 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 "chef/knife" # NOTE: only knife user command that is backwards compatible with OSC 11, # so no deprecation warnings are necessary. class Chef class Knife class UserList < Knife deps do require "chef/user_v1" require "chef/json_compat" end banner "knife user list (options)" option :with_uri, :short => "-w", :long => "--with-uri", :description => "Show corresponding URIs" def run output(format_list_for_display(Chef::UserV1.list)) end end end end chef-12.14.60/lib/chef/knife/user_reregister.rb000066400000000000000000000051521276456504500212000ustar00rootroot00000000000000# # Author:: Steven Danna () # 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 "chef/knife" class Chef class Knife class UserReregister < Knife deps do require "chef/user_v1" require "chef/json_compat" end banner "knife user reregister USER (options)" def osc_11_warning <<-EOF The Chef Server you are using does not support the username field. This means it is an Open Source 11 Server. knife user reregister for Open Source 11 Server is being deprecated. Open Source 11 Server user commands now live under the knife osc_user namespace. For backwards compatibility, we will forward this request to knife osc_user reregister. If you are using an Open Source 11 Server, please use that command to avoid this warning. EOF end def run_osc_11_user_reregister # run osc_user_edit with our input ARGV.delete("user") ARGV.unshift("osc_user") Chef::Knife.run(ARGV, Chef::Application::Knife.options) end option :file, :short => "-f FILE", :long => "--file FILE", :description => "Write the private key to a file" def run @user_name = @name_args[0] if @user_name.nil? show_usage ui.fatal("You must specify a user name") exit 1 end user = Chef::UserV1.load(@user_name) # DEPRECATION NOTE # Remove this if statement and corrosponding code post OSC 11 support. # # if username is nil, we are in the OSC 11 case, # forward to deprecated command if user.username.nil? ui.warn(osc_11_warning) run_osc_11_user_reregister else # EC / CS 12 case user.reregister Chef::Log.debug("Updated user data: #{user.inspect}") key = user.private_key if config[:file] File.open(config[:file], "w") do |f| f.print(key) end else ui.msg key end end end end end end chef-12.14.60/lib/chef/knife/user_show.rb000066400000000000000000000043441276456504500200070ustar00rootroot00000000000000# # Author:: Steven Danna () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" class Chef class Knife class UserShow < Knife include Knife::Core::MultiAttributeReturnOption deps do require "chef/user_v1" require "chef/json_compat" end banner "knife user show USER (options)" def osc_11_warning <<-EOF The Chef Server you are using does not support the username field. This means it is an Open Source 11 Server. knife user show for Open Source 11 Server is being deprecated. Open Source 11 Server user commands now live under the knife osc_user namespace. For backwards compatibility, we will forward this request to knife osc_user show. If you are using an Open Source 11 Server, please use that command to avoid this warning. EOF end def run_osc_11_user_show # run osc_user_edit with our input ARGV.delete("user") ARGV.unshift("osc_user") Chef::Knife.run(ARGV, Chef::Application::Knife.options) end def run @user_name = @name_args[0] if @user_name.nil? show_usage ui.fatal("You must specify a user name") exit 1 end user = Chef::UserV1.load(@user_name) # DEPRECATION NOTE # Remove this if statement and corrosponding code post OSC 11 support. # # if username is nil, we are in the OSC 11 case, # forward to deprecated command if user.username.nil? ui.warn(osc_11_warning) run_osc_11_user_show else output(format_for_display(user)) end end end end end chef-12.14.60/lib/chef/knife/xargs.rb000066400000000000000000000212101276456504500171040ustar00rootroot00000000000000require "chef/chef_fs/knife" class Chef class Knife class Xargs < Chef::ChefFS::Knife banner "knife xargs [COMMAND]" category "path-based" deps do require "chef/chef_fs/file_system" require "chef/chef_fs/file_system/not_found_error" end # TODO modify to remote-only / local-only pattern (more like delete) option :local, :long => "--local", :boolean => true, :description => "Xargs local files instead of remote" option :patterns, :long => "--pattern [PATTERN]", :short => "-p [PATTERN]", :description => "Pattern on command line (if these are not specified, a list of patterns is expected on standard input). Multiple patterns may be passed in this way.", :arg_arity => [1, -1] option :diff, :long => "--[no-]diff", :default => true, :boolean => true, :description => "Whether to show a diff when files change (default: true)" option :dry_run, :long => "--dry-run", :boolean => true, :description => "Prevents changes from actually being uploaded to the server." option :force, :long => "--[no-]force", :boolean => true, :default => false, :description => "Force upload of files even if they are not changed (quicker and harmless, but doesn't print out what it changed)" option :replace_first, :long => "--replace-first REPLACESTR", :short => "-J REPLACESTR", :description => "String to replace with filenames. -J will only replace the FIRST occurrence of the replacement string." option :replace_all, :long => "--replace REPLACESTR", :short => "-I REPLACESTR", :description => "String to replace with filenames. -I will replace ALL occurrence of the replacement string." option :max_arguments_per_command, :long => "--max-args MAXARGS", :short => "-n MAXARGS", :description => "Maximum number of arguments per command line." option :max_command_line, :long => "--max-chars LENGTH", :short => "-s LENGTH", :description => "Maximum size of command line, in characters" option :verbose_commands, :short => "-t", :description => "Print command to be run on the command line" option :null_separator, :short => "-0", :boolean => true, :description => "Use the NULL character (\0) as a separator, instead of whitespace" def run error = false # Get the matches (recursively) files = [] pattern_args_from(get_patterns).each do |pattern| Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result| if result.dir? # TODO option to include directories ui.warn "#{format_path(result)}: is a directory. Will not run #{command} on it." else files << result ran = false # If the command would be bigger than max command line, back it off a bit # and run a slightly smaller command (with one less arg) if config[:max_command_line] command, tempfiles = create_command(files) begin if command.length > config[:max_command_line].to_i if files.length > 1 command, tempfiles_minus_one = create_command(files[0..-2]) begin error = true if xargs_files(command, tempfiles_minus_one) files = [ files[-1] ] ran = true ensure destroy_tempfiles(tempfiles) end else error = true if xargs_files(command, tempfiles) files = [ ] ran = true end end ensure destroy_tempfiles(tempfiles) end end # If the command has hit the limit for the # of arguments, run it if !ran && config[:max_arguments_per_command] && files.size >= config[:max_arguments_per_command].to_i command, tempfiles = create_command(files) begin error = true if xargs_files(command, tempfiles) files = [] ran = true ensure destroy_tempfiles(tempfiles) end end end end end # Any leftovers commands shall be run if files.size > 0 command, tempfiles = create_command(files) begin error = true if xargs_files(command, tempfiles) ensure destroy_tempfiles(tempfiles) end end if error exit 1 end end def get_patterns if config[:patterns] [ config[:patterns] ].flatten elsif config[:null_separator] stdin.binmode stdin.read.split("\000") else stdin.read.split(/\s+/) end end def create_command(files) command = name_args.join(" ") # Create the (empty) tempfiles tempfiles = {} begin # Create the temporary files files.each do |file| tempfile = Tempfile.new(file.name) tempfiles[tempfile] = { :file => file } end rescue destroy_tempfiles(files) raise end # Create the command paths = tempfiles.keys.map { |tempfile| tempfile.path }.join(" ") if config[:replace_all] final_command = command.gsub(config[:replace_all], paths) elsif config[:replace_first] final_command = command.sub(config[:replace_first], paths) else final_command = "#{command} #{paths}" end [final_command, tempfiles] end def destroy_tempfiles(tempfiles) # Unlink the files now that we're done with them tempfiles.keys.each { |tempfile| tempfile.close! } end def xargs_files(command, tempfiles) error = false # Create the temporary files tempfiles.each_pair do |tempfile, file| begin value = file[:file].read file[:value] = value tempfile.open tempfile.write(value) tempfile.close rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e ui.error "#{format_path(e.entry)}: #{e.reason}." error = true tempfile.close! tempfiles.delete(tempfile) next rescue Chef::ChefFS::FileSystem::NotFoundError => e ui.error "#{format_path(e.entry)}: No such file or directory" error = true tempfile.close! tempfiles.delete(tempfile) next end end return error if error && tempfiles.size == 0 # Run the command if config[:verbose_commands] || Chef::Config[:verbosity] && Chef::Config[:verbosity] >= 1 output sub_filenames(command, tempfiles) end command_output = `#{command}` command_output = sub_filenames(command_output, tempfiles) stdout.write command_output # Check if the output is different tempfiles.each_pair do |tempfile, file| # Read the new output new_value = IO.binread(tempfile.path) # Upload the output if different if config[:force] || new_value != file[:value] if config[:dry_run] output "Would update #{format_path(file[:file])}" else file[:file].write(new_value) output "Updated #{format_path(file[:file])}" end end # Print a diff of what was uploaded if config[:diff] && new_value != file[:value] old_file = Tempfile.open(file[:file].name) begin old_file.write(file[:value]) old_file.close diff = `diff -u #{old_file.path} #{tempfile.path}` diff.gsub!(old_file.path, "#{format_path(file[:file])} (old)") diff.gsub!(tempfile.path, "#{format_path(file[:file])} (new)") stdout.write diff ensure old_file.close! end end end error end def sub_filenames(str, tempfiles) tempfiles.each_pair do |tempfile, file| str = str.gsub(tempfile.path, format_path(file[:file])) end str end end end end chef-12.14.60/lib/chef/local_mode.rb000066400000000000000000000075201276456504500167720ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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" if Chef::Platform.windows? if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1") require "chef/monkey_patches/webrick-utils" end end class Chef module LocalMode # Create a chef local server (if the configuration requires one) for the # duration of the given block. # # # This ... # with_server_connectivity { stuff } # # # Is exactly equivalent to this ... # Chef::LocalMode.setup_server_connectivity # begin # stuff # ensure # Chef::LocalMode.destroy_server_connectivity # end # def self.with_server_connectivity setup_server_connectivity begin yield ensure destroy_server_connectivity end end # If Chef::Config.chef_zero.enabled is true, sets up a chef-zero server # according to the Chef::Config.chef_zero and path options, and sets # chef_server_url to point at it. def self.setup_server_connectivity if Chef::Config.chef_zero.enabled destroy_server_connectivity require "chef_zero/server" require "chef/chef_fs/chef_fs_data_store" require "chef/chef_fs/config" @chef_fs = Chef::ChefFS::Config.new.local_fs @chef_fs.write_pretty_json = true data_store = Chef::ChefFS::ChefFSDataStore.new(@chef_fs) data_store = ChefZero::DataStore::V1ToV2Adapter.new(data_store, "chef") server_options = {} server_options[:data_store] = data_store server_options[:log_level] = Chef::Log.level server_options[:osc_compat] = Chef::Config.chef_zero.osc_compat server_options[:single_org] = Chef::Config.chef_zero.single_org server_options[:host] = Chef::Config.chef_zero.host server_options[:port] = parse_port(Chef::Config.chef_zero.port) @chef_zero_server = ChefZero::Server.new(server_options) if Chef::Config[:listen] @chef_zero_server.start_background else @chef_zero_server.start_socketless end local_mode_url = @chef_zero_server.local_mode_url Chef::Log.info("Started chef-zero at #{local_mode_url} with #{@chef_fs.fs_description}") Chef::Config.chef_server_url = local_mode_url end end # Return the current chef-zero server set up by setup_server_connectivity. def self.chef_zero_server @chef_zero_server end # Return the chef_fs object for the current chef-zero server. def self.chef_fs @chef_fs end # If chef_zero_server is non-nil, stop it and remove references to it. def self.destroy_server_connectivity if @chef_zero_server @chef_zero_server.stop @chef_zero_server = nil end end def self.parse_port(port) if port.is_a?(String) parts = port.split(",") if parts.size == 1 a, b = parts[0].split("-", 2) if b a.to_i.upto(b.to_i) else [ a.to_i ] end else array = [] parts.each do |part| array += parse_port(part).to_a end array end else port end end end end chef-12.14.60/lib/chef/log.rb000066400000000000000000000043271276456504500154570ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: AJ Christensen (<@aj@opscode.com>) # Author:: Christopher Brown () # 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 "logger" require "chef/monologger" require "chef/exceptions" require "mixlib/log" require "chef/log/syslog" unless RUBY_PLATFORM =~ /mswin|mingw|windows/ require "chef/log/winevt" class Chef class Log extend Mixlib::Log # Force initialization of the primary log device (@logger) init(MonoLogger.new(STDOUT)) class Formatter def self.show_time=(*args) Mixlib::Log::Formatter.show_time = *args end end # # Get the location of the caller (from the recipe). Grabs the first caller # that is *not* in the chef gem proper (allowing us to weed out internal # calls and give the user a more useful perspective). # # @return [String] The location of the caller (file:line#) from caller(0..20), or nil if no non-chef caller is found. # def self.caller_location # Pick the first caller that is *not* part of the Chef gem, that's the # thing the user wrote. chef_gem_path = File.expand_path("../..", __FILE__) caller(0..20).find { |c| !c.start_with?(chef_gem_path) } end def self.deprecation(msg = nil, location = caller(2..2)[0], &block) if msg msg << " at #{Array(location).join("\n")}" msg = msg.join("") if msg.respond_to?(:join) end if Chef::Config[:treat_deprecation_warnings_as_errors] error(msg, &block) raise Chef::Exceptions::DeprecatedFeatureError.new(msg) else warn(msg, &block) end end end end chef-12.14.60/lib/chef/log/000077500000000000000000000000001276456504500151245ustar00rootroot00000000000000chef-12.14.60/lib/chef/log/syslog.rb000066400000000000000000000025501276456504500167730ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Author:: SAWANOBORI Yukihiko () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "logger" require "syslog-logger" require "chef/mixin/unformatter" class Chef class Log # # Chef::Log::Syslog class. # usage in client.rb: # log_location Chef::Log::Syslog.new("chef-client", ::Syslog::LOG_DAEMON) # class Syslog < Logger::Syslog include Chef::Mixin::Unformatter attr_accessor :sync, :formatter def initialize(program_name = "chef-client", facility = ::Syslog::LOG_DAEMON, logopts = nil) super return if defined? ::Logger::Syslog::SYSLOG ::Logger::Syslog.const_set :SYSLOG, SYSLOG end def close end end end end chef-12.14.60/lib/chef/log/winevt.rb000066400000000000000000000050251276456504500167670ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # # Copyright:: Copyright 2015-2016, Chef Software, Inc. # # 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/event_loggers/base" require "chef/platform/query_helpers" require "chef/mixin/unformatter" class Chef class Log # # Chef::Log::WinEvt class. # usage in client.rb: # log_location Chef::Log::WinEvt.new # class WinEvt # These must match those that are defined in the manifest file INFO_EVENT_ID = 10100 WARN_EVENT_ID = 10101 DEBUG_EVENT_ID = 10102 ERROR_EVENT_ID = 10103 FATAL_EVENT_ID = 10104 # Since we must install the event logger, this is not really configurable SOURCE = "Chef" include Chef::Mixin::Unformatter attr_accessor :sync, :formatter, :level def initialize(eventlog = nil) @eventlog = eventlog || ::Win32::EventLog.open("Application") end def close end def info(msg) @eventlog.report_event( :event_type => ::Win32::EventLog::INFO_TYPE, :source => SOURCE, :event_id => INFO_EVENT_ID, :data => [msg] ) end def warn(msg) @eventlog.report_event( :event_type => ::Win32::EventLog::WARN_TYPE, :source => SOURCE, :event_id => WARN_EVENT_ID, :data => [msg] ) end def debug(msg) @eventlog.report_event( :event_type => ::Win32::EventLog::INFO_TYPE, :source => SOURCE, :event_id => DEBUG_EVENT_ID, :data => [msg] ) end def error(msg) @eventlog.report_event( :event_type => ::Win32::EventLog::ERROR_TYPE, :source => SOURCE, :event_id => ERROR_EVENT_ID, :data => [msg] ) end def fatal(msg) @eventlog.report_event( :event_type => ::Win32::EventLog::ERROR_TYPE, :source => SOURCE, :event_id => FATAL_EVENT_ID, :data => [msg] ) end end end end chef-12.14.60/lib/chef/mash.rb000066400000000000000000000155161276456504500156300ustar00rootroot00000000000000# Copyright 2009-2016, Dan Kubb # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # --- # --- # Some portions of blank.rb and mash.rb are verbatim copies of software # licensed under the MIT license. That license is included below: # Copyright 2005-2016, David Heinemeier Hansson # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # This class has dubious semantics and we only have it so that people can write # params[:key] instead of params['key']. class Mash < Hash # @param constructor # The default value for the mash. Defaults to an empty hash. # # @details [Alternatives] # If constructor is a Hash, a new mash will be created based on the keys of # the hash and no default value will be set. def initialize(constructor = {}) if constructor.is_a?(Hash) super() update(constructor) else super(constructor) end end # @param orig Mash being copied # # @return [Object] A new copied Mash def initialize_copy(orig) super # Handle nested values each do |k, v| if v.kind_of?(Mash) || v.is_a?(Array) self[k] = v.dup end end self end # @param key The default value for the mash. Defaults to nil. # # @details [Alternatives] # If key is a Symbol and it is a key in the mash, then the default value will # be set to the value matching the key. def default(key = nil) if key.is_a?(Symbol) && include?(key = key.to_s) self[key] else super end end alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) alias_method :regular_update, :update unless method_defined?(:regular_update) # @param key The key to set. # @param value # The value to set the key to. # # @see Mash#convert_key # @see Mash#convert_value def []=(key, value) regular_writer(convert_key(key), convert_value(value)) end # @param other_hash # A hash to update values in the mash with. The keys and the values will be # converted to Mash format. # # @return [Mash] The updated mash. def update(other_hash) other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) } self end alias_method :merge!, :update # @param key The key to check for. This will be run through convert_key. # # @return [Boolean] True if the key exists in the mash. def key?(key) super(convert_key(key)) end # def include? def has_key? def member? alias_method :include?, :key? alias_method :has_key?, :key? alias_method :member?, :key? # @param key The key to fetch. This will be run through convert_key. # @param *extras Default value. # # @return [Object] The value at key or the default value. def fetch(key, *extras) super(convert_key(key), *extras) end # @param *indices # The keys to retrieve values for. These will be run through +convert_key+. # # @return [Array] The values at each of the provided keys def values_at(*indices) indices.collect { |key| self[convert_key(key)] } end # @param hash The hash to merge with the mash. # # @return [Mash] A new mash with the hash values merged in. def merge(hash) self.dup.update(hash) end # @param key # The key to delete from the mash.\ def delete(key) super(convert_key(key)) end # @param *rejected 1, :two => 2, :three => 3 }.except(:one) # #=> { "two" => 2, "three" => 3 } def except(*keys) super(*keys.map { |k| convert_key(k) }) end # Used to provide the same interface as Hash. # # @return [Mash] This mash unchanged. def stringify_keys!; self end # @return [Hash] The mash as a Hash with symbolized keys. def symbolize_keys h = Hash.new(default) each { |key, val| h[key.to_sym] = val } h end # @return [Hash] The mash as a Hash with string keys. def to_hash Hash.new(default).merge(self) end # @return [Mash] Convert a Hash into a Mash # The input Hash's default value is maintained def self.from_hash(hash) mash = Mash.new(hash) mash.default = hash.default mash end protected # @param key The key to convert. # # @param [Object] # The converted key. If the key was a symbol, it will be converted to a # string. # # @api private def convert_key(key) key.kind_of?(Symbol) ? key.to_s : key end # @param value The value to convert. # # @return [Object] # The converted value. A Hash or an Array of hashes, will be converted to # their Mash equivalents. # # @api private def convert_value(value) if value.class == Hash Mash.from_hash(value) elsif value.is_a?(Array) value.collect { |e| convert_value(e) } else value end end end chef-12.14.60/lib/chef/mixin/000077500000000000000000000000001276456504500154675ustar00rootroot00000000000000chef-12.14.60/lib/chef/mixin/api_version_request_handling.rb000066400000000000000000000053611276456504500237530ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module ApiVersionRequestHandling # Input: # exeception: # Net::HTTPServerException that may or may not contain the x-ops-server-api-version header # supported_client_versions: # An array of Integers that represent the API versions the client supports. # # Output: # nil: # If the execption was not a 406 or the server does not support versioning # Array of length zero: # If there was no intersection between supported client versions and supported server versions # Arrary of Integers: # If there was an intersection of supported versions, the array returns will contain that intersection def server_client_api_version_intersection(exception, supported_client_versions) # return empty array unless 406 Unacceptable with proper header return nil if exception.response.code != "406" || exception.response["x-ops-server-api-version"].nil? # intersection of versions the server and client support, will be of length zero if no intersection server_supported_client_versions = Array.new header = Chef::JSONCompat.from_json(exception.response["x-ops-server-api-version"]) min_server_version = Integer(header["min_version"]) max_server_version = Integer(header["max_version"]) supported_client_versions.each do |version| if version >= min_server_version && version <= max_server_version server_supported_client_versions.push(version) end end server_supported_client_versions end def reregister_only_v0_supported_error_msg(max_version, min_version) <<-EOH The reregister command only supports server API version 0. The server that received the request supports a min version of #{min_version} and a max version of #{max_version}. User keys are now managed via the key rotation commmands. Please refer to the documentation on how to manage your keys via the key rotation commands: https://docs.chef.io/server_security.html#key-rotation EOH end end end end chef-12.14.60/lib/chef/mixin/checksum.rb000066400000000000000000000015571276456504500176260ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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 "digest/sha2" require "chef/digester" class Chef module Mixin module Checksum def checksum(file) Chef::Digester.checksum_for_file(file) end end end end chef-12.14.60/lib/chef/mixin/command.rb000066400000000000000000000165341276456504500174430ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/exceptions" require "tmpdir" require "fcntl" require "etc" class Chef module Mixin #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # NOTE: # The popen4 method upon which all the code here is based has a race # condition where it may fail to read all of the data written to stdout and # stderr after the child process exits. The tests for the code here # occasionally fail because of this race condition, so they have been # tagged "volatile". # # This code is considered deprecated, so it should not need to be modified # frequently, if at all. HOWEVER, if you do modify the code here, you must # explicitly enable volatile tests: # # bundle exec rspec spec/unit/mixin/command_spec.rb -t volatile # # In addition, you should make a note that tests need to be run with # volatile tests enabled on any pull request or bug report you submit with # your patch. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! module Command extend self # NOTE: run_command is deprecated in favor of using Chef::Shellout which now comes from the mixlib-shellout gem. NOTE # if RUBY_PLATFORM =~ /mswin|mingw32|windows/ require "chef/mixin/command/windows" include ::Chef::Mixin::Command::Windows extend ::Chef::Mixin::Command::Windows else require "chef/mixin/command/unix" include ::Chef::Mixin::Command::Unix extend ::Chef::Mixin::Command::Unix end # === Parameters # args: A number of required and optional arguments # command, : A complete command with options to execute or a command and options as an Array # creates: The absolute path to a file that prevents the command from running if it exists # cwd: Working directory to execute command in, defaults to Dir.tmpdir # timeout: How many seconds to wait for the command to execute before timing out # returns: The single exit value command is expected to return, otherwise causes an exception # ignore_failure: Whether to raise an exception on failure, or just return the status # output_on_failure: Return output in raised exception regardless of Log.level # # user: The UID or user name of the user to execute the command as # group: The GID or group name of the group to execute the command as # environment: Pairs of environment variable names and their values to set before execution # # === Returns # Returns the exit status of args[:command] def run_command(args = {}) status, stdout, stderr = run_command_and_return_stdout_stderr(args) status end # works same as above, except that it returns stdout and stderr # requirement => platforms like solaris 9,10 has weird issues where # even in command failure the exit code is zero, so we need to lookup stderr. def run_command_and_return_stdout_stderr(args = {}) command_output = "" args[:ignore_failure] ||= false args[:output_on_failure] ||= false # TODO: This is the wrong place for this responsibility. if args.has_key?(:creates) if File.exists?(args[:creates]) Chef::Log.debug("Skipping #{args[:command]} - creates #{args[:creates]} exists.") return false end end status, stdout, stderr = output_of_command(args[:command], args) command_output << "STDOUT: #{stdout}" command_output << "STDERR: #{stderr}" handle_command_failures(status, command_output, args) return status, stdout, stderr end def output_of_command(command, args) Chef::Log.debug("Executing #{command}") stderr_string, stdout_string, status = "", "", nil exec_processing_block = lambda do |pid, stdin, stdout, stderr| stdout_string, stderr_string = stdout.string.chomp, stderr.string.chomp end args[:cwd] ||= Dir.tmpdir unless ::File.directory?(args[:cwd]) raise Chef::Exceptions::Exec, "#{args[:cwd]} does not exist or is not a directory" end Dir.chdir(args[:cwd]) do if args[:timeout] begin Timeout.timeout(args[:timeout]) do status = popen4(command, args, &exec_processing_block) end rescue Timeout::Error => e Chef::Log.error("#{command} exceeded timeout #{args[:timeout]}") raise(e) end else status = popen4(command, args, &exec_processing_block) end Chef::Log.debug("---- Begin output of #{command} ----") Chef::Log.debug("STDOUT: #{stdout_string}") Chef::Log.debug("STDERR: #{stderr_string}") Chef::Log.debug("---- End output of #{command} ----") Chef::Log.debug("Ran #{command} returned #{status.exitstatus}") end return status, stdout_string, stderr_string end def handle_command_failures(status, command_output, opts = {}) return if opts[:ignore_failure] opts[:returns] ||= 0 return if Array(opts[:returns]).include?(status.exitstatus) # if the log level is not debug, through output of command when we fail output = "" if Chef::Log.level == :debug || opts[:output_on_failure] output << "\n---- Begin output of #{opts[:command]} ----\n" output << command_output.to_s output << "\n---- End output of #{opts[:command]} ----\n" end raise Chef::Exceptions::Exec, "#{opts[:command]} returned #{status.exitstatus}, expected #{opts[:returns]}#{output}" end # Call #run_command but set LC_ALL to the system's current environment so it doesn't get changed to C. # # === Parameters # args: A number of required and optional arguments that will be handed out to #run_command # # === Returns # Returns the result of #run_command def run_command_with_systems_locale(args = {}) args[:environment] ||= {} args[:environment]["LC_ALL"] = ENV["LC_ALL"] run_command args end # def popen4(cmd, args={}, &b) # @@os_handler.popen4(cmd, args, &b) # end # module_function :popen4 # FIXME: yard with @yield def chdir_or_tmpdir(dir) dir ||= Dir.tmpdir unless File.directory?(dir) raise Chef::Exceptions::Exec, "#{dir} does not exist or is not a directory" end Dir.chdir(dir) do yield end end end end end chef-12.14.60/lib/chef/mixin/command/000077500000000000000000000000001276456504500171055ustar00rootroot00000000000000chef-12.14.60/lib/chef/mixin/command/unix.rb000066400000000000000000000161001276456504500204130ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module Command module Unix # This is taken directly from Ara T Howard's Open4 library, and then # modified to suit the needs of Chef. Any bugs here are most likely # my own, and not Ara's. # # The original appears in external/open4.rb in its unmodified form. # # Thanks Ara! def popen4(cmd, args = {}, &b) # Ruby 1.8 suffers from intermittent segfaults believed to be due to GC while IO.select # See CHEF-2916 / CHEF-1305 GC.disable # Waitlast - this is magic. # # Do we wait for the child process to die before we yield # to the block, or after? That is the magic of waitlast. # # By default, we are waiting before we yield the block. args[:waitlast] ||= false args[:user] ||= nil unless args[:user].kind_of?(Integer) args[:user] = Etc.getpwnam(args[:user]).uid if args[:user] end args[:group] ||= nil unless args[:group].kind_of?(Integer) args[:group] = Etc.getgrnam(args[:group]).gid if args[:group] end args[:environment] ||= {} # Default on C locale so parsing commands output can be done # independently of the node's default locale. # "LC_ALL" could be set to nil, in which case we also must ignore it. unless args[:environment].has_key?("LC_ALL") args[:environment]["LC_ALL"] = "C" end pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe verbose = $VERBOSE begin $VERBOSE = nil ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) cid = fork do pw.last.close STDIN.reopen pw.first pw.first.close pr.first.close STDOUT.reopen pr.last pr.last.close pe.first.close STDERR.reopen pe.last pe.last.close STDOUT.sync = STDERR.sync = true if args[:group] Process.egid = args[:group] Process.gid = args[:group] end if args[:user] Process.euid = args[:user] Process.uid = args[:user] end args[:environment].each do |key, value| ENV[key] = value end if args[:umask] umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777) File.umask(umask) end begin if cmd.kind_of?(Array) Kernel.exec(*cmd) else Kernel.exec(cmd) end raise "forty-two" rescue Exception => e Marshal.dump(e, ps.last) ps.last.flush end ps.last.close unless ps.last.closed? exit! end ensure $VERBOSE = verbose end [pw.first, pr.last, pe.last, ps.last].each { |fd| fd.close } begin e = Marshal.load ps.first raise(Exception === e ? e : "unknown failure!") rescue EOFError # If we get an EOF error, then the exec was successful 42 ensure ps.first.close end pw.last.sync = true pi = [pw.last, pr.first, pe.first] if b begin if args[:waitlast] b[cid, *pi] # send EOF so that if the child process is reading from STDIN # it will actually finish up and exit pi[0].close_write Process.waitpid2(cid).last else # This took some doing. # The trick here is to close STDIN # Then set our end of the childs pipes to be O_NONBLOCK # Then wait for the child to die, which means any IO it # wants to do must be done - it's dead. If it isn't, # it's because something totally skanky is happening, # and we don't care. o = StringIO.new e = StringIO.new pi[0].close stdout = pi[1] stderr = pi[2] stdout.sync = true stderr.sync = true stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) stdout_finished = false stderr_finished = false results = nil while !stdout_finished || !stderr_finished begin channels_to_watch = [] channels_to_watch << stdout if !stdout_finished channels_to_watch << stderr if !stderr_finished ready = IO.select(channels_to_watch, nil, nil, 1.0) rescue Errno::EAGAIN ensure results = Process.waitpid2(cid, Process::WNOHANG) if results stdout_finished = true stderr_finished = true end end if ready && ready.first.include?(stdout) line = results ? stdout.gets(nil) : stdout.gets if line o.write(line) else stdout_finished = true end end if ready && ready.first.include?(stderr) line = results ? stderr.gets(nil) : stderr.gets if line e.write(line) else stderr_finished = true end end end results = Process.waitpid2(cid) unless results o.rewind e.rewind b[cid, pi[0], o, e] results.last end ensure pi.each { |fd| fd.close unless fd.closed? } end else [cid, pw.last, pr.first, pe.first] end ensure GC.enable end end end end end chef-12.14.60/lib/chef/mixin/command/windows.rb000066400000000000000000000037761276456504500211410ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "open3" class Chef module Mixin module Command module Windows def popen4(cmd, args = {}, &b) # By default, we are waiting before we yield the block. args[:waitlast] ||= false #XXX :user, :group, :environment support? Open3.popen3(cmd) do |stdin, stdout, stderr, cid| if b if args[:waitlast] b[cid, stdin, stdout, stderr] # send EOF so that if the child process is reading from STDIN # it will actually finish up and exit stdin.close_write else o = StringIO.new e = StringIO.new stdin.close stdout.sync = true stderr.sync = true line = stdout.gets(nil) if line o.write(line) end line = stderr.gets(nil) if line e.write(line) end o.rewind e.rewind b[cid, stdin, o, e] end else [cid, stdin, stdout, stderr] end end $? end end end end end chef-12.14.60/lib/chef/mixin/convert_to_class_name.rb000066400000000000000000000077731276456504500224010ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module ConvertToClassName extend self def convert_to_class_name(str) str = normalize_snake_case_name(str) rname = nil regexp = %r{^(.+?)(_(.+))?$} mn = str.match(regexp) if mn rname = mn[1].capitalize while mn && mn[3] mn = mn[3].match(regexp) rname << mn[1].capitalize if mn end end rname end def convert_to_snake_case(str, namespace = nil) str = str.dup str.sub!(/^#{namespace}(\:\:)?/, "") if namespace str.gsub!(/[A-Z]/) { |s| "_" + s } str.downcase! str.sub!(/^\_/, "") str end def normalize_snake_case_name(str) str = str.dup str.gsub!(/[^A-Za-z0-9_]/, "_") str.gsub!(/^(_+)?/, "") str end def snake_case_basename(str) with_namespace = convert_to_snake_case(str) with_namespace.split("::").last.sub(/^_/, "") end def filename_to_qualified_string(base, filename) file_base = File.basename(filename, ".rb") str = base.to_s + (file_base == "default" ? "" : "_#{file_base}") normalize_snake_case_name(str) end # Copied from rails activesupport. In ruby >= 2.0 const_get will just do this, so this can # be deprecated and removed. # # MIT LICENSE is here: https://github.com/rails/rails/blob/master/activesupport/MIT-LICENSE # Tries to find a constant with the name specified in the argument string. # # 'Module'.constantize # => Module # 'Test::Unit'.constantize # => Test::Unit # # The name is assumed to be the one of a top-level constant, no matter # whether it starts with "::" or not. No lexical context is taken into # account: # # C = 'outside' # module M # C = 'inside' # C # => 'inside' # 'C'.constantize # => 'outside', same as ::C # end # # NameError is raised when the name is not in CamelCase or the constant is # unknown. def constantize(camel_cased_word) names = camel_cased_word.split("::") # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(camel_cased_word) if names.empty? # Remove the first blank element in case of '::ClassName' notation. names.shift if names.size > 1 && names.first.empty? names.inject(Object) do |constant, name| if constant == Object constant.const_get(name) else candidate = constant.const_get(name) next candidate if constant.const_defined?(name, false) next candidate unless Object.const_defined?(name) # Go down the ancestors to check if it is owned directly. The check # stops when we reach Object or the end of ancestors tree. constant = constant.ancestors.inject do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) const end # owner is in Object, so raise constant.const_get(name, false) end end end end end end chef-12.14.60/lib/chef/mixin/create_path.rb000066400000000000000000000050231276456504500202730ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class Chef module Mixin module CreatePath # Creates a given path, including all directories that lead up to it. # Like mkdir_p, but without the leaking. # # === Parameters # file_path:: A string that represents the path to create, # or an Array with the path-parts. # # === Returns # The created file_path. def create_path(file_path) unless file_path.kind_of?(String) || file_path.kind_of?(Array) raise ArgumentError, "file_path must be a string or an array!" end if file_path.kind_of?(String) file_path = File.expand_path(file_path).split(File::SEPARATOR) file_path.shift if file_path[0] == "" # Check if path starts with a separator or drive letter (Windows) unless file_path[0].match("^#{File::SEPARATOR}|^[a-zA-Z]:") file_path[0] = "#{File::SEPARATOR}#{file_path[0]}" end end file_path.each_index do |i| create_path = File.join(file_path[0, i + 1]) create_dir(create_path) unless File.directory?(create_path) end File.expand_path(File.join(file_path)) end private def create_dir(path) begin # When doing multithreaded downloads into the file cache, the following # interleaving raises an error here: # # thread1 thread2 # File.directory?(create_path) <- false # File.directory?(create_path) <- false # Dir.mkdir(create_path) # Dir.mkdir(create_path) <- raises Errno::EEXIST Chef::Log.debug("Creating directory #{path}") Dir.mkdir(path) rescue Errno::EEXIST end end end end end chef-12.14.60/lib/chef/mixin/deep_merge.rb000066400000000000000000000116711276456504500201160ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Steve Midgley (http://www.misuse.org/science) # Copyright:: Copyright 2009-2016, Chef Software Inc. # Copyright:: Copyright 2008-2016, Steve Midgley # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class Chef module Mixin # == Chef::Mixin::DeepMerge # Implements a deep merging algorithm for nested data structures. # ==== Notice: # This code was originally imported from deep_merge by Steve Midgley. # deep_merge is available under the MIT license from # http://trac.misuse.org/science/wiki/DeepMerge module DeepMerge extend self def merge(first, second) first = Mash.new(first) unless first.kind_of?(Mash) second = Mash.new(second) unless second.kind_of?(Mash) DeepMerge.deep_merge(second, first) end class InvalidParameter < StandardError; end # Deep Merge core documentation. # deep_merge! method permits merging of arbitrary child elements. The two top level # elements must be hashes. These hashes can contain unlimited (to stack limit) levels # of child elements. These child elements to not have to be of the same types. # Where child elements are of the same type, deep_merge will attempt to merge them together. # Where child elements are not of the same type, deep_merge will skip or optionally overwrite # the destination element with the contents of the source element at that level. # So if you have two hashes like this: # source = {:x => [1,2,3], :y => 2} # dest = {:x => [4,5,'6'], :y => [7,8,9]} # dest.deep_merge!(source) # Results: {:x => [1,2,3,4,5,'6'], :y => 2} # By default, "deep_merge!" will overwrite any unmergeables and merge everything else. # To avoid this, use "deep_merge" (no bang/exclamation mark) def deep_merge!(source, dest) # if dest doesn't exist, then simply copy source to it if dest.nil? dest = source; return dest end case source when nil dest when Hash if dest.kind_of?(Hash) source.each do |src_key, src_value| if dest[src_key] dest[src_key] = deep_merge!(src_value, dest[src_key]) else # dest[src_key] doesn't exist so we take whatever source has dest[src_key] = src_value end end else # dest isn't a hash, so we overwrite it completely dest = source end when Array if dest.kind_of?(Array) dest = dest | source else dest = source end when String dest = source else # src_hash is not an array or hash, so we'll have to overwrite dest dest = source end dest end # deep_merge! def hash_only_merge(merge_onto, merge_with) hash_only_merge!(safe_dup(merge_onto), safe_dup(merge_with)) end def safe_dup(thing) thing.dup rescue TypeError thing end # Deep merge without Array merge. # `merge_onto` is the object that will "lose" in case of conflict. # `merge_with` is the object whose values will replace `merge_onto`s # values when there is a conflict. def hash_only_merge!(merge_onto, merge_with) # If there are two Hashes, recursively merge. if merge_onto.kind_of?(Hash) && merge_with.kind_of?(Hash) merge_with.each do |key, merge_with_value| value = if merge_onto.has_key?(key) hash_only_merge(merge_onto[key], merge_with_value) else merge_with_value end if merge_onto.respond_to?(:public_method_that_only_deep_merge_should_use) # we can't call ImmutableMash#[]= because its immutable, but we need to mutate it to build it in-place merge_onto.public_method_that_only_deep_merge_should_use(key, value) else merge_onto[key] = value end end merge_onto # If merge_with is nil, don't replace merge_onto elsif merge_with.nil? merge_onto # In all other cases, replace merge_onto with merge_with else merge_with end end def deep_merge(source, dest) deep_merge!(safe_dup(source), safe_dup(dest)) end end end end chef-12.14.60/lib/chef/mixin/deprecation.rb000066400000000000000000000107721276456504500203200ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin def self.deprecated_constants @deprecated_constants ||= {} end # Add a deprecated constant to the Chef::Mixin namespace. # === Arguments # * name: the constant name, as a relative symbol. # * replacement: the constant to return instead. # * message: A message telling the user what to do instead. # === Example: # deprecate_constant(:RecipeDefinitionDSLCore, Chef::DSL::Recipe, <<-EOM) # Chef::Mixin::RecipeDefinitionDSLCore is deprecated, use Chef::DSL::Recipe instead. # EOM def self.deprecate_constant(name, replacement, message) deprecated_constants[name] = { :replacement => replacement, :message => message } end # Const missing hook to look up deprecated constants defined with # deprecate_constant. Emits a warning to the logger and returns the # replacement constant. Will call super, most likely causing an exception # for the missing constant, if +name+ is not found in the # deprecated_constants collection. def self.const_missing(name) if new_const = deprecated_constants[name] Chef::Log.warn(new_const[:message]) Chef::Log.warn("Called from: \n#{caller[0...3].map { |l| "\t#{l}" }.join("\n")}") new_const[:replacement] else super end end module Deprecation class DeprecatedObjectProxyBase KEEPERS = %w{__id__ __send__ instance_eval == equal? initialize object_id} instance_methods.each { |method_name| undef_method(method_name) unless KEEPERS.include?(method_name.to_s) } end class DeprecatedInstanceVariable < DeprecatedObjectProxyBase def initialize(target, ivar_name, level = nil) @target, @ivar_name = target, ivar_name @level ||= :warn end def method_missing(method_name, *args, &block) log_deprecation_msg(caller[0..3]) @target.send(method_name, *args, &block) end def inspect @target.inspect end private def log_deprecation_msg(*called_from) called_from = called_from.flatten log("Accessing #{@ivar_name} by the variable @#{@ivar_name} is deprecated. Support will be removed in a future release.") log("Please update your cookbooks to use #{@ivar_name} in place of @#{@ivar_name}. Accessed from:") called_from.each { |l| log(l) } end def log(msg) # WTF: I don't get the log prefix (i.e., "[timestamp] LEVEL:") if I # send to Chef::Log. No one but me should use method_missing, ever. Chef::Log.logger.send(@level, msg) end end def deprecated_ivar(obj, name, level = nil) DeprecatedInstanceVariable.new(obj, name, level) end def deprecated_attr(name, alternative) deprecated_attr_reader(name, alternative) deprecated_attr_writer(name, alternative) end def deprecated_attr_reader(name, alternative, level = :warn) define_method(name) do Chef.log_deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.") Chef.log_deprecation(alternative) Chef.log_deprecation("Called from:") caller[0..3].each { |c| Chef.log_deprecation(c) } instance_variable_get("@#{name}") end end def deprecated_attr_writer(name, alternative, level = :warn) define_method("#{name}=") do |value| Chef.log_deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.") Chef.log_deprecation(alternative) Chef.log_deprecation("Called from:") caller[0..3].each { |c| Chef.log_deprecation(c) } instance_variable_set("@#{name}", value) end end end end end chef-12.14.60/lib/chef/mixin/descendants_tracker.rb000066400000000000000000000052551276456504500220310ustar00rootroot00000000000000# # Copyright 2005-2016, David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # This is lifted from rails activesupport (note the copyright above): # https://github.com/rails/rails/blob/9f84e60ac9d7bf07d6ae1bc94f3941f5b8f1a228/activesupport/lib/active_support/descendants_tracker.rb class Chef module Mixin module DescendantsTracker @@direct_descendants = {} class << self def direct_descendants(klass) @@direct_descendants[klass] || [] end def descendants(klass) arr = [] accumulate_descendants(klass, arr) arr end def find_descendants_by_name(klass, name) descendants(klass).first { |c| c.name == name } end # This is the only method that is not thread safe, but is only ever called # during the eager loading phase. def store_inherited(klass, descendant) (@@direct_descendants[klass] ||= []) << descendant end private def accumulate_descendants(klass, acc) if direct_descendants = @@direct_descendants[klass] acc.concat(direct_descendants) direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) } end end end def inherited(base) DescendantsTracker.store_inherited(self, base) super end def direct_descendants DescendantsTracker.direct_descendants(self) end def find_descendants_by_name(name) DescendantsTracker.find_descendants_by_name(self, name) end def descendants DescendantsTracker.descendants(self) end end end end chef-12.14.60/lib/chef/mixin/enforce_ownership_and_permissions.rb000066400000000000000000000023361276456504500250140ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/file_access_control" class Chef module Mixin module EnforceOwnershipAndPermissions def access_controls @access_controls ||= Chef::FileAccessControl.new(current_resource, new_resource, self) end # will set the proper user, group and # permissions using a platform specific # version of Chef::FileAccessControl def enforce_ownership_and_permissions access_controls.set_all new_resource.updated_by_last_action(true) if access_controls.modified? end end end end chef-12.14.60/lib/chef/mixin/file_class.rb000066400000000000000000000021511276456504500201170ustar00rootroot00000000000000# # Author:: Mark Mzyk # Author:: Seth Chisamore # Author:: Bryan McLellan # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module FileClass def file_class @host_os_file ||= if Chef::Platform.windows? require "chef/win32/file" Chef::ReservedNames::Win32::File else ::File end end end end end chef-12.14.60/lib/chef/mixin/from_file.rb000066400000000000000000000032251276456504500177600ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module FromFile # Loads a given ruby file, and runs instance_eval against it in the context of the current # object. # # Raises an IOError if the file cannot be found, or is not readable. def from_file(filename) if File.exists?(filename) && File.readable?(filename) self.instance_eval(IO.read(filename), filename, 1) else raise IOError, "Cannot open or read #{filename}!" end end # Loads a given ruby file, and runs class_eval against it in the context of the current # object. # # Raises an IOError if the file cannot be found, or is not readable. def class_from_file(filename) if File.exists?(filename) && File.readable?(filename) self.class_eval(IO.read(filename), filename, 1) else raise IOError, "Cannot open or read #{filename}!" end end end end end chef-12.14.60/lib/chef/mixin/get_source_from_package.rb000066400000000000000000000036511276456504500226560ustar00rootroot00000000000000# Author:: Lamont Granquist () # 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. # # # mixin to make this syntax work without specifying a source: # # gem_pacakge "/tmp/foo-x.y.z.gem" # rpm_package "/tmp/foo-x.y-z.rpm" # dpkg_package "/tmp/foo-x.y.z.deb" # class Chef module Mixin module GetSourceFromPackage # FIXME: this is some bad code that I wrote a long time ago. # - it does too much in the initializer # - it mutates the new_resource # - it does not support multipackage arrays # this code is deprecated, check out the :use_package_names_for_source # subclass directive instead def initialize(new_resource, run_context) super return if new_resource.package_name.is_a?(Array) # if we're passed something that looks like a filesystem path, with no source, use it # - require at least one '/' in the path to avoid gem_package "foo" breaking if a file named 'foo' exists in the cwd if new_resource.source.nil? && new_resource.package_name.match(/#{::File::SEPARATOR}/) && ::File.exists?(new_resource.package_name) Chef::Log.debug("No package source specified, but #{new_resource.package_name} exists on the filesystem, copying to package source") new_resource.source(@new_resource.package_name) end end end end end chef-12.14.60/lib/chef/mixin/homebrew_user.rb000066400000000000000000000045471276456504500206740ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Author:: Graeme Mathieson () # # Copyright 2011-2016, Chef Software Inc. # Copyright 2014-2016, Chef Software, Inc # # 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. # # Ported from the homebrew cookbook's Homebrew::Mixin owner helpers # # This lives here in Chef::Mixin because Chef's namespacing makes it # awkward to use modules elsewhere (e.g., chef/provider/package/homebrew/owner) require "chef/mixin/shell_out" require "etc" class Chef module Mixin module HomebrewUser include Chef::Mixin::ShellOut ## # This tries to find the user to execute brew as. If a user is provided, that overrides the brew # executable user. It is an error condition if the brew executable owner is root or we cannot find # the brew executable. def find_homebrew_uid(provided_user = nil) # They could provide us a user name or a UID if provided_user return provided_user if provided_user.is_a? Integer return Etc.getpwnam(provided_user).uid end @homebrew_owner ||= calculate_owner @homebrew_owner end private def calculate_owner default_brew_path = "/usr/local/bin/brew" if ::File.exist?(default_brew_path) # By default, this follows symlinks which is what we want owner = ::File.stat(default_brew_path).uid elsif (brew_path = shell_out("which brew").stdout.strip) && !brew_path.empty? owner = ::File.stat(brew_path).uid else raise Chef::Exceptions::CannotDetermineHomebrewOwner, 'Could not find the "brew" executable in /usr/local/bin or anywhere on the path.' end Chef::Log.debug "Found Homebrew owner #{Etc.getpwuid(owner).name}; executing `brew` commands as them" owner end end end end chef-12.14.60/lib/chef/mixin/language.rb000066400000000000000000000032721276456504500176030ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/dsl/platform_introspection" require "chef/dsl/data_query" require "chef/mixin/deprecation" class Chef module Mixin # == [DEPRECATED] Chef::Mixin::DeprecatedLanguageModule # This module is a temporary replacement for the previous # Chef::Mixin::Language. That module's functionality was split into two # modules, Chef::DSL::PlatformIntrospection, and Chef::DSL::DataQuery. # # This module includes both PlatformIntrospection and DataQuery to provide # the same interfaces and behavior as the prior Mixin::Language. # # This module is loaded via const_missing hook when Chef::Mixin::Language # is accessed. See chef/mixin/deprecation for details. module DeprecatedLanguageModule include Chef::DSL::PlatformIntrospection include Chef::DSL::DataQuery end deprecate_constant(:Language, DeprecatedLanguageModule, <<-EOM) Chef::Mixin::Language is deprecated. Use either (or both) Chef::DSL::PlatformIntrospection or Chef::DSL::DataQuery instead. EOM end end chef-12.14.60/lib/chef/mixin/language_include_attribute.rb000066400000000000000000000020711276456504500233650ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/dsl/include_attribute" require "chef/mixin/deprecation" class Chef module Mixin # DEPRECATED: This is just here for compatibility, use # Chef::DSL::IncludeAttribute instead. deprecate_constant(:LanguageIncludeAttribute, Chef::DSL::IncludeAttribute, <<-EOM) Chef::Mixin::LanguageIncludeAttribute is deprecated. Use Chef::DSL::IncludeAttribute instead. EOM end end chef-12.14.60/lib/chef/mixin/language_include_recipe.rb000066400000000000000000000017031276456504500226320ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/dsl/include_recipe" require "chef/mixin/deprecation" class Chef module Mixin deprecate_constant(:LanguageIncludeRecipe, Chef::DSL::IncludeRecipe, <<-EOM) Chef::Mixin::LanguageIncludeRecipe is deprecated, use Chef::DSL::IncludeRecipe instead. EOM end end chef-12.14.60/lib/chef/mixin/lazy_module_include.rb000066400000000000000000000040161276456504500220440ustar00rootroot00000000000000# # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin # If you have: # # module A # extend LazyModuleInclude # end # # module B # include A # end # # module C # include B # end # # module Monkeypatches # def monkey # puts "monkey!" # end # end # # A.send(:include, Monkeypatches) # # Then B and C and any classes that they're included in will also get the #monkey method patched into them. # module LazyModuleInclude # Most of the magick is in this hook which creates a closure over the parent class and then builds an # "infector" module which infects all descendants and which is responsible for updating the list of # descendants in the parent class. def included(klass) super parent_klass = self infector = Module.new do define_method(:included) do |subklass| super(subklass) subklass.extend(infector) parent_klass.descendants.push(subklass) end end klass.extend(infector) parent_klass.descendants.push(klass) end def descendants @descendants ||= [] end def include(*classes) super classes.each do |klass| descendants.each do |descendant| descendant.send(:include, klass) end end end end end end chef-12.14.60/lib/chef/mixin/notifying_block.rb000066400000000000000000000030541276456504500211760ustar00rootroot00000000000000#-- # Author:: Lamont Granquist # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class Chef module Mixin module NotifyingBlock def notifying_block(&block) begin subcontext = subcontext_block(&block) Chef::Runner.new(subcontext).converge ensure # recipes don't have a new_resource if respond_to?(:new_resource) if subcontext && subcontext.resource_collection.any?(&:updated?) new_resource.updated_by_last_action(true) end end end end def subcontext_block(parent_context = nil, &block) parent_context ||= @run_context sub_run_context = parent_context.create_child begin outer_run_context = @run_context @run_context = sub_run_context instance_eval(&block) ensure @run_context = outer_run_context end sub_run_context end end end end chef-12.14.60/lib/chef/mixin/params_validate.rb000066400000000000000000000425621276456504500211610ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/constants" require "chef/property" require "chef/delayed_evaluator" class Chef module Mixin module ParamsValidate # Takes a hash of options, along with a map to validate them. Returns the original # options hash, plus any changes that might have been made (through things like setting # default values in the validation map) # # For example: # # validate({ :one => "neat" }, { :one => { :kind_of => String }}) # # Would raise an exception if the value of :one above is not a kind_of? string. Valid # map options are: # # @param opts [Hash] Validation opts. # @option opts [Object,Array] :is An object, or list of # objects, that must match the value using Ruby's `===` operator # (`opts[:is].any? { |v| v === value }`). (See #_pv_is.) # @option opts [Object,Array] :equal_to An object, or list # of objects, that must be equal to the value using Ruby's `==` # operator (`opts[:is].any? { |v| v == value }`) (See #_pv_equal_to.) # @option opts [Regexp,Array] :regex An object, or # list of objects, that must match the value with `regex.match(value)`. # (See #_pv_regex) # @option opts [Class,Array] :kind_of A class, or # list of classes, that the value must be an instance of. (See # #_pv_kind_of.) # @option opts [Hash] :callbacks A hash of # messages -> procs, all of which match the value. The proc must # return a truthy or falsey value (true means it matches). (See # #_pv_callbacks.) # @option opts [Symbol,Array] :respond_to A method # name, or list of method names, the value must respond to. (See # #_pv_respond_to.) # @option opts [Symbol,Array] :cannot_be A property, # or a list of properties, that the value cannot have (such as `:nil` or # `:empty`). The method with a questionmark at the end is called on the # value (e.g. `value.empty?`). If the value does not have this method, # it is considered valid (i.e. if you don't respond to `empty?` we # assume you are not empty). (See #_pv_cannot_be.) # @option opts [Proc] :coerce A proc which will be called to # transform the user input to canonical form. The value is passed in, # and the transformed value returned as output. Lazy values will *not* # be passed to this method until after they are evaluated. Called in the # context of the resource (meaning you can access other properties). # (See #_pv_coerce.) (See #_pv_coerce.) # @option opts [Boolean] :required `true` if this property # must be present and not `nil`; `false` otherwise. This is checked # after the resource is fully initialized. (See #_pv_required.) # @option opts [Boolean] :name_property `true` if this # property defaults to the same value as `name`. Equivalent to # `default: lazy { name }`, except that #property_is_set? will # return `true` if the property is set *or* if `name` is set. (See # #_pv_name_property.) # @option opts [Boolean] :name_attribute Same as `name_property`. # @option opts [Object] :default The value this property # will return if the user does not set one. If this is `lazy`, it will # be run in the context of the instance (and able to access other # properties). (See #_pv_default.) # def validate(opts, map) map = map.validation_options if map.is_a?(Property) #-- # validate works by taking the keys in the validation map, assuming it's a hash, and # looking for _pv_:symbol as methods. Assuming it find them, it calls the right # one. #++ raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash) raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash) map.each do |key, validation| unless key.kind_of?(Symbol) || key.kind_of?(String) raise ArgumentError, "Validation map keys must be symbols or strings!" end case validation when true _pv_required(opts, key) when false true when Hash validation.each do |check, carg| check_method = "_pv_#{check}" if self.respond_to?(check_method, true) self.send(check_method, opts, key, carg) else raise ArgumentError, "Validation map has unknown check: #{check}" end end end end opts end def lazy(&block) DelayedEvaluator.new(&block) end def set_or_return(symbol, value, validation) property = SetOrReturnProperty.new(name: symbol, **validation) property.call(self, value) end private def explicitly_allows_nil?(key, validation) validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false) end # Return the value of a parameter, or nil if it doesn't exist. def _pv_opts_lookup(opts, key) if opts.has_key?(key.to_s) opts[key.to_s] elsif opts.has_key?(key.to_sym) opts[key.to_sym] else nil end end # Raise an exception if the parameter is not found. def _pv_required(opts, key, is_required = true, explicitly_allows_nil = false) if is_required return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?) return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?) raise Exceptions::ValidationFailed, "Required argument #{key.inspect} is missing!" end true end # # List of things values must be equal to. # # Uses Ruby's `==` to evaluate (equal_to == value). At least one must # match for the value to be valid. # # `nil` passes this validation automatically. # # @return [Array,nil] List of things values must be equal to, or nil if # equal_to is unspecified. # def _pv_equal_to(opts, key, to_be) value = _pv_opts_lookup(opts, key) unless value.nil? to_be = Array(to_be) to_be.each do |tb| return true if value == tb end raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}." end end # # List of things values must be instances of. # # Uses value.kind_of?(kind_of) to evaluate. At least one must match for # the value to be valid. # # `nil` automatically passes this validation. # def _pv_kind_of(opts, key, to_be) value = _pv_opts_lookup(opts, key) unless value.nil? to_be = Array(to_be) to_be.each do |tb| return true if value.kind_of?(tb) end raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}." end end # # List of method names values must respond to. # # Uses value.respond_to?(respond_to) to evaluate. At least one must match # for the value to be valid. # def _pv_respond_to(opts, key, method_name_list) value = _pv_opts_lookup(opts, key) unless value.nil? Array(method_name_list).each do |method_name| unless value.respond_to?(method_name) raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!" end end end end # # List of things that must not be true about the value. # # Calls `value.?` All responses must be false for the value to be # valid. # Values which do not respond to ? are considered valid (because if # a value doesn't respond to `:readable?`, then it probably isn't # readable.) # # @example # ```ruby # property :x, cannot_be: [ :nil, :empty ] # x [ 1, 2 ] #=> valid # x 1 #=> valid # x [] #=> invalid # x nil #=> invalid # ``` # def _pv_cannot_be(opts, key, predicate_method_base_name) value = _pv_opts_lookup(opts, key) if !value.nil? Array(predicate_method_base_name).each do |method_name| predicate_method = :"#{method_name}?" if value.respond_to?(predicate_method) if value.send(predicate_method) raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}" end end end end end # # The default value for a property. # # When the property is not assigned, this will be used. # # If this is a lazy value, it will either be passed the resource as a value, # or if the lazy proc does not take parameters, it will be run in the # context of the instance with instance_eval. # # @example # ```ruby # property :x, default: 10 # ``` # # @example # ```ruby # property :x # property :y, default: lazy { x+2 } # ``` # # @example # ```ruby # property :x # property :y, default: lazy { |r| r.x+2 } # ``` # def _pv_default(opts, key, default_value) value = _pv_opts_lookup(opts, key) if value.nil? default_value = default_value.freeze if !default_value.is_a?(DelayedEvaluator) opts[key] = default_value end end # # List of regexes values that must match. # # Uses regex.match() to evaluate. At least one must match for the value to # be valid. # # `nil` passes regex validation automatically. # # @example # ```ruby # property :x, regex: [ /abc/, /xyz/ ] # ``` # def _pv_regex(opts, key, regex) value = _pv_opts_lookup(opts, key) if !value.nil? Array(regex).flatten.each do |r| return true if r.match(value.to_s) end raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}" end end # # List of procs we pass the value to. # # All procs must return true for the value to be valid. If any procs do # not return true, the key will be used for the message: `"Property x's # value :y "`. # # @example # ```ruby # property :x, callbacks: { "is bigger than 10" => proc { |v| v <= 10 }, "is not awesome" => proc { |v| !v.awesome }} # ``` # def _pv_callbacks(opts, key, callbacks) raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash) value = _pv_opts_lookup(opts, key) if !value.nil? callbacks.each do |message, zeproc| unless zeproc.call(value) raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!" end end end end # # Allows a parameter to default to the value of the resource name. # # @example # ```ruby # property :x, name_property: true # ``` # def _pv_name_property(opts, key, is_name_property = true) if is_name_property if opts[key].nil? raise CannotValidateStaticallyError, "name_property cannot be evaluated without a resource." if self == Chef::Mixin::ParamsValidate opts[key] = self.instance_variable_get(:"@name") end end end alias :_pv_name_attribute :_pv_name_property # # List of valid things values can be. # # Uses Ruby's `===` to evaluate (is === value). At least one must match # for the value to be valid. # # If a proc is passed, it is instance_eval'd in the resource, passed the # value, and must return a truthy or falsey value. # # @example Class # ```ruby # property :x, String # x 'valid' #=> valid # x 1 #=> invalid # x nil #=> invalid # # @example Value # ```ruby # property :x, [ :a, :b, :c, nil ] # x :a #=> valid # x nil #=> valid # ``` # # @example Regex # ```ruby # property :x, /bar/ # x 'foobar' #=> valid # x 'foo' #=> invalid # x nil #=> invalid # ``` # # @example Proc # ```ruby # property :x, proc { |x| x > y } # property :y, default: 2 # x 3 #=> valid # x 1 #=> invalid # ``` # # @example Property # ```ruby # type = Property.new(is: String) # property :x, type # x 'foo' #=> valid # x 1 #=> invalid # x nil #=> invalid # ``` # # @example RSpec Matcher # ```ruby # include RSpec::Matchers # property :x, a_string_matching /bar/ # x 'foobar' #=> valid # x 'foo' #=> invalid # x nil #=> invalid # ``` # def _pv_is(opts, key, to_be, raise_error: true) return true if !opts.has_key?(key.to_s) && !opts.has_key?(key.to_sym) value = _pv_opts_lookup(opts, key) to_be = [ to_be ].flatten(1) errors = [] passed = to_be.any? do |tb| case tb when Proc raise CannotValidateStaticallyError, "is: proc { } must be evaluated once for each resource" if self == Chef::Mixin::ParamsValidate instance_exec(value, &tb) when Property begin validate(opts, { key => tb.validation_options }) true rescue Exceptions::ValidationFailed # re-raise immediately if there is only one "is" so we get a better stack raise if to_be.size == 1 errors << $! false end else tb === value end end if passed true else message = "Property #{key} must be one of: #{to_be.map { |v| v.inspect }.join(", ")}! You passed #{value.inspect}." unless errors.empty? message << " Errors:\n#{errors.map { |m| "- #{m}" }.join("\n")}" end raise Exceptions::ValidationFailed, message end end # # Method to mess with a value before it is validated and stored. # # Allows you to transform values into a canonical form that is easy to # work with. # # This is passed the value to transform, and is run in the context of the # instance (so it has access to other resource properties). It must return # the value that will be stored in the instance. # # @example # ```ruby # property :x, Integer, coerce: { |v| v.to_i } # ``` # def _pv_coerce(opts, key, coercer) if opts.has_key?(key.to_s) raise CannotValidateStaticallyError, "coerce must be evaluated for each resource." if self == Chef::Mixin::ParamsValidate opts[key.to_s] = instance_exec(opts[key], &coercer) elsif opts.has_key?(key.to_sym) raise CannotValidateStaticallyError, "coerce must be evaluated for each resource." if self == Chef::Mixin::ParamsValidate opts[key.to_sym] = instance_exec(opts[key], &coercer) end end # We allow Chef::Mixin::ParamsValidate.validate(), but we will raise an # error if you try to do anything requiring there to be an actual resource. # This way, you can statically validate things if you have constant validation # (which is the norm). extend self # Used by #set_or_return to avoid emitting a deprecation warning for # "value nil" and to keep default stickiness working exactly the same # @api private class SetOrReturnProperty < Chef::Property def get(resource, nil_set: false) value = super # All values are sticky, frozen or not if !is_set?(resource) set_value(resource, value) end value end def call(resource, value = NOT_PASSED) # setting to nil does a get if value.nil? && !explicitly_accepts_nil?(resource) get(resource, nil_set: true) else super end end end end end end chef-12.14.60/lib/chef/mixin/path_sanity.rb000066400000000000000000000041451276456504500203430ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module PathSanity def enforce_path_sanity(env = ENV) if Chef::Config[:enforce_path_sanity] env["PATH"] = "" if env["PATH"].nil? path_separator = Chef::Platform.windows? ? ";" : ":" existing_paths = env["PATH"].split(path_separator) # ensure the Ruby and Gem bindirs are included # mainly for 'full-stack' Chef installs paths_to_add = [] paths_to_add << ruby_bindir unless sane_paths.include?(ruby_bindir) paths_to_add << gem_bindir unless sane_paths.include?(gem_bindir) paths_to_add << sane_paths if sane_paths paths_to_add.flatten!.compact! paths_to_add.each do |sane_path| unless existing_paths.include?(sane_path) env_path = env["PATH"].dup env_path << path_separator unless env["PATH"].empty? env_path << sane_path env["PATH"] = env_path.encode("utf-8", invalid: :replace, undef: :replace) end end end end private def sane_paths @sane_paths ||= begin if Chef::Platform.windows? %w{} else %w{/usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin} end end end def ruby_bindir RbConfig::CONFIG["bindir"] end def gem_bindir Gem.bindir end end end end chef-12.14.60/lib/chef/mixin/powershell_out.rb000066400000000000000000000067231276456504500210770ustar00rootroot00000000000000#-- # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/mixin/shell_out" require "chef/mixin/windows_architecture_helper" class Chef module Mixin module PowershellOut include Chef::Mixin::ShellOut include Chef::Mixin::WindowsArchitectureHelper # Run a command under powershell with the same API as shell_out. The # options hash is extended to take an "architecture" flag which # can be set to :i386 or :x86_64 to force the windows architecture. # # @param script [String] script to run # @param options [Hash] options hash # @return [Mixlib::Shellout] mixlib-shellout object def powershell_out(*command_args) script = command_args.first options = command_args.last.is_a?(Hash) ? command_args.last : nil run_command_with_os_architecture(script, options) end # Run a command under powershell with the same API as shell_out! # (raises exceptions on errors) # # @param script [String] script to run # @param options [Hash] options hash # @return [Mixlib::Shellout] mixlib-shellout object def powershell_out!(*command_args) cmd = powershell_out(*command_args) cmd.error! cmd end private # Helper function to run shell_out and wrap it with the correct # flags to possibly disable WOW64 redirection (which we often need # because chef-client runs as a 32-bit app on 64-bit windows). # # @param script [String] script to run # @param options [Hash] options hash # @return [Mixlib::Shellout] mixlib-shellout object def run_command_with_os_architecture(script, options) options ||= {} options = options.dup arch = options.delete(:architecture) with_os_architecture(nil, architecture: arch) do shell_out( build_powershell_command(script), options ) end end # Helper to build a powershell command around the script to run. # # @param script [String] script to run # @retrurn [String] powershell command to execute def build_powershell_command(script) flags = [ # Hides the copyright banner at startup. "-NoLogo", # Does not present an interactive prompt to the user. "-NonInteractive", # Does not load the Windows PowerShell profile. "-NoProfile", # always set the ExecutionPolicy flag # see http://technet.microsoft.com/en-us/library/ee176961.aspx "-ExecutionPolicy Unrestricted", # Powershell will hang if STDIN is redirected # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected "-InputFormat None", ] "powershell.exe #{flags.join(' ')} -Command \"#{script}\"" end end end end chef-12.14.60/lib/chef/mixin/powershell_type_coercions.rb000066400000000000000000000047631276456504500233170ustar00rootroot00000000000000# # Author:: Adam Edwards () # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module PowershellTypeCoercions def type_coercions @type_coercions ||= { Fixnum => { :type => lambda { |x| x.to_s } }, Float => { :type => lambda { |x| x.to_s } }, FalseClass => { :type => lambda { |x| "$false" } }, TrueClass => { :type => lambda { |x| "$true" } }, Hash => { :type => Proc.new { |x| translate_hash(x) } }, Array => { :type => Proc.new { |x| translate_array(x) } }, Chef::Node::ImmutableMash => { :type => Proc.new { |x| translate_hash(x) } }, Chef::Node::ImmutableArray => { :type => Proc.new { |x| translate_array(x) } }, } end def translate_type(value) translation = type_coercions[value.class] if translation translation[:type].call(value) elsif value.respond_to? :to_psobject "(#{value.to_psobject})" else safe_string(value.to_s) end end private def translate_hash(x) translated = x.inject([]) do |memo, (k, v)| memo << "#{k}=#{translate_type(v)}" end "@{#{translated.join(';')}}" end def translate_array(x) translated = x.map do |v| translate_type(v) end "@(#{translated.join(',')})" end def unsafe?(s) ["'", "#", "`", '"'].any? do |x| s.include? x end end def safe_string(s) # do we need to worry about binary data? if unsafe?(s) encoded_str = Base64.strict_encode64(s.encode("UTF-8")) "([System.Text.Encoding]::UTF8.GetString("\ "[System.Convert]::FromBase64String('#{encoded_str}')"\ "))" else "'#{s}'" end end end end end chef-12.14.60/lib/chef/mixin/properties.rb000066400000000000000000000303401276456504500202100ustar00rootroot00000000000000require "chef/delayed_evaluator" require "chef/mixin/params_validate" require "chef/property" class Chef module Mixin module Properties module ClassMethods # # The list of properties defined on this resource. # # Everything defined with `property` is in this list. # # @param include_superclass [Boolean] `true` to include properties defined # on superclasses; `false` or `nil` to return the list of properties # directly on this class. # # @return [Hash] The list of property names and types. # def properties(include_superclass = true) if include_superclass result = {} ancestors.reverse_each { |c| result.merge!(c.properties(false)) if c.respond_to?(:properties) } result else @properties ||= {} end end # # Create a property on this resource class. # # If a superclass has this property, or if this property has already been # defined by this resource, this will *override* the previous value. # # @param name [Symbol] The name of the property. # @param type [Object,Array] The type(s) of this property. # If present, this is prepended to the `is` validation option. # @param options [Hash] Validation options. # @option options [Object,Array] :is An object, or list of # objects, that must match the value using Ruby's `===` operator # (`options[:is].any? { |v| v === value }`). # @option options [Object,Array] :equal_to An object, or list # of objects, that must be equal to the value using Ruby's `==` # operator (`options[:is].any? { |v| v == value }`) # @option options [Regexp,Array] :regex An object, or # list of objects, that must match the value with `regex.match(value)`. # @option options [Class,Array] :kind_of A class, or # list of classes, that the value must be an instance of. # @option options [Hash] :callbacks A hash of # messages -> procs, all of which match the value. The proc must # return a truthy or falsey value (true means it matches). # @option options [Symbol,Array] :respond_to A method # name, or list of method names, the value must respond to. # @option options [Symbol,Array] :cannot_be A property, # or a list of properties, that the value cannot have (such as `:nil` or # `:empty`). The method with a questionmark at the end is called on the # value (e.g. `value.empty?`). If the value does not have this method, # it is considered valid (i.e. if you don't respond to `empty?` we # assume you are not empty). # @option options [Proc] :coerce A proc which will be called to # transform the user input to canonical form. The value is passed in, # and the transformed value returned as output. Lazy values will *not* # be passed to this method until after they are evaluated. Called in the # context of the resource (meaning you can access other properties). # @option options [Boolean] :required `true` if this property # must be present; `false` otherwise. This is checked after the resource # is fully initialized. # @option options [Boolean] :name_property `true` if this # property defaults to the same value as `name`. Equivalent to # `default: lazy { name }`, except that #property_is_set? will # return `true` if the property is set *or* if `name` is set. # @option options [Boolean] :name_attribute Same as `name_property`. # @option options [Object] :default The value this property # will return if the user does not set one. If this is `lazy`, it will # be run in the context of the instance (and able to access other # properties). # @option options [Boolean] :desired_state `true` if this property is # part of desired state. Defaults to `true`. # @option options [Boolean] :identity `true` if this property # is part of object identity. Defaults to `false`. # @option options [Boolean] :sensitive `true` if this property could # contain sensitive information and whose value should be redacted # in any resource reporting / auditing output. Defaults to `false`. # # @example Bare property # property :x # # @example With just a type # property :x, String # # @example With just options # property :x, default: 'hi' # # @example With type and options # property :x, String, default: 'hi' # def property(name, type = NOT_PASSED, **options) name = name.to_sym options = options.inject({}) { |memo, (key, value)| memo[key.to_sym] = value; memo } options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name) options[:name] = name options[:declared_in] = self if type == NOT_PASSED # If a type is not passed, the property derives from the # superclass property (if any) if properties.has_key?(name) property = properties[name].derive(**options) else property = property_type(**options) end # If a Property is specified, derive a new one from that. elsif type.is_a?(Property) || (type.is_a?(Class) && type <= Property) property = type.derive(**options) # If a primitive type was passed, combine it with "is" else if options[:is] options[:is] = ([ type ] + [ options[:is] ]).flatten(1) else options[:is] = type end property = property_type(**options) end local_properties = properties(false) local_properties[name] = property property.emit_dsl end # # Create a reusable property type that can be used in multiple properties # in different resources. # # @param options [Hash] Validation options. see #property for # the list of options. # # @example # property_type(default: 'hi') # def property_type(**options) Property.derive(**options) end # # Create a lazy value for assignment to a default value. # # @param block The block to run when the value is retrieved. # # @return [Chef::DelayedEvaluator] The lazy value # def lazy(&block) DelayedEvaluator.new(&block) end # # Get or set the list of desired state properties for this resource. # # State properties are properties that describe the desired state # of the system, such as file permissions or ownership. # In general, state properties are properties that could be populated by # examining the state of the system (e.g., File.stat can tell you the # permissions on an existing file). Contrarily, properties that are not # "state properties" usually modify the way Chef itself behaves, for example # by providing additional options for a package manager to use when # installing a package. # # This list is used by the Chef client auditing system to extract # information from resources to describe changes made to the system. # # This method is unnecessary when declaring properties with `property`; # properties are added to state_properties by default, and can be turned off # with `desired_state: false`. # # ```ruby # property :x # part of desired state # property :y, desired_state: false # not part of desired state # ``` # # @param names [Array] A list of property names to set as desired # state. # # @return [Array] All properties in desired state. # def state_properties(*names) if !names.empty? names = names.map { |name| name.to_sym }.uniq local_properties = properties(false) # Add new properties to the list. names.each do |name| property = properties[name] if !property self.property name, instance_variable_name: false, desired_state: true elsif !property.desired_state? self.property name, desired_state: true end end # If state_attrs *excludes* something which is currently desired state, # mark it as desired_state: false. local_properties.each do |name, property| if property.desired_state? && !names.include?(name) self.property name, desired_state: false end end end properties.values.select { |property| property.desired_state? } end # # Set the identity of this resource to a particular set of properties. # # This drives #identity, which returns data that uniquely refers to a given # resource on the given node (in such a way that it can be correlated # across Chef runs). # # This method is unnecessary when declaring properties with `property`; # properties can be added to identity during declaration with # `identity: true`. # # ```ruby # property :x, identity: true # part of identity # property :y # not part of identity # ``` # # If no properties are marked as identity, "name" is considered the identity. # # @param names [Array] A list of property names to set as the identity. # # @return [Array] All identity properties. # def identity_properties(*names) if !names.empty? names = names.map { |name| name.to_sym } # Add or change properties that are not part of the identity. names.each do |name| property = properties[name] if !property self.property name, instance_variable_name: false, identity: true elsif !property.identity? self.property name, identity: true end end # If identity_properties *excludes* something which is currently part of # the identity, mark it as identity: false. properties.each do |name, property| if property.identity? && !names.include?(name) self.property name, identity: false end end end result = properties.values.select { |property| property.identity? } result = [ properties[:name] ] if result.empty? result end def included(other) other.extend ClassMethods end end def self.included(other) other.extend ClassMethods end include Chef::Mixin::ParamsValidate # # Whether this property has been set (or whether it has a default that has # been retrieved). # # @param name [Symbol] The name of the property. # @return [Boolean] `true` if the property has been set. # def property_is_set?(name) property = self.class.properties[name.to_sym] raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property property.is_set?(self) end # # Clear this property as if it had never been set. It will thereafter return # the default. # been retrieved). # # @param name [Symbol] The name of the property. # def reset_property(name) property = self.class.properties[name.to_sym] raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property property.reset(self) end end end end chef-12.14.60/lib/chef/mixin/provides.rb000066400000000000000000000013521276456504500176500ustar00rootroot00000000000000 require "chef/mixin/descendants_tracker" class Chef module Mixin module Provides # TODO no longer needed, remove or deprecate? include Chef::Mixin::DescendantsTracker def provides(short_name, opts = {}) raise NotImplementedError, :provides end # Check whether this resource provides the resource_name DSL for the given # node. TODO remove this when we stop checking unregistered things. # FIXME: yard with @yield def provides?(node, resource) raise NotImplementedError, :provides? end # Get the list of recipe DSL this resource is responsible for on the given # node. def provided_as(node) node_map.list(node) end end end end chef-12.14.60/lib/chef/mixin/proxified_socket.rb000066400000000000000000000025161276456504500213610ustar00rootroot00000000000000# Author:: Tyler Ball () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "proxifier" require "chef-config/mixin/fuzzy_hostname_matcher" class Chef module Mixin module ProxifiedSocket include ChefConfig::Mixin::FuzzyHostnameMatcher # This looks at the environment variables and leverages Proxifier to # make the TCPSocket respect ENV['https_proxy'] or ENV['http_proxy'] if # they are present def proxified_socket(host, port) proxy = ENV["https_proxy"] || ENV["http_proxy"] || false if proxy && !fuzzy_hostname_match_any?(host, ENV["no_proxy"]) Proxifier.Proxy(proxy).open(host, port) else TCPSocket.new(host, port) end end end end end chef-12.14.60/lib/chef/mixin/recipe_definition_dsl_core.rb000066400000000000000000000022311276456504500233430ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Christopher Walters () # 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. # ### # NOTE: This file and constant are here only for backwards compatibility. # New code should use Chef::DSL::Recipe instead. # # This constant (module name) will eventually be deprecated and then removed. ### require "chef/mixin/deprecation" class Chef module Mixin deprecate_constant(:RecipeDefinitionDSLCore, Chef::DSL::Recipe, <<-EOM) Chef::Mixin::RecipeDefinitionDSLCore is deprecated. Use Chef::DSL::Recipe instead. EOM end end chef-12.14.60/lib/chef/mixin/securable.rb000066400000000000000000000163151276456504500177670ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module Securable def owner(arg = nil) set_or_return( :owner, arg, :regex => Chef::Config[:user_valid_regex] ) end alias :user :owner def group(arg = nil) set_or_return( :group, arg, :regex => Chef::Config[:group_valid_regex] ) end def mode(arg = nil) set_or_return( :mode, arg, :callbacks => { "not in valid numeric range" => lambda do |m| if m.kind_of?(String) m =~ /^0/ || m = "0#{m}" end # Windows does not support the sticky or setuid bits if Chef::Platform.windows? Integer(m) <= 0777 && Integer(m) >= 0 else Integer(m) <= 07777 && Integer(m) >= 0 end end, } ) end #==WindowsMacros # Defines methods for adding attributes to a chef resource to describe # Windows file security metadata. # # This module is meant to be used to extend a class (instead of # `include`-ing). A class is automatically extended with this module when # it includes WindowsSecurableAttributes. # -- # TODO should this be separated into different files? module WindowsMacros # === rights_attribute # "meta-method" for dynamically creating rights attributes on resources. # # Multiple rights attributes can be declared. This enables resources to # have multiple rights attributes with separate runtime states. # # For example, +Chef::Resource::RemoteDirectory+ supports different # rights on the directories and files by declaring separate rights # attributes for each (rights and files_rights). # # ==== User Level API # Given a resource that calls # # rights_attribute(:rights) # # Then the resource DSL could be used like this: # # rights :read, ["Administrators","Everyone"] # rights :deny, "Pinky" # rights :full_control, "Users", :applies_to_children => true # rights :write, "John Keiser", :applies_to_children => :containers_only, :applies_to_self => false, :one_level_deep => true # # ==== Internal Data Structure # rights attributes support multiple right declarations # in a single resource block--the data will be merged # into a single internal hash. # # The internal representation is a hash with the following keys: # # * `:permissions`: Integer of Windows permissions flags, 1..2^32 # or one of `[:full_control, :modify, :read_execute, :read, :write]` # * `:principals`: String or Array of Strings represnting usernames on # the system. # * `:applies_to_children` (optional): Boolean # * `:applies_to_self` (optional): Boolean # * `:one_level_deep` (optional): Boolean # def rights_attribute(name) # equivalent to something like: # def rights(permissions=nil, principals=nil, args_hash=nil) define_method(name) do |permissions = nil, principals = nil, args_hash = nil| rights = self.instance_variable_get("@#{name}".to_sym) unless permissions.nil? input = { :permissions => permissions, :principals => principals, } input.merge!(args_hash) unless args_hash.nil? validations = { :permissions => { :required => true }, :principals => { :required => true, :kind_of => [String, Array] }, :applies_to_children => { :equal_to => [ true, false, :containers_only, :objects_only ] }, :applies_to_self => { :kind_of => [ TrueClass, FalseClass ] }, :one_level_deep => { :kind_of => [ TrueClass, FalseClass ] }, } validate(input, validations) [ permissions ].flatten.each do |permission| if permission.is_a?(Integer) if permission < 0 || permission > 1 << 32 raise ArgumentError, "permissions flags must be positive and <= 32 bits (#{permission})" end elsif !([:full_control, :modify, :read_execute, :read, :write].include?(permission.to_sym)) raise ArgumentError, "permissions parameter must be :full_control, :modify, :read_execute, :read, :write or an integer representing Windows permission flags" end end [ principals ].flatten.each do |principal| if !principal.is_a?(String) raise ArgumentError, "principals parameter must be a string or array of strings representing usernames" end end if input[:applies_to_children] == false if input[:applies_to_self] == false raise ArgumentError, "'rights' attribute must specify either :applies_to_children or :applies_to_self." end if input[:one_level_deep] == true raise ArgumentError, "'rights' attribute specified :one_level_deep without specifying :applies_to_children." end end rights ||= [] rights << input end set_or_return( name, rights, {} ) end end end #==WindowsSecurableAttributes # Defines #inherits to describe Windows file security ACLs on the # including class module WindowsSecurableAttributes def inherits(arg = nil) set_or_return( :inherits, arg, :kind_of => [ TrueClass, FalseClass ] ) end end if RUBY_PLATFORM =~ /mswin|mingw|windows/ include WindowsSecurableAttributes end # Callback that fires when included; will extend the including class # with WindowsMacros and define #rights and #deny_rights on it. def self.included(including_class) if RUBY_PLATFORM =~ /mswin|mingw|windows/ including_class.extend(WindowsMacros) # create a default 'rights' attribute including_class.rights_attribute(:rights) including_class.rights_attribute(:deny_rights) end end end end end chef-12.14.60/lib/chef/mixin/shell_out.rb000066400000000000000000000122021276456504500200070ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 "mixlib/shellout" class Chef module Mixin module ShellOut # shell_out! runs a command on the system and will raise an error if the command fails, which is what you want # for debugging, shell_out and shell_out! both will display command output to the tty when the log level is debug # Generally speaking, 'extend Chef::Mixin::ShellOut' in your recipes and include 'Chef::Mixin::ShellOut' in your LWRPs # You can also call Mixlib::Shellout.new directly, but you lose all of the above functionality # we use 'en_US.UTF-8' by default because we parse localized strings in English as an API and # generally must support UTF-8 unicode. def shell_out(*args, **options) options = options.dup env_key = options.has_key?(:env) ? :env : :environment options[env_key] = { "LC_ALL" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], }.update(options[env_key] || {}) shell_out_command(*args, **options) end # call shell_out (using en_US.UTF-8) and raise errors def shell_out!(*command_args) cmd = shell_out(*command_args) cmd.error! cmd end def shell_out_with_systems_locale(*command_args) shell_out_command(*command_args) end def shell_out_with_systems_locale!(*command_args) cmd = shell_out_with_systems_locale(*command_args) cmd.error! cmd end DEPRECATED_OPTIONS = [ [:command_log_level, :log_level], [:command_log_prepend, :log_tag] ] # CHEF-3090: Deprecate command_log_level and command_log_prepend # Patterned after https://github.com/opscode/chef/commit/e1509990b559984b43e428d4d801c394e970f432 def run_command_compatible_options(command_args) return command_args unless command_args.last.is_a?(Hash) my_command_args = command_args.dup my_options = my_command_args.last DEPRECATED_OPTIONS.each do |old_option, new_option| # Edge case: someone specifies :command_log_level and 'command_log_level' in the option hash next unless value = my_options.delete(old_option) || my_options.delete(old_option.to_s) deprecate_option old_option, new_option my_options[new_option] = value end return my_command_args end # Helper for sublcasses to convert an array of string args into a string. It # will compact nil or empty strings in the array and will join the array elements # with spaces, without introducing any double spaces for nil/empty elements. # # @param args [String] variable number of string arguments # @return [String] nicely concatenated string or empty string def a_to_s(*args) clean_array(*args).join(" ") end # Helper for sublcasses to reject nil and empty strings out of an array. It allows # using the array form of shell_out (which avoids the need to surround arguments with # quote marks to deal with shells). # # Usage: # shell_out!(*clean_array("useradd", universal_options, useradd_options, new_resource.username)) # # universal_options and useradd_options can be nil, empty array, empty string, strings or arrays # and the result makes sense. # # keeping this separate from shell_out!() makes it a bit easier to write expectations against the # shell_out args and be able to omit nils and such in the tests (and to test that the nils are # being rejected correctly). # # @param args [String] variable number of string arguments # @return [Array] array of strings with nil and null string rejection def clean_array(*args) args.flatten.reject { |i| i.nil? || i == "" }.map(&:to_s) end private def shell_out_command(*command_args) cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args)) cmd.live_stream ||= io_for_live_stream cmd.run_command cmd end def deprecate_option(old_option, new_option) Chef.log_deprecation "DEPRECATION: Chef::Mixin::ShellOut option :#{old_option} is deprecated. Use :#{new_option}" end def io_for_live_stream if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.debug? STDOUT else nil end end end end end # Break circular dep require "chef/config" chef-12.14.60/lib/chef/mixin/subclass_directive.rb000066400000000000000000000021071276456504500216710ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module SubclassDirective def subclass_directive(sym) define_singleton_method sym do instance_variable_set(:"@#{sym}", true) end define_singleton_method :"#{sym}?" do !!instance_variable_get(:"@#{sym}") end define_method :"#{sym}?" do self.class.send(:"#{sym}?") end end end end end chef-12.14.60/lib/chef/mixin/template.rb000066400000000000000000000226731276456504500176410ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # 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 "tempfile" require "erubis" class Chef module Mixin module Template # == ChefContext # ChefContext was previously used to mix behavior into Erubis::Context so # that it would be available to templates. This behavior has now moved to # TemplateContext, but this module is still mixed in to the # TemplateContext class so that any user code that modified ChefContext # will continue to work correctly. module ChefContext end # == TemplateContext # TemplateContext is the base context class for all templates in Chef. It # defines user-facing extensions to the base Erubis::Context to provide # enhanced features. Individual instances of TemplateContext can be # extended to add logic to a specific template. # class TemplateContext < Erubis::Context include ChefContext attr_reader :_extension_modules # # Helpers for adding context of which resource is rendering the template (CHEF-5012) # # name of the cookbook containing the template resource, e.g.: # test # # @return [String] cookbook name attr_reader :cookbook_name # name of the recipe containing the template resource, e.g.: # default # # @return [String] recipe name attr_reader :recipe_name # string representation of the line in the recipe containing the template resource, e.g.: # /Users/lamont/solo/cookbooks/test/recipes/default.rb:2:in `from_file' # # @return [String] recipe line attr_reader :recipe_line_string # path to the recipe containing the template resource, e.g.: # /Users/lamont/solo/cookbooks/test/recipes/default.rb # # @return [String] recipe path attr_reader :recipe_path # line in the recipe containing the template reosurce, e.g.: # 2 # # @return [String] recipe line attr_reader :recipe_line # name of the template source itself, e.g.: # foo.erb # # @return [String] template name attr_reader :template_name # path to the template source itself, e.g.: # /Users/lamont/solo/cookbooks/test/templates/default/foo.erb # # @return [String] template path attr_reader :template_path def initialize(variables) super @_extension_modules = [] end ### # USER FACING API ### # Returns the current node object, or raises an error if it's not set. # Provides API consistency, allowing users to reference the node object # by the bare `node` everywhere. def node return @node if @node raise "Could not find a value for node. If you are explicitly setting variables in a template, " + "include a node variable if you plan to use it." end # # Takes the name of the partial, plus a hash of options. Returns a # string that contains the result of the evaluation of the partial. # # All variables from the parent template will be propagated down to # the partial, unless you pass the +variables+ option (see below). # # Valid options are: # # :local:: If true then the partial name will be interpreted as the # path to a file on the local filesystem; if false (the # default) it will be looked up in the cookbook according to # the normal rules for templates. # :source:: If specified then the partial will be looked up with this # name or path (according to the +local+ option) instead of # +partial_name+. # :cookbook:: Search for the partial in the provided cookbook instead # of the cookbook that contains the top-level template. # :variables:: A Hash of variable_name => value that will be made # available to the partial. If specified, none of the # variables from the master template will be, so if you # need them you will need to propagate them explicitly. # def render(partial_name, options = {}) raise "You cannot render partials in this context" unless @template_finder partial_variables = options.delete(:variables) || _public_instance_variables partial_variables[:template_finder] = @template_finder partial_context = self.class.new(partial_variables) partial_context._extend_modules(@_extension_modules) template_location = @template_finder.find(partial_name, options) _render_template(IO.binread(template_location), partial_context) end def render_template(template_location) _render_template(IO.binread(template_location), self) end def render_template_from_string(template) _render_template(template, self) end ### # INTERNAL PUBLIC API ### def _render_template(template, context) begin eruby = Erubis::Eruby.new(template) output = eruby.evaluate(context) rescue Object => e raise TemplateError.new(e, template, context) end # CHEF-4399 # Erubis always emits unix line endings during template # rendering. Chef used to convert line endings to the # original line endings in the template. However this # created problems in cases when cookbook developer is # coding the cookbook on windows but using it on non-windows # platforms. # The safest solution is to make sure that native to the # platform we are running on is used in order to minimize # potential issues for the applications that will consume # this template. if Chef::Platform.windows? output = output.gsub(/\r?\n/, "\r\n") end output end def _extend_modules(module_names) module_names.each do |mod| context_methods = [:node, :render, :render_template, :render_template_from_string] context_methods.each do |core_method| if mod.method_defined?(core_method) || mod.private_method_defined?(core_method) Chef::Log.warn("Core template method `#{core_method}' overridden by extension module #{mod}") end end extend(mod) @_extension_modules << mod end end # Collects instance variables set on the current object as a Hash # suitable for creating a new TemplateContext. Instance variables that # are only valid for this specific instance are omitted from the # collection. def _public_instance_variables all_ivars = instance_variables all_ivars.delete(:@_extension_modules) all_ivars.inject({}) do |ivar_map, ivar_symbol_name| value = instance_variable_get(ivar_symbol_name) name_without_at = ivar_symbol_name.to_s[1..-1].to_sym ivar_map[name_without_at] = value ivar_map end end end class TemplateError < RuntimeError attr_reader :original_exception, :context SOURCE_CONTEXT_WINDOW = 2 def initialize(original_exception, template, context) @original_exception, @template, @context = original_exception, template, context end def message @original_exception.message end def line_number @line_number ||= $1.to_i if original_exception.backtrace.find { |line| line =~ /\(erubis\):(\d+)/ } end def source_location "on line ##{line_number}" end def source_listing @source_listing ||= begin lines = @template.split(/\n/) if line_number line_index = line_number - 1 beginning_line = line_index <= SOURCE_CONTEXT_WINDOW ? 0 : line_index - SOURCE_CONTEXT_WINDOW source_size = SOURCE_CONTEXT_WINDOW * 2 + 1 else beginning_line = 0 source_size = lines.length end contextual_lines = lines[beginning_line, source_size] output = [] contextual_lines.each_with_index do |line, index| line_number = (index + beginning_line + 1).to_s.rjust(3) output << "#{line_number}: #{line}" end output.join("\n") end end def to_s "\n\n#{self.class} (#{message}) #{source_location}:\n\n" + "#{source_listing}\n\n #{original_exception.backtrace.join("\n ")}\n\n" end end end end end chef-12.14.60/lib/chef/mixin/unformatter.rb000066400000000000000000000017101276456504500203610ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module Unformatter def write(message) data = message.match(/(\[.+?\] )?([\w]+):(.*)$/) self.send(data[2].downcase.chomp.to_sym, data[3].strip) rescue NoMethodError self.send(:info, message) end end end end chef-12.14.60/lib/chef/mixin/uris.rb000066400000000000000000000024431276456504500170010ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "uri" require "addressable/uri" class Chef module Mixin module Uris # uri_scheme? returns true if the string starts with # scheme:// # For example, it will match http://foo.bar.com def uri_scheme?(source) # From open-uri !!(%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ source) end def as_uri(source) begin URI.parse(source) rescue URI::InvalidURIError Chef::Log.warn("#{source} was an invalid URI. Trying to escape invalid characters") URI.parse(Addressable::URI.encode(source)) end end end end end chef-12.14.60/lib/chef/mixin/which.rb000066400000000000000000000022761276456504500171250ustar00rootroot00000000000000#-- # Author:: Lamont Granquist # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class Chef module Mixin module Which def which(cmd, opts = {}) extra_path = if opts[:extra_path].nil? [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ] else [ opts[:extra_path] ].flatten end paths = ENV["PATH"].split(File::PATH_SEPARATOR) + extra_path paths.each do |path| filename = File.join(path, cmd) return filename if File.executable?(Chef.path_to(filename)) end false end end end end chef-12.14.60/lib/chef/mixin/why_run.rb000066400000000000000000000342611276456504500175150ustar00rootroot00000000000000# # Author:: Dan DeLeo ( ) # Author:: Marc Paradise ( ) # 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 module Mixin module WhyRun # ==ConvergeActions # ConvergeActions implements the logic for why run. A ConvergeActions # object wraps a collection of actions, which consist of a descriptive # string and a block/Proc. Actions are executed by calling #converge! # When why_run mode is enabled, each action's description will be # printed, but the block will not be called. Conversely, in normal mode, # the block is called, but the message is not printed. # # In general, this class should be accessed through the API provided by # Chef::Provider. class ConvergeActions attr_reader :actions def initialize(resource, run_context, action) @resource, @run_context = resource, run_context @actions = [] end def events @run_context.events end # Adds an action to the list. +descriptions+ can either be an Array of # Strings, or a single String describing the action; +block+ is a # block/proc that implements the action. def add_action(descriptions, &block) @actions << [descriptions, block] if (@resource.respond_to?(:is_guard_interpreter) && @resource.is_guard_interpreter) || !Chef::Config[:why_run] yield end events.resource_update_applied(@resource, @action, descriptions) end # True if there are no actions to execute. def empty? @actions.empty? end end # == ResourceRequirements # ResourceRequirements provides a framework for making assertions about # the host system's state. It also provides a mechanism for making # assumptions about what the system's state might have been when running # in why run mode. # # For example, consider a recipe that consists of a package resource and # a service resource. If the service's init script is installed by the # package, and Chef is running in why run mode, then the service resource # would fail when attempting to run `/etc/init.d/software-name status`. # In order to provide a more useful approximation of what would happen in # a real chef run, we want to instead assume that the service was created # but isn't running. The logic would look like this: # # # Hypothetical service provider demonstrating why run assumption logic. # # This isn't the actual API, it just shows the logic. # class HypotheticalServiceProvider < Chef::Provider # # def load_current_resource # # Make sure we have the init script available: # if ::File.exist?("/etc/init.d/some-service" # # If the init script exists, proceed as normal: # status_cmd = shell_out("/etc/init.d/some-service status") # if status_cmd.success? # @current_resource.status(:running) # else # @current_resource.status(:stopped) # end # else # if whyrun_mode? # # If the init script is not available, and we're in why run mode, # # assume that some previous action would've created it: # log("warning: init script '/etc/init.d/some-service' is not available") # log("warning: assuming that the init script would have been created, assuming the state of 'some-service' is 'stopped'") # @current_resource.status(:stopped) # else # raise "expected init script /etc/init.d/some-service doesn't exist" # end # end # end # # end # # In short, the code above does the following: # * runs a test to determine if a requirement is met: # `::File.exist?("/etc/init.d/some-service"` # * raises an error if the requirement is not met, and we're not in why # run mode. # * if we *are* in why run mode, print a message explaining the # situation, and run some code that makes an assumption about what the # state of the system would be. In this case, we also skip the normal # `load_current_resource` logic # * when the requirement *is* met, we run the normal `load_current_resource` # logic # # ResourceRequirements encapsulates the above logic in a more declarative API. # # === Examples # Assertions and assumptions should be created through the WhyRun#assert # method, which gets mixed in to providers. See that method's # documentation for examples. class ResourceRequirements # Implements the logic for a single assertion/assumption. See the # documentation for ResourceRequirements for full discussion. class Assertion class AssertionFailure < RuntimeError end def initialize @block_action = false @assertion_proc = nil @failure_message = nil @whyrun_message = nil @resource_modifier = nil @assertion_failed = false @exception_type = AssertionFailure end # Defines the code block that determines if a requirement is met. The # block should return a truthy value to indicate that the requirement # is met, and a falsey value if the requirement is not met. # # in a provider: # assert(:some_action) do |a| # # This provider requires the file /tmp/foo to exist: # a.assertion { ::File.exist?("/tmp/foo") } # end def assertion(&assertion_proc) @assertion_proc = assertion_proc end # Defines the failure message, and optionally the Exception class to # use when a requirement is not met. It works like `raise`: # # in a provider: # assert(:some_action) do |a| # # This example shows usage with 1 or 2 args by calling #failure_message twice. # # In practice you should only call this once per Assertion. # # # Set the Exception class explicitly # a.failure_message(Chef::Exceptions::MissingRequiredFile, "File /tmp/foo doesn't exist") # # Fallback to the default error class (AssertionFailure) # a.failure_message("File /tmp/foo" doesn't exist") # end def failure_message(*args) case args.size when 1 @failure_message = args[0] when 2 @exception_type, @failure_message = args[0], args[1] else raise ArgumentError, "#{self.class}#failure_message takes 1 or 2 arguments, you gave #{args.inspect}" end end # Defines a message and optionally provides a code block to execute # when the requirement is not met and Chef is executing in why run # mode # # If no failure_message is provided (above), then execution # will be allowed to continue in both whyrun and non-whyrun # mode # # With a service resource that requires /etc/init.d/service-name to exist: # # in a provider # assert(:start, :restart) do |a| # a.assertion { ::File.exist?("/etc/init.d/service-name") } # a.whyrun("Init script '/etc/init.d/service-name' doesn't exist, assuming a prior action would have created it.") do # # blindly assume that the service exists but is stopped in why run mode: # @new_resource.status(:stopped) # end # end def whyrun(message, &resource_modifier) @whyrun_message = message @resource_modifier = resource_modifier end # Prevents associated actions from being invoked in whyrun mode. # This will also stop further processing of assertions for a given action. # # An example from the template provider: if the source template doesn't exist # we can't parse it in the action_create block of template - something that we do # even in whyrun mode. Because the source template may have been created in an earlier # step, we still want to keep going in whyrun mode. # # assert(:create, :create_if_missing) do |a| # a.assertion { File::exists?(@new_resource.source) } # a.whyrun "Template source file does not exist, assuming it would have been created." # a.block_action! # end # def block_action! @block_action = true end def block_action? @block_action end def assertion_failed? @assertion_failed end # Runs the assertion/assumption logic. Will raise an Exception of the # type specified in #failure_message (or AssertionFailure by default) # if the requirement is not met and Chef is not running in why run # mode. An exception will also be raised if running in why run mode # and no why run message or block has been declared. def run(action, events, resource) if !@assertion_proc || !@assertion_proc.call @assertion_failed = true if Chef::Config[:why_run] && @whyrun_message events.provider_requirement_failed(action, resource, @exception_type, @failure_message) events.whyrun_assumption(action, resource, @whyrun_message) if @whyrun_message @resource_modifier.call if @resource_modifier else if @failure_message events.provider_requirement_failed(action, resource, @exception_type, @failure_message) raise @exception_type, @failure_message end end end end end def initialize(resource, run_context) @resource, @run_context = resource, run_context @assertions = Hash.new { |h, k| h[k] = [] } @blocked_actions = [] end def events @run_context.events end # Check to see if a given action is blocked by a failed assertion # # Takes the action name to be verified. def action_blocked?(action) @blocked_actions.include?(action) end # Define a new Assertion. # # Takes a list of action names for which the assertion should be made. # ==== Examples: # A File provider that requires the parent directory to exist: # # assert(:create, :create_if_missing) do |a| # parent_dir = File.basename(@new_resource.path) # a.assertion { ::File.directory?(parent_dir) } # a.failure_message(Exceptions::ParentDirectoryDoesNotExist, # "Can't create file #{@new_resource.path}: parent directory #{parent_dir} doesn't exist") # a.why_run("assuming parent directory #{parent_dir} would have been previously created" # end # # A service provider that requires the init script to exist: # # assert(:start, :restart) do |a| # a.assertion { ::File.exist?(@new_resource.init_script) } # a.failure_message(Exceptions::MissingInitScript, # "Can't check status of #{@new_resource}: init script #{@new_resource.init_script} is missing") # a.why_run("Assuming init script would have been created and service is stopped") do # @current_resource.status(:stopped) # end # end # # A File provider that will error out if you don't have permissions do # delete the file, *even in why run mode*: # # assert(:delete) do |a| # a.assertion { ::File.writable?(@new_resource.path) } # a.failure_message(Exceptions::InsufficientPrivileges, # "You don't have sufficient privileges to delete #{@new_resource.path}") # end # # A Template provider that will prevent action execution but continue the run in # whyrun mode if the template source is not available. # assert(:create, :create_if_missing) do |a| # a.assertion { File::exist?(@new_resource.source) } # a.failure_message Chef::Exceptions::TemplateError, "Template #{@new_resource.source} could not be found exist." # a.whyrun "Template source #{@new_resource.source} does not exist. Assuming it would have been created." # a.block_action! # end # # assert(:delete) do |a| # a.assertion { ::File.writable?(@new_resource.path) } # a.failure_message(Exceptions::InsufficientPrivileges, # "You don't have sufficient privileges to delete #{@new_resource.path}") # end def assert(*actions) assertion = Assertion.new yield assertion actions.each { |action| @assertions[action] << assertion } end # Run the assertion and assumption logic. def run(action) @assertions[action.to_sym].each do |a| a.run(action, events, @resource) if a.assertion_failed? && a.block_action? @blocked_actions << action break end end end end end end end chef-12.14.60/lib/chef/mixin/wide_string.rb000066400000000000000000000043071276456504500203360ustar00rootroot00000000000000# # Author:: Jay Mundrawala() # Copyright:: Copyright 2015-2016, Chef Software # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef module Mixin module WideString def wstring(str) if str.nil? || str.encoding == Encoding::UTF_16LE str else utf8_to_wide(str) end end def utf8_to_wide(ustring) # ensure it is actually UTF-8 # Ruby likes to mark binary data as ASCII-8BIT ustring = (ustring + "").force_encoding("UTF-8") if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8" # ensure we have the double-null termination Windows Wide likes ustring = ustring + "\000\000" if ustring.length == 0 || ustring[-1].chr != "\000" # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode ustring = begin if ustring.respond_to?(:encode) ustring.encode("UTF-16LE") else require "iconv" Iconv.conv("UTF-16LE", "UTF-8", ustring) end end ustring end def wide_to_utf8(wstring) # ensure it is actually UTF-16LE # Ruby likes to mark binary data as ASCII-8BIT wstring = wstring.force_encoding("UTF-16LE") if wstring.respond_to?(:force_encoding) # encode it all as UTF-8 wstring = begin if wstring.respond_to?(:encode) wstring.encode("UTF-8") else require "iconv" Iconv.conv("UTF-8", "UTF-16LE", wstring) end end # remove trailing CRLF and NULL characters wstring.strip! wstring end end end end chef-12.14.60/lib/chef/mixin/windows_architecture_helper.rb000066400000000000000000000073021276456504500236110ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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/exceptions" require "chef/platform/query_helpers" require "chef/win32/process" if Chef::Platform.windows? require "chef/win32/system" if Chef::Platform.windows? class Chef module Mixin module WindowsArchitectureHelper def node_windows_architecture(node) node[:kernel][:machine].to_sym end def wow64_architecture_override_required?(node, desired_architecture) desired_architecture == :x86_64 && node_windows_architecture(node) == :x86_64 && is_i386_process_on_x86_64_windows? end def forced_32bit_override_required?(node, desired_architecture) desired_architecture == :i386 && node_windows_architecture(node) == :x86_64 && !is_i386_process_on_x86_64_windows? end def wow64_directory Chef::ReservedNames::Win32::System.get_system_wow64_directory end def with_os_architecture(node, architecture: nil) node ||= begin os_arch = ENV["PROCESSOR_ARCHITEW6432"] || ENV["PROCESSOR_ARCHITECTURE"] Hash.new.tap do |n| n[:kernel] = Hash.new n[:kernel][:machine] = os_arch == "AMD64" ? :x86_64 : :i386 end end architecture ||= node_windows_architecture(node) wow64_redirection_state = nil if wow64_architecture_override_required?(node, architecture) wow64_redirection_state = disable_wow64_file_redirection(node) end begin yield ensure if wow64_redirection_state restore_wow64_file_redirection(node, wow64_redirection_state) end end end def node_supports_windows_architecture?(node, desired_architecture) assert_valid_windows_architecture!(desired_architecture) return ( node_windows_architecture(node) == :x86_64 ) || ( desired_architecture == :i386 ) end def valid_windows_architecture?(architecture) return ( architecture == :x86_64 ) || ( architecture == :i386 ) end def assert_valid_windows_architecture!(architecture) if !valid_windows_architecture?(architecture) raise Chef::Exceptions::Win32ArchitectureIncorrect, "The specified architecture was not valid. It must be one of :i386 or :x86_64" end end def is_i386_process_on_x86_64_windows? if Chef::Platform.windows? Chef::ReservedNames::Win32::Process.is_wow64_process else false end end def disable_wow64_file_redirection( node ) if ( node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows? Chef::ReservedNames::Win32::System.wow64_disable_wow64_fs_redirection end end def restore_wow64_file_redirection( node, original_redirection_state ) if (node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows? Chef::ReservedNames::Win32::System.wow64_revert_wow64_fs_redirection(original_redirection_state) end end end end end chef-12.14.60/lib/chef/mixin/windows_env_helper.rb000066400000000000000000000046651276456504500217300ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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/exceptions" require "chef/mixin/wide_string" require "chef/platform/query_helpers" require "chef/win32/error" if Chef::Platform.windows? require "chef/win32/api/system" if Chef::Platform.windows? require "chef/win32/api/unicode" if Chef::Platform.windows? class Chef module Mixin module WindowsEnvHelper include Chef::Mixin::WideString if Chef::Platform.windows? include Chef::ReservedNames::Win32::API::System end #see: http://msdn.microsoft.com/en-us/library/ms682653%28VS.85%29.aspx HWND_BROADCAST = 0xffff WM_SETTINGCHANGE = 0x001A SMTO_BLOCK = 0x0001 SMTO_ABORTIFHUNG = 0x0002 SMTO_NOTIMEOUTIFNOTHUNG = 0x0008 def broadcast_env_change flags = SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG # for why two calls, see: # http://stackoverflow.com/questions/4968373/why-doesnt-sendmessagetimeout-update-the-environment-variables if SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string("Environment").address, flags, 5000, nil) == 0 Chef::ReservedNames::Win32::Error.raise! end if SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string( utf8_to_wide("Environment") ).address, flags, 5000, nil) == 0 Chef::ReservedNames::Win32::Error.raise! end end def expand_path(path) # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724265%28v=vs.85%29.aspx # Max size of env block on windows is 32k buf = 0.chr * 32 * 1024 if ExpandEnvironmentStringsA(path, buf, buf.length) == 0 Chef::ReservedNames::Win32::Error.raise! end buf.strip end end end end chef-12.14.60/lib/chef/mixin/xml_escape.rb000066400000000000000000000115231276456504500201360ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Chef Software Inc. # Copyright:: Copyright 2005-2016, Sam Ruby # 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. #-- # Portions of this code are adapted from Sam Ruby's xchar.rb # http://intertwingly.net/stories/2005/09/28/xchar.rb # # Such code appears here under Sam's original MIT license, while portions of # this file are covered by the above Apache License. For a completely MIT # licensed version, please see Sam's original. # # Thanks, Sam! # # Copyright 2005-2016, Sam Ruby # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require "chef/log" begin require "fast_xs" rescue LoadError Chef::Log.info "The fast_xs gem is not installed, slower pure ruby XML escaping will be used." end class Chef module Mixin module XMLEscape module PureRuby extend self CP1252 = { 128 => 8364, # euro sign 130 => 8218, # single low-9 quotation mark 131 => 402, # latin small letter f with hook 132 => 8222, # double low-9 quotation mark 133 => 8230, # horizontal ellipsis 134 => 8224, # dagger 135 => 8225, # double dagger 136 => 710, # modifier letter circumflex accent 137 => 8240, # per mille sign 138 => 352, # latin capital letter s with caron 139 => 8249, # single left-pointing angle quotation mark 140 => 338, # latin capital ligature oe 142 => 381, # latin capital letter z with caron 145 => 8216, # left single quotation mark 146 => 8217, # right single quotation mark 147 => 8220, # left double quotation mark 148 => 8221, # right double quotation mark 149 => 8226, # bullet 150 => 8211, # en dash 151 => 8212, # em dash 152 => 732, # small tilde 153 => 8482, # trade mark sign 154 => 353, # latin small letter s with caron 155 => 8250, # single right-pointing angle quotation mark 156 => 339, # latin small ligature oe 158 => 382, # latin small letter z with caron 159 => 376 # latin capital letter y with diaeresis } # http://www.w3.org/TR/REC-xml/#dt-chardata PREDEFINED = { 38 => "&", # ampersand 60 => "<", # left angle bracket 62 => ">" # right angle bracket } # http://www.w3.org/TR/REC-xml/#charsets VALID = [[0x9, 0xA, 0xD], (0x20..0xD7FF), (0xE000..0xFFFD), (0x10000..0x10FFFF)] def xml_escape(unescaped_str) begin unescaped_str.unpack("U*").map { |char| xml_escape_char!(char) }.join rescue unescaped_str.unpack("C*").map { |char| xml_escape_char!(char) }.join end end private def xml_escape_char!(char) char = CP1252[char] || char char = 42 unless VALID.detect { |range| range.include? char } char = PREDEFINED[char] || (char < 128 ? char.chr : "&##{char};") end end module FastXS extend self def xml_escape(string) string.fast_xs end end if "strings".respond_to?(:fast_xs) include FastXS extend FastXS else include PureRuby extend PureRuby end end end end chef-12.14.60/lib/chef/mixins.rb000066400000000000000000000006771276456504500162110ustar00rootroot00000000000000require "chef/mixin/shell_out" require "chef/mixin/checksum" require "chef/mixin/command" require "chef/mixin/convert_to_class_name" require "chef/mixin/create_path" require "chef/mixin/deep_merge" require "chef/mixin/enforce_ownership_and_permissions" require "chef/mixin/from_file" require "chef/mixin/params_validate" require "chef/mixin/path_sanity" require "chef/mixin/template" require "chef/mixin/securable" require "chef/mixin/xml_escape" chef-12.14.60/lib/chef/monkey_patches/000077500000000000000000000000001276456504500173545ustar00rootroot00000000000000chef-12.14.60/lib/chef/monkey_patches/net-ssh-multi.rb000066400000000000000000000116561276456504500224230ustar00rootroot00000000000000# # Author:: Serdar Sutay () # 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. # # == net-ssh-multi gem patch for concurrency # net-ssh-multi gem has 2 bugs associated with the use of # :concurrent_connections option. # 1-) There is a race condition while fetching the next_session when # :concurrent_connections are set. @open_connections is being # incremented by the connection thread and sometimes # realize_pending_connections!() method can create more than required # connection threads before the @open_connections is set by the # previously created threads. # 2-) When :concurrent_connections is set, server classes are setup # with PendingConnection objects that always return true to busy? # calls. If a connection fails when :concurrent_connections is set, # server ends up returning true to all busy? calls since the session # object is not replaced. Due to this, main event loop (process() # function) never gets terminated. # # See: https://github.com/net-ssh/net-ssh-multi/pull/4 require "net/ssh/multi/version" if Net::SSH::Multi::Version::STRING == "1.1.0" || Net::SSH::Multi::Version::STRING == "1.2.0" require "net/ssh/multi" module Net module SSH module Multi class Server # Make sure that server returns false if the ssh connection # has failed. def busy?(include_invisible = false) !failed? && session && session.busy?(include_invisible) end end class Session def next_session(server, force = false) #:nodoc: # don't retry a failed attempt return nil if server.failed? @session_mutex.synchronize do if !force && concurrent_connections && concurrent_connections <= open_connections connection = PendingConnection.new(server) @pending_sessions << connection return connection end # ===== PATCH START # Only increment the open_connections count if the connection # is not being forced. Incase of a force, it will already be # incremented. if !force @open_connections += 1 end # ===== PATCH END end begin server.new_session # I don't understand why this should be necessary--StandardError is a # subclass of Exception, after all--but without explicitly rescuing # StandardError, things like Errno::* and SocketError don't get caught # here! rescue Exception, StandardError => e server.fail! @session_mutex.synchronize { @open_connections -= 1 } case on_error when :ignore then # do nothing when :warn then warn("error connecting to #{server}: #{e.class} (#{e.message})") when Proc then go = catch(:go) { on_error.call(server); nil } case go when nil, :ignore then # nothing when :retry then retry when :raise then raise else warn "unknown 'go' command: #{go.inspect}" end else raise end return nil end end def realize_pending_connections! #:nodoc: return unless concurrent_connections server_list.each do |server| server.close if !server.busy?(true) server.update_session! end @connect_threads.delete_if { |t| !t.alive? } count = concurrent_connections ? (concurrent_connections - open_connections) : @pending_sessions.length count.times do session = @pending_sessions.pop break unless session # ===== PATCH START # Increment the open_connections count here to prevent # creation of connection thread again before that is # incremented by the thread. @session_mutex.synchronize { @open_connections += 1 } # ===== PATCH END @connect_threads << Thread.new do session.replace_with(next_session(session.server, true)) end end end end end end end end chef-12.14.60/lib/chef/monkey_patches/net_http.rb000066400000000000000000000046431276456504500215350ustar00rootroot00000000000000 # Module gets mixed in to Net::HTTP exception classes so we can attach our # RESTRequest object to them and get the request parameters back out later. module ChefNetHTTPExceptionExtensions attr_accessor :chef_rest_request end require "net/http" module Net class HTTPError include ChefNetHTTPExceptionExtensions end class HTTPRetriableError include ChefNetHTTPExceptionExtensions end class HTTPServerException include ChefNetHTTPExceptionExtensions end class HTTPFatalError include ChefNetHTTPExceptionExtensions end end if Net::HTTP.instance_methods.map { |m| m.to_s }.include?("proxy_uri") begin # Ruby 2.0 changes the way proxy support is implemented in Net::HTTP. # The implementation does not work correctly with IPv6 literals because it # concatenates the address into a URI without adding square brackets for # IPv6 addresses. # # If the bug is present, a call to Net::HTTP#proxy_uri when the host is an # IPv6 address will fail by creating an invalid URI, like so: # # ruby -r'net/http' -e 'Net::HTTP.new("::1", 80).proxy_uri' # /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/generic.rb:214:in `initialize': the scheme http does not accept registry part: ::1:80 (or bad hostname?) (URI::InvalidURIError) # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/http.rb:84:in `initialize' # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:214:in `new' # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:214:in `parse' # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:747:in `parse' # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:994:in `URI' # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/net/http.rb:1027:in `proxy_uri' # from -e:1:in `
' # # https://bugs.ruby-lang.org/issues/9129 # # NOTE: This should be fixed in Ruby 2.2.0, and backported to Ruby 2.0 and # 2.1 (not yet released so the version/patchlevel required isn't known # yet). Net::HTTP.new("::1", 80).proxy_uri rescue URI::InvalidURIError class Net::HTTP def proxy_uri # :nodoc: ipv6_safe_addr = address.to_s.include?(":") ? "[#{address}]" : address @proxy_uri ||= URI("http://#{ipv6_safe_addr}:#{port}").find_proxy end end end end chef-12.14.60/lib/chef/monkey_patches/webrick-utils.rb000066400000000000000000000033121276456504500224640ustar00rootroot00000000000000require "webrick/utils" module WEBrick module Utils ## # Creates TCP server sockets bound to +address+:+port+ and returns them. # # It will create IPV4 and IPV6 sockets on all interfaces. # # NOTE: We need to monkey patch this method because # create_listeners on Windows with Ruby > 2.0.0 does not # raise an error if we're already listening on a port. # def create_listeners(address, port, logger = nil) # # utils.rb -- Miscellaneous utilities # # Author: IPR -- Internet Programming with Ruby -- writers # Copyright 2001-2016, TAKAHASHI Masayoshi, GOTOU Yuuzou # Copyright 2002-2016, Internet Programming with Ruby writers. All rights # reserved. # # $IPR: utils.rb,v 1.10 2003/02/16 22:22:54 gotoyuzo Exp $ unless port raise ArgumentError, "must specify port" end res = Socket.getaddrinfo(address, port, Socket::AF_UNSPEC, # address family Socket::SOCK_STREAM, # socket type 0, # protocol Socket::AI_PASSIVE) # flag last_error = nil sockets = [] res.each do |ai| begin logger.debug("TCPServer.new(#{ai[3]}, #{port})") if logger sock = TCPServer.new(ai[3], port) port = sock.addr[1] if port == 0 Utils.set_close_on_exec(sock) sockets << sock rescue => ex logger.warn("TCPServer Error: #{ex}") if logger last_error = ex end end raise last_error if sockets.empty? return sockets end module_function :create_listeners end end chef-12.14.60/lib/chef/monkey_patches/win32/000077500000000000000000000000001276456504500203165ustar00rootroot00000000000000chef-12.14.60/lib/chef/monkey_patches/win32/registry.rb000066400000000000000000000061301276456504500225130ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api/registry" require "chef/win32/unicode" require "win32/registry" module Win32 class Registry # ::Win32::Registry#export_string is used when enumerating child # keys and values and re encodes a UTF-16LE to the local codepage. # This can result in encoding incompatibilities if the native codepage # does not support the characters in the registry. There is an open bug # in ruby at https://bugs.ruby-lang.org/issues/11410. Rather than converting # the UTF-16LE originally returned by the win32 api, we encode to UTF-8 # which will likely not result in any conversion error. def export_string(str, enc = Encoding.default_internal || "utf-8") str.encode(enc) end module API extend Chef::ReservedNames::Win32::API::Registry module_function # ::Win32::Registry#delete_value uses RegDeleteValue which # is not an imported function after bug 10820 was solved. So # we overwrite it to call the correct imported function. # https://bugs.ruby-lang.org/issues/10820 # Still a bug in trunk as of March 21, 2016 (Ruby 2.3.0) def DeleteValue(hkey, name) check RegDeleteValueW(hkey, name.to_wstring) end # ::Win32::Registry#delete_key uses RegDeleteKeyW. We need to use # RegDeleteKeyExW to properly support WOW64 systems. # Still a bug in trunk as of March 21, 2016 (Ruby 2.3.0) def DeleteKey(hkey, name) check RegDeleteKeyExW(hkey, name.to_wstring, 0, 0) end end if RUBY_VERSION =~ /^2\.1/ # ::Win32::Registry#write does not correctly handle data in Ruby 2.1 # This bug is _reportedly_ resolved in Ruby 2.1.7 and 2.2.3 # but fails in appveyor on 2.1.8 unless we keep applying this monkeypatch # https://bugs.ruby-lang.org/issues/11439 def write(name, type, data) case type when REG_SZ, REG_EXPAND_SZ data = data.to_s.encode(WCHAR) + WCHAR_NUL when REG_MULTI_SZ data = data.to_a.map { |s| s.encode(WCHAR) }.join(WCHAR_NUL) << WCHAR_NUL << WCHAR_NUL when REG_BINARY data = data.to_s when REG_DWORD data = API.packdw(data.to_i) when REG_DWORD_BIG_ENDIAN data = [data.to_i].pack("N") when REG_QWORD data = API.packqw(data.to_i) else raise TypeError, "Unsupported type #{type}" end API.SetValue(@hkey, name, type, data, data.bytesize) end end end end chef-12.14.60/lib/chef/monologger.rb000066400000000000000000000036321276456504500170440ustar00rootroot00000000000000require "logger" require "pp" #== MonoLogger # A subclass of Ruby's stdlib Logger with all the mutex and logrotation stuff # ripped out. class MonoLogger < Logger # # === Synopsis # # Logger.new(name, shift_age = 7, shift_size = 1048576) # Logger.new(name, shift_age = 'weekly') # # === Args # # +logdev+:: # The log device. This is a filename (String) or IO object (typically # +STDOUT+, +STDERR+, or an open file). # +shift_age+:: # Number of old log files to keep, *or* frequency of rotation (+daily+, # +weekly+ or +monthly+). # +shift_size+:: # Maximum logfile size (only applies when +shift_age+ is a number). # # === Description # # Create an instance. # def initialize(logdev) @progname = nil @level = DEBUG @default_formatter = Formatter.new @formatter = nil @logdev = nil if logdev @logdev = LocklessLogDevice.new(logdev) end end class LocklessLogDevice < LogDevice def initialize(log = nil) @dev = @filename = @shift_age = @shift_size = nil if log.respond_to?(:write) && log.respond_to?(:close) @dev = log else @dev = open_logfile(log) @filename = log end @dev.sync = true end def write(message) @dev.write(message) rescue Exception => ignored warn("log writing failed. #{ignored}") end def close @dev.close rescue nil end private def open_logfile(filename) if FileTest.exist?(filename) open(filename, (File::WRONLY | File::APPEND)) else create_logfile(filename) end end def create_logfile(filename) logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT)) add_log_header(logdev) logdev end def add_log_header(file) file.write( "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName] ) end end end chef-12.14.60/lib/chef/nil_argument.rb000066400000000000000000000000531276456504500173520ustar00rootroot00000000000000class Chef NIL_ARGUMENT = Object.new end chef-12.14.60/lib/chef/node.rb000066400000000000000000000537571276456504500156360ustar00rootroot00000000000000# Author:: Adam Jacob () # Author:: Christopher Brown () # Author:: Christopher Walters () # Author:: Tim Hinderliter () # 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 "forwardable" require "chef/config" require "chef/nil_argument" require "chef/mixin/params_validate" require "chef/mixin/from_file" require "chef/mixin/deep_merge" require "chef/dsl/include_attribute" require "chef/dsl/universal" require "chef/environment" require "chef/server_api" require "chef/run_list" require "chef/node/attribute" require "chef/mash" require "chef/json_compat" require "chef/search/query" require "chef/whitelist" class Chef class Node extend Forwardable def_delegators :attributes, :keys, :each_key, :each_value, :key?, :has_key? def_delegators :attributes, :rm, :rm_default, :rm_normal, :rm_override def_delegators :attributes, :default!, :normal!, :override!, :force_default!, :force_override! def_delegators :attributes, :default_unless, :normal_unless, :override_unless, :set_unless def_delegators :attributes, :read, :read!, :write, :write!, :unlink, :unlink! attr_accessor :recipe_list, :run_state, :override_runlist attr_accessor :chef_server_rest # RunContext will set itself as run_context via this setter when # initialized. This is needed so DSL::IncludeAttribute (in particular, # #include_recipe) can access the run_context to determine if an attributes # file has been seen yet. #-- # TODO: This is a pretty ugly way to solve that problem. attr_accessor :run_context include Chef::Mixin::FromFile include Chef::DSL::IncludeAttribute include Chef::DSL::Universal include Chef::Mixin::ParamsValidate NULL_ARG = Object.new # Create a new Chef::Node object. def initialize(chef_server_rest: nil) @chef_server_rest = chef_server_rest @name = nil @chef_environment = "_default" @primary_runlist = Chef::RunList.new @override_runlist = Chef::RunList.new @policy_name = nil @policy_group = nil @attributes = Chef::Node::Attribute.new({}, {}, {}, {}) @run_state = {} end # after the run_context has been set on the node, go through the cookbook_collection # and setup the node[:cookbooks] attribute so that it is published in the node object def set_cookbook_attribute return unless run_context.cookbook_collection run_context.cookbook_collection.each do |cookbook_name, cookbook| automatic_attrs[:cookbooks][cookbook_name][:version] = cookbook.version end end # Used by DSL def node self end def chef_server_rest # for saving node data we use validate_utf8: false which will not # raise an exception on bad utf8 data, but will replace the bad # characters and render valid JSON. @chef_server_rest ||= Chef::ServerAPI.new( Chef::Config[:chef_server_url], client_name: Chef::Config[:node_name], signing_key_filename: Chef::Config[:client_key], validate_utf8: false ) end # Set the name of this Node, or return the current name. def name(arg = nil) if arg != nil validate( { :name => arg }, { :name => { :kind_of => String, :cannot_be => :blank, :regex => /^[\-[:alnum:]_:.]+$/ }, }) @name = arg else @name end end def chef_environment(arg = nil) set_or_return( :chef_environment, arg, { :regex => /^[\-[:alnum:]_]+$/, :kind_of => String } ) end def chef_environment=(environment) chef_environment(environment) end alias :environment :chef_environment # The `policy_name` for this node. Setting this to a non-nil value will # enable policyfile mode when `chef-client` is run. If set in the config # file or in node json, running `chef-client` will update this value. # # @see Chef::PolicyBuilder::Dynamic # @see Chef::PolicyBuilder::Policyfile # # @param arg [String] the new policy_name value # @return [String] the current policy_name, or the one you just set def policy_name(arg = NULL_ARG) return @policy_name if arg.equal?(NULL_ARG) validate({ policy_name: arg }, { policy_name: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } }) @policy_name = arg end # A "non-DSL-style" setter for `policy_name` # # @see #policy_name def policy_name=(policy_name) policy_name(policy_name) end # The `policy_group` for this node. Setting this to a non-nil value will # enable policyfile mode when `chef-client` is run. If set in the config # file or in node json, running `chef-client` will update this value. # # @see Chef::PolicyBuilder::Dynamic # @see Chef::PolicyBuilder::Policyfile # # @param arg [String] the new policy_group value # @return [String] the current policy_group, or the one you just set def policy_group(arg = NULL_ARG) return @policy_group if arg.equal?(NULL_ARG) validate({ policy_group: arg }, { policy_group: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } }) @policy_group = arg end # A "non-DSL-style" setter for `policy_group` # # @see #policy_group def policy_group=(policy_group) policy_group(policy_group) end def attributes @attributes end alias :attribute :attributes alias :construct_attributes :attributes # Return an attribute of this node. Returns nil if the attribute is not found. def [](attrib) attributes[attrib] end # Set a normal attribute of this node, but auto-vivify any Mashes that # might be missing def normal attributes.top_level_breadcrumb = nil attributes.normal end def set Chef.log_deprecation("node.set is deprecated and will be removed in Chef 14, please use node.default/node.override (or node.normal only if you really need persistence)") normal end # Set a default of this node, but auto-vivify any Mashes that might # be missing def default attributes.top_level_breadcrumb = nil attributes.default end # Set an override attribute of this node, but auto-vivify any Mashes that # might be missing def override attributes.top_level_breadcrumb = nil attributes.override end alias :override_attrs :override alias :default_attrs :default alias :normal_attrs :normal def override_attrs=(new_values) attributes.override = new_values end def default_attrs=(new_values) attributes.default = new_values end def normal_attrs=(new_values) attributes.normal = new_values end def automatic_attrs attributes.top_level_breadcrumb = nil attributes.automatic end def automatic_attrs=(new_values) attributes.automatic = new_values end # Return true if this Node has a given attribute, false if not. Takes either a symbol or # a string. # # Only works on the top level. Preferred way is to use the normal [] style # lookup and call attribute?() def attribute?(attrib) attributes.attribute?(attrib) end # Yield each key of the top level to the block. def each(&block) attributes.each(&block) end # Iterates over each attribute, passing the attribute and value to the block. def each_attribute(&block) attributes.each_attribute(&block) end # Only works for attribute fetches, setting is no longer supported # XXX: this should be deprecated def method_missing(method, *args, &block) attributes.public_send(method, *args, &block) end # Fix respond_to + method so that it works with method_missing delegation def respond_to_missing?(method, include_private = false) attributes.respond_to?(method, false) end # Returns true if this Node expects a given recipe, false if not. # # First, the run list is consulted to see whether the recipe is # explicitly included. If it's not there, it looks in # `node[:recipes]`, which is populated when the run_list is expanded # # NOTE: It's used by cookbook authors def recipe?(recipe_name) run_list.include?(recipe_name) || Array(self[:recipes]).include?(recipe_name) end # used by include_recipe to add recipes to the expanded run_list to be # saved back to the node and be searchable def loaded_recipe(cookbook, recipe) fully_qualified_recipe = "#{cookbook}::#{recipe}" automatic_attrs[:recipes] << fully_qualified_recipe unless Array(self[:recipes]).include?(fully_qualified_recipe) end # Returns true if this Node expects a given role, false if not. def role?(role_name) run_list.include?("role[#{role_name}]") end def primary_runlist @primary_runlist end def override_runlist(*args) args.length > 0 ? @override_runlist.reset!(args) : @override_runlist end def select_run_list @override_runlist.empty? ? @primary_runlist : @override_runlist end # Returns an Array of roles and recipes, in the order they will be applied. # If you call it with arguments, they will become the new list of roles and recipes. def run_list(*args) rl = select_run_list args.length > 0 ? rl.reset!(args) : rl end def run_list=(list) rl = select_run_list rl = list end # Returns true if this Node expects a given role, false if not. def run_list?(item) run_list.detect { |r| r == item } ? true : false end # Consume data from ohai and Attributes provided as JSON on the command line. def consume_external_attrs(ohai_data, json_cli_attrs) Chef::Log.debug("Extracting run list from JSON attributes provided on command line") consume_attributes(json_cli_attrs) self.automatic_attrs = ohai_data platform, version = Chef::Platform.find_platform_and_version(self) Chef::Log.debug("Platform is #{platform} version #{version}") self.automatic[:platform] = platform self.automatic[:platform_version] = version end # Consumes the combined run_list and other attributes in +attrs+ def consume_attributes(attrs) normal_attrs_to_merge = consume_run_list(attrs) normal_attrs_to_merge = consume_chef_environment(normal_attrs_to_merge) Chef::Log.debug("Applying attributes from json file") self.normal_attrs = Chef::Mixin::DeepMerge.merge(normal_attrs, normal_attrs_to_merge) self.tags # make sure they're defined end # Lazy initializer for tags attribute def tags normal[:tags] = Array(normal[:tags]) normal[:tags] end def tag(*args) args.each do |tag| tags.push(tag.to_s) unless tags.include? tag.to_s end tags end # Extracts the run list from +attrs+ and applies it. Returns the remaining attributes def consume_run_list(attrs) attrs = attrs ? attrs.dup : {} if new_run_list = attrs.delete("recipes") || attrs.delete("run_list") if attrs.key?("recipes") || attrs.key?("run_list") raise Chef::Exceptions::AmbiguousRunlistSpecification, "please set the node's run list using the 'run_list' attribute only." end Chef::Log.info("Setting the run_list to #{new_run_list} from CLI options") run_list(new_run_list) end attrs end # chef_environment when set in -j JSON will take precedence over # -E ENVIRONMENT. Ideally, IMO, the order of precedence should be (lowest to # highest): # config_file # -j JSON # -E ENVIRONMENT # so that users could reuse their JSON and override the chef_environment # configured within it with -E ENVIRONMENT. Because command line options are # merged with Chef::Config there is currently no way to distinguish between # an environment set via config from an environment set via command line. def consume_chef_environment(attrs) attrs = attrs ? attrs.dup : {} if env = attrs.delete("chef_environment") chef_environment(env) end attrs end # Clear defaults and overrides, so that any deleted attributes # between runs are still gone. def reset_defaults_and_overrides self.default.clear self.override.clear end # Expands the node's run list and sets the default and override # attributes. Also applies stored attributes (from json provided # on the command line) # # Returns the fully-expanded list of recipes, a RunListExpansion. # #-- # TODO: timh/cw, 5-14-2010: Should this method exist? Should we # instead modify default_attrs and override_attrs whenever our # run_list is mutated? Or perhaps do something smarter like # on-demand generation of default_attrs and override_attrs, # invalidated only when run_list is mutated? def expand!(data_source = "server") expansion = run_list.expand(chef_environment, data_source) raise Chef::Exceptions::MissingRole, expansion if expansion.errors? self.tags # make sure they're defined automatic_attrs[:recipes] = expansion.recipes.with_duplicate_names automatic_attrs[:expanded_run_list] = expansion.recipes.with_fully_qualified_names_and_version_constraints automatic_attrs[:roles] = expansion.roles apply_expansion_attributes(expansion) expansion end # Apply the default and overrides attributes from the expansion # passed in, which came from roles. def apply_expansion_attributes(expansion) loaded_environment = if chef_environment == "_default" Chef::Environment.new.tap { |e| e.name("_default") } else Chef::Environment.load(chef_environment) end attributes.env_default = loaded_environment.default_attributes attributes.env_override = loaded_environment.override_attributes attribute.role_default = expansion.default_attrs attributes.role_override = expansion.override_attrs end # Transform the node to a Hash def to_hash index_hash = Hash.new index_hash["chef_type"] = "node" index_hash["name"] = name index_hash["chef_environment"] = chef_environment attribute.each do |key, value| index_hash[key] = value end index_hash["recipe"] = run_list.recipe_names if run_list.recipe_names.length > 0 index_hash["role"] = run_list.role_names if run_list.role_names.length > 0 index_hash["run_list"] = run_list.run_list_items index_hash end def display_hash display = {} display["name"] = name display["chef_environment"] = chef_environment display["automatic"] = automatic_attrs display["normal"] = normal_attrs display["default"] = attributes.combined_default display["override"] = attributes.combined_override display["run_list"] = run_list.run_list_items display end # Serialize this object as a hash def to_json(*a) Chef::JSONCompat.to_json(for_json, *a) end def for_json result = { "name" => name, "chef_environment" => chef_environment, "json_class" => self.class.name, "automatic" => attributes.automatic, "normal" => attributes.normal, "chef_type" => "node", "default" => attributes.combined_default, "override" => attributes.combined_override, #Render correctly for run_list items so malformed json does not result "run_list" => @primary_runlist.run_list.map { |item| item.to_s }, } # Chef Server rejects node JSON with extra keys; prior to 12.3, # "policy_name" and "policy_group" are unknown; after 12.3 they are # optional, therefore only including them in the JSON if present # maximizes compatibility for most people. unless policy_group.nil? && policy_name.nil? result["policy_name"] = policy_name result["policy_group"] = policy_group end result end def update_from!(o) run_list.reset!(o.run_list) self.automatic_attrs = o.automatic_attrs self.normal_attrs = o.normal_attrs self.override_attrs = o.override_attrs self.default_attrs = o.default_attrs chef_environment(o.chef_environment) self end # Create a Chef::Node from JSON def self.json_create(o) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::Node#from_hash") from_hash(o) end def self.from_hash(o) return o if o.kind_of? Chef::Node node = new node.name(o["name"]) node.chef_environment(o["chef_environment"]) if o.has_key?("attributes") node.normal_attrs = o["attributes"] end node.automatic_attrs = Mash.new(o["automatic"]) if o.has_key?("automatic") node.normal_attrs = Mash.new(o["normal"]) if o.has_key?("normal") node.default_attrs = Mash.new(o["default"]) if o.has_key?("default") node.override_attrs = Mash.new(o["override"]) if o.has_key?("override") if o.has_key?("run_list") node.run_list.reset!(o["run_list"]) elsif o.has_key?("recipes") o["recipes"].each { |r| node.recipes << r } end node.policy_name = o["policy_name"] if o.has_key?("policy_name") node.policy_group = o["policy_group"] if o.has_key?("policy_group") node end def self.list_by_environment(environment, inflate = false) if inflate response = Hash.new Chef::Search::Query.new.search(:node, "chef_environment:#{environment}") { |n| response[n.name] = n unless n.nil? } response else Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("environments/#{environment}/nodes") end end def self.list(inflate = false) if inflate response = Hash.new Chef::Search::Query.new.search(:node) do |n| n = Chef::Node.from_hash(n) response[n.name] = n unless n.nil? end response else Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("nodes") end end def self.find_or_create(node_name) load(node_name) rescue Net::HTTPServerException => e raise unless e.response.code == "404" node = build(node_name) node.create end def self.build(node_name) node = new node.name(node_name) node.chef_environment(Chef::Config[:environment]) unless Chef::Config[:environment].nil? || Chef::Config[:environment].chomp.empty? node end # Load a node by name def self.load(name) from_hash(Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("nodes/#{name}")) end # Remove this node via the REST API def destroy chef_server_rest.delete("nodes/#{name}") end # Save this node via the REST API def save # Try PUT. If the node doesn't yet exist, PUT will return 404, # so then POST to create. begin if Chef::Config[:why_run] Chef::Log.warn("In why-run mode, so NOT performing node save.") else chef_server_rest.put("nodes/#{name}", data_for_save) end rescue Net::HTTPServerException => e if e.response.code == "404" chef_server_rest.post("nodes", data_for_save) # Chef Server before 12.3 rejects node JSON with 'policy_name' or # 'policy_group' keys, but 'policy_name' will be detected first. # Backcompat can be removed in 13.0 elsif e.response.code == "400" && e.response.body.include?("Invalid key policy_name") save_without_policyfile_attrs else raise end end self end # Create the node via the REST API def create chef_server_rest.post("nodes", data_for_save) self rescue Net::HTTPServerException => e # Chef Server before 12.3 rejects node JSON with 'policy_name' or # 'policy_group' keys, but 'policy_name' will be detected first. # Backcompat can be removed in 13.0 if e.response.code == "400" && e.response.body.include?("Invalid key policy_name") chef_server_rest.post("nodes", data_for_save_without_policyfile_attrs) else raise end end def to_s "node[#{name}]" end def ==(other) if other.kind_of?(self.class) self.name == other.name else false end end def <=>(other) self.name <=> other.name end private def save_without_policyfile_attrs trimmed_data = data_for_save_without_policyfile_attrs chef_server_rest.put("nodes/#{name}", trimmed_data) rescue Net::HTTPServerException => e raise e unless e.response.code == "404" chef_server_rest.post("nodes", trimmed_data) end def data_for_save_without_policyfile_attrs data_for_save.tap do |trimmed_data| trimmed_data.delete("policy_name") trimmed_data.delete("policy_group") end end def data_for_save data = for_json %w{automatic default normal override}.each do |level| whitelist_config_option = "#{level}_attribute_whitelist".to_sym whitelist = Chef::Config[whitelist_config_option] unless whitelist.nil? # nil => save everything Chef::Log.info("Whitelisting #{level} node attributes for save.") data[level] = Chef::Whitelist.filter(data[level], whitelist) end end data end end end chef-12.14.60/lib/chef/node/000077500000000000000000000000001276456504500152705ustar00rootroot00000000000000chef-12.14.60/lib/chef/node/attribute.rb000066400000000000000000000474221276456504500176310ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: AJ Christensen () # 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/node/immutable_collections" require "chef/node/attribute_collections" require "chef/decorator/unchain" require "chef/mixin/deep_merge" require "chef/log" class Chef class Node # == Attribute # Attribute implements a nested key-value (Hash) and flat collection # (Array) data structure supporting multiple levels of precedence, such # that a given key may have multiple values internally, but will only # return the highest precedence value when reading. class Attribute < Mash include Immutablize include Enumerable # List of the component attribute hashes, in order of precedence, low to # high. COMPONENTS = [ :@default, :@env_default, :@role_default, :@force_default, :@normal, :@override, :@role_override, :@env_override, :@force_override, :@automatic, ].freeze DEFAULT_COMPONENTS = [ :@default, :@env_default, :@role_default, :@force_default, ] OVERRIDE_COMPONENTS = [ :@override, :@role_override, :@env_override, :@force_override, ] [:all?, :any?, :assoc, :chunk, :collect, :collect_concat, :compare_by_identity, :compare_by_identity?, :count, :cycle, :detect, :drop, :drop_while, :each, :each_cons, :each_entry, :each_key, :each_pair, :each_slice, :each_value, :each_with_index, :each_with_object, :empty?, :entries, :except, :fetch, :find, :find_all, :find_index, :first, :flat_map, :flatten, :grep, :group_by, :has_value?, :include?, :index, :inject, :invert, :key, :keys, :length, :map, :max, :max_by, :merge, :min, :min_by, :minmax, :minmax_by, :none?, :one?, :partition, :rassoc, :reduce, :reject, :reverse_each, :select, :size, :slice_before, :sort, :sort_by, :store, :symbolize_keys, :take, :take_while, :to_a, :to_h, :to_hash, :to_set, :value?, :values, :values_at, :zip].each do |delegated_method| define_method(delegated_method) do |*args, &block| merged_attributes.send(delegated_method, *args, &block) end end # return the cookbook level default attribute component attr_reader :default # return the role level default attribute component attr_reader :role_default # return the environment level default attribute component attr_reader :env_default # return the force_default level attribute component attr_reader :force_default # return the "normal" level attribute component attr_reader :normal # return the cookbook level override attribute component attr_reader :override # return the role level override attribute component attr_reader :role_override # return the enviroment level override attribute component attr_reader :env_override # return the force override level attribute component attr_reader :force_override # return the automatic level attribute component attr_reader :automatic # This is used to track the top level key as we descend through method chaining into # a precedence level (e.g. node.default['foo']['bar']['baz']= results in 'foo' here). We # need this so that when we hit the end of a method chain which results in a mutator method # that we can invalidate the whole top-level deep merge cache for the top-level key. It is # the responsibility of the accessor on the Chef::Node object to reset this to nil, and then # the first VividMash#[] call can ||= and set this to the first key we encounter. attr_accessor :top_level_breadcrumb # Cache of deep merged values by top-level key. This is a simple hash which has keys that are the # top-level keys of the node object, and we save the computed deep-merge for that key here. There is # no cache of subtrees. attr_accessor :deep_merge_cache def initialize(normal, default, override, automatic) @default = VividMash.new(self, default) @env_default = VividMash.new(self, {}) @role_default = VividMash.new(self, {}) @force_default = VividMash.new(self, {}) @normal = VividMash.new(self, normal) @override = VividMash.new(self, override) @role_override = VividMash.new(self, {}) @env_override = VividMash.new(self, {}) @force_override = VividMash.new(self, {}) @automatic = VividMash.new(self, automatic) @merged_attributes = nil @combined_override = nil @combined_default = nil @top_level_breadcrumb = nil @deep_merge_cache = {} end # Debug what's going on with an attribute. +args+ is a path spec to the # attribute you're interested in. For example, to debug where the value # of `node[:network][:default_interface]` is coming from, use: # debug_value(:network, :default_interface). # The return value is an Array of Arrays. The Arrays # are pairs of `["precedence_level", value]`, where precedence level is # the component, such as role default, normal, etc. and value is the # attribute value set at that precedence level. If there is no value at # that precedence level, +value+ will be the symbol +:not_present+. def debug_value(*args) COMPONENTS.map do |component| ivar = instance_variable_get(component) value = args.inject(ivar) do |so_far, key| if so_far == :not_present :not_present elsif so_far.has_key?(key) so_far[key] else :not_present end end [component.to_s.sub(/^@/, ""), value] end end # Invalidate a key in the deep_merge_cache. If called with nil, or no arg, this will invalidate # the entire deep_merge cache. In the case of the user doing node.default['foo']['bar']['baz']= # that eventually results in a call to reset_cache('foo') here. A node.default=hash_thing call # must invalidate the entire cache and re-deep-merge the entire node object. def reset_cache(path = nil) if path.nil? @deep_merge_cache = {} else deep_merge_cache.delete(path.to_s) end end alias :reset :reset_cache # Set the cookbook level default attribute component to +new_data+. def default=(new_data) reset @default = VividMash.new(self, new_data) end # Set the role level default attribute component to +new_data+ def role_default=(new_data) reset @role_default = VividMash.new(self, new_data) end # Set the environment level default attribute component to +new_data+ def env_default=(new_data) reset @env_default = VividMash.new(self, new_data) end # Set the force_default (+default!+) level attributes to +new_data+ def force_default=(new_data) reset @force_default = VividMash.new(self, new_data) end # Set the normal level attribute component to +new_data+ def normal=(new_data) reset @normal = VividMash.new(self, new_data) end # Set the cookbook level override attribute component to +new_data+ def override=(new_data) reset @override = VividMash.new(self, new_data) end # Set the role level override attribute component to +new_data+ def role_override=(new_data) reset @role_override = VividMash.new(self, new_data) end # Set the environment level override attribute component to +new_data+ def env_override=(new_data) reset @env_override = VividMash.new(self, new_data) end def force_override=(new_data) reset @force_override = VividMash.new(self, new_data) end def automatic=(new_data) reset @automatic = VividMash.new(self, new_data) end # # Deleting attributes # # clears attributes from all precedence levels def rm(*args) with_deep_merged_return_value(self, *args) do rm_default(*args) rm_normal(*args) rm_override(*args) end end # clears attributes from all default precedence levels # # similar to: force_default!['foo']['bar'].delete('baz') # - does not autovivify # - does not trainwreck if interior keys do not exist def rm_default(*args) with_deep_merged_return_value(combined_default, *args) do default.unlink(*args) role_default.unlink(*args) env_default.unlink(*args) force_default.unlink(*args) end end # clears attributes from normal precedence # # equivalent to: normal!['foo']['bar'].delete('baz') # - does not autovivify # - does not trainwreck if interior keys do not exist def rm_normal(*args) normal.unlink(*args) end # clears attributes from all override precedence levels # # equivalent to: force_override!['foo']['bar'].delete('baz') # - does not autovivify # - does not trainwreck if interior keys do not exist def rm_override(*args) with_deep_merged_return_value(combined_override, *args) do override.unlink(*args) role_override.unlink(*args) env_override.unlink(*args) force_override.unlink(*args) end end def with_deep_merged_return_value(obj, *path, last) hash = obj.read(*path) return nil unless hash.is_a?(Hash) ret = hash[last] yield ret end private :with_deep_merged_return_value # # Replacing attributes without merging # # sets default attributes without merging # # - this API autovivifies (and cannot trainwreck) def default!(*args) return Decorator::Unchain.new(self, :default!) unless args.length > 0 write(:default, *args) end # sets normal attributes without merging # # - this API autovivifies (and cannot trainwreck) def normal!(*args) return Decorator::Unchain.new(self, :normal!) unless args.length > 0 write(:normal, *args) end # sets override attributes without merging # # - this API autovivifies (and cannot trainwreck) def override!(*args) return Decorator::Unchain.new(self, :override!) unless args.length > 0 write(:override, *args) end # clears from all default precedence levels and then sets force_default # # - this API autovivifies (and cannot trainwreck) def force_default!(*args) return Decorator::Unchain.new(self, :force_default!) unless args.length > 0 value = args.pop rm_default(*args) write(:force_default, *args, value) end # clears from all override precedence levels and then sets force_override def force_override!(*args) return Decorator::Unchain.new(self, :force_override!) unless args.length > 0 value = args.pop rm_override(*args) write(:force_override, *args, value) end # method-style access to attributes def read(*path) merged_attributes.read(*path) end def read!(*path) merged_attributes.read!(*path) end def exist?(*path) merged_attributes.exist?(*path) end def write(level, *args, &block) self.send(level).write(*args, &block) end def write!(level, *args, &block) self.send(level).write!(*args, &block) end def unlink(level, *path) self.send(level).unlink(*path) end def unlink!(level, *path) self.send(level).unlink!(*path) end # # Accessing merged attributes. # # Note that merged_attributes('foo', 'bar', 'baz') can be called to compute only the # deep merge of node['foo']['bar']['baz'], but in practice we currently always compute # all of node['foo'] even if the user only requires node['foo']['bar']['baz']. # def merged_attributes(*path) # immutablize( merge_all(path) # ) end def combined_override(*path) immutablize(merge_overrides(path)) end def combined_default(*path) immutablize(merge_defaults(path)) end def normal_unless(*args) return Decorator::Unchain.new(self, :normal_unless) unless args.length > 0 write(:normal, *args) if read(*args[0...-1]).nil? end def default_unless(*args) return Decorator::Unchain.new(self, :default_unless) unless args.length > 0 write(:default, *args) if read(*args[0...-1]).nil? end def override_unless(*args) return Decorator::Unchain.new(self, :override_unless) unless args.length > 0 write(:override, *args) if read(*args[0...-1]).nil? end def set_unless(*args) Chef.log_deprecation("node.set_unless is deprecated and will be removed in Chef 14, please use node.default_unless/node.override_unless (or node.normal_unless if you really need persistence)") return Decorator::Unchain.new(self, :default_unless) unless args.length > 0 write(:normal, *args) if read(*args[0...-1]).nil? end def [](key) if deep_merge_cache.has_key?(key.to_s) # return the cache of the deep merged values by top-level key deep_merge_cache[key.to_s] else # save all the work of computing node[key] deep_merge_cache[key.to_s] = merged_attributes(key) end end def []=(key, value) raise Exceptions::ImmutableAttributeModification end def has_key?(key) COMPONENTS.any? do |component_ivar| instance_variable_get(component_ivar).has_key?(key) end end alias :attribute? :has_key? alias :member? :has_key? alias :include? :has_key? alias :key? :has_key? alias :each_attribute :each def method_missing(symbol, *args) if symbol == :to_ary merged_attributes.send(symbol, *args) elsif args.empty? Chef.log_deprecation %q{method access to node attributes (node.foo.bar) is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]["bar"])} if key?(symbol) self[symbol] else raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'" end elsif symbol.to_s =~ /=$/ Chef.log_deprecation %q{method setting of node attributes (node.foo="bar") is deprecated and will be removed in Chef 13, please use bracket syntax (node["foo"]="bar")} key_to_set = symbol.to_s[/^(.+)=$/, 1] self[key_to_set] = (args.length == 1 ? args[0] : args) else raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'" end end def to_s merged_attributes.to_s end def inspect "#<#{self.class} " << (COMPONENTS + [:@merged_attributes, :@properties]).map do |iv| "#{iv}=#{instance_variable_get(iv).inspect}" end.join(", ") << ">" end private # Helper method for merge_all/merge_defaults/merge_overrides. # # apply_path(thing, [ "foo", "bar", "baz" ]) = thing["foo"]["bar"]["baz"] # # The path value can be nil in which case the whole component is returned. # # It returns nil (does not raise an exception) if it walks off the end of an Mash/Hash/Array, it does not # raise any TypeError if it attempts to apply a hash key to an Integer/String/TrueClass, and just returns # nil in that case. # def apply_path(component, path) path ||= [] path.inject(component) do |val, path_arg| if val.respond_to?(:[]) # Have an Array-like or Hash-like thing if !val.respond_to?(:has_key?) # Have an Array-like thing val[path_arg] elsif val.has_key?(path_arg) # Hash-like thing (must check has_key? first to protect against Autovivification) val[path_arg] else nil end else nil end end end # For elements like Fixnums, true, nil... def safe_dup(e) e.dup rescue TypeError e end # Deep merge all attribute levels using hash-only merging between different precidence # levels (so override arrays completely replace arrays set at any default level). # # The path allows for selectively deep-merging a subtree of the node object. # # @param path [Array] Array of args to method chain to descend into the node object # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object def merge_all(path) components = [ merge_defaults(path), apply_path(@normal, path), merge_overrides(path), apply_path(@automatic, path), ] components.map! do |component| safe_dup(component) end return nil if components.compact.empty? components.inject(ImmutableMash.new({})) do |merged, component| Chef::Mixin::DeepMerge.hash_only_merge!(merged, component) end end # Deep merge the default attribute levels with array merging. # # The path allows for selectively deep-merging a subtree of the node object. # # @param path [Array] Array of args to method chain to descend into the node object # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object def merge_defaults(path) DEFAULT_COMPONENTS.inject(nil) do |merged, component_ivar| component_value = apply_path(instance_variable_get(component_ivar), path) Chef::Mixin::DeepMerge.deep_merge(component_value, merged) end end # Deep merge the override attribute levels with array merging. # # The path allows for selectively deep-merging a subtree of the node object. # # @param path [Array] Array of args to method chain to descend into the node object # @return [attr] Deep Merged values (may be VividMash, Hash, Array, etc) from the node object def merge_overrides(path) OVERRIDE_COMPONENTS.inject(nil) do |merged, component_ivar| component_value = apply_path(instance_variable_get(component_ivar), path) Chef::Mixin::DeepMerge.deep_merge(component_value, merged) end end end end end chef-12.14.60/lib/chef/node/attribute_collections.rb000066400000000000000000000130331276456504500222160ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # 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 "chef/node/common_api" class Chef class Node # == AttrArray # AttrArray is identical to Array, except that it keeps a reference to the # "root" (Chef::Node::Attribute) object, and will trigger a cache # invalidation on that object when mutated. class AttrArray < Array MUTATOR_METHODS = [ :<<, :[]=, :clear, :collect!, :compact!, :default=, :default_proc=, :delete, :delete_at, :delete_if, :fill, :flatten!, :insert, :keep_if, :map!, :merge!, :pop, :push, :update, :reject!, :reverse!, :replace, :select!, :shift, :slice!, :sort!, :sort_by!, :uniq!, :unshift, ] # For all of the methods that may mutate an Array, we override them to # also invalidate the cached merged_attributes on the root # Node::Attribute object. MUTATOR_METHODS.each do |mutator| define_method(mutator) do |*args, &block| ret = super(*args, &block) root.reset_cache(root.top_level_breadcrumb) ret end end attr_reader :root def initialize(root, data) @root = root super(data) end # For elements like Fixnums, true, nil... def safe_dup(e) e.dup rescue TypeError e end def dup Array.new(map { |e| safe_dup(e) }) end end # == VividMash # VividMash is identical to a Mash, with a few exceptions: # * It has a reference to the root Chef::Node::Attribute to which it # belongs, and will trigger cache invalidation on that object when # mutated. # * It auto-vivifies, that is a reference to a missing element will result # in the creation of a new VividMash for that key. (This only works when # using the element reference method, `[]` -- other methods, such as # #fetch, work as normal). # * attr_accessor style element set and get are supported via method_missing class VividMash < Mash attr_reader :root include CommonAPI # Methods that mutate a VividMash. Each of them is overridden so that it # also invalidates the cached merged_attributes on the root Attribute # object. MUTATOR_METHODS = [ :clear, :delete, :delete_if, :keep_if, :merge!, :update, :reject!, :replace, :select!, :shift, ] # For all of the mutating methods on Mash, override them so that they # also invalidate the cached `merged_attributes` on the root Attribute # object. MUTATOR_METHODS.each do |mutator| define_method(mutator) do |*args, &block| root.reset_cache(root.top_level_breadcrumb) super(*args, &block) end end def initialize(root, data = {}) @root = root super(data) end def [](key) root.top_level_breadcrumb ||= key value = super if !key?(key) value = self.class.new(root) self[key] = value else value end end def []=(key, value) root.top_level_breadcrumb ||= key ret = super root.reset_cache(root.top_level_breadcrumb) ret end alias :attribute? :has_key? def method_missing(symbol, *args) # Calling `puts arg` implicitly calls #to_ary on `arg`. If `arg` does # not implement #to_ary, ruby recognizes it as a single argument, and # if it returns an Array, then ruby prints each element. If we don't # account for that here, we'll auto-vivify a VividMash for the key # :to_ary which creates an unwanted key and raises a TypeError. if symbol == :to_ary super elsif args.empty? self[symbol] elsif symbol.to_s =~ /=$/ key_to_set = symbol.to_s[/^(.+)=$/, 1] self[key_to_set] = (args.length == 1 ? args[0] : args) else raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'. To set an attribute, use `#{symbol}=value' instead." end end def convert_key(key) super end # Mash uses #convert_value to mashify values on input. # We override it here to convert hash or array values to VividMash or # AttrArray for consistency and to ensure that the added parts of the # attribute tree will have the correct cache invalidation behavior. def convert_value(value) case value when VividMash value when Hash VividMash.new(root, value) when Array AttrArray.new(root, value) else value end end def dup Mash.new(self) end end end end chef-12.14.60/lib/chef/node/common_api.rb000066400000000000000000000102721276456504500177400ustar00rootroot00000000000000#-- # 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 Node # shared API between VividMash and ImmutableMash, writer code can be # 'shared' to keep it logically in this file by adding them to the # block list in ImmutableMash. module CommonAPI # method-style access to attributes def valid_container?(obj, key) return obj.is_a?(Hash) || (obj.is_a?(Array) && key.is_a?(Fixnum)) end private :valid_container? # - autovivifying / autoreplacing writer # - non-container-ey intermediate objects are replaced with hashes def write(*args, &block) root.top_level_breadcrumb = nil if respond_to?(:root) value = block_given? ? yield : args.pop last = args.pop prev_memo = prev_key = nil chain = args.inject(self) do |memo, key| if !valid_container?(memo, key) prev_memo[prev_key] = {} memo = prev_memo[prev_key] end prev_memo = memo prev_key = key memo[key] end if !valid_container?(chain, last) prev_memo[prev_key] = {} chain = prev_memo[prev_key] end chain[last] = value end # this autovivifies, but can throw NoSuchAttribute when trying to access #[] on # something that is not a container ("schema violation" issues). # def write!(*args, &block) root.top_level_breadcrumb = nil if respond_to?(:root) value = block_given? ? yield : args.pop last = args.pop obj = args.inject(self) do |memo, key| raise Chef::Exceptions::AttributeTypeMismatch unless valid_container?(memo, key) memo[key] end raise Chef::Exceptions::AttributeTypeMismatch unless valid_container?(obj, last) obj[last] = value end # FIXME:(?) does anyone need a non-autovivifying writer for attributes that throws exceptions? # return true or false based on if the attribute exists def exist?(*path) root.top_level_breadcrumb = nil if respond_to?(:root) path.inject(self) do |memo, key| return false unless valid_container?(memo, key) if memo.is_a?(Hash) if memo.key?(key) memo[key] else return false end elsif memo.is_a?(Array) if memo.length > key memo[key] else return false end end end return true end # this is a safe non-autovivifying reader that returns nil if the attribute does not exist def read(*path) begin read!(*path) rescue Chef::Exceptions::NoSuchAttribute nil end end # non-autovivifying reader that throws an exception if the attribute does not exist def read!(*path) raise Chef::Exceptions::NoSuchAttribute unless exist?(*path) root.top_level_breadcrumb = nil if respond_to?(:root) path.inject(self) do |memo, key| memo[key] end end # FIXME:(?) does anyone really like the autovivifying reader that we have and wants the same behavior? readers that write? ugh... def unlink(*path, last) root.top_level_breadcrumb = nil if respond_to?(:root) hash = path.empty? ? self : read(*path) return nil unless hash.is_a?(Hash) || hash.is_a?(Array) root.top_level_breadcrumb ||= last hash.delete(last) end def unlink!(*path) raise Chef::Exceptions::NoSuchAttribute unless exist?(*path) unlink(*path) end end end end chef-12.14.60/lib/chef/node/immutable_collections.rb000066400000000000000000000146241276456504500222010ustar00rootroot00000000000000#-- # 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 "chef/node/common_api" class Chef class Node module Immutablize def immutablize(value) case value when Hash ImmutableMash.new(value) when Array ImmutableArray.new(value) else value end end end # == ImmutableArray # ImmutableArray is used to implement Array collections when reading node # attributes. # # ImmutableArray acts like an ordinary Array, except: # * Methods that mutate the array are overridden to raise an error, making # the collection more or less immutable. # * Since this class stores values computed from a parent # Chef::Node::Attribute's values, it overrides all reader methods to # detect staleness and raise an error if accessed when stale. class ImmutableArray < Array include Immutablize alias :internal_push :<< private :internal_push # A list of methods that mutate Array. Each of these is overridden to # raise an error, making this instances of this class more or less # immutable. DISALLOWED_MUTATOR_METHODS = [ :<<, :[]=, :clear, :collect!, :compact!, :default=, :default_proc=, :delete, :delete_at, :delete_if, :fill, :flatten!, :insert, :keep_if, :map!, :merge!, :pop, :push, :update, :reject!, :reverse!, :replace, :select!, :shift, :slice!, :sort!, :sort_by!, :uniq!, :unshift, ] def initialize(array_data) array_data.each do |value| internal_push(immutablize(value)) end end # Redefine all of the methods that mutate a Hash to raise an error when called. # This is the magic that makes this object "Immutable" DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name| define_method(mutator_method_name) do |*args, &block| raise Exceptions::ImmutableAttributeModification end end # For elements like Fixnums, true, nil... def safe_dup(e) e.dup rescue TypeError e end def dup Array.new(map { |e| safe_dup(e) }) end def to_a a = Array.new each do |v| a << case v when ImmutableArray v.to_a when ImmutableMash v.to_hash else v end end a end end # == ImmutableMash # ImmutableMash implements Hash/Dict behavior for reading values from node # attributes. # # ImmutableMash acts like a Mash (Hash that is indifferent to String or # Symbol keys), with some important exceptions: # * Methods that mutate state are overridden to raise an error instead. # * Methods that read from the collection are overriden so that they check # if the Chef::Node::Attribute has been modified since an instance of # this class was generated. An error is raised if the object detects that # it is stale. # * Values can be accessed in attr_reader-like fashion via method_missing. class ImmutableMash < Mash include Immutablize include CommonAPI alias :internal_set :[]= private :internal_set DISALLOWED_MUTATOR_METHODS = [ :[]=, :clear, :collect!, :default=, :default_proc=, :delete, :delete_if, :keep_if, :map!, :merge!, :update, :reject!, :replace, :select!, :shift, :write, :write!, :unlink, :unlink!, ] def initialize(mash_data) mash_data.each do |key, value| internal_set(key, immutablize(value)) end end def public_method_that_only_deep_merge_should_use(key, value) internal_set(key, immutablize(value)) end alias :attribute? :has_key? # Redefine all of the methods that mutate a Hash to raise an error when called. # This is the magic that makes this object "Immutable" DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name| define_method(mutator_method_name) do |*args, &block| raise Exceptions::ImmutableAttributeModification end end def method_missing(symbol, *args) if symbol == :to_ary super elsif args.empty? if key?(symbol) self[symbol] else raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'" end # This will raise a ImmutableAttributeModification error: elsif symbol.to_s =~ /=$/ key_to_set = symbol.to_s[/^(.+)=$/, 1] self[key_to_set] = (args.length == 1 ? args[0] : args) else raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'" end end # Mash uses #convert_value to mashify values on input. # Since we're handling this ourselves, override it to be a no-op def convert_value(value) value end # NOTE: #default and #default= are likely to be pretty confusing. For a # regular ruby Hash, they control what value is returned for, e.g., # hash[:no_such_key] #=> hash.default # Of course, 'default' has a specific meaning in Chef-land def dup Mash.new(self) end def to_hash h = Hash.new each_pair do |k, v| h[k] = case v when ImmutableMash v.to_hash when ImmutableArray v.to_a else v end end h end end end end chef-12.14.60/lib/chef/node_map.rb000066400000000000000000000200151276456504500164500ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class NodeMap # # Set a key/value pair on the map with a filter. The filter must be true # when applied to the node in order to retrieve the value. # # @param key [Object] Key to store # @param value [Object] Value associated with the key # @param filters [Hash] Node filter options to apply to key retrieval # # @yield [node] Arbitrary node filter as a block which takes a node argument # # @return [NodeMap] Returns self for possible chaining # def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, override: nil, &block) Chef.log_deprecation("The on_platform option to node_map has been deprecated") if on_platform Chef.log_deprecation("The on_platforms option to node_map has been deprecated") if on_platforms platform ||= on_platform || on_platforms filters = {} filters[:platform] = platform if platform filters[:platform_version] = platform_version if platform_version filters[:platform_family] = platform_family if platform_family filters[:os] = os if os new_matcher = { value: value, filters: filters } new_matcher[:block] = block if block new_matcher[:canonical] = canonical if canonical new_matcher[:override] = override if override # The map is sorted in order of preference already; we just need to find # our place in it (just before the first value with the same preference level). insert_at = nil map[key] ||= [] map[key].each_with_index do |matcher, index| cmp = compare_matchers(key, new_matcher, matcher) insert_at ||= index if cmp && cmp <= 0 end if insert_at map[key].insert(insert_at, new_matcher) else map[key] << new_matcher end map end # # Get a value from the NodeMap via applying the node to the filters that # were set on the key. # # @param node [Chef::Node] The Chef::Node object for the run, or `nil` to # ignore all filters. # @param key [Object] Key to look up # @param canonical [Boolean] `true` or `false` to match canonical or # non-canonical values only. `nil` to ignore canonicality. Default: `nil` # # @return [Object] Value # def get(node, key, canonical: nil) raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil? list(node, key, canonical: canonical).first end # # List all matches for the given node and key from the NodeMap, from # most-recently added to oldest. # # @param node [Chef::Node] The Chef::Node object for the run, or `nil` to # ignore all filters. # @param key [Object] Key to look up # @param canonical [Boolean] `true` or `false` to match canonical or # non-canonical values only. `nil` to ignore canonicality. Default: `nil` # # @return [Object] Value # def list(node, key, canonical: nil) raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil? return [] unless map.has_key?(key) map[key].select do |matcher| node_matches?(node, matcher) && canonical_matches?(canonical, matcher) end.map { |matcher| matcher[:value] } end # Seriously, don't use this, it's nearly certain to change on you # @return remaining # @api private def delete_canonical(key, value) remaining = map[key] if remaining remaining.delete_if { |matcher| matcher[:canonical] && Array(matcher[:value]) == Array(value) } if remaining.empty? map.delete(key) remaining = nil end end remaining end protected # # Succeeds if: # - no negative matches (!value) # - at least one positive match (value or :all), or no positive filters # def matches_black_white_list?(node, filters, attribute) # It's super common for the filter to be nil. Catch that so we don't # spend any time here. return true if !filters[attribute] filter_values = Array(filters[attribute]) value = node[attribute] # Split the blacklist and whitelist blacklist, whitelist = filter_values.partition { |v| v.is_a?(String) && v.start_with?("!") } # If any blacklist value matches, we don't match return false if blacklist.any? { |v| v[1..-1] == value } # If the whitelist is empty, or anything matches, we match. whitelist.empty? || whitelist.any? { |v| v == :all || v == value } end def matches_version_list?(node, filters, attribute) # It's super common for the filter to be nil. Catch that so we don't # spend any time here. return true if !filters[attribute] filter_values = Array(filters[attribute]) value = node[attribute] filter_values.empty? || Array(filter_values).any? do |v| Chef::VersionConstraint::Platform.new(v).include?(value) end end def filters_match?(node, filters) matches_black_white_list?(node, filters, :os) && matches_black_white_list?(node, filters, :platform_family) && matches_black_white_list?(node, filters, :platform) && matches_version_list?(node, filters, :platform_version) end def block_matches?(node, block) return true if block.nil? block.call node end def node_matches?(node, matcher) return true if !node filters_match?(node, matcher[:filters]) && block_matches?(node, matcher[:block]) end def canonical_matches?(canonical, matcher) return true if canonical.nil? !!canonical == !!matcher[:canonical] end def compare_matchers(key, new_matcher, matcher) cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:block] } return cmp if cmp != 0 cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] } return cmp if cmp != 0 cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform] } return cmp if cmp != 0 cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_family] } return cmp if cmp != 0 cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:os] } return cmp if cmp != 0 cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:override] } return cmp if cmp != 0 # If all things are identical, return 0 0 end def compare_matcher_properties(new_matcher, matcher) a = yield(new_matcher) b = yield(matcher) # Check for blcacklists ('!windows'). Those always come *after* positive # whitelists. a_negated = Array(a).any? { |f| f.is_a?(String) && f.start_with?("!") } b_negated = Array(b).any? { |f| f.is_a?(String) && f.start_with?("!") } if a_negated != b_negated return 1 if a_negated return -1 if b_negated end # We treat false / true and nil / not-nil with the same comparison a = nil if a == false b = nil if b == false cmp = a <=> b # This is the case where one is non-nil, and one is nil. The one that is # nil is "greater" (i.e. it should come last). if cmp.nil? return 1 if a.nil? return -1 if b.nil? end cmp end def map @map ||= {} end end end chef-12.14.60/lib/chef/null_logger.rb000066400000000000000000000032471276456504500172070ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class Chef # Null logger implementation that just ignores everything. This is used by # classes that are intended to be reused outside of Chef, but need to offer # logging functionality when used by other Chef code. # # It does not define the full interface provided by Logger, just enough to be # a reasonable duck type. In particular, methods setting the log level, log # device, etc., are not implemented because any code calling those methods # probably expected a real logger and not this "fake" one. class NullLogger def fatal(message, &block) end def error(message, &block) end def warn(message, &block) end def info(message, &block) end def debug(message, &block) end def add(severity, message = nil, progname = nil) end def <<(message) end def fatal? false end def error? false end def warn? false end def info? false end def debug? false end end end chef-12.14.60/lib/chef/org.rb000066400000000000000000000100141276456504500154530ustar00rootroot00000000000000# # Author:: Steven Danna (steve@chef.io) # Copyright:: Copyright 2014-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/json_compat" require "chef/mixin/params_validate" require "chef/server_api" class Chef class Org include Chef::Mixin::ParamsValidate def initialize(name) @name = name @full_name = "" # The Chef API returns the private key of the validator # client on create @private_key = nil @guid = nil end def chef_rest @chef_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root]) end def name(arg = nil) set_or_return(:name, arg, :regex => /^[a-z0-9\-_]+$/) end def full_name(arg = nil) set_or_return(:full_name, arg, :kind_of => String) end def private_key(arg = nil) set_or_return(:private_key, arg, :kind_of => String) end def guid(arg = nil) set_or_return(:guid, arg, :kind_of => String) end def to_hash result = { "name" => @name, "full_name" => @full_name, } result["private_key"] = @private_key if @private_key result["guid"] = @guid if @guid result end def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def create payload = { :name => self.name, :full_name => self.full_name } new_org = chef_rest.post("organizations", payload) Chef::Org.from_hash(self.to_hash.merge(new_org)) end def update payload = { :name => self.name, :full_name => self.full_name } new_org = chef_rest.put("organizations/#{name}", payload) Chef::Org.from_hash(self.to_hash.merge(new_org)) end def destroy chef_rest.delete("organizations/#{@name}") end def save begin create rescue Net::HTTPServerException => e if e.response.code == "409" update else raise e end end end def associate_user(username) request_body = { :user => username } response = chef_rest.post "organizations/#{@name}/association_requests", request_body association_id = response["uri"].split("/").last chef_rest.put "users/#{username}/association_requests/#{association_id}", { :response => "accept" } end def dissociate_user(username) chef_rest.delete "organizations/#{name}/users/#{username}" end # Class methods def self.from_hash(org_hash) org = Chef::Org.new(org_hash["name"]) org.full_name org_hash["full_name"] org.private_key org_hash["private_key"] if org_hash.key?("private_key") org.guid org_hash["guid"] if org_hash.key?("guid") org end def self.from_json(json) Chef::Org.from_hash(Chef::JSONCompat.from_json(json)) end def self.json_create(json) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::Org#from_json or Chef::Org#load.") Chef::Org.from_json(json) end def self.load(org_name) response = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("organizations/#{org_name}") Chef::Org.from_hash(response) end def self.list(inflate = false) orgs = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("organizations") if inflate orgs.inject({}) do |org_map, (name, _url)| org_map[name] = Chef::Org.load(name) org_map end else orgs end end end end chef-12.14.60/lib/chef/platform.rb000066400000000000000000000017501276456504500165170ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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. # # Order of these headers is important: query helpers is needed by many things require "chef/platform/query_helpers" require "chef/platform/provider_mapping" class Chef class Platform # Functionality for this class is defined in chef/platform/provider_mapping # and chef/platform/query_helpers end end chef-12.14.60/lib/chef/platform/000077500000000000000000000000001276456504500161675ustar00rootroot00000000000000chef-12.14.60/lib/chef/platform/handler_map.rb000066400000000000000000000024271276456504500207730ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/node_map" class Chef class Platform class HandlerMap < Chef::NodeMap # # "provides" lines with identical filters sort by class name (ascending). # def compare_matchers(key, new_matcher, matcher) cmp = super if cmp == 0 # Sort by class name (ascending) as well, if all other properties # are exactly equal if new_matcher[:value].is_a?(Class) && !new_matcher[:override] cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:value].name } end end cmp end end end end chef-12.14.60/lib/chef/platform/priority_map.rb000066400000000000000000000023461276456504500212370ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/node_map" class Chef class Platform class PriorityMap < Chef::NodeMap def priority(resource_name, priority_array, *filter) set_priority_array(resource_name.to_sym, priority_array, *filter) end # @api private def get_priority_array(node, key) get(node, key) end # @api private def set_priority_array(key, priority_array, *filter, &block) priority_array = Array(priority_array) set(key, priority_array, *filter, &block) priority_array end end end end chef-12.14.60/lib/chef/platform/provider_handler_map.rb000066400000000000000000000015751276456504500227100ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "singleton" require "chef/platform/handler_map" class Chef class Platform # @api private class ProviderHandlerMap < Chef::Platform::HandlerMap include Singleton end end end chef-12.14.60/lib/chef/platform/provider_mapping.rb000066400000000000000000000165041276456504500220670ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/exceptions" require "chef/mixin/params_validate" require "chef/version_constraint/platform" require "chef/provider" class Chef class Platform class << self attr_writer :platforms def platforms @platforms ||= { default: {} } end include Chef::Mixin::ParamsValidate def find(name, version) provider_map = platforms[:default].clone name_sym = name if name.kind_of?(String) name = name.downcase name.gsub!(/\s/, "_") name_sym = name.to_sym end if platforms.has_key?(name_sym) platform_versions = platforms[name_sym].select { |k, v| k != :default } if platforms[name_sym].has_key?(:default) provider_map.merge!(platforms[name_sym][:default]) end platform_versions.each do |platform_version, provider| begin version_constraint = Chef::VersionConstraint::Platform.new(platform_version) if version_constraint.include?(version) Chef::Log.debug("Platform #{name} version #{version} found") provider_map.merge!(provider) end rescue Chef::Exceptions::InvalidPlatformVersion Chef::Log.debug("Chef::Version::Comparable does not know how to parse the platform version: #{version}") end end end provider_map end def find_platform_and_version(node) platform = nil version = nil if node[:platform] platform = node[:platform] elsif node.attribute?("os") platform = node[:os] end raise ArgumentError, "Cannot find a platform for #{node}" unless platform if node[:platform_version] version = node[:platform_version] elsif node[:os_version] version = node[:os_version] elsif node[:os_release] version = node[:os_release] end raise ArgumentError, "Cannot find a version for #{node}" unless version return platform, version end def provider_for_resource(resource, action = :nothing) node = resource.run_context && resource.run_context.node raise ArgumentError, "Cannot find the provider for a resource with no run context set" unless node provider = find_provider_for_node(node, resource).new(resource, resource.run_context) provider.action = action provider end def provider_for_node(node, resource_type) raise NotImplementedError, "#{self.class.name} no longer supports #provider_for_node" end def find_provider_for_node(node, resource_type) platform, version = find_platform_and_version(node) find_provider(platform, version, resource_type) end def set(args) validate( args, { :platform => { :kind_of => Symbol, :required => false, }, :version => { :kind_of => String, :required => false, }, :resource => { :kind_of => Symbol, }, :provider => { :kind_of => [ String, Symbol, Class ], }, } ) if args.has_key?(:platform) if args.has_key?(:version) if platforms.has_key?(args[:platform]) if platforms[args[:platform]].has_key?(args[:version]) platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider] else platforms[args[:platform]][args[:version]] = { args[:resource].to_sym => args[:provider], } end else platforms[args[:platform]] = { args[:version] => { args[:resource].to_sym => args[:provider], }, } end else if platforms.has_key?(args[:platform]) if platforms[args[:platform]].has_key?(:default) platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider] elsif args[:platform] == :default platforms[:default][args[:resource].to_sym] = args[:provider] else platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } } end else platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider], }, } end end else if platforms.has_key?(:default) platforms[:default][args[:resource].to_sym] = args[:provider] else platforms[:default] = { args[:resource].to_sym => args[:provider], } end end end def find_provider(platform, version, resource_type) provider_klass = explicit_provider(platform, version, resource_type) || platform_provider(platform, version, resource_type) || resource_matching_provider(platform, version, resource_type) raise Chef::Exceptions::ProviderNotFound, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil? provider_klass end private def explicit_provider(platform, version, resource_type) resource_type.kind_of?(Chef::Resource) ? resource_type.provider : nil end def platform_provider(platform, version, resource_type) pmap = Chef::Platform.find(platform, version) rtkey = resource_type.kind_of?(Chef::Resource) ? resource_type.resource_name.to_sym : resource_type pmap.has_key?(rtkey) ? pmap[rtkey] : nil end include Chef::Mixin::ConvertToClassName def resource_matching_provider(platform, version, resource_type) if resource_type.kind_of?(Chef::Resource) class_name = if resource_type.class.name resource_type.class.name.split("::").last else convert_to_class_name(resource_type.resource_name.to_s) end if Chef::Provider.const_defined?(class_name, false) Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'provides #{convert_to_snake_case(class_name).to_sym.inspect}'.") Chef::Log.warn("This will no longer work in Chef 13: you must use 'provides' to use the resource's DSL.") return Chef::Provider.const_get(class_name, false) end end nil end end end end chef-12.14.60/lib/chef/platform/provider_priority_map.rb000066400000000000000000000003171276456504500231450ustar00rootroot00000000000000require "singleton" require "chef/platform/priority_map" class Chef class Platform # @api private class ProviderPriorityMap < Chef::Platform::PriorityMap include Singleton end end end chef-12.14.60/lib/chef/platform/query_helpers.rb000066400000000000000000000071351276456504500214110ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Platform class << self def windows? ChefConfig.windows? end def windows_server_2003? # WMI startup shouldn't be performed unless we're on Windows. return false unless windows? require "wmi-lite/wmi" wmi = WmiLite::Wmi.new host = wmi.first_of("Win32_OperatingSystem") is_server_2003 = (host["version"] && host["version"].start_with?("5.2")) is_server_2003 end def windows_nano_server? return false unless windows? require "win32/registry" # This method may be called before ohai runs (e.g., it may be used to # determine settings in config.rb). Chef::Win32::Registry.new uses # node attributes to verify the machine architecture which aren't # accessible before ohai runs. nano = nil key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels" access = ::Win32::Registry::KEY_QUERY_VALUE | 0x0100 # nano is 64-bit only begin ::Win32::Registry::HKEY_LOCAL_MACHINE.open(key, access) do |reg| nano = reg["NanoServer"] end rescue ::Win32::Registry::Error # If accessing the registry key failed, then we're probably not on # nano. Fail through. end return nano == 1 end def supports_msi? return false unless windows? require "win32/registry" key = "System\\CurrentControlSet\\Services\\msiserver" access = ::Win32::Registry::KEY_QUERY_VALUE begin ::Win32::Registry::HKEY_LOCAL_MACHINE.open(key, access) do |reg| true end rescue ::Win32::Registry::Error false end end def supports_powershell_execution_bypass?(node) node[:languages] && node[:languages][:powershell] && node[:languages][:powershell][:version].to_i >= 3 end def supports_dsc?(node) node[:languages] && node[:languages][:powershell] && node[:languages][:powershell][:version].to_i >= 4 end def supports_dsc_invoke_resource?(node) supports_dsc?(node) && supported_powershell_version?(node, "5.0.10018.0") end def supports_refresh_mode_enabled?(node) supported_powershell_version?(node, "5.0.10586.0") end def dsc_refresh_mode_disabled?(node) require "chef/util/powershell/cmdlet" cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object) metadata = cmdlet.run!.return_value metadata["RefreshMode"] == "Disabled" end def supported_powershell_version?(node, version_string) return false unless node[:languages] && node[:languages][:powershell] require "rubygems" Gem::Version.new(node[:languages][:powershell][:version]) >= Gem::Version.new(version_string) end end end end chef-12.14.60/lib/chef/platform/rebooter.rb000066400000000000000000000042111276456504500203330ustar00rootroot00000000000000# # Author:: Chris Doherty ) # Copyright:: Copyright 2014-2016, Chef, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/dsl/reboot_pending" require "chef/log" require "chef/platform" require "chef/application/exit_code" class Chef class Platform module Rebooter extend Chef::Mixin::ShellOut class << self include Chef::DSL::RebootPending def reboot!(node) reboot_info = node.run_context.reboot_info cmd = if Chef::Platform.windows? # should this do /f as well? do we then need a minimum delay to let apps quit? "shutdown /r /t #{reboot_info[:delay_mins] * 60} /c \"#{reboot_info[:reason]}\"" else # probably Linux-only. "shutdown -r +#{reboot_info[:delay_mins]} \"#{reboot_info[:reason]}\"" end 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. def reboot_if_needed!(node) if node.run_context.reboot_requested? reboot!(node) end end end end end end chef-12.14.60/lib/chef/platform/resource_handler_map.rb000066400000000000000000000015751276456504500227050ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "singleton" require "chef/platform/handler_map" class Chef class Platform # @api private class ResourceHandlerMap < Chef::Platform::HandlerMap include Singleton end end end chef-12.14.60/lib/chef/platform/resource_priority_map.rb000066400000000000000000000003171276456504500231420ustar00rootroot00000000000000require "singleton" require "chef/platform/priority_map" class Chef class Platform # @api private class ResourcePriorityMap < Chef::Platform::PriorityMap include Singleton end end end chef-12.14.60/lib/chef/platform/service_helpers.rb000066400000000000000000000075211276456504500217030ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/chef_class" class Chef class Platform class ServiceHelpers class << self # This helper is mostly used to sort out the mess of different # linux mechanisms that can be used to start services. It does # not necessarily need to linux-specific, but currently all our # other service providers are narrowly platform-specific with no # alternatives. # # NOTE: if a system has (for example) chkconfig installed then we # should report that chkconfig is installed. The fact that a system # may also have systemd installed does not mean that we do not # report that systemd is also installed. This module is purely for # discovery of all the alternatives, handling the priority of the # different services is NOT a design concern of this module. # def service_resource_providers providers = [] if ::File.exist?(Chef.path_to("/usr/sbin/update-rc.d")) providers << :debian end if ::File.exist?(Chef.path_to("/usr/sbin/invoke-rc.d")) providers << :invokercd end if ::File.exist?(Chef.path_to("/sbin/initctl")) providers << :upstart end if ::File.exist?(Chef.path_to("/sbin/insserv")) providers << :insserv end if systemd_is_init? providers << :systemd end if ::File.exist?(Chef.path_to("/sbin/chkconfig")) providers << :redhat end providers end def config_for_service(service_name) configs = [] if ::File.exist?(Chef.path_to("/etc/init.d/#{service_name}")) configs << :initd end if ::File.exist?(Chef.path_to("/etc/init/#{service_name}.conf")) configs << :upstart end if ::File.exist?(Chef.path_to("/etc/xinetd.d/#{service_name}")) configs << :xinetd end if ::File.exist?(Chef.path_to("/etc/rc.d/#{service_name}")) configs << :etc_rcd end if ::File.exist?(Chef.path_to("/usr/local/etc/rc.d/#{service_name}")) configs << :usr_local_etc_rcd end if has_systemd_service_unit?(service_name) || has_systemd_unit?(service_name) configs << :systemd end configs end private def systemd_is_init? ::File.exist?(Chef.path_to("/proc/1/comm")) && ::File.open(Chef.path_to("/proc/1/comm")).gets.chomp == "systemd" end def has_systemd_service_unit?(svc_name) %w{ /etc /usr/lib /lib /run }.any? do |load_path| ::File.exist?( Chef.path_to("#{load_path}/systemd/system/#{svc_name.gsub(/@.*$/, '@')}.service") ) end end def has_systemd_unit?(svc_name) # TODO: stop supporting non-service units with service resource %w{ /etc /usr/lib /lib /run }.any? do |load_path| ::File.exist?(Chef.path_to("#{load_path}/systemd/system/#{svc_name}")) end end end end end end chef-12.14.60/lib/chef/policy_builder.rb000066400000000000000000000024261276456504500177010ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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/policy_builder/expand_node_object" require "chef/policy_builder/policyfile" require "chef/policy_builder/dynamic" class Chef # PolicyBuilder contains classes that handles fetching policy from server or # disk and resolving any indirection (e.g. expanding run_list). # # INPUTS # * event stream object # * node object/run_list # * json_attribs # * override_runlist # # OUTPUTS # * mutated node object (implicit) # * a new RunStatus (probably doesn't need to be here) # * cookbooks sync'd to disk # * cookbook_hash is stored in run_context module PolicyBuilder end end chef-12.14.60/lib/chef/policy_builder/000077500000000000000000000000001276456504500173505ustar00rootroot00000000000000chef-12.14.60/lib/chef/policy_builder/dynamic.rb000066400000000000000000000135051276456504500213250ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "forwardable" require "chef/log" require "chef/run_context" require "chef/config" require "chef/node" require "chef/exceptions" class Chef module PolicyBuilder # PolicyBuilder that selects either a Policyfile or non-Policyfile # implementation based on the content of the node object. class Dynamic extend Forwardable attr_reader :node attr_reader :node_name attr_reader :ohai_data attr_reader :json_attribs attr_reader :override_runlist attr_reader :events def initialize(node_name, ohai_data, json_attribs, override_runlist, events) @implementation = nil @node_name = node_name @ohai_data = ohai_data @json_attribs = json_attribs @override_runlist = override_runlist @events = events @node = nil end ## PolicyBuilder API ## # Loads the node state from the server, then picks the correct # implementation class based on the node and json_attribs. # # Calls #finish_load_node on the implementation object to complete the # loading process. All subsequent lifecycle calls are delegated. # # @return [Chef::Node] the loaded node. def load_node events.node_load_start(node_name, config) Chef::Log.debug("Building node object for #{node_name}") @node = if Chef::Config[:solo_legacy_mode] Chef::Node.build(node_name) else Chef::Node.find_or_create(node_name) end select_implementation(node) implementation.finish_load_node(node) node rescue Exception => e events.node_load_failed(node_name, e, config) raise end ## Delegated Public API Methods ## ### Accessors ### def_delegator :implementation, :original_runlist def_delegator :implementation, :run_context def_delegator :implementation, :run_list_expansion ### Lifecycle Methods ### # @!method build_node # # Applies external attributes (e.g., from JSON file, environment, # policyfile, etc.) and determines the correct expanded run list for the # run. # # @return [Chef::Node] def_delegator :implementation, :build_node # @!method setup_run_context # # Synchronizes cookbooks and initializes the run context object for the # run. # # @return [Chef::RunContext] def_delegator :implementation, :setup_run_context # @!method expanded_run_list # # Resolves the run list to a form containing only recipes and sets the # `roles` and `recipes` automatic attributes on the node. # # @return [#recipes, #roles] A RunListExpansion or duck-type. def_delegator :implementation, :expand_run_list # @!method sync_cookbooks # # Synchronizes cookbooks. In a normal chef-client run, this is handled by # #setup_run_context, but may be called directly in some circumstances. # # @return [Hash{String => Chef::CookbookManifest}] A map of # CookbookManifest objects by cookbook name. def_delegator :implementation, :sync_cookbooks # @!method temporary_policy? # # Indicates whether the policy is temporary, which means an # override_runlist was provided. Chef::Client uses this to decide whether # to do the final node save at the end of the run or not. # # @return [true,false] def_delegator :implementation, :temporary_policy? ## Internal Public API ## # Returns the selected implementation, or raises if not set. The # implementation is set when #load_node is called. # # @return [PolicyBuilder::Policyfile, PolicyBuilder::ExpandNodeObject] def implementation @implementation || raise(Exceptions::InvalidPolicybuilderCall, "#load_node must be called before other policy builder methods") end # @api private # # Sets the implementation based on the content of the node, node JSON # (i.e., the `-j JSON_FILE` data), and config. This is only public for # testing purposes; production code should call #load_node instead. def select_implementation(node) if policyfile_set_in_config? || policyfile_attribs_in_node_json? || node_has_policyfile_attrs?(node) || policyfile_compat_mode_config? @implementation = Policyfile.new(node_name, ohai_data, json_attribs, override_runlist, events) else @implementation = ExpandNodeObject.new(node_name, ohai_data, json_attribs, override_runlist, events) end end def config Chef::Config end private def node_has_policyfile_attrs?(node) node.policy_name || node.policy_group end def policyfile_attribs_in_node_json? json_attribs.key?("policy_name") || json_attribs.key?("policy_group") end def policyfile_set_in_config? config[:use_policyfile] || config[:policy_name] || config[:policy_group] end def policyfile_compat_mode_config? config[:deployment_group] && !config[:policy_document_native_api] end end end end chef-12.14.60/lib/chef/policy_builder/expand_node_object.rb000066400000000000000000000241431276456504500235130ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tim Hinderliter () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/server_api" require "chef/run_context" require "chef/config" require "chef/node" require "chef/chef_class" class Chef module PolicyBuilder # ExpandNodeObject is the "classic" policy builder implementation. It # expands the run_list on a node object and then queries the chef-server # to find the correct set of cookbooks, given version constraints of the # node's environment. # # Note that this class should only be used via PolicyBuilder::Dynamic and # not instantiated directly. class ExpandNodeObject attr_reader :events attr_reader :node attr_reader :node_name attr_reader :ohai_data attr_reader :json_attribs attr_reader :override_runlist attr_reader :run_context attr_reader :run_list_expansion def initialize(node_name, ohai_data, json_attribs, override_runlist, events) @node_name = node_name @ohai_data = ohai_data @json_attribs = json_attribs @override_runlist = override_runlist @events = events @node = nil @run_list_expansion = nil end # This method injects the run_context and into the Chef class. # # NOTE: This is duplicated with the Policyfile implementation. If # it gets any more complicated, it needs to be moved elsewhere. # # @param run_context [Chef::RunContext] the run_context to inject def setup_chef_class(run_context) Chef.set_run_context(run_context) end def setup_run_context(specific_recipes = nil) if Chef::Config[:solo_legacy_mode] Chef::Cookbook::FileVendor.fetch_from_disk(Chef::Config[:cookbook_path]) cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path]) cl.load_cookbooks cookbook_collection = Chef::CookbookCollection.new(cl) cookbook_collection.validate! cookbook_collection.install_gems(events) run_context = Chef::RunContext.new(node, cookbook_collection, @events) else Chef::Cookbook::FileVendor.fetch_from_remote(api_service) cookbook_hash = sync_cookbooks cookbook_collection = Chef::CookbookCollection.new(cookbook_hash) cookbook_collection.validate! cookbook_collection.install_gems(events) run_context = Chef::RunContext.new(node, cookbook_collection, @events) end # TODO: this is really obviously not the place for this # FIXME: need same edits setup_chef_class(run_context) # TODO: this is not the place for this. It should be in Runner or # CookbookCompiler or something. run_context.load(@run_list_expansion) if specific_recipes specific_recipes.each do |recipe_file| run_context.load_recipe_file(recipe_file) end end run_context end # DEPRECATED: As of Chef 12.5, chef selects either policyfile mode or # "expand node" mode dynamically, based on the content of the node # object, first boot JSON, and config. This happens in # PolicyBuilder::Dynamic, which selects the implementation during # #load_node and then delegates to either ExpandNodeObject or Policyfile # implementations as appropriate. Tools authors should update their code # to create a PolicyBuilder::Dynamc policy builder and allow it to select # the proper implementation. def load_node Chef.log_deprecation("ExpandNodeObject#load_node is deprecated. Please use Chef::PolicyBuilder::Dynamic instead of using ExpandNodeObject directly") events.node_load_start(node_name, config) Chef::Log.debug("Building node object for #{node_name}") @node = if Chef::Config[:solo_legacy_mode] Chef::Node.build(node_name) else Chef::Node.find_or_create(node_name) end finish_load_node(node) node rescue Exception => e events.node_load_failed(node_name, e, config) raise end def finish_load_node(node) @node = node end # Applies environment, external JSON attributes, and override run list to # the node, Then expands the run_list. # # === Returns # node:: The modified node object. node is modified in place. def build_node # Allow user to override the environment of a node by specifying # a config parameter. if Chef::Config[:environment] && !Chef::Config[:environment].chomp.empty? node.chef_environment(Chef::Config[:environment]) end # consume_external_attrs may add items to the run_list. Save the # expanded run_list, which we will pass to the server later to # determine which versions of cookbooks to use. node.reset_defaults_and_overrides node.consume_external_attrs(ohai_data, @json_attribs) setup_run_list_override expand_run_list Chef::Log.info("Run List is [#{node.run_list}]") Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]") events.node_load_completed(node, @expanded_run_list_with_versions, Chef::Config) events.run_list_expanded(@run_list_expansion) node end # Expands the node's run list. Stores the run_list_expansion object for later use. def expand_run_list @run_list_expansion = if Chef::Config[:solo_legacy_mode] node.expand!("disk") else node.expand!("server") end # @run_list_expansion is a RunListExpansion. # # Convert @expanded_run_list, which is an # Array of Hashes of the form # {:name => NAME, :version_constraint => Chef::VersionConstraint }, # into @expanded_run_list_with_versions, an # Array of Strings of the form # "#{NAME}@#{VERSION}" @expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings @run_list_expansion rescue Exception => e # TODO: wrap/munge exception with useful error output. events.run_list_expand_failed(node, e) raise end # Sync_cookbooks eagerly loads all files except files and # templates. It returns the cookbook_hash -- the return result # from /environments/#{node.chef_environment}/cookbook_versions, # which we will use for our run_context. # # === Returns # Hash:: The hash of cookbooks with download URLs as given by the server def sync_cookbooks Chef::Log.debug("Synchronizing cookbooks") begin events.cookbook_resolution_start(@expanded_run_list_with_versions) cookbook_hash = api_service.post("environments/#{node.chef_environment}/cookbook_versions", { :run_list => @expanded_run_list_with_versions }) cookbook_hash = cookbook_hash.inject({}) do |memo, (key, value)| memo[key] = Chef::CookbookVersion.from_hash(value) memo end rescue Exception => e # TODO: wrap/munge exception to provide helpful error output events.cookbook_resolution_failed(@expanded_run_list_with_versions, e) raise else events.cookbook_resolution_complete(cookbook_hash) end synchronizer = Chef::CookbookSynchronizer.new(cookbook_hash, events) if temporary_policy? synchronizer.remove_obsoleted_files = false end synchronizer.sync_cookbooks # register the file cache path in the cookbook path so that CookbookLoader actually picks up the synced cookbooks Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks") cookbook_hash end # Indicates whether the policy is temporary, which means an # override_runlist was provided. Chef::Client uses this to decide whether # to do the final node save at the end of the run or not. def temporary_policy? !node.override_runlist.empty? end ######################################## # Internal public API ######################################## def setup_run_list_override runlist_override_sanity_check! unless override_runlist.empty? node.override_runlist(*override_runlist) Chef::Log.warn "Run List override has been provided." Chef::Log.warn "Original Run List: [#{node.primary_runlist}]" Chef::Log.warn "Overridden Run List: [#{node.run_list}]" end end # Ensures runlist override contains RunListItem instances def runlist_override_sanity_check! # Convert to array and remove whitespace if override_runlist.is_a?(String) @override_runlist = override_runlist.split(",").map { |e| e.strip } end @override_runlist = [override_runlist].flatten.compact override_runlist.map! do |item| if item.is_a?(Chef::RunList::RunListItem) item else Chef::RunList::RunListItem.new(item) end end end def api_service @api_service ||= Chef::ServerAPI.new(config[:chef_server_url]) end def config Chef::Config end end end end chef-12.14.60/lib/chef/policy_builder/policyfile.rb000066400000000000000000000416241276456504500220430ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tim Hinderliter () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/run_context" require "chef/config" require "chef/node" require "chef/server_api" class Chef module PolicyBuilder # Policyfile is an experimental policy builder implementation that gets run # list and cookbook version information from a single document. # # == WARNING # This implementation is experimental. It may be changed in incompatible # ways in minor or even patch releases, or even abandoned altogether. If # using this with other tools, you may be forced to upgrade those tools in # lockstep with chef-client because of incompatible behavior changes. # # == Unsupported Options: # * override_runlist:: This could potentially be integrated into the # policyfile, or replaced with a similar feature that has different # semantics. # * specific_recipes:: put more design thought into this use case. # * run_list in json_attribs:: would be ignored anyway, so it raises an error. # * chef-solo:: not currently supported. Need more design thought around # how this should work. class Policyfile class UnsupportedFeature < StandardError; end class PolicyfileError < StandardError; end RunListExpansionIsh = Struct.new(:recipes, :roles) attr_reader :events attr_reader :node attr_reader :node_name attr_reader :ohai_data attr_reader :json_attribs attr_reader :run_context def initialize(node_name, ohai_data, json_attribs, override_runlist, events) @node_name = node_name @ohai_data = ohai_data @json_attribs = json_attribs @events = events @node = nil if Chef::Config[:solo_legacy_mode] raise UnsupportedFeature, "Policyfile does not support chef-solo. Use chef-client local mode instead." end if override_runlist raise UnsupportedFeature, "Policyfile does not support override run lists. Use named run_lists instead." end if json_attribs && json_attribs.key?("run_list") raise UnsupportedFeature, "Policyfile does not support setting the run_list in json data." end if Chef::Config[:environment] && !Chef::Config[:environment].chomp.empty? raise UnsupportedFeature, "Policyfile does not work with Chef Environments." end end ## API Compat ## # Methods related to unsupported features # Override run_list is not supported. def original_runlist nil end # Override run_list is not supported. def override_runlist nil end # Policyfile gives you the run_list already expanded, but users of this # class may expect to get a run_list expansion compatible object by # calling this method. # # === Returns # RunListExpansionIsh:: A RunListExpansion duck type def run_list_expansion run_list_expansion_ish end ## PolicyBuilder API ## def finish_load_node(node) @node = node select_policy_name_and_group validate_policyfile events.policyfile_loaded(policy) end # Applies environment, external JSON attributes, and override run list to # the node, Then expands the run_list. # # === Returns # node:: The modified node object. node is modified in place. def build_node # consume_external_attrs may add items to the run_list. Save the # expanded run_list, which we will pass to the server later to # determine which versions of cookbooks to use. node.reset_defaults_and_overrides node.consume_external_attrs(ohai_data, json_attribs) expand_run_list apply_policyfile_attributes Chef::Log.info("Run List is [#{run_list}]") Chef::Log.info("Run List expands to [#{run_list_with_versions_for_display.join(', ')}]") events.node_load_completed(node, run_list_with_versions_for_display, Chef::Config) node rescue Exception => e events.node_load_failed(node_name, e, Chef::Config) raise end # Synchronizes cookbooks and initializes the run context object for the # run. # # @return [Chef::RunContext] def setup_run_context(specific_recipes = nil) Chef::Cookbook::FileVendor.fetch_from_remote(http_api) sync_cookbooks cookbook_collection = Chef::CookbookCollection.new(cookbooks_to_sync) cookbook_collection.validate! cookbook_collection.install_gems(events) run_context = Chef::RunContext.new(node, cookbook_collection, events) setup_chef_class(run_context) run_context.load(run_list_expansion_ish) setup_chef_class(run_context) run_context end # Sets `run_list` on the node from the policy, sets `roles` and `recipes` # attributes on the node accordingly. # # @return [RunListExpansionIsh] A RunListExpansion duck-type. def expand_run_list CookbookCacheCleaner.instance.skip_removal = true if named_run_list_requested? node.run_list(run_list) node.automatic_attrs[:roles] = [] node.automatic_attrs[:recipes] = run_list_expansion_ish.recipes run_list_expansion_ish end # Synchronizes cookbooks. In a normal chef-client run, this is handled by # #setup_run_context, but may be called directly in some circumstances. # # @return [Hash{String => Chef::CookbookManifest}] A map of # CookbookManifest objects by cookbook name. def sync_cookbooks Chef::Log.debug("Synchronizing cookbooks") synchronizer = Chef::CookbookSynchronizer.new(cookbooks_to_sync, events) synchronizer.sync_cookbooks # register the file cache path in the cookbook path so that CookbookLoader actually picks up the synced cookbooks Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks") cookbooks_to_sync end # Whether or not this is a temporary policy. Since PolicyBuilder doesn't # support override_runlist, this is always false. # # @return [false] def temporary_policy? false end ## Internal Public API ## # @api private # # Generates an array of strings with recipe names including version and # identifier info. def run_list_with_versions_for_display run_list.map do |recipe_spec| cookbook, recipe = parse_recipe_spec(recipe_spec) lock_data = cookbook_lock_for(cookbook) display = "#{cookbook}::#{recipe}@#{lock_data["version"]} (#{lock_data["identifier"][0...7]})" display end end # @api private # # Sets up a RunListExpansionIsh object so that it can be used in place of # a RunListExpansion object, to satisfy the API contract of # #expand_run_list def run_list_expansion_ish recipes = run_list.map do |recipe_spec| cookbook, recipe = parse_recipe_spec(recipe_spec) "#{cookbook}::#{recipe}" end RunListExpansionIsh.new(recipes, []) end # @api private # # Sets attributes from the policyfile on the node, using the role priority. def apply_policyfile_attributes node.attributes.role_default = policy["default_attributes"] node.attributes.role_override = policy["override_attributes"] end # @api private def parse_recipe_spec(recipe_spec) rmatch = recipe_spec.match(/recipe\[([^:]+)::([^:]+)\]/) if rmatch.nil? raise PolicyfileError, "invalid recipe specification #{recipe_spec} in Policyfile from #{policyfile_location}" else [rmatch[1], rmatch[2]] end end # @api private def cookbook_lock_for(cookbook_name) cookbook_locks[cookbook_name] end # @api private def run_list if named_run_list_requested? named_run_list || raise(ConfigurationError, "Policy '#{retrieved_policy_name}' revision '#{revision_id}' does not have named_run_list '#{named_run_list_name}'" + "(available named_run_lists: [#{available_named_run_lists.join(', ')}])") else policy["run_list"] end end # @api private def policy @policy ||= http_api.get(policyfile_location) rescue Net::HTTPServerException => e raise ConfigurationError, "Error loading policyfile from `#{policyfile_location}': #{e.class} - #{e.message}" end # @api private def policyfile_location if Chef::Config[:policy_document_native_api] validate_policy_config! "policy_groups/#{policy_group}/policies/#{policy_name}" else "data/policyfiles/#{deployment_group}" end end # Do some mimimal validation of the policyfile we fetched from the # server. Compatibility mode relies on using data bags to store policy # files; therefore no real validation will be performed server-side and # we need to make additional checks to ensure the data will be formatted # correctly. def validate_policyfile errors = [] unless run_list errors << "Policyfile is missing run_list element" end unless policy.key?("cookbook_locks") errors << "Policyfile is missing cookbook_locks element" end if run_list.kind_of?(Array) run_list_errors = run_list.select do |maybe_recipe_spec| validate_recipe_spec(maybe_recipe_spec) end errors += run_list_errors else errors << "Policyfile run_list is malformed, must be an array of `recipe[cb_name::recipe_name]` items: #{policy["run_list"]}" end unless errors.empty? raise PolicyfileError, "Policyfile fetched from #{policyfile_location} was invalid:\n#{errors.join("\n")}" end end # @api private def validate_recipe_spec(recipe_spec) parse_recipe_spec(recipe_spec) nil rescue PolicyfileError => e e.message end class ConfigurationError < StandardError; end # @api private def deployment_group Chef::Config[:deployment_group] || raise(ConfigurationError, "Setting `deployment_group` is not configured.") end # @api private def validate_policy_config! raise ConfigurationError, "Setting `policy_group` is not configured." unless policy_group raise ConfigurationError, "Setting `policy_name` is not configured." unless policy_name end # @api private def policy_group Chef::Config[:policy_group] end # @api private def policy_name Chef::Config[:policy_name] end # @api private # # Selects the `policy_name` and `policy_group` from the following sources # in priority order: # # 1. JSON attribs (i.e., `-j JSON_FILE`) # 2. `Chef::Config` # 3. The node object # # The selected values are then copied to `Chef::Config` and the node. def select_policy_name_and_group policy_name_to_set = policy_name_from_json_attribs || policy_name_from_config || policy_name_from_node policy_group_to_set = policy_group_from_json_attribs || policy_group_from_config || policy_group_from_node node.policy_name = policy_name_to_set node.policy_group = policy_group_to_set Chef::Config[:policy_name] = policy_name_to_set Chef::Config[:policy_group] = policy_group_to_set end # @api private def policy_group_from_json_attribs json_attribs["policy_group"] end # @api private def policy_name_from_json_attribs json_attribs["policy_name"] end # @api private def policy_group_from_config Chef::Config[:policy_group] end # @api private def policy_name_from_config Chef::Config[:policy_name] end # @api private def policy_group_from_node node.policy_group end # @api private def policy_name_from_node node.policy_name end # @api private # Builds a 'cookbook_hash' map of the form # "COOKBOOK_NAME" => "IDENTIFIER" # # This can be passed to a Chef::CookbookSynchronizer object to # synchronize the cookbooks. # # TODO: Currently this makes N API calls to the server to get the # cookbook objects. With server support (bulk API or the like), this # should be reduced to a single call. def cookbooks_to_sync @cookbook_to_sync ||= begin events.cookbook_resolution_start(run_list_with_versions_for_display) cookbook_versions_by_name = cookbook_locks.inject({}) do |cb_map, (name, lock_data)| cb_map[name] = manifest_for(name, lock_data) cb_map end events.cookbook_resolution_complete(cookbook_versions_by_name) cookbook_versions_by_name end rescue Exception => e # TODO: wrap/munge exception to provide helpful error output events.cookbook_resolution_failed(run_list_with_versions_for_display, e) raise end # @api private # Fetches the CookbookVersion object for the given name and identifer # specified in the lock_data. # TODO: This only implements Chef 11 compatibility mode, which means that # cookbooks are fetched by the "dotted_decimal_identifier": a # representation of a SHA1 in the traditional x.y.z version format. def manifest_for(cookbook_name, lock_data) if Chef::Config[:policy_document_native_api] artifact_manifest_for(cookbook_name, lock_data) else compat_mode_manifest_for(cookbook_name, lock_data) end end # @api private def cookbook_locks policy["cookbook_locks"] end # @api private def revision_id policy["revision_id"] end # @api private def http_api @api_service ||= Chef::ServerAPI.new(config[:chef_server_url]) end # @api private def config Chef::Config end private # This method injects the run_context and into the Chef class. # # NOTE: This is duplicated with the ExpandNodeObject implementation. If # it gets any more complicated, it needs to be moved elsewhere. # # @param run_context [Chef::RunContext] the run_context to inject def setup_chef_class(run_context) Chef.set_run_context(run_context) end def retrieved_policy_name policy["name"] end def named_run_list policy["named_run_lists"] && policy["named_run_lists"][named_run_list_name] end def available_named_run_lists (policy["named_run_lists"] || {}).keys end def named_run_list_requested? !!Chef::Config[:named_run_list] end def named_run_list_name Chef::Config[:named_run_list] end def compat_mode_manifest_for(cookbook_name, lock_data) xyz_version = lock_data["dotted_decimal_identifier"] rel_url = "cookbooks/#{cookbook_name}/#{xyz_version}" inflate_cbv_object(http_api.get(rel_url)) rescue Exception => e message = "Error loading cookbook #{cookbook_name} at version #{xyz_version} from #{rel_url}: #{e.class} - #{e.message}" err = Chef::Exceptions::CookbookNotFound.new(message) err.set_backtrace(e.backtrace) raise err end def artifact_manifest_for(cookbook_name, lock_data) identifier = lock_data["identifier"] rel_url = "cookbook_artifacts/#{cookbook_name}/#{identifier}" inflate_cbv_object(http_api.get(rel_url)) rescue Exception => e message = "Error loading cookbook #{cookbook_name} with identifier #{identifier} from #{rel_url}: #{e.class} - #{e.message}" err = Chef::Exceptions::CookbookNotFound.new(message) err.set_backtrace(e.backtrace) raise err end def inflate_cbv_object(raw_manifest) Chef::CookbookVersion.from_cb_artifact_data(raw_manifest) end end end end chef-12.14.60/lib/chef/property.rb000066400000000000000000000606701276456504500165650ustar00rootroot00000000000000# # Author:: John Keiser # Copyright:: Copyright 2015-2016, John Keiser. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/exceptions" require "chef/delayed_evaluator" require "chef/chef_class" require "chef/log" class Chef # # Type and validation information for a property on a resource. # # A property named "x" manipulates the "@x" instance variable on a # resource. The *presence* of the variable (`instance_variable_defined?(@x)`) # tells whether the variable is defined; it may have any actual value, # constrained only by validation. # # Properties may have validation, defaults, and coercion, and have full # support for lazy values. # # @see Chef::Resource.property # @see Chef::DelayedEvaluator # class Property # # Create a reusable property type that can be used in multiple properties # in different resources. # # @param options [Hash] Validation options. See Chef::Resource.property for # the list of options. # # @example # Property.derive(default: 'hi') # def self.derive(**options) new(**options) end # # Create a new property. # # @param options [Hash] Property options, including # control options here, as well as validation options (see # Chef::Mixin::ParamsValidate#validate for a description of validation # options). # @option options [Symbol] :name The name of this property. # @option options [Class] :declared_in The class this property comes from. # @option options [Symbol] :instance_variable_name The instance variable # tied to this property. Must include a leading `@`. Defaults to `@`. # `nil` means the property is opaque and not tied to a specific instance # variable. # @option options [Boolean] :desired_state `true` if this property is part of desired # state. Defaults to `true`. # @option options [Boolean] :identity `true` if this property is part of object # identity. Defaults to `false`. # @option options [Boolean] :name_property `true` if this # property defaults to the same value as `name`. Equivalent to # `default: lazy { name }`, except that #property_is_set? will # return `true` if the property is set *or* if `name` is set. # @option options [Boolean] :nillable `true` opt-in to Chef-13 style behavior where # attempting to set a nil value will really set a nil value instead of issuing # a warning and operating like a getter # @option options [Object] :default The value this property # will return if the user does not set one. If this is `lazy`, it will # be run in the context of the instance (and able to access other # properties) and cached. If not, the value will be frozen with Object#freeze # to prevent users from modifying it in an instance. # @option options [Proc] :coerce A proc which will be called to # transform the user input to canonical form. The value is passed in, # and the transformed value returned as output. Lazy values will *not* # be passed to this method until after they are evaluated. Called in the # context of the resource (meaning you can access other properties). # @option options [Boolean] :required `true` if this property # must be present; `false` otherwise. This is checked after the resource # is fully initialized. # def initialize(**options) options = options.inject({}) { |memo, (key, value)| memo[key.to_sym] = value; memo } @options = options options[:name] = options[:name].to_sym if options[:name] options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name] # Replace name_attribute with name_property if options.has_key?(:name_attribute) # If we have both name_attribute and name_property and they differ, raise an error if options.has_key?(:name_property) raise ArgumentError, "Cannot specify both name_property and name_attribute together on property #{self}." end # replace name_property with name_attribute in place options = Hash[options.map { |k, v| k == :name_attribute ? [ :name_property, v ] : [ k, v ] }] @options = options end # Only pick the first of :default, :name_property and :name_attribute if # more than one is specified. if options.has_key?(:default) && options[:name_property] if options[:default].nil? || options.keys.index(:name_property) < options.keys.index(:default) options.delete(:default) preferred_default = :name_property else options.delete(:name_property) preferred_default = :default end Chef.log_deprecation("Cannot specify both default and name_property together on property #{self}. Only one (#{preferred_default}) will be obeyed. In Chef 13, this will become an error. Please remove one or the other from the property.") end # Validate the default early, so the user gets a good error message, and # cache it so we don't do it again if so begin # If we can validate it all the way to output, do it. @stored_default = input_to_stored_value(nil, default, is_default: true) rescue Chef::Exceptions::CannotValidateStaticallyError # If the validation is not static (i.e. has procs), we will have to # coerce and validate the default each time we run end end def to_s "#{name || ""}#{declared_in ? " of resource #{declared_in.resource_name}" : ""}" end # # The name of this property. # # @return [String] # def name options[:name] end # # The class this property was defined in. # # @return [Class] # def declared_in options[:declared_in] end # # The instance variable associated with this property. # # Defaults to `@` # # @return [Symbol] # def instance_variable_name if options.has_key?(:instance_variable_name) options[:instance_variable_name] elsif name :"@#{name}" end end # # The raw default value for this resource. # # Does not coerce or validate the default. Does not evaluate lazy values. # # Defaults to `lazy { name }` if name_property is true; otherwise defaults to # `nil` # def default return options[:default] if options.has_key?(:default) return Chef::DelayedEvaluator.new { name } if name_property? nil end # # Whether this is part of the resource's natural identity or not. # # @return [Boolean] # def identity? options[:identity] end # # Whether this is part of desired state or not. # # Defaults to true. # # @return [Boolean] # def desired_state? return true if !options.has_key?(:desired_state) options[:desired_state] end # # Whether this is name_property or not. # # @return [Boolean] # def name_property? options[:name_property] end # # Whether this property has a default value. # # @return [Boolean] # def has_default? options.has_key?(:default) || name_property? end # # Whether this property is required or not. # # @return [Boolean] # def required? options[:required] end # # Whether this property is sensitive or not. # # Defaults to false. # # @return [Boolean] # def sensitive? options.fetch(:sensitive, false) end # # Validation options. (See Chef::Mixin::ParamsValidate#validate.) # # @return [Hash] # def validation_options @validation_options ||= options.reject do |k, v| [:declared_in, :name, :instance_variable_name, :desired_state, :identity, :default, :name_property, :coerce, :required, :nillable, :sensitive].include?(k) end end # # Handle the property being called. # # The base implementation does the property get-or-set: # # ```ruby # resource.myprop # get # resource.myprop value # set # ``` # # Subclasses may implement this with any arguments they want, as long as # the corresponding DSL calls it correctly. # # @param resource [Chef::Resource] The resource to get the property from. # @param value The value to set (or NOT_PASSED if it is a get). # # @return The current value of the property. If it is a `set`, lazy values # will be returned without running, validating or coercing. If it is a # `get`, the non-lazy, coerced, validated value will always be returned. # def call(resource, value = NOT_PASSED) if value == NOT_PASSED return get(resource) end if value.nil? && !nillable? # In Chef 12, value(nil) does a *get* instead of a set, so we # warn if the value would have been changed. In Chef 13, it will be # equivalent to value = nil. result = get(resource, nil_set: true) # Warn about this becoming a set in Chef 13. begin input_to_stored_value(resource, value) # If nil is valid, and it would change the value, warn that this will change to a set. if !result.nil? Chef.log_deprecation("An attempt was made to change #{name} from #{result.inspect} to nil by calling #{name}(nil). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil.") end rescue Chef::Exceptions::DeprecatedFeatureError raise rescue # If nil is invalid, warn that this will become an error. Chef.log_deprecation("nil is an invalid value for #{self}. In Chef 13, this warning will change to an error. Error: #{$!}") end result else # Anything else, such as myprop(value) is a set set(resource, value) end end # # Get the property value from the resource, handling lazy values, # defaults, and validation. # # - If the property's value is lazy, it is evaluated, coerced and validated. # - If the property has no value, and is required, raises ValidationFailed. # - If the property has no value, but has a lazy default, it is evaluated, # coerced and validated. If the evaluated value is frozen, the resulting # - If the property has no value, but has a default, the default value # will be returned and frozen. If the default value is lazy, it will be # evaluated, coerced and validated, and the result stored in the property. # - If the property has no value, but is name_property, `resource.name` # is retrieved, coerced, validated and stored in the property. # - Otherwise, `nil` is returned. # # @param resource [Chef::Resource] The resource to get the property from. # # @return The value of the property. # # @raise Chef::Exceptions::ValidationFailed If the value is invalid for # this property, or if the value is required and not set. # def get(resource, nil_set: false) # If it's set, return it (and evaluate any lazy values) if is_set?(resource) value = get_value(resource) value = stored_value_to_output(resource, value) else # We are getting the default value. # If the user does something like this: # # ``` # class MyResource < Chef::Resource # property :content # action :create do # file '/x.txt' do # content content # end # end # end # ``` # # It won't do what they expect. This checks whether you try to *read* # `content` while we are compiling the resource. if !nil_set && resource.respond_to?(:resource_initializing) && resource.resource_initializing && resource.respond_to?(:enclosing_provider) && resource.enclosing_provider && resource.enclosing_provider.new_resource && resource.enclosing_provider.new_resource.respond_to?(name) Chef::Log.warn("#{Chef::Log.caller_location}: property #{name} is declared in both #{resource} and #{resource.enclosing_provider}. Use new_resource.#{name} instead. At #{Chef::Log.caller_location}") end if has_default? # If we were able to cache the stored_default, grab it. if defined?(@stored_default) value = @stored_default else # Otherwise, we have to validate it now. value = input_to_stored_value(resource, default, is_default: true) end value = stored_value_to_output(resource, value, is_default: true) # If the value is mutable (non-frozen), we set it on the instance # so that people can mutate it. (All constant default values are # frozen.) if !value.frozen? && !value.nil? set_value(resource, value) end value elsif required? raise Chef::Exceptions::ValidationFailed, "#{name} is required" end end end # # Set the value of this property in the given resource. # # Non-lazy values are coerced and validated before being set. Coercion # and validation of lazy values is delayed until they are first retrieved. # # @param resource [Chef::Resource] The resource to set this property in. # @param value The value to set. # # @return The value that was set, after coercion (if lazy, still returns # the lazy value) # # @raise Chef::Exceptions::ValidationFailed If the value is invalid for # this property. # def set(resource, value) set_value(resource, input_to_stored_value(resource, value)) end # # Find out whether this property has been set. # # This will be true if: # - The user explicitly set the value # - The property has a default, and the value was retrieved. # # From this point of view, it is worth looking at this as "what does the # user think this value should be." In order words, if the user grabbed # the value, even if it was a default, they probably based calculations on # it. If they based calculations on it and the value changes, the rest of # the world gets inconsistent. # # @param resource [Chef::Resource] The resource to get the property from. # # @return [Boolean] # def is_set?(resource) value_is_set?(resource) end # # Reset the value of this property so that is_set? will return false and the # default will be returned in the future. # # @param resource [Chef::Resource] The resource to get the property from. # def reset(resource) reset_value(resource) end # # Coerce an input value into canonical form for the property. # # After coercion, the value is suitable for storage in the resource. # You must validate values after coercion, however. # # Does no special handling for lazy values. # # @param resource [Chef::Resource] The resource we're coercing against # (to provide context for the coerce). # @param value The value to coerce. # # @return The coerced value. # # @raise Chef::Exceptions::ValidationFailed If the value is invalid for # this property. # def coerce(resource, value) if options.has_key?(:coerce) # If we have no default value, `nil` is never coerced or validated unless !has_default? && value.nil? value = exec_in_resource(resource, options[:coerce], value) end end value end # # Validate a value. # # Calls Chef::Mixin::ParamsValidate#validate with #validation_options as # options. # # @param resource [Chef::Resource] The resource we're validating against # (to provide context for the validate). # @param value The value to validate. # # @raise Chef::Exceptions::ValidationFailed If the value is invalid for # this property. # def validate(resource, value) # If we have no default value, `nil` is never coerced or validated unless value.nil? && !has_default? if resource resource.validate({ name => value }, { name => validation_options }) else name = self.name || :property_type Chef::Mixin::ParamsValidate.validate({ name => value }, { name => validation_options }) end end end # # Derive a new Property that is just like this one, except with some added or # changed options. # # @param options [Hash] List of options that would be passed # to #initialize. # # @return [Property] The new property type. # def derive(**modified_options) # Since name_property, name_attribute and default override each other, # if you specify one of them in modified_options it overrides anything in # the original options. options = self.options if modified_options.has_key?(:name_property) || modified_options.has_key?(:name_attribute) || modified_options.has_key?(:default) options = options.reject { |k, v| k == :name_attribute || k == :name_property || k == :default } end self.class.new(options.merge(modified_options)) end # # Emit the DSL for this property into the resource class (`declared_in`). # # Creates a getter and setter for the property. # def emit_dsl # We don't create the getter/setter if it's a custom property; we will # be using the existing getter/setter to manipulate it instead. return if !instance_variable_name # We prefer this form because the property name won't show up in the # stack trace if you use `define_method`. declared_in.class_eval <<-EOM, __FILE__, __LINE__ + 1 def #{name}(value=NOT_PASSED) raise "Property #{name} of \#{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block_given? self.class.properties[#{name.inspect}].call(self, value) end def #{name}=(value) raise "Property #{name} of \#{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block_given? self.class.properties[#{name.inspect}].set(self, value) end EOM rescue SyntaxError # If the name is not a valid ruby name, we use define_method. declared_in.define_method(name) do |value = NOT_PASSED, &block| raise "Property #{name} of #{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block self.class.properties[name].call(self, value) end declared_in.define_method("#{name}=") do |value, &block| raise "Property #{name} of #{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block self.class.properties[name].set(self, value) end end # # The options this Property will use for get/set behavior and validation. # # @see #initialize for a list of valid options. # attr_reader :options # # Find out whether this type accepts nil explicitly. # # A type accepts nil explicitly if "is" allows nil, it validates as nil, *and* is not simply # an empty type. # # A type is presumed to accept nil if it does coercion (which must handle nil). # # These examples accept nil explicitly: # ```ruby # property :a, [ String, nil ] # property :a, [ String, NilClass ] # property :a, [ String, proc { |v| v.nil? } ] # ``` # # This does not (because the "is" doesn't exist or doesn't have nil): # # ```ruby # property :x, String # ``` # # These do not, even though nil would validate fine (because they do not # have "is"): # # ```ruby # property :a # property :a, equal_to: [ 1, 2, 3, nil ] # property :a, kind_of: [ String, NilClass ] # property :a, respond_to: [ ] # property :a, callbacks: { "a" => proc { |v| v.nil? } } # ``` # # @param resource [Chef::Resource] The resource we're coercing against # (to provide context for the coerce). # # @return [Boolean] Whether this value explicitly accepts nil. # # @api private def explicitly_accepts_nil?(resource) options.has_key?(:coerce) || (options.has_key?(:is) && resource.send(:_pv_is, { name => nil }, name, options[:is], raise_error: false)) end # @api private def get_value(resource) if instance_variable_name resource.instance_variable_get(instance_variable_name) else resource.send(name) end end # @api private def set_value(resource, value) if instance_variable_name resource.instance_variable_set(instance_variable_name, value) else resource.send(name, value) end end # @api private def value_is_set?(resource) if instance_variable_name resource.instance_variable_defined?(instance_variable_name) else true end end # @api private def reset_value(resource) if instance_variable_name if value_is_set?(resource) resource.remove_instance_variable(instance_variable_name) end else raise ArgumentError, "Property #{name} has no instance variable defined and cannot be reset" end end private def exec_in_resource(resource, proc, *args) if resource if proc.arity > args.size value = proc.call(resource, *args) else value = resource.instance_exec(*args, &proc) end else # If we don't have a resource yet, we can't exec in resource! raise Chef::Exceptions::CannotValidateStaticallyError, "Cannot validate or coerce without a resource" end end def input_to_stored_value(resource, value, is_default: false) unless value.is_a?(DelayedEvaluator) value = coerce_and_validate(resource, value, is_default: is_default) end value end def stored_value_to_output(resource, value, is_default: false) # Crack open lazy values before giving the result to the user if value.is_a?(DelayedEvaluator) value = exec_in_resource(resource, value) value = coerce_and_validate(resource, value, is_default: is_default) end value end # Coerces and validates the value. If the value is a default, it will warn # the user that invalid defaults are bad mmkay, and return it as if it were # valid. def coerce_and_validate(resource, value, is_default: false) result = coerce(resource, value) begin # If the input is from a default, we need to emit an invalid default warning on validate. validate(resource, result) rescue Chef::Exceptions::CannotValidateStaticallyError # This one gets re-raised raise rescue # Anything else is just an invalid default: in those cases, we just # warn and return the (possibly coerced) value to the user. if is_default if value.nil? Chef.log_deprecation("Default value nil is invalid for property #{self}. Possible fixes: 1. Remove 'default: nil' if nil means 'undefined'. 2. Set a valid default value if there is a reasonable one. 3. Allow nil as a valid value of your property (for example, 'property #{name.inspect}, [ String, nil ], default: nil'). Error: #{$!}") else Chef.log_deprecation("Default value #{value.inspect} is invalid for property #{self}. In Chef 13 this will become an error: #{$!}.") end else raise end end result end def nillable? !!options[:nillable] end end end chef-12.14.60/lib/chef/provider.rb000066400000000000000000000411411276456504500165230ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Copyright:: Copyright 2008-2016, 2009-2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/from_file" require "chef/mixin/convert_to_class_name" require "chef/mixin/enforce_ownership_and_permissions" require "chef/mixin/why_run" require "chef/mixin/shell_out" require "chef/mixin/provides" require "chef/dsl/core" require "chef/platform/service_helpers" require "chef/node_map" require "forwardable" class Chef class Provider require "chef/mixin/why_run" require "chef/mixin/provides" attr_accessor :new_resource attr_accessor :current_resource attr_accessor :run_context attr_reader :recipe_name attr_reader :cookbook_name include Chef::Mixin::WhyRun extend Chef::Mixin::Provides # includes the "core" DSL and not the "recipe" DSL by design include Chef::DSL::Core # supports the given resource and action (late binding) def self.supports?(resource, action) true end #-- # TODO: this should be a reader, and the action should be passed in the # constructor; however, many/most subclasses override the constructor so # changing the arity would be a breaking change. Change this at the next # break, e.g., Chef 11. attr_accessor :action def initialize(new_resource, run_context) @new_resource = new_resource @action = action @current_resource = nil @run_context = run_context @converge_actions = nil @recipe_name = nil @cookbook_name = nil self.class.include_resource_dsl_module(new_resource) end def whyrun_mode? Chef::Config[:why_run] end def whyrun_supported? false end def node run_context && run_context.node end # Used by providers supporting embedded recipes def resource_collection run_context && run_context.resource_collection end def cookbook_name new_resource.cookbook_name end def check_resource_semantics! end def load_current_resource raise Chef::Exceptions::Override, "You must override load_current_resource in #{self}" end def define_resource_requirements end def cleanup_after_converge end def action_nothing Chef::Log.debug("Doing nothing for #{@new_resource}") true end def events run_context.events end def run_action(action = nil) @action = action unless action.nil? # TODO: it would be preferable to get the action to be executed in the # constructor... check_resource_semantics! # user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode if whyrun_mode? && !whyrun_supported? events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource) else load_current_resource events.resource_current_state_loaded(@new_resource, @action, @current_resource) end define_resource_requirements process_resource_requirements # user-defined providers including LWRPs may # not include whyrun support - if they don't support it # we can't execute any actions while we're running in # whyrun mode. Instead we 'fake' whyrun by documenting that # we can't execute the action. # in non-whyrun mode, this will still cause the action to be # executed normally. if whyrun_mode? && (!whyrun_supported? || requirements.action_blocked?(@action)) events.resource_bypassed(@new_resource, @action, self) else send("action_#{@action}") end set_updated_status cleanup_after_converge end def process_resource_requirements requirements.run(:all_actions) unless @action == :nothing requirements.run(@action) end def resource_updated? !converge_actions.empty? || @new_resource.updated_by_last_action? end def set_updated_status if !resource_updated? events.resource_up_to_date(@new_resource, @action) else events.resource_updated(@new_resource, @action) new_resource.updated_by_last_action(true) end end def requirements @requirements ||= ResourceRequirements.new(@new_resource, run_context) end def converge_by(descriptions, &block) converge_actions.add_action(descriptions, &block) end # # Handle patchy convergence safely. # # - Does *not* call the block if the current_resource's properties match # the properties the user specified on the resource. # - Calls the block if current_resource does not exist # - Calls the block if the user has specified any properties in the resource # whose values are *different* from current_resource. # - Does *not* call the block if why-run is enabled (just prints out text). # - Prints out automatic green text saying what properties have changed. # # @param properties An optional list of property names (symbols). If not # specified, `new_resource.class.state_properties` will be used. # @param converge_block The block to do the converging in. # # @return [Boolean] whether the block was executed. # def converge_if_changed(*properties, &converge_block) if !converge_block raise ArgumentError, "converge_if_changed must be passed a block!" end properties = new_resource.class.state_properties.map { |p| p.name } if properties.empty? properties = properties.map { |p| p.to_sym } if current_resource # Collect the list of modified properties specified_properties = properties.select { |property| new_resource.property_is_set?(property) } modified = specified_properties.select { |p| new_resource.send(p) != current_resource.send(p) } if modified.empty? properties_str = if sensitive specified_properties.join(", ") else specified_properties.map { |p| "#{p}=#{new_resource.send(p).inspect}" }.join(", ") end Chef::Log.debug("Skipping update of #{new_resource}: has not changed any of the specified properties #{properties_str}.") return false end # Print the pretty green text and run the block property_size = modified.map { |p| p.size }.max modified.map! do |p| properties_str = if sensitive "(suppressed sensitive property)" else "#{new_resource.send(p).inspect} (was #{current_resource.send(p).inspect})" end " set #{p.to_s.ljust(property_size)} to #{properties_str}" end converge_by([ "update #{current_resource.identity}" ] + modified, &converge_block) else # The resource doesn't exist. Mark that we are *creating* this, and # write down any properties we are setting. property_size = properties.map { |p| p.size }.max created = properties.map do |property| default = " (default value)" unless new_resource.property_is_set?(property) properties_str = if sensitive "(suppressed sensitive property)" else new_resource.send(property).inspect end " set #{property.to_s.ljust(property_size)} to #{properties_str}#{default}" end converge_by([ "create #{new_resource.identity}" ] + created, &converge_block) end true end def self.provides(short_name, opts = {}, &block) Chef.provider_handler_map.set(short_name, self, opts, &block) end def self.provides?(node, resource) Chef::ProviderResolver.new(node, resource, :nothing).provided_by?(self) end # # Include attributes, public and protected methods from this Resource in # the provider. # # If this is set to true, delegate methods are included in the provider so # that you can call (for example) `attrname` and it will call # `new_resource.attrname`. # # The actual include does not happen until the first time the Provider # is instantiated (so that we don't have to worry about load order issues). # # @param include_resource_dsl [Boolean] Whether to include resource DSL or # not (defaults to `false`). # def self.include_resource_dsl(include_resource_dsl) @include_resource_dsl = include_resource_dsl end # Create the resource DSL module that forwards resource methods to new_resource # # @api private def self.include_resource_dsl_module(resource) if @include_resource_dsl && !defined?(@included_resource_dsl_module) provider_class = self @included_resource_dsl_module = Module.new do extend Forwardable define_singleton_method(:to_s) { "forwarder module for #{provider_class}" } define_singleton_method(:inspect) { to_s } # Add a delegator for each explicit property that will get the *current* value # of the property by default instead of the *actual* value. resource.class.properties.each do |name, property| class_eval(<<-EOM, __FILE__, __LINE__) def #{name}(*args, &block) # If no arguments were passed, we process "get" by defaulting # the value to current_resource, not new_resource. This helps # avoid issues where resources accidentally overwrite perfectly # valid stuff with default values. if args.empty? && !block if !new_resource.property_is_set?(__method__) && current_resource return current_resource.public_send(__method__) end end new_resource.public_send(__method__, *args, &block) end EOM end dsl_methods = resource.class.public_instance_methods + resource.class.protected_instance_methods - provider_class.instance_methods - resource.class.properties.keys def_delegators(:new_resource, *dsl_methods) end include @included_resource_dsl_module end end # Enables inline evaluation of resources in provider actions. # # Without this option, any resources declared inside the Provider are added # to the resource collection after the current position at the time the # action is executed. Because they are added to the primary resource # collection for the chef run, they can notify other resources outside # the Provider, and potentially be notified by resources outside the Provider # (but this is complicated by the fact that they don't exist until the # provider executes). In this mode, it is impossible to correctly set the # updated_by_last_action flag on the parent Provider resource, since it # executes and returns before its component resources are run. # # With this option enabled, each action creates a temporary run_context # with its own resource collection, evaluates the action's code in that # context, and then converges the resources created. If any resources # were updated, then this provider's new_resource will be marked updated. # # In this mode, resources created within the Provider cannot interact with # external resources via notifies, though notifications to other # resources within the Provider will work. Delayed notifications are executed # at the conclusion of the provider's action, *not* at the end of the # main chef run. # # This mode of evaluation is experimental, but is believed to be a better # set of tradeoffs than the append-after mode, so it will likely become # the default in a future major release of Chef. # def self.use_inline_resources extend InlineResources::ClassMethods include InlineResources end # Chef::Provider::InlineResources # Implementation of inline resource convergence for providers. See # Provider.use_inline_resources for a longer explanation. # # This code is restricted to a module so that it can be selectively # applied to providers on an opt-in basis. # # @api private module InlineResources # Create a child run_context, compile the block, and converge it. # # @api private def compile_and_converge_action(&block) old_run_context = run_context @run_context = run_context.create_child return_value = instance_eval(&block) Chef::Runner.new(run_context).converge return_value ensure if run_context.resource_collection.any? { |r| r.updated? } new_resource.updated_by_last_action(true) end @run_context = old_run_context end # Class methods for InlineResources. Overrides the `action` DSL method # with one that enables inline resource convergence. # # @api private module ClassMethods # Defines an action method on the provider, running the block to # compile the resources, converging them, and then checking if any # were updated (and updating new-resource if so) def action(name, &block) # We need the block directly in a method so that `super` works define_method("compile_action_#{name}", &block) # We try hard to use `def` because define_method doesn't show the method name in the stack. begin class_eval <<-EOM def action_#{name} compile_and_converge_action { compile_action_#{name} } end EOM rescue SyntaxError define_method("action_#{name}") { send("compile_action_#{name}") } end end end end protected def converge_actions @converge_actions ||= ConvergeActions.new(@new_resource, run_context, @action) end def recipe_eval(&block) # This block has new resource definitions within it, which # essentially makes it an in-line Chef run. Save our current # run_context and create one anew, so the new Chef run only # executes the embedded resources. # # TODO: timh,cw: 2010-5-14: This means that the resources within # this block cannot interact with resources outside, e.g., # manipulating notifies. converge_by ("evaluate block and run any associated actions") do saved_run_context = run_context begin @run_context = run_context.create_child instance_eval(&block) Chef::Runner.new(run_context).converge ensure @run_context = saved_run_context end end end module DeprecatedLWRPClass def const_missing(class_name) if Chef::Provider.deprecated_constants[class_name.to_sym] Chef.log_deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.") Chef::Provider.deprecated_constants[class_name.to_sym] else raise NameError, "uninitialized constant Chef::Provider::#{class_name}" end end # @api private def register_deprecated_lwrp_class(provider_class, class_name) # Register Chef::Provider::MyProvider with deprecation warnings if you # try to access it if Chef::Provider.const_defined?(class_name, false) Chef::Log.warn "Chef::Provider::#{class_name} already exists! Cannot create deprecation class for #{provider_class}" else Chef::Provider.deprecated_constants[class_name.to_sym] = provider_class end end def deprecated_constants raise "Deprecated constants should be called only on Chef::Provider" unless self == Chef::Provider @deprecated_constants ||= {} end end extend DeprecatedLWRPClass end end # Requiring things at the bottom breaks cycles require "chef/chef_class" require "chef/mixin/why_run" require "chef/resource_collection" require "chef/runner" chef-12.14.60/lib/chef/provider/000077500000000000000000000000001276456504500161755ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/apt_repository.rb000066400000000000000000000177021276456504500216140ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/dsl/declare_resource" require "chef/mixin/shell_out" require "chef/mixin/which" require "chef/http/simple" require "chef/provider/noop" class Chef class Provider class AptRepository < Chef::Provider use_inline_resources include Chef::Mixin::ShellOut extend Chef::Mixin::Which provides :apt_repository do which("apt-get") end def whyrun_supported? true end def load_current_resource end action :add do unless new_resource.key.nil? if is_key_id?(new_resource.key) && !has_cookbook_file?(new_resource.key) install_key_from_keyserver else install_key_from_uri end end declare_resource(:execute, "apt-cache gencaches") do ignore_failure true action :nothing end declare_resource(:apt_update, new_resource.name) do ignore_failure true action :nothing end components = if is_ppa_url?(new_resource.uri) && new_resource.components.empty? "main" else new_resource.components end repo = build_repo( new_resource.uri, new_resource.distribution, components, new_resource.trusted, new_resource.arch, new_resource.deb_src ) declare_resource(:file, "/etc/apt/sources.list.d/#{new_resource.name}.list") do owner "root" group "root" mode "0644" content repo sensitive new_resource.sensitive action :create notifies :run, "execute[apt-cache gencaches]", :immediately notifies :update, "apt_update[#{new_resource.name}]", :immediately if new_resource.cache_rebuild end end action :remove do if ::File.exist?("/etc/apt/sources.list.d/#{new_resource.name}.list") converge_by "Removing #{new_resource.name} repository from /etc/apt/sources.list.d/" do declare_resource(:file, "/etc/apt/sources.list.d/#{new_resource.name}.list") do sensitive new_resource.sensitive action :delete notifies :update, "apt_update[#{new_resource.name}]", :immediately if new_resource.cache_rebuild end declare_resource(:apt_update, new_resource.name) do ignore_failure true action :nothing end end end end def is_key_id?(id) id = id[2..-1] if id.start_with?("0x") id =~ /^\h+$/ && [8, 16, 40].include?(id.length) end def extract_fingerprints_from_cmd(cmd) so = shell_out(cmd) so.run_command so.stdout.split(/\n/).map do |t| if z = t.match(/^ +Key fingerprint = ([0-9A-F ]+)/) z[1].split.join end end.compact end def key_is_valid?(cmd, key) valid = true so = shell_out(cmd) so.run_command so.stdout.split(/\n/).map do |t| if t =~ %r{^\/#{key}.*\[expired: .*\]$} Chef::Log.debug "Found expired key: #{t}" valid = false break end end Chef::Log.debug "key #{key} #{valid ? "is valid" : "is not valid"}" valid end def cookbook_name new_resource.cookbook || new_resource.cookbook_name end def has_cookbook_file?(fn) run_context.has_cookbook_file_in_cookbook?(cookbook_name, fn) end def no_new_keys?(file) installed_keys = extract_fingerprints_from_cmd("apt-key finger") proposed_keys = extract_fingerprints_from_cmd("gpg --with-fingerprint #{file}") (installed_keys & proposed_keys).sort == proposed_keys.sort end def install_key_from_uri key_name = new_resource.key.split(%r{\/}).last cached_keyfile = ::File.join(Chef::Config[:file_cache_path], key_name) type = if new_resource.key.start_with?("http") :remote_file elsif has_cookbook_file?(new_resource.key) :cookbook_file else raise Chef::Exceptions::FileNotFound, "Cannot locate key file" end declare_resource(type, cached_keyfile) do source new_resource.key mode "0644" sensitive new_resource.sensitive action :create end raise "The key #{cached_keyfile} is invalid and cannot be used to verify an apt repository." unless key_is_valid?("gpg #{cached_keyfile}", "") declare_resource(:execute, "apt-key add #{cached_keyfile}") do sensitive new_resource.sensitive action :run not_if do no_new_keys?(cached_keyfile) end notifies :run, "execute[apt-cache gencaches]", :immediately end end def install_key_from_keyserver(key = new_resource.key, keyserver = new_resource.keyserver) cmd = "apt-key adv --recv" cmd << " --keyserver-options http-proxy=#{new_resource.key_proxy}" if new_resource.key_proxy cmd << " --keyserver " cmd << if keyserver.start_with?("hkp://") keyserver else "hkp://#{keyserver}:80" end cmd << " #{key}" declare_resource(:execute, "install-key #{key}") do command cmd sensitive new_resource.sensitive not_if do present = extract_fingerprints_from_cmd("apt-key finger").any? do |fp| fp.end_with? key.upcase end present && key_is_valid?("apt-key list", key.upcase) end notifies :run, "execute[apt-cache gencaches]", :immediately end raise "The key #{key} is invalid and cannot be used to verify an apt repository." unless key_is_valid?("apt-key list", key.upcase) end def install_ppa_key(owner, repo) url = "https://launchpad.net/api/1.0/~#{owner}/+archive/#{repo}" key_id = Chef::HTTP::Simple.new(url).get("signing_key_fingerprint").delete('"') install_key_from_keyserver(key_id, "keyserver.ubuntu.com") rescue Net::HTTPServerException => e raise "Could not access Launchpad ppa API: #{e.message}" end def is_ppa_url?(url) url.start_with?("ppa:") end def make_ppa_url(ppa) return unless is_ppa_url?(ppa) owner, repo = ppa[4..-1].split("/") repo ||= "ppa" install_ppa_key(owner, repo) "http://ppa.launchpad.net/#{owner}/#{repo}/ubuntu" end def build_repo(uri, distribution, components, trusted, arch, add_src = false) uri = make_ppa_url(uri) if is_ppa_url?(uri) uri = '"' + uri + '"' unless uri.start_with?("'", '"') components = Array(components).join(" ") options = [] options << "arch=#{arch}" if arch options << "trusted=yes" if trusted optstr = unless options.empty? "[" + options.join(" ") + "]" end info = [ optstr, uri, distribution, components ].compact.join(" ") repo = "deb #{info}\n" repo << "deb-src #{info}\n" if add_src repo end end end end Chef::Provider::Noop.provides :apt_repository chef-12.14.60/lib/chef/provider/apt_update.rb000066400000000000000000000043321276456504500206520ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 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" require "chef/provider/noop" require "chef/mixin/which" class Chef class Provider class AptUpdate < Chef::Provider use_inline_resources extend Chef::Mixin::Which provides :apt_update do which("apt-get") end APT_CONF_DIR = "/etc/apt/apt.conf.d" STAMP_DIR = "/var/lib/apt/periodic" def whyrun_supported? true end def load_current_resource end action :periodic do if !apt_up_to_date? converge_by "update new lists of packages" do do_update end end end action :update do converge_by "force update new lists of packages" do do_update end end private # Determines whether we need to run `apt-get update` # # @return [Boolean] def apt_up_to_date? ::File.exist?("#{STAMP_DIR}/update-success-stamp") && ::File.mtime("#{STAMP_DIR}/update-success-stamp") > Time.now - new_resource.frequency end def do_update [STAMP_DIR, APT_CONF_DIR].each do |d| declare_resource(:directory, d) do recursive true end end declare_resource(:file, "#{APT_CONF_DIR}/15update-stamp") do content "APT::Update::Post-Invoke-Success {\"touch #{STAMP_DIR}/update-success-stamp 2>/dev/null || true\";};" action :create_if_missing end declare_resource(:execute, "apt-get -q update") end end end end Chef::Provider::Noop.provides :apt_update chef-12.14.60/lib/chef/provider/batch.rb000066400000000000000000000025121276456504500176030ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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/windows_script" class Chef class Provider class Batch < Chef::Provider::WindowsScript provides :batch, os: "windows" def initialize(new_resource, run_context) super(new_resource, run_context, ".bat") end def command basepath = is_forced_32bit ? wow64_directory : run_context.node["kernel"]["os_info"]["system_directory"] interpreter_path = Chef::Util::PathHelper.join(basepath, interpreter) "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\"" end def flags @new_resource.flags.nil? ? "/c" : new_resource.flags + " /c" end end end end chef-12.14.60/lib/chef/provider/breakpoint.rb000066400000000000000000000021131276456504500206550ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Provider class Breakpoint < Chef::Provider provides :breakpoint def load_current_resource end def action_break if defined?(Shell) && Shell.running? run_context.resource_collection.iterator.pause @new_resource.updated_by_last_action(true) run_context.resource_collection.iterator end end end end end chef-12.14.60/lib/chef/provider/cookbook_file.rb000066400000000000000000000030651276456504500213330ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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/file" require "chef/deprecation/provider/cookbook_file" require "chef/deprecation/warnings" class Chef class Provider class CookbookFile < Chef::Provider::File provides :cookbook_file extend Chef::Deprecation::Warnings include Chef::Deprecation::Provider::CookbookFile add_deprecation_warnings_for(Chef::Deprecation::Provider::CookbookFile.instance_methods) def initialize(new_resource, run_context) @content_class = Chef::Provider::CookbookFile::Content super end def load_current_resource @current_resource = Chef::Resource::CookbookFile.new(@new_resource.name) super end private def managing_content? return true if @new_resource.checksum return true if !@new_resource.source.nil? && @action != :create_if_missing false end end end end chef-12.14.60/lib/chef/provider/cookbook_file/000077500000000000000000000000001276456504500210025ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/cookbook_file/content.rb000066400000000000000000000032171276456504500230040ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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/file_content_management/content_base" require "chef/file_content_management/tempfile" class Chef class Provider class CookbookFile class Content < Chef::FileContentManagement::ContentBase private def file_for_provider cookbook = run_context.cookbook_collection[resource_cookbook] file_cache_location = cookbook.preferred_filename_on_disk_location(run_context.node, :files, @new_resource.source) if file_cache_location.nil? nil else tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile tempfile.close Chef::Log.debug("#{@new_resource} staging #{file_cache_location} to #{tempfile.path}") FileUtils.cp(file_cache_location, tempfile.path) tempfile end end def resource_cookbook @new_resource.cookbook || @new_resource.cookbook_name end end end end end chef-12.14.60/lib/chef/provider/cron.rb000066400000000000000000000210301276456504500174570ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Copyright:: Copyright 2009-2016, Bryan McLellan # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/mixin/command" require "chef/provider" class Chef class Provider class Cron < Chef::Provider include Chef::Mixin::Command provides :cron, os: ["!aix", "!solaris2"] SPECIAL_TIME_VALUES = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly] CRON_ATTRIBUTES = [:minute, :hour, :day, :month, :weekday, :time, :command, :mailto, :path, :shell, :home, :environment] WEEKDAY_SYMBOLS = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday] CRON_PATTERN = /\A([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+|[a-zA-Z]{3})\s([-0-9*,\/]+|[a-zA-Z]{3})\s(.*)/ SPECIAL_PATTERN = /\A(@(#{SPECIAL_TIME_VALUES.join('|')}))\s(.*)/ ENV_PATTERN = /\A(\S+)=(\S*)/ def initialize(new_resource, run_context) super(new_resource, run_context) @cron_exists = false @cron_empty = false end attr_accessor :cron_exists, :cron_empty def whyrun_supported? true end def load_current_resource crontab_lines = [] @current_resource = Chef::Resource::Cron.new(@new_resource.name) @current_resource.user(@new_resource.user) @cron_exists = false if crontab = read_crontab cron_found = false crontab.each_line do |line| case line.chomp when "# Chef Name: #{@new_resource.name}" Chef::Log.debug("Found cron '#{@new_resource.name}'") cron_found = true @cron_exists = true next when ENV_PATTERN set_environment_var($1, $2) if cron_found next when SPECIAL_PATTERN if cron_found @current_resource.time($2.to_sym) @current_resource.command($3) cron_found = false end when CRON_PATTERN if cron_found @current_resource.minute($1) @current_resource.hour($2) @current_resource.day($3) @current_resource.month($4) @current_resource.weekday($5) @current_resource.command($6) cron_found = false end next else cron_found = false # We've got a Chef comment with no following crontab line next end end Chef::Log.debug("Cron '#{@new_resource.name}' not found") unless @cron_exists else Chef::Log.debug("Cron empty for '#{@new_resource.user}'") @cron_empty = true end @current_resource end def cron_different? CRON_ATTRIBUTES.any? do |cron_var| @new_resource.send(cron_var) != @current_resource.send(cron_var) end end def action_create crontab = String.new newcron = String.new cron_found = false newcron = get_crontab_entry if @cron_exists unless cron_different? Chef::Log.debug("Skipping existing cron entry '#{@new_resource.name}'") return end read_crontab.each_line do |line| case line.chomp when "# Chef Name: #{@new_resource.name}" cron_found = true next when ENV_PATTERN crontab << line unless cron_found next when SPECIAL_PATTERN if cron_found cron_found = false crontab << newcron next end when CRON_PATTERN if cron_found cron_found = false crontab << newcron next end else if cron_found # We've got a Chef comment with no following crontab line crontab << newcron cron_found = false end end crontab << line end # Handle edge case where the Chef comment is the last line in the current crontab crontab << newcron if cron_found converge_by("update crontab entry for #{@new_resource}") do write_crontab crontab Chef::Log.info("#{@new_resource} updated crontab entry") end else crontab = read_crontab unless @cron_empty crontab << newcron converge_by("add crontab entry for #{@new_resource}") do write_crontab crontab Chef::Log.info("#{@new_resource} added crontab entry") end end end def action_delete if @cron_exists crontab = String.new cron_found = false read_crontab.each_line do |line| case line.chomp when "# Chef Name: #{@new_resource.name}" cron_found = true next when ENV_PATTERN next if cron_found when SPECIAL_PATTERN if cron_found cron_found = false next end when CRON_PATTERN if cron_found cron_found = false next end else # We've got a Chef comment with no following crontab line cron_found = false end crontab << line end description = cron_found ? "remove #{@new_resource.name} from crontab" : "save unmodified crontab" converge_by(description) do write_crontab crontab Chef::Log.info("#{@new_resource} deleted crontab entry") end end end private def set_environment_var(attr_name, attr_value) if %w{MAILTO PATH SHELL HOME}.include?(attr_name) @current_resource.send(attr_name.downcase.to_sym, attr_value) else @current_resource.environment(@current_resource.environment.merge(attr_name => attr_value)) end end def read_crontab crontab = nil status = popen4("crontab -l -u #{@new_resource.user}") do |pid, stdin, stdout, stderr| crontab = stdout.read end if status.exitstatus > 1 raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}" end crontab end def write_crontab(crontab) write_exception = false status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr| begin stdin.write crontab rescue Errno::EPIPE => e # popen4 could yield while child has already died. write_exception = true Chef::Log.debug("#{e.message}") end end if status.exitstatus > 0 || write_exception raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{status.exitstatus}" end end def get_crontab_entry newcron = "" newcron << "# Chef Name: #{new_resource.name}\n" [ :mailto, :path, :shell, :home ].each do |v| newcron << "#{v.to_s.upcase}=\"#{@new_resource.send(v)}\"\n" if @new_resource.send(v) end @new_resource.environment.each do |name, value| newcron << "#{name}=#{value}\n" end if @new_resource.time newcron << "@#{@new_resource.time} #{@new_resource.command}\n" else newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n" end newcron end def weekday_in_crontab weekday_in_crontab = WEEKDAY_SYMBOLS.index(@new_resource.weekday) if weekday_in_crontab.nil? @new_resource.weekday else weekday_in_crontab.to_s end end end end end chef-12.14.60/lib/chef/provider/cron/000077500000000000000000000000001276456504500171365ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/cron/aix.rb000066400000000000000000000032541276456504500202500ustar00rootroot00000000000000# # Author:: Kaustubh Deorukhkar () # Copyright:: Copyright 2013-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/cron/unix" class Chef class Provider class Cron class Aix < Chef::Provider::Cron::Unix provides :cron, os: "aix" private # For AIX we ignore env vars/[ :mailto, :path, :shell, :home ] def get_crontab_entry if env_vars_are_set? raise Chef::Exceptions::Cron, "Aix cron entry does not support environment variables. Please set them in script and use script in cron." end newcron = "" newcron << "# Chef Name: #{new_resource.name}\n" newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday}" newcron << " #{@new_resource.command}\n" newcron end def env_vars_are_set? @new_resource.environment.length > 0 || !@new_resource.mailto.nil? || !@new_resource.path.nil? || !@new_resource.shell.nil? || !@new_resource.home.nil? end end end end end chef-12.14.60/lib/chef/provider/cron/solaris.rb000066400000000000000000000016161276456504500211430ustar00rootroot00000000000000# # Author:: Kaustubh Deorukhkar () # Copyright:: Copyright 2013-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/cron/unix" # Just to create an alias so 'Chef::Provider::Cron::Solaris' is exposed and accessible to existing consumers of class. Chef::Provider::Cron::Solaris = Chef::Provider::Cron::Unix chef-12.14.60/lib/chef/provider/cron/unix.rb000066400000000000000000000054141276456504500204520ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Author:: Toomas Pelberg (toomasp@gmx.net) # Copyright:: Copyright 2009-2016, Bryan McLellan # Copyright:: Copyright 2010-2016, Toomas Pelberg # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/provider" require "chef/provider/cron" class Chef class Provider class Cron class Unix < Chef::Provider::Cron include Chef::Mixin::ShellOut provides :cron, os: "solaris2" private def read_crontab crontab = shell_out("/usr/bin/crontab -l", :user => @new_resource.user) status = crontab.status.exitstatus Chef::Log.debug crontab.format_for_exception if status > 0 if status > 1 raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status}" end return nil if status > 0 crontab.stdout.chomp << "\n" end def write_crontab(crontab) tempcron = Tempfile.new("chef-cron") tempcron << crontab tempcron.flush tempcron.chmod(0644) exit_status = 0 error_message = "" begin crontab_write = shell_out("/usr/bin/crontab #{tempcron.path}", :user => @new_resource.user) stderr = crontab_write.stderr exit_status = crontab_write.status.exitstatus # solaris9, 10 on some failures for example invalid 'mins' in crontab fails with exit code of zero :( if stderr && stderr.include?("errors detected in input, no crontab file generated") error_message = stderr exit_status = 1 end rescue Chef::Exceptions::Exec => e Chef::Log.debug(e.message) exit_status = 1 error_message = e.message rescue ArgumentError => e # usually raised on invalid user. Chef::Log.debug(e.message) exit_status = 1 error_message = e.message end tempcron.close! if exit_status > 0 raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{exit_status}, message: #{error_message}" end end end end end end chef-12.14.60/lib/chef/provider/deploy.rb000066400000000000000000000420371276456504500200240ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/command" require "chef/mixin/from_file" require "chef/provider/git" require "chef/provider/subversion" require "chef/dsl/recipe" require "chef/util/path_helper" class Chef class Provider class Deploy < Chef::Provider include Chef::DSL::Recipe include Chef::Mixin::FromFile include Chef::Mixin::Command attr_reader :scm_provider, :release_path, :shared_path, :previous_release_path def initialize(new_resource, run_context) super(new_resource, run_context) # will resolve to either git or svn based on resource attributes, # and will create a resource corresponding to that provider @scm_provider = new_resource.scm_provider.new(new_resource, run_context) # @configuration is not used by Deploy, it is only for backwards compat with # chef-deploy or capistrano hooks that might use it to get environment information @configuration = @new_resource.to_hash @configuration[:environment] = @configuration[:environment] && @configuration[:environment]["RAILS_ENV"] end def whyrun_supported? true end def load_current_resource @scm_provider.load_current_resource @release_path = @new_resource.deploy_to + "/releases/#{release_slug}" @shared_path = @new_resource.shared_path end def sudo(command, &block) execute(command, &block) end def run(command, &block) exec = execute(command, &block) exec.user(@new_resource.user) if @new_resource.user exec.group(@new_resource.group) if @new_resource.group exec.cwd(release_path) unless exec.cwd exec.environment(@new_resource.environment) unless exec.environment converge_by("execute #{command}") do exec end end def define_resource_requirements requirements.assert(:rollback) do |a| a.assertion { all_releases[-2] } a.failure_message(RuntimeError, "There is no release to rollback to!") #There is no reason to assume 2 deployments in a single chef run, hence fails in whyrun. end [ @new_resource.before_migrate, @new_resource.before_symlink, @new_resource.before_restart, @new_resource.after_restart ].each do |script| requirements.assert(:deploy, :force_deploy) do |a| callback_file = "#{release_path}/#{script}" a.assertion do if script && script.class == String ::File.exist?(callback_file) else true end end a.failure_message(RuntimeError, "Can't find your callback file #{callback_file}") a.whyrun("Would assume callback file #{callback_file} included in release") end end end def action_deploy save_release_state if deployed?(release_path ) if current_release?(release_path ) Chef::Log.debug("#{@new_resource} is the latest version") else rollback_to release_path end else with_rollback_on_error do deploy end end end def action_force_deploy if deployed?(release_path) converge_by("delete deployed app at #{release_path} prior to force-deploy") do Chef::Log.info("Already deployed app at #{release_path}, forcing.") FileUtils.rm_rf(release_path) Chef::Log.info("#{@new_resource} forcing deploy of already deployed app at #{release_path}") end end # Alternatives: # * Move release_path directory before deploy and move it back when error occurs # * Rollback to previous commit # * Do nothing - because deploy is force, it will be retried in short time # Because last is simplest, keep it deploy end def action_rollback rollback_to all_releases[-2] end def rollback_to(target_release_path) @release_path = target_release_path rp_index = all_releases.index(release_path) releases_to_nuke = all_releases[(rp_index + 1)..-1] rollback releases_to_nuke.each do |i| converge_by("roll back by removing release #{i}") do Chef::Log.info "#{@new_resource} removing release: #{i}" FileUtils.rm_rf i end release_deleted(i) end end def deploy verify_directories_exist update_cached_repo # no converge-by - scm provider will dothis enforce_ownership copy_cached_repo install_gems enforce_ownership callback(:before_migrate, @new_resource.before_migrate) migrate callback(:before_symlink, @new_resource.before_symlink) symlink callback(:before_restart, @new_resource.before_restart) restart callback(:after_restart, @new_resource.after_restart) cleanup! Chef::Log.info "#{@new_resource} deployed to #{@new_resource.deploy_to}" end def rollback Chef::Log.info "#{@new_resource} rolling back to previous release #{release_path}" symlink Chef::Log.info "#{@new_resource} restarting with previous release" restart end def callback(what, callback_code = nil) @collection = Chef::ResourceCollection.new case callback_code when Proc Chef::Log.info "#{@new_resource} running callback #{what}" recipe_eval(&callback_code) when String run_callback_from_file("#{release_path}/#{callback_code}") when nil run_callback_from_file("#{release_path}/deploy/#{what}.rb") end end def migrate run_symlinks_before_migrate if @new_resource.migrate enforce_ownership environment = @new_resource.environment env_info = environment && environment.map do |key_and_val| "#{key_and_val.first}='#{key_and_val.last}'" end.join(" ") converge_by("execute migration command #{@new_resource.migration_command}") do Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}" shell_out!(@new_resource.migration_command, run_options(:cwd => release_path, :log_level => :info)) end end end def symlink purge_tempfiles_from_current_release link_tempfiles_to_current_release link_current_release_to_production Chef::Log.info "#{@new_resource} updated symlinks" end def restart if restart_cmd = @new_resource.restart_command if restart_cmd.kind_of?(Proc) Chef::Log.info("#{@new_resource} restarting app with embedded recipe") recipe_eval(&restart_cmd) else converge_by("restart app using command #{@new_resource.restart_command}") do Chef::Log.info("#{@new_resource} restarting app") shell_out!(@new_resource.restart_command, run_options(:cwd => @new_resource.current_path)) end end end end def cleanup! converge_by("update release history data") do release_created(release_path) end chop = -1 - @new_resource.keep_releases all_releases[0..chop].each do |old_release| converge_by("remove old release #{old_release}") do Chef::Log.info "#{@new_resource} removing old release #{old_release}" FileUtils.rm_rf(old_release) end release_deleted(old_release) end end def all_releases Dir.glob(Chef::Util::PathHelper.escape_glob_dir(@new_resource.deploy_to) + "/releases/*").sort end def update_cached_repo if @new_resource.svn_force_export # TODO assertion, non-recoverable - @scm_provider must be svn if force_export? svn_force_export else run_scm_sync end end def run_scm_sync @scm_provider.run_action(:sync) end def svn_force_export Chef::Log.info "#{@new_resource} exporting source repository" @scm_provider.run_action(:force_export) end def copy_cached_repo target_dir_path = @new_resource.deploy_to + "/releases" converge_by("deploy from repo to #{target_dir_path} ") do FileUtils.rm_rf(release_path) if ::File.exist?(release_path) FileUtils.mkdir_p(target_dir_path) FileUtils.cp_r(::File.join(@new_resource.destination, "."), release_path, :preserve => true) Chef::Log.info "#{@new_resource} copied the cached checkout to #{release_path}" end end def enforce_ownership converge_by("force ownership of #{@new_resource.deploy_to} to #{@new_resource.group}:#{@new_resource.user}") do FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to, :force => true) Chef::Log.info("#{@new_resource} set user to #{@new_resource.user}") if @new_resource.user Chef::Log.info("#{@new_resource} set group to #{@new_resource.group}") if @new_resource.group end end def verify_directories_exist create_dir_unless_exists(@new_resource.deploy_to) create_dir_unless_exists(@new_resource.shared_path) end def link_current_release_to_production converge_by(["remove existing link at #{@new_resource.current_path}", "link release #{release_path} into production at #{@new_resource.current_path}"]) do FileUtils.rm_f(@new_resource.current_path) begin FileUtils.ln_sf(release_path, @new_resource.current_path) rescue => e raise Chef::Exceptions::FileNotFound.new("Cannot symlink current release to production: #{e.message}") end Chef::Log.info "#{@new_resource} linked release #{release_path} into production at #{@new_resource.current_path}" end enforce_ownership end def run_symlinks_before_migrate links_info = @new_resource.symlink_before_migrate.map { |src, dst| "#{src} => #{dst}" }.join(", ") converge_by("make pre-migration symlinks: #{links_info}") do @new_resource.symlink_before_migrate.each do |src, dest| begin FileUtils.ln_sf(@new_resource.shared_path + "/#{src}", release_path + "/#{dest}") rescue => e raise Chef::Exceptions::FileNotFound.new("Cannot symlink #{@new_resource.shared_path}/#{src} to #{release_path}/#{dest} before migrate: #{e.message}") end end Chef::Log.info "#{@new_resource} made pre-migration symlinks" end end def link_tempfiles_to_current_release dirs_info = @new_resource.create_dirs_before_symlink.join(",") @new_resource.create_dirs_before_symlink.each do |dir| create_dir_unless_exists(release_path + "/#{dir}") end Chef::Log.info("#{@new_resource} created directories before symlinking: #{dirs_info}") links_info = @new_resource.symlinks.map { |src, dst| "#{src} => #{dst}" }.join(", ") converge_by("link shared paths into current release: #{links_info}") do @new_resource.symlinks.each do |src, dest| begin FileUtils.ln_sf(::File.join(@new_resource.shared_path, src), ::File.join(release_path, dest)) rescue => e raise Chef::Exceptions::FileNotFound.new("Cannot symlink shared data #{::File.join(@new_resource.shared_path, src)} to #{::File.join(release_path, dest)}: #{e.message}") end end Chef::Log.info("#{@new_resource} linked shared paths into current release: #{links_info}") end run_symlinks_before_migrate enforce_ownership end def create_dirs_before_symlink end def purge_tempfiles_from_current_release log_info = @new_resource.purge_before_symlink.join(", ") converge_by("purge directories in checkout #{log_info}") do @new_resource.purge_before_symlink.each { |dir| FileUtils.rm_rf(release_path + "/#{dir}") } Chef::Log.info("#{@new_resource} purged directories in checkout #{log_info}") end end protected # Internal callback, called after copy_cached_repo. # Override if you need to keep state externally. # Note that YOU are responsible for implementing whyrun-friendly behavior # in any actions you take in this callback. def release_created(release_path) end # Note that YOU are responsible for using appropriate whyrun nomenclature # Override if you need to keep state externally. # Note that YOU are responsible for implementing whyrun-friendly behavior # in any actions you take in this callback. def release_deleted(release_path) end def release_slug raise Chef::Exceptions::Override, "You must override release_slug in #{self}" end def install_gems gem_resource_collection_runner.converge end def gem_resource_collection_runner child_context = run_context.create_child gem_packages.each { |rbgem| child_context.resource_collection.insert(rbgem) } Chef::Runner.new(child_context) end def gem_packages return [] unless ::File.exist?("#{release_path}/gems.yml") gems = YAML.load(IO.read("#{release_path}/gems.yml")) gems.map do |g| r = Chef::Resource::GemPackage.new(g[:name], run_context) r.version g[:version] r.action :install r.source "http://gems.github.com" r end end def run_options(run_opts = {}) run_opts[:user] = @new_resource.user if @new_resource.user run_opts[:group] = @new_resource.group if @new_resource.group run_opts[:environment] = @new_resource.environment if @new_resource.environment run_opts[:log_tag] = @new_resource.to_s run_opts[:log_level] ||= :debug if run_opts[:log_level] == :info if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info? run_opts[:live_stream] = STDOUT end end run_opts end def run_callback_from_file(callback_file) Chef::Log.info "#{@new_resource} queueing checkdeploy hook #{callback_file}" recipe_eval do Dir.chdir(release_path) do from_file(callback_file) if ::File.exist?(callback_file) end end end def create_dir_unless_exists(dir) if ::File.directory?(dir) Chef::Log.debug "#{@new_resource} not creating #{dir} because it already exists" return false end converge_by("create new directory #{dir}") do begin FileUtils.mkdir_p(dir) Chef::Log.debug "#{@new_resource} created directory #{dir}" if @new_resource.user FileUtils.chown(@new_resource.user, nil, dir) Chef::Log.debug("#{@new_resource} set user to #{@new_resource.user} for #{dir}") end if @new_resource.group FileUtils.chown(nil, @new_resource.group, dir) Chef::Log.debug("#{@new_resource} set group to #{@new_resource.group} for #{dir}") end rescue => e raise Chef::Exceptions::FileNotFound.new("Cannot create directory #{dir}: #{e.message}") end end end def with_rollback_on_error yield rescue ::Exception => e if @new_resource.rollback_on_error Chef::Log.warn "Error on deploying #{release_path}: #{e.message}" failed_release = release_path if previous_release_path @release_path = previous_release_path rollback end converge_by("remove failed deploy #{failed_release}") do Chef::Log.info "Removing failed deploy #{failed_release}" FileUtils.rm_rf failed_release end release_deleted(failed_release) end raise end def save_release_state if ::File.exists?(@new_resource.current_path) release = ::File.readlink(@new_resource.current_path) @previous_release_path = release if ::File.exists?(release) end end def deployed?(release) all_releases.include?(release) end def current_release?(release) @previous_release_path == release end end end end chef-12.14.60/lib/chef/provider/deploy/000077500000000000000000000000001276456504500174715ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/deploy/revision.rb000066400000000000000000000057251276456504500216650ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Tim Hinderliter () # Author:: Seth Falcon () # Copyright:: Copyright 2009-2016, Daniel DeLeo # Copyright:: Copyright 2010-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" require "chef/provider/deploy" require "chef/json_compat" class Chef class Provider class Deploy class Revision < Chef::Provider::Deploy provides :deploy_revision provides :deploy_branch def all_releases sorted_releases end def action_deploy validate_release_history! super end def cleanup! super known_releases = sorted_releases Dir["#{Chef::Util::PathHelper.escape_glob_dir(new_resource.deploy_to)}/releases/*"].each do |release_dir| unless known_releases.include?(release_dir) converge_by("Remove unknown release in #{release_dir}") do FileUtils.rm_rf(release_dir) end end end end protected def release_created(release) sorted_releases { |r| r.delete(release); r << release } end def release_deleted(release) sorted_releases { |r| r.delete(release) } end def release_slug scm_provider.revision_slug end private def sorted_releases cache = load_cache if block_given? yield cache save_cache(cache) end cache end def validate_release_history! sorted_releases do |release_list| release_list.each do |path| release_list.delete(path) unless ::File.exist?(path) end end end def sorted_releases_from_filesystem Dir.glob(Chef::Util::PathHelper.escape_glob_dir(new_resource.deploy_to) + "/releases/*").sort_by { |d| ::File.ctime(d) } end def load_cache begin Chef::JSONCompat.parse(Chef::FileCache.load("revision-deploys/#{new_resource.name}")) rescue Chef::Exceptions::FileNotFound sorted_releases_from_filesystem end end def save_cache(cache) Chef::FileCache.store("revision-deploys/#{new_resource.name}", Chef::JSONCompat.to_json(cache)) cache end end end end end chef-12.14.60/lib/chef/provider/deploy/timestamped.rb000066400000000000000000000017161276456504500223370ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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 Provider class Deploy class Timestamped < Chef::Provider::Deploy provides :timestamped_deploy provides :deploy protected def release_slug Time.now.utc.strftime("%Y%m%d%H%M%S") end end end end end chef-12.14.60/lib/chef/provider/directory.rb000066400000000000000000000144021276456504500205270ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/log" require "chef/resource/directory" require "chef/provider" require "chef/provider/file" require "fileutils" class Chef class Provider class Directory < Chef::Provider::File provides :directory def whyrun_supported? true end def load_current_resource @current_resource = Chef::Resource::Directory.new(@new_resource.name) @current_resource.path(@new_resource.path) if ::File.exists?(@current_resource.path) && @action != :create_if_missing load_resource_attributes_from_file(@current_resource) end @current_resource end def define_resource_requirements # deep inside FAC we have to assert requirements, so call FACs hook to set that up access_controls.define_resource_requirements requirements.assert(:create) do |a| # Make sure the parent dir exists, or else fail. # for why run, print a message explaining the potential error. parent_directory = ::File.dirname(@new_resource.path) a.assertion do if @new_resource.recursive does_parent_exist = lambda do |base_dir| base_dir = ::File.dirname(base_dir) if ::File.exist?(base_dir) ::File.directory?(base_dir) else does_parent_exist.call(base_dir) end end does_parent_exist.call(@new_resource.path) else ::File.directory?(parent_directory) end end a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist, cannot create #{@new_resource.path}") a.whyrun("Assuming directory #{parent_directory} would have been created") end requirements.assert(:create) do |a| parent_directory = ::File.dirname(@new_resource.path) a.assertion do if @new_resource.recursive # find the lowest-level directory in @new_resource.path that already exists # make sure we have write permissions to that directory is_parent_writable = lambda do |base_dir| base_dir = ::File.dirname(base_dir) if ::File.exists?(base_dir) if Chef::FileAccessControl.writable?(base_dir) true elsif Chef::Util::PathHelper.is_sip_path?(base_dir, node) Chef::Util::PathHelper.writable_sip_path?(base_dir) else false end else is_parent_writable.call(base_dir) end end is_parent_writable.call(@new_resource.path) else # in why run mode & parent directory does not exist no permissions check is required # If not in why run, permissions must be valid and we rely on prior assertion that dir exists if !whyrun_mode? || ::File.exists?(parent_directory) if Chef::FileAccessControl.writable?(parent_directory) true elsif Chef::Util::PathHelper.is_sip_path?(parent_directory, node) Chef::Util::PathHelper.writable_sip_path?(@new_resource.path) else false end else true end end end a.failure_message(Chef::Exceptions::InsufficientPermissions, "Cannot create #{@new_resource} at #{@new_resource.path} due to insufficient permissions") end requirements.assert(:delete) do |a| a.assertion do if ::File.exists?(@new_resource.path) ::File.directory?(@new_resource.path) && Chef::FileAccessControl.writable?(@new_resource.path) else true end end a.failure_message(RuntimeError, "Cannot delete #{@new_resource} at #{@new_resource.path}!") # No why-run handling here: # * if we don't have permissions, this is unlikely to be changed earlier in the run # * if the target is a file (not a dir), there's no reasonable path by which this would have been changed end end def action_create unless ::File.exists?(@new_resource.path) converge_by("create new directory #{@new_resource.path}") do if @new_resource.recursive == true ::FileUtils.mkdir_p(@new_resource.path) else ::Dir.mkdir(@new_resource.path) end Chef::Log.info("#{@new_resource} created directory #{@new_resource.path}") end end do_acl_changes do_selinux(true) load_resource_attributes_from_file(@new_resource) end def action_delete if ::File.exists?(@new_resource.path) converge_by("delete existing directory #{@new_resource.path}") do if @new_resource.recursive == true # 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) Chef::Log.info("#{@new_resource} deleted #{@new_resource.path}") end end end end private def managing_content? false end end end end chef-12.14.60/lib/chef/provider/dsc_resource.rb000066400000000000000000000145201276456504500212040ustar00rootroot00000000000000# # Author:: Adam Edwards () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # 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/util/powershell/cmdlet" require "chef/util/dsc/local_configuration_manager" require "chef/mixin/powershell_type_coercions" require "chef/util/dsc/resource_store" class Chef class Provider class DscResource < Chef::Provider include Chef::Mixin::PowershellTypeCoercions provides :dsc_resource, os: "windows" def initialize(new_resource, run_context) super @new_resource = new_resource @module_name = new_resource.module_name @reboot_resource = nil end def action_run if ! test_resource converge_by(generate_description) do result = set_resource reboot_if_required end end end def load_current_resource end def whyrun_supported? true end def define_resource_requirements requirements.assert(:run) do |a| a.assertion { supports_dsc_invoke_resource? } err = ["You must have Powershell version >= 5.0.10018.0 to use dsc_resource."] a.failure_message Chef::Exceptions::ProviderNotFound, err a.whyrun err + ["Assuming a previous resource installs Powershell 5.0.10018.0 or higher."] a.block_action! end requirements.assert(:run) do |a| a.assertion { supports_refresh_mode_enabled? || dsc_refresh_mode_disabled? } err = ["The LCM must have its RefreshMode set to Disabled for" \ " PowerShell versions before 5.0.10586.0."] a.failure_message Chef::Exceptions::ProviderNotFound, err.join(" ") a.whyrun err + ["Assuming a previous resource sets the RefreshMode."] a.block_action! end end protected def local_configuration_manager @local_configuration_manager ||= Chef::Util::DSC::LocalConfigurationManager.new( node, nil ) end def resource_store Chef::Util::DSC::ResourceStore.instance end def supports_dsc_invoke_resource? run_context && Chef::Platform.supports_dsc_invoke_resource?(node) end def dsc_refresh_mode_disabled? Chef::Platform.dsc_refresh_mode_disabled?(node) end def supports_refresh_mode_enabled? Chef::Platform.supports_refresh_mode_enabled?(node) end def generate_description @converge_description end def dsc_resource_name new_resource.resource.to_s end def module_name @module_name ||= begin found = resource_store.find(dsc_resource_name) r = case found.length when 0 raise Chef::Exceptions::ResourceNotFound, "Could not find #{dsc_resource_name}. Check to make "\ "sure that it shows up when running Get-DscResource" when 1 if found[0]["Module"].nil? "PSDesiredStateConfiguration" # default DSC module else found[0]["Module"]["Name"] end else raise Chef::Exceptions::MultipleDscResourcesFound, found end end end def test_resource result = invoke_resource(:test) add_dsc_verbose_log(result) return_dsc_resource_result(result, "InDesiredState") end def set_resource result = invoke_resource(:set) add_dsc_verbose_log(result) create_reboot_resource if return_dsc_resource_result(result, "RebootRequired") result.return_value end def add_dsc_verbose_log(result) # We really want this information from the verbose stream, # however in some versions of WMF, Invoke-DscResource is not correctly # writing to that stream and instead just dumping to stdout verbose_output = result.stream(:verbose) verbose_output = result.stdout if verbose_output.empty? if @converge_description.nil? || @converge_description.empty? @converge_description = verbose_output else @converge_description << "\n" @converge_description << verbose_output end end def invoke_resource(method, output_format = :object) properties = translate_type(@new_resource.properties) switches = "-Method #{method} -Name #{@new_resource.resource}"\ " -Property #{properties} -Module #{module_name} -Verbose" cmdlet = Chef::Util::Powershell::Cmdlet.new( node, "Invoke-DscResource #{switches}", output_format ) cmdlet.run!({}, { :timeout => new_resource.timeout }) end def return_dsc_resource_result(result, property_name) if result.return_value.is_a?(Array) # WMF Feb 2015 Preview result.return_value[0][property_name] else # WMF April 2015 Preview result.return_value[property_name] end end def create_reboot_resource @reboot_resource = Chef::Resource::Reboot.new( "Reboot for #{@new_resource.name}", run_context ).tap do |r| r.reason("Reboot for #{@new_resource.resource}.") end end def reboot_if_required reboot_action = @new_resource.reboot_action unless @reboot_resource.nil? case reboot_action when :nothing Chef::Log.debug("A reboot was requested by the DSC resource, but reboot_action is :nothing.") Chef::Log.debug("This dsc_resource will not reboot the node.") else Chef::Log.debug("Requesting node reboot with #{reboot_action}.") @reboot_resource.run_action(reboot_action) end end end end end end chef-12.14.60/lib/chef/provider/dsc_script.rb000066400000000000000000000154041276456504500206630ustar00rootroot00000000000000# # Author:: Adam Edwards () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # 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/util/powershell/cmdlet" require "chef/util/dsc/configuration_generator" require "chef/util/dsc/local_configuration_manager" require "chef/util/path_helper" class Chef class Provider class DscScript < Chef::Provider provides :dsc_script, os: "windows" def initialize(dsc_resource, run_context) super(dsc_resource, run_context) @dsc_resource = dsc_resource @resource_converged = false @operations = { :set => Proc.new do |config_manager, document, shellout_flags| config_manager.set_configuration(document, shellout_flags) end, :test => Proc.new do |config_manager, document, shellout_flags| config_manager.test_configuration(document, shellout_flags) end } end def action_run if ! @resource_converged converge_by(generate_description) do run_configuration(:set) Chef::Log.info("DSC resource configuration completed successfully") end end end def load_current_resource if supports_dsc? @dsc_resources_info = run_configuration(:test) @resource_converged = @dsc_resources_info.all? do |resource| !resource.changes_state? end end end def whyrun_supported? true end def define_resource_requirements requirements.assert(:run) do |a| err = [ "Could not find PowerShell DSC support on the system", powershell_info_str, "Powershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource.", ] a.assertion { supports_dsc? } a.failure_message Chef::Exceptions::ProviderNotFound, err.join(" ") a.whyrun err + ["Assuming a previous resource installs Powershell 4.0 or higher."] a.block_action! end end protected def supports_dsc? run_context && Chef::Platform.supports_dsc?(node) end def run_configuration(operation) config_directory = ::Dir.mktmpdir("chef-dsc-script") configuration_data_path = get_configuration_data_path(config_directory) configuration_flags = get_augmented_configuration_flags(configuration_data_path) config_manager = Chef::Util::DSC::LocalConfigurationManager.new(@run_context.node, config_directory) shellout_flags = { :cwd => @dsc_resource.cwd, :environment => @dsc_resource.environment, :timeout => @dsc_resource.timeout, } begin configuration_document = generate_configuration_document(config_directory, configuration_flags) @operations[operation].call(config_manager, configuration_document, shellout_flags) rescue Exception => e Chef::Log.error("DSC operation failed: #{e.message}") raise e ensure ::FileUtils.rm_rf(config_directory) end end def get_augmented_configuration_flags(configuration_data_path) updated_flags = @dsc_resource.flags.nil? ? {} : @dsc_resource.flags.dup if configuration_data_path Chef::Util::PathHelper.validate_path(configuration_data_path) updated_flags[:configurationdata] = configuration_data_path end updated_flags end def generate_configuration_document(config_directory, configuration_flags) shellout_flags = { :cwd => @dsc_resource.cwd, :environment => @dsc_resource.environment, :timeout => @dsc_resource.timeout, } generator = Chef::Util::DSC::ConfigurationGenerator.new(@run_context.node, config_directory) if @dsc_resource.command generator.configuration_document_from_script_path(@dsc_resource.command, configuration_name, configuration_flags, shellout_flags) else # If code is also not provided, we mimic what the other script resources do (execute nothing) Chef::Log.warn("Neither code or command were provided for dsc_resource[#{@dsc_resource.name}].") unless @dsc_resource.code generator.configuration_document_from_script_code(@dsc_resource.code || "", configuration_flags, @dsc_resource.imports, shellout_flags) end end def get_configuration_data_path(config_directory) if @dsc_resource.configuration_data_script @dsc_resource.configuration_data_script elsif @dsc_resource.configuration_data configuration_data_path = "#{config_directory}/chef_dsc_config_data.psd1" ::File.open(configuration_data_path, "wt") do |script| script.write(@dsc_resource.configuration_data) end configuration_data_path end end def configuration_name @dsc_resource.configuration_name || @dsc_resource.name end def configuration_friendly_name if @dsc_resource.code @dsc_resource.name else configuration_name end end private def generate_description ["converge DSC configuration '#{configuration_friendly_name}'"] + @dsc_resources_info.map do |resource| if resource.changes_state? # We ignore the last log message because it only contains the time it took, which looks weird cleaned_messages = resource.change_log[0..-2].map { |c| c.sub(/^#{Regexp.escape(resource.name)}/, "").strip } "converge DSC resource #{resource.name} by #{cleaned_messages.find_all { |c| c != '' }.join("\n")}" else # This is needed because a dsc script can have resources that are both converged and not "converge DSC resource #{resource.name} by doing nothing because it is already converged" end end end def powershell_info_str if run_context && run_context.node[:languages] && run_context.node[:languages][:powershell] install_info = "Powershell #{run_context.node[:languages][:powershell][:version]} was found on the system." else install_info = "Powershell was not found." end end end end end chef-12.14.60/lib/chef/provider/env.rb000066400000000000000000000123411276456504500173130ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider" require "chef/mixin/command" require "chef/resource/env" class Chef class Provider class Env < Chef::Provider include Chef::Mixin::Command attr_accessor :key_exists provides :env, os: "!windows" def initialize(new_resource, run_context) super @key_exists = true end def load_current_resource @current_resource = Chef::Resource::Env.new(@new_resource.name) @current_resource.key_name(@new_resource.key_name) if env_key_exists(@new_resource.key_name) @current_resource.value(env_value(@new_resource.key_name)) else @key_exists = false Chef::Log.debug("#{@new_resource} key does not exist") end @current_resource end def env_value(key_name) raise Chef::Exceptions::Env, "#{self} provider does not implement env_value!" end def env_key_exists(key_name) env_value(key_name) ? true : false end # Check to see if value needs any changes # # ==== Returns # :: If a change is required # :: If a change is not required def requires_modify_or_create? if @new_resource.delim #e.g. check for existing value within PATH new_values.inject(0) do |index, val| next_index = current_values.find_index val return true if next_index.nil? || next_index < index next_index end false else @new_resource.value != @current_resource.value end end alias_method :compare_value, :requires_modify_or_create? def action_create if @key_exists if requires_modify_or_create? modify_env Chef::Log.info("#{@new_resource} altered") @new_resource.updated_by_last_action(true) end else create_env Chef::Log.info("#{@new_resource} created") @new_resource.updated_by_last_action(true) end end #e.g. delete a PATH element # # ==== Returns # :: If we handled the element case and caller should not delete the key # :: Caller should delete the key, either no :delim was specific or value was empty # after we removed the element. def delete_element return false unless @new_resource.delim #no delim: delete the key needs_delete = new_values.any? { |v| current_values.include?(v) } if !needs_delete Chef::Log.debug("#{@new_resource} element '#{@new_resource.value}' does not exist") return true #do not delete the key else new_value = current_values.select do |item| not new_values.include?(item) end.join(@new_resource.delim) if new_value.empty? return false #nothing left here, delete the key else old_value = @new_resource.value(new_value) create_env Chef::Log.debug("#{@new_resource} deleted #{old_value} element") @new_resource.updated_by_last_action(true) return true #we removed the element and updated; do not delete the key end end end def action_delete if @key_exists && !delete_element delete_env Chef::Log.info("#{@new_resource} deleted") @new_resource.updated_by_last_action(true) end end def action_modify if @key_exists if requires_modify_or_create? modify_env Chef::Log.info("#{@new_resource} modified") @new_resource.updated_by_last_action(true) end else raise Chef::Exceptions::Env, "Cannot modify #{@new_resource} - key does not exist!" end end def create_env raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :#{@new_resource.action}" end def delete_env raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :delete" end def modify_env if @new_resource.delim @new_resource.value((new_values + current_values).uniq.join(@new_resource.delim)) end create_env end # Returns the current values to split by delimiter def current_values @current_values ||= @current_resource.value.split(@new_resource.delim) end # Returns the new values to split by delimiter def new_values @new_values ||= @new_resource.value.split(@new_resource.delim) end end end end chef-12.14.60/lib/chef/provider/env/000077500000000000000000000000001276456504500167655ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/env/windows.rb000066400000000000000000000043771276456504500210170ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/windows_env_helper" class Chef class Provider class Env class Windows < Chef::Provider::Env include Chef::Mixin::WindowsEnvHelper provides :env, os: "windows" def create_env obj = env_obj(@new_resource.key_name) unless obj obj = WIN32OLE.connect("winmgmts://").get("Win32_Environment").spawninstance_ obj.name = @new_resource.key_name obj.username = "" end obj.variablevalue = @new_resource.value obj.put_ value = @new_resource.value value = expand_path(value) if @new_resource.key_name.casecmp("PATH").zero? ENV[@new_resource.key_name] = value broadcast_env_change end def delete_env obj = env_obj(@new_resource.key_name) if obj obj.delete_ broadcast_env_change end if ENV[@new_resource.key_name] ENV.delete(@new_resource.key_name) end end def env_value(key_name) obj = env_obj(key_name) return obj ? obj.variablevalue : ENV[key_name] end def env_obj(key_name) wmi = WmiLite::Wmi.new # Note that by design this query is case insensitive with regard to key_name environment_variables = wmi.query("select * from Win32_Environment where name = '#{key_name}'") if environment_variables && environment_variables.length > 0 environment_variables[0].wmi_ole_object end end end end end end chef-12.14.60/lib/chef/provider/erl_call.rb000066400000000000000000000056561276456504500203130ustar00rootroot00000000000000# # Author:: Joe Williams () # Copyright:: Copyright 2009-2016, Joe Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/mixin/command" require "chef/provider" class Chef class Provider class ErlCall < Chef::Provider include Chef::Mixin::Command provides :erl_call def initialize(node, new_resource) super(node, new_resource) end def whyrun_supported? true end def load_current_resource true end def action_run case @new_resource.name_type when "sname" node = "-sname #{@new_resource.node_name}" when "name" node = "-name #{@new_resource.node_name}" end if @new_resource.cookie cookie = "-c #{@new_resource.cookie}" else cookie = "" end if @new_resource.distributed distributed = "-s" else distributed = "" end command = "erl_call -e #{distributed} #{node} #{cookie}" converge_by("run erlang block") do begin pid, stdin, stdout, stderr = popen4(command, :waitlast => true) Chef::Log.debug("#{@new_resource} running") Chef::Log.debug("#{@new_resource} command: #{command}") Chef::Log.debug("#{@new_resource} code: #{@new_resource.code}") @new_resource.code.each_line { |line| stdin.puts(line.chomp) } stdin.close Chef::Log.debug("#{@new_resource} output: ") stdout_output = "" stdout.each_line { |line| stdout_output << line } stdout.close stderr_output = "" stderr.each_line { |line| stderr_output << line } stderr.close # fail if stderr contains anything if stderr_output.length > 0 raise Chef::Exceptions::ErlCall, stderr_output end # fail if the first 4 characters aren't "{ok," unless stdout_output[0..3].include?("{ok,") raise Chef::Exceptions::ErlCall, stdout_output end @new_resource.updated_by_last_action(true) Chef::Log.debug("#{@new_resource} #{stdout_output}") Chef::Log.info("#{@new_resouce} ran successfully") ensure Process.wait(pid) if pid end end end end end end chef-12.14.60/lib/chef/provider/execute.rb000066400000000000000000000073471276456504500201770ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/provider" require "forwardable" class Chef class Provider class Execute < Chef::Provider extend Forwardable provides :execute def_delegators :@new_resource, :command, :returns, :environment, :user, :group, :cwd, :umask, :creates def load_current_resource current_resource = Chef::Resource::Execute.new(new_resource.name) current_resource end def whyrun_supported? true end def define_resource_requirements # @todo: this should change to raise in some appropriate major version bump. if creates && creates_relative? && !cwd Chef::Log.warn "Providing a relative path for the creates attribute without the cwd is deprecated and will be changed to fail in the future (CHEF-3819)" end end def timeout # original implementation did not specify a timeout, but ShellOut # *always* times out. So, set a very long default timeout new_resource.timeout || 3600 end def action_run if creates && sentinel_file.exist? Chef::Log.debug("#{new_resource} sentinel file #{sentinel_file} exists - nothing to do") return false end converge_by("execute #{description}") do begin shell_out!(command, opts) rescue Mixlib::ShellOut::ShellCommandFailed if sensitive? raise Mixlib::ShellOut::ShellCommandFailed, "Command execution failed. STDOUT/STDERR suppressed for sensitive resource" else raise end end Chef::Log.info("#{new_resource} ran successfully") end end private def sensitive? !!new_resource.sensitive end def live_stream? Chef::Config[:stream_execute_output] || !!new_resource.live_stream end def stream_to_stdout? STDOUT.tty? && !Chef::Config[:daemon] end def opts opts = {} opts[:timeout] = timeout opts[:returns] = returns if returns opts[:environment] = environment if environment opts[:user] = user if user opts[:group] = group if group opts[:cwd] = cwd if cwd opts[:umask] = umask if umask opts[:log_level] = :info opts[:log_tag] = new_resource.to_s if (Chef::Log.info? || live_stream?) && !sensitive? if run_context.events.formatter? opts[:live_stream] = Chef::EventDispatch::EventsOutputStream.new(run_context.events, :name => :execute) elsif stream_to_stdout? opts[:live_stream] = STDOUT end end opts end def description sensitive? ? "sensitive resource" : command end def creates_relative? Pathname(creates).relative? end def sentinel_file Pathname.new(Chef::Util::PathHelper.cleanpath( ( cwd && creates_relative? ) ? ::File.join(cwd, creates) : creates )) end end end end chef-12.14.60/lib/chef/provider/file.rb000066400000000000000000000450371276456504500174520ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Lamont Granquist () # 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/log" require "chef/resource/file" require "chef/provider" require "etc" require "fileutils" require "chef/scan_access_control" require "chef/mixin/checksum" require "chef/mixin/file_class" require "chef/mixin/enforce_ownership_and_permissions" require "chef/util/backup" require "chef/util/diff" require "chef/util/selinux" require "chef/deprecation/provider/file" require "chef/deprecation/warnings" require "chef/file_content_management/deploy" # The Tao of File Providers: # - the content provider must always return a tempfile that we can delete/mv # - do_create_file shall always create the file first and obey umask when perms are not specified # - do_contents_changes may assume the destination file exists (simplifies exception checking, # and always gives us something to diff against) # - do_contents_changes must restore the perms to the dest file and not obliterate them with # random tempfile permissions # - do_acl_changes may assume perms were not modified between lcr and when it runs (although the # file may have been created) class Chef class Provider class File < Chef::Provider include Chef::Mixin::EnforceOwnershipAndPermissions include Chef::Mixin::Checksum include Chef::Util::Selinux include Chef::Mixin::FileClass extend Chef::Deprecation::Warnings include Chef::Deprecation::Provider::File add_deprecation_warnings_for(Chef::Deprecation::Provider::File.instance_methods) provides :file attr_reader :deployment_strategy attr_accessor :needs_creating attr_accessor :needs_unlinking attr_accessor :managing_symlink def initialize(new_resource, run_context) @content_class ||= Chef::Provider::File::Content if new_resource.respond_to?(:atomic_update) @deployment_strategy = Chef::FileContentManagement::Deploy.strategy(new_resource.atomic_update) end super end def whyrun_supported? true end def load_current_resource # true if there is a symlink and we need to manage what it points at @managing_symlink = file_class.symlink?(new_resource.path) && ( new_resource.manage_symlink_source || new_resource.manage_symlink_source.nil? ) # true if there is a non-file thing in the way that we need to unlink first @needs_unlinking = if ::File.exist?(new_resource.path) if managing_symlink? !symlink_to_real_file?(new_resource.path) else !real_file?(new_resource.path) end else false end # true if we are going to be creating a new file @needs_creating = !::File.exist?(new_resource.path) || needs_unlinking? # Let children resources override constructing the @current_resource @current_resource ||= Chef::Resource::File.new(new_resource.name) current_resource.path(new_resource.path) if !needs_creating? # we are updating an existing file if managing_content? Chef::Log.debug("#{new_resource} checksumming file at #{new_resource.path}.") current_resource.checksum(checksum(current_resource.path)) else # if the file does not exist or is not a file, then the checksum is invalid/pointless current_resource.checksum(nil) end load_resource_attributes_from_file(current_resource) end current_resource end def define_resource_requirements # deep inside FAC we have to assert requirements, so call FACs hook to set that up access_controls.define_resource_requirements # Make sure the parent directory exists, otherwise fail. For why-run assume it would have been created. requirements.assert(:create, :create_if_missing, :touch) do |a| parent_directory = ::File.dirname(@new_resource.path) a.assertion { ::File.directory?(parent_directory) } a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist.") a.whyrun("Assuming directory #{parent_directory} would have been created") end # Make sure the file is deletable if it exists, otherwise fail. if ::File.exist?(@new_resource.path) requirements.assert(:delete) do |a| a.assertion { ::File.writable?(@new_resource.path) } a.failure_message(Chef::Exceptions::InsufficientPermissions, "File #{@new_resource.path} exists but is not writable so it cannot be deleted") end end error, reason, whyrun_message = inspect_existing_fs_entry requirements.assert(:create) do |a| a.assertion { error.nil? } a.failure_message(error, reason) a.whyrun(whyrun_message) # Subsequent attempts to read the fs entry at the path (e.g., for # calculating checksums) could blow up, so give up trying to continue # why-running. a.block_action! end end def action_create do_generate_content do_validate_content do_unlink do_create_file do_contents_changes do_acl_changes do_selinux load_resource_attributes_from_file(@new_resource) end def action_create_if_missing unless ::File.exist?(@new_resource.path) action_create else Chef::Log.debug("#{@new_resource} exists at #{@new_resource.path} taking no action.") end end def action_delete if ::File.exists?(@new_resource.path) converge_by("delete file #{@new_resource.path}") do do_backup unless file_class.symlink?(@new_resource.path) ::File.delete(@new_resource.path) Chef::Log.info("#{@new_resource} deleted file at #{@new_resource.path}") end end end def action_touch action_create converge_by("update utime on file #{@new_resource.path}") do time = Time.now ::File.utime(time, time, @new_resource.path) Chef::Log.info("#{@new_resource} updated atime and mtime to #{time}") end end # Implementation components *should* follow symlinks when managing access # control (e.g., use chmod instead of lchmod even if the path we're # managing is a symlink). def manage_symlink_access? false end private # What to check in this resource to see if we're going to be actively managing # content (for things like doing checksums in load_current_resource). Expected to # be overridden in subclasses. def managing_content? return true if @new_resource.checksum return true if !@new_resource.content.nil? && @action != :create_if_missing false end # Handles resource requirements for action :create when some fs entry # already exists at the destination path. For actions other than create, # we don't care what kind of thing is at the destination path because: # * for :create_if_missing, we're assuming the user wanted to avoid blowing away the non-file here # * for :touch, we can modify perms of whatever is at this path, regardless of its type # * for :delete, we can blow away whatever is here, regardless of its type # # For the action :create case, we need to deal with user-selectable # behavior to see if we're in an error condition. # * If there's no file at the destination path currently, we're cool to # create it. # * If the fs entry that currently exists at the destination is a regular # file, we're cool to update it with new content. # * If the fs entry is a symlink AND the resource has # `manage_symlink_source` enabled, we need to verify that the symlink is # a valid pointer to a real file. If it is, we can manage content and # permissions on the symlink source, otherwise, error. # * If `manage_symlink_source` is not enabled, fall through. # * If force_unlink is true, action :create will unlink whatever is in the way. # * If force_unlink is false, we're in an exceptional situation, so we # want to error. # # Note that this method returns values to be used with requirement # assertions, which then decide whether or not to raise or issue a # warning for whyrun mode. def inspect_existing_fs_entry path = @new_resource.path if !l_exist?(path) [nil, nil, nil] elsif real_file?(path) [nil, nil, nil] elsif file_class.symlink?(path) && @new_resource.manage_symlink_source verify_symlink_sanity(path) elsif file_class.symlink?(@new_resource.path) && @new_resource.manage_symlink_source.nil? Chef::Log.warn("File #{path} managed by #{@new_resource} is really a symlink. Managing the source file instead.") Chef::Log.warn("Disable this warning by setting `manage_symlink_source true` on the resource") Chef::Log.warn("In a future Chef release, 'manage_symlink_source' will not be enabled by default") verify_symlink_sanity(path) elsif @new_resource.force_unlink [nil, nil, nil] else [ Chef::Exceptions::FileTypeMismatch, "File #{path} exists, but is a #{file_type_string(@new_resource.path)}, set force_unlink to true to remove", "Assuming #{file_type_string(@new_resource.path)} at #{@new_resource.path} would have been removed by a previous resource", ] end end # Returns values suitable for use in a requirements assertion statement # when managing symlink source. If we're managing symlink source we can # hit 3 error cases: # 1. Symlink to nowhere: File.realpath(symlink) -> raise Errno::ENOENT # 2. Symlink loop: File.realpath(symlink) -> raise Errno::ELOOP # 3. Symlink to not-a-real-file: File.realpath(symlink) -> (directory|blockdev|etc.) # If any of the above apply, returns a 3-tuple of Exception class, # exception message, whyrun message; otherwise returns a 3-tuple of nil. def verify_symlink_sanity(path) real_path = ::File.realpath(path) if real_file?(real_path) [nil, nil, nil] else [ Chef::Exceptions::FileTypeMismatch, "File #{path} exists, but is a symlink to #{real_path} which is a #{file_type_string(real_path)}. " + "Disable manage_symlink_source and set force_unlink to remove it.", "Assuming symlink #{path} or source file #{real_path} would have been fixed by a previous resource", ] end rescue Errno::ELOOP [ Chef::Exceptions::InvalidSymlink, "Symlink at #{path} (pointing to #{::File.readlink(path)}) exists but attempting to resolve it creates a loop", "Assuming symlink loop would be fixed by a previous resource" ] rescue Errno::ENOENT [ Chef::Exceptions::InvalidSymlink, "Symlink at #{path} (pointing to #{::File.readlink(path)}) exists but attempting to resolve it leads to a nonexistent file", "Assuming symlink source would be created by a previous resource" ] end def content @content ||= begin load_current_resource if @current_resource.nil? @content_class.new(@new_resource, @current_resource, @run_context) end end def file_type_string(path) case when ::File.blockdev?(path) "block device" when ::File.chardev?(path) "char device" when ::File.directory?(path) "directory" when ::File.pipe?(path) "pipe" when ::File.socket?(path) "socket" when file_class.symlink?(path) "symlink" else "unknown filetype" end end def real_file?(path) !file_class.symlink?(path) && ::File.file?(path) end # like real_file? that follows (sane) symlinks def symlink_to_real_file?(path) begin real_file?(::File.realpath(path)) rescue Errno::ELOOP, Errno::ENOENT false end end # Similar to File.exist?, but also returns true in the case that the # named file is a broken symlink. def l_exist?(path) ::File.exist?(path) || file_class.symlink?(path) end def unlink(path) # Directories can not be unlinked. Remove them using FileUtils. if ::File.directory?(path) FileUtils.rm_rf(path) else ::File.unlink(path) end end def do_generate_content # referencing the tempfile magically causes content to be generated tempfile end def tempfile_checksum @tempfile_checksum ||= checksum(tempfile.path) end def do_validate_content if new_resource.checksum && tempfile && ( new_resource.checksum != tempfile_checksum ) raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(tempfile_checksum)) end if tempfile new_resource.verify.each do |v| if ! v.verify(tempfile.path) raise Chef::Exceptions::ValidationFailed.new "Proposed content for #{new_resource.path} failed verification #{v}" end end end end def do_unlink if @new_resource.force_unlink if needs_unlinking? # unlink things that aren't normal files description = "unlink #{file_type_string(@new_resource.path)} at #{@new_resource.path}" converge_by(description) do unlink(@new_resource.path) end end end end def do_create_file if needs_creating? converge_by("create new file #{@new_resource.path}") do deployment_strategy.create(@new_resource.path) Chef::Log.info("#{@new_resource} created file #{@new_resource.path}") end end end def do_backup(file = nil) Chef::Util::Backup.new(@new_resource, file).backup! end def diff @diff ||= Chef::Util::Diff.new end def update_file_contents do_backup unless needs_creating? deployment_strategy.deploy(tempfile.path, ::File.realpath(new_resource.path)) Chef::Log.info("#{new_resource} updated file contents #{new_resource.path}") if managing_content? # save final checksum for reporting. new_resource.final_checksum = checksum(new_resource.path) end end def do_contents_changes # a nil tempfile is okay, means the resource has no content or no new content return if tempfile.nil? # but a tempfile that has no path or doesn't exist should not happen if tempfile.path.nil? || !::File.exists?(tempfile.path) raise "chef-client is confused, trying to deploy a file that has no path or does not exist..." end # the file? on the next line suppresses the case in why-run when we have a not-file here that would have otherwise been removed if ::File.file?(@new_resource.path) && contents_changed? description = [ "update content in file #{@new_resource.path} from \ #{short_cksum(@current_resource.checksum)} to #{short_cksum(tempfile_checksum)}" ] # Hide the diff output if the resource is marked as a sensitive resource if @new_resource.sensitive @new_resource.diff("suppressed sensitive resource") description << "suppressed sensitive resource" else diff.diff(@current_resource.path, tempfile.path) @new_resource.diff( diff.for_reporting ) unless needs_creating? description << diff.for_output end converge_by(description) do update_file_contents end end # unlink necessary to clean up in why-run mode tempfile.close tempfile.unlink end # This logic ideally will be made into some kind of generic # platform-dependent post-converge hook for file-like # resources, but for now we only have the single selinux use # case. def do_selinux(recursive = false) if resource_updated? && Chef::Config[:enable_selinux_file_permission_fixup] if selinux_enabled? converge_by("restore selinux security context") do restore_security_context(::File.realpath(@new_resource.path), recursive) end else Chef::Log.debug "selinux utilities can not be found. Skipping selinux permission fixup." end end end def do_acl_changes if access_controls.requires_changes? converge_by(access_controls.describe_changes) do access_controls.set_all end end end def contents_changed? Chef::Log.debug "calculating checksum of #{tempfile.path} to compare with #{@current_resource.checksum}" tempfile_checksum != @current_resource.checksum end def tempfile @tempfile ||= content.tempfile end def short_cksum(checksum) return "none" if checksum.nil? checksum.slice(0, 6) end def load_resource_attributes_from_file(resource) if Chef::Platform.windows? # This is a work around for CHEF-3554. # OC-6534: is tracking the real fix for this workaround. # Add support for Windows equivalent, or implicit resource # reporting won't work for Windows. return end acl_scanner = ScanAccessControl.new(@new_resource, resource) acl_scanner.set_all! end def managing_symlink? !!@managing_symlink end def needs_creating? !!@needs_creating end def needs_unlinking? !!@needs_unlinking end end end end chef-12.14.60/lib/chef/provider/file/000077500000000000000000000000001276456504500171145ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/file/content.rb000066400000000000000000000023241276456504500211140ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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/file_content_management/content_base" require "chef/file_content_management/tempfile" class Chef class Provider class File class Content < Chef::FileContentManagement::ContentBase def file_for_provider if @new_resource.content tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile tempfile.write(@new_resource.content) tempfile.close tempfile else nil end end end end end end chef-12.14.60/lib/chef/provider/git.rb000066400000000000000000000316671276456504500173220ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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/exceptions" require "chef/log" require "chef/provider" require "fileutils" class Chef class Provider class Git < Chef::Provider extend Forwardable provides :git def_delegator :@new_resource, :destination, :cwd def whyrun_supported? true end def load_current_resource @resolved_reference = nil @current_resource = Chef::Resource::Git.new(@new_resource.name) if current_revision = find_current_revision @current_resource.revision current_revision end end def define_resource_requirements # Parent directory of the target must exist. requirements.assert(:checkout, :sync) do |a| dirname = ::File.dirname(cwd) a.assertion { ::File.directory?(dirname) } a.whyrun("Directory #{dirname} does not exist, this run will fail unless it has been previously created. Assuming it would have been created.") a.failure_message(Chef::Exceptions::MissingParentDirectory, "Cannot clone #{@new_resource} to #{cwd}, the enclosing directory #{dirname} does not exist") end requirements.assert(:all_actions) do |a| a.assertion { !(@new_resource.revision =~ /^origin\//) } a.failure_message Chef::Exceptions::InvalidRemoteGitReference, "Deploying remote branches is not supported. " + "Specify the remote branch as a local branch for " + "the git repository you're deploying from " + "(ie: '#{@new_resource.revision.gsub('origin/', '')}' rather than '#{@new_resource.revision}')." end requirements.assert(:all_actions) do |a| # this can't be recovered from in why-run mode, because nothing that # we do in the course of a run is likely to create a valid target_revision # if we can't resolve it up front. a.assertion { target_revision != nil } a.failure_message Chef::Exceptions::UnresolvableGitReference, "Unable to parse SHA reference for '#{@new_resource.revision}' in repository '#{@new_resource.repository}'. " + "Verify your (case-sensitive) repository URL and revision.\n" + "`git ls-remote '#{@new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}" end end def action_checkout if target_dir_non_existent_or_empty? clone if @new_resource.enable_checkout checkout end enable_submodules add_remotes else Chef::Log.debug "#{@new_resource} checkout destination #{cwd} already exists or is a non-empty directory" end end def action_export action_checkout converge_by("complete the export by removing #{cwd}.git after checkout") do FileUtils.rm_rf(::File.join(cwd, ".git")) end end def action_sync if existing_git_clone? Chef::Log.debug "#{@new_resource} current revision: #{@current_resource.revision} target revision: #{target_revision}" unless current_revision_matches_target_revision? fetch_updates enable_submodules Chef::Log.info "#{@new_resource} updated to revision #{target_revision}" end add_remotes else action_checkout end end def git_minor_version @git_minor_version ||= Gem::Version.new( git("--version").stdout.split.last ) end def existing_git_clone? ::File.exist?(::File.join(cwd, ".git")) end def target_dir_non_existent_or_empty? !::File.exist?(cwd) || Dir.entries(cwd).sort == [".", ".."] end def find_current_revision Chef::Log.debug("#{@new_resource} finding current git revision") if ::File.exist?(::File.join(cwd, ".git")) # 128 is returned when we're not in a git repo. this is fine result = git("rev-parse", "HEAD", cwd: cwd, returns: [0, 128]).stdout.strip end sha_hash?(result) ? result : nil end def add_remotes if @new_resource.additional_remotes.length > 0 @new_resource.additional_remotes.each_pair do |remote_name, remote_url| converge_by("add remote #{remote_name} from #{remote_url}") do Chef::Log.info "#{@new_resource} adding git remote #{remote_name} = #{remote_url}" setup_remote_tracking_branches(remote_name, remote_url) end end end end def clone converge_by("clone from #{@new_resource.repository} into #{cwd}") do remote = @new_resource.remote clone_cmd = ["clone"] clone_cmd << "-o #{remote}" unless remote == "origin" clone_cmd << "--depth #{@new_resource.depth}" if @new_resource.depth clone_cmd << "--no-single-branch" if @new_resource.depth && git_minor_version >= Gem::Version.new("1.7.10") clone_cmd << "\"#{@new_resource.repository}\"" clone_cmd << "\"#{cwd}\"" Chef::Log.info "#{@new_resource} cloning repo #{@new_resource.repository} to #{cwd}" git clone_cmd end end def checkout sha_ref = target_revision converge_by("checkout ref #{sha_ref} branch #{@new_resource.revision}") do # checkout into a local branch rather than a detached HEAD git("branch", "-f", @new_resource.checkout_branch, sha_ref, cwd: cwd) git("checkout", @new_resource.checkout_branch, cwd: cwd) Chef::Log.info "#{@new_resource} checked out branch: #{@new_resource.revision} onto: #{@new_resource.checkout_branch} reference: #{sha_ref}" end end def enable_submodules if @new_resource.enable_submodules converge_by("enable git submodules for #{@new_resource}") do Chef::Log.info "#{@new_resource} synchronizing git submodules" git("submodule", "sync", cwd: cwd) Chef::Log.info "#{@new_resource} enabling git submodules" # the --recursive flag means we require git 1.6.5+ now, see CHEF-1827 git("submodule", "update", "--init", "--recursive", cwd: cwd) end end end def fetch_updates setup_remote_tracking_branches(@new_resource.remote, @new_resource.repository) converge_by("fetch updates for #{@new_resource.remote}") do # since we're in a local branch already, just reset to specified revision rather than merge Chef::Log.debug "Fetching updates from #{new_resource.remote} and resetting to revision #{target_revision}" git("fetch", @new_resource.remote, cwd: cwd) git("fetch", @new_resource.remote, "--tags", cwd: cwd) git("reset", "--hard", target_revision, cwd: cwd) end end def setup_remote_tracking_branches(remote_name, remote_url) converge_by("set up remote tracking branches for #{remote_url} at #{remote_name}") do Chef::Log.debug "#{@new_resource} configuring remote tracking branches for repository #{remote_url} " + "at remote #{remote_name}" check_remote_command = ["config", "--get", "remote.#{remote_name}.url"] remote_status = git(check_remote_command, cwd: cwd, returns: [0, 1, 2]) case remote_status.exitstatus when 0, 2 # * Status 0 means that we already have a remote with this name, so we should update the url # if it doesn't match the url we want. # * Status 2 means that we have multiple urls assigned to the same remote (not a good idea) # which we can fix by replacing them all with our target url (hence the --replace-all option) if multiple_remotes?(remote_status) || !remote_matches?(remote_url, remote_status) git("config", "--replace-all", "remote.#{remote_name}.url", remote_url, cwd: cwd) end when 1 git("remote", "add", remote_name, remote_url, cwd: cwd) end end end def multiple_remotes?(check_remote_command_result) check_remote_command_result.exitstatus == 2 end def remote_matches?(remote_url, check_remote_command_result) check_remote_command_result.stdout.strip.eql?(remote_url) end def current_revision_matches_target_revision? (!@current_resource.revision.nil?) && (target_revision.strip.to_i(16) == @current_resource.revision.strip.to_i(16)) end def target_revision @target_revision ||= begin if sha_hash?(@new_resource.revision) @target_revision = @new_resource.revision else @target_revision = remote_resolve_reference end end end alias :revision_slug :target_revision def remote_resolve_reference Chef::Log.debug("#{@new_resource} resolving remote reference") # The sha pointed to by an annotated tag is identified by the # '^{}' suffix appended to the tag. In order to resolve # annotated tags, we have to search for "revision*" and # post-process. Special handling for 'HEAD' to ignore a tag # named 'HEAD'. @resolved_reference = git_ls_remote(rev_search_pattern) refs = @resolved_reference.split("\n").map { |line| line.split("\t") } # First try for ^{} indicating the commit pointed to by an # annotated tag. # It is possible for a user to create a tag named 'HEAD'. # Using such a degenerate annotated tag would be very # confusing. We avoid the issue by disallowing the use of # annotated tags named 'HEAD'. if rev_search_pattern != "HEAD" found = find_revision(refs, @new_resource.revision, "^{}") else found = refs_search(refs, "HEAD") end found = find_revision(refs, @new_resource.revision) if found.empty? found.size == 1 ? found.first[0] : nil end def find_revision(refs, revision, suffix = "") found = refs_search(refs, rev_match_pattern("refs/tags/", revision) + suffix) found = refs_search(refs, rev_match_pattern("refs/heads/", revision) + suffix) if found.empty? found = refs_search(refs, revision + suffix) if found.empty? found end def rev_match_pattern(prefix, revision) if revision.start_with?(prefix) revision else prefix + revision end end def rev_search_pattern if ["", "HEAD"].include? @new_resource.revision "HEAD" else @new_resource.revision + "*" end end def git_ls_remote(rev_pattern) git("ls-remote", "\"#{@new_resource.repository}\"", "\"#{rev_pattern}\"").stdout end def refs_search(refs, pattern) refs.find_all { |m| m[1] == pattern } end private def run_options(run_opts = {}) env = {} if @new_resource.user run_opts[:user] = @new_resource.user # Certain versions of `git` misbehave if git configuration is # inaccessible in $HOME. We need to ensure $HOME matches the # user who is executing `git` not the user running Chef. env["HOME"] = begin require "etc" case @new_resource.user when Integer Etc.getpwuid(@new_resource.user).dir else Etc.getpwnam(@new_resource.user.to_s).dir end rescue ArgumentError # user not found raise Chef::Exceptions::User, "Could not determine HOME for specified user '#{@new_resource.user}' for resource '#{@new_resource.name}'" end end run_opts[:group] = @new_resource.group if @new_resource.group env["GIT_SSH"] = @new_resource.ssh_wrapper if @new_resource.ssh_wrapper run_opts[:log_tag] = @new_resource.to_s run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout env.merge!(@new_resource.environment) if @new_resource.environment run_opts[:environment] = env unless env.empty? run_opts end def git(*args, **run_opts) git_command = ["git", args].compact.join(" ") Chef::Log.debug "running #{git_command}" shell_out!(git_command, run_options(run_opts)) end def sha_hash?(string) string =~ /^[0-9a-f]{40}$/ end end end end chef-12.14.60/lib/chef/provider/group.rb000066400000000000000000000141201276456504500176540ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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" require "chef/mixin/shell_out" require "chef/mixin/command" require "etc" class Chef class Provider class Group < Chef::Provider include Chef::Mixin::ShellOut include Chef::Mixin::Command attr_accessor :group_exists attr_accessor :change_desc def whyrun_supported? true end def initialize(new_resource, run_context) super @group_exists = true end def load_current_resource @current_resource = Chef::Resource::Group.new(@new_resource.name) @current_resource.group_name(@new_resource.group_name) group_info = nil begin group_info = Etc.getgrnam(@new_resource.group_name) rescue ArgumentError => e @group_exists = false Chef::Log.debug("#{@new_resource} group does not exist") end if group_info @new_resource.gid(group_info.gid) unless @new_resource.gid @current_resource.gid(group_info.gid) @current_resource.members(group_info.mem) end @current_resource end def define_resource_requirements requirements.assert(:modify) do |a| a.assertion { @group_exists } a.failure_message(Chef::Exceptions::Group, "Cannot modify #{@new_resource} - group does not exist!") a.whyrun("Group #{@new_resource} does not exist. Unless it would have been created earlier in this run, this attempt to modify it would fail.") end requirements.assert(:all_actions) do |a| # Make sure that the resource doesn't contain any common # user names in the members and exclude_members properties. if !@new_resource.members.nil? && !@new_resource.excluded_members.nil? common_members = @new_resource.members & @new_resource.excluded_members a.assertion { common_members.empty? } a.failure_message(Chef::Exceptions::ConflictingMembersInGroup, "Attempting to both add and remove users from a group: '#{common_members.join(', ')}'") # No why-run alternative end end end # Check to see if a group needs any changes. Populate # @change_desc with a description of why a change must occur # # ==== Returns # :: If a change is required # :: If a change is not required def compare_group @change_desc = [ ] if @new_resource.gid.to_s != @current_resource.gid.to_s @change_desc << "change gid #{@current_resource.gid} to #{@new_resource.gid}" end if @new_resource.append missing_members = [] @new_resource.members.each do |member| next if has_current_group_member?(member) validate_member!(member) missing_members << member end if missing_members.length > 0 @change_desc << "add missing member(s): #{missing_members.join(", ")}" end members_to_be_removed = [] @new_resource.excluded_members.each do |member| if has_current_group_member?(member) members_to_be_removed << member end end if members_to_be_removed.length > 0 @change_desc << "remove existing member(s): #{members_to_be_removed.join(", ")}" end else if @new_resource.members != @current_resource.members @change_desc << "replace group members with new list of members" end end !@change_desc.empty? end def has_current_group_member?(member) @current_resource.members.include?(member) end def validate_member!(member) # Sub-classes can do any validation if needed # and raise an error if validation fails true end def action_create case @group_exists when false converge_by("create group #{@new_resource.group_name}") do create_group Chef::Log.info("#{@new_resource} created") end else if compare_group converge_by(["alter group #{@new_resource.group_name}"] + change_desc) do manage_group Chef::Log.info("#{@new_resource} altered") end end end end def action_remove if @group_exists converge_by("remove group #{@new_resource.group_name}") do remove_group Chef::Log.info("#{@new_resource} removed") end end end def action_manage if @group_exists && compare_group converge_by(["manage group #{@new_resource.group_name}"] + change_desc) do manage_group Chef::Log.info("#{@new_resource} managed") end end end def action_modify if compare_group converge_by(["modify group #{@new_resource.group_name}"] + change_desc) do manage_group Chef::Log.info("#{@new_resource} modified") end end end def create_group raise NotImplementedError, "subclasses of Chef::Provider::Group should define #create_group" end def manage_group raise NotImplementedError, "subclasses of Chef::Provider::Group should define #manage_group" end def remove_group raise NotImplementedError, "subclasses of Chef::Provider::Group should define #remove_group" end end end end chef-12.14.60/lib/chef/provider/group/000077500000000000000000000000001276456504500173315ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/group/aix.rb000066400000000000000000000051511276456504500204410ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/group/groupadd" require "chef/mixin/shell_out" class Chef class Provider class Group class Aix < Chef::Provider::Group::Groupadd provides :group, platform: "aix" def required_binaries [ "/usr/bin/mkgroup", "/usr/bin/chgroup", "/usr/bin/chgrpmem", "/usr/sbin/rmgroup" ] end def create_group command = "mkgroup" command << set_options << " #{@new_resource.group_name}" run_command(:command => command) modify_group_members end def manage_group command = "chgroup" options = set_options #Usage: chgroup [-R load_module] "attr=value" ... group if options.size > 0 command << options << " #{@new_resource.group_name}" run_command(:command => command) end modify_group_members end def remove_group run_command(:command => "rmgroup #{@new_resource.group_name}") end def add_member(member) shell_out!("chgrpmem -m + #{member} #{@new_resource.group_name}") end def set_members(members) return if members.empty? shell_out!("chgrpmem -m = #{members.join(',')} #{@new_resource.group_name}") end def remove_member(member) shell_out!("chgrpmem -m - #{member} #{@new_resource.group_name}") end def set_options opts = "" { :gid => "id" }.sort { |a, b| a[0] <=> b[0] }.each do |field, option| if @current_resource.send(field) != @new_resource.send(field) if @new_resource.send(field) Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field)}") opts << " '#{option}=#{@new_resource.send(field)}'" end end end opts end end end end end chef-12.14.60/lib/chef/provider/group/dscl.rb000066400000000000000000000150751276456504500206130ustar00rootroot00000000000000# # Author:: Dreamcat4 () # Copyright:: Copyright 2009-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 Provider class Group class Dscl < Chef::Provider::Group provides :group, os: "darwin" def dscl(*args) host = "." stdout_result = ""; stderr_result = ""; cmd = "dscl #{host} -#{args.join(' ')}" status = shell_out(cmd) status.stdout.each_line { |line| stdout_result << line } status.stderr.each_line { |line| stderr_result << line } return [cmd, status, stdout_result, stderr_result] end def safe_dscl(*args) result = dscl(*args) return "" if ( args.first =~ /^delete/ ) && ( result[1].exitstatus != 0 ) raise(Chef::Exceptions::Group, "dscl error: #{result.inspect}") unless result[1].exitstatus == 0 raise(Chef::Exceptions::Group, "dscl error: #{result.inspect}") if result[2] =~ /No such key: / return result[2] end def load_current_resource @current_resource = Chef::Resource::Group.new(@new_resource.name) @current_resource.group_name(@new_resource.group_name) group_info = nil begin group_info = safe_dscl("read /Groups/#{@new_resource.group_name}") rescue Chef::Exceptions::Group @group_exists = false Chef::Log.debug("#{@new_resource} group does not exist") end if group_info group_info.each_line do |line| key, val = line.split(": ") val.strip! if val case key.downcase when "primarygroupid" @new_resource.gid(val) unless @new_resource.gid @current_resource.gid(val) when "groupmembership" @current_resource.members(val.split(" ")) end end end @current_resource end # get a free GID greater than 200 def get_free_gid(search_limit = 1000) gid = nil; next_gid_guess = 200 groups_gids = safe_dscl("list /Groups gid") while next_gid_guess < search_limit + 200 if groups_gids =~ Regexp.new("#{Regexp.escape(next_gid_guess.to_s)}\n") next_gid_guess += 1 else gid = next_gid_guess break end end return gid || raise("gid not found. Exhausted. Searched #{search_limit} times") end def gid_used?(gid) return false unless gid groups_gids = safe_dscl("list /Groups gid") !! ( groups_gids =~ Regexp.new("#{Regexp.escape(gid.to_s)}\n") ) end def set_gid @new_resource.gid(get_free_gid) if [nil, ""].include? @new_resource.gid raise(Chef::Exceptions::Group, "gid is already in use") if gid_used?(@new_resource.gid) safe_dscl("create /Groups/#{@new_resource.group_name} PrimaryGroupID #{@new_resource.gid}") end def set_members # First reset the memberships if the append is not set unless @new_resource.append Chef::Log.debug("#{@new_resource} removing group members #{@current_resource.members.join(' ')}") unless @current_resource.members.empty? safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembers ''") # clear guid list safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembership ''") # clear user list @current_resource.members([ ]) end # Add any members that need to be added if @new_resource.members && !@new_resource.members.empty? members_to_be_added = [ ] @new_resource.members.each do |member| members_to_be_added << member if !@current_resource.members.include?(member) end unless members_to_be_added.empty? Chef::Log.debug("#{@new_resource} setting group members #{members_to_be_added.join(', ')}") safe_dscl("append /Groups/#{@new_resource.group_name} GroupMembership #{members_to_be_added.join(' ')}") end end # Remove any members that need to be removed if @new_resource.excluded_members && !@new_resource.excluded_members.empty? members_to_be_removed = [ ] @new_resource.excluded_members.each do |member| members_to_be_removed << member if @current_resource.members.include?(member) end unless members_to_be_removed.empty? Chef::Log.debug("#{@new_resource} removing group members #{members_to_be_removed.join(', ')}") safe_dscl("delete /Groups/#{@new_resource.group_name} GroupMembership #{members_to_be_removed.join(' ')}") end end end def define_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?("/usr/bin/dscl") } a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/dscl for #{@new_resource.name}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end end def create_group dscl_create_group set_gid set_members end def manage_group if @new_resource.group_name && (@current_resource.group_name != @new_resource.group_name) dscl_create_group end if @new_resource.gid && (@current_resource.gid != @new_resource.gid) set_gid end if @new_resource.members || @new_resource.excluded_members set_members end end def dscl_create_group safe_dscl("create /Groups/#{@new_resource.group_name}") safe_dscl("create /Groups/#{@new_resource.group_name} Password '*'") end def remove_group safe_dscl("delete /Groups/#{@new_resource.group_name}") end end end end end chef-12.14.60/lib/chef/provider/group/gpasswd.rb000066400000000000000000000035271276456504500213350ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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/group/groupadd" class Chef class Provider class Group class Gpasswd < Chef::Provider::Group::Groupadd provides :group def load_current_resource super end def define_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?("/usr/bin/gpasswd") } a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{@new_resource}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end end def set_members(members) unless members.empty? shell_out!("gpasswd -M #{members.join(',')} #{@new_resource.group_name}") else shell_out!("gpasswd -M \"\" #{@new_resource.group_name}") end end def add_member(member) shell_out!("gpasswd -a #{member} #{@new_resource.group_name}") end def remove_member(member) shell_out!("gpasswd -d #{member} #{@new_resource.group_name}") end end end end end chef-12.14.60/lib/chef/provider/group/groupadd.rb000066400000000000000000000113641276456504500214700ustar00rootroot00000000000000# # Author:: AJ Christensen () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Provider class Group class Groupadd < Chef::Provider::Group def required_binaries [ "/usr/sbin/groupadd", "/usr/sbin/groupmod", "/usr/sbin/groupdel" ] end def load_current_resource super end def define_resource_requirements super required_binaries.each do |required_binary| requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?(required_binary) } a.failure_message Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{@new_resource}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end end end # Create the group def create_group command = "groupadd" command << set_options command << groupadd_options run_command(:command => command) modify_group_members end # Manage the group when it already exists def manage_group command = "groupmod" command << set_options run_command(:command => command) modify_group_members end # Remove the group def remove_group run_command(:command => "groupdel #{@new_resource.group_name}") end def modify_group_members if @new_resource.append if @new_resource.members && !@new_resource.members.empty? members_to_be_added = [ ] @new_resource.members.each do |member| members_to_be_added << member if !@current_resource.members.include?(member) end members_to_be_added.each do |member| Chef::Log.debug("#{@new_resource} appending member #{member} to group #{@new_resource.group_name}") add_member(member) end end if @new_resource.excluded_members && !@new_resource.excluded_members.empty? members_to_be_removed = [ ] @new_resource.excluded_members.each do |member| members_to_be_removed << member if @current_resource.members.include?(member) end members_to_be_removed.each do |member| Chef::Log.debug("#{@new_resource} removing member #{member} from group #{@new_resource.group_name}") remove_member(member) end end else members_description = @new_resource.members.empty? ? "none" : @new_resource.members.join(", ") Chef::Log.debug("#{@new_resource} setting group members to: #{members_description}") set_members(@new_resource.members) end end def add_member(member) raise Chef::Exceptions::Group, "you must override add_member in #{self}" end def remove_member(member) raise Chef::Exceptions::Group, "you must override remove_member in #{self}" end def set_members(members) raise Chef::Exceptions::Group, "you must override set_members in #{self}" end # Little bit of magic as per Adam's useradd provider to pull the assign the command line flags # # ==== Returns # :: A string containing the option and then the quoted value def set_options opts = "" { :gid => "-g" }.sort { |a, b| a[0] <=> b[0] }.each do |field, option| if @current_resource.send(field) != @new_resource.send(field) if @new_resource.send(field) opts << " #{option} '#{@new_resource.send(field)}'" Chef::Log.debug("#{@new_resource} set #{field} to #{@new_resource.send(field)}") end end end opts << " #{@new_resource.group_name}" end def groupadd_options opts = "" opts << " -r" if @new_resource.system opts << " -o" if @new_resource.non_unique opts end end end end end chef-12.14.60/lib/chef/provider/group/groupmod.rb000066400000000000000000000111411276456504500215100ustar00rootroot00000000000000# # Author:: Dan Crosta () # 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 Provider class Group class Groupmod < Chef::Provider::Group provides :group, os: "netbsd" def load_current_resource super %w{group user}.each do |binary| raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/#{binary} for #{@new_resource}" unless ::File.exists?("/usr/sbin/#{binary}") end end # Create the group def create_group command = "group add" command << set_options shell_out!(command) add_group_members(@new_resource.members) end # Manage the group when it already exists def manage_group if @new_resource.append members_to_be_added = [ ] if @new_resource.excluded_members && !@new_resource.excluded_members.empty? # First find out if any member needs to be removed members_to_be_removed = [ ] @new_resource.excluded_members.each do |member| members_to_be_removed << member if @current_resource.members.include?(member) end unless members_to_be_removed.empty? # We are using a magic trick to remove the groups. reset_group_membership # Capture the members we need to add in # members_to_be_added to be added later on. @current_resource.members.each do |member| members_to_be_added << member unless members_to_be_removed.include?(member) end end end if @new_resource.members && !@new_resource.members.empty? @new_resource.members.each do |member| members_to_be_added << member if !@current_resource.members.include?(member) end end Chef::Log.debug("#{@new_resource} not changing group members, the group has no members to add") if members_to_be_added.empty? add_group_members(members_to_be_added) else # We are resetting the members of a group so use the same trick reset_group_membership Chef::Log.debug("#{@new_resource} setting group members to: none") if @new_resource.members.empty? add_group_members(@new_resource.members) end end # Remove the group def remove_group shell_out!("group del #{@new_resource.group_name}") end # Adds a list of usernames to the group using `user mod` def add_group_members(members) Chef::Log.debug("#{@new_resource} adding members #{members.join(', ')}") if !members.empty? members.each do |user| shell_out!("user mod -G #{@new_resource.group_name} #{user}") end end # This is tricky, but works: rename the existing group to # "_bak", create a new group with the same GID and # "", then set correct members on that group def reset_group_membership rename = "group mod -n #{@new_resource.group_name}_bak #{@new_resource.group_name}" shell_out!(rename) create = "group add" create << set_options(:overwrite_gid => true) shell_out!(create) remove = "group del #{@new_resource.group_name}_bak" shell_out!(remove) end # Little bit of magic as per Adam's useradd provider to pull and assign the command line flags # # ==== Returns # :: A string containing the option and then the quoted value def set_options(overwrite_gid = false) opts = "" if overwrite_gid || @new_resource.gid && (@current_resource.gid != @new_resource.gid) opts << " -g '#{@new_resource.gid}'" end if overwrite_gid opts << " -o" end opts << " #{@new_resource.group_name}" opts end end end end end chef-12.14.60/lib/chef/provider/group/pw.rb000066400000000000000000000120741276456504500203100ustar00rootroot00000000000000# # Author:: Stephen Haynes () # Copyright:: Copyright 2009-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 Provider class Group class Pw < Chef::Provider::Group provides :group, platform: "freebsd" def load_current_resource super end def define_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?("/usr/sbin/pw") } a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/pw for #{@new_resource}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end end # Create the group def create_group command = "pw groupadd" command << set_options unless @new_resource.members.empty? # pw group[add|mod] -M is used to set the full membership list on a # new or existing group. Because pw groupadd does not support the -m # and -d options used by manage_group, we treat group creation as a # special case and use -M. Chef::Log.debug("#{@new_resource} setting group members: #{@new_resource.members.join(',')}") command += " -M #{@new_resource.members.join(',')}" end run_command(:command => command) end # Manage the group when it already exists def manage_group command = "pw groupmod" command << set_options member_options = set_members_options if member_options.empty? run_command(:command => command) else member_options.each do |option| run_command(:command => command + option) end end end # Remove the group def remove_group run_command(:command => "pw groupdel #{@new_resource.group_name}") end # Little bit of magic as per Adam's useradd provider to pull and assign the command line flags # # ==== Returns # :: A string containing the option and then the quoted value def set_options opts = " #{@new_resource.group_name}" if @new_resource.gid && (@current_resource.gid != @new_resource.gid) Chef::Log.debug("#{@new_resource}: current gid (#{@current_resource.gid}) doesnt match target gid (#{@new_resource.gid}), changing it") opts << " -g '#{@new_resource.gid}'" end opts end # Set the membership option depending on the current resource states def set_members_options opts = [ ] members_to_be_added = [ ] members_to_be_removed = [ ] if @new_resource.append # Append is set so we will only add members given in the # members list and remove members given in the # excluded_members list. if @new_resource.members && !@new_resource.members.empty? @new_resource.members.each do |member| members_to_be_added << member if !@current_resource.members.include?(member) end end if @new_resource.excluded_members && !@new_resource.excluded_members.empty? @new_resource.excluded_members.each do |member| members_to_be_removed << member if @current_resource.members.include?(member) end end else # Append is not set so we're resetting the membership of # the group to the given members. members_to_be_added = @new_resource.members.dup @current_resource.members.each do |member| # No need to re-add a member if it's present in the new # list of members if members_to_be_added.include? member members_to_be_added.delete member else members_to_be_removed << member end end end unless members_to_be_added.empty? Chef::Log.debug("#{@new_resource} adding group members: #{members_to_be_added.join(',')}") opts << " -m #{members_to_be_added.join(',')}" end unless members_to_be_removed.empty? Chef::Log.debug("#{@new_resource} removing group members: #{members_to_be_removed.join(',')}") opts << " -d #{members_to_be_removed.join(',')}" end opts end end end end end chef-12.14.60/lib/chef/provider/group/suse.rb000066400000000000000000000040251276456504500206360ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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/group/groupadd" class Chef class Provider class Group class Suse < Chef::Provider::Group::Groupadd provides :group, platform: "opensuse", platform_version: "< 12.3" provides :group, platform: "suse", platform_version: "< 12.0" def load_current_resource super end def define_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?("/usr/sbin/groupmod") } a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/groupmod for #{@new_resource.name}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end end def set_members(members) to_delete = @current_resource.members - members to_delete.each do |member| remove_member(member) end to_add = members - @current_resource.members to_add.each do |member| add_member(member) end end def add_member(member) shell_out!("groupmod -A #{member} #{@new_resource.group_name}") end def remove_member(member) shell_out!("groupmod -R #{member} #{@new_resource.group_name}") end end end end end chef-12.14.60/lib/chef/provider/group/usermod.rb000066400000000000000000000063431276456504500213420ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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/group/groupadd" class Chef class Provider class Group class Usermod < Chef::Provider::Group::Groupadd provides :group, os: %w{openbsd solaris2 hpux} provides :group, platform: "opensuse" def load_current_resource super end def define_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?("/usr/sbin/usermod") } a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod for #{@new_resource}" # No whyrun alternative: this component should be available in the base install of any given system that uses it end requirements.assert(:modify, :manage) do |a| a.assertion { @new_resource.members.empty? || @new_resource.append } a.failure_message Chef::Exceptions::Group, "setting group members directly is not supported by #{self}, must set append true in group" # No whyrun alternative - this action is simply not supported. end requirements.assert(:all_actions) do |a| a.assertion { @new_resource.excluded_members.empty? } a.failure_message Chef::Exceptions::Group, "excluded_members is not supported by #{self}" # No whyrun alternative - this action is simply not supported. end end def set_members(members) return if members.empty? # This provider only supports adding members with # append. Only if the action is create we will go # ahead and add members. if @new_resource.action.include?(:create) members.each do |member| add_member(member) end else raise Chef::Exceptions::UnsupportedAction, "Setting members directly is not supported by #{self}" end end def add_member(member) shell_out!("usermod #{append_flags} #{@new_resource.group_name} #{member}") end def remove_member(member) # This provider only supports adding members with # append. This function should never be called. raise Chef::Exceptions::UnsupportedAction, "Removing members members is not supported by #{self}" end def append_flags case node[:platform] when "openbsd", "netbsd", "aix", "solaris2", "smartos", "omnios" "-G" when "solaris", "suse", "opensuse" "-a -G" end end end end end end chef-12.14.60/lib/chef/provider/group/windows.rb000066400000000000000000000070511276456504500213530ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/user" if RUBY_PLATFORM =~ /mswin|mingw32|windows/ require "chef/util/windows/net_group" end class Chef class Provider class Group class Windows < Chef::Provider::Group provides :group, os: "windows" def initialize(new_resource, run_context) super @net_group = Chef::Util::Windows::NetGroup.new(@new_resource.group_name) end def load_current_resource @current_resource = Chef::Resource::Group.new(@new_resource.name) @current_resource.group_name(@new_resource.group_name) members = nil begin members = @net_group.local_get_members rescue => e @group_exists = false Chef::Log.debug("#{@new_resource} group does not exist") end if members @current_resource.members(members) end @current_resource end def create_group @net_group.local_add manage_group end def manage_group if @new_resource.append members_to_be_added = [ ] @new_resource.members.each do |member| members_to_be_added << member if ! has_current_group_member?(member) && validate_member!(member) end # local_add_members will raise ERROR_MEMBER_IN_ALIAS if a # member already exists in the group. @net_group.local_add_members(members_to_be_added) unless members_to_be_added.empty? members_to_be_removed = [ ] @new_resource.excluded_members.each do |member| member_sid = lookup_account_name(member) members_to_be_removed << member if has_current_group_member?(member) end @net_group.local_delete_members(members_to_be_removed) unless members_to_be_removed.empty? else @net_group.local_set_members(@new_resource.members) end end def has_current_group_member?(member) member_sid = lookup_account_name(member) @current_resource.members.include?(member_sid) end def remove_group @net_group.local_delete end def locally_qualified_name(account_name) account_name.include?("\\") ? account_name : "#{ENV['COMPUTERNAME']}\\#{account_name}" end def validate_member!(member) Chef::ReservedNames::Win32::Security.lookup_account_name(locally_qualified_name(member))[1].to_s end def lookup_account_name(account_name) begin Chef::ReservedNames::Win32::Security.lookup_account_name(locally_qualified_name(account_name))[1].to_s rescue Chef::Exceptions::Win32APIError Chef::Log.warn("SID for '#{locally_qualified_name(account_name)}' could not be found") "" end end end end end end chef-12.14.60/lib/chef/provider/http_request.rb000066400000000000000000000100031276456504500212430ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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 "tempfile" require "chef/http/simple" class Chef class Provider class HttpRequest < Chef::Provider provides :http_request attr_accessor :http def whyrun_supported? true end def load_current_resource @http = Chef::HTTP::Simple.new(@new_resource.url) end # Send a HEAD request to @new_resource.url def action_head message = check_message(@new_resource.message) # CHEF-4762: we expect a nil return value from Chef::HTTP for a "200 Success" response # and false for a "304 Not Modified" response modified = @http.head( "#{@new_resource.url}", @new_resource.headers ) Chef::Log.info("#{@new_resource} HEAD to #{@new_resource.url} successful") Chef::Log.debug("#{@new_resource} HEAD request response: #{modified}") # :head is usually used to trigger notifications, which converge_by now does if modified != false converge_by("#{@new_resource} HEAD to #{@new_resource.url} returned modified, trigger notifications") {} end end # Send a GET request to @new_resource.url def action_get converge_by("#{@new_resource} GET to #{@new_resource.url}") do message = check_message(@new_resource.message) body = @http.get( "#{@new_resource.url}", @new_resource.headers ) Chef::Log.info("#{@new_resource} GET to #{@new_resource.url} successful") Chef::Log.debug("#{@new_resource} GET request response: #{body}") end end # Send a PUT request to @new_resource.url, with the message as the payload def action_put converge_by("#{@new_resource} PUT to #{@new_resource.url}") do message = check_message(@new_resource.message) body = @http.put( "#{@new_resource.url}", message, @new_resource.headers ) Chef::Log.info("#{@new_resource} PUT to #{@new_resource.url} successful") Chef::Log.debug("#{@new_resource} PUT request response: #{body}") end end # Send a POST request to @new_resource.url, with the message as the payload def action_post converge_by("#{@new_resource} POST to #{@new_resource.url}") do message = check_message(@new_resource.message) body = @http.post( "#{@new_resource.url}", message, @new_resource.headers ) Chef::Log.info("#{@new_resource} POST to #{@new_resource.url} message: #{message.inspect} successful") Chef::Log.debug("#{@new_resource} POST request response: #{body}") end end # Send a DELETE request to @new_resource.url def action_delete converge_by("#{@new_resource} DELETE to #{@new_resource.url}") do body = @http.delete( "#{@new_resource.url}", @new_resource.headers ) @new_resource.updated_by_last_action(true) Chef::Log.info("#{@new_resource} DELETE to #{@new_resource.url} successful") Chef::Log.debug("#{@new_resource} DELETE request response: #{body}") end end private def check_message(message) if message.kind_of?(Proc) message.call else message end end end end end chef-12.14.60/lib/chef/provider/ifconfig.rb000066400000000000000000000165441276456504500203200ustar00rootroot00000000000000# # Author:: Jason K. Jackson (jasonjackson@gmail.com) # Copyright:: Copyright 2009-2016, Jason K. Jackson # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/mixin/command" require "chef/mixin/shell_out" require "chef/provider" require "chef/resource/file" require "chef/exceptions" require "erb" # Recipe example: # # int = {Hash with your network settings...} # # ifconfig int['ip'] do # ignore_failure true # device int['dev'] # mask int['mask'] # gateway int['gateway'] # mtu int['mtu'] # end class Chef class Provider class Ifconfig < Chef::Provider provides :ifconfig include Chef::Mixin::ShellOut include Chef::Mixin::Command attr_accessor :config_template attr_accessor :config_path def initialize(new_resource, run_context) super(new_resource, run_context) @config_template = nil @config_path = nil end def whyrun_supported? true end def load_current_resource @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name) @ifconfig_success = true @interfaces = {} @status = shell_out("ifconfig") @status.stdout.each_line do |line| if !line[0..9].strip.empty? @int_name = line[0..9].strip @interfaces[@int_name] = { "hwaddr" => (line =~ /(HWaddr)/ ? ($') : "nil").strip.chomp } else @interfaces[@int_name]["inet_addr"] = (line =~ /inet addr:(\S+)/ ? ($1) : "nil") if line =~ /inet addr:/ @interfaces[@int_name]["bcast"] = (line =~ /Bcast:(\S+)/ ? ($1) : "nil") if line =~ /Bcast:/ @interfaces[@int_name]["mask"] = (line =~ /Mask:(\S+)/ ? ($1) : "nil") if line =~ /Mask:/ @interfaces[@int_name]["mtu"] = (line =~ /MTU:(\S+)/ ? ($1) : "nil") if line =~ /MTU:/ @interfaces[@int_name]["metric"] = (line =~ /Metric:(\S+)/ ? ($1) : "nil") if line =~ /Metric:/ end if @interfaces.has_key?(@new_resource.device) @interface = @interfaces.fetch(@new_resource.device) @current_resource.target(@new_resource.target) @current_resource.device(@new_resource.device) @current_resource.inet_addr(@interface["inet_addr"]) @current_resource.hwaddr(@interface["hwaddr"]) @current_resource.bcast(@interface["bcast"]) @current_resource.mask(@interface["mask"]) @current_resource.mtu(@interface["mtu"]) @current_resource.metric(@interface["metric"]) end end @current_resource end def define_resource_requirements requirements.assert(:all_actions) do |a| a.assertion { @status.exitstatus == 0 } a.failure_message Chef::Exceptions::Ifconfig, "ifconfig failed - #{@status.inspect}!" # no whyrun - if the base ifconfig used in load_current_resource fails # there's no reasonable action that could have been taken in the course of # a chef run to fix it. end end def action_add # check to see if load_current_resource found interface in ifconfig unless @current_resource.inet_addr unless @new_resource.device == loopback_device command = add_command converge_by ("run #{command} to add #{@new_resource}") do run_command( :command => command ) Chef::Log.info("#{@new_resource} added") end end end # Write out the config files generate_config end def action_enable # check to see if load_current_resource found ifconfig # enables, but does not manage config files unless @current_resource.inet_addr unless @new_resource.device == loopback_device command = enable_command converge_by ("run #{command} to enable #{@new_resource}") do run_command( :command => command ) Chef::Log.info("#{@new_resource} enabled") end end end end def action_delete # check to see if load_current_resource found the interface if @current_resource.device command = delete_command converge_by ("run #{command} to delete #{@new_resource}") do run_command( :command => command ) Chef::Log.info("#{@new_resource} deleted") end else Chef::Log.debug("#{@new_resource} does not exist - nothing to do") end delete_config end def action_disable # check to see if load_current_resource found the interface # disables, but leaves config files in place. if @current_resource.device command = disable_command converge_by ("run #{command} to disable #{@new_resource}") do run_command( :command => command ) Chef::Log.info("#{@new_resource} disabled") end else Chef::Log.debug("#{@new_resource} does not exist - nothing to do") end end def can_generate_config? ! @config_template.nil? && ! @config_path.nil? end def resource_for_config(path) Chef::Resource::File.new(path, run_context) end def generate_config return unless can_generate_config? b = binding template = ::ERB.new(@config_template) config = resource_for_config(@config_path) config.content(template.result(b)) config.run_action(:create) @new_resource.updated_by_last_action(true) if config.updated? end def delete_config return unless can_generate_config? config = resource_for_config(@config_path) config.run_action(:delete) @new_resource.updated_by_last_action(true) if config.updated? end private def add_command command = "ifconfig #{@new_resource.device} #{@new_resource.target}" command << " netmask #{@new_resource.mask}" if @new_resource.mask command << " metric #{@new_resource.metric}" if @new_resource.metric command << " mtu #{@new_resource.mtu}" if @new_resource.mtu command end def enable_command command = "ifconfig #{@new_resource.device} #{@new_resource.target}" command << " netmask #{@new_resource.mask}" if @new_resource.mask command << " metric #{@new_resource.metric}" if @new_resource.metric command << " mtu #{@new_resource.mtu}" if @new_resource.mtu command end def disable_command "ifconfig #{@new_resource.device} down" end def delete_command "ifconfig #{@new_resource.device} down" end def loopback_device "lo" end end end end chef-12.14.60/lib/chef/provider/ifconfig/000077500000000000000000000000001276456504500177615ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/ifconfig/aix.rb000066400000000000000000000070151276456504500210720ustar00rootroot00000000000000# # Author:: Kaustubh Deorukhkar (kaustubh@clogeny.com) # Copyright:: Copyright 2013-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/ifconfig" class Chef class Provider class Ifconfig class Aix < Chef::Provider::Ifconfig provides :ifconfig, platform: %w{aix} def load_current_resource @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name) @interface_exists = false found_interface = false interface = {} @status = shell_out("ifconfig -a") @status.stdout.each_line do |line| if !found_interface if line =~ /^(\S+):\sflags=(\S+)/ # We have interface name, if this is the interface for @current_resource, load info else skip till next interface is found. if $1 == @new_resource.device # Found interface found_interface = true @interface_exists = true @current_resource.target(@new_resource.target) @current_resource.device($1) interface[:flags] = $2 @current_resource.metric($1) if line =~ /metric\s(\S+)/ end end else # parse interface related information, stop when next interface is found. if line =~ /^(\S+):\sflags=(\S+)/ # we are done parsing interface info and hit another one, so stop. found_interface = false break else if found_interface # read up interface info @current_resource.inet_addr($1) if line =~ /inet\s(\S+)\s/ @current_resource.bcast($1) if line =~ /broadcast\s(\S+)/ @current_resource.mask(hex_to_dec_netmask($1)) if line =~ /netmask\s(\S+)\s/ end end end end @current_resource end private def add_command # ifconfig changes are temporary, chdev persist across reboots. raise Chef::Exceptions::Ifconfig, "interface metric attribute cannot be set for :add action" if @new_resource.metric command = "chdev -l #{@new_resource.device} -a netaddr=#{@new_resource.name}" command << " -a netmask=#{@new_resource.mask}" if @new_resource.mask command << " -a mtu=#{@new_resource.mtu}" if @new_resource.mtu command end def delete_command # ifconfig changes are temporary, chdev persist across reboots. "chdev -l #{@new_resource.device} -a state=down" end def loopback_device "lo0" end def hex_to_dec_netmask(netmask) # example '0xffff0000' -> '255.255.0.0' dec = netmask[2..3].to_i(16).to_s(10) [4, 6, 8].each { |n| dec = dec + "." + netmask[n..n + 1].to_i(16).to_s(10) } dec end end end end end chef-12.14.60/lib/chef/provider/ifconfig/debian.rb000066400000000000000000000065021276456504500215330ustar00rootroot00000000000000# # Author:: Xabier de Zuazo (xabier@onddo.com) # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/ifconfig" require "chef/util/file_edit" class Chef class Provider class Ifconfig class Debian < Chef::Provider::Ifconfig provides :ifconfig, platform: %w{ubuntu}, platform_version: ">= 11.10" provides :ifconfig, platform: %w{debian}, platform_version: ">= 7.0" INTERFACES_FILE = "/etc/network/interfaces" INTERFACES_DOT_D_DIR = "/etc/network/interfaces.d" def initialize(new_resource, run_context) super(new_resource, run_context) @config_template = %{ <% if @new_resource.device %> <% if @new_resource.onboot == "yes" %>auto <%= @new_resource.device %><% end %> <% case @new_resource.bootproto when "dhcp" %> iface <%= @new_resource.device %> inet dhcp <% when "bootp" %> iface <%= @new_resource.device %> inet bootp <% else %> iface <%= @new_resource.device %> inet static <% if @new_resource.target %>address <%= @new_resource.target %><% end %> <% if @new_resource.mask %>netmask <%= @new_resource.mask %><% end %> <% if @new_resource.network %>network <%= @new_resource.network %><% end %> <% if @new_resource.bcast %>broadcast <%= @new_resource.bcast %><% end %> <% if @new_resource.metric %>metric <%= @new_resource.metric %><% end %> <% if @new_resource.hwaddr %>hwaddress <%= @new_resource.hwaddr %><% end %> <% if @new_resource.mtu %>mtu <%= @new_resource.mtu %><% end %> <% end %> <% end %> } @config_path = "#{INTERFACES_DOT_D_DIR}/ifcfg-#{@new_resource.device}" end def generate_config enforce_interfaces_dot_d_sanity super end protected def enforce_interfaces_dot_d_sanity # create /etc/network/interfaces.d via dir resource (to get reporting, etc) dir = Chef::Resource::Directory.new(INTERFACES_DOT_D_DIR, run_context) dir.run_action(:create) new_resource.updated_by_last_action(true) if dir.updated_by_last_action? # roll our own file_edit resource, this will not get reported until we have a file_edit resource interfaces_dot_d_for_regexp = INTERFACES_DOT_D_DIR.gsub(/\./, '\.') # escape dots for the regexp regexp = %r{^\s*source\s+#{interfaces_dot_d_for_regexp}/\*\s*$} unless ::File.exists?(INTERFACES_FILE) && regexp.match(IO.read(INTERFACES_FILE)) converge_by("modifying #{INTERFACES_FILE} to source #{INTERFACES_DOT_D_DIR}") do conf = Chef::Util::FileEdit.new(INTERFACES_FILE) conf.insert_line_if_no_match(regexp, "source #{INTERFACES_DOT_D_DIR}/*") conf.write_file end end end end end end end chef-12.14.60/lib/chef/provider/ifconfig/redhat.rb000066400000000000000000000036521276456504500215630ustar00rootroot00000000000000# # Author:: Xabier de Zuazo (xabier@onddo.com) # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/ifconfig" class Chef class Provider class Ifconfig class Redhat < Chef::Provider::Ifconfig provides :ifconfig, platform_family: %w{fedora rhel} def initialize(new_resource, run_context) super(new_resource, run_context) @config_template = %{ <% if @new_resource.device %>DEVICE=<%= @new_resource.device %><% end %> <% if @new_resource.onboot == "yes" %>ONBOOT=<%= @new_resource.onboot %><% end %> <% if @new_resource.bootproto %>BOOTPROTO=<%= @new_resource.bootproto %><% end %> <% if @new_resource.target %>IPADDR=<%= @new_resource.target %><% end %> <% if @new_resource.mask %>NETMASK=<%= @new_resource.mask %><% end %> <% if @new_resource.network %>NETWORK=<%= @new_resource.network %><% end %> <% if @new_resource.bcast %>BROADCAST=<%= @new_resource.bcast %><% end %> <% if @new_resource.onparent %>ONPARENT=<%= @new_resource.onparent %><% end %> <% if @new_resource.hwaddr %>HWADDR=<%= @new_resource.hwaddr %><% end %> <% if @new_resource.metric %>METRIC=<%= @new_resource.metric %><% end %> <% if @new_resource.mtu %>MTU=<%= @new_resource.mtu %><% end %> } @config_path = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}" end end end end end chef-12.14.60/lib/chef/provider/launchd.rb000066400000000000000000000146021276456504500201430ustar00rootroot00000000000000# # Author:: Mike Dodge () # Copyright:: Copyright (c) 2015 Facebook, 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" require "chef/resource/launchd" require "chef/resource/file" require "chef/resource/cookbook_file" require "chef/resource/macosx_service" require "plist" require "forwardable" class Chef class Provider class Launchd < Chef::Provider extend Forwardable provides :launchd, os: "darwin" def_delegators :@new_resource, *[ :backup, :cookbook, :group, :label, :mode, :owner, :path, :source, :session_type, :type, ] def load_current_resource current_resource = Chef::Resource::Launchd.new(new_resource.name) @path = path ? path : gen_path_from_type end def gen_path_from_type types = { "daemon" => "/Library/LaunchDaemons/#{label}.plist", "agent" => "/Library/LaunchAgents/#{label}.plist", } types[type] end def action_create manage_plist(:create) end def action_create_if_missing manage_plist(:create_if_missing) end def action_delete # If you delete a service you want to make sure its not loaded or # the service will be in memory and you wont be able to stop it. if ::File.exists?(@path) manage_service(:disable) end manage_plist(:delete) end def action_enable if manage_plist(:create) manage_service(:restart) else manage_service(:enable) end end def action_disable manage_service(:disable) end def manage_plist(action) if source res = cookbook_file_resource else res = file_resource end res.run_action(action) new_resource.updated_by_last_action(true) if res.updated? res.updated end def manage_service(action) res = service_resource res.run_action(action) new_resource.updated_by_last_action(true) if res.updated? end def service_resource res = Chef::Resource::MacosxService.new(label, run_context) res.name(label) if label res.service_name(label) if label res.plist(@path) if @path res.session_type(session_type) if session_type res end def file_resource res = Chef::Resource::File.new(@path, run_context) res.name(@path) if @path res.backup(backup) if backup res.content(content) if content res.group(group) if group res.mode(mode) if mode res.owner(owner) if owner res end def cookbook_file_resource res = Chef::Resource::CookbookFile.new(@path, run_context) res.cookbook_name = cookbook if cookbook res.name(@path) if @path res.backup(backup) if backup res.group(group) if group res.mode(mode) if mode res.owner(owner) if owner res.source(source) if source res end def define_resource_requirements requirements.assert( :create, :create_if_missing, :delete, :enable, :disable ) do |a| type = new_resource.type a.assertion { %w{daemon agent}.include?(type.to_s) } error_msg = "type must be daemon or agent." a.failure_message Chef::Exceptions::ValidationFailed, error_msg end end def content? !!content end def content plist_hash = new_resource.hash || gen_hash Plist::Emit.dump(plist_hash) unless plist_hash.nil? end def gen_hash return nil unless new_resource.program || new_resource.program_arguments { "label" => "Label", "program" => "Program", "program_arguments" => "ProgramArguments", "abandon_process_group" => "AbandonProcessGroup", "debug" => "Debug", "disabled" => "Disabled", "enable_globbing" => "EnableGlobbing", "enable_transactions" => "EnableTransactions", "environment_variables" => "EnvironmentVariables", "exit_timeout" => "ExitTimeout", "ld_group" => "GroupName", "hard_resource_limits" => "HardreSourceLimits", "inetd_compatibility" => "inetdCompatibility", "init_groups" => "InitGroups", "keep_alive" => "KeepAlive", "launch_only_once" => "LaunchOnlyOnce", "limit_load_from_hosts" => "LimitLoadFromHosts", "limit_load_to_hosts" => "LimitLoadToHosts", "limit_load_to_session_type" => "LimitLoadToSessionType", "low_priority_io" => "LowPriorityIO", "mach_services" => "MachServices", "nice" => "Nice", "on_demand" => "OnDemand", "process_type" => "ProcessType", "queue_directories" => "QueueDirectories", "root_directory" => "RootDirectory", "run_at_load" => "RunAtLoad", "sockets" => "Sockets", "soft_resource_limits" => "SoftResourceLimits", "standard_error_path" => "StandardErrorPath", "standard_in_path" => "StandardInPath", "standard_out_path" => "StandardOutPath", "start_calendar_interval" => "StartCalendarInterval", "start_interval" => "StartInterval", "start_on_mount" => "StartOnMount", "throttle_interval" => "ThrottleInterval", "time_out" => "TimeOut", "umask" => "Umask", "username" => "UserName", "wait_for_debugger" => "WaitForDebugger", "watch_paths" => "WatchPaths", "working_directory" => "WorkingDirectory", }.each_with_object({}) do |(key, val), memo| memo[val] = new_resource.send(key) if new_resource.send(key) end end end end end chef-12.14.60/lib/chef/provider/link.rb000066400000000000000000000150111276456504500174550ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/log" require "chef/mixin/file_class" require "chef/resource/link" require "chef/provider" require "chef/scan_access_control" require "chef/util/path_helper" class Chef class Provider class Link < Chef::Provider provides :link include Chef::Mixin::EnforceOwnershipAndPermissions include Chef::Mixin::FileClass def negative_complement(big) if big > 1073741823 # Fixnum max big -= (2**32) # diminished radix wrap to negative end big end private :negative_complement def whyrun_supported? true end def load_current_resource @current_resource = Chef::Resource::Link.new(@new_resource.name) @current_resource.target_file(@new_resource.target_file) if file_class.symlink?(@current_resource.target_file) @current_resource.link_type(:symbolic) @current_resource.to( canonicalize(file_class.readlink(@current_resource.target_file)) ) else @current_resource.link_type(:hard) if ::File.exists?(@current_resource.target_file) if ::File.exists?(@new_resource.to) && file_class.stat(@current_resource.target_file).ino == file_class.stat(@new_resource.to).ino @current_resource.to(canonicalize(@new_resource.to)) else @current_resource.to("") end end end ScanAccessControl.new(@new_resource, @current_resource).set_all! @current_resource end def define_resource_requirements requirements.assert(:delete) do |a| a.assertion do if @current_resource.to @current_resource.link_type == @new_resource.link_type && (@current_resource.link_type == :symbolic || @current_resource.to != "") else true end end a.failure_message Chef::Exceptions::Link, "Cannot delete #{@new_resource} at #{@new_resource.target_file}! Not a #{@new_resource.link_type} link." a.whyrun("Would assume the link at #{@new_resource.target_file} was previously created") end end def canonicalize(path) Chef::Platform.windows? ? path.tr("/", '\\') : path end def action_create # current_resource is the symlink that currently exists # new_resource is the symlink we need to create # to - the location to link to # target_file - the name of the link if @current_resource.to != canonicalize(@new_resource.to) || @current_resource.link_type != @new_resource.link_type # Handle the case where the symlink already exists and is pointing at a valid to_file if @current_resource.to # On Windows, to fix a symlink already pointing at a directory we must first # ::Dir.unlink the symlink (not the directory), while if we have a symlink # pointing at file we must use ::File.unlink on the symlink. # However if the new symlink will point to a file and the current symlink is pointing at a # directory we want to throw an exception and calling ::File.unlink on the directory symlink # will throw the correct ones. if Chef::Platform.windows? && ::File.directory?(@new_resource.to) && ::File.directory?(@current_resource.target_file) converge_by("unlink existing windows symlink to dir at #{@new_resource.target_file}") do ::Dir.unlink(@new_resource.target_file) end else converge_by("unlink existing symlink to file at #{@new_resource.target_file}") do ::File.unlink(@new_resource.target_file) end end end if @new_resource.link_type == :symbolic converge_by("create symlink at #{@new_resource.target_file} to #{@new_resource.to}") do file_class.symlink(canonicalize(@new_resource.to), @new_resource.target_file) Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.target_file} -> #{@new_resource.to}") Chef::Log.info("#{@new_resource} created") end elsif @new_resource.link_type == :hard converge_by("create hard link at #{@new_resource.target_file} to #{@new_resource.to}") do file_class.link(@new_resource.to, @new_resource.target_file) Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.target_file} -> #{@new_resource.to}") Chef::Log.info("#{@new_resource} created") end end end if @new_resource.link_type == :symbolic if access_controls.requires_changes? converge_by(access_controls.describe_changes) do access_controls.set_all end end end end def action_delete if @current_resource.to # Exists if Chef::Platform.windows? && ::File.directory?(@current_resource.target_file) converge_by("delete link to dir at #{@new_resource.target_file}") do ::Dir.delete(@new_resource.target_file) Chef::Log.info("#{@new_resource} deleted") end else converge_by("delete link to file at #{@new_resource.target_file}") do ::File.delete(@new_resource.target_file) Chef::Log.info("#{@new_resource} deleted") end end end end # Implementation components *should not* follow symlinks when managing # access control (e.g., use lchmod instead of chmod) if the resource is a # symlink. def manage_symlink_access? @new_resource.link_type == :symbolic end end end end chef-12.14.60/lib/chef/provider/log.rb000066400000000000000000000026201276456504500173030ustar00rootroot00000000000000# # Author:: Cary Penniman () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Provider class Log # Chef log provider, allows logging to chef's logs from recipes class ChefLog < Chef::Provider provides :log def whyrun_supported? true end # No concept of a 'current' resource for logs, this is a no-op # # === Return # true:: Always return true def load_current_resource true end # Write the log to Chef's log # # === Return # true:: Always return true def action_write Chef::Log.send(@new_resource.level, @new_resource.message) @new_resource.updated_by_last_action(true) end end end end end chef-12.14.60/lib/chef/provider/lwrp_base.rb000066400000000000000000000062601276456504500205040ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # 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" require "chef/dsl/recipe" require "chef/dsl/include_recipe" class Chef class Provider # == Chef::Provider::LWRPBase # Base class from which LWRP providers inherit. class LWRPBase < Provider include Chef::DSL::Recipe # These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore. # They are not included by its replacement, Chef::DSL::Recipe, but # they may be used in existing LWRPs. include Chef::DSL::DataQuery # Allow include_recipe from within LWRP provider code include Chef::DSL::IncludeRecipe # no-op `load_current_resource`. Allows simple LWRP providers to work # without defining this method explicitly (silences # Chef::Exceptions::Override exception) def load_current_resource end # class methods class <) # Copyright:: Copyright 2009-2016, Joe Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/provider" class Chef class Provider class Mdadm < Chef::Provider provides :mdadm def popen4 raise Exception, "deprecated" end def whyrun_supported? true end def load_current_resource @current_resource = Chef::Resource::Mdadm.new(@new_resource.name) @current_resource.raid_device(@new_resource.raid_device) Chef::Log.debug("#{@new_resource} checking for software raid device #{@current_resource.raid_device}") device_not_found = 4 mdadm = shell_out!("mdadm --detail --test #{@new_resource.raid_device}", :returns => [0, device_not_found]) exists = (mdadm.status == 0) @current_resource.exists(exists) end def action_create unless @current_resource.exists converge_by("create RAID device #{new_resource.raid_device}") do command = "yes | mdadm --create #{@new_resource.raid_device} --level #{@new_resource.level}" command << " --chunk=#{@new_resource.chunk}" unless @new_resource.level == 1 command << " --metadata=#{@new_resource.metadata}" command << " --bitmap=#{@new_resource.bitmap}" if @new_resource.bitmap command << " --layout=#{@new_resource.layout}" if @new_resource.layout command << " --raid-devices #{@new_resource.devices.length} #{@new_resource.devices.join(" ")}" Chef::Log.debug("#{@new_resource} mdadm command: #{command}") shell_out!(command) Chef::Log.info("#{@new_resource} created raid device (#{@new_resource.raid_device})") end else Chef::Log.debug("#{@new_resource} raid device already exists, skipping create (#{@new_resource.raid_device})") end end def action_assemble unless @current_resource.exists converge_by("assemble RAID device #{new_resource.raid_device}") do command = "yes | mdadm --assemble #{@new_resource.raid_device} #{@new_resource.devices.join(" ")}" Chef::Log.debug("#{@new_resource} mdadm command: #{command}") shell_out!(command) Chef::Log.info("#{@new_resource} assembled raid device (#{@new_resource.raid_device})") end else Chef::Log.debug("#{@new_resource} raid device already exists, skipping assemble (#{@new_resource.raid_device})") end end def action_stop if @current_resource.exists converge_by("stop RAID device #{new_resource.raid_device}") do command = "yes | mdadm --stop #{@new_resource.raid_device}" Chef::Log.debug("#{@new_resource} mdadm command: #{command}") shell_out!(command) Chef::Log.info("#{@new_resource} stopped raid device (#{@new_resource.raid_device})") end else Chef::Log.debug("#{@new_resource} raid device doesn't exist (#{@new_resource.raid_device}) - not stopping") end end end end end chef-12.14.60/lib/chef/provider/mount.rb000066400000000000000000000127531276456504500176740ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Author:: Lamont Granquist () # Copyright:: Copyright 2009-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/mixin/shell_out" require "chef/provider" class Chef class Provider class Mount < Chef::Provider include Chef::Mixin::ShellOut attr_accessor :unmount_retries def whyrun_supported? true end def load_current_resource true end def initialize(new_resource, run_context) super self.unmount_retries = 20 end def action_mount unless current_resource.mounted converge_by("mount #{current_resource.device} to #{current_resource.mount_point}") do mount_fs Chef::Log.info("#{new_resource} mounted") end else Chef::Log.debug("#{new_resource} is already mounted") end end def action_umount if current_resource.mounted converge_by("unmount #{current_resource.device}") do umount_fs Chef::Log.info("#{new_resource} unmounted") end else Chef::Log.debug("#{new_resource} is already unmounted") end end def action_remount if current_resource.mounted if new_resource.supports[:remount] converge_by("remount #{current_resource.device}") do remount_fs Chef::Log.info("#{new_resource} remounted") end else converge_by("unmount #{current_resource.device}") do umount_fs Chef::Log.info("#{new_resource} unmounted") end wait_until_unmounted(unmount_retries) converge_by("mount #{current_resource.device}") do mount_fs Chef::Log.info("#{new_resource} mounted") end end else Chef::Log.debug("#{new_resource} not mounted, nothing to remount") end end def action_enable unless current_resource.enabled && mount_options_unchanged? converge_by("enable #{current_resource.device}") do enable_fs Chef::Log.info("#{new_resource} enabled") end else Chef::Log.debug("#{new_resource} already enabled") end end def action_disable if current_resource.enabled converge_by("disable #{current_resource.device}") do disable_fs Chef::Log.info("#{new_resource} disabled") end else Chef::Log.debug("#{new_resource} already disabled") end end # # Abstract Methods to be implemented by subclasses # # should actually check if the filesystem is mounted (not just return current_resource) and return true/false def mounted? raise Chef::Exceptions::UnsupportedAction, "#{self} does not implement #mounted?" end # should check new_resource against current_resource to see if mount options need updating, returns true/false def mount_options_unchanged? raise Chef::Exceptions::UnsupportedAction, "#{self} does not implement #mount_options_unchanged?" end # # NOTE: for the following methods, this superclass will already have checked if the filesystem is # enabled and/or mounted and they will be called in converge_by blocks, so most defensive checking # does not need to be done in the subclass implementation -- just do the thing. # # should implement mounting of the filesystem, raises if action does not succeed def mount_fs raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :mount" end # should implement unmounting of the filesystem, raises if action does not succeed def umount_fs raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :umount" end # should implement remounting of the filesystem (via a -o remount or some other atomic-ish action that isn't # simply a umount/mount style remount), raises if action does not succeed def remount_fs raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :remount" end # should implement enabling of the filesystem (e.g. in /etc/fstab), raises if action does not succeed def enable_fs raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :enable" end # should implement disabling of the filesystem (e.g. in /etc/fstab), raises if action does not succeed def disable_fs raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :disable" end private def wait_until_unmounted(tries) while mounted? if (tries -= 1) < 0 raise Chef::Exceptions::Mount, "Retries exceeded waiting for filesystem to unmount" end sleep 0.1 end end end end end chef-12.14.60/lib/chef/provider/mount/000077500000000000000000000000001276456504500173375ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/mount/aix.rb000066400000000000000000000160101276456504500204430ustar00rootroot00000000000000# # Author:: # Copyright:: Copyright 2009-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/mount" class Chef class Provider class Mount class Aix < Chef::Provider::Mount::Mount provides :mount, platform: %w{aix} # Override for aix specific handling def initialize(new_resource, run_context) super # options and fstype are set to "defaults" and "auto" respectively in the Mount Resource class. These options are not valid for AIX, override them. if @new_resource.options[0] == "defaults" @new_resource.options.clear end if @new_resource.fstype == "auto" @new_resource.send(:clear_fstype) end end def enabled? # Check to see if there is an entry in /etc/filesystems. Last entry for a volume wins. Using command "lsfs" to fetch entries. enabled = false # lsfs o/p = #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct # search only for current mount point shell_out("lsfs -c #{@new_resource.mount_point}").stdout.each_line do |line| case line when /^#\s/ next when /^#{Regexp.escape(@new_resource.mount_point)}:#{device_fstab_regex}:(\S+):(\[\S+\])?:(\S+)?:(\S+):(\S+):(\S+):(\S+)/ # mount point entry with ipv6 address for nodename (ipv6 address use ':') enabled = true @current_resource.fstype($1) @current_resource.options($5) Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/filesystems") next when /^#{Regexp.escape(@new_resource.mount_point)}:#{device_fstab_regex}::(\S+):(\S+)?:(\S+)?:(\S+):(\S+):(\S+):(\S+)/ # mount point entry with hostname or ipv4 address enabled = true @current_resource.fstype($1) @current_resource.options($5) Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/filesystems") next when /^#{Regexp.escape(@new_resource.mount_point)}/ enabled = false Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/filesystems") end end @current_resource.enabled(enabled) end def mounted? mounted = false shell_out!("mount").stdout.each_line do |line| if network_device? device_details = device_fstab.split(":") search_device = device_details[1] else search_device = device_fstab_regex end case line when /#{search_device}\s+#{Regexp.escape(@new_resource.mount_point)}/ mounted = true Chef::Log.debug("Special device #{device_logstring} mounted as #{@new_resource.mount_point}") when /^[\/\w]+\s+#{Regexp.escape(@new_resource.mount_point)}\s+/ mounted = false Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab") end end @current_resource.mounted(mounted) end def mount_fs unless @current_resource.mounted mountable? command = "mount -v #{@new_resource.fstype}" if !(@new_resource.options.nil? || @new_resource.options.empty?) command << " -o #{@new_resource.options.join(',')}" end command << case @new_resource.device_type when :device " #{device_real}" when :label " -L #{@new_resource.device}" when :uuid " -U #{@new_resource.device}" end command << " #{@new_resource.mount_point}" shell_out!(command) Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}") else Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}") end end def remount_command if !(@new_resource.options.nil? || @new_resource.options.empty?) return "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.device} #{@new_resource.mount_point}" else return "mount -o remount #{@new_resource.device} #{@new_resource.mount_point}" end end def enable_fs if @current_resource.enabled && mount_options_unchanged? Chef::Log.debug("#{@new_resource} is already enabled - nothing to do") return nil end if @current_resource.enabled # The current options don't match what we have, so # disable, then enable. disable_fs end ::File.open("/etc/filesystems", "a") do |fstab| fstab.puts("#{@new_resource.mount_point}:") if network_device? device_details = device_fstab.split(":") fstab.puts("\tdev\t\t= #{device_details[1]}") fstab.puts("\tnodename\t\t= #{device_details[0]}") else fstab.puts("\tdev\t\t= #{device_fstab}") end fstab.puts("\tvfs\t\t= #{@new_resource.fstype}") fstab.puts("\tmount\t\t= false") fstab.puts "\toptions\t\t= #{@new_resource.options.join(',')}" unless @new_resource.options.nil? || @new_resource.options.empty? Chef::Log.debug("#{@new_resource} is enabled at #{@new_resource.mount_point}") end end def disable_fs contents = [] if @current_resource.enabled found_device = false ::File.open("/etc/filesystems", "r").each_line do |line| case line when /^\/.+:\s*$/ if line =~ /#{Regexp.escape(@new_resource.mount_point)}+:/ found_device = true else found_device = false end end if !found_device contents << line end end ::File.open("/etc/filesystems", "w") do |fstab| contents.each { |line| fstab.puts line } end else Chef::Log.debug("#{@new_resource} is not enabled - nothing to do") end end end end end end chef-12.14.60/lib/chef/provider/mount/mount.rb000066400000000000000000000235201276456504500210300ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Copyright:: Copyright 2009-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/mount" require "chef/log" class Chef class Provider class Mount class Mount < Chef::Provider::Mount provides :mount def initialize(new_resource, run_context) super @real_device = nil end attr_accessor :real_device def load_current_resource @current_resource = Chef::Resource::Mount.new(@new_resource.name) @current_resource.mount_point(@new_resource.mount_point) @current_resource.device(@new_resource.device) mounted? enabled? end def mountable? # only check for existence of non-remote devices if device_should_exist? && !::File.exists?(device_real) raise Chef::Exceptions::Mount, "Device #{@new_resource.device} does not exist" elsif @new_resource.mount_point != "none" && !::File.exists?(@new_resource.mount_point) raise Chef::Exceptions::Mount, "Mount point #{@new_resource.mount_point} does not exist" end return true end def enabled? # Check to see if there is a entry in /etc/fstab. Last entry for a volume wins. enabled = false ::File.foreach("/etc/fstab") do |line| case line when /^[#\s]/ next when /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ enabled = true @current_resource.fstype($1) @current_resource.options($2) @current_resource.dump($3.to_i) @current_resource.pass($4.to_i) Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/fstab") next when /^[\/\w]+\s+#{Regexp.escape(@new_resource.mount_point)}\s+/ enabled = false Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab") end end @current_resource.enabled(enabled) end def mounted? mounted = false # "mount" outputs the mount points as real paths. Convert # the mount_point of the resource to a real path in case it # contains symlinks in its parents dirs. real_mount_point = if ::File.exists? @new_resource.mount_point ::File.realpath(@new_resource.mount_point) else @new_resource.mount_point end shell_out!("mount").stdout.each_line do |line| case line when /^#{device_mount_regex}\s+on\s+#{Regexp.escape(real_mount_point)}\s/ mounted = true Chef::Log.debug("Special device #{device_logstring} mounted as #{real_mount_point}") when /^([\/\w])+\son\s#{Regexp.escape(real_mount_point)}\s+/ mounted = false Chef::Log.debug("Special device #{$~[1]} mounted as #{real_mount_point}") end end @current_resource.mounted(mounted) end def mount_fs unless @current_resource.mounted mountable? command = "mount -t #{@new_resource.fstype}" command << " -o #{@new_resource.options.join(',')}" unless @new_resource.options.nil? || @new_resource.options.empty? command << case @new_resource.device_type when :device " #{device_real}" when :label " -L #{@new_resource.device}" when :uuid " -U #{@new_resource.device}" end command << " #{@new_resource.mount_point}" shell_out!(command) Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}") else Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}") end end def umount_fs if @current_resource.mounted shell_out!("umount #{@new_resource.mount_point}") Chef::Log.debug("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}") else Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point}") end end def remount_command return "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.mount_point}" end def remount_fs if @current_resource.mounted && @new_resource.supports[:remount] shell_out!(remount_command) @new_resource.updated_by_last_action(true) Chef::Log.debug("#{@new_resource} is remounted at #{@new_resource.mount_point}") elsif @current_resource.mounted umount_fs sleep 1 mount_fs else Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point} - nothing to do") end end def enable_fs if @current_resource.enabled && mount_options_unchanged? Chef::Log.debug("#{@new_resource} is already enabled - nothing to do") return nil end if @current_resource.enabled # The current options don't match what we have, so # disable, then enable. disable_fs end ::File.open("/etc/fstab", "a") do |fstab| fstab.puts("#{device_fstab} #{@new_resource.mount_point} #{@new_resource.fstype} #{@new_resource.options.nil? ? "defaults" : @new_resource.options.join(",")} #{@new_resource.dump} #{@new_resource.pass}") Chef::Log.debug("#{@new_resource} is enabled at #{@new_resource.mount_point}") end end def disable_fs if @current_resource.enabled contents = [] found = false ::File.readlines("/etc/fstab").reverse_each do |line| if !found && line =~ /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}\s/ found = true Chef::Log.debug("#{@new_resource} is removed from fstab") next else contents << line end end ::File.open("/etc/fstab", "w") do |fstab| contents.reverse_each { |line| fstab.puts line } end else Chef::Log.debug("#{@new_resource} is not enabled - nothing to do") end end def network_device? @new_resource.device =~ /:/ || @new_resource.device =~ /\/\// end def device_should_exist? ( @new_resource.device != "none" ) && ( not network_device? ) && ( not %w{ cgroup tmpfs fuse vboxsf }.include? @new_resource.fstype ) end private def device_fstab case @new_resource.device_type when :device @new_resource.device when :label "LABEL=#{@new_resource.device}" when :uuid "UUID=#{@new_resource.device}" end end def device_real if @real_device == nil if @new_resource.device_type == :device @real_device = @new_resource.device else @real_device = "" ret = shell_out("/sbin/findfs #{device_fstab}") device_line = ret.stdout.lines.first # stdout.first consumes @real_device = device_line.chomp unless device_line.nil? end end @real_device end def device_logstring case @new_resource.device_type when :device "#{device_real}" when :label "#{device_real} with label #{@new_resource.device}" when :uuid "#{device_real} with uuid #{@new_resource.device}" end end def device_mount_regex if network_device? # ignore trailing slash Regexp.escape(device_real) + "/?" elsif ::File.symlink?(device_real) # This regular expression tries to match device_real. If that does not match it will try to match the target of device_real. # So given a symlink like this: # /dev/mapper/vgroot-tmp.vol -> /dev/dm-9 # First it will try to match "/dev/mapper/vgroot-tmp.vol". If there is no match it will try matching for "/dev/dm-9". "(?:#{Regexp.escape(device_real)}|#{Regexp.escape(::File.expand_path(::File.readlink(device_real), ::File.dirname(device_real)))})" else Regexp.escape(device_real) end end def device_fstab_regex if @new_resource.device_type == :device device_mount_regex else device_fstab end end def mount_options_unchanged? @current_resource.fstype == @new_resource.fstype && @current_resource.options == @new_resource.options && @current_resource.dump == @new_resource.dump && @current_resource.pass == @new_resource.pass end end end end end chef-12.14.60/lib/chef/provider/mount/solaris.rb000066400000000000000000000241511276456504500213430ustar00rootroot00000000000000# Encoding: utf-8 # Author:: Hugo Fichter # Author:: Lamont Granquist () # Author:: Joshua Timberman () # Copyright:: Copyright 2009-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/mount" require "chef/log" require "forwardable" class Chef class Provider class Mount # Mount Solaris File systems class Solaris < Chef::Provider::Mount provides :mount, platform: %w{openindiana opensolaris nexentacore omnios solaris2 smartos} extend Forwardable VFSTAB = "/etc/vfstab".freeze def_delegator :@new_resource, :device, :device def_delegator :@new_resource, :device_type, :device_type def_delegator :@new_resource, :dump, :dump def_delegator :@new_resource, :fsck_device, :fsck_device def_delegator :@new_resource, :fstype, :fstype def_delegator :@new_resource, :mount_point, :mount_point def_delegator :@new_resource, :options, :options def_delegator :@new_resource, :pass, :pass def load_current_resource @current_resource = Chef::Resource::Mount.new(new_resource.name) current_resource.mount_point(mount_point) current_resource.device(device) current_resource.fsck_device(fsck_device) current_resource.device_type(device_type) update_current_resource_state end def define_resource_requirements requirements.assert(:mount, :remount) do |a| a.assertion { !device_should_exist? || ::File.exist?(device) } a.failure_message(Chef::Exceptions::Mount, "Device #{device} does not exist") a.whyrun("Assuming device #{device} would have been created") end unless fsck_device == "-" requirements.assert(:mount, :remount) do |a| a.assertion { ::File.exist?(fsck_device) } a.failure_message(Chef::Exceptions::Mount, "Device #{fsck_device} does not exist") a.whyrun("Assuming device #{fsck_device} would have been created") end end requirements.assert(:mount, :remount) do |a| a.assertion { ::File.exist?(mount_point) } a.failure_message(Chef::Exceptions::Mount, "Mount point #{mount_point} does not exist") a.whyrun("Assuming mount point #{mount_point} would have been created") end end def mount_fs actual_options = options || [] actual_options.delete("noauto") command = "mount -F #{fstype}" command << " -o #{actual_options.join(',')}" unless actual_options.empty? command << " #{device} #{mount_point}" shell_out!(command) end def umount_fs shell_out!("umount #{mount_point}") end def remount_fs # FIXME: Should remount always do the remount or only if the options change? actual_options = options || [] actual_options.delete("noauto") mount_options = actual_options.empty? ? "" : ",#{actual_options.join(',')}" shell_out!("mount -o remount#{mount_options} #{mount_point}") end def enable_fs unless mount_options_unchanged? # we are enabling because our options have changed, so disable first then re-enable. # XXX: this should be refactored to be the responsibility of the caller disable_fs if current_resource.enabled end vfstab_write(merge_vfstab_entry) end def disable_fs contents, found = delete_vfstab_entry if found vfstab_write(contents.reverse) else # this is likely some kind of internal error, since we should only call disable_fs when there # the filesystem we want to disable is enabled. Chef::Log.warn("#{new_resource} did not find the mountpoint to disable in the vfstab") end end def etc_tempfile yield Tempfile.open("vfstab", "/etc") end def mount_options_unchanged? new_options = options_remove_noauto(options) current_options = options_remove_noauto(current_resource.nil? ? nil : current_resource.options) current_resource.fsck_device == fsck_device && current_resource.fstype == fstype && current_options == new_options && current_resource.dump == dump && current_resource.pass == pass && current_resource.options.include?("noauto") == !mount_at_boot? end def update_current_resource_state current_resource.mounted(mounted?) (enabled, fstype, options, pass) = read_vfstab_status current_resource.enabled(enabled) current_resource.fstype(fstype) current_resource.options(options) current_resource.pass(pass) end def enabled? read_vfstab_status[0] end # Check for the device in mounttab. # on type on # /dev/dsk/c1t0d0s0 on / type ufs read/write/setuid/devices/intr/largefiles/logging/xattr/onerror=panic/dev=700040 on Tue May 1 11:33:55 2012 def mounted? mounted = false shell_out!("mount -v").stdout.each_line do |line| case line when /^#{device_regex}\s+on\s+#{Regexp.escape(mount_point)}\s+/ Chef::Log.debug("Special device #{device} is mounted as #{mount_point}") mounted = true when /^([\/\w]+)\son\s#{Regexp.escape(mount_point)}\s+/ Chef::Log.debug("Special device #{Regexp.last_match[1]} is mounted as #{mount_point}") mounted = false end end mounted end private def read_vfstab_status # Check to see if there is an entry in /etc/vfstab. Last entry for a volume wins. enabled = false fstype = options = pass = nil ::File.foreach(VFSTAB) do |line| case line when /^[#\s]/ next # solaris /etc/vfstab format: # device device mount FS fsck mount mount # to mount to fsck point type pass at boot options when /^#{device_regex}\s+[-\/\w]+\s+#{Regexp.escape(mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ enabled = true fstype = Regexp.last_match[1] options = Regexp.last_match[4] # Store the 'mount at boot' column from vfstab as the 'noauto' option # in current_resource.options (linux style) if Regexp.last_match[3] == "no" if options.nil? || options.empty? options = "noauto" else options += ",noauto" end end pass = (Regexp.last_match[2] == "-") ? 0 : Regexp.last_match[2].to_i Chef::Log.debug("Found mount #{device} to #{mount_point} in #{VFSTAB}") next when /^[-\/\w]+\s+[-\/\w]+\s+#{Regexp.escape(mount_point)}\s+/ # if we find a mountpoint on top of our mountpoint, then we are not enabled enabled = false Chef::Log.debug("Found conflicting mount point #{mount_point} in #{VFSTAB}") end end [enabled, fstype, options, pass] end def device_should_exist? !%w{tmpfs nfs ctfs proc mntfs objfs sharefs fd smbfs vxfs}.include?(fstype) end def mount_at_boot? options.nil? || !options.include?("noauto") end def vfstab_write(contents) etc_tempfile do |f| f.write(contents.join("")) f.close # move, preserving modes of destination file mover = Chef::FileContentManagement::Deploy.strategy(true) mover.deploy(f.path, VFSTAB) end end def vfstab_entry actual_options = unless options.nil? tempops = options.dup tempops.delete("noauto") tempops end autostr = mount_at_boot? ? "yes" : "no" passstr = pass == 0 ? "-" : pass optstr = (actual_options.nil? || actual_options.empty?) ? "-" : actual_options.join(",") "\n#{device}\t#{fsck_device}\t#{mount_point}\t#{fstype}\t#{passstr}\t#{autostr}\t#{optstr}\n" end def delete_vfstab_entry contents = [] found = false ::File.readlines(VFSTAB).reverse_each do |line| if !found && line =~ /^#{device_regex}\s+\S+\s+#{Regexp.escape(mount_point)}/ found = true Chef::Log.debug("#{new_resource} is removed from vfstab") next end contents << line end [contents, found] end def merge_vfstab_entry contents = ::File.readlines(VFSTAB) contents[-1].chomp! contents << vfstab_entry end def options_remove_noauto(temp_options) new_options = [] new_options += temp_options.nil? ? [] : temp_options new_options.delete("noauto") new_options end def device_regex if ::File.symlink?(device) "(?:#{Regexp.escape(device)}|#{Regexp.escape(::File.expand_path(::File.readlink(device), ::File.dirname(device)))})" else Regexp.escape(device) end end end end end end chef-12.14.60/lib/chef/provider/mount/windows.rb000066400000000000000000000060401276456504500213560ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/mount" if RUBY_PLATFORM =~ /mswin|mingw32|windows/ require "chef/util/windows/net_use" require "chef/util/windows/volume" end class Chef class Provider class Mount class Windows < Chef::Provider::Mount provides :mount, os: "windows" def is_volume(name) name =~ /^\\\\\?\\Volume\{[\w-]+\}\\$/ ? true : false end def initialize(new_resource, run_context) super @mount = nil end def load_current_resource if is_volume(@new_resource.device) @mount = Chef::Util::Windows::Volume.new(@new_resource.name) else #assume network drive @mount = Chef::Util::Windows::NetUse.new(@new_resource.name) end @current_resource = Chef::Resource::Mount.new(@new_resource.name) @current_resource.mount_point(@new_resource.mount_point) Chef::Log.debug("Checking for mount point #{@current_resource.mount_point}") begin @current_resource.device(@mount.device) Chef::Log.debug("#{@current_resource.device} mounted on #{@new_resource.mount_point}") @current_resource.mounted(true) rescue ArgumentError => e @current_resource.mounted(false) Chef::Log.debug("#{@new_resource.mount_point} is not mounted: #{e.message}") end end def mount_fs unless @current_resource.mounted @mount.add(:remote => @new_resource.device, :username => @new_resource.username, :domainname => @new_resource.domain, :password => @new_resource.password) Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}") else Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}") end end def umount_fs if @current_resource.mounted @mount.delete Chef::Log.debug("#{@new_resource} is no longer mounted at #{@new_resource.mount_point}") else Chef::Log.debug("#{@new_resource} is not mounted at #{@new_resource.mount_point}") end end private def mount_options_unchanged? @current_resource.device == @new_resource.device end end end end end chef-12.14.60/lib/chef/provider/noop.rb000066400000000000000000000021601276456504500174740ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 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 Provider class Noop < Chef::Provider def load_current_resource; end def respond_to_missing?(method_sym, include_private = false) method_sym.to_s.start_with?("action_") || super end def method_missing(method_sym, *arguments, &block) if method_sym.to_s =~ /^action_/ Chef::Log.debug("NoOp-ing for #{method_sym}") else super end end end end end chef-12.14.60/lib/chef/provider/ohai.rb000066400000000000000000000027171276456504500174510ustar00rootroot00000000000000# # Author:: Michael Leianrtas () # Copyright:: Copyright 2010-2016, Michael Leinartas # 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 "ohai" class Chef class Provider class Ohai < Chef::Provider provides :ohai def whyrun_supported? true end def load_current_resource true end def action_reload converge_by("re-run ohai and merge results into node attributes") do ohai = ::Ohai::System.new # If @new_resource.plugin is nil, ohai will reload all the plugins # Otherwise it will only reload the specified plugin # Note that any changes to plugins, or new plugins placed on # the path are picked up by ohai. ohai.all_plugins @new_resource.plugin node.automatic_attrs.merge! ohai.data Chef::Log.info("#{@new_resource} reloaded") end end end end end chef-12.14.60/lib/chef/provider/osx_profile.rb000066400000000000000000000177501276456504500210650ustar00rootroot00000000000000# # Author:: Nate Walck () # Copyright:: Copyright 2015-2016, Facebook, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/provider" require "chef/resource" require "chef/resource/file" require "uuidtools" class Chef class Provider class OsxProfile < Chef::Provider include Chef::Mixin::Command provides :osx_profile, os: "darwin" provides :osx_config_profile, os: "darwin" def whyrun_supported? true end def load_current_resource @current_resource = Chef::Resource::OsxProfile.new(@new_resource.name) @current_resource.profile_name(@new_resource.profile_name) all_profiles = get_installed_profiles @new_resource.profile( @new_resource.profile || @new_resource.profile_name ) @new_profile_hash = get_profile_hash(@new_resource.profile) @new_profile_hash["PayloadUUID"] = config_uuid(@new_profile_hash) if @new_profile_hash if @new_profile_hash @new_profile_identifier = @new_profile_hash["PayloadIdentifier"] else @new_profile_identifier = @new_resource.identifier || @new_resource.profile_name end current_profile = nil if all_profiles && !all_profiles.empty? current_profile = all_profiles["_computerlevel"].find do |item| item["ProfileIdentifier"] == @new_profile_identifier end end @current_resource.profile(current_profile) end def define_resource_requirements requirements.assert(:remove) do |a| if @new_profile_identifier a.assertion do !@new_profile_identifier.nil? && !@new_profile_identifier.end_with?(".mobileconfig") && /^\w+(?:(\.| )\w+)+$/.match(@new_profile_identifier) end a.failure_message RuntimeError, "when removing using the identifier attribute, it must match the profile identifier" else new_profile_name = @new_resource.profile_name a.assertion do !new_profile_name.end_with?(".mobileconfig") && /^\w+(?:(\.| )\w+)+$/.match(new_profile_name) end a.failure_message RuntimeError, "When removing by resource name, it must match the profile identifier " end end requirements.assert(:install) do |a| if @new_profile_hash.is_a?(Hash) a.assertion do @new_profile_hash.include?("PayloadIdentifier") end a.failure_message RuntimeError, "The specified profile does not seem to be valid" end if @new_profile_hash.is_a?(String) a.assertion do @new_profile_hash.end_with?(".mobileconfig") end a.failure_message RuntimeError, "#{new_profile_hash}' is not a valid profile" end end end def action_install unless profile_installed? converge_by("install profile #{@new_profile_identifier}") do profile_path = write_profile_to_disk install_profile(profile_path) get_installed_profiles(true) end end end def action_remove # Clean up profile after removing it if profile_installed? converge_by("remove profile #{@new_profile_identifier}") do remove_profile get_installed_profiles(true) end end end def load_profile_hash(new_profile) # file must exist in cookbook if new_profile.end_with?(".mobileconfig") unless cookbook_file_available?(new_profile) error_string = "#{self}: '#{new_profile}' not found in cookbook" raise Chef::Exceptions::FileNotFound, error_string end cookbook_profile = cache_cookbook_profile(new_profile) return read_plist(cookbook_profile) else return nil end end def cookbook_file_available?(cookbook_file) run_context.has_cookbook_file_in_cookbook?( @new_resource.cookbook_name, cookbook_file ) end def get_cache_dir cache_dir = Chef::FileCache.create_cache_path( "profiles/#{@new_resource.cookbook_name}" ) end def cache_cookbook_profile(cookbook_file) Chef::FileCache.create_cache_path( ::File.join( "profiles", @new_resource.cookbook_name, ::File.dirname(cookbook_file) ) ) remote_file = Chef::Resource::CookbookFile.new( ::File.join( get_cache_dir, "#{cookbook_file}.remote" ), run_context ) remote_file.cookbook_name = @new_resource.cookbook_name remote_file.source(cookbook_file) remote_file.backup(false) remote_file.run_action(:create) remote_file.path end def get_profile_hash(new_profile) if new_profile.is_a?(Hash) return new_profile elsif new_profile.is_a?(String) return load_profile_hash(new_profile) end end def config_uuid(profile) # Make a UUID of the profile contents and return as string UUIDTools::UUID.sha1_create( UUIDTools::UUID_DNS_NAMESPACE, profile.to_s ).to_s end def write_profile_to_disk @new_resource.path(Chef::FileCache.create_cache_path("profiles")) tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile tempfile.write(@new_profile_hash.to_plist) tempfile.close tempfile.path end def install_profile(profile_path) cmd = "profiles -I -F '#{profile_path}'" Chef::Log.debug("cmd: #{cmd}") shellout_results = shell_out(cmd) shellout_results.exitstatus end def remove_profile cmd = "profiles -R -p '#{@new_profile_identifier}'" Chef::Log.debug("cmd: #{cmd}") shellout_results = shell_out(cmd) shellout_results.exitstatus end def get_installed_profiles(update = nil) if update node.run_state[:config_profiles] = query_installed_profiles else node.run_state[:config_profiles] ||= query_installed_profiles end end def query_installed_profiles # Dump all profile metadata to a tempfile tempfile = generate_tempfile write_installed_profiles(tempfile) installed_profiles = read_plist(tempfile) Chef::Log.debug("Saved profiles to run_state") # Clean up the temp file as we do not need it anymore ::File.unlink(tempfile) installed_profiles end def generate_tempfile tempfile = ::Dir::Tmpname.create("allprofiles.plist") {} end def write_installed_profiles(tempfile) cmd = "profiles -P -o '#{tempfile}'" shell_out!(cmd) end def read_plist(xml_file) Plist.parse_xml(xml_file) end def profile_installed? # Profile Identifier and UUID must match a currently installed profile if @current_resource.profile.nil? || @current_resource.profile.empty? false else if @new_resource.action.include?(:remove) true else @current_resource.profile["ProfileUUID"] == @new_profile_hash["PayloadUUID"] end end end end end end chef-12.14.60/lib/chef/provider/package.rb000066400000000000000000000602351276456504500201230ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/shell_out" require "chef/mixin/command" require "chef/mixin/subclass_directive" require "chef/log" require "chef/file_cache" require "chef/platform" require "chef/decorator/lazy_array" class Chef class Provider class Package < Chef::Provider include Chef::Mixin::Command include Chef::Mixin::ShellOut extend Chef::Mixin::SubclassDirective # subclasses declare this if they want all their arguments as arrays of packages and names subclass_directive :use_multipackage_api # subclasses declare this if they want sources (filenames) pulled from their package names subclass_directive :use_package_name_for_source # # Hook that subclasses use to populate the candidate_version(s) # # @return [Array, String] candidate_version(s) may be a string or array attr_accessor :candidate_version def initialize(new_resource, run_context) super @candidate_version = nil end def whyrun_supported? true end def check_resource_semantics! # FIXME: this is not universally true and subclasses are needing to override this and no-ops it. It should be turned into # another "subclass_directive" and the apt and yum providers should declare that they need this behavior. if new_resource.package_name.is_a?(Array) && new_resource.source != nil raise Chef::Exceptions::InvalidResourceSpecification, "You may not specify both multipackage and source" end end def load_current_resource end def define_resource_requirements # XXX: upgrade with a specific version doesn't make a whole lot of sense, but why don't we throw this anyway if it happens? # if not, shouldn't we raise to tell the user to use install instead of upgrade if they want to pin a version? requirements.assert(:install) do |a| a.assertion { candidates_exist_for_all_forced_changes? } a.failure_message(Chef::Exceptions::Package, "No version specified, and no candidate version available for #{forced_packages_missing_candidates.join(", ")}") a.whyrun("Assuming a repository that offers #{forced_packages_missing_candidates.join(", ")} would have been configured") end # XXX: Does it make sense to pass in a source with :upgrade? Probably # not, but as with the above comment, we don't yet enforce such a thing, # so we'll just leave things as-is for now. requirements.assert(:upgrade, :install) do |a| a.assertion { candidates_exist_for_all_uninstalled? || new_resource.source } a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(", ")}") a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(", ")} would have been configured") end end def action_install unless target_version_array.any? Chef::Log.debug("#{@new_resource} is already installed - nothing to do") return end # @todo: move the preseed code out of the base class (and complete the fix for Array of preseeds? ugh...) if @new_resource.response_file if preseed_file = get_preseed_file(package_names_for_targets, versions_for_targets) converge_by("preseed package #{package_names_for_targets}") do preseed_package(preseed_file) end end end converge_by(install_description) do multipackage_api_adapter(package_names_for_targets, versions_for_targets) do |name, version| install_package(name, version) end Chef::Log.info("#{@new_resource} installed #{package_names_for_targets} at #{versions_for_targets}") end end def install_description description = [] target_version_array.each_with_index do |target_version, i| next if target_version.nil? package_name = package_name_array[i] description << "install version #{target_version} of package #{package_name}" end description end private :install_description def action_upgrade if !target_version_array.any? Chef::Log.debug("#{@new_resource} no versions to upgrade - nothing to do") return end converge_by(upgrade_description) do multipackage_api_adapter(package_names_for_targets, versions_for_targets) do |name, version| upgrade_package(name, version) end log_allow_downgrade = allow_downgrade ? "(allow_downgrade)" : "" Chef::Log.info("#{@new_resource} upgraded#{log_allow_downgrade} #{package_names_for_targets} to #{versions_for_targets}") end end def upgrade_description log_allow_downgrade = allow_downgrade ? "(allow_downgrade)" : "" description = [] target_version_array.each_with_index do |target_version, i| next if target_version.nil? package_name = package_name_array[i] candidate_version = candidate_version_array[i] current_version = current_version_array[i] || "uninstalled" description << "upgrade#{log_allow_downgrade} package #{package_name} from #{current_version} to #{candidate_version}" end description end private :upgrade_description def action_remove if removing_package? description = @new_resource.version ? "version #{@new_resource.version} of " : "" converge_by("remove #{description}package #{@current_resource.package_name}") do multipackage_api_adapter(@current_resource.package_name, @new_resource.version) do |name, version| remove_package(name, version) end Chef::Log.info("#{@new_resource} removed") end else Chef::Log.debug("#{@new_resource} package does not exist - nothing to do") end end def have_any_matching_version? f = [] new_version_array.each_with_index do |item, index| f << (item == current_version_array[index]) end f.any? end def removing_package? if !current_version_array.any? # ! any? means it's all nil's, which means nothing is installed false elsif !new_version_array.any? true # remove any version of all packages elsif have_any_matching_version? true # remove the version we have else false # we don't have the version we want to remove end end def action_purge if removing_package? description = @new_resource.version ? "version #{@new_resource.version} of" : "" converge_by("purge #{description} package #{@current_resource.package_name}") do multipackage_api_adapter(@current_resource.package_name, @new_resource.version) do |name, version| purge_package(name, version) end Chef::Log.info("#{@new_resource} purged") end end end def action_reconfig if @current_resource.version == nil Chef::Log.debug("#{@new_resource} is NOT installed - nothing to do") return end unless @new_resource.response_file Chef::Log.debug("#{@new_resource} no response_file provided - nothing to do") return end if preseed_file = get_preseed_file(@new_resource.package_name, @current_resource.version) converge_by("reconfigure package #{@new_resource.package_name}") do preseed_package(preseed_file) multipackage_api_adapter(@new_resource.package_name, @current_resource.version) do |name, version| reconfig_package(name, version) end Chef::Log.info("#{@new_resource} reconfigured") end else Chef::Log.debug("#{@new_resource} preseeding has not changed - nothing to do") end end # @todo use composition rather than inheritance def multipackage_api_adapter(name, version) if use_multipackage_api? yield [name].flatten, [version].flatten else yield name, version end end def install_package(name, version) raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :install" end def upgrade_package(name, version) raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :upgrade" end def remove_package(name, version) raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :remove" end def purge_package(name, version) raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :purge" end def preseed_package(file) raise Chef::Exceptions::UnsupportedAction, "#{self} does not support pre-seeding package install/upgrade instructions" end def reconfig_package(name, version) raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :reconfig" ) end # used by subclasses. deprecated. use #a_to_s instead. def expand_options(options) options ? " #{options}" : "" end # Check the current_version against either the candidate_version or the new_version # # For some reason the windows provider subclasses this (to implement passing Arrays to # versions for some reason other than multipackage stuff, which is mildly terrifying). # # This MUST have 'equality' semantics -- the exact thing matches the exact thing. # # The current_version should probably be dropped out of the method signature, it should # always be the first argument. # # The name is not just bad, but i find it completely misleading, consider: # # target_version_already_installed?(current_version, new_version) # target_version_already_installed?(current_version, candidate_version) # # which of those is the 'target_version'? i'd say the new_version and i'm confused when # i see it called with the candidate_version. # # `current_version_equals?(version)` would be a better name def target_version_already_installed?(current_version, target_version) return false unless current_version && target_version current_version == target_version end # Check the current_version against the new_resource.version, possibly using fuzzy # matching criteria. # # Subclasses MAY override this to provide fuzzy matching on the resource ('>=' and '~>' stuff) # # This should only ever be offered the same arguments (so they should most likely be # removed from the method signature). # # `new_version_satisfied?()` might be a better name def version_requirement_satisfied?(current_version, new_version) target_version_already_installed?(current_version, new_version) end # @todo: extract apt/dpkg specific preseeding to a helper class def get_preseed_file(name, version) resource = preseed_resource(name, version) resource.run_action(:create) Chef::Log.debug("#{@new_resource} fetched preseed file to #{resource.path}") if resource.updated_by_last_action? resource.path else false end end # @todo: extract apt/dpkg specific preseeding to a helper class def preseed_resource(name, version) # A directory in our cache to store this cookbook's preseed files in file_cache_dir = Chef::FileCache.create_cache_path("preseed/#{@new_resource.cookbook_name}") # The full path where the preseed file will be stored cache_seed_to = "#{file_cache_dir}/#{name}-#{version}.seed" Chef::Log.debug("#{@new_resource} fetching preseed file to #{cache_seed_to}") if template_available?(@new_resource.response_file) Chef::Log.debug("#{@new_resource} fetching preseed file via Template") remote_file = Chef::Resource::Template.new(cache_seed_to, run_context) remote_file.variables(@new_resource.response_file_variables) elsif cookbook_file_available?(@new_resource.response_file) Chef::Log.debug("#{@new_resource} fetching preseed file via cookbook_file") remote_file = Chef::Resource::CookbookFile.new(cache_seed_to, run_context) else message = "No template or cookbook file found for response file #{@new_resource.response_file}" raise Chef::Exceptions::FileNotFound, message end remote_file.cookbook_name = @new_resource.cookbook_name remote_file.source(@new_resource.response_file) remote_file.backup(false) remote_file end # helper method used by subclasses # def as_array(thing) [ thing ].flatten end private # Returns the package names which need to be modified. If the resource was called with an array of packages # then this will return an array of packages to update (may have 0 or 1 entries). If the resource was called # with a non-array package_name to manage then this will return a string rather than an Array. The output # of this is meant to be fed into subclass interfaces to install/upgrade packages and not all of them are # Array-aware. # # @return [String, Array] package_name(s) to actually update/install def package_names_for_targets package_names_for_targets = [] target_version_array.each_with_index do |target_version, i| next if target_version.nil? package_name = package_name_array[i] package_names_for_targets.push(package_name) end multipackage? ? package_names_for_targets : package_names_for_targets[0] end # Returns the package versions which need to be modified. If the resource was called with an array of packages # then this will return an array of versions to update (may have 0 or 1 entries). If the resource was called # with a non-array package_name to manage then this will return a string rather than an Array. The output # of this is meant to be fed into subclass interfaces to install/upgrade packages and not all of them are # Array-aware. # # @return [String, Array] package version(s) to actually update/install def versions_for_targets versions_for_targets = [] target_version_array.each_with_index do |target_version, i| next if target_version.nil? versions_for_targets.push(target_version) end multipackage? ? versions_for_targets : versions_for_targets[0] end # Return an array indexed the same as *_version_array which contains either the target version to install/upgrade to # or else nil if the package is not being modified. # # @return [Array] array of package versions which need to be upgraded (nil = not being upgraded) def target_version_array @target_version_array ||= begin target_version_array = [] each_package do |package_name, new_version, current_version, candidate_version| case action when :upgrade if target_version_already_installed?(current_version, new_version) # this is an odd use case Chef::Log.debug("#{new_resource} #{package_name} #{new_version} is already installed -- you are equality pinning with an :upgrade action, this may be deprecated in the future") target_version_array.push(nil) elsif target_version_already_installed?(current_version, candidate_version) Chef::Log.debug("#{new_resource} #{package_name} #{candidate_version} is already installed") target_version_array.push(nil) elsif candidate_version.nil? Chef::Log.debug("#{new_resource} #{package_name} has no candidate_version to upgrade to") target_version_array.push(nil) else Chef::Log.debug("#{new_resource} #{package_name} is out of date, will upgrade to #{candidate_version}") target_version_array.push(candidate_version) end when :install if new_version if version_requirement_satisfied?(current_version, new_version) Chef::Log.debug("#{new_resource} #{package_name} #{current_version} satisifies #{new_version} requirement") target_version_array.push(nil) else Chef::Log.debug("#{new_resource} #{package_name} #{current_version} needs updating to #{new_version}") target_version_array.push(new_version) end elsif current_version.nil? Chef::Log.debug("#{new_resource} #{package_name} not installed, installing #{candidate_version}") target_version_array.push(candidate_version) else Chef::Log.debug("#{new_resource} #{package_name} #{current_version} already installed") target_version_array.push(nil) end else # in specs please test the public interface provider.run_action(:install) instead of provider.action_install raise "internal error - target_version_array in package provider does not understand this action" end end target_version_array end end # Check the list of current_version_array and candidate_version_array. For any of the # packages if both versions are missing (uninstalled and no candidate) this will be an # unsolvable error. # # @return [Boolean] valid candidates exist for all uninstalled packages def candidates_exist_for_all_uninstalled? packages_missing_candidates.empty? end # Returns array of all packages which are missing candidate versions. # # @return [Array] names of packages missing candidates def packages_missing_candidates @packages_missing_candidates ||= begin missing = [] each_package do |package_name, new_version, current_version, candidate_version| missing.push(package_name) if current_version.nil? && candidate_version.nil? end missing end end # This looks for packages which have a new_version and a current_version, and they are # different (a "forced change") and for which there is no candidate. This is an edge # condition that candidates_exist_for_all_uninstalled? does not catch since in this case # it is not uninstalled but must be installed anyway and no version exists. # # @return [Boolean] valid candidates exist for all uninstalled packages def candidates_exist_for_all_forced_changes? forced_packages_missing_candidates.empty? end # Returns an array of all forced packages which are missing candidate versions # # @return [Array] names of packages missing candidates def forced_packages_missing_candidates @forced_packages_missing_candidates ||= begin missing = [] each_package do |package_name, new_version, current_version, candidate_version| next if new_version.nil? || current_version.nil? if !version_requirement_satisfied?(current_version, new_version) && candidate_version.nil? missing.push(package_name) end end missing end end # Helper to iterate over all the indexed *_array's in sync # # @yield [package_name, new_version, current_version, candidate_version] Description of block def each_package package_name_array.each_with_index do |package_name, i| candidate_version = candidate_version_array[i] current_version = current_version_array[i] new_version = new_version_array[i] yield package_name, new_version, current_version, candidate_version end end # @return [Boolean] if we're doing a multipackage install or not def multipackage? new_resource.package_name.is_a?(Array) end # @return [Array] package_name(s) as an array def package_name_array [ new_resource.package_name ].flatten end # @return [Array] candidate_version(s) as an array def candidate_version_array # NOTE: even with use_multipackage_api candidate_version may be a bare nil and need wrapping # ( looking at you, dpkg provider... ) Chef::Decorator::LazyArray.new { [ candidate_version ].flatten } end # @return [Array] current_version(s) as an array def current_version_array [ current_resource.version ].flatten end # @return [Array] new_version(s) as an array def new_version_array [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v } end # TIP: less error prone to simply always call resolved_source_array, even if you # don't think that you need to. # # @return [Array] new_resource.source as an array def source_array if new_resource.source.nil? package_name_array.map { nil } else [ new_resource.source ].flatten end end # Helper to handle use_package_name_for_source to convert names into local packages to install. # # @return [Array] Array of sources with package_names converted to sources def resolved_source_array @resolved_source_array ||= begin source_array.each_with_index.map do |source, i| package_name = package_name_array[i] # we require at least one '/' in the package_name to avoid [XXX_]package 'foo' breaking due to a random 'foo' file in cwd if use_package_name_for_source? && source.nil? && package_name.match(/#{::File::SEPARATOR}/) && ::File.exist?(package_name) Chef::Log.debug("No package source specified, but #{package_name} exists on filesystem, using #{package_name} as source.") package_name else source end end end end # @todo: extract apt/dpkg specific preseeding to a helper class def template_available?(path) run_context.has_template_in_cookbook?(new_resource.cookbook_name, path) end # @todo: extract apt/dpkg specific preseeding to a helper class def cookbook_file_available?(path) run_context.has_cookbook_file_in_cookbook?(new_resource.cookbook_name, path) end def allow_downgrade if @new_resource.respond_to?("allow_downgrade") @new_resource.allow_downgrade else false end end def shell_out_with_timeout(*command_args) shell_out(*add_timeout_option(command_args)) end def shell_out_with_timeout!(*command_args) shell_out!(*add_timeout_option(command_args)) end def add_timeout_option(command_args) args = command_args.dup if args.last.is_a?(Hash) options = args.pop.dup options[:timeout] = new_resource.timeout if new_resource.timeout options[:timeout] = 900 unless options.has_key?(:timeout) args << options else args << { :timeout => new_resource.timeout ? new_resource.timeout : 900 } end args end end end end chef-12.14.60/lib/chef/provider/package/000077500000000000000000000000001276456504500175705ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/package/aix.rb000066400000000000000000000136671276456504500207130ustar00rootroot00000000000000# # Author:: Deepali Jagtap # Copyright:: Copyright 2013-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" require "chef/mixin/command" require "chef/resource/package" require "chef/mixin/get_source_from_package" class Chef class Provider class Package class Aix < Chef::Provider::Package provides :package, os: "aix" provides :bff_package, os: "aix" include Chef::Mixin::GetSourceFromPackage def define_resource_requirements super requirements.assert(:install) do |a| a.assertion { @new_resource.source } a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install" end requirements.assert(:all_actions) do |a| a.assertion { !@new_resource.source || @package_source_found } a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}" a.whyrun "would assume #{@new_resource.source} would be have previously been made available" end end def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) if @new_resource.source @package_source_found = ::File.exists?(@new_resource.source) if @package_source_found Chef::Log.debug("#{@new_resource} checking pkg status") ret = shell_out_with_timeout("installp -L -d #{@new_resource.source}") ret.stdout.each_line do |line| case line when /:#{@new_resource.package_name}:/ fields = line.split(":") @new_resource.version(fields[2]) when /^#{@new_resource.package_name}:/ Chef::Log.warn("You are installing a bff package by product name. For idempotent installs, please install individual filesets") fields = line.split(":") @new_resource.version(fields[2]) end end raise Chef::Exceptions::Package, "package source #{@new_resource.source} does not provide package #{@new_resource.package_name}" unless @new_resource.version end end Chef::Log.debug("#{@new_resource} checking install state") ret = shell_out_with_timeout("lslpp -lcq #{@current_resource.package_name}") ret.stdout.each_line do |line| case line when /#{@current_resource.package_name}/ fields = line.split(":") Chef::Log.debug("#{@new_resource} version #{fields[2]} is already installed") @current_resource.version(fields[2]) end end unless ret.exitstatus == 0 || ret.exitstatus == 1 raise Chef::Exceptions::Package, "lslpp failed - #{ret.format_for_exception}!" end @current_resource end def candidate_version return @candidate_version if @candidate_version ret = shell_out_with_timeout("installp -L -d #{@new_resource.source}") ret.stdout.each_line do |line| case line when /\w:#{Regexp.escape(@new_resource.package_name)}:(.*)/ fields = line.split(":") @candidate_version = fields[2] @new_resource.version(fields[2]) Chef::Log.debug("#{@new_resource} setting install candidate version to #{@candidate_version}") end end unless ret.exitstatus == 0 raise Chef::Exceptions::Package, "installp -L -d #{@new_resource.source} - #{ret.format_for_exception}!" end @candidate_version end # # The install/update action needs to be tested with various kinds of packages # on AIX viz. packages with or without licensing file dependencies, packages # with dependencies on other packages which will help to test additional # options of installp. # So far, the code has been tested only with standalone packages. # def install_package(name, version) Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}") if @new_resource.options.nil? shell_out_with_timeout!( "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" ) Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") else shell_out_with_timeout!( "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" ) Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") end end alias_method :upgrade_package, :install_package def remove_package(name, version) if @new_resource.options.nil? shell_out_with_timeout!( "installp -u #{name}" ) Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}") else shell_out_with_timeout!( "installp -u #{expand_options(@new_resource.options)} #{name}" ) Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}") end end end end end end chef-12.14.60/lib/chef/provider/package/apt.rb000066400000000000000000000145031276456504500207040ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "chef/resource/apt_package" class Chef class Provider class Package class Apt < Chef::Provider::Package use_multipackage_api provides :package, platform_family: "debian" provides :apt_package, os: "linux" def initialize(new_resource, run_context) super end def load_current_resource @current_resource = Chef::Resource::AptPackage.new(new_resource.name) current_resource.package_name(new_resource.package_name) current_resource.version(get_current_versions) current_resource end def define_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { !new_resource.source } a.failure_message(Chef::Exceptions::Package, "apt package provider cannot handle source attribute. Use dpkg provider instead") end end def package_data @package_data ||= Hash.new do |hash, key| hash[key] = package_data_for(key) end end def get_current_versions package_name_array.map do |package_name| package_data[package_name][:current_version] end end def get_candidate_versions package_name_array.map do |package_name| package_data[package_name][:candidate_version] end end def candidate_version @candidate_version ||= get_candidate_versions end def install_package(name, version) package_name = name.zip(version).map do |n, v| package_data[n][:virtual] ? n : "#{n}=#{v}" end run_noninteractive("apt-get -q -y", default_release_options, new_resource.options, "install", package_name) end def upgrade_package(name, version) install_package(name, version) end def remove_package(name, version) package_name = name.map do |n| package_data[n][:virtual] ? resolve_virtual_package_name(n) : n end run_noninteractive("apt-get -q -y", new_resource.options, "remove", package_name) end def purge_package(name, version) package_name = name.map do |n| package_data[n][:virtual] ? resolve_virtual_package_name(n) : n end run_noninteractive("apt-get -q -y", new_resource.options, "purge", package_name) end def preseed_package(preseed_file) Chef::Log.info("#{new_resource} pre-seeding package installation instructions") run_noninteractive("debconf-set-selections", preseed_file) end def reconfig_package(name, version) Chef::Log.info("#{new_resource} reconfiguring") run_noninteractive("dpkg-reconfigure", name) end private # Runs command via shell_out with magic environment to disable # interactive prompts. Command is run with default localization rather # than forcing locale to "C", so command output may not be stable. def run_noninteractive(*args) shell_out_with_timeout!(a_to_s(*args), :env => { "DEBIAN_FRONTEND" => "noninteractive" }) end def default_release_options # Use apt::Default-Release option only if provider supports it "-o APT::Default-Release=#{new_resource.default_release}" if new_resource.respond_to?(:default_release) && new_resource.default_release end def resolve_package_versions(pkg) current_version = nil candidate_version = nil run_noninteractive("apt-cache", default_release_options, "policy", pkg).stdout.each_line do |line| case line when /^\s{2}Installed: (.+)$/ current_version = ( $1 != "(none)" ) ? $1 : nil Chef::Log.debug("#{new_resource} installed version for #{pkg} is #{$1}") when /^\s{2}Candidate: (.+)$/ candidate_version = ( $1 != "(none)" ) ? $1 : nil Chef::Log.debug("#{new_resource} candidate version for #{pkg} is #{$1}") end end [ current_version, candidate_version ] end def resolve_virtual_package_name(pkg) showpkg = run_noninteractive("apt-cache showpkg", pkg).stdout partitions = showpkg.rpartition(/Reverse Provides: ?#{$/}/) return nil if partitions[0] == "" && partitions[1] == "" # not found in output set = partitions[2].lines.each_with_object(Set.new) do |line, acc| # there may be multiple reverse provides for a single package acc.add(line.split[0]) end if set.size > 1 raise Chef::Exceptions::Package, "#{new_resource.package_name} is a virtual package provided by multiple packages, you must explicitly select one" end return set.to_a.first end def package_data_for(pkg) virtual = false current_version = nil candidate_version = nil current_version, candidate_version = resolve_package_versions(pkg) if candidate_version.nil? newpkg = resolve_virtual_package_name(pkg) if newpkg virtual = true Chef::Log.info("#{new_resource} is a virtual package, actually acting on package[#{newpkg}]") current_version, candidate_version = resolve_package_versions(newpkg) end end return { current_version: current_version, candidate_version: candidate_version, virtual: virtual, } end end end end end chef-12.14.60/lib/chef/provider/package/chocolatey.rb000066400000000000000000000265771276456504500222700ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/package" require "chef/resource/chocolatey_package" require "chef/mixin/powershell_out" class Chef class Provider class Package class Chocolatey < Chef::Provider::Package include Chef::Mixin::PowershellOut provides :chocolatey_package, os: "windows" # Declare that our arguments should be arrays use_multipackage_api PATHFINDING_POWERSHELL_COMMAND = "[System.Environment]::GetEnvironmentVariable('ChocolateyInstall', 'MACHINE')" CHOCO_MISSING_MSG = <<-EOS Could not locate your Chocolatey install. To install chocolatey, we recommend the 'chocolatey' cookbook (https://github.com/chocolatey/chocolatey-cookbook). If Chocolatey is installed, ensure that the 'ChocolateyInstall' environment variable is correctly set. You can verify this with the PowerShell command '#{PATHFINDING_POWERSHELL_COMMAND}'. EOS # Responsible for building the current_resource. # # @return [Chef::Resource::ChocolateyPackage] the current_resource def load_current_resource @current_resource = Chef::Resource::ChocolateyPackage.new(new_resource.name) current_resource.package_name(new_resource.package_name) current_resource.version(build_current_versions) current_resource end def define_resource_requirements super # The check that Chocolatey is installed is in #choco_exe. # Chocolatey source attribute points to an alternate feed # and not a package specific alternate source like other providers # so we want to assert candidates exist for the alternate source requirements.assert(:upgrade, :install) do |a| a.assertion { candidates_exist_for_all_uninstalled? } a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(", ")}") a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(", ")} would have been configured") end end # Lazy initializer for candidate_version. A nil value means that there is no candidate # version and the package is not installable (generally an error). # # @return [Array] list of candidate_versions indexed same as new_resource.package_name/version def candidate_version @candidate_version ||= build_candidate_versions end # Install multiple packages via choco.exe # # @param names [Array] array of package names to install # @param versions [Array] array of versions to install def install_package(names, versions) name_versions_to_install = desired_name_versions.select { |n, v| lowercase_names(names).include?(n) } name_nil_versions = name_versions_to_install.select { |n, v| v.nil? } name_has_versions = name_versions_to_install.reject { |n, v| v.nil? } # choco does not support installing multiple packages with version pins name_has_versions.each do |name, version| choco_command("install -y -version", version, cmd_args, name) end # but we can do all the ones without version pins at once unless name_nil_versions.empty? cmd_names = name_nil_versions.keys choco_command("install -y", cmd_args, *cmd_names) end end # Upgrade multiple packages via choco.exe # # @param names [Array] array of package names to install # @param versions [Array] array of versions to install def upgrade_package(names, versions) name_versions_to_install = desired_name_versions.select { |n, v| lowercase_names(names).include?(n) } name_nil_versions = name_versions_to_install.select { |n, v| v.nil? } name_has_versions = name_versions_to_install.reject { |n, v| v.nil? } # choco does not support installing multiple packages with version pins name_has_versions.each do |name, version| choco_command("upgrade -y -version", version, cmd_args, name) end # but we can do all the ones without version pins at once unless name_nil_versions.empty? cmd_names = name_nil_versions.keys choco_command("upgrade -y", cmd_args, *cmd_names) end end # Remove multiple packages via choco.exe # # @param names [Array] array of package names to install # @param versions [Array] array of versions to install def remove_package(names, versions) choco_command("uninstall -y", cmd_args(include_source: false), *names) end # Support :uninstall as an action in order for users to easily convert # from the `chocolatey` provider in the cookbook. It is, however, # already deprecated. def action_uninstall Chef::Log.deprecation "The use of action :uninstall on the chocolatey_package provider is deprecated, please use :remove" action_remove end # Choco does not have dpkg's distinction between purge and remove alias_method :purge_package, :remove_package # Override the superclass check. The semantics for our new_resource.source is not files to # install from, but like the rubygem provider's sources which are more like repos. def check_resource_semantics! end private # Magic to find where chocolatey is installed in the system, and to # return the full path of choco.exe # # @return [String] full path of choco.exe def choco_exe @choco_exe ||= begin # if this check is in #define_resource_requirements, it won't get # run before choco.exe gets called from #load_current_resource. exe_path = ::File.join(choco_install_path.to_s, "bin", "choco.exe") raise Chef::Exceptions::MissingLibrary, CHOCO_MISSING_MSG unless ::File.exist?(exe_path) exe_path end end # lets us mock out an incorrect value for testing. def choco_install_path @choco_install_path ||= powershell_out!( PATHFINDING_POWERSHELL_COMMAND ).stdout.chomp end # Helper to dispatch a choco command through shell_out using the timeout # set on the new resource, with nice command formatting. # # @param args [String] variable number of string arguments # @return [Mixlib::ShellOut] object returned from shell_out! def choco_command(*args) shell_out_with_timeout!(args_to_string(choco_exe, *args)) end # Use the available_packages Hash helper to create an array suitable for # using in candidate_version # # @return [Array] list of candidate_version, same index as new_resource.package_name/version def build_candidate_versions new_resource.package_name.map do |package_name| available_packages[package_name.downcase] end end # Use the installed_packages Hash helper to create an array suitable for # using in current_resource.version # # @return [Array] list of candidate_version, same index as new_resource.package_name/version def build_current_versions new_resource.package_name.map do |package_name| installed_packages[package_name.downcase] end end # Helper to construct Hash of names-to-versions, requested on the new_resource. # If new_resource.version is nil, then all values will be nil. # # @return [Hash] Mapping of requested names to versions def desired_name_versions desired_versions = new_resource.version || new_resource.package_name.map { nil } Hash[*lowercase_names(new_resource.package_name).zip(desired_versions).flatten] end # Helper to construct optional args out of new_resource # # @param include_source [Boolean] should the source parameter be added # @return [String] options from new_resource or empty string def cmd_args(include_source: true) cmd_args = [ new_resource.options ] cmd_args.push( "-source #{new_resource.source}" ) if new_resource.source && include_source args_to_string(*cmd_args) end # Helper to nicely convert variable string args into a single command line. It # will compact nulls or empty strings and join arguments with single spaces, without # introducing any double-spaces for missing args. # # @param args [String] variable number of string arguments # @return [String] nicely concatenated string or empty string def args_to_string(*args) args.reject { |i| i.nil? || i == "" }.join(" ") end # Available packages in chocolatey as a Hash of names mapped to versions # If pinning a package to a specific version, filter out all non matching versions # (names are downcased for case-insensitive matching) # # @return [Hash] name-to-version mapping of available packages def available_packages @available_packages ||= begin cmd = [ "list -r #{package_name_array.join ' '}" ] cmd.push( "-source #{new_resource.source}" ) if new_resource.source raw = parse_list_output(*cmd) raw.keys.each_with_object({}) do |name, available| available[name] = desired_name_versions[name] || raw[name] end end end # Installed packages in chocolatey as a Hash of names mapped to versions # (names are downcased for case-insensitive matching) # # @return [Hash] name-to-version mapping of installed packages def installed_packages @installed_packages ||= Hash[*parse_list_output("list -l -r").flatten] end # Helper to convert choco.exe list output to a Hash # (names are downcased for case-insenstive matching) # # @param cmd [String] command to run # @return [Hash] list output converted to ruby Hash def parse_list_output(*args) hash = {} choco_command(*args).stdout.each_line do |line| next if line.start_with?("Chocolatey v") name, version = line.split("|") hash[name.downcase] = version.chomp end hash end # Helper to downcase all names in an array # # @param names [Array] original mixed case names # @return [Array] same names in lower case def lowercase_names(names) names.map { |name| name.downcase } end end end end end chef-12.14.60/lib/chef/provider/package/dpkg.rb000066400000000000000000000201261276456504500210430ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Copyright:: Copyright 2009-2016, Bryan McLellan # 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" require "chef/resource/package" class Chef class Provider class Package class Dpkg < Chef::Provider::Package DPKG_REMOVED = /^Status: deinstall ok config-files/ DPKG_INSTALLED = /^Status: install ok installed/ DPKG_VERSION = /^Version: (.+)$/ provides :dpkg_package, os: "linux" use_multipackage_api use_package_name_for_source def define_resource_requirements super requirements.assert(:install, :upgrade) do |a| a.assertion { !resolved_source_array.compact.empty? } a.failure_message Chef::Exceptions::Package, "#{new_resource} the source property is required for action :install or :upgrade" end requirements.assert(:install, :upgrade) do |a| a.assertion { source_files_exist? } a.failure_message Chef::Exceptions::Package, "#{new_resource} source file(s) do not exist: #{missing_sources}" a.whyrun "Assuming they would have been previously created." end end def load_current_resource @current_resource = Chef::Resource::Package.new(new_resource.name) current_resource.package_name(new_resource.package_name) if source_files_exist? @candidate_version = get_candidate_version current_resource.package_name(get_package_name) # if the source file exists then our package_name is right current_resource.version(get_current_version_from(current_package_name_array)) elsif !installing? # we can't do this if we're installing with no source, because our package_name # is probably not right. # # if we're removing or purging we don't use source, and our package_name must # be right so we can do this. # # we don't error here on the dpkg command since we'll handle the exception or # the why-run message in define_resource_requirements. current_resource.version(get_current_version_from(current_package_name_array)) end current_resource end def install_package(name, version) sources = name.map { |n| name_sources[n] } Chef::Log.info("#{new_resource} installing package(s): #{name.join(' ')}") run_noninteractive("dpkg -i", new_resource.options, *sources) end def remove_package(name, version) Chef::Log.info("#{new_resource} removing package(s): #{name.join(' ')}") run_noninteractive("dpkg -r", new_resource.options, *name) end def purge_package(name, version) Chef::Log.info("#{new_resource} purging packages(s): #{name.join(' ')}") run_noninteractive("dpkg -P", new_resource.options, *name) end def upgrade_package(name, version) install_package(name, version) end def preseed_package(preseed_file) Chef::Log.info("#{new_resource} pre-seeding package installation instructions") run_noninteractive("debconf-set-selections", *preseed_file) end def reconfig_package(name, version) Chef::Log.info("#{new_resource} reconfiguring") run_noninteractive("dpkg-reconfigure", *name) end # Override the superclass check. Multiple sources are required here. def check_resource_semantics! end private def read_current_version_of_package(package_name) Chef::Log.debug("#{new_resource} checking install state of #{package_name}") status = shell_out_with_timeout!("dpkg -s #{package_name}", returns: [0, 1]) package_installed = false status.stdout.each_line do |line| case line when DPKG_REMOVED # if we are 'purging' then we consider 'removed' to be 'installed' package_installed = true if action == :purge when DPKG_INSTALLED package_installed = true when DPKG_VERSION if package_installed Chef::Log.debug("#{new_resource} current version is #{$1}") return $1 end end end return nil end def get_current_version_from(array) array.map do |name| read_current_version_of_package(name) end end # Runs command via shell_out_with_timeout with magic environment to disable # interactive prompts. def run_noninteractive(*command) shell_out_with_timeout!(a_to_s(*command), :env => { "DEBIAN_FRONTEND" => "noninteractive" }) end # Returns true if all sources exist. Returns false if any do not, or if no # sources were specified. # # @return [Boolean] True if all sources exist def source_files_exist? resolved_source_array.all? { |s| s && ::File.exist?(s) } end # Helper to return all the nanes of the missing sources for error messages. # # @return [Array] Array of missing sources def missing_sources resolved_source_array.select { |s| s.nil? || !::File.exist?(s) } end def current_package_name_array [ current_resource.package_name ].flatten end # Helper to construct Hash of names-to-sources. # # @return [Hash] Mapping of package names to sources def name_sources @name_sources = begin Hash[*package_name_array.zip(resolved_source_array).flatten] end end # Helper to construct Hash of names-to-package-information. # # @return [Hash] Mapping of package names to package information def name_pkginfo @name_pkginfo ||= begin pkginfos = resolved_source_array.map do |src| Chef::Log.debug("#{new_resource} checking #{src} dpkg status") status = shell_out_with_timeout!("dpkg-deb -W #{src}") status.stdout end Hash[*package_name_array.zip(pkginfos).flatten] end end def name_candidate_version @name_candidate_version ||= begin Hash[name_pkginfo.map { |k, v| [k, v ? v.split("\t")[1].strip : nil] }] end end def name_package_name @name_package_name ||= begin Hash[name_pkginfo.map { |k, v| [k, v ? v.split("\t")[0] : nil] }] end end # Return candidate version array from pkg-deb -W against the source file(s). # # @return [Array] Array of candidate versions read from the source files def get_candidate_version package_name_array.map { |name| name_candidate_version[name] } end # Return package names from the candidate source file(s). # # @return [Array] Array of actual package names read from the source files def get_package_name package_name_array.map { |name| name_package_name[name] } end # Since upgrade just calls install, this is a helper to determine # if our action means that we'll be calling install_package. # # @return [Boolean] true if we're doing :install or :upgrade def installing? [:install, :upgrade].include?(action) end end end end end chef-12.14.60/lib/chef/provider/package/easy_install.rb000066400000000000000000000111731276456504500226070ustar00rootroot00000000000000# # Author:: Joe Williams () # Copyright:: Copyright 2009-2016, Joe Williams # 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" require "chef/mixin/command" require "chef/resource/package" class Chef class Provider class Package class EasyInstall < Chef::Provider::Package provides :easy_install_package def install_check(name) check = false begin # first check to see if we can import it output = shell_out_with_timeout!("#{python_binary_path} -c \"import #{name}\"", :returns => [0, 1]).stderr if output.include? "ImportError" # then check to see if its on the path output = shell_out_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns => [0, 1]).stdout if output.downcase.include? "#{name.downcase}" check = true end else check = true end rescue # it's probably not installed end check end def easy_install_binary_path path = @new_resource.easy_install_binary path ? path : "easy_install" end def python_binary_path path = @new_resource.python_binary path ? path : "python" end def module_name m = @new_resource.module_name m ? m : @new_resource.name end def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) # get the currently installed version if installed package_version = nil if install_check(module_name) begin output = shell_out_with_timeout!("#{python_binary_path} -c \"import #{module_name}; print #{module_name}.__version__\"").stdout package_version = output.strip rescue output = shell_out_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns => [0, 1]).stdout output_array = output.gsub(/[\[\]]/, "").split(/\s*,\s*/) package_path = "" output_array.each do |entry| if entry.downcase.include?(@new_resource.package_name) package_path = entry end end package_path[/\S\S(.*)\/(.*)-(.*)-py(.*).egg\S/] package_version = $3 end end if package_version == @new_resource.version Chef::Log.debug("#{@new_resource} at version #{@new_resource.version}") @current_resource.version(@new_resource.version) else Chef::Log.debug("#{@new_resource} at version #{package_version}") @current_resource.version(package_version) end @current_resource end def candidate_version return @candidate_version if @candidate_version # do a dry run to get the latest version result = shell_out_with_timeout!("#{easy_install_binary_path} -n #{@new_resource.package_name}", :returns => [0, 1]) @candidate_version = result.stdout[/(.*)Best match: (.*) (.*)$/, 3] @candidate_version end def install_package(name, version) Chef.log_deprecation("The easy_install package provider is deprecated and will be removed in Chef 13.") run_command(:command => "#{easy_install_binary_path}#{expand_options(@new_resource.options)} \"#{name}==#{version}\"") end def upgrade_package(name, version) install_package(name, version) end def remove_package(name, version) Chef.log_deprecation("The easy_install package provider is deprecated and will be removed in Chef 13.") run_command(:command => "#{easy_install_binary_path }#{expand_options(@new_resource.options)} -m #{name}") end def purge_package(name, version) remove_package(name, version) end end end end end chef-12.14.60/lib/chef/provider/package/freebsd/000077500000000000000000000000001276456504500212025ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/package/freebsd/base.rb000066400000000000000000000061451276456504500224470ustar00rootroot00000000000000# # Authors:: Bryan McLellan (btm@loftninjas.org) # Matthew Landauer (matthew@openaustralia.org) # Richard Manyanza (liseki@nyikacraftsmen.com) # Copyright:: Copyright 2009-2016, Bryan McLellan, Matthew Landauer # Copyright:: Copyright 2014-2016, Richard Manyanza # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package" require "chef/mixin/get_source_from_package" class Chef class Provider class Package module Freebsd module PortsHelper def supports_ports? ::File.exist?("/usr/ports/Makefile") end def port_dir(port) case port # When the package name starts with a '/' treat it as the full path to the ports directory. when /^\// port # Otherwise if the package name contains a '/' not at the start (like 'www/wordpress') treat # as a relative path from /usr/ports. when /\// "/usr/ports/#{port}" # Otherwise look up the path to the ports directory using 'whereis' else whereis = shell_out_with_timeout!("whereis -s #{port}", :env => nil) unless path = whereis.stdout[/^#{Regexp.escape(port)}:\s+(.+)$/, 1] raise Chef::Exceptions::Package, "Could not find port with the name #{port}" end path end end def makefile_variable_value(variable, dir = nil) options = dir ? { :cwd => dir } : {} make_v = shell_out_with_timeout!("make -V #{variable}", options.merge!(:env => nil, :returns => [0, 1])) make_v.exitstatus.zero? ? make_v.stdout.strip.split($\).first : nil # $\ is the line separator, i.e. newline. end end class Base < Chef::Provider::Package include Chef::Mixin::GetSourceFromPackage def initialize(*args) super @current_resource = Chef::Resource::Package.new(@new_resource.name) end def load_current_resource @current_resource.package_name(@new_resource.package_name) @current_resource.version(current_installed_version) Chef::Log.debug("#{@new_resource} current version is #{@current_resource.version}") if @current_resource.version @candidate_version = candidate_version Chef::Log.debug("#{@new_resource} candidate version is #{@candidate_version}") if @candidate_version @current_resource end end end end end end chef-12.14.60/lib/chef/provider/package/freebsd/pkg.rb000066400000000000000000000100101276456504500223000ustar00rootroot00000000000000# # Authors:: Bryan McLellan (btm@loftninjas.org) # Matthew Landauer (matthew@openaustralia.org) # Richard Manyanza (liseki@nyikacraftsmen.com) # Copyright:: Copyright 2009-2016, Bryan McLellan, Matthew Landauer # Copyright:: Copyright 2014-2016, Richard Manyanza # 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/freebsd/base" require "chef/util/path_helper" class Chef class Provider class Package module Freebsd class Pkg < Base include PortsHelper def install_package(name, version) unless @current_resource.version case @new_resource.source when /^http/, /^ftp/ if @new_resource.source =~ /\/$/ shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, "LC_ALL" => nil }).status else shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGEROOT" => @new_resource.source, "LC_ALL" => nil }).status end Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}") when /^\// shell_out_with_timeout!("pkg_add #{file_candidate_version_path}", :env => { "PKG_PATH" => @new_resource.source , "LC_ALL" => nil }).status Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}") else shell_out_with_timeout!("pkg_add -r #{latest_link_name}", :env => nil).status end end end def remove_package(name, version) shell_out_with_timeout!("pkg_delete #{package_name}-#{version || @current_resource.version}", :env => nil).status end # The name of the package (without the version number) as understood by pkg_add and pkg_info. def package_name if supports_ports? if makefile_variable_value("PKGNAME", port_path) =~ /^(.+)-[^-]+$/ $1 else raise Chef::Exceptions::Package, "Unexpected form for PKGNAME variable in #{port_path}/Makefile" end else @new_resource.package_name end end def latest_link_name makefile_variable_value("LATEST_LINK", port_path) end def current_installed_version pkg_info = shell_out_with_timeout!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0, 1]) pkg_info.stdout[/^#{Regexp.escape(package_name)}-(.+)/, 1] end def candidate_version case @new_resource.source when /^http/, /^ftp/ repo_candidate_version when /^\// file_candidate_version else ports_candidate_version end end def file_candidate_version_path Dir[Chef::Util::PathHelper.escape_glob_dir("#{@new_resource.source}/#{@current_resource.package_name}") + "*"][-1].to_s end def file_candidate_version file_candidate_version_path.split(/-/).last.split(/.tbz/).first end def repo_candidate_version "0.0.0" end def ports_candidate_version makefile_variable_value("PORTVERSION", port_path) end def port_path port_dir @new_resource.package_name end end end end end end chef-12.14.60/lib/chef/provider/package/freebsd/pkgng.rb000066400000000000000000000054261276456504500226440ustar00rootroot00000000000000# # Authors:: Richard Manyanza (liseki@nyikacraftsmen.com) # Copyright:: Copyright 2014-2016, Richard Manyanza # 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/freebsd/base" class Chef class Provider class Package module Freebsd class Pkgng < Base def install_package(name, version) unless @current_resource.version case @new_resource.source when /^(http|ftp|\/)/ shell_out_with_timeout!("pkg add#{expand_options(@new_resource.options)} #{@new_resource.source}", :env => { "LC_ALL" => nil }).status Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}") else shell_out_with_timeout!("pkg install -y#{expand_options(@new_resource.options)} #{name}", :env => { "LC_ALL" => nil }).status end end end def remove_package(name, version) options = @new_resource.options && @new_resource.options.sub(repo_regex, "") options && !options.empty? || options = nil shell_out_with_timeout!("pkg delete -y#{expand_options(options)} #{name}#{version ? '-' + version : ''}", :env => nil).status end def current_installed_version pkg_info = shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0, 70]) pkg_info.stdout[/^Version +: (.+)$/, 1] end def candidate_version @new_resource.source ? file_candidate_version : repo_candidate_version end private def file_candidate_version @new_resource.source[/#{Regexp.escape(@new_resource.package_name)}-(.+)\.txz/, 1] end def repo_candidate_version if @new_resource.options && @new_resource.options.match(repo_regex) options = $1 end pkg_query = shell_out_with_timeout!("pkg rquery#{expand_options(options)} '%v' #{@new_resource.package_name}", :env => nil) pkg_query.exitstatus.zero? ? pkg_query.stdout.strip.split(/\n/).last : nil end def repo_regex /(-r\s?\S+)\b/ end end end end end end chef-12.14.60/lib/chef/provider/package/freebsd/port.rb000066400000000000000000000041021276456504500225100ustar00rootroot00000000000000# # Authors:: Richard Manyanza (liseki@nyikacraftsmen.com) # Copyright:: Copyright 2014-2016, Richard Manyanza # 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/freebsd/base" class Chef class Provider class Package module Freebsd class Port < Base include PortsHelper def install_package(name, version) shell_out_with_timeout!("make -DBATCH install clean", :timeout => 1800, :env => nil, :cwd => port_dir).status end def remove_package(name, version) shell_out_with_timeout!("make deinstall", :timeout => 300, :env => nil, :cwd => port_dir).status end def current_installed_version pkg_info = if @new_resource.supports_pkgng? shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0, 70]) else shell_out_with_timeout!("pkg_info -E \"#{@new_resource.package_name}*\"", :env => nil, :returns => [0, 1]) end pkg_info.stdout[/^#{Regexp.escape(@new_resource.package_name)}-(.+)/, 1] end def candidate_version if supports_ports? makefile_variable_value("PORTVERSION", port_dir) else raise Chef::Exceptions::Package, "Ports collection could not be found" end end def port_dir super(@new_resource.package_name) end end end end end end chef-12.14.60/lib/chef/provider/package/homebrew.rb000066400000000000000000000120311276456504500217220ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Author:: Graeme Mathieson () # # Copyright 2011-2016, Chef Software Inc. # Copyright 2014-2016, Chef Software, Inc # # 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 "etc" require "chef/mixin/homebrew_user" class Chef class Provider class Package class Homebrew < Chef::Provider::Package provides :package, os: "darwin", override: true provides :homebrew_package include Chef::Mixin::HomebrewUser def load_current_resource self.current_resource = Chef::Resource::Package.new(new_resource.name) current_resource.package_name(new_resource.package_name) current_resource.version(current_installed_version) Chef::Log.debug("#{new_resource} current version is #{current_resource.version}") if current_resource.version @candidate_version = candidate_version Chef::Log.debug("#{new_resource} candidate version is #{@candidate_version}") if @candidate_version current_resource end def install_package(name, version) unless current_resource.version == version brew("install", new_resource.options, name) end end def upgrade_package(name, version) current_version = current_resource.version if current_version.nil? || current_version.empty? install_package(name, version) elsif current_version != version brew("upgrade", new_resource.options, name) end end def remove_package(name, version) if current_resource.version brew("uninstall", new_resource.options, name) end end # Homebrew doesn't really have a notion of purging, do a "force remove" def purge_package(name, version) new_resource.options((new_resource.options || "") << " --force").strip remove_package(name, version) end def brew(*args) get_response_from_command("brew #{args.join(' ')}") end # We implement a querying method that returns the JSON-as-Hash # data for a formula per the Homebrew documentation. Previous # implementations of this provider in the homebrew cookbook # performed a bit of magic with the load path to get this # information, but that is not any more robust than using the # command-line interface that returns the same thing. # # https://github.com/Homebrew/homebrew/wiki/Querying-Brew def brew_info @brew_info ||= Chef::JSONCompat.from_json(brew("info", "--json=v1", new_resource.package_name)).first end # Some packages (formula) are "keg only" and aren't linked, # because multiple versions installed can cause conflicts. We # handle this by using the last installed version as the # "current" (as in latest). Otherwise, we will use the version # that brew thinks is linked as the current version. # def current_installed_version if brew_info["keg_only"] if brew_info["installed"].empty? nil else brew_info["installed"].last["version"] end else brew_info["linked_keg"] end end # Packages (formula) available to install should have a # "stable" version, per the Homebrew project's acceptable # formula documentation, so we will rely on that being the # case. Older implementations of this provider in the homebrew # cookbook would fall back to +brew_info['version']+, but the # schema has changed, and homebrew is a constantly rolling # forward project. # # https://github.com/Homebrew/homebrew/wiki/Acceptable-Formulae#stable-versions def candidate_version brew_info["versions"]["stable"] end private def get_response_from_command(command) homebrew_uid = find_homebrew_uid(new_resource.respond_to?(:homebrew_user) && new_resource.homebrew_user) homebrew_user = Etc.getpwuid(homebrew_uid) Chef::Log.debug "Executing '#{command}' as user '#{homebrew_user.name}'" # FIXME: this 1800 second default timeout should be deprecated output = shell_out_with_timeout!(command, :timeout => 1800, :user => homebrew_uid, :environment => { "HOME" => homebrew_user.dir, "RUBYOPT" => nil, "TMPDIR" => nil }) output.stdout.chomp end end end end end chef-12.14.60/lib/chef/provider/package/ips.rb000066400000000000000000000062221276456504500207120ustar00rootroot00000000000000# # Author:: Jason J. W. Williams () # Author:: Stephen Nelson-Smith () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "open3" require "chef/provider/package" require "chef/mixin/command" require "chef/resource/package" class Chef class Provider class Package class Ips < Chef::Provider::Package provides :package, platform: %w{openindiana opensolaris omnios solaris2} provides :ips_package, os: "solaris2" attr_accessor :virtual def define_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { ! @candidate_version.nil? } a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.package_name} not found" a.whyrun "Assuming package #{@new_resource.package_name} would have been made available." end end def get_current_version shell_out_with_timeout("pkg info #{@new_resource.package_name}").stdout.each_line do |line| return $1.split[0] if line =~ /^\s+Version: (.*)/ end return nil end def get_candidate_version shell_out_with_timeout!("pkg info -r #{new_resource.package_name}").stdout.each_line do |line| return $1.split[0] if line =~ /Version: (.*)/ end return nil end def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) Chef::Log.debug("Checking package status for #{@new_resource.name}") @current_resource.version(get_current_version) @candidate_version = get_candidate_version @current_resource end def install_package(name, version) package_name = "#{name}@#{version}" normal_command = "pkg#{expand_options(@new_resource.options)} install -q #{package_name}" command = if @new_resource.respond_to?(:accept_license) && @new_resource.accept_license normal_command.gsub("-q", "-q --accept") else normal_command end shell_out_with_timeout(command) end def upgrade_package(name, version) install_package(name, version) end def remove_package(name, version) package_name = "#{name}@#{version}" shell_out_with_timeout!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" ) end end end end end chef-12.14.60/lib/chef/provider/package/macports.rb000066400000000000000000000067641276456504500217620ustar00rootroot00000000000000class Chef class Provider class Package class Macports < Chef::Provider::Package provides :package, os: "darwin" provides :macports_package def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) @current_resource.version(current_installed_version) Chef::Log.debug("#{@new_resource} current version is #{@current_resource.version}") if @current_resource.version @candidate_version = macports_candidate_version if !@new_resource.version && !@candidate_version raise Chef::Exceptions::Package, "Could not get a candidate version for this package -- #{@new_resource.name} does not seem to be a valid package!" end Chef::Log.debug("#{@new_resource} candidate version is #{@candidate_version}") if @candidate_version @current_resource end def current_installed_version command = "port installed #{@new_resource.package_name}" output = get_response_from_command(command) response = nil output.each_line do |line| match = line.match(/^.+ @([^\s]+) \(active\)$/) response = match[1] if match end response end def macports_candidate_version command = "port info --version #{@new_resource.package_name}" output = get_response_from_command(command) match = output.match(/^version: (.+)$/) match ? match[1] : nil end def install_package(name, version) unless @current_resource.version == version command = "port#{expand_options(@new_resource.options)} install #{name}" command << " @#{version}" if version && !version.empty? shell_out_with_timeout!(command) end end def purge_package(name, version) command = "port#{expand_options(@new_resource.options)} uninstall #{name}" command << " @#{version}" if version && !version.empty? shell_out_with_timeout!(command) end def remove_package(name, version) command = "port#{expand_options(@new_resource.options)} deactivate #{name}" command << " @#{version}" if version && !version.empty? shell_out_with_timeout!(command) end def upgrade_package(name, version) # Saving this to a variable -- weird rSpec behavior # happens otherwise... current_version = @current_resource.version if current_version.nil? || current_version.empty? # Macports doesn't like when you upgrade a package # that hasn't been installed. install_package(name, version) elsif current_version != version shell_out_with_timeout!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" ) end end private def get_response_from_command(command) output = nil status = shell_out_with_timeout(command) begin output = status.stdout rescue Exception raise Chef::Exceptions::Package, "Could not read from STDOUT on command: #{command}" end unless status.exitstatus == 0 || status.exitstatus == 1 raise Chef::Exceptions::Package, "#{command} failed - #{status.insect}!" end output end end end end end chef-12.14.60/lib/chef/provider/package/openbsd.rb000066400000000000000000000117071276456504500215550ustar00rootroot00000000000000# # Authors:: Bryan McLellan (btm@loftninjas.org) # Matthew Landauer (matthew@openaustralia.org) # Richard Manyanza (liseki@nyikacraftsmen.com) # Scott Bonds (scott@ggr.com) # Copyright:: Copyright 2009-2016, Bryan McLellan, Matthew Landauer # Copyright:: Copyright 2014-2016, Richard Manyanza, Scott Bonds # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package" require "chef/mixin/get_source_from_package" require "chef/exceptions" class Chef class Provider class Package class Openbsd < Chef::Provider::Package provides :package, os: "openbsd" provides :openbsd_package include Chef::Mixin::ShellOut include Chef::Mixin::GetSourceFromPackage def initialize(*args) super @current_resource = Chef::Resource::Package.new(new_resource.name) end def load_current_resource @current_resource.package_name(new_resource.package_name) @current_resource.version(installed_version) @current_resource end def define_resource_requirements super # Below are incomplete/missing features for this package provider requirements.assert(:all_actions) do |a| a.assertion { !new_resource.source } a.failure_message(Chef::Exceptions::Package, "The openbsd package provider does not support the source attribute") end requirements.assert(:all_actions) do |a| a.assertion do if new_resource.package_name =~ /^(.+?)--(.+)/ !new_resource.version else true end end a.failure_message(Chef::Exceptions::Package, "The openbsd package provider does not support providing a version and flavor") end end def install_package(name, version) unless @current_resource.version if parts = name.match(/^(.+?)--(.+)/) # use double-dash for stems with flavors, see man page for pkg_add name = parts[1] end shell_out_with_timeout!("pkg_add -r #{name}#{version_string(version)}", :env => { "PKG_PATH" => pkg_path }).status Chef::Log.debug("#{new_resource.package_name} installed") end end def remove_package(name, version) if parts = name.match(/^(.+?)--(.+)/) name = parts[1] end shell_out_with_timeout!("pkg_delete #{name}#{version_string(version)}", :env => nil).status end private def installed_version if parts = new_resource.package_name.match(/^(.+?)--(.+)/) name = parts[1] else name = new_resource.package_name end pkg_info = shell_out_with_timeout!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0, 1]) result = pkg_info.stdout[/^inst:#{Regexp.escape(name)}-(.+?)\s/, 1] Chef::Log.debug("installed_version of '#{new_resource.package_name}' is '#{result}'") result end def candidate_version @candidate_version ||= begin results = [] shell_out_with_timeout!("pkg_info -I \"#{new_resource.package_name}#{version_string(new_resource.version)}\"", :env => nil, :returns => [0, 1]).stdout.each_line do |line| if parts = new_resource.package_name.match(/^(.+?)--(.+)/) results << line[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1] else results << line[/^#{Regexp.escape(new_resource.package_name)}-(.+?)\s/, 1] end end results = results.reject(&:nil?) Chef::Log.debug("Candidate versions of '#{new_resource.package_name}' are '#{results}'") case results.length when 0 [] when 1 results[0] else raise Chef::Exceptions::Package, "#{new_resource.name} has multiple matching candidates. Please use a more specific name" if results.length > 1 end end end def version_string(version) ver = "" ver += "-#{version}" if version end def pkg_path ENV["PKG_PATH"] || "http://ftp.OpenBSD.org/pub/#{node["kernel"]["name"]}/#{node["kernel"]["release"]}/packages/#{node["kernel"]["machine"]}/" end end end end end chef-12.14.60/lib/chef/provider/package/pacman.rb000066400000000000000000000067511276456504500213650ustar00rootroot00000000000000# # Author:: Jan Zimmek () # Copyright:: Copyright 2010-2016, Jan Zimmek # 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" require "chef/mixin/command" require "chef/resource/package" class Chef class Provider class Package class Pacman < Chef::Provider::Package provides :package, platform: "arch" provides :pacman_package, os: "linux" def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) Chef::Log.debug("#{@new_resource} checking pacman for #{@new_resource.package_name}") status = shell_out_with_timeout("pacman -Qi #{@new_resource.package_name}") status.stdout.each_line do |line| case line when /^Version(\s?)*: (.+)$/ Chef::Log.debug("#{@new_resource} current version is #{$2}") @current_resource.version($2) end end unless status.exitstatus == 0 || status.exitstatus == 1 raise Chef::Exceptions::Package, "pacman failed - #{status.inspect}!" end @current_resource end def candidate_version return @candidate_version if @candidate_version repos = %w{extra core community} if ::File.exists?("/etc/pacman.conf") pacman = ::File.read("/etc/pacman.conf") repos = pacman.scan(/\[(.+)\]/).flatten end package_repos = repos.map { |r| Regexp.escape(r) }.join("|") status = shell_out_with_timeout("pacman -Sl") status.stdout.each_line do |line| case line when /^(#{package_repos}) #{Regexp.escape(@new_resource.package_name)} (.+)$/ # $2 contains a string like "4.4.0-1" or "3.10-4 [installed]" # simply split by space and use first token @candidate_version = $2.split(" ").first end end unless status.exitstatus == 0 || status.exitstatus == 1 raise Chef::Exceptions::Package, "pacman failed - #{status.inspect}!" end unless @candidate_version raise Chef::Exceptions::Package, "pacman does not have a version of package #{@new_resource.package_name}" end @candidate_version end def install_package(name, version) shell_out_with_timeout!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" ) end def upgrade_package(name, version) install_package(name, version) end def remove_package(name, version) shell_out_with_timeout!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" ) end def purge_package(name, version) remove_package(name, version) end end end end end chef-12.14.60/lib/chef/provider/package/paludis.rb000066400000000000000000000053141276456504500215610ustar00rootroot00000000000000# # Author:: Vasiliy Tolstov () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/package" require "chef/resource/package" class Chef class Provider class Package class Paludis < Chef::Provider::Package provides :package, platform: "exherbo" provides :paludis_package, os: "linux" def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.package_name) @current_resource.package_name(@new_resource.package_name) Chef::Log.debug("Checking package status for #{@new_resource.package_name}") installed = false re = Regexp.new("(.*)[[:blank:]](.*)[[:blank:]](.*)$") shell_out!("cave -L warning print-ids -M none -m \"#{@new_resource.package_name}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line| res = re.match(line) unless res.nil? case res[3] when "accounts", "installed-accounts" next when "installed" installed = true @current_resource.version(res[2]) else @candidate_version = res[2] end end end @current_resource end def install_package(name, version) if version pkg = "=#{name}-#{version}" else pkg = "#{@new_resource.package_name}" end shell_out!("cave -L warning resolve -x#{expand_options(@new_resource.options)} \"#{pkg}\"", :timeout => @new_resource.timeout) end def upgrade_package(name, version) install_package(name, version) end def remove_package(name, version) if version pkg = "=#{@new_resource.package_name}-#{version}" else pkg = "#{@new_resource.package_name}" end shell_out!("cave -L warning uninstall -x#{expand_options(@new_resource.options)} \"#{pkg}\"") end def purge_package(name, version) remove_package(name, version) end end end end end chef-12.14.60/lib/chef/provider/package/portage.rb000066400000000000000000000116171276456504500215640ustar00rootroot00000000000000# # Author:: Ezra Zygmuntowicz () # 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" require "chef/mixin/command" require "chef/resource/package" require "chef/util/path_helper" class Chef class Provider class Package class Portage < Chef::Provider::Package provides :package, platform: "gentoo" provides :portage_package PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)} def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) category, pkg = %r{^#{PACKAGE_NAME_PATTERN}$}.match(@new_resource.package_name)[1, 2] globsafe_category = category ? Chef::Util::PathHelper.escape_glob_dir(category) : nil 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]*[a-z]?((_(alpha|beta|pre|rc|p)\d*)*)?(-r\d+)?)} [$&, $1] end end.compact if versions.size > 1 atoms = versions.map { |v| v.first }.sort categories = atoms.map { |v| v.split("/")[0] }.uniq if !category && categories.size > 1 raise Chef::Exceptions::Package, "Multiple packages found for #{@new_resource.package_name}: #{atoms.join(" ")}. Specify a category." end elsif versions.size == 1 @current_resource.version(versions.first.last) Chef::Log.debug("#{@new_resource} current version #{$1}") end @current_resource end def parse_emerge(package, txt) availables = {} found_package_name = nil txt.each_line do |line| if line =~ /\*\s+#{PACKAGE_NAME_PATTERN}/ found_package_name = $&.delete("*").strip if package =~ /\// #the category is specified if found_package_name == package availables[found_package_name] = nil end else #the category is not specified if found_package_name.split("/").last == package availables[found_package_name] = nil end end end if line =~ /Latest version available: (.*)/ && availables.has_key?(found_package_name) availables[found_package_name] = $1.strip end end if availables.size > 1 # shouldn't happen if a category is specified so just use `package` raise Chef::Exceptions::Package, "Multiple emerge results found for #{package}: #{availables.keys.join(" ")}. Specify a category." end availables.values.first end def candidate_version return @candidate_version if @candidate_version status = shell_out("emerge --color n --nospinner --search #{@new_resource.package_name.split('/').last}") available, installed = parse_emerge(@new_resource.package_name, status.stdout) @candidate_version = available unless status.exitstatus == 0 raise Chef::Exceptions::Package, "emerge --search failed - #{status.inspect}!" end @candidate_version end def install_package(name, version) pkg = "=#{name}-#{version}" if version =~ /^\~(.+)/ # If we start with a tilde pkg = "~#{name}-#{$1}" end shell_out!( "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" ) end def upgrade_package(name, version) install_package(name, version) end def remove_package(name, version) if version pkg = "=#{@new_resource.package_name}-#{version}" else pkg = "#{@new_resource.package_name}" end shell_out!( "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" ) end def purge_package(name, version) remove_package(name, version) end end end end end chef-12.14.60/lib/chef/provider/package/rpm.rb000066400000000000000000000107261276456504500207210ustar00rootroot00000000000000# # Author:: Joshua Timberman () # 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" require "chef/mixin/command" require "chef/resource/package" require "chef/mixin/get_source_from_package" class Chef class Provider class Package class Rpm < Chef::Provider::Package provides :rpm_package, os: %w{linux aix} include Chef::Mixin::GetSourceFromPackage def define_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { @package_source_exists } a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}" a.whyrun "Assuming package #{@new_resource.name} would have been made available." end requirements.assert(:all_actions) do |a| a.assertion { !@rpm_status.nil? && (@rpm_status.exitstatus == 0 || @rpm_status.exitstatus == 1) } a.failure_message Chef::Exceptions::Package, "Unable to determine current version due to RPM failure. Detail: #{@rpm_status.inspect}" a.whyrun "Assuming current version would have been determined for package#{@new_resource.name}." end end def load_current_resource @package_source_provided = true @package_source_exists = true @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) if @new_resource.source unless uri_scheme?(@new_resource.source) || ::File.exists?(@new_resource.source) @package_source_exists = false return end Chef::Log.debug("#{@new_resource} checking rpm status") shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line| case line when /^(\S+)\s(\S+)$/ @current_resource.package_name($1) @new_resource.version($2) @candidate_version = $2 end end else if Array(@new_resource.action).include?(:install) @package_source_exists = false return end end Chef::Log.debug("#{@new_resource} checking install state") @rpm_status = shell_out_with_timeout("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}") @rpm_status.stdout.each_line do |line| case line when /^(\S+)\s(\S+)$/ Chef::Log.debug("#{@new_resource} current version is #{$2}") @current_resource.version($2) end end @current_resource end def install_package(name, version) unless @current_resource.version shell_out_with_timeout!( "rpm #{@new_resource.options} -i #{@new_resource.source}" ) else if allow_downgrade shell_out_with_timeout!( "rpm #{@new_resource.options} -U --oldpackage #{@new_resource.source}" ) else shell_out_with_timeout!( "rpm #{@new_resource.options} -U #{@new_resource.source}" ) end end end alias_method :upgrade_package, :install_package def remove_package(name, version) if version shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}-#{version}" ) else shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}" ) end end private def uri_scheme?(str) scheme = URI.split(str).first return false unless scheme %w{http https ftp file}.include?(scheme.downcase) rescue URI::InvalidURIError return false end end end end end chef-12.14.60/lib/chef/provider/package/rubygems.rb000066400000000000000000000553421276456504500217630ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, 2010-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/provider/package" require "chef/mixin/command" require "chef/resource/package" require "chef/mixin/get_source_from_package" # Class methods on Gem are defined in rubygems require "rubygems" # Ruby 1.9's gem_prelude can interact poorly with loading the full rubygems # explicitly like this. Make sure rubygems/specification is always last in this # list require "rubygems/version" require "rubygems/dependency" require "rubygems/spec_fetcher" require "rubygems/platform" require "rubygems/package" require "rubygems/dependency_installer" require "rubygems/uninstaller" require "rubygems/specification" class Chef class Provider class Package class Rubygems < Chef::Provider::Package class GemEnvironment # HACK: trigger gem config load early. Otherwise it can get lazy # loaded during operations where we've set Gem.sources to an # alternate value and overwrite it with the defaults. Gem.configuration DEFAULT_UNINSTALLER_OPTS = { :ignore => true, :executables => true } ## # The paths where rubygems should search for installed gems. # Implemented by subclasses. def gem_paths raise NotImplementedError end ## # A rubygems source index containing the list of gemspecs for all # available gems in the gem installation. # Implemented by subclasses # === Returns # Gem::SourceIndex def gem_source_index raise NotImplementedError end ## # A rubygems specification object containing the list of gemspecs for all # available gems in the gem installation. # Implemented by subclasses # For rubygems >= 1.8.0 # === Returns # Gem::Specification def gem_specification raise NotImplementedError end ## # Lists the installed versions of +gem_name+, constrained by the # version spec in +gem_dep+ # === Arguments # Gem::Dependency +gem_dep+ is a Gem::Dependency object, its version # specification constrains which gems are returned. # === Returns # [Gem::Specification] an array of Gem::Specification objects def installed_versions(gem_dep) if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.0") gem_specification.find_all_by_name(gem_dep.name, gem_dep.requirement) else gem_source_index.search(gem_dep) end end ## # Yields to the provided block with rubygems' source list set to the # list provided. Always resets the list when the block returns or # raises an exception. def with_gem_sources(*sources) sources.compact! original_sources = Gem.sources Gem.sources = sources unless sources.empty? yield ensure Gem.sources = original_sources end ## # Extracts the gemspec from a (on-disk) gem package. # === Returns # Gem::Specification # #-- # Compatibility note: Rubygems 1.x uses Gem::Format, 2.0 moved this # code into Gem::Package. def spec_from_file(file) if defined?(Gem::Format) && Gem::Package.respond_to?(:open) Gem::Format.from_file_by_path(file).spec else Gem::Package.new(file).spec end end ## # Determines the candidate version for a gem from a .gem file on disk # and checks if it matches the version constraints in +gem_dependency+ # === Returns # Gem::Version a singular gem version object is returned if the gem # is available # nil returns nil if the gem on disk doesn't match the # version constraints for +gem_dependency+ def candidate_version_from_file(gem_dependency, source) spec = spec_from_file(source) if spec.satisfies_requirement?(gem_dependency) logger.debug { "#{@new_resource} found candidate gem version #{spec.version} from local gem package #{source}" } spec.version else # This is probably going to end badly... logger.warn { "#{@new_resource} gem package #{source} does not satisfy the requirements #{gem_dependency}" } nil end end ## # Finds the newest version that satisfies the constraints of # +gem_dependency+. The version is determined from the cache or a # round-trip to the server as needed. The architecture and gem # sources will be set before making the query. # === Returns # Gem::Version a singular gem version object is returned if the gem # is available # nil returns nil if the gem could not be found def candidate_version_from_remote(gem_dependency, *sources) raise NotImplementedError end ## # Find the newest gem version available from Gem.sources that satisfies # the constraints of +gem_dependency+ def find_newest_remote_version(gem_dependency, *sources) spec, source = if Chef::Config[:rubygems_cache_enabled] # This code caches every gem on rubygems.org and uses lots of RAM available_gems = dependency_installer.find_gems_with_sources(gem_dependency) available_gems.pick_best! best_gem = available_gems.set.first best_gem && [best_gem.spec, best_gem.source] else # Use the API that 'gem install' calls which does not pull down the rubygems universe begin rs = dependency_installer.resolve_dependencies gem_dependency.name, gem_dependency.requirement rs.specs.select { |s| s.name == gem_dependency.name }.first rescue Gem::UnsatisfiableDependencyError nil end end version = spec && spec.version if version logger.debug { "#{@new_resource} found gem #{spec.name} version #{version} for platform #{spec.platform} from #{source}" } version else source_list = sources.compact.empty? ? "[#{Gem.sources.to_a.join(', ')}]" : "[#{sources.join(', ')}]" logger.warn { "#{@new_resource} failed to find gem #{gem_dependency} from #{source_list}" } nil end end ## # Installs a gem via the rubygems ruby API. # === Options # :sources rubygems servers to use # Other options are passed to Gem::DependencyInstaller.new def install(gem_dependency, options = {}) with_gem_sources(*options.delete(:sources)) do with_correct_verbosity do dependency_installer(options).install(gem_dependency) end end end ## # Uninstall the gem +gem_name+ via the rubygems ruby API. If # +gem_version+ is provided, only that version will be uninstalled. # Otherwise, all versions are uninstalled. # === Options # Options are passed to Gem::Uninstaller.new def uninstall(gem_name, gem_version = nil, opts = {}) gem_version ? opts[:version] = gem_version : opts[:all] = true with_correct_verbosity do uninstaller(gem_name, opts).uninstall end end ## # Set rubygems' user interaction to ConsoleUI or SilentUI depending # on our current debug level def with_correct_verbosity Gem::DefaultUserInteraction.ui = Chef::Log.debug? ? Gem::ConsoleUI.new : Gem::SilentUI.new yield end def dependency_installer(opts = {}) Gem::DependencyInstaller.new(opts) end def uninstaller(gem_name, opts = {}) Gem::Uninstaller.new(gem_name, DEFAULT_UNINSTALLER_OPTS.merge(opts)) end private def logger Chef::Log.logger end end class CurrentGemEnvironment < GemEnvironment def gem_paths Gem.path end def gem_source_index Gem.source_index end def gem_specification Gem::Specification end def candidate_version_from_remote(gem_dependency, *sources) with_gem_sources(*sources) do find_newest_remote_version(gem_dependency, *sources) end end end class AlternateGemEnvironment < GemEnvironment JRUBY_PLATFORM = /(:?universal|x86_64|x86)\-java\-[0-9\.]+/ def self.gempath_cache @gempath_cache ||= {} end def self.platform_cache @platform_cache ||= {} end include Chef::Mixin::ShellOut attr_reader :gem_binary_location def initialize(gem_binary_location) @gem_binary_location = gem_binary_location end def gem_paths if self.class.gempath_cache.key?(@gem_binary_location) self.class.gempath_cache[@gem_binary_location] else # shellout! is a fork/exec which won't work on windows shell_style_paths = shell_out!("#{@gem_binary_location} env gempath").stdout # on windows, the path separator is (usually? always?) semicolon paths = shell_style_paths.split(::File::PATH_SEPARATOR).map { |path| path.strip } self.class.gempath_cache[@gem_binary_location] = paths end end def gem_source_index @source_index ||= Gem::SourceIndex.from_gems_in(*gem_paths.map { |p| p + "/specifications" }) end def gem_specification # Only once, dirs calls a reset unless @specification Gem::Specification.dirs = gem_paths @specification = Gem::Specification end @specification end ## # Attempt to detect the correct platform settings for the target gem # environment. # # In practice, this only makes a difference if different versions are # available depending on platform, and only if the target gem # environment has a radically different platform (i.e., jruby), so we # just try to detect jruby and fall back to the current platforms # (Gem.platforms) if we don't detect it. # # === Returns # [String|Gem::Platform] returns an array of Gem::Platform-compatible # objects, i.e., Strings that are valid for Gem::Platform or actual # Gem::Platform objects. def gem_platforms if self.class.platform_cache.key?(@gem_binary_location) self.class.platform_cache[@gem_binary_location] else gem_environment = shell_out!("#{@gem_binary_location} env").stdout if jruby = gem_environment[JRUBY_PLATFORM] self.class.platform_cache[@gem_binary_location] = ["ruby", Gem::Platform.new(jruby)] else self.class.platform_cache[@gem_binary_location] = Gem.platforms end end end def with_gem_platforms(*alt_gem_platforms) alt_gem_platforms.flatten! original_gem_platforms = Gem.platforms Gem.platforms = alt_gem_platforms yield ensure Gem.platforms = original_gem_platforms end def candidate_version_from_remote(gem_dependency, *sources) with_gem_sources(*sources) do with_gem_platforms(*gem_platforms) do find_newest_remote_version(gem_dependency, *sources) end end end end attr_reader :gem_env attr_reader :cleanup_gem_env def logger Chef::Log.logger end provides :chef_gem provides :gem_package include Chef::Mixin::GetSourceFromPackage def initialize(new_resource, run_context = nil) super @cleanup_gem_env = true if new_resource.gem_binary if new_resource.options && new_resource.options.kind_of?(Hash) msg = "options cannot be given as a hash when using an explicit gem_binary\n" msg << "in #{new_resource} from #{new_resource.source_line}" raise ArgumentError, msg end @gem_env = AlternateGemEnvironment.new(new_resource.gem_binary) Chef::Log.debug("#{@new_resource} using gem '#{new_resource.gem_binary}'") elsif is_omnibus? && (!@new_resource.instance_of? Chef::Resource::ChefGem) # Opscode Omnibus - The ruby that ships inside omnibus is only used for Chef # Default to installing somewhere more functional if new_resource.options && new_resource.options.kind_of?(Hash) msg = [ "Gem options must be passed to gem_package as a string instead of a hash when", "using this installation of Chef because it runs with its own packaged Ruby. A hash", "may only be used when installing a gem to the same Ruby installation that Chef is", "running under. See https://docs.chef.io/resource_gem_package.html for more information.", "Error raised at #{new_resource} from #{new_resource.source_line}", ].join("\n") raise ArgumentError, msg end gem_location = find_gem_by_path @new_resource.gem_binary gem_location @gem_env = AlternateGemEnvironment.new(gem_location) Chef::Log.debug("#{@new_resource} using gem '#{gem_location}'") else @gem_env = CurrentGemEnvironment.new @cleanup_gem_env = false Chef::Log.debug("#{@new_resource} using gem from running ruby environment") end end def is_omnibus? if RbConfig::CONFIG["bindir"] =~ %r{/(opscode|chef|chefdk)/embedded/bin} Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}") # Omnibus installs to a static path because of linking on unix, find it. true elsif RbConfig::CONFIG["bindir"].sub(/^[\w]:/, "") == "/opscode/chef/embedded/bin" Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}") # windows, with the drive letter removed true else false end end def find_gem_by_path Chef::Log.debug("#{@new_resource} searching for 'gem' binary in path: #{ENV['PATH']}") separator = ::File::ALT_SEPARATOR ? ::File::ALT_SEPARATOR : ::File::SEPARATOR path_to_first_gem = ENV["PATH"].split(::File::PATH_SEPARATOR).find { |path| ::File.exists?(path + separator + "gem") } raise Chef::Exceptions::FileNotFound, "Unable to find 'gem' binary in path: #{ENV['PATH']}" if path_to_first_gem.nil? path_to_first_gem + separator + "gem" end def gem_dependency Gem::Dependency.new(@new_resource.package_name, @new_resource.version) end def source_is_remote? return true if @new_resource.source.nil? scheme = URI.parse(@new_resource.source).scheme # URI.parse gets confused by MS Windows paths with forward slashes. scheme = nil if scheme =~ /^[a-z]$/ %w{http https}.include?(scheme) rescue URI::InvalidURIError Chef::Log.debug("#{@new_resource} failed to parse source '#{@new_resource.source}' as a URI, assuming a local path") false end def current_version # rubygems 2.6.3 ensures that gem lists are sorted newest first pos = if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("2.6.3") :first else :last end # If one or more matching versions are installed, the newest of them # is the current version if !matching_installed_versions.empty? gemspec = matching_installed_versions.send(pos) logger.debug { "#{@new_resource} found installed gem #{gemspec.name} version #{gemspec.version} matching #{gem_dependency}" } gemspec # If no version matching the requirements exists, the latest installed # version is the current version. elsif !all_installed_versions.empty? gemspec = all_installed_versions.send(pos) logger.debug { "#{@new_resource} newest installed version of gem #{gemspec.name} is #{gemspec.version}" } gemspec else logger.debug { "#{@new_resource} no installed version found for #{gem_dependency}" } nil end end def matching_installed_versions @matching_installed_versions ||= @gem_env.installed_versions(gem_dependency) end def all_installed_versions @all_installed_versions ||= begin @gem_env.installed_versions(Gem::Dependency.new(gem_dependency.name, ">= 0")) end end def gem_sources @new_resource.source ? Array(@new_resource.source) : nil end def load_current_resource @current_resource = Chef::Resource::Package::GemPackage.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) if current_spec = current_version @current_resource.version(current_spec.version.to_s) end @current_resource end def cleanup_after_converge if @cleanup_gem_env logger.debug { "#{@new_resource} resetting gem environment to default" } Gem.clear_paths end end def candidate_version @candidate_version ||= begin if source_is_remote? @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s else @gem_env.candidate_version_from_file(gem_dependency, @new_resource.source).to_s end end end def version_requirement_satisfied?(current_version, new_version) return false unless current_version && new_version Gem::Requirement.new(new_version).satisfied_by?(Gem::Version.new(current_version)) end ## # Installs the gem, using either the gems API or shelling out to `gem` # according to the following criteria: # 1. Use gems API (Gem::DependencyInstaller) by default # 2. shell out to `gem install` when a String of options is given # 3. use gems API with options if a hash of options is given def install_package(name, version) if source_is_remote? && @new_resource.gem_binary.nil? if @new_resource.options.nil? @gem_env.install(gem_dependency, :sources => gem_sources) elsif @new_resource.options.kind_of?(Hash) options = @new_resource.options options[:sources] = gem_sources @gem_env.install(gem_dependency, options) else install_via_gem_command(name, version) end elsif @new_resource.gem_binary.nil? @gem_env.install(@new_resource.source) else install_via_gem_command(name, version) end true end def gem_binary_path @new_resource.gem_binary || "gem" end def install_via_gem_command(name, version) if @new_resource.source =~ /\.gem$/i name = @new_resource.source src = " --local" unless source_is_remote? elsif @new_resource.clear_sources src = " --clear-sources" src << (@new_resource.source && " --source=#{@new_resource.source}" || "") else src = @new_resource.source && " --source=#{@new_resource.source} --source=#{Chef::Config[:rubygems_url]}" end if !version.nil? && version.length > 0 shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env => nil) else shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env => nil) end end def upgrade_package(name, version) install_package(name, version) end def remove_package(name, version) if @new_resource.gem_binary.nil? if @new_resource.options.nil? @gem_env.uninstall(name, version) elsif @new_resource.options.kind_of?(Hash) @gem_env.uninstall(name, version, @new_resource.options) else uninstall_via_gem_command(name, version) end else uninstall_via_gem_command(name, version) end end def uninstall_via_gem_command(name, version) if version shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env => nil) else shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env => nil) end end def purge_package(name, version) remove_package(name, version) end private def opts expand_options(@new_resource.options) end end end end end chef-12.14.60/lib/chef/provider/package/smartos.rb000066400000000000000000000065111276456504500216100ustar00rootroot00000000000000# # Authors:: Trevor O (trevoro@joyent.com) # Bryan McLellan (btm@loftninjas.org) # Matthew Landauer (matthew@openaustralia.org) # Ben Rockwood (benr@joyent.com) # Copyright:: Copyright 2009-2016, Bryan McLellan, Matthew Landauer # 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" require "chef/resource/package" require "chef/mixin/get_source_from_package" class Chef class Provider class Package class SmartOS < Chef::Provider::Package attr_accessor :is_virtual_package provides :package, platform: "smartos" provides :smartos_package, os: "solaris2", platform_family: "smartos" def load_current_resource Chef::Log.debug("#{@new_resource} loading current resource") @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) check_package_state(@new_resource.package_name) @current_resource # modified by check_package_state end def check_package_state(name) Chef::Log.debug("#{@new_resource} checking package #{name}") version = nil info = shell_out_with_timeout!("/opt/local/sbin/pkg_info", "-E", "#{name}*", :env => nil, :returns => [0, 1]) if info.stdout version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1] end if version @current_resource.version(version) end end def candidate_version return @candidate_version if @candidate_version name = nil version = nil pkg = shell_out_with_timeout!("/opt/local/bin/pkgin", "se", new_resource.package_name, :env => nil, :returns => [0, 1]) pkg.stdout.each_line do |line| case line when /^#{new_resource.package_name}/ name, version = line.split(/[; ]/)[0].split(/-([^-]+)$/) end end @candidate_version = version version end def install_package(name, version) Chef::Log.debug("#{@new_resource} installing package #{name} version #{version}") package = "#{name}-#{version}" out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "install", package, :env => nil) end def upgrade_package(name, version) Chef::Log.debug("#{@new_resource} upgrading package #{name} version #{version}") install_package(name, version) end def remove_package(name, version) Chef::Log.debug("#{@new_resource} removing package #{name} version #{version}") package = "#{name}" out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "remove", package, :env => nil) end end end end end chef-12.14.60/lib/chef/provider/package/solaris.rb000066400000000000000000000131321276456504500215710ustar00rootroot00000000000000# # Author:: Toomas Pelberg () # Copyright:: Copyright 2010-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" require "chef/mixin/command" require "chef/resource/package" require "chef/mixin/get_source_from_package" class Chef class Provider class Package class Solaris < Chef::Provider::Package include Chef::Mixin::GetSourceFromPackage provides :package, platform: "nexentacore" provides :package, platform: "solaris2", platform_version: "< 5.11" provides :solaris_package, os: "solaris2" # def initialize(*args) # super # @current_resource = Chef::Resource::Package.new(@new_resource.name) # end def define_resource_requirements super requirements.assert(:install) do |a| a.assertion { @new_resource.source } a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install" end requirements.assert(:all_actions) do |a| a.assertion { !@new_resource.source || @package_source_found } a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}" a.whyrun "would assume #{@new_resource.source} would be have previously been made available" end end def load_current_resource @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) if @new_resource.source @package_source_found = ::File.exists?(@new_resource.source) if @package_source_found Chef::Log.debug("#{@new_resource} checking pkg status") shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}").stdout.each_line do |line| case line when /VERSION:\s+(.+)/ @new_resource.version($1) end end end end Chef::Log.debug("#{@new_resource} checking install state") status = shell_out_with_timeout("pkginfo -l #{@current_resource.package_name}") status.stdout.each_line do |line| case line when /VERSION:\s+(.+)/ Chef::Log.debug("#{@new_resource} version #{$1} is already installed") @current_resource.version($1) end end unless status.exitstatus == 0 || status.exitstatus == 1 raise Chef::Exceptions::Package, "pkginfo failed - #{status.inspect}!" end @current_resource end def candidate_version return @candidate_version if @candidate_version status = shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}") status.stdout.each_line do |line| case line when /VERSION:\s+(.+)/ @candidate_version = $1 @new_resource.version($1) Chef::Log.debug("#{@new_resource} setting install candidate version to #{@candidate_version}") end end unless status.exitstatus == 0 raise Chef::Exceptions::Package, "pkginfo -l -d #{@new_resource.source} - #{status.inspect}!" end @candidate_version end def install_package(name, version) Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}") if @new_resource.options.nil? if ::File.directory?(@new_resource.source) # CHEF-4469 command = "pkgadd -n -d #{@new_resource.source} #{@new_resource.package_name}" else command = "pkgadd -n -d #{@new_resource.source} all" end shell_out_with_timeout!(command) Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") else if ::File.directory?(@new_resource.source) # CHEF-4469 command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" else command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all" end shell_out_with_timeout!(command) Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}") end end alias_method :upgrade_package, :install_package def remove_package(name, version) if @new_resource.options.nil? shell_out_with_timeout!( "pkgrm -n #{name}" ) Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}") else shell_out_with_timeout!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" ) Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}") end end end end end end chef-12.14.60/lib/chef/provider/package/windows.rb000066400000000000000000000241621276456504500216140ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/uris" require "chef/resource/windows_package" require "chef/provider/package" require "chef/util/path_helper" require "chef/mixin/checksum" class Chef class Provider class Package class Windows < Chef::Provider::Package include Chef::Mixin::Uris include Chef::Mixin::Checksum provides :package, os: "windows" provides :windows_package, os: "windows" require "chef/provider/package/windows/registry_uninstall_entry.rb" def define_resource_requirements requirements.assert(:install) do |a| a.assertion { new_resource.source || msi? } a.failure_message Chef::Exceptions::NoWindowsPackageSource, "Source for package #{new_resource.name} must be specified in the resource's source property for package to be installed because the package_name property is used to test for the package installation state for this package type." end end # load_current_resource is run in Chef::Provider#run_action when not in whyrun_mode? def load_current_resource @current_resource = Chef::Resource::WindowsPackage.new(new_resource.name) if downloadable_file_missing? Chef::Log.debug("We do not know the version of #{new_resource.source} because the file is not downloaded") current_resource.version(:unknown.to_s) else current_resource.version(package_provider.installed_version) new_resource.version(package_provider.package_version) if package_provider.package_version end current_resource end def package_provider @package_provider ||= begin case installer_type when :msi Chef::Log.debug("#{new_resource} is MSI") require "chef/provider/package/windows/msi" Chef::Provider::Package::Windows::MSI.new(resource_for_provider, uninstall_registry_entries) else Chef::Log.debug("#{new_resource} is EXE with type '#{installer_type}'") require "chef/provider/package/windows/exe" Chef::Provider::Package::Windows::Exe.new(resource_for_provider, installer_type, uninstall_registry_entries) end end end def installer_type # Depending on the installer, we may need to examine installer_type or # source attributes, or search for text strings in the installer file # binary to determine the installer type for the user. Since the file # must be on disk to do so, we have to make this choice in the provider. @installer_type ||= begin return :msi if msi? if new_resource.installer_type new_resource.installer_type elsif source_location.nil? inferred_registry_type else basename = ::File.basename(source_location) file_extension = basename.split(".").last.downcase # search the binary file for installer type ::Kernel.open(::File.expand_path(source_location), "rb") do |io| filesize = io.size bufsize = 4096 # read 4K buffers overlap = 16 # bytes to overlap between buffer reads until io.eof contents = io.read(bufsize) case contents when /inno/i # Inno Setup return :inno when /wise/i # Wise InstallMaster return :wise when /nullsoft/i # Nullsoft Scriptable Install System return :nsis end if io.tell() < filesize io.seek(io.tell() - overlap) end end # if file is named 'setup.exe' assume installshield if basename == "setup.exe" :installshield else raise Chef::Exceptions::CannotDetermineWindowsInstallerType, "Installer type for Windows Package '#{new_resource.name}' not specified and cannot be determined from file extension '#{file_extension}'" end end end end end def action_install if uri_scheme?(new_resource.source) download_source_file load_current_resource else validate_content! end super end # Chef::Provider::Package action_install + action_remove call install_package + remove_package # Pass those calls to the correct sub-provider def install_package(name, version) package_provider.install_package end def remove_package(name, version) package_provider.remove_package end # @return [Array] new_version(s) as an array def new_version_array # Because the one in the parent caches things [new_resource.version] end # @return [String] candidate_version def candidate_version @candidate_version ||= (new_resource.version || "latest") end # @return [Array] current_version(s) as an array # this package provider does not support package arrays # However, There may be multiple versions for a single # package so the first element may be a nested array def current_version_array [ current_resource.version ] end # @param current_version one or more versions currently installed # @param new_version version of the new resource # # @return [Boolean] true if new_version is equal to or included in current_version def target_version_already_installed?(current_version, new_version) Chef::Log.debug("Checking if #{new_resource} version '#{new_version}' is already installed. #{current_version} is currently installed") if current_version.is_a?(Array) current_version.include?(new_version) else new_version == current_version end end def have_any_matching_version? target_version_already_installed?(current_resource.version, new_resource.version) end private def uninstall_registry_entries @uninstall_registry_entries ||= Chef::Provider::Package::Windows::RegistryUninstallEntry.find_entries(new_resource.package_name) end def inferred_registry_type @inferred_registry_type ||= begin uninstall_registry_entries.each do |entry| return :inno if entry.key.end_with?("_is1") return :msi if entry.uninstall_string.downcase.start_with?("msiexec.exe ") return :nsis if entry.uninstall_string.downcase.end_with?("uninst.exe\"") end nil end end def downloadable_file_missing? !new_resource.source.nil? && uri_scheme?(new_resource.source) && !::File.exists?(source_location) end def resource_for_provider @resource_for_provider = Chef::Resource::WindowsPackage.new(new_resource.name).tap do |r| r.source(Chef::Util::PathHelper.validate_path(source_location)) unless source_location.nil? r.cookbook_name = new_resource.cookbook_name r.version(new_resource.version) r.timeout(new_resource.timeout) r.returns(new_resource.returns) r.options(new_resource.options) end end def download_source_file source_resource.run_action(:create) Chef::Log.debug("#{new_resource} fetched source file to #{source_resource.path}") end def source_resource @source_resource ||= Chef::Resource::RemoteFile.new(default_download_cache_path, run_context).tap do |r| r.source(new_resource.source) r.cookbook_name = new_resource.cookbook_name r.checksum(new_resource.checksum) r.backup(false) if new_resource.remote_file_attributes new_resource.remote_file_attributes.each do |(k, v)| r.send(k.to_sym, v) end end end end def default_download_cache_path uri = ::URI.parse(new_resource.source) filename = ::File.basename(::URI.unescape(uri.path)) file_cache_dir = Chef::FileCache.create_cache_path("package/") Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}") end def source_location if new_resource.source.nil? nil elsif uri_scheme?(new_resource.source) source_resource.path else new_source = Chef::Util::PathHelper.cleanpath(new_resource.source) ::File.exist?(new_source) ? new_source : nil end end def validate_content! if new_resource.checksum source_checksum = checksum(source_location) if new_resource.checksum != source_checksum raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(source_checksum)) end end end def msi? return true if new_resource.installer_type == :msi if source_location.nil? inferred_registry_type == :msi else ::File.extname(source_location).casecmp(".msi").zero? end end end end end end chef-12.14.60/lib/chef/provider/package/windows/000077500000000000000000000000001276456504500212625ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/package/windows/exe.rb000066400000000000000000000075241276456504500224000ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Author:: Matt Wrock # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/shell_out" class Chef class Provider class Package class Windows class Exe include Chef::Mixin::ShellOut def initialize(resource, installer_type, uninstall_entries) @new_resource = resource @installer_type = installer_type @uninstall_entries = uninstall_entries end attr_reader :new_resource attr_reader :installer_type attr_reader :uninstall_entries # From Chef::Provider::Package def expand_options(options) options ? " #{options}" : "" end # Returns a version if the package is installed or nil if it is not. def installed_version Chef::Log.debug("#{new_resource} checking package version") current_installed_version end def package_version new_resource.version end def install_package Chef::Log.debug("#{new_resource} installing #{new_resource.installer_type} package '#{new_resource.source}'") shell_out!( [ "start", "\"\"", "/wait", "\"#{new_resource.source}\"", unattended_flags, expand_options(new_resource.options), "& exit %%%%ERRORLEVEL%%%%", ].join(" "), timeout: new_resource.timeout, returns: new_resource.returns ) end def remove_package uninstall_version = new_resource.version || current_installed_version uninstall_entries.select { |entry| [uninstall_version].flatten.include?(entry.display_version) } .map { |version| version.uninstall_string }.uniq.each do |uninstall_string| Chef::Log.debug("Registry provided uninstall string for #{new_resource} is '#{uninstall_string}'") shell_out!(uninstall_command(uninstall_string), { :timeout => new_resource.timeout, :returns => new_resource.returns }) end end private def uninstall_command(uninstall_string) uninstall_string = "\"#{uninstall_string}\"" if ::File.exist?(uninstall_string) uninstall_string = [ uninstall_string, expand_options(new_resource.options), " ", unattended_flags, ].join %Q{start "" /wait #{uninstall_string} & exit %%%%ERRORLEVEL%%%%} end def current_installed_version @current_installed_version ||= uninstall_entries.count == 0 ? nil : begin uninstall_entries.map { |entry| entry.display_version }.uniq end end # http://unattended.sourceforge.net/installers.php def unattended_flags case installer_type when :installshield "/s /sms" when :nsis "/S /NCRC" when :inno "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART" when :wise "/s" end end end end end end end chef-12.14.60/lib/chef/provider/package/windows/msi.rb000066400000000000000000000103601276456504500223770ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # TODO: Allow @new_resource.source to be a Product Code as a GUID for uninstall / network install require "chef/win32/api/installer" if (RUBY_PLATFORM =~ /mswin|mingw32|windows/) && Chef::Platform.supports_msi? require "chef/mixin/shell_out" class Chef class Provider class Package class Windows class MSI include Chef::ReservedNames::Win32::API::Installer if (RUBY_PLATFORM =~ /mswin|mingw32|windows/) && Chef::Platform.supports_msi? include Chef::Mixin::ShellOut def initialize(resource, uninstall_entries) @new_resource = resource @uninstall_entries = uninstall_entries end attr_reader :new_resource attr_reader :uninstall_entries # From Chef::Provider::Package def expand_options(options) options ? " #{options}" : "" end # Returns a version if the package is installed or nil if it is not. def installed_version if !new_resource.source.nil? && ::File.exist?(new_resource.source) Chef::Log.debug("#{new_resource} getting product code for package at #{new_resource.source}") product_code = get_product_property(new_resource.source, "ProductCode") Chef::Log.debug("#{new_resource} checking package status and version for #{product_code}") get_installed_version(product_code) else uninstall_entries.count == 0 ? nil : begin uninstall_entries.map { |entry| entry.display_version }.uniq end end end def package_version return new_resource.version if new_resource.version if !new_resource.source.nil? && ::File.exist?(new_resource.source) Chef::Log.debug("#{new_resource} getting product version for package at #{new_resource.source}") get_product_property(new_resource.source, "ProductVersion") end end def install_package # We could use MsiConfigureProduct here, but we'll start off with msiexec Chef::Log.debug("#{new_resource} installing MSI package '#{new_resource.source}'") shell_out!("msiexec /qn /i \"#{new_resource.source}\" #{expand_options(new_resource.options)}", { :timeout => new_resource.timeout, :returns => new_resource.returns }) end def remove_package # We could use MsiConfigureProduct here, but we'll start off with msiexec if !new_resource.source.nil? && ::File.exist?(new_resource.source) Chef::Log.debug("#{new_resource} removing MSI package '#{new_resource.source}'") shell_out!("msiexec /qn /x \"#{new_resource.source}\" #{expand_options(new_resource.options)}", { :timeout => new_resource.timeout, :returns => new_resource.returns }) else uninstall_version = new_resource.version || installed_version uninstall_entries.select { |entry| [uninstall_version].flatten.include?(entry.display_version) } .map { |version| version.uninstall_string }.uniq.each do |uninstall_string| Chef::Log.debug("#{new_resource} removing MSI package version using '#{uninstall_string}'") uninstall_string += expand_options(new_resource.options) uninstall_string += " /Q" unless uninstall_string =~ / \/Q\b/ shell_out!(uninstall_string, { :timeout => new_resource.timeout, :returns => new_resource.returns }) end end end end end end end end chef-12.14.60/lib/chef/provider/package/windows/registry_uninstall_entry.rb000066400000000000000000000066441276456504500270030ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Author:: Matt Wrock # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "win32/registry" if RUBY_PLATFORM =~ /mswin|mingw32|windows/ class Chef class Provider class Package class Windows class RegistryUninstallEntry def self.find_entries(package_name) Chef::Log.debug("Finding uninstall entries for #{package_name}") entries = [] [ [::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0100)], [::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0200)], [::Win32::Registry::HKEY_CURRENT_USER], ].each do |hkey| desired = hkey.length > 1 ? hkey[1] : ::Win32::Registry::Constants::KEY_READ begin ::Win32::Registry.open(hkey[0], UNINSTALL_SUBKEY, desired) do |reg| reg.each_key do |key, _wtime| begin entry = reg.open(key, desired) display_name = read_registry_property(entry, "DisplayName") if display_name == package_name entries.push(RegistryUninstallEntry.new(hkey, key, entry)) end rescue ::Win32::Registry::Error => ex Chef::Log.debug("Registry error opening key '#{key}' on node #{desired}: #{ex}") end end end rescue ::Win32::Registry::Error => ex Chef::Log.debug("Registry error opening hive '#{hkey[0]}' :: #{desired}: #{ex}") end end entries end def self.read_registry_property(data, property) data[property] rescue ::Win32::Registry::Error => ex Chef::Log.debug("Failure to read property '#{property}'") nil end def initialize(hive, key, registry_data) Chef::Log.debug("Creating uninstall entry for #{hive}::#{key}") @hive = hive @key = key @data = registry_data @display_name = RegistryUninstallEntry.read_registry_property(registry_data, "DisplayName") @display_version = RegistryUninstallEntry.read_registry_property(registry_data, "DisplayVersion") @uninstall_string = RegistryUninstallEntry.read_registry_property(registry_data, "UninstallString") end attr_reader :hive attr_reader :key attr_reader :display_name attr_reader :display_version attr_reader :uninstall_string attr_reader :data UNINSTALL_SUBKEY = 'Software\Microsoft\Windows\CurrentVersion\Uninstall'.freeze end end end end end chef-12.14.60/lib/chef/provider/package/yum.rb000066400000000000000000000440751276456504500207410ustar00rootroot00000000000000 # Author:: Adam Jacob () # 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/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 class Package class Yum < Chef::Provider::Package provides :package, platform_family: %w{rhel fedora} provides :yum_package, os: "linux" include Chef::Mixin::GetSourceFromPackage def initialize(new_resource, run_context) super @yum = YumCache.instance @yum.yum_binary = yum_binary end def yum_binary @yum_binary ||= begin yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage) yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum" end end # Extra attributes # def arch_for_name(n) if @new_resource.respond_to?("arch") @new_resource.arch elsif @arch idx = package_name_array.index(n) as_array(@arch)[idx] else nil end end def arch if @new_resource.respond_to?("arch") @new_resource.arch else nil end end def set_arch(arch) if @new_resource.respond_to?("arch") @new_resource.arch(arch) end end def flush_cache if @new_resource.respond_to?("flush_cache") @new_resource.flush_cache else { :before => false, :after => false } end end # Helpers # def yum_arch(arch) arch ? ".#{arch}" : nil end def yum_command(command) command = "#{yum_binary} #{command}" Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"") status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] }) # This is fun: rpm can encounter errors in the %post/%postun scripts which aren't # considered fatal - meaning the rpm is still successfully installed. These issue # cause yum to emit a non fatal warning but still exit(1). As there's currently no # way to suppress this behavior and an exit(1) will break a Chef run we make an # effort to trap these and re-run the same install command - it will either fail a # second time or succeed. # # A cleaner solution would have to be done in python and better hook into # yum/rpm to handle exceptions as we see fit. if status.exitstatus == 1 status.stdout.each_line do |l| # rpm-4.4.2.3 lib/psm.c line 2182 if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$} Chef::Log.warn("#{@new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " + "so running install again to verify.") status = shell_out_with_timeout(command, { :timeout => Chef::Config[:yum_timeout] }) break end end end if status.exitstatus > 0 command_output = "STDOUT: #{status.stdout}\nSTDERR: #{status.stderr}" raise Chef::Exceptions::Exec, "#{command} returned #{status.exitstatus}:\n#{command_output}" end end # Standard Provider methods for Parent # def load_current_resource if flush_cache[:before] @yum.reload end if @new_resource.options repo_control = [] @new_resource.options.split.each do |opt| if opt =~ %r{--(enable|disable)repo=.+} repo_control << opt end end if repo_control.size > 0 @yum.enable_extra_repo_control(repo_control.join(" ")) else @yum.disable_extra_repo_control end else @yum.disable_extra_repo_control end # At this point package_name could be: # # 1) a package name, eg: "foo" # 2) a package name.arch, eg: "foo.i386" # 3) or a dependency, eg: "foo >= 1.1" # Check if we have name or name+arch which has a priority over a dependency package_name_array.each_with_index do |n, index| unless @yum.package_available?(n) # If they aren't in the installed packages they could be a dependency dep = parse_dependency(n, new_version_array[index]) if dep if @new_resource.package_name.is_a?(Array) @new_resource.package_name(package_name_array - [n] + [dep.first]) @new_resource.version(new_version_array - [new_version_array[index]] + [dep.last]) if dep.last else @new_resource.package_name(dep.first) @new_resource.version(dep.last) if dep.last end end end end @current_resource = Chef::Resource::YumPackage.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) installed_version = [] @candidate_version = [] @arch = [] if @new_resource.source unless ::File.exists?(@new_resource.source) raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}" end Chef::Log.debug("#{@new_resource} checking rpm status") shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line| case line when /([\w\d_.-]+)\s([\w\d_.-]+)/ @current_resource.package_name($1) @new_resource.version($2) end end @candidate_version << @new_resource.version installed_version << @yum.installed_version(@current_resource.package_name, arch) else package_name_array.each_with_index do |pkg, idx| # Don't overwrite an existing arch if arch name, parch = pkg, arch else name, parch = parse_arch(pkg) # if we parsed an arch from the name, update the name # to be just the package name. if parch if @new_resource.package_name.is_a?(Array) @new_resource.package_name[idx] = name else @new_resource.package_name(name) # only set the arch if it's a single package set_arch(parch) end end end if @new_resource.version new_resource = "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch(parch)}" else new_resource = "#{@new_resource.package_name}#{yum_arch(parch)}" end Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}") installed_version << @yum.installed_version(name, parch) @candidate_version << @yum.candidate_version(name, parch) @arch << parch end end if installed_version.size == 1 @current_resource.version(installed_version[0]) @candidate_version = @candidate_version[0] @arch = @arch[0] else @current_resource.version(installed_version) end Chef::Log.debug("#{@new_resource} installed version: #{installed_version || "(none)"} candidate version: " + "#{@candidate_version || "(none)"}") @current_resource end def install_remote_package(name, version) # Work around yum not exiting with an error if a package doesn't exist # for CHEF-2062 all_avail = as_array(name).zip(as_array(version)).any? do |n, v| @yum.version_available?(n, v, arch_for_name(n)) end method = log_method = nil methods = [] if all_avail # More Yum fun: # # yum install of an old name+version will exit(1) # yum install of an old name+version+arch will exit(0) for some reason # # Some packages can be installed multiple times like the kernel as_array(name).zip(as_array(version)).each do |n, v| method = "install" log_method = "installing" idx = package_name_array.index(n) unless @yum.allow_multi_install.include?(n) if RPMVersion.parse(current_version_array[idx]) > RPMVersion.parse(v) # We allow downgrading only in the evenit of single-package # rules where the user explicitly allowed it if allow_downgrade method = "downgrade" log_method = "downgrading" else # we bail like yum when the package is older raise Chef::Exceptions::Package, "Installed package #{n}-#{current_version_array[idx]} is newer " + "than candidate package #{n}-#{v}" end end end # methods don't count for packages we won't be touching next if RPMVersion.parse(current_version_array[idx]) == RPMVersion.parse(v) methods << method end # We could split this up into two commands if we wanted to, but # for now, just don't support this. if methods.uniq.length > 1 raise Chef::Exceptions::Package, "Multipackage rule #{name} has a mix of upgrade and downgrade packages. Cannot proceed." end repos = [] pkg_string_bits = [] as_array(name).zip(as_array(version)).each do |n, v| idx = package_name_array.index(n) a = arch_for_name(n) s = "" unless v == current_version_array[idx] s = "#{n}-#{v}#{yum_arch(a)}" repo = @yum.package_repository(n, v, a) repos << "#{s} from #{repo} repository" pkg_string_bits << s end end pkg_string = pkg_string_bits.join(" ") Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}") yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}") else raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " + "and release? (version-release, e.g. 1.84-10.fc6)" end end def install_package(name, version) if @new_resource.source yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}") else install_remote_package(name, version) end if flush_cache[:after] @yum.reload else @yum.reload_installed end end # Keep upgrades from trying to install an older candidate version. Can happen when a new # version is installed then removed from a repository, now the older available version # shows up as a viable install candidate. # # Can be done in upgrade_package but an upgraded from->to log message slips out # # Hacky - better overall solution? Custom compare in Package provider? def action_upgrade # Could be uninstalled or have no candidate if @current_resource.version.nil? || !candidate_version_array.any? super elsif candidate_version_array.zip(current_version_array).any? do |c, i| RPMVersion.parse(c) > RPMVersion.parse(i) end super else Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do") end end def upgrade_package(name, version) install_package(name, version) end def remove_package(name, version) if version remove_str = as_array(name).zip(as_array(version)).map do |n, v| a = arch_for_name(n) "#{[n, v].join('-')}#{yum_arch(a)}" end.join(" ") else remove_str = as_array(name).map do |n| a = arch_for_name(n) "#{n}#{yum_arch(a)}" end.join(" ") end yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}") if flush_cache[:after] @yum.reload else @yum.reload_installed end end def purge_package(name, version) remove_package(name, version) end private def parse_arch(package_name) # Allow for foo.x86_64 style package_name like yum uses in it's output # if package_name =~ %r{^(.*)\.(.*)$} new_package_name = $1 new_arch = $2 # foo.i386 and foo.beta1 are both valid package names or expressions of an arch. # Ensure we don't have an existing package matching package_name, then ensure we at # least have a match for the new_package+new_arch before we overwrite. If neither # then fall through to standard package handling. old_installed = @yum.installed_version(package_name) old_candidate = @yum.candidate_version(package_name) new_installed = @yum.installed_version(new_package_name, new_arch) new_candidate = @yum.candidate_version(new_package_name, new_arch) if (old_installed.nil? && old_candidate.nil?) && (new_installed || new_candidate) Chef::Log.debug("Parsed out arch #{new_arch}, new package name is #{new_package_name}") return new_package_name, new_arch end end return package_name, nil end # If we don't have the package we could have been passed a 'whatprovides' feature # # eg: yum install "perl(Config)" # yum install "mtr = 2:0.71-3.1" # yum install "mtr > 2:0.71" # # We support resolving these out of the Provides data imported from yum-dump.py and # matching them up with an actual package so the standard resource handling can apply. # # There is currently no support for filename matching. def parse_dependency(name, version) # Transform the package_name into a requirement # If we are passed a version or a version constraint we have to assume it's a requirement first. If it can't be # parsed only yum_require.name will be set and @new_resource.version will be left intact if version require_string = "#{name} #{version}" else # Transform the package_name into a requirement, might contain a version, could just be # a match for virtual provides require_string = name end yum_require = RPMRequire.parse(require_string) # and gather all the packages that have a Provides feature satisfying the requirement. # It could be multiple be we can only manage one packages = @yum.packages_from_require(yum_require) if packages.empty? # Don't bother if we are just ensuring a package is removed - we don't need Provides data actions = Array(@new_resource.action) unless actions.size == 1 && (actions[0] == :remove || actions[0] == :purge) Chef::Log.debug("#{@new_resource} couldn't match #{@new_resource.package_name} in " + "installed Provides, loading available Provides - this may take a moment") @yum.reload_provides packages = @yum.packages_from_require(yum_require) end end unless packages.empty? new_package_name = packages.first.name new_package_version = packages.first.version.to_s debug_msg = "#{name}: Unable to match package '#{name}' but matched #{packages.size} " debug_msg << (packages.size == 1 ? "package" : "packages") debug_msg << ", selected '#{new_package_name}' version '#{new_package_version}'" Chef::Log.debug(debug_msg) # Ensure it's not the same package under a different architecture unique_names = [] packages.each do |pkg| unique_names << "#{pkg.name}-#{pkg.version.evr}" end unique_names.uniq! if unique_names.size > 1 Chef::Log.warn("#{@new_resource} matched multiple Provides for #{@new_resource.package_name} " + "but we can only use the first match: #{new_package_name}. Please use a more " + "specific version.") end if yum_require.version.to_s.nil? new_package_version = nil end [new_package_name, new_package_version] end end end end end end chef-12.14.60/lib/chef/provider/package/yum/000077500000000000000000000000001276456504500204025ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/package/yum/rpm_utils.rb000066400000000000000000000453141276456504500227540ustar00rootroot00000000000000 # Author:: Adam Jacob () # 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 <=>(other) compare_versions(other) end def compare(other) compare_versions(other, false) end def partial_compare(other) compare_versions(other, 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 <=>(other) compare(other) 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 # , 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 chef-12.14.60/lib/chef/provider/package/yum/yum-dump.py000066400000000000000000000227741276456504500225450ustar00rootroot00000000000000# # Author:: Matthew Kent () # Copyright:: Copyright 2009-2016, Matthew Kent # 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. # # yum-dump.py # Inspired by yumhelper.py by David Lutterkort # # Produce a list of installed, available and re-installable packages using yum # and dump the results to stdout. # # yum-dump invokes yum similarly to the command line interface which makes it # subject to most of the configuration parameters in yum.conf. yum-dump will # also load yum plugins in the same manor as yum - these can affect the output. # # Can be run as non root, but that won't update the cache. # # Intended to support yum 2.x and 3.x import os import sys import time import yum import re import errno from yum import Errors from optparse import OptionParser from distutils import version YUM_PID_FILE='/var/run/yum.pid' YUM_VER = version.StrictVersion(yum.__version__) YUM_MAJOR = YUM_VER.version[0] if YUM_MAJOR > 3 or YUM_MAJOR < 2: print >> sys.stderr, "yum-dump Error: Can't match supported yum version" \ " (%s)" % yum.__version__ sys.exit(1) # Required for Provides output if YUM_MAJOR == 2: import rpm import rpmUtils.miscutils def setup(yb, options): # Only want our output # if YUM_MAJOR == 3: try: if YUM_VER >= version.StrictVersion("3.2.22"): yb.preconf.errorlevel=0 yb.preconf.debuglevel=0 # initialize the config yb.conf else: yb.doConfigSetup(errorlevel=0, debuglevel=0) except yum.Errors.ConfigError, e: # suppresses an ignored exception at exit yb.preconf = None print >> sys.stderr, "yum-dump Config Error: %s" % e return 1 except ValueError, e: yb.preconf = None print >> sys.stderr, "yum-dump Options Error: %s" % e return 1 elif YUM_MAJOR == 2: yb.doConfigSetup() def __log(a,b): pass yb.log = __log yb.errorlog = __log # Give Chef every possible package version, it can decide what to do with them if YUM_MAJOR == 3: yb.conf.showdupesfromrepos = True elif YUM_MAJOR == 2: yb.conf.setConfigOption('showdupesfromrepos', True) # Optionally run only on cached repositories, but non root must use the cache if os.geteuid() != 0: if YUM_MAJOR == 3: yb.conf.cache = True elif YUM_MAJOR == 2: yb.conf.setConfigOption('cache', True) else: if YUM_MAJOR == 3: yb.conf.cache = options.cache elif YUM_MAJOR == 2: yb.conf.setConfigOption('cache', options.cache) # Handle repo toggle via id or glob exactly like yum for opt, repos in options.repo_control: for repo in repos: if opt == '--enablerepo': yb.repos.enableRepo(repo) elif opt == '--disablerepo': yb.repos.disableRepo(repo) return 0 def dump_packages(yb, list, output_provides): packages = {} if YUM_MAJOR == 2: yb.doTsSetup() yb.doRepoSetup() yb.doSackSetup() db = yb.doPackageLists(list) for pkg in db.installed: pkg.type = 'i' packages[str(pkg)] = pkg if YUM_VER >= version.StrictVersion("3.2.21"): for pkg in db.available: pkg.type = 'a' packages[str(pkg)] = pkg # These are both installed and available for pkg in db.reinstall_available: pkg.type = 'r' packages[str(pkg)] = pkg else: # Old style method - no reinstall list for pkg in yb.pkgSack.returnPackages(): if str(pkg) in packages: if packages[str(pkg)].type == "i": packages[str(pkg)].type = 'r' continue pkg.type = 'a' packages[str(pkg)] = pkg unique_packages = packages.values() unique_packages.sort(lambda x, y: cmp(x.name, y.name)) for pkg in unique_packages: if output_provides == "all" or \ (output_provides == "installed" and (pkg.type == "i" or pkg.type == "r")): # yum 2 doesn't have provides_print, implement it ourselves using methods # based on requires gathering in packages.py if YUM_MAJOR == 2: provlist = [] # Installed and available are gathered in different ways if pkg.type == 'i' or pkg.type == 'r': names = pkg.hdr[rpm.RPMTAG_PROVIDENAME] flags = pkg.hdr[rpm.RPMTAG_PROVIDEFLAGS] ver = pkg.hdr[rpm.RPMTAG_PROVIDEVERSION] if names is not None: tmplst = zip(names, flags, ver) for (n, f, v) in tmplst: prov = rpmUtils.miscutils.formatRequire(n, v, f) provlist.append(prov) # This is slow :( elif pkg.type == 'a': for prcoTuple in pkg.returnPrco('provides'): prcostr = pkg.prcoPrintable(prcoTuple) provlist.append(prcostr) provides = provlist else: provides = pkg.provides_print else: provides = "[]" print '%s %s %s %s %s %s %s %s' % ( pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch, provides, pkg.type, pkg.repoid ) return 0 def yum_dump(options): lock_obtained = False yb = yum.YumBase() status = setup(yb, options) if status != 0: return status if options.output_options: print "[option installonlypkgs] %s" % " ".join(yb.conf.installonlypkgs) # Non root can't handle locking on rhel/centos 4 if os.geteuid() != 0: return dump_packages(yb, options.package_list, options.output_provides) # Wrap the collection and output of packages in yum's global lock to prevent # any inconsistencies. try: # Spin up to --yum-lock-timeout option countdown = options.yum_lock_timeout while True: try: yb.doLock(YUM_PID_FILE) lock_obtained = True except Errors.LockError, e: time.sleep(1) countdown -= 1 if countdown == 0: print >> sys.stderr, "yum-dump Locking Error! Couldn't obtain an " \ "exclusive yum lock in %d seconds. Giving up." % options.yum_lock_timeout return 200 else: break return dump_packages(yb, options.package_list, options.output_provides) # Ensure we clear the lock and cleanup any resources finally: try: yb.closeRpmDB() if lock_obtained == True: yb.doUnlock(YUM_PID_FILE) except Errors.LockError, e: print >> sys.stderr, "yum-dump Unlock Error: %s" % e return 200 # Preserve order of enable/disable repo args like yum does def gather_repo_opts(option, opt, value, parser): if getattr(parser.values, option.dest, None) is None: setattr(parser.values, option.dest, []) getattr(parser.values, option.dest).append((opt, value.split(','))) def main(): usage = "Usage: %prog [options]\n" + \ "Output a list of installed, available and re-installable packages via yum" parser = OptionParser(usage=usage) parser.add_option("-C", "--cache", action="store_true", dest="cache", default=False, help="run entirely from cache, don't update cache") parser.add_option("-o", "--options", action="store_true", dest="output_options", default=False, help="output select yum options useful to Chef") parser.add_option("-p", "--installed-provides", action="store_const", const="installed", dest="output_provides", default="none", help="output Provides for installed packages, big/wide output") parser.add_option("-P", "--all-provides", action="store_const", const="all", dest="output_provides", default="none", help="output Provides for all package, slow, big/wide output") parser.add_option("-i", "--installed", action="store_const", const="installed", dest="package_list", default="all", help="output only installed packages") parser.add_option("-a", "--available", action="store_const", const="available", dest="package_list", default="all", help="output only available and re-installable packages") parser.add_option("--enablerepo", action="callback", callback=gather_repo_opts, type="string", dest="repo_control", default=[], help="enable disabled repositories by id or glob") parser.add_option("--disablerepo", action="callback", callback=gather_repo_opts, type="string", dest="repo_control", default=[], help="disable repositories by id or glob") parser.add_option("--yum-lock-timeout", action="store", type="int", dest="yum_lock_timeout", default=30, help="Time in seconds to wait for yum process lock") (options, args) = parser.parse_args() try: return yum_dump(options) except yum.Errors.RepoError, e: print >> sys.stderr, "yum-dump Repository Error: %s" % e return 1 except yum.Errors.YumBaseError, e: print >> sys.stderr, "yum-dump General Error: %s" % e return 1 try: status = main() # Suppress a nasty broken pipe error when output is piped to utilities like 'head' except IOError, e: if e.errno == errno.EPIPE: sys.exit(1) else: raise sys.exit(status) chef-12.14.60/lib/chef/provider/package/yum/yum_cache.rb000066400000000000000000000274111276456504500226710ustar00rootroot00000000000000 # Author:: Adam Jacob () # 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 chef-12.14.60/lib/chef/provider/package/zypper.rb000066400000000000000000000112641276456504500214520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Authors:: Adam Jacob () # Ionuț Arțăriși () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # Copyright 2013-2016, SUSE Linux GmbH # 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" require "chef/resource/zypper_package" class Chef class Provider class Package class Zypper < Chef::Provider::Package use_multipackage_api provides :package, platform_family: "suse" provides :zypper_package, os: "linux" def get_versions(package_name) candidate_version = current_version = nil is_installed = false Chef::Log.debug("#{new_resource} checking zypper") status = shell_out_with_timeout!("zypper --non-interactive info #{package_name}") status.stdout.each_line do |line| case line when /^Version *: (.+) *$/ candidate_version = $1.strip Chef::Log.debug("#{new_resource} version #{candidate_version}") when /^Installed *: Yes *$/ is_installed = true Chef::Log.debug("#{new_resource} is installed") when /^Status *: out-of-date \(version (.+) installed\) *$/ current_version = $1.strip Chef::Log.debug("#{new_resource} out of date version #{current_version}") end end current_version = candidate_version if is_installed { current_version: current_version, candidate_version: candidate_version } end def versions @versions ||= begin raw_versions = package_name_array.map do |package_name| get_versions(package_name) end Hash[*package_name_array.zip(raw_versions).flatten] end end def get_candidate_versions package_name_array.map do |package_name| versions[package_name][:candidate_version] end end def get_current_versions package_name_array.map do |package_name| versions[package_name][:current_version] end end def load_current_resource @current_resource = Chef::Resource::ZypperPackage.new(new_resource.name) current_resource.package_name(new_resource.package_name) @candidate_version = get_candidate_versions current_resource.version(get_current_versions) current_resource end def zypper_version @zypper_version ||= `zypper -V 2>&1`.scan(/\d+/).join(".").to_f end def install_package(name, version) zypper_package("install --auto-agree-with-licenses", name, version) end def upgrade_package(name, version) # `zypper install` upgrades packages, we rely on the idempotency checks to get action :install behavior install_package(name, version) end def remove_package(name, version) zypper_package("remove", name, version) end def purge_package(name, version) zypper_package("remove --clean-deps", name, version) end private def zip(names, versions) names.zip(versions).map do |n, v| (v.nil? || v.empty?) ? n : "#{n}=#{v}" end end def zypper_package(command, names, versions) zipped_names = zip(names, versions) if zypper_version < 1.0 shell_out_with_timeout!(a_to_s("zypper", gpg_checks, command, "-y", names)) else shell_out_with_timeout!(a_to_s("zypper --non-interactive", gpg_checks, command, zipped_names)) end end def gpg_checks() case Chef::Config[:zypper_check_gpg] when true "" when false "--no-gpg-checks" when nil Chef::Log.warn("Chef::Config[:zypper_check_gpg] was not set. " + "All packages will be installed without gpg signature checks. " + "This is a security hazard.") "--no-gpg-checks" end end end end end end chef-12.14.60/lib/chef/provider/powershell_script.rb000066400000000000000000000201451276456504500222740ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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/platform/query_helpers" require "chef/provider/windows_script" class Chef class Provider class PowershellScript < Chef::Provider::WindowsScript provides :powershell_script, os: "windows" def initialize(new_resource, run_context) super(new_resource, run_context, ".ps1") add_exit_status_wrapper end def action_run validate_script_syntax! super end def command basepath = is_forced_32bit ? wow64_directory : run_context.node["kernel"]["os_info"]["system_directory"] # Powershell.exe is always in "v1.0" folder (for backwards compatibility) interpreter_path = Chef::Util::PathHelper.join(basepath, "WindowsPowerShell", "v1.0", interpreter) # Must use -File rather than -Command to launch the script # file created by the base class that contains the script # code -- otherwise, powershell.exe does not propagate the # error status of a failed Windows process that ran at the # end of the script, it gets changed to '1'. # # Nano only supports -Command cmd = "\"#{interpreter_path}\" #{flags}" if Chef::Platform.windows_nano_server? cmd << " -Command \". '#{script_file.path}'\"" else cmd << " -File \"#{script_file.path}\"" end cmd end def flags interpreter_flags = [*default_interpreter_flags].join(" ") if ! (@new_resource.flags.nil?) interpreter_flags = [@new_resource.flags, interpreter_flags].join(" ") end interpreter_flags end protected # Process exit codes are strange with PowerShell and require # special handling to cover common use cases. def add_exit_status_wrapper self.code = wrapper_script Chef::Log.debug("powershell_script provider called with script code:\n\n#{@new_resource.code}\n") Chef::Log.debug("powershell_script provider will execute transformed code:\n\n#{self.code}\n") end def validate_script_syntax! interpreter_arguments = default_interpreter_flags.join(" ") Tempfile.open(["chef_powershell_script-user-code", ".ps1"]) do |user_script_file| # Wrap the user's code in a PowerShell script block so that # it isn't executed. However, syntactically invalid script # in that block will still trigger a syntax error which is # exactly what we want here -- verify the syntax without # actually running the script. user_code_wrapped_in_powershell_script_block = <<-EOH { #{@new_resource.code} } EOH user_script_file.puts user_code_wrapped_in_powershell_script_block # A .close or explicit .flush required to ensure the file is # written to the file system at this point, which is required since # the intent is to execute the code just written to it. user_script_file.close validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command \". '#{user_script_file.path}'\"" # Note that other script providers like bash allow syntax errors # to be suppressed by setting 'returns' to a value that the # interpreter would return as a status code in the syntax # error case. We explicitly don't do this here -- syntax # errors will not be suppressed, since doing so could make # it harder for users to detect / debug invalid scripts. # Therefore, the only return value for a syntactically valid # script is 0. If an exception is raised by shellout, this # means a non-zero return and thus a syntactically invalid script. with_os_architecture(node, architecture: new_resource.architecture) do shell_out!(validation_command, { returns: [0] }) end end end def default_interpreter_flags return [] if Chef::Platform.windows_nano_server? # Execution policy 'Bypass' is preferable since it doesn't require # user input confirmation for files such as PowerShell modules # downloaded from the Internet. However, 'Bypass' is not supported # prior to PowerShell 3.0, so the fallback is 'Unrestricted' execution_policy = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? "Bypass" : "Unrestricted" [ "-NoLogo", "-NonInteractive", "-NoProfile", "-ExecutionPolicy #{execution_policy}", # Powershell will hang if STDIN is redirected # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected "-InputFormat None", ] end # A wrapper script is used to launch user-supplied script while # still obtaining useful process exit codes. Unless you # explicitly call exit in Powershell, the powershell.exe # interpreter returns only 0 for success or 1 for failure. Since # we'd like to get specific exit codes from executable tools run # with Powershell, we do some work using the automatic variables # $? and $LASTEXITCODE to return the process exit code of the # last process run in the script if it is the last command # executed, otherwise 0 or 1 based on whether $? is set to true # (success, where we return 0) or false (where we return 1). def wrapper_script <<-EOH # Chef Client wrapper for powershell_script resources # LASTEXITCODE can be uninitialized -- make it explictly 0 # to avoid incorrect detection of failure (non-zero) codes $global:LASTEXITCODE = 0 # Catch any exceptions -- without this, exceptions will result # In a zero return code instead of the desired non-zero code # that indicates a failure trap [Exception] {write-error ($_.Exception.Message);exit 1} # Variable state that should not be accessible to the user code new-variable -name interpolatedexitcode -visibility private -value $#{@new_resource.convert_boolean_return} new-variable -name chefscriptresult -visibility private # Initialize a variable we use to capture $? inside a block $global:lastcmdlet = $null # Execute the user's code in a script block -- $chefscriptresult = { #{@new_resource.code} # This assignment doesn't affect the block's return value $global:lastcmdlet = $? }.invokereturnasis() # Assume failure status of 1 -- success cases # will have to override this $exitstatus = 1 # If convert_boolean_return is enabled, the block's return value # gets precedence in determining our exit status if ($interpolatedexitcode -and $chefscriptresult -ne $null -and $chefscriptresult.gettype().name -eq 'boolean') { $exitstatus = [int32](!$chefscriptresult) } elseif ($lastcmdlet) { # Otherwise, a successful cmdlet execution defines the status $exitstatus = 0 } elseif ( $LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0 ) { # If the cmdlet status is failed, allow the Win32 status # in $LASTEXITCODE to define exit status. This handles the case # where no cmdlets, only Win32 processes have run since $? # will be set to $false whenever a Win32 process returns a non-zero # status. $exitstatus = $LASTEXITCODE } # Print STDOUT for the script execution Write-Output $chefscriptresult # If this script is launched with -File, the process exit # status of PowerShell.exe will be $exitstatus. If it was # launched with -Command, it will be 0 if $exitstatus was 0, # 1 (i.e. failed) otherwise. exit $exitstatus EOH end end end end chef-12.14.60/lib/chef/provider/reboot.rb000066400000000000000000000041221276456504500200130ustar00rootroot00000000000000# # Author:: Chris Doherty ) # Copyright:: Copyright 2014-2016, Chef, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/provider" class Chef class Provider class Reboot < Chef::Provider provides :reboot def whyrun_supported? true end def load_current_resource @current_resource ||= Chef::Resource::Reboot.new(@new_resource.name) @current_resource.reason(@new_resource.reason) @current_resource.delay_mins(@new_resource.delay_mins) @current_resource end def request_reboot node.run_context.request_reboot( :delay_mins => @new_resource.delay_mins, :reason => @new_resource.reason, :timestamp => Time.now, :requested_by => @new_resource.name ) end def action_request_reboot converge_by("request a system reboot to occur if the run succeeds") do Chef::Log.warn "Reboot requested:'#{@new_resource.name}'" request_reboot end end def action_reboot_now converge_by("rebooting the system immediately") do Chef::Log.warn "Rebooting system immediately, requested by '#{@new_resource.name}'" request_reboot throw :end_client_run_early end end def action_cancel converge_by("cancel any existing end-of-run reboot request") do Chef::Log.warn "Reboot canceled: '#{@new_resource.name}'" node.run_context.cancel_reboot end end end end end chef-12.14.60/lib/chef/provider/registry_key.rb000066400000000000000000000134451276456504500212510ustar00rootroot00000000000000# # Author:: Prajakta Purohit () # Author:: Lamont Granquist () # # Copyright:: Copyright 2011-2016, Chef Software Inc. # # 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/log" require "chef/resource/file" require "chef/mixin/checksum" require "chef/provider" require "etc" require "fileutils" require "chef/scan_access_control" require "chef/win32/registry" class Chef class Provider class RegistryKey < Chef::Provider provides :registry_key include Chef::Mixin::Checksum def whyrun_supported? true end def running_on_windows! unless Chef::Platform.windows? raise Chef::Exceptions::Win32NotWindows, "Attempt to manipulate the windows registry on a non-windows node" end end def load_current_resource running_on_windows! @current_resource ||= Chef::Resource::RegistryKey.new(@new_resource.key, run_context) @current_resource.key(@new_resource.key) @current_resource.architecture(@new_resource.architecture) @current_resource.recursive(@new_resource.recursive) if registry.key_exists?(@new_resource.key) @current_resource.values(registry.get_values(@new_resource.key)) end values_to_hash(@current_resource.unscrubbed_values) @current_resource end def registry @registry ||= Chef::Win32::Registry.new(@run_context, @new_resource.architecture) end def values_to_hash(values) if values @name_hash = Hash[values.map { |val| [val[:name].downcase, val] }] else @name_hash = {} end end def define_resource_requirements requirements.assert(:create, :create_if_missing, :delete, :delete_key) do |a| a.assertion { registry.hive_exists?(@new_resource.key) } a.failure_message(Chef::Exceptions::Win32RegHiveMissing, "Hive #{@new_resource.key.split("\\").shift} does not exist") end requirements.assert(:create) do |a| a.assertion { registry.key_exists?(@new_resource.key) } a.whyrun("Key #{@new_resource.key} does not exist. Unless it would have been created before, attempt to modify its values would fail.") end requirements.assert(:create, :create_if_missing) do |a| #If keys missing in the path and recursive == false a.assertion { !registry.keys_missing?(@current_resource.key) || @new_resource.recursive } a.failure_message(Chef::Exceptions::Win32RegNoRecursive, "Intermediate keys missing but recursive is set to false") a.whyrun("Intermediate keys in #{@new_resource.key} do not exist. Unless they would have been created earlier, attempt to modify them would fail.") end requirements.assert(:delete_key) do |a| #If key to be deleted has subkeys but recurssive == false a.assertion { !registry.key_exists?(@new_resource.key) || !registry.has_subkeys?(@new_resource.key) || @new_resource.recursive } a.failure_message(Chef::Exceptions::Win32RegNoRecursive, "#{@new_resource.key} has subkeys but recursive is set to false.") a.whyrun("#{@current_resource.key} has subkeys, but recursive is set to false. attempt to delete would fails unless subkeys were deleted prior to this action.") end end def action_create unless registry.key_exists?(@current_resource.key) converge_by("create key #{@new_resource.key}") do registry.create_key(@new_resource.key, @new_resource.recursive) end end @new_resource.unscrubbed_values.each do |value| if @name_hash.has_key?(value[:name].downcase) current_value = @name_hash[value[:name].downcase] unless current_value[:type] == value[:type] && current_value[:data] == value[:data] converge_by("set value #{value}") do registry.set_value(@new_resource.key, value) end end else converge_by("set value #{value}") do registry.set_value(@new_resource.key, value) end end end end def action_create_if_missing unless registry.key_exists?(@new_resource.key) converge_by("create key #{@new_resource.key}") do registry.create_key(@new_resource.key, @new_resource.recursive) end end @new_resource.unscrubbed_values.each do |value| unless @name_hash.has_key?(value[:name].downcase) converge_by("create value #{value}") do registry.set_value(@new_resource.key, value) end end end end def action_delete if registry.key_exists?(@new_resource.key) @new_resource.unscrubbed_values.each do |value| if @name_hash.has_key?(value[:name].downcase) converge_by("delete value #{value}") do registry.delete_value(@new_resource.key, value) end end end end end def action_delete_key if registry.key_exists?(@new_resource.key) converge_by("delete key #{@new_resource.key}") do registry.delete_key(@new_resource.key, @new_resource.recursive) end end end end end end chef-12.14.60/lib/chef/provider/remote_directory.rb000066400000000000000000000232111276456504500221000ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/directory" require "chef/resource/file" require "chef/resource/directory" require "chef/resource/cookbook_file" require "chef/mixin/file_class" require "chef/platform/query_helpers" require "chef/util/path_helper" require "chef/deprecation/warnings" require "chef/deprecation/provider/remote_directory" require "forwardable" class Chef class Provider class RemoteDirectory < Chef::Provider::Directory extend Forwardable include Chef::Mixin::FileClass provides :remote_directory def_delegators :@new_resource, :purge, :path, :source, :cookbook, :cookbook_name def_delegators :@new_resource, :files_rights, :files_mode, :files_group, :files_owner, :files_backup def_delegators :@new_resource, :rights, :mode, :group, :owner # The overwrite property on the resource. Delegates to new_resource but can be mutated. # # @return [Boolean] if we are overwriting # def overwrite? @overwrite = new_resource.overwrite if @overwrite.nil? !!@overwrite end attr_accessor :managed_files # Hash containing keys of the paths for all the files that we sync, plus all their # parent directories. # # @return [Set] Ruby Set of the files that we manage # def managed_files @managed_files ||= Set.new end # Handle action :create. # def action_create super # Transfer files files_to_transfer.each do |cookbook_file_relative_path| create_cookbook_file(cookbook_file_relative_path) # parent directories and file being transferred need to not be removed in the purge add_managed_file(cookbook_file_relative_path) end purge_unmanaged_files end # Handle action :create_if_missing. # def action_create_if_missing # if this action is called, ignore the existing overwrite flag @overwrite = false action_create end private # Add a file and its parent directories to the managed_files Hash. # # @param [String] cookbook_file_relative_path relative path to the file # @api private # def add_managed_file(cookbook_file_relative_path) if purge Pathname.new(Chef::Util::PathHelper.cleanpath(::File.join(path, cookbook_file_relative_path))).descend do |d| managed_files.add(d.to_s) end end end # Remove all files not in the managed_files Set. # # @api private # def purge_unmanaged_files if purge Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob_dir(path), "**", "*"), ::File::FNM_DOTMATCH).sort!.reverse!.each do |file| # skip '.' and '..' next if [".", ".."].include?(Pathname.new(file).basename().to_s) # Clean the path. This is required because of the ::File.join file = Chef::Util::PathHelper.cleanpath(file) # Skip files that we've sync'd and their parent dirs next if managed_files.include?(file) if ::File.directory?(file) if !Chef::Platform.windows? && file_class.symlink?(file.dup) # Unix treats dir symlinks as files purge_file(file) else # Unix dirs are dirs, Windows dirs and dir symlinks are dirs purge_directory(file) end else purge_file(file) end end end end # Use a Chef directory sub-resource to remove a directory. # # @param [String] dir The path of the directory to remove # @api private # def purge_directory(dir) res = Chef::Resource::Directory.new(dir, run_context) res.run_action(:delete) new_resource.updated_by_last_action(true) if res.updated? end # Use a Chef file sub-resource to remove a file. # # @param [String] file The path of the file to remove # @api private # def purge_file(file) res = Chef::Resource::File.new(file, run_context) res.run_action(:delete) new_resource.updated_by_last_action(true) if res.updated? end # Get the files to tranfer. This returns files in lexicographical sort order. # # FIXME: it should do breadth-first, see CHEF-5080 (please use a performant sort) # # @return Array The list of files to transfer # @api private # def files_to_transfer cookbook = run_context.cookbook_collection[resource_cookbook] files = cookbook.relative_filenames_in_preferred_directory(node, :files, source) files.sort_by! { |x| x.count(::File::SEPARATOR) } end # Either the explicit cookbook that the user sets on the resource, or the implicit # cookbook_name that the resource was declared in. # # @return [String] Cookbook to get file from. # @api private # def resource_cookbook cookbook || cookbook_name end # If we are overwriting, then cookbook_file sub-resources should all be action :create, # otherwise they should be :create_if_missing # # @return [Symbol] Action to take on cookbook_file sub-resources # @api private # def action_for_cookbook_file overwrite? ? :create : :create_if_missing end # This creates and uses a cookbook_file resource to sync a single file from the cookbook. # # @param [String] cookbook_file_relative_path The relative path to the cookbook file # @api private # def create_cookbook_file(cookbook_file_relative_path) full_path = ::File.join(path, cookbook_file_relative_path) ensure_directory_exists(::File.dirname(full_path)) res = cookbook_file_resource(full_path, cookbook_file_relative_path) res.run_action(action_for_cookbook_file) new_resource.updated_by_last_action(true) if res.updated? end # This creates the cookbook_file resource for use by create_cookbook_file. # # @param [String] target_path Path on the system to create # @param [String] relative_source_path Relative path in the cookbook to the base source # @return [Chef::Resource::CookbookFile] The built cookbook_file resource # @api private # def cookbook_file_resource(target_path, relative_source_path) res = Chef::Resource::CookbookFile.new(target_path, run_context) res.cookbook_name = resource_cookbook # Set the sensitivity level res.sensitive(new_resource.sensitive) res.source(::File.join(source, relative_source_path)) if Chef::Platform.windows? && files_rights files_rights.each_pair do |permission, *args| res.rights(permission, *args) end end res.mode(files_mode) if files_mode res.group(files_group) if files_group res.owner(files_owner) if files_owner res.backup(files_backup) if files_backup res end # This creates and uses a directory resource to create a directory if it is needed. # # @param [String] dir The path to the directory to create. # @api private # def ensure_directory_exists(dir) # doing the check here and skipping the resource should be more performant unless ::File.directory?(dir) res = directory_resource(dir) res.run_action(:create) new_resource.updated_by_last_action(true) if res.updated? end end # This creates the directory resource for ensure_directory_exists. # # @param [String] dir Directory path on the system # @return [Chef::Resource::Directory] The built directory resource # @api private # def directory_resource(dir) res = Chef::Resource::Directory.new(dir, run_context) res.cookbook_name = resource_cookbook if Chef::Platform.windows? && rights # rights are only meant to be applied to the toppest-level directory; # Windows will handle inheritance. if dir == path rights.each do |r| r = r.dup # do not update the new_resource permissions = r.delete(:permissions) principals = r.delete(:principals) res.rights(permissions, principals, r) end end end res.mode(mode) if mode res.group(group) if group res.owner(owner) if owner res.recursive(true) res end # # Add back deprecated methods and aliases that are internally unused and should be removed in Chef-13 # extend Chef::Deprecation::Warnings include Chef::Deprecation::Provider::RemoteDirectory add_deprecation_warnings_for(Chef::Deprecation::Provider::RemoteDirectory.instance_methods) alias_method :resource_for_directory, :directory_resource add_deprecation_warnings_for([:resource_for_directory]) end end end chef-12.14.60/lib/chef/provider/remote_file.rb000066400000000000000000000031241276456504500210140ustar00rootroot00000000000000# # Author:: Jesse Campbell () # Author:: Adam Jacob () # 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/file" require "chef/deprecation/provider/remote_file" require "chef/deprecation/warnings" class Chef class Provider class RemoteFile < Chef::Provider::File provides :remote_file extend Chef::Deprecation::Warnings include Chef::Deprecation::Provider::RemoteFile add_deprecation_warnings_for(Chef::Deprecation::Provider::RemoteFile.instance_methods) def initialize(new_resource, run_context) @content_class = Chef::Provider::RemoteFile::Content super end def load_current_resource @current_resource = Chef::Resource::RemoteFile.new(@new_resource.name) super end private def managing_content? return true if @new_resource.checksum return true if !@new_resource.source.nil? && @action != :create_if_missing false end end end end chef-12.14.60/lib/chef/provider/remote_file/000077500000000000000000000000001276456504500204675ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/remote_file/cache_control_data.rb000066400000000000000000000154461276456504500246220ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Jesse Campbell () # Author:: Lamont Granquist () # Copyright:: Copyright 2013-2016, Jesse Campbell # Copyright:: Copyright 2013-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 "stringio" require "chef/file_cache" require "chef/json_compat" require "chef/digester" require "chef/exceptions" class Chef class Provider class RemoteFile # == CacheControlData # Implements per-uri storage of cache control data for a remote resource # along with a sanity check checksum of the file in question. # Provider::RemoteFile protocol implementation classes can use this # information to avoid re-fetching files when the current copy is up to # date. The way this information is used is protocol-dependent. For HTTP, # this information is sent to the origin server via headers to make a # conditional GET request. # # == API # The general shape of the API is active-record-the-pattern-like. New # instances should be instantiated via # `CacheControlData.load_and_validate`, which will do a find-or-create # operation and then sanity check the data against the checksum of the # current copy of the file. If there is no data or the sanity check # fails, the `etag` and `mtime` attributes will be set to nil; otherwise # they are populated with the previously saved values. # # After fetching a file, the CacheControlData instance should be updated # with new etag, mtime and checksum values in whatever format is # preferred by the protocol used. Then call #save to save the data to disk. class CacheControlData def self.load_and_validate(uri, current_copy_checksum) ccdata = new(uri) ccdata.load ccdata.validate!(current_copy_checksum) ccdata end # Entity Tag of the resource. HTTP-specific. See also: # http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.2 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19 attr_accessor :etag # Last modified time of the remote resource. Different protocols will # use different types for this field (e.g., string representation of a # specific date format, integer, etc.) For HTTP-specific references, # see: # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.1 # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25 attr_accessor :mtime # SHA2-256 Hash of the file as last fetched. attr_accessor :checksum # URI of the resource as a String. This is the "primary key" used for # storage and retrieval. attr_reader :uri def initialize(uri) uri = uri.dup uri.password = "XXXX" unless uri.userinfo.nil? @uri = uri.to_s end def load if previous_cc_data = load_data apply(previous_cc_data) self else false end end def validate!(current_copy_checksum) if current_copy_checksum.nil? || checksum != current_copy_checksum reset! false else true end end # Saves the data to disk using Chef::FileCache. The filename is a # sanitized version of the URI with a MD5 of the same URI appended (to # avoid collisions between different URIs having the same sanitized # form). def save Chef::FileCache.store("remote_file/#{sanitized_cache_file_basename}", json_data) end # :nodoc: # JSON representation of this object for storage. def json_data Chef::JSONCompat.to_json(hash_data) end private def hash_data as_hash = {} as_hash["etag"] = etag as_hash["mtime"] = mtime as_hash["checksum"] = checksum as_hash end def reset! @etag, @mtime = nil, nil end def apply(previous_cc_data) @etag = previous_cc_data["etag"] @mtime = previous_cc_data["mtime"] @checksum = previous_cc_data["checksum"] end def load_data Chef::JSONCompat.parse(load_json_data) rescue Chef::Exceptions::FileNotFound, Chef::Exceptions::JSON::ParseError false end def load_json_data path = sanitized_cache_file_path(sanitized_cache_file_basename) if Chef::FileCache.has_key?(path) Chef::FileCache.load(path) else old_path = sanitized_cache_file_path(sanitized_cache_file_basename_md5) if Chef::FileCache.has_key?(old_path) # We found an old cache control data file. We started using sha256 instead of md5 # to name these. Upgrade the file to the new name. Chef::Log.debug("Found old cache control data file at #{old_path}. Moving to #{path}.") Chef::FileCache.load(old_path).tap do |data| Chef::FileCache.store(path, data) Chef::FileCache.delete(old_path) end else raise Chef::Exceptions::FileNotFound end end end def sanitized_cache_file_path(basename) "remote_file/#{basename}" end def scrubbed_uri # Scrub and truncate in accordance with the goals of keeping the name # human-readable but within the bounds of local file system # path length limits uri.gsub(/\W/, "_")[0..63] end def sanitized_cache_file_basename uri_sha2 = Chef::Digester.instance.generate_checksum(StringIO.new(uri)) cache_file_basename(uri_sha2[0, 32]) end def sanitized_cache_file_basename_md5 # Old way of creating the file basename uri_md5 = Chef::Digester.instance.generate_md5_checksum(StringIO.new(uri)) cache_file_basename(uri_md5) end def cache_file_basename(checksum) "#{scrubbed_uri}-#{checksum}.json" end end end end end chef-12.14.60/lib/chef/provider/remote_file/content.rb000066400000000000000000000055021276456504500224700ustar00rootroot00000000000000# # Author:: Jesse Campbell () # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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 "tempfile" require "chef/file_content_management/content_base" require "chef/mixin/uris" class Chef class Provider class RemoteFile class Content < Chef::FileContentManagement::ContentBase private include Chef::Mixin::Uris def file_for_provider Chef::Log.debug("#{@new_resource} checking for changes") if current_resource_matches_target_checksum? Chef::Log.debug("#{@new_resource} checksum matches target checksum (#{@new_resource.checksum}) - not updating") else sources = @new_resource.source raw_file = try_multiple_sources(sources) end raw_file end # Given an array of source uris, iterate through them until one does not fail def try_multiple_sources(sources) sources = sources.dup source = sources.shift begin uri = if Chef::Provider::RemoteFile::Fetcher.network_share?(source) source else as_uri(source) end raw_file = grab_file_from_uri(uri) rescue SocketError, Errno::ECONNREFUSED, Errno::ENOENT, Errno::EACCES, Timeout::Error, Net::HTTPServerException, Net::HTTPFatalError, Net::FTPError => e Chef::Log.warn("#{@new_resource} cannot be downloaded from #{source}: #{e}") if source = sources.shift Chef::Log.info("#{@new_resource} trying to download from another mirror") retry else raise e end end raw_file end # Given a source uri, return a Tempfile, or a File that acts like a Tempfile (close! method) def grab_file_from_uri(uri) Chef::Provider::RemoteFile::Fetcher.for_resource(uri, @new_resource, @current_resource).fetch end def current_resource_matches_target_checksum? @new_resource.checksum && @current_resource.checksum && @current_resource.checksum =~ /^#{Regexp.escape(@new_resource.checksum)}/ end end end end end chef-12.14.60/lib/chef/provider/remote_file/fetcher.rb000066400000000000000000000036401276456504500224370ustar00rootroot00000000000000# # Author:: Jesse Campbell () # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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 Provider class RemoteFile class Fetcher def self.for_resource(uri, new_resource, current_resource) if network_share?(uri) Chef::Provider::RemoteFile::NetworkFile.new(uri, new_resource, current_resource) else case uri.scheme when "http", "https" Chef::Provider::RemoteFile::HTTP.new(uri, new_resource, current_resource) when "ftp" Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) when "sftp" Chef::Provider::RemoteFile::SFTP.new(uri, new_resource, current_resource) when "file" Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource) else raise ArgumentError, "Invalid uri, Only http(s), ftp, and file are currently supported" end end end # Windows network share: \\computername\share\file def self.network_share?(source) case source when String !!(%r{\A\\\\[A-Za-z0-9+\-\.]+} =~ source) else false end end end end end end chef-12.14.60/lib/chef/provider/remote_file/ftp.rb000066400000000000000000000102441276456504500216060ustar00rootroot00000000000000# # Author:: Jesse Campbell () # Copyright:: Copyright 2013-2016, Jesse Campbell # 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 "tempfile" require "net/ftp" require "chef/provider/remote_file" require "chef/file_content_management/tempfile" class Chef class Provider class RemoteFile class FTP attr_reader :uri attr_reader :new_resource attr_reader :current_resource def initialize(uri, new_resource, current_resource) @uri = uri @new_resource = new_resource @current_resource = current_resource validate_typecode! validate_path! end def hostname @uri.host end def port @uri.port end def use_passive_mode? ! new_resource.ftp_active_mode end def typecode uri.typecode end def user if uri.userinfo URI.unescape(uri.user) else "anonymous" end end def pass if uri.userinfo URI.unescape(uri.password) else nil end end def directories parse_path if @directories.nil? @directories end def filename parse_path if @filename.nil? @filename end def fetch with_connection do get end end def ftp @ftp ||= Net::FTP.new end private def with_proxy_env saved_socks_env = ENV["SOCKS_SERVER"] ENV["SOCKS_SERVER"] = proxy_uri(@uri).to_s yield ensure ENV["SOCKS_SERVER"] = saved_socks_env end def with_connection with_proxy_env do connect yield end ensure disconnect end def validate_typecode! # Only support ascii and binary types if typecode && /\A[ai]\z/ !~ typecode raise ArgumentError, "invalid typecode: #{typecode.inspect}" end end def validate_path! parse_path end def connect # The access sequence is defined by RFC 1738 ftp.connect(hostname, port) ftp.passive = use_passive_mode? ftp.login(user, pass) directories.each do |cwd| ftp.voidcmd("CWD #{cwd}") end end def disconnect ftp.close end # Fetches using Net::FTP, returns a Tempfile with the content def get tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile if typecode ftp.voidcmd("TYPE #{typecode.upcase}") end ftp.getbinaryfile(filename, tempfile.path) tempfile.close if tempfile tempfile end def proxy_uri(uri) Chef::Config.proxy_uri("ftp", hostname, port) end def parse_path path = uri.path.sub(%r{\A/}, "%2F") # re-encode the beginning slash because uri library decodes it. directories = path.split(%r{/}, -1) directories.each do |d| d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") } end unless filename = directories.pop raise ArgumentError, "no filename: #{path.inspect}" end if filename.length == 0 || filename.end_with?( "/" ) raise ArgumentError, "no filename: #{path.inspect}" end @directories, @filename = directories, filename end end end end end chef-12.14.60/lib/chef/provider/remote_file/http.rb000066400000000000000000000104251276456504500217750ustar00rootroot00000000000000# # Author:: Jesse Campbell () # Author:: Lamont Granquist () # Copyright:: Copyright 2013-2016, Jesse Campbell # 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/http/simple" require "chef/digester" require "chef/provider/remote_file" require "chef/provider/remote_file/cache_control_data" class Chef class Provider class RemoteFile class HTTP attr_reader :uri attr_reader :new_resource attr_reader :current_resource # Parse the uri into instance variables def initialize(uri, new_resource, current_resource) @uri = uri @new_resource = new_resource @current_resource = current_resource end def events new_resource.events end def headers conditional_get_headers.merge(new_resource.headers) end def conditional_get_headers cache_control_headers = {} if (last_modified = cache_control_data.mtime) && want_mtime_cache_control? cache_control_headers["if-modified-since"] = last_modified end if (etag = cache_control_data.etag) && want_etag_cache_control? cache_control_headers["if-none-match"] = etag end Chef::Log.debug("Cache control headers: #{cache_control_headers.inspect}") cache_control_headers end def fetch http = Chef::HTTP::Simple.new(uri, http_client_opts) if want_progress? tempfile = http.streaming_request_with_progress(uri, headers) do |size, total| events.resource_update_progress(new_resource, size, total, progress_interval) end else tempfile = http.streaming_request(uri, headers) end if tempfile update_cache_control_data(tempfile, http.last_response) tempfile.close end tempfile end private def update_cache_control_data(tempfile, response) cache_control_data.checksum = Chef::Digester.checksum_for_file(tempfile.path) cache_control_data.mtime = last_modified_time_from(response) cache_control_data.etag = etag_from(response) cache_control_data.save end def cache_control_data @cache_control_data ||= CacheControlData.load_and_validate(uri, current_resource.checksum) end def want_progress? events.formatter? && (Chef::Config[:show_download_progress] || !!new_resource.show_progress) end def progress_interval Chef::Config[:download_progress_interval] end def want_mtime_cache_control? new_resource.use_last_modified end def want_etag_cache_control? new_resource.use_etag end def last_modified_time_from(response) response["last_modified"] || response["date"] end def etag_from(response) response["etag"] end def http_client_opts opts = {} # CHEF-3140 # 1. If it's already compressed, trying to compress it more will # probably be counter-productive. # 2. Some servers are misconfigured so that you GET $URL/file.tgz but # they respond with content type of tar and content encoding of gzip, # which tricks Chef::REST into decompressing the response body. In this # case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz, # which is not what you wanted. if uri.to_s =~ /gz$/ Chef::Log.debug("Turning gzip compression off due to filename ending in gz") opts[:disable_gzip] = true end opts end end end end end chef-12.14.60/lib/chef/provider/remote_file/local_file.rb000066400000000000000000000034361276456504500231130ustar00rootroot00000000000000# # Author:: Jesse Campbell () # Copyright:: Copyright 2013-2016, Jesse Campbell # 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 "tempfile" require "chef/provider/remote_file" class Chef class Provider class RemoteFile class LocalFile attr_reader :uri attr_reader :new_resource def initialize(uri, new_resource, current_resource) @new_resource = new_resource @uri = uri end # CHEF-4472: Remove the leading slash from windows paths that we receive from a file:// URI def fix_windows_path(path) path.gsub(/^\/([a-zA-Z]:)/, '\1') end def source_path @source_path ||= begin path = URI.unescape(uri.path) Chef::Platform.windows? ? fix_windows_path(path) : path end end # Fetches the file at uri, returning a Tempfile-like File handle def fetch tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile Chef::Log.debug("#{new_resource} staging #{source_path} to #{tempfile.path}") FileUtils.cp(source_path, tempfile.path) tempfile.close if tempfile tempfile end end end end end chef-12.14.60/lib/chef/provider/remote_file/network_file.rb000066400000000000000000000026511276456504500235100ustar00rootroot00000000000000# # Author:: Jesse Campbell () # Copyright:: Copyright 2013-2016, Jesse Campbell # 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 "tempfile" require "chef/provider/remote_file" class Chef class Provider class RemoteFile class NetworkFile attr_reader :new_resource def initialize(source, new_resource, current_resource) @new_resource = new_resource @source = source end # Fetches the file on a network share, returning a Tempfile-like File handle # windows only def fetch tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile Chef::Log.debug("#{new_resource} staging #{@source} to #{tempfile.path}") FileUtils.cp(@source, tempfile.path) tempfile.close if tempfile tempfile end end end end end chef-12.14.60/lib/chef/provider/remote_file/sftp.rb000066400000000000000000000055451276456504500220010ustar00rootroot00000000000000# # Author:: John Kerry () # Copyright:: Copyright 2013-2016, John Kerry # 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 "tempfile" require "net/sftp" require "chef/provider/remote_file" require "chef/file_content_management/tempfile" class Chef class Provider class RemoteFile class SFTP attr_reader :uri attr_reader :new_resource attr_reader :current_resource def initialize(uri, new_resource, current_resource) @uri = uri @new_resource = new_resource @current_resource = current_resource validate_path! validate_userinfo! end def hostname @uri.host end def port @uri.port end def user URI.unescape(uri.user) end def fetch get end private def sftp host = port ? "#{hostname}:#{port}" : hostname @sftp ||= Net::SFTP.start(host, user, :password => pass) end def pass URI.unescape(uri.password) end def validate_path! path = uri.path.sub(%r{\A/}, "%2F") # re-encode the beginning slash because uri library decodes it. directories = path.split(%r{/}, -1) directories.each do |d| d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") } end unless filename = directories.pop raise ArgumentError, "no filename: #{path.inspect}" end if filename.length == 0 || filename.end_with?( "/" ) raise ArgumentError, "no filename: #{path.inspect}" end end def validate_userinfo! if uri.userinfo unless uri.user raise ArgumentError, "no user name provided in the sftp URI" end unless uri.password raise ArgumentError, "no password provided in the sftp URI" end else raise ArgumentError, "no userinfo provided in the sftp URI" end end def get tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile sftp.download!(uri.path, tempfile.path) tempfile.close if tempfile tempfile end end end end end chef-12.14.60/lib/chef/provider/resource_update.rb000066400000000000000000000025261276456504500217200ustar00rootroot00000000000000 class Chef class Provider # { # "run_id" : "1000", # "resource" : { # "type" : "file", # "name" : "/etc/passwd", # "start_time" : "2012-01-09T08:15:30-05:00", # "end_time" : "2012-01-09T08:15:30-05:00", # "status" : "modified", # "initial_state" : "exists", # "final_state" : "modified", # "before" : { # "group" : "root", # "owner" : "root", # "checksum" : "xyz" # }, # "after" : { # "group" : "root", # "owner" : "root", # "checksum" : "abc" # }, # "delta" : "escaped delta goes here" # }, # "event_data" : "" # } class ResourceUpdate attr_accessor :type attr_accessor :name attr_accessor :duration #ms attr_accessor :status attr_accessor :initial_state attr_accessor :final_state attr_accessor :initial_properties attr_accessor :final_properties attr_accessor :event_data # e.g., a diff. def initial_state_from_resource(resource) @initial_properties = resource.to_hash end def updated_state_from_resource(resource) @final_properties = resource.to_hash end end end end chef-12.14.60/lib/chef/provider/route.rb000066400000000000000000000165601276456504500176700ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org), Jesse Nelson (spheromak@gmail.com) # Copyright:: Copyright 2009-2016, Bryan McLellan # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/mixin/command" require "chef/provider" require "ipaddr" class Chef::Provider::Route < Chef::Provider include Chef::Mixin::Command provides :route attr_accessor :is_running MASK = { "0.0.0.0" => "0", "128.0.0.0" => "1", "192.0.0.0" => "2", "224.0.0.0" => "3", "240.0.0.0" => "4", "248.0.0.0" => "5", "252.0.0.0" => "6", "254.0.0.0" => "7", "255.0.0.0" => "8", "255.128.0.0" => "9", "255.192.0.0" => "10", "255.224.0.0" => "11", "255.240.0.0" => "12", "255.248.0.0" => "13", "255.252.0.0" => "14", "255.254.0.0" => "15", "255.255.0.0" => "16", "255.255.128.0" => "17", "255.255.192.0" => "18", "255.255.224.0" => "19", "255.255.240.0" => "20", "255.255.248.0" => "21", "255.255.252.0" => "22", "255.255.254.0" => "23", "255.255.255.0" => "24", "255.255.255.128" => "25", "255.255.255.192" => "26", "255.255.255.224" => "27", "255.255.255.240" => "28", "255.255.255.248" => "29", "255.255.255.252" => "30", "255.255.255.254" => "31", "255.255.255.255" => "32" } def hex2ip(hex_data) # Cleanup hex data hex_ip = hex_data.to_s.downcase.gsub(/[^0-9a-f]/, "") # Check hex data format (IP is a 32bit integer, so should be 8 chars long) return nil if hex_ip.length != hex_data.length || hex_ip.length != 8 # Extract octets from hex data octets = hex_ip.scan(/../).reverse.collect { |octet| [octet].pack("H2").unpack("C").first } # Validate IP ip = octets.join(".") begin IPAddr.new(ip, Socket::AF_INET).to_s rescue ArgumentError Chef::Log.debug("Invalid IP address data: hex=#{hex_ip}, ip=#{ip}") return nil end end def whyrun_supported? true end def load_current_resource self.is_running = false # cidr or quad dot mask if @new_resource.netmask new_ip = IPAddr.new("#{@new_resource.target}/#{@new_resource.netmask}") else new_ip = IPAddr.new(@new_resource.target) end # For linux, we use /proc/net/route file to read proc table info if node[:os] == "linux" route_file = ::File.open("/proc/net/route", "r") # Read all routes while (line = route_file.gets) # Get all the fields for a route iface, destination, gateway, flags, refcnt, use, metric, mask, mtu, window, irtt = line.split # Convert hex-encoded values to quad-dotted notation (e.g. 0064A8C0 => 192.168.100.0) destination = hex2ip(destination) gateway = hex2ip(gateway) mask = hex2ip(mask) # Skip formatting lines (header, etc) next unless destination && gateway && mask Chef::Log.debug("#{@new_resource} system has route: dest=#{destination} mask=#{mask} gw=#{gateway}") # check if what were trying to configure is already there # use an ipaddr object with ip/mask this way we can have # a new resource be in cidr format (i don't feel like # expanding bitmask by hand. # running_ip = IPAddr.new("#{destination}/#{mask}") Chef::Log.debug("#{@new_resource} new ip: #{new_ip.inspect} running ip: #{running_ip.inspect}") self.is_running = true if running_ip == new_ip && gateway == @new_resource.gateway end route_file.close end end def action_add # check to see if load_current_resource found the route if is_running Chef::Log.debug("#{@new_resource} route already active - nothing to do") else command = generate_command(:add) converge_by ("run #{ command } to add route") do run_command( :command => command ) Chef::Log.info("#{@new_resource} added") end end #for now we always write the file (ugly but its what it is) generate_config end def action_delete if is_running command = generate_command(:delete) converge_by ("run #{ command } to delete route ") do run_command( :command => command ) Chef::Log.info("#{@new_resource} removed") end else Chef::Log.debug("#{@new_resource} route does not exist - nothing to do") end #for now we always write the file (ugly but its what it is) generate_config end def generate_config conf = Hash.new case node[:platform] when "centos", "redhat", "fedora" # walk the collection run_context.resource_collection.each do |resource| if resource.is_a? Chef::Resource::Route # default to eth0 if resource.device dev = resource.device else dev = "eth0" end conf[dev] = String.new if conf[dev].nil? case @action when :add conf[dev] << config_file_contents(:add, :target => resource.target, :netmask => resource.netmask, :gateway => resource.gateway) if resource.action == [:add] when :delete # need to do this for the case when the last route on an int # is removed conf[dev] << config_file_contents(:delete) end end end conf.each do |k, v| network_file_name = "/etc/sysconfig/network-scripts/route-#{k}" converge_by ("write route route.#{k}\n#{conf[k]} to #{ network_file_name }") do network_file = ::File.new(network_file_name, "w") network_file.puts(conf[k]) Chef::Log.debug("#{@new_resource} writing route.#{k}\n#{conf[k]}") network_file.close end end end end def generate_command(action) common_route_items = "" common_route_items << "/#{MASK[@new_resource.netmask.to_s]}" if @new_resource.netmask common_route_items << " via #{@new_resource.gateway} " if @new_resource.gateway case action when :add command = "ip route replace #{@new_resource.target}" command << common_route_items command << " dev #{@new_resource.device} " if @new_resource.device when :delete command = "ip route delete #{@new_resource.target}" command << common_route_items end return command end def config_file_contents(action, options = {}) content = "" case action when :add content << "#{options[:target]}" content << "/#{options[:netmask]}" if options[:netmask] content << " via #{options[:gateway]}" if options[:gateway] content << "\n" end return content end end chef-12.14.60/lib/chef/provider/ruby_block.rb000066400000000000000000000022271276456504500206600ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: AJ Christensen () # Copyright:: Copyright 2009-2016, Opscode # 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 Provider class RubyBlock < Chef::Provider provides :ruby_block def whyrun_supported? true end def load_current_resource true end def action_run converge_by("execute the ruby block #{@new_resource.name}") do @new_resource.block.call Chef::Log.info("#{@new_resource} called") end end alias :action_create :action_run end end end chef-12.14.60/lib/chef/provider/script.rb000066400000000000000000000043111276456504500200250ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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 "tempfile" require "chef/provider/execute" require "forwardable" class Chef class Provider class Script < Chef::Provider::Execute extend Forwardable provides :bash provides :csh provides :ksh provides :perl provides :python provides :ruby provides :script def_delegators :@new_resource, :interpreter, :flags attr_accessor :code def initialize(new_resource, run_context) super self.code = new_resource.code end def command "\"#{interpreter}\" #{flags} \"#{script_file.path}\"" end def load_current_resource super # @todo Chef-13: change this to an exception if code.nil? Chef::Log.warn "#{@new_resource}: No code attribute was given, resource does nothing, this behavior is deprecated and will be removed in Chef-13" end end def action_run script_file.puts(code) script_file.close set_owner_and_group super unlink_script_file end def set_owner_and_group # FileUtils itself implements a no-op if +user+ or +group+ are nil # You can prove this by running FileUtils.chown(nil,nil,'/tmp/file') # as an unprivileged user. FileUtils.chown(new_resource.user, new_resource.group, script_file.path) end def script_file @script_file ||= Tempfile.open("chef-script") end def unlink_script_file script_file && script_file.close! end end end end chef-12.14.60/lib/chef/provider/service.rb000066400000000000000000000201671276456504500201700ustar00rootroot00000000000000# # Author:: AJ Christensen () # Author:: Davide Cavalca () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/command" require "chef/provider" class Chef class Provider class Service < Chef::Provider include Chef::Mixin::Command def supports @supports ||= new_resource.supports.dup end def initialize(new_resource, run_context) super @enabled = nil end def whyrun_supported? true end def load_current_resource supports[:status] = false if supports[:status].nil? supports[:reload] = false if supports[:reload].nil? supports[:restart] = false if supports[:restart].nil? end # the new_resource#enabled and #running variables are not user input, but when we # do (e.g.) action_enable we want to set new_resource.enabled so that the comparison # between desired and current state produces the correct change in reporting. # XXX?: the #nil? check below will likely fail if this is a cloned resource or if # we just run multiple actions. def load_new_resource_state if @new_resource.enabled.nil? @new_resource.enabled(@current_resource.enabled) end if @new_resource.running.nil? @new_resource.running(@current_resource.running) end if @new_resource.masked.nil? @new_resource.masked(@current_resource.masked) end end # subclasses should override this if they do implement user services def user_services_requirements requirements.assert(:all_actions) do |a| a.assertion { @new_resource.user.nil? } a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support user services" end end def shared_resource_requirements user_services_requirements end def define_resource_requirements requirements.assert(:reload) do |a| a.assertion { supports[:reload] || @new_resource.reload_command } a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload" # if a service is not declared to support reload, that won't # typically change during the course of a run - so no whyrun # alternative here. end end def action_enable if @current_resource.enabled Chef::Log.debug("#{@new_resource} already enabled - nothing to do") else converge_by("enable service #{@new_resource}") do enable_service Chef::Log.info("#{@new_resource} enabled") end end load_new_resource_state @new_resource.enabled(true) end def action_disable if @current_resource.enabled converge_by("disable service #{@new_resource}") do disable_service Chef::Log.info("#{@new_resource} disabled") end else Chef::Log.debug("#{@new_resource} already disabled - nothing to do") end load_new_resource_state @new_resource.enabled(false) end def action_mask if @current_resource.masked Chef::Log.debug("#{@new_resource} already masked - nothing to do") else converge_by("mask service #{@new_resource}") do mask_service Chef::Log.info("#{@new_resource} masked") end end load_new_resource_state @new_resource.masked(true) end def action_unmask if @current_resource.masked converge_by("unmask service #{@new_resource}") do unmask_service Chef::Log.info("#{@new_resource} unmasked") end else Chef::Log.debug("#{@new_resource} already unmasked - nothing to do") end load_new_resource_state @new_resource.masked(false) end def action_start unless @current_resource.running converge_by("start service #{@new_resource}") do start_service Chef::Log.info("#{@new_resource} started") end else Chef::Log.debug("#{@new_resource} already running - nothing to do") end load_new_resource_state @new_resource.running(true) end def action_stop if @current_resource.running converge_by("stop service #{@new_resource}") do stop_service Chef::Log.info("#{@new_resource} stopped") end else Chef::Log.debug("#{@new_resource} already stopped - nothing to do") end load_new_resource_state @new_resource.running(false) end def action_restart converge_by("restart service #{@new_resource}") do restart_service Chef::Log.info("#{@new_resource} restarted") end load_new_resource_state @new_resource.running(true) end def action_reload if @current_resource.running converge_by("reload service #{@new_resource}") do reload_service Chef::Log.info("#{@new_resource} reloaded") end end load_new_resource_state end def enable_service raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :enable" end def disable_service raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :disable" end def mask_service raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :mask" end def unmask_service raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :unmask" end def start_service raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :start" end def stop_service raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :stop" end def restart_service raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :restart" end def reload_service raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload" end protected def default_init_command if @new_resource.init_command @new_resource.init_command elsif self.instance_variable_defined?(:@init_command) @init_command end end def custom_command_for_action?(action) method_name = "#{action}_command".to_sym @new_resource.respond_to?(method_name) && !!@new_resource.send(method_name) end module ServicePriorityInit # # Platform-specific versions # # # Linux # require "chef/chef_class" require "chef/provider/service/systemd" require "chef/provider/service/insserv" require "chef/provider/service/redhat" require "chef/provider/service/arch" require "chef/provider/service/gentoo" require "chef/provider/service/upstart" require "chef/provider/service/debian" require "chef/provider/service/invokercd" Chef.set_provider_priority_array :service, [ Systemd, Arch ], platform_family: "arch" Chef.set_provider_priority_array :service, [ Systemd, Gentoo ], platform_family: "gentoo" Chef.set_provider_priority_array :service, [ Systemd, Upstart, Insserv, Debian, Invokercd ], platform_family: "debian" Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w{rhel fedora suse} end end end end chef-12.14.60/lib/chef/provider/service/000077500000000000000000000000001276456504500176355ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/service/aix.rb000066400000000000000000000100241276456504500207400ustar00rootroot00000000000000# # Author:: kaustubh () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/service" class Chef class Provider class Service class Aix < Chef::Provider::Service attr_reader :status_load_success provides :service, os: "aix" def initialize(new_resource, run_context) super end def load_current_resource @current_resource = Chef::Resource::Service.new(@new_resource.name) @current_resource.service_name(@new_resource.service_name) @status_load_success = true @priority_success = true @is_resource_group = false determine_current_status! @current_resource end def whyrun_supported? true end def start_service if @is_resource_group shell_out!("startsrc -g #{@new_resource.service_name}") else shell_out!("startsrc -s #{@new_resource.service_name}") end end def stop_service if @is_resource_group shell_out!("stopsrc -g #{@new_resource.service_name}") else shell_out!("stopsrc -s #{@new_resource.service_name}") end end def restart_service stop_service start_service end def reload_service if @is_resource_group shell_out!("refresh -g #{@new_resource.service_name}") else shell_out!("refresh -s #{@new_resource.service_name}") end end def shared_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { @status_load_success } a.whyrun ["Service status not available. Assuming a prior action would have installed the service.", "Assuming status of not running."] end end def define_resource_requirements # FIXME? need reload from service.rb shared_resource_requirements end protected def determine_current_status! Chef::Log.debug "#{@new_resource} using lssrc to check the status" begin if is_resource_group? # Groups as a whole have no notion of whether they're running @current_resource.running false else service = shell_out!("lssrc -s #{@new_resource.service_name}").stdout if service.split(" ").last == "active" @current_resource.running true else @current_resource.running false end end Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}" # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed. # Temporarily catching different types of exceptions here until we get Shellout fixed. # TODO: Remove the line before one we get the ShellOut fix. rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError @status_load_success = false @current_resource.running false nil end end def is_resource_group? so = shell_out("lssrc -g #{@new_resource.service_name}") if so.exitstatus == 0 Chef::Log.debug("#{@new_resource.service_name} is a group") @is_resource_group = true end end end end end end chef-12.14.60/lib/chef/provider/service/aixinit.rb000066400000000000000000000076761276456504500216470ustar00rootroot00000000000000# # Author:: kaustubh () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/service/init" class Chef class Provider class Service class AixInit < Chef::Provider::Service::Init RC_D_SCRIPT_NAME = /\/etc\/rc.d\/rc2.d\/([SK])(\d\d|)/i def initialize(new_resource, run_context) super @init_command = "/etc/rc.d/init.d/#{@new_resource.service_name}" end def load_current_resource super @priority_success = true @rcd_status = nil set_current_resource_attributes @current_resource end def action_enable if @new_resource.priority.nil? priority_ok = true else priority_ok = @current_resource.priority == @new_resource.priority end if @current_resource.enabled && priority_ok Chef::Log.debug("#{@new_resource} already enabled - nothing to do") else converge_by("enable service #{@new_resource}") do enable_service Chef::Log.info("#{@new_resource} enabled") end end load_new_resource_state @new_resource.enabled(true) end def enable_service Dir.glob(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).each { |f| ::File.delete(f) } if @new_resource.priority.is_a? Integer create_symlink(2, "S", @new_resource.priority) elsif @new_resource.priority.is_a? Hash @new_resource.priority.each do |level, o| create_symlink(level, (o[0] == :start ? "S" : "K"), o[1]) end else create_symlink(2, "S", "") end end def disable_service Dir.glob(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).each { |f| ::File.delete(f) } if @new_resource.priority.is_a? Integer create_symlink(2, "K", 100 - @new_resource.priority) elsif @new_resource.priority.is_a? Hash @new_resource.priority.each do |level, o| create_symlink(level, "K", 100 - o[1]) if o[0] == :stop end else create_symlink(2, "K", "") end end def create_symlink(run_level, status, priority) ::File.symlink("/etc/rc.d/init.d/#{@new_resource.service_name}", "/etc/rc.d/rc#{run_level}.d/#{status}#{priority}#{@new_resource.service_name}") end def set_current_resource_attributes # assuming run level 2 for aix is_enabled = false files = Dir.glob(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]) priority = {} files.each do |file| if RC_D_SCRIPT_NAME =~ file priority[2] = [($1 == "S" ? :start : :stop), ($2.empty? ? "" : $2.to_i)] if $1 == "S" is_enabled = true end end end if is_enabled && files.length == 1 priority = priority[2][1] end @current_resource.enabled(is_enabled) @current_resource.priority(priority) end end end end end chef-12.14.60/lib/chef/provider/service/arch.rb000066400000000000000000000072001276456504500210760ustar00rootroot00000000000000# # Author:: Jan Zimmek () # Copyright:: Copyright 2010-2016, Jan Zimmek # 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/service/init" class Chef::Provider::Service::Arch < Chef::Provider::Service::Init provides :service, platform_family: "arch" def self.supports?(resource, action) Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:etc_rcd) end def initialize(new_resource, run_context) super @init_command = "/etc/rc.d/#{@new_resource.service_name}" end def load_current_resource raise Chef::Exceptions::Service, "Could not find /etc/rc.conf" unless ::File.exists?("/etc/rc.conf") raise Chef::Exceptions::Service, "No DAEMONS found in /etc/rc.conf" unless ::File.read("/etc/rc.conf") =~ /DAEMONS=\((.*)\)/m super @current_resource.enabled(daemons.include?(@current_resource.service_name)) @current_resource end # Get list of all daemons from the file '/etc/rc.conf'. # Mutiple lines and background form are supported. Example: # DAEMONS=(\ # foobar \ # @example \ # !net \ # ) def daemons entries = [] if ::File.read("/etc/rc.conf") =~ /DAEMONS=\((.*)\)/m entries += $1.gsub(/\\?[\r\n]/, " ").gsub(/# *[^ ]+/, " ").split(" ") if $1.length > 0 end yield(entries) if block_given? entries end # FIXME: Multiple entries of DAEMONS will cause very bad results :) def update_daemons(entries) content = ::File.read("/etc/rc.conf").gsub(/DAEMONS=\((.*)\)/m, "DAEMONS=(#{entries.join(' ')})") ::File.open("/etc/rc.conf", "w") do |f| f.write(content) end end def enable_service() new_daemons = [] entries = daemons if entries.include?(new_resource.service_name) || entries.include?("@#{new_resource.service_name}") # exists and already enabled (or already enabled as a background service) # new_daemons += entries else if entries.include?("!#{new_resource.service_name}") # exists but disabled entries.each do |daemon| if daemon == "!#{new_resource.service_name}" new_daemons << new_resource.service_name else new_daemons << daemon end end else # does not exist new_daemons += entries new_daemons << new_resource.service_name end update_daemons(new_daemons) end end def disable_service() new_daemons = [] entries = daemons if entries.include?("!#{new_resource.service_name}") # exists and disabled # new_daemons += entries else if entries.include?(new_resource.service_name) || entries.include?("@#{new_resource.service_name}") # exists but enabled (or enabled as a back-ground service) # FIXME: Does arch support !@foobar ? entries.each do |daemon| if [new_resource.service_name, "@#{new_resource.service_name}"].include?(daemon) new_daemons << "!#{new_resource.service_name}" else new_daemons << daemon end end end update_daemons(new_daemons) end end end chef-12.14.60/lib/chef/provider/service/debian.rb000066400000000000000000000171211276456504500214060ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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/service/init" class Chef class Provider class Service class Debian < Chef::Provider::Service::Init provides :service, platform_family: "debian" do |node| Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian) end UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i def self.supports?(resource, action) Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd) end def load_current_resource super @priority_success = true @rcd_status = nil current_resource.priority(get_priority) current_resource.enabled(service_currently_enabled?(current_resource.priority)) current_resource end def define_resource_requirements # do not call super here, inherit only shared_requirements shared_resource_requirements requirements.assert(:all_actions) do |a| update_rcd = "/usr/sbin/update-rc.d" a.assertion { ::File.exists? update_rcd } a.failure_message Chef::Exceptions::Service, "#{update_rcd} does not exist!" # no whyrun recovery - this is a base system component of debian # distros and must be present end requirements.assert(:all_actions) do |a| a.assertion { @priority_success } a.failure_message Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{current_resource.service_name} failed - #{@rcd_status.inspect}" # This can happen if the service is not yet installed,so we'll fake it. a.whyrun ["Unable to determine priority of service, assuming service would have been correctly installed earlier in the run.", "Assigning temporary priorities to continue.", "If this service is not properly installed prior to this point, this will fail."] do temp_priorities = { "6" => [:stop, "20"], "0" => [:stop, "20"], "1" => [:stop, "20"], "2" => [:start, "20"], "3" => [:start, "20"], "4" => [:start, "20"], "5" => [:start, "20"] } current_resource.priority(temp_priorities) end end end def get_priority priority = {} @rcd_status = popen4("/usr/sbin/update-rc.d -n -f #{current_resource.service_name} remove") do |pid, stdin, stdout, stderr| [stdout, stderr].each do |iop| iop.each_line do |line| if UPDATE_RC_D_PRIORITIES =~ line # priority[runlevel] = [ S|K, priority ] # S = Start, K = Kill # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot priority[$1] = [($2 == "S" ? :start : :stop), $3] end if line =~ UPDATE_RC_D_ENABLED_MATCHES enabled = true end end end end # Reduce existing priority back to an integer if appropriate, picking # runlevel 2 as a baseline if priority[2] && [2..5].all? { |runlevel| priority[runlevel] == priority[2] } priority = priority[2].last end unless @rcd_status.exitstatus == 0 @priority_success = false end priority end def service_currently_enabled?(priority) enabled = false priority.each do |runlevel, arguments| Chef::Log.debug("#{new_resource} runlevel #{runlevel}, action #{arguments[0]}, priority #{arguments[1]}") # if we are in a update-rc.d default startup runlevel && we start in this runlevel if %w{ 1 2 3 4 5 S }.include?(runlevel) && arguments[0] == :start enabled = true end end enabled end # Override method from parent to ensure priority is up-to-date def action_enable if new_resource.priority.nil? priority_ok = true else priority_ok = @current_resource.priority == new_resource.priority end if current_resource.enabled && priority_ok Chef::Log.debug("#{new_resource} already enabled - nothing to do") else converge_by("enable service #{new_resource}") do enable_service Chef::Log.info("#{new_resource} enabled") end end load_new_resource_state new_resource.enabled(true) end def enable_service if new_resource.priority.is_a? Integer shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults #{new_resource.priority} #{100 - new_resource.priority}") elsif new_resource.priority.is_a? Hash # we call the same command regardless of we're enabling or disabling # users passing a Hash are responsible for setting their own start priorities set_priority else # No priority, go with update-rc.d defaults shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults") end end def disable_service if new_resource.priority.is_a? Integer # Stop processes in reverse order of start using '100 - start_priority' shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} stop #{100 - new_resource.priority} 2 3 4 5 .") elsif new_resource.priority.is_a? Hash # we call the same command regardless of we're enabling or disabling # users passing a Hash are responsible for setting their own stop priorities set_priority else # no priority, using '100 - 20 (update-rc.d default)' to stop in reverse order of start shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} stop 80 2 3 4 5 .") end end def set_priority args = "" new_resource.priority.each do |level, o| action = o[0] priority = o[1] args += "#{action} #{priority} #{level} . " end shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} #{args}") end end end end end chef-12.14.60/lib/chef/provider/service/freebsd.rb000066400000000000000000000154511276456504500216020ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Copyright:: Copyright 2009-2016, Bryan McLellan # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/service" require "chef/provider/service/init" require "chef/mixin/command" class Chef class Provider class Service class Freebsd < Chef::Provider::Service::Init attr_reader :enabled_state_found provides :service, os: %w{freebsd netbsd} include Chef::Mixin::ShellOut def initialize(new_resource, run_context) super @enabled_state_found = false @init_command = nil if ::File.exist?("/etc/rc.d/#{new_resource.service_name}") @init_command = "/etc/rc.d/#{new_resource.service_name}" elsif ::File.exist?("/usr/local/etc/rc.d/#{new_resource.service_name}") @init_command = "/usr/local/etc/rc.d/#{new_resource.service_name}" end end def load_current_resource @current_resource = Chef::Resource::Service.new(new_resource.name) current_resource.service_name(new_resource.service_name) return current_resource unless init_command Chef::Log.debug("#{current_resource} found at #{init_command}") @status_load_success = true determine_current_status! # see Chef::Provider::Service::Simple determine_enabled_status! current_resource end def define_resource_requirements shared_resource_requirements requirements.assert(:start, :enable, :reload, :restart) do |a| a.assertion { init_command } a.failure_message Chef::Exceptions::Service, "#{new_resource}: unable to locate the rc.d script" a.whyrun("Assuming rc.d script will be installed by a previous action.") end requirements.assert(:all_actions) do |a| a.assertion { enabled_state_found } # for consistentcy with original behavior, this will not fail in non-whyrun mode; # rather it will silently set enabled state=>false a.whyrun "Unable to determine enabled/disabled state, assuming this will be correct for an actual run. Assuming disabled." end requirements.assert(:start, :enable, :reload, :restart) do |a| a.assertion { service_enable_variable_name != nil } a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{init_command} and rcvar" # No recovery in whyrun mode - the init file is present but not correct. end end def start_service if new_resource.start_command super else shell_out_with_systems_locale!("#{init_command} faststart") end end def stop_service if new_resource.stop_command super else shell_out_with_systems_locale!("#{init_command} faststop") end end def restart_service if new_resource.restart_command super elsif supports[:restart] shell_out_with_systems_locale!("#{init_command} fastrestart") else stop_service sleep 1 start_service end end def enable_service set_service_enable("YES") unless current_resource.enabled end def disable_service set_service_enable("NO") if current_resource.enabled end private def read_rc_conf ::File.open("/etc/rc.conf", "r") { |file| file.readlines } end def write_rc_conf(lines) ::File.open("/etc/rc.conf", "w") do |file| lines.each { |line| file.puts(line) } end end # The variable name used in /etc/rc.conf for enabling this service def service_enable_variable_name @service_enable_variable_name ||= begin # Look for name="foo" in the shell script @init_command. Use this for determining the variable name in /etc/rc.conf # corresponding to this service # For example: to enable the service mysql-server with the init command /usr/local/etc/rc.d/mysql-server, you need # to set mysql_enable="YES" in /etc/rc.conf$ if init_command ::File.open(init_command) do |rcscript| rcscript.each_line do |line| if line =~ /^name="?(\w+)"?/ return $1 + "_enable" end end end # some scripts support multiple instances through symlinks such as openvpn. # We should get the service name from rcvar. Chef::Log.debug("name=\"service\" not found at #{init_command}. falling back to rcvar") shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1] else # for why-run mode when the rcd_script is not there yet new_resource.service_name end end end def determine_enabled_status! var_name = service_enable_variable_name if ::File.exist?("/etc/rc.conf") && var_name read_rc_conf.each do |line| case line when /^#{Regexp.escape(var_name)}="(\w+)"/ enabled_state_found! if $1 =~ /^yes$/i current_resource.enabled true elsif $1 =~ /^(no|none)$/i current_resource.enabled false end end end end if current_resource.enabled.nil? Chef::Log.debug("#{new_resource.name} enable/disable state unknown") current_resource.enabled false end end def set_service_enable(value) lines = read_rc_conf # Remove line that set the old value lines.delete_if { |line| line =~ /^\#?\s*#{Regexp.escape(service_enable_variable_name)}=/ } # And append the line that sets the new value at the end lines << "#{service_enable_variable_name}=\"#{value}\"" write_rc_conf(lines) end def enabled_state_found! @enabled_state_found = true end end end end end chef-12.14.60/lib/chef/provider/service/gentoo.rb000066400000000000000000000046371276456504500214670ustar00rootroot00000000000000# # Author:: Lee Jensen () # Author:: AJ Christensen () # 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/service/init" require "chef/mixin/command" require "chef/util/path_helper" class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init provides :service, platform_family: "gentoo" def load_current_resource supports[:status] = true if supports[:status].nil? supports[:restart] = true if supports[:restart].nil? @found_script = false super @current_resource.enabled( Dir.glob("/etc/runlevels/**/#{Chef::Util::PathHelper.escape_glob_dir(@current_resource.service_name)}").any? do |file| @found_script = true exists = ::File.exists? file readable = ::File.readable? file Chef::Log.debug "#{@new_resource} exists: #{exists}, readable: #{readable}" exists && readable end ) Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}" @current_resource end def define_resource_requirements requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?("/sbin/rc-update") } a.failure_message Chef::Exceptions::Service, "/sbin/rc-update does not exist" # no whyrun recovery -t his is a core component whose presence is # unlikely to be affected by what we do in the course of a chef run end requirements.assert(:all_actions) do |a| a.assertion { @found_script } # No failure, just informational output from whyrun a.whyrun "Could not find service #{@new_resource.service_name} under any runlevel" end end def enable_service() shell_out!("/sbin/rc-update add #{@new_resource.service_name} default") end def disable_service() shell_out!("/sbin/rc-update del #{@new_resource.service_name} default") end end chef-12.14.60/lib/chef/provider/service/init.rb000066400000000000000000000057031276456504500211320ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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/service/simple" require "chef/mixin/command" require "chef/platform/service_helpers" class Chef class Provider class Service class Init < Chef::Provider::Service::Simple attr_accessor :init_command provides :service, os: "!windows" def self.supports?(resource, action) Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd) end def initialize(new_resource, run_context) super @init_command = "/etc/init.d/#{@new_resource.service_name}" end def define_resource_requirements # do not call super here, inherit only shared_requirements shared_resource_requirements requirements.assert(:start, :stop, :restart, :reload) do |a| a.assertion do custom_command_for_action?(action) || ::File.exist?(default_init_command) end a.failure_message(Chef::Exceptions::Service, "#{default_init_command} does not exist!") a.whyrun("Init script '#{default_init_command}' doesn't exist, assuming a prior action would have created it.") do # blindly assume that the service exists but is stopped in why run mode: @status_load_success = false end end end def start_service if @new_resource.start_command super else shell_out_with_systems_locale!("#{default_init_command} start") end end def stop_service if @new_resource.stop_command super else shell_out_with_systems_locale!("#{default_init_command} stop") end end def restart_service if @new_resource.restart_command super elsif supports[:restart] shell_out_with_systems_locale!("#{default_init_command} restart") else stop_service sleep 1 start_service end end def reload_service if @new_resource.reload_command super elsif supports[:reload] shell_out_with_systems_locale!("#{default_init_command} reload") end end end end end end chef-12.14.60/lib/chef/provider/service/insserv.rb000066400000000000000000000036531276456504500216620ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/service/init" require "chef/util/path_helper" class Chef class Provider class Service class Insserv < Chef::Provider::Service::Init provides :service, platform_family: %w{debian rhel fedora suse} do |node| Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv) end def self.supports?(resource, action) Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd) end def load_current_resource super # Look for a /etc/rc.*/SnnSERVICE link to signify that the service would be started in a runlevel if Dir.glob("/etc/rc**/S*#{Chef::Util::PathHelper.escape_glob_dir(current_resource.service_name)}").empty? current_resource.enabled false else current_resource.enabled true end current_resource end def enable_service() shell_out!("/sbin/insserv -r -f #{new_resource.service_name}") shell_out!("/sbin/insserv -d -f #{new_resource.service_name}") end def disable_service() shell_out!("/sbin/insserv -r -f #{new_resource.service_name}") end end end end end chef-12.14.60/lib/chef/provider/service/invokercd.rb000066400000000000000000000025171276456504500221530ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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/service/init" class Chef class Provider class Service class Invokercd < Chef::Provider::Service::Init provides :service, platform_family: "debian", override: true do |node| Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd) end def self.supports?(resource, action) Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd) end def initialize(new_resource, run_context) super @init_command = "/usr/sbin/invoke-rc.d #{@new_resource.service_name}" end end end end end chef-12.14.60/lib/chef/provider/service/macosx.rb000066400000000000000000000204751276456504500214640ustar00rootroot00000000000000# # Author:: Igor Afonov # Copyright:: Copyright 2011-2016, Igor Afonov # 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 "etc" require "rexml/document" require "chef/resource/service" require "chef/resource/macosx_service" require "chef/provider/service/simple" require "chef/util/path_helper" class Chef class Provider class Service class Macosx < Chef::Provider::Service::Simple provides :macosx_service, os: "darwin" provides :service, os: "darwin" def self.gather_plist_dirs locations = %w{/Library/LaunchAgents /Library/LaunchDaemons /System/Library/LaunchAgents /System/Library/LaunchDaemons } Chef::Util::PathHelper.home("Library", "LaunchAgents") { |p| locations << p } locations end PLIST_DIRS = gather_plist_dirs def this_version_or_newer?(this_version) Gem::Version.new(node["platform_version"]) >= Gem::Version.new(this_version) end def load_current_resource @current_resource = Chef::Resource::MacosxService.new(@new_resource.name) @current_resource.service_name(@new_resource.service_name) @plist_size = 0 @plist = @new_resource.plist ? @new_resource.plist : find_service_plist @service_label = find_service_label # LauchAgents should be loaded as the console user. @console_user = @plist ? @plist.include?("LaunchAgents") : false @session_type = @new_resource.session_type if @console_user @console_user = Etc.getlogin Chef::Log.debug("#{new_resource} console_user: '#{@console_user}'") cmd = "su " param = this_version_or_newer?("10.10") ? "" : "-l " @base_user_cmd = cmd + param + "#{@console_user} -c" # Default LauchAgent session should be Aqua @session_type = "Aqua" if @session_type.nil? end Chef::Log.debug("#{new_resource} Plist: '#{@plist}' service_label: '#{@service_label}'") set_service_status @current_resource end def define_resource_requirements requirements.assert(:reload) do |a| a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload" end requirements.assert(:all_actions) do |a| a.assertion { @plist_size < 2 } a.failure_message Chef::Exceptions::Service, "Several plist files match service name. Please use full service name." end requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?(@plist.to_s) } a.failure_message Chef::Exceptions::Service, "Could not find plist for #{@new_resource}" end requirements.assert(:enable, :disable) do |a| a.assertion { !@service_label.to_s.empty? } a.failure_message Chef::Exceptions::Service, "Could not find service's label in plist file '#{@plist}'!" end requirements.assert(:all_actions) do |a| a.assertion { @plist_size > 0 } # No failure here in original code - so we also will not # fail. Instead warn that the service is potentially missing a.whyrun "Assuming that the service would have been previously installed and is currently disabled." do @current_resource.enabled(false) @current_resource.running(false) end end end def start_service if @current_resource.running Chef::Log.debug("#{@new_resource} already running, not starting") else if @new_resource.start_command super else load_service end end end def stop_service unless @current_resource.running Chef::Log.debug("#{@new_resource} not running, not stopping") else if @new_resource.stop_command super else unload_service end end end def restart_service if @new_resource.restart_command super else unload_service sleep 1 load_service end end # On OS/X, enabling a service has the side-effect of starting it, # and disabling a service has the side-effect of stopping it. # # This makes some sense on OS/X since launchctl is an "init"-style # supervisor that will restart daemons that are crashing, etc. def enable_service if @current_resource.enabled Chef::Log.debug("#{@new_resource} already enabled, not enabling") else load_service end end def disable_service unless @current_resource.enabled Chef::Log.debug("#{@new_resource} not enabled, not disabling") else unload_service end end def load_service session = @session_type ? "-S #{@session_type} " : "" cmd = "launchctl load -w " + session + @plist shell_out_as_user(cmd) end def unload_service cmd = "launchctl unload -w " + @plist shell_out_as_user(cmd) end def shell_out_as_user(cmd) if @console_user shell_out_with_systems_locale("#{@base_user_cmd} '#{cmd}'") else shell_out_with_systems_locale(cmd) end end def set_service_status return if @plist == nil || @service_label.to_s.empty? cmd = "launchctl list #{@service_label}" res = shell_out_as_user(cmd) if res.exitstatus == 0 @current_resource.enabled(true) else @current_resource.enabled(false) end if @current_resource.enabled res.stdout.each_line do |line| case line.downcase when /\s+\"pid\"\s+=\s+(\d+).*/ pid = $1 @current_resource.running(!pid.to_i.zero?) Chef::Log.debug("Current PID for #{@service_label} is #{pid}") end end else @current_resource.running(false) end end private def find_service_label # CHEF-5223 "you can't glob for a file that hasn't been converged # onto the node yet." return nil if @plist.nil? # Plist must exist by this point raise Chef::Exceptions::FileNotFound, "Cannot find #{@plist}!" unless ::File.exists?(@plist) # Most services have the same internal label as the name of the # plist file. However, there is no rule saying that *has* to be # the case, and some core services (notably, ssh) do not follow # this rule. # plist files can come in XML or Binary formats. this command # will make sure we get XML every time. plist_xml = shell_out_with_systems_locale!( "plutil -convert xml1 -o - #{@plist}" ).stdout plist_doc = REXML::Document.new(plist_xml) plist_doc.elements[ "/plist/dict/key[text()='Label']/following::string[1]/text()"] end def find_service_plist plists = PLIST_DIRS.inject([]) do |results, dir| edir = ::File.expand_path(dir) entries = Dir.glob( "#{edir}/*#{Chef::Util::PathHelper.escape_glob_dir(@current_resource.service_name)}*.plist" ) entries.any? ? results << entries : results end plists.flatten! @plist_size = plists.size plists.first end end end end end chef-12.14.60/lib/chef/provider/service/openbsd.rb000066400000000000000000000173531276456504500216250ustar00rootroot00000000000000# # Author:: Scott Bonds () # Copyright:: Copyright 2014-2016, Scott Bonds # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/command" require "chef/mixin/shell_out" require "chef/provider/service/init" require "chef/resource/service" class Chef class Provider class Service class Openbsd < Chef::Provider::Service::Init provides :service, os: "openbsd" include Chef::Mixin::ShellOut attr_reader :init_command, :rc_conf, :rc_conf_local, :enabled_state_found RC_CONF_PATH = "/etc/rc.conf" RC_CONF_LOCAL_PATH = "/etc/rc.conf.local" def initialize(new_resource, run_context) super @rc_conf = ::File.read(RC_CONF_PATH) rescue "" @rc_conf_local = ::File.read(RC_CONF_LOCAL_PATH) rescue "" @init_command = ::File.exist?(rcd_script_path) ? rcd_script_path : nil new_resource.status_command("#{default_init_command} check") end def load_current_resource supports[:status] = true if supports[:status].nil? @current_resource = Chef::Resource::Service.new(new_resource.name) current_resource.service_name(new_resource.service_name) Chef::Log.debug("#{current_resource} found at #{init_command}") determine_current_status! determine_enabled_status! current_resource end def define_resource_requirements shared_resource_requirements requirements.assert(:start, :enable, :reload, :restart) do |a| a.assertion { init_command } a.failure_message Chef::Exceptions::Service, "#{new_resource}: unable to locate the rc.d script" end requirements.assert(:all_actions) do |a| a.assertion { enabled_state_found } # for consistency with original behavior, this will not fail in non-whyrun mode; # rather it will silently set enabled state=>false a.whyrun "Unable to determine enabled/disabled state, assuming this will be correct for an actual run. Assuming disabled." end requirements.assert(:start, :enable, :reload, :restart) do |a| a.assertion { init_command && builtin_service_enable_variable_name != nil } a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{init_command} and rcvar" # No recovery in whyrun mode - the init file is present but not correct. end end def enable_service if !is_enabled? if is_builtin? if is_enabled_by_default? update_rcl rc_conf_local.sub(/^#{Regexp.escape(builtin_service_enable_variable_name)}=.*/, "") else # add line with blank string, which means enable update_rcl rc_conf_local + "\n" + "#{builtin_service_enable_variable_name}=\"\"\n" end else # add to pkg_scripts, most recent addition goes last old_services_list = rc_conf_local.match(/^pkg_scripts="(.*)"/) old_services_list = old_services_list ? old_services_list[1].split(" ") : [] new_services_list = old_services_list + [new_resource.service_name] if rc_conf_local =~ /^pkg_scripts="(.*)"/ new_rcl = rc_conf_local.sub(/^pkg_scripts="(.*)"/, "pkg_scripts=\"#{new_services_list.join(' ')}\"") else new_rcl = rc_conf_local + "\n" + "pkg_scripts=\"#{new_services_list.join(' ')}\"\n" end update_rcl new_rcl end end end def disable_service if is_enabled? if is_builtin? if is_enabled_by_default? # add line to disable update_rcl rc_conf_local + "\n" + "#{builtin_service_enable_variable_name}=\"NO\"\n" else # remove line to disable update_rcl rc_conf_local.sub(/^#{Regexp.escape(builtin_service_enable_variable_name)}=.*/, "") end else # remove from pkg_scripts old_list = rc_conf_local.match(/^pkg_scripts="(.*)"/) old_list = old_list ? old_list[1].split(" ") : [] new_list = old_list - [new_resource.service_name] update_rcl rc_conf_local.sub(/^pkg_scripts="(.*)"/, pkg_scripts = "#{new_list.join(' ')}") end end end private def rcd_script_found? !init_command.nil? end def rcd_script_path "/etc/rc.d/#{new_resource.service_name}" end def update_rcl(value) FileUtils.touch RC_CONF_LOCAL_PATH if !::File.exists? RC_CONF_LOCAL_PATH ::File.write(RC_CONF_LOCAL_PATH, value) @rc_conf_local = value end # The variable name used in /etc/rc.conf.local for enabling this service def builtin_service_enable_variable_name @bsevn ||= begin result = nil if rcd_script_found? ::File.open(init_command) do |rcscript| if m = rcscript.read.match(/^# \$OpenBSD: (\w+)[(.rc),]?/) result = m[1] + "_flags" end end end # Fallback allows us to keep running in whyrun mode when # the script does not exist. result || new_resource.service_name end end def is_builtin? result = false var_name = builtin_service_enable_variable_name if var_name if rc_conf =~ /^#{Regexp.escape(var_name)}=(.*)/ result = true end end result end def is_enabled_by_default? result = false var_name = builtin_service_enable_variable_name if var_name if m = rc_conf.match(/^#{Regexp.escape(var_name)}=(.*)/) if !(m[1] =~ /"?[Nn][Oo]"?/) result = true end end end result end def determine_enabled_status! result = false # Default to disabled if the service doesn't currently exist at all @enabled_state_found = false if is_builtin? var_name = builtin_service_enable_variable_name if var_name if m = rc_conf_local.match(/^#{Regexp.escape(var_name)}=(.*)/) @enabled_state_found = true if !(m[1] =~ /"?[Nn][Oo]"?/) # e.g. looking for httpd_flags=NO result = true end end end if !@enabled_state_found result = is_enabled_by_default? end else var_name = @new_resource.service_name if var_name if m = rc_conf_local.match(/^pkg_scripts="(.*)"/) @enabled_state_found = true if m[1].include?(var_name) # e.g. looking for 'gdm' in pkg_scripts="gdm unbound" result = true end end end end current_resource.enabled result end alias :is_enabled? :determine_enabled_status! end end end end chef-12.14.60/lib/chef/provider/service/redhat.rb000066400000000000000000000110571276456504500214350ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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/service/init" class Chef class Provider class Service class Redhat < Chef::Provider::Service::Init # @api private attr_accessor :service_missing # @api private attr_accessor :current_run_levels provides :service, platform_family: %w{rhel fedora suse} do |node| Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat) end CHKCONFIG_ON = /\d:on/ CHKCONFIG_MISSING = /No such/ def self.supports?(resource, action) Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd) end def initialize(new_resource, run_context) super @init_command = "/sbin/service #{new_resource.service_name}" @service_missing = false @current_run_levels = [] end # @api private def run_levels new_resource.run_levels end def define_resource_requirements shared_resource_requirements requirements.assert(:all_actions) do |a| chkconfig_file = "/sbin/chkconfig" a.assertion { ::File.exists? chkconfig_file } a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} does not exist!" end requirements.assert(:enable) do |a| a.assertion { !@service_missing } a.failure_message Chef::Exceptions::Service, "#{new_resource}: Service is not known to chkconfig." a.whyrun "Assuming service would be enabled. The init script is not presently installed." end requirements.assert(:start, :reload, :restart) do |a| a.assertion do new_resource.init_command || custom_command_for_action?(action) || !@service_missing end a.failure_message Chef::Exceptions::Service, "#{new_resource}: No custom command for #{action} specified and unable to locate the init.d script!" a.whyrun "Assuming service would be enabled. The init script is not presently installed." end end def load_current_resource supports[:status] = true if supports[:status].nil? super if ::File.exists?("/sbin/chkconfig") chkconfig = shell_out!("/sbin/chkconfig --list #{current_resource.service_name}", :returns => [0, 1]) unless run_levels.nil? || run_levels.empty? all_levels_match = true chkconfig.stdout.split(/\s+/)[1..-1].each do |level| index = level.split(":").first status = level.split(":").last if level =~ CHKCONFIG_ON @current_run_levels << index.to_i all_levels_match = false unless run_levels.include?(index.to_i) else all_levels_match = false if run_levels.include?(index.to_i) end end current_resource.enabled(all_levels_match) else current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON)) end @service_missing = !!(chkconfig.stderr =~ CHKCONFIG_MISSING) end current_resource end # @api private def levels (run_levels.nil? || run_levels.empty?) ? "" : "--level #{run_levels.join('')} " end def enable_service() unless run_levels.nil? || run_levels.empty? disable_levels = current_run_levels - run_levels shell_out! "/sbin/chkconfig --level #{disable_levels.join('')} #{new_resource.service_name} off" unless disable_levels.empty? end shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} on" end def disable_service() shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} off" end end end end end chef-12.14.60/lib/chef/provider/service/simple.rb000066400000000000000000000156021276456504500214570ustar00rootroot00000000000000# # Author:: Mathieu Sauve-Frankel # Copyright:: Copyright 2009-2016, Mathieu Sauve-Frankel # 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/service" require "chef/resource/service" require "chef/mixin/command" class Chef class Provider class Service class Simple < Chef::Provider::Service # this must be subclassed to be useful so does not directly implement :service attr_reader :status_load_success def load_current_resource @current_resource = Chef::Resource::Service.new(@new_resource.name) @current_resource.service_name(@new_resource.service_name) @status_load_success = true @ps_command_failed = false determine_current_status! @current_resource end def whyrun_supported? true end def shared_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { @status_load_success } a.whyrun ["Service status not available. Assuming a prior action would have installed the service.", "Assuming status of not running."] end end def define_resource_requirements # FIXME? need reload from service.rb shared_resource_requirements requirements.assert(:start) do |a| a.assertion { @new_resource.start_command } a.failure_message Chef::Exceptions::Service, "#{self} requires that start_command be set" end requirements.assert(:stop) do |a| a.assertion { @new_resource.stop_command } a.failure_message Chef::Exceptions::Service, "#{self} requires that stop_command be set" end requirements.assert(:restart) do |a| a.assertion { @new_resource.restart_command || ( @new_resource.start_command && @new_resource.stop_command ) } a.failure_message Chef::Exceptions::Service, "#{self} requires a restart_command or both start_command and stop_command be set in order to perform a restart" end requirements.assert(:reload) do |a| a.assertion { @new_resource.reload_command } a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} requires a reload_command be set in order to perform a reload" end requirements.assert(:all_actions) do |a| a.assertion do @new_resource.status_command || supports[:status] || (!ps_cmd.nil? && !ps_cmd.empty?) end a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute" end requirements.assert(:all_actions) do |a| a.assertion { !@ps_command_failed } a.failure_message Chef::Exceptions::Service, "Command #{ps_cmd} failed to execute, cannot determine service current status" end end def start_service shell_out_with_systems_locale!(@new_resource.start_command) end def stop_service shell_out_with_systems_locale!(@new_resource.stop_command) end def restart_service if @new_resource.restart_command shell_out_with_systems_locale!(@new_resource.restart_command) else stop_service sleep 1 start_service end end def reload_service shell_out_with_systems_locale!(@new_resource.reload_command) end protected def determine_current_status! if @new_resource.status_command Chef::Log.debug("#{@new_resource} you have specified a status command, running..") begin if shell_out(@new_resource.status_command).exitstatus == 0 @current_resource.running true Chef::Log.debug("#{@new_resource} is running") end rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed. # Temporarily catching different types of exceptions here until we get Shellout fixed. # TODO: Remove the line before one we get the ShellOut fix. @status_load_success = false @current_resource.running false nil end elsif supports[:status] Chef::Log.debug("#{@new_resource} supports status, running") begin if shell_out("#{default_init_command} status").exitstatus == 0 @current_resource.running true Chef::Log.debug("#{@new_resource} is running") end # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed. # Temporarily catching different types of exceptions here until we get Shellout fixed. # TODO: Remove the line before one we get the ShellOut fix. rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError @status_load_success = false @current_resource.running false nil end else Chef::Log.debug "#{@new_resource} falling back to process table inspection" r = Regexp.new(@new_resource.pattern) Chef::Log.debug "#{@new_resource} attempting to match '#{@new_resource.pattern}' (#{r.inspect}) against process list" begin shell_out!(ps_cmd).stdout.each_line do |line| if r.match(line) @current_resource.running true break end end @current_resource.running false unless @current_resource.running Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}" # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed. # Temporarily catching different types of exceptions here until we get Shellout fixed. # TODO: Remove the line before one we get the ShellOut fix. rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError @ps_command_failed = true end end end def ps_cmd @run_context.node[:command] && @run_context.node[:command][:ps] end end end end end chef-12.14.60/lib/chef/provider/service/solaris.rb000066400000000000000000000075031276456504500216430ustar00rootroot00000000000000# # Author:: Toomas Pelberg () # Copyright:: Copyright 2010-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/service" require "chef/resource/service" require "chef/mixin/command" class Chef class Provider class Service class Solaris < Chef::Provider::Service attr_reader :maintenance provides :service, os: "solaris2" def initialize(new_resource, run_context = nil) super @init_command = "/usr/sbin/svcadm" @status_command = "/bin/svcs" @maintenace = false end def load_current_resource @current_resource = Chef::Resource::Service.new(@new_resource.name) @current_resource.service_name(@new_resource.service_name) [@init_command, @status_command].each do |cmd| unless ::File.executable? cmd raise Chef::Exceptions::Service, "#{cmd} not executable!" end end @status = service_status.enabled @current_resource end def define_resource_requirements # FIXME? need reload from service.rb shared_resource_requirements end def enable_service shell_out!(default_init_command, "clear", @new_resource.service_name) if @maintenance shell_out!(default_init_command, "enable", "-s", @new_resource.service_name) end def disable_service shell_out!(default_init_command, "disable", "-s", @new_resource.service_name) end alias_method :stop_service, :disable_service alias_method :start_service, :enable_service def reload_service shell_out!(default_init_command, "refresh", @new_resource.service_name) end def restart_service ## svcadm restart doesn't supports sync(-s) option disable_service return enable_service end def service_status cmd = shell_out!(@status_command, "-l", @current_resource.service_name, :returns => [0, 1]) # Example output # $ svcs -l rsyslog # fmri svc:/application/rsyslog:default # name rsyslog logging utility # enabled true # state online # next_state none # state_time April 2, 2015 04:25:19 PM EDT # logfile /var/svc/log/application-rsyslog:default.log # restarter svc:/system/svc/restarter:default # contract_id 1115271 # dependency require_all/error svc:/milestone/multi-user:default (online) # $ # load output into hash status = {} cmd.stdout.each_line do |line| key, value = line.strip.split(/\s+/, 2) status[key] = value end # check service state @maintenance = false case status["state"] when "online" @current_resource.enabled(true) @current_resource.running(true) when "maintenance" @maintenance = true end unless @current_resource.enabled @current_resource.enabled(false) @current_resource.running(false) end @current_resource end end end end end chef-12.14.60/lib/chef/provider/service/systemd.rb000066400000000000000000000130561276456504500216570ustar00rootroot00000000000000# # Author:: Stephen Haynes () # Author:: Davide Cavalca () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/service" require "chef/provider/service/simple" require "chef/mixin/which" class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple include Chef::Mixin::Which provides :service, os: "linux" do |node| Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd) end attr_accessor :status_check_success def self.supports?(resource, action) Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:systemd) end def load_current_resource @current_resource = Chef::Resource::Service.new(new_resource.name) current_resource.service_name(new_resource.service_name) @status_check_success = true if new_resource.status_command Chef::Log.debug("#{new_resource} you have specified a status command, running..") unless shell_out(new_resource.status_command).error? current_resource.running(true) else @status_check_success = false current_resource.running(false) current_resource.enabled(false) current_resource.masked(false) end else current_resource.running(is_active?) end current_resource.enabled(is_enabled?) current_resource.masked(is_masked?) current_resource end # systemd supports user services just fine def user_services_requirements end def define_resource_requirements shared_resource_requirements requirements.assert(:all_actions) do |a| a.assertion { status_check_success } # We won't stop in any case, but in whyrun warn and tell what we're doing. a.whyrun ["Failed to determine status of #{new_resource}, using command #{new_resource.status_command}.", "Assuming service would have been installed and is disabled"] end end def get_systemctl_options_args if new_resource.user uid = node["etc"]["passwd"][new_resource.user]["uid"] options = { :environment => { "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/#{uid}/bus", }, :user => new_resource.user, } args = "--user" else options = {} args = "--system" end return options, args end def start_service if current_resource.running Chef::Log.debug("#{new_resource} already running, not starting") else if new_resource.start_command super else options, args = get_systemctl_options_args shell_out_with_systems_locale!("#{systemctl_path} #{args} start #{new_resource.service_name}", options) end end end def stop_service unless current_resource.running Chef::Log.debug("#{new_resource} not running, not stopping") else if new_resource.stop_command super else options, args = get_systemctl_options_args shell_out_with_systems_locale!("#{systemctl_path} #{args} stop #{new_resource.service_name}", options) end end end def restart_service if new_resource.restart_command super else options, args = get_systemctl_options_args shell_out_with_systems_locale!("#{systemctl_path} #{args} restart #{new_resource.service_name}", options) end end def reload_service if new_resource.reload_command super else if current_resource.running options, args = get_systemctl_options_args shell_out_with_systems_locale!("#{systemctl_path} #{args} reload #{new_resource.service_name}", options) else start_service end end end def enable_service options, args = get_systemctl_options_args shell_out!("#{systemctl_path} #{args} enable #{new_resource.service_name}", options) end def disable_service options, args = get_systemctl_options_args shell_out!("#{systemctl_path} #{args} disable #{new_resource.service_name}", options) end def mask_service options, args = get_systemctl_options_args shell_out!("#{systemctl_path} #{args} mask #{new_resource.service_name}", options) end def unmask_service options, args = get_systemctl_options_args shell_out!("#{systemctl_path} #{args} unmask #{new_resource.service_name}", options) end def is_active? options, args = get_systemctl_options_args shell_out("#{systemctl_path} #{args} is-active #{new_resource.service_name} --quiet", options).exitstatus == 0 end def is_enabled? options, args = get_systemctl_options_args shell_out("#{systemctl_path} #{args} is-enabled #{new_resource.service_name} --quiet", options).exitstatus == 0 end def is_masked? options, args = get_systemctl_options_args s = shell_out("#{systemctl_path} #{args} is-enabled #{new_resource.service_name}", options) s.exitstatus != 0 && s.stdout.include?("masked") end private def systemctl_path if @systemctl_path.nil? @systemctl_path = which("systemctl") end @systemctl_path end end chef-12.14.60/lib/chef/provider/service/upstart.rb000066400000000000000000000227131276456504500216710ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2010-2016, Bryan McLellan # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/service" require "chef/provider/service/simple" require "chef/mixin/command" require "chef/util/file_edit" class Chef class Provider class Service class Upstart < Chef::Provider::Service::Simple provides :service, platform_family: "debian", override: true do |node| Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart) end UPSTART_STATE_FORMAT = /\S+ \(?(start|stop)?\)? ?[\/ ](\w+)/ def self.supports?(resource, action) Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:upstart) end # Upstart does more than start or stop a service, creating multiple 'states' [1] that a service can be in. # In chef, when we ask a service to start, we expect it to have started before performing the next step # since we have top down dependencies. Which is to say we may follow witha resource next that requires # that service to be running. According to [2] we can trust that sending a 'goal' such as start will not # return until that 'goal' is reached, or some error has occurred. # # [1] http://upstart.ubuntu.com/wiki/JobStates # [2] http://www.netsplit.com/2008/04/27/upstart-05-events/ def initialize(new_resource, run_context) # TODO: re-evaluate if this is needed after integrating cookbook fix raise ArgumentError, "run_context cannot be nil" unless run_context super run_context.node # dup so we can mutate @job @job = @new_resource.service_name.dup if @new_resource.parameters @new_resource.parameters.each do |key, value| @job << " #{key}=#{value}" end end platform, version = Chef::Platform.find_platform_and_version(run_context.node) if platform == "ubuntu" && (8.04..9.04).cover?(version.to_f) @upstart_job_dir = "/etc/event.d" @upstart_conf_suffix = "" else @upstart_job_dir = "/etc/init" @upstart_conf_suffix = ".conf" end @command_success = true # new_resource.status_command= false, means upstart used @config_file_found = true @upstart_command_success = true end def define_resource_requirements # Do not call super, only call shared requirements shared_resource_requirements requirements.assert(:all_actions) do |a| if !@command_success whyrun_msg = if @new_resource.status_command "Provided status command #{@new_resource.status_command} failed." else "Could not determine upstart state for service" end end a.assertion { @command_success } # no failure here, just document the assumptions made. a.whyrun "#{whyrun_msg} Assuming service installed and not running." end requirements.assert(:all_actions) do |a| a.assertion { @config_file_found } # no failure here, just document the assumptions made. a.whyrun "Could not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}. Assuming service is disabled." end end def load_current_resource @current_resource = Chef::Resource::Service.new(@new_resource.name) @current_resource.service_name(@new_resource.service_name) # Get running/stopped state # We do not support searching for a service via ps when using upstart since status is a native # upstart function. We will however support status_command in case someone wants to do something special. if @new_resource.status_command Chef::Log.debug("#{@new_resource} you have specified a status command, running..") begin if shell_out!(@new_resource.status_command).exitstatus == 0 @current_resource.running true end rescue @command_success = false @current_resource.running false nil end else begin if upstart_goal_state == "start" @current_resource.running true else @current_resource.running false end rescue Chef::Exceptions::Exec @command_success = false @current_resource.running false nil end end # Get enabled/disabled state by reading job configuration file if ::File.exists?("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}") Chef::Log.debug("#{@new_resource} found #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}") ::File.open("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}", "r") do |file| while line = file.gets case line when /^start on/ Chef::Log.debug("#{@new_resource} enabled: #{line.chomp}") @current_resource.enabled true break when /^#start on/ Chef::Log.debug("#{@new_resource} disabled: #{line.chomp}") @current_resource.enabled false break end end end else @config_file_found = false Chef::Log.debug("#{@new_resource} did not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}") @current_resource.enabled false end @current_resource end def start_service # Calling start on a service that is already started will return 1 # Our 'goal' when we call start is to ensure the service is started if @current_resource.running Chef::Log.debug("#{@new_resource} already running, not starting") else if @new_resource.start_command super else shell_out_with_systems_locale!("/sbin/start #{@job}") end end end def stop_service # Calling stop on a service that is already stopped will return 1 # Our 'goal' when we call stop is to ensure the service is stopped unless @current_resource.running Chef::Log.debug("#{@new_resource} not running, not stopping") else if @new_resource.stop_command super else shell_out_with_systems_locale!("/sbin/stop #{@job}") end end end def restart_service if @new_resource.restart_command super # Upstart always provides restart functionality so we don't need to mimic it with stop/sleep/start. # Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883 else if @current_resource.running shell_out_with_systems_locale!("/sbin/restart #{@job}") else start_service end end end def reload_service if @new_resource.reload_command super else # upstart >= 0.6.3-4 supports reload (HUP) shell_out_with_systems_locale!("/sbin/reload #{@job}") end end # https://bugs.launchpad.net/upstart/+bug/94065 def enable_service Chef::Log.debug("#{@new_resource} upstart lacks inherent support for enabling services, editing job config file") conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}") conf.search_file_replace(/^#start on/, "start on") conf.write_file end def disable_service Chef::Log.debug("#{@new_resource} upstart lacks inherent support for disabling services, editing job config file") conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}") conf.search_file_replace(/^start on/, "#start on") conf.write_file end def upstart_goal_state command = "/sbin/status #{@job}" status = popen4(command) do |pid, stdin, stdout, stderr| stdout.each_line do |line| # service goal/state # OR # service (instance) goal/state # OR # service (goal) state line =~ UPSTART_STATE_FORMAT data = Regexp.last_match return data[1] end end end end end end end chef-12.14.60/lib/chef/provider/service/windows.rb000066400000000000000000000241111276456504500216530ustar00rootroot00000000000000# # Author:: Nuo Yan # Author:: Bryan McLellan # Author:: Seth Chisamore # Copyright:: Copyright 2010-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/service/simple" if RUBY_PLATFORM =~ /mswin|mingw32|windows/ require "chef/win32/error" require "win32/service" end class Chef::Provider::Service::Windows < Chef::Provider::Service provides :service, os: "windows" provides :windows_service, os: "windows" include Chef::Mixin::ShellOut include Chef::ReservedNames::Win32::API::Error rescue LoadError #Win32::Service.get_start_type AUTO_START = "auto start" MANUAL = "demand start" DISABLED = "disabled" #Win32::Service.get_current_state RUNNING = "running" STOPPED = "stopped" CONTINUE_PENDING = "continue pending" PAUSE_PENDING = "pause pending" PAUSED = "paused" START_PENDING = "start pending" STOP_PENDING = "stop pending" TIMEOUT = 60 SERVICE_RIGHT = "SeServiceLogonRight" def whyrun_supported? false end def load_current_resource @current_resource = Chef::Resource::WindowsService.new(@new_resource.name) @current_resource.service_name(@new_resource.service_name) @current_resource.running(current_state == RUNNING) Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}" case current_start_type when AUTO_START @current_resource.enabled(true) when DISABLED @current_resource.enabled(false) end Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}" @current_resource end def start_service if Win32::Service.exists?(@new_resource.service_name) # reconfiguration is idempotent, so just do it. new_config = { service_name: @new_resource.service_name, service_start_name: @new_resource.run_as_user, password: @new_resource.run_as_password, }.reject { |k, v| v.nil? || v.length == 0 } Win32::Service.configure(new_config) Chef::Log.info "#{@new_resource} configured with #{new_config.inspect}" if new_config.has_key?(:service_start_name) unless Chef::ReservedNames::Win32::Security.get_account_right(canonicalize_username(new_config[:service_start_name])).include?(SERVICE_RIGHT) grant_service_logon(new_config[:service_start_name]) end end state = current_state if state == RUNNING Chef::Log.debug "#{@new_resource} already started - nothing to do" elsif state == START_PENDING Chef::Log.debug "#{@new_resource} already sent start signal - waiting for start" wait_for_state(RUNNING) elsif state == STOPPED if @new_resource.start_command Chef::Log.debug "#{@new_resource} starting service using the given start_command" shell_out!(@new_resource.start_command) else spawn_command_thread do begin Win32::Service.start(@new_resource.service_name) rescue SystemCallError => ex if ex.errno == ERROR_SERVICE_LOGON_FAILED Chef::Log.error ex.message raise Chef::Exceptions::Service, "Service #{@new_resource} did not start due to a logon failure (error #{ERROR_SERVICE_LOGON_FAILED}): possibly the specified user '#{@new_resource.run_as_user}' does not have the 'log on as a service' privilege, or the password is incorrect." else raise ex end end end wait_for_state(RUNNING) end @new_resource.updated_by_last_action(true) else raise Chef::Exceptions::Service, "Service #{@new_resource} can't be started from state [#{state}]" end else Chef::Log.debug "#{@new_resource} does not exist - nothing to do" end end def stop_service if Win32::Service.exists?(@new_resource.service_name) state = current_state if state == RUNNING if @new_resource.stop_command Chef::Log.debug "#{@new_resource} stopping service using the given stop_command" shell_out!(@new_resource.stop_command) else spawn_command_thread do Win32::Service.stop(@new_resource.service_name) end wait_for_state(STOPPED) end @new_resource.updated_by_last_action(true) elsif state == STOPPED Chef::Log.debug "#{@new_resource} already stopped - nothing to do" elsif state == STOP_PENDING Chef::Log.debug "#{@new_resource} already sent stop signal - waiting for stop" wait_for_state(STOPPED) else raise Chef::Exceptions::Service, "Service #{@new_resource} can't be stopped from state [#{state}]" end else Chef::Log.debug "#{@new_resource} does not exist - nothing to do" end end def restart_service if Win32::Service.exists?(@new_resource.service_name) if @new_resource.restart_command Chef::Log.debug "#{@new_resource} restarting service using the given restart_command" shell_out!(@new_resource.restart_command) else stop_service start_service end @new_resource.updated_by_last_action(true) else Chef::Log.debug "#{@new_resource} does not exist - nothing to do" end end def enable_service if Win32::Service.exists?(@new_resource.service_name) set_startup_type(:automatic) else Chef::Log.debug "#{@new_resource} does not exist - nothing to do" end end def disable_service if Win32::Service.exists?(@new_resource.service_name) set_startup_type(:disabled) else Chef::Log.debug "#{@new_resource} does not exist - nothing to do" end end def action_enable if current_start_type != AUTO_START converge_by("enable service #{@new_resource}") do enable_service Chef::Log.info("#{@new_resource} enabled") end else Chef::Log.debug("#{@new_resource} already enabled - nothing to do") end load_new_resource_state @new_resource.enabled(true) end def action_disable if current_start_type != DISABLED converge_by("disable service #{@new_resource}") do disable_service Chef::Log.info("#{@new_resource} disabled") end else Chef::Log.debug("#{@new_resource} already disabled - nothing to do") end load_new_resource_state @new_resource.enabled(false) end def action_configure_startup case @new_resource.startup_type when :automatic if current_start_type != AUTO_START converge_by("set service #{@new_resource} startup type to automatic") do set_startup_type(:automatic) end else Chef::Log.debug("#{@new_resource} startup_type already automatic - nothing to do") end when :manual if current_start_type != MANUAL converge_by("set service #{@new_resource} startup type to manual") do set_startup_type(:manual) end else Chef::Log.debug("#{@new_resource} startup_type already manual - nothing to do") end when :disabled if current_start_type != DISABLED converge_by("set service #{@new_resource} startup type to disabled") do set_startup_type(:disabled) end else Chef::Log.debug("#{@new_resource} startup_type already disabled - nothing to do") end end # Avoid changing enabled from true/false for now @new_resource.enabled(nil) end private def grant_service_logon(username) begin Chef::ReservedNames::Win32::Security.add_account_right(canonicalize_username(username), SERVICE_RIGHT) rescue Chef::Exceptions::Win32APIError => err Chef::Log.fatal "Logon-as-service grant failed with output: #{err}" raise Chef::Exceptions::Service, "Logon-as-service grant failed for #{username}: #{err}" end Chef::Log.info "Grant logon-as-service to user '#{username}' successful." true end # remove characters that make for broken or wonky filenames. def clean_username_for_path(username) username.gsub(/[\/\\. ]+/, "_") end def canonicalize_username(username) username.sub(/^\.?\\+/, "") end def current_state Win32::Service.status(@new_resource.service_name).current_state end def current_start_type Win32::Service.config_info(@new_resource.service_name).start_type end # Helper method that waits for a status to change its state since state # changes aren't usually instantaneous. def wait_for_state(desired_state) retries = 0 loop do break if current_state == desired_state raise Timeout::Error if ( retries += 1 ) > resource_timeout sleep 1 end end def resource_timeout @resource_timeout ||= @new_resource.timeout || TIMEOUT end def spawn_command_thread worker = Thread.new do yield end Timeout.timeout(resource_timeout) do worker.join end end # Takes Win32::Service start_types def set_startup_type(type) # Set-Service Startup Type => Win32::Service Constant allowed_types = { :automatic => Win32::Service::AUTO_START, :manual => Win32::Service::DEMAND_START, :disabled => Win32::Service::DISABLED } unless allowed_types.keys.include?(type) raise Chef::Exceptions::ConfigurationError, "#{@new_resource.name}: Startup type '#{type}' is not supported" end Chef::Log.debug "#{@new_resource.name} setting start_type to #{type}" Win32::Service.configure( :service_name => @new_resource.service_name, :start_type => allowed_types[type] ) @new_resource.updated_by_last_action(true) end end chef-12.14.60/lib/chef/provider/subversion.rb000066400000000000000000000211241276456504500207210ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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. # #TODO subversion and git should both extend from a base SCM provider. require "chef/log" require "chef/provider" require "chef/mixin/command" require "chef-config/mixin/fuzzy_hostname_matcher" require "fileutils" class Chef class Provider class Subversion < Chef::Provider provides :subversion SVN_INFO_PATTERN = /^([\w\s]+): (.+)$/ include Chef::Mixin::Command include ChefConfig::Mixin::FuzzyHostnameMatcher def whyrun_supported? true end def load_current_resource @current_resource = Chef::Resource::Subversion.new(@new_resource.name) unless [:export, :force_export].include?(Array(@new_resource.action).first) if current_revision = find_current_revision @current_resource.revision current_revision end end end def define_resource_requirements requirements.assert(:all_actions) do |a| # Make sure the parent dir exists, or else fail. # for why run, print a message explaining the potential error. parent_directory = ::File.dirname(@new_resource.destination) a.assertion { ::File.directory?(parent_directory) } a.failure_message(Chef::Exceptions::MissingParentDirectory, "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{parent_directory} does not exist") a.whyrun("Directory #{parent_directory} does not exist, assuming it would have been created") end end def action_checkout if target_dir_non_existent_or_empty? converge_by("perform checkout of #{@new_resource.repository} into #{@new_resource.destination}") do shell_out!(checkout_command, run_options) end else Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do" end end def action_export if target_dir_non_existent_or_empty? action_force_export else Chef::Log.debug "#{@new_resource} export destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do" end end def action_force_export converge_by("export #{@new_resource.repository} into #{@new_resource.destination}") do shell_out!(export_command, run_options) end end def action_sync assert_target_directory_valid! if ::File.exist?(::File.join(@new_resource.destination, ".svn")) current_rev = find_current_revision Chef::Log.debug "#{@new_resource} current revision: #{current_rev} target revision: #{revision_int}" unless current_revision_matches_target_revision? converge_by("sync #{@new_resource.destination} from #{@new_resource.repository}") do shell_out!(sync_command, run_options) Chef::Log.info "#{@new_resource} updated to revision: #{revision_int}" end end else action_checkout end end def sync_command c = scm :update, @new_resource.svn_arguments, verbose, authentication, proxy, "-r#{revision_int}", @new_resource.destination Chef::Log.debug "#{@new_resource} updated working copy #{@new_resource.destination} to revision #{@new_resource.revision}" c end def checkout_command c = scm :checkout, @new_resource.svn_arguments, verbose, authentication, proxy, "-r#{revision_int}", @new_resource.repository, @new_resource.destination Chef::Log.info "#{@new_resource} checked out #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}" c end def export_command args = ["--force"] args << @new_resource.svn_arguments << verbose << authentication << proxy << "-r#{revision_int}" << @new_resource.repository << @new_resource.destination c = scm :export, *args Chef::Log.info "#{@new_resource} exported #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}" c end # If the specified revision isn't an integer ("HEAD" for example), look # up the revision id by asking the server # If the specified revision is an integer, trust it. def revision_int @revision_int ||= begin if @new_resource.revision =~ /^\d+$/ @new_resource.revision else command = scm(:info, @new_resource.repository, @new_resource.svn_info_args, authentication, "-r#{@new_resource.revision}") svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0, 1])).stdout extract_revision_info(svn_info) end end end alias :revision_slug :revision_int def find_current_revision return nil unless ::File.exist?(::File.join(@new_resource.destination, ".svn")) command = scm(:info) svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0, 1])).stdout extract_revision_info(svn_info) end def current_revision_matches_target_revision? (!@current_resource.revision.nil?) && (revision_int.strip.to_i == @current_resource.revision.strip.to_i) end def run_options(run_opts = {}) run_opts[:user] = @new_resource.user if @new_resource.user run_opts[:group] = @new_resource.group if @new_resource.group run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout run_opts end private def cwd @new_resource.destination end def verbose "-q" end def extract_revision_info(svn_info) repo_attrs = svn_info.lines.inject({}) do |attrs, line| if line =~ SVN_INFO_PATTERN property, value = $1, $2 attrs[property] = value end attrs end rev = (repo_attrs["Last Changed Rev"] || repo_attrs["Revision"]) rev.strip! if rev raise "Could not parse `svn info` data: #{svn_info}" if repo_attrs.empty? Chef::Log.debug "#{@new_resource} resolved revision #{@new_resource.revision} to #{rev}" rev end # If a username is configured for the SCM, return the command-line # switches for that. Note that we don't need to return the password # switch, since Capistrano will check for that prompt in the output # and will respond appropriately. def authentication return "" unless @new_resource.svn_username result = "--username #{@new_resource.svn_username} " result << "--password #{@new_resource.svn_password} " result end def proxy repo_uri = URI.parse(@new_resource.repository) proxy_uri = Chef::Config.proxy_uri(repo_uri.scheme, repo_uri.host, repo_uri.port) return "" if proxy_uri.nil? result = "--config-option servers:global:http-proxy-host=#{proxy_uri.host} " result << "--config-option servers:global:http-proxy-port=#{proxy_uri.port} " result end def scm(*args) binary = svn_binary binary = "\"#{binary}\"" if binary =~ /\s/ [binary, *args].compact.join(" ") end def target_dir_non_existent_or_empty? !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == [".", ".."] end def svn_binary @new_resource.svn_binary || (Chef::Platform.windows? ? "svn.exe" : "svn") end def assert_target_directory_valid! target_parent_directory = ::File.dirname(@new_resource.destination) unless ::File.directory?(target_parent_directory) msg = "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{target_parent_directory} does not exist" raise Chef::Exceptions::MissingParentDirectory, msg end end end end end chef-12.14.60/lib/chef/provider/support/000077500000000000000000000000001276456504500177115ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/support/yum_repo.erb000066400000000000000000000055751276456504500222560ustar00rootroot00000000000000# This file was generated by Chef # Do NOT modify this file by hand. [<%= @config.repositoryid %>] name=<%= @config.description %> <% if @config.baseurl %> baseurl=<%= @config.baseurl %> <% end %> <% if @config.cost %> cost=<%= @config.cost %> <% end %> <% if @config.enabled %> enabled=1 <% else %> enabled=0 <% end %> <% if @config.enablegroups %> enablegroups=1 <% end %> <% if @config.exclude %> exclude=<%= @config.exclude %> <% end %> <% if @config.failovermethod %> failovermethod=<%= @config.failovermethod %> <% end %> <% if @config.fastestmirror_enabled %> fastestmirror_enabled=<%= @config.fastestmirror_enabled %> <% end %> <% if @config.gpgcheck %> gpgcheck=1 <% else %> gpgcheck=0 <% end %> <% if @config.gpgkey %> gpgkey=<%= case @config.gpgkey when Array @config.gpgkey.join("\n ") else @config.gpgkey end %> <% end -%> <% if @config.http_caching %> http_caching=<%= @config.http_caching %> <% end %> <% if @config.include_config %> include=<%= @config.include_config %> <% end %> <% if @config.includepkgs %> includepkgs=<%= @config.includepkgs %> <% end %> <% if @config.keepalive %> keepalive=1 <% end %> <% if @config.metadata_expire %> metadata_expire=<%= @config.metadata_expire %> <% end %> <% if @config.mirrorlist %> mirrorlist=<%= @config.mirrorlist %> <% end %> <% if @config.mirror_expire %> mirror_expire=<%= @config.mirror_expire %> <% end %> <% if @config.mirrorlist_expire %> mirrorlist_expire=<%= @config.mirrorlist_expire %> <% end %> <% if @config.priority %> priority=<%= @config.priority %> <% end %> <% if @config.proxy %> proxy=<%= @config.proxy %> <% end %> <% if @config.proxy_username %> proxy_username=<%= @config.proxy_username %> <% end %> <% if @config.proxy_password %> proxy_password=<%= @config.proxy_password %> <% end %> <% if @config.username %> username=<%= @config.username %> <% end %> <% if @config.password %> password=<%= @config.password %> <% end %> <% if @config.repo_gpgcheck %> repo_gpgcheck=1 <% end %> <% if @config.max_retries %> retries=<%= @config.max_retries %> <% end %> <% if @config.report_instanceid %> report_instanceid=<%= @config.report_instanceid %> <% end %> <% if @config.skip_if_unavailable %> skip_if_unavailable=1 <% end %> <% if @config.sslcacert %> sslcacert=<%= @config.sslcacert %> <% end %> <% if @config.sslclientcert %> sslclientcert=<%= @config.sslclientcert %> <% end %> <% if @config.sslclientkey %> sslclientkey=<%= @config.sslclientkey %> <% end %> <% unless @config.sslverify.nil? %> sslverify=<%= ( @config.sslverify ) ? 'true' : 'false' %> <% end %> <% if @config.timeout %> timeout=<%= @config.timeout %> <% end %> <% if @config.options -%> <% @config.options.each do |key, value| -%> <%= key %>=<%= case value when Array value.join("\n ") when TrueClass '1' when FalseClass '0' else value end %> <% end -%> <% end -%> chef-12.14.60/lib/chef/provider/systemd_unit.rb000066400000000000000000000161701276456504500212560ustar00rootroot00000000000000# # Author:: Nathan Williams () # Copyright:: Copyright 2016, Nathan Williams # 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" require "chef/mixin/which" require "chef/mixin/shell_out" require "chef/resource/file" require "iniparse" class Chef class Provider class SystemdUnit < Chef::Provider include Chef::Mixin::Which include Chef::Mixin::ShellOut provides :systemd_unit, os: "linux" def load_current_resource @current_resource = Chef::Resource::SystemdUnit.new(new_resource.name) current_resource.content(::File.read(unit_path)) if ::File.exist?(unit_path) current_resource.user(new_resource.user) current_resource.enabled(enabled?) current_resource.active(active?) current_resource.masked(masked?) current_resource.static(static?) current_resource.triggers_reload(new_resource.triggers_reload) current_resource end def define_resource_requirements super requirements.assert(:create) do |a| a.assertion { IniParse.parse(new_resource.to_ini) } a.failure_message "Unit content is not valid INI text" end end def action_create if current_resource.content != new_resource.to_ini converge_by("creating unit: #{new_resource.name}") do manage_unit_file(:create) daemon_reload if new_resource.triggers_reload end end end def action_delete if ::File.exist?(unit_path) converge_by("deleting unit: #{new_resource.name}") do manage_unit_file(:delete) daemon_reload if new_resource.triggers_reload end end end def action_enable if current_resource.static Chef::Log.debug("#{new_resource.name} is a static unit, enabling is a NOP.") end unless current_resource.enabled || current_resource.static converge_by("enabling unit: #{new_resource.name}") do systemctl_execute!(:enable, new_resource.name) end end end def action_disable if current_resource.static Chef::Log.debug("#{new_resource.name} is a static unit, disabling is a NOP.") end if current_resource.enabled && !current_resource.static converge_by("disabling unit: #{new_resource.name}") do systemctl_execute!(:disable, new_resource.name) end end end def action_mask unless current_resource.masked converge_by("masking unit: #{new_resource.name}") do systemctl_execute!(:mask, new_resource.name) end end end def action_unmask if current_resource.masked converge_by("unmasking unit: #{new_resource.name}") do systemctl_execute!(:unmask, new_resource.name) end end end def action_start unless current_resource.active converge_by("starting unit: #{new_resource.name}") do systemctl_execute!(:start, new_resource.name) end end end def action_stop if current_resource.active converge_by("stopping unit: #{new_resource.name}") do systemctl_execute!(:stop, new_resource.name) end end end def action_restart converge_by("restarting unit: #{new_resource.name}") do systemctl_execute!(:restart, new_resource.name) end end def action_reload if current_resource.active converge_by("reloading unit: #{new_resource.name}") do systemctl_execute!(:reload, new_resource.name) end else Chef::Log.debug("#{new_resource.name} is not active, skipping reload.") 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 def enabled? systemctl_execute("is-enabled", new_resource.name).exitstatus == 0 end def masked? systemctl_execute(:status, new_resource.name).stdout.include?("masked") end def static? systemctl_execute("is-enabled", new_resource.name).stdout.include?("static") end private def unit_path if new_resource.user "/etc/systemd/user/#{new_resource.name}" else "/etc/systemd/system/#{new_resource.name}" end end def manage_unit_file(action = :nothing) Chef::Resource::File.new(unit_path, run_context).tap do |f| f.owner "root" f.group "root" f.mode "0644" f.content new_resource.to_ini f.verify systemd_analyze_cmd if systemd_analyze_path end.run_action(action) end def daemon_reload shell_out_with_systems_locale!("#{systemctl_path} daemon-reload") end def systemctl_execute!(action, unit) shell_out_with_systems_locale!("#{systemctl_cmd} #{action} #{unit}", systemctl_opts) end def systemctl_execute(action, unit) shell_out("#{systemctl_cmd} #{action} #{unit}", systemctl_opts) end def systemctl_cmd @systemctl_cmd ||= "#{systemctl_path} #{systemctl_args}" end def systemctl_path @systemctl_path ||= which("systemctl") end def systemctl_args @systemctl_args ||= new_resource.user ? "--user" : "--system" end def systemctl_opts @systemctl_opts ||= if new_resource.user { :user => new_resource.user, :environment => { "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/#{node['etc']['passwd'][new_resource.user]['uid']}/bus", }, } else {} end end def systemd_analyze_cmd @systemd_analyze_cmd ||= "#{systemd_analyze_path} verify %{path}" end def systemd_analyze_path @systemd_analyze_path ||= which("systemd-analyze") end end end end chef-12.14.60/lib/chef/provider/template.rb000066400000000000000000000040501276456504500203340ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Daniel DeLeo () # 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/template_finder" require "chef/provider/file" require "chef/deprecation/provider/template" require "chef/deprecation/warnings" class Chef class Provider class Template < Chef::Provider::File provides :template extend Chef::Deprecation::Warnings include Chef::Deprecation::Provider::Template add_deprecation_warnings_for(Chef::Deprecation::Provider::Template.instance_methods) def initialize(new_resource, run_context) @content_class = Chef::Provider::Template::Content super end def load_current_resource @current_resource = Chef::Resource::Template.new(@new_resource.name) super end def define_resource_requirements super requirements.assert(:create, :create_if_missing) do |a| a.assertion { ::File.exists?(content.template_location) } a.failure_message "Template source #{content.template_location} could not be found." a.whyrun "Template source #{content.template_location} does not exist. Assuming it would have been created." a.block_action! end end private def managing_content? return true if @new_resource.checksum return true if !@new_resource.source.nil? && @action != :create_if_missing false end end end end chef-12.14.60/lib/chef/provider/template/000077500000000000000000000000001276456504500200105ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/template/content.rb000066400000000000000000000051621276456504500220130ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/template" require "chef/file_content_management/content_base" class Chef class Provider class Template class Content < Chef::FileContentManagement::ContentBase include Chef::Mixin::Template def template_location @template_file_cache_location ||= begin template_finder.find(new_resource.source, :local => new_resource.local, :cookbook => new_resource.cookbook) end end private def file_for_provider context = TemplateContext.new(new_resource.variables) context[:node] = run_context.node context[:template_finder] = template_finder # helper variables context[:cookbook_name] = new_resource.cookbook_name unless context.keys.include?(:coookbook_name) context[:recipe_name] = new_resource.recipe_name unless context.keys.include?(:recipe_name) context[:recipe_line_string] = new_resource.source_line unless context.keys.include?(:recipe_line_string) context[:recipe_path] = new_resource.source_line_file unless context.keys.include?(:recipe_path) context[:recipe_line] = new_resource.source_line_number unless context.keys.include?(:recipe_line) context[:template_name] = new_resource.source unless context.keys.include?(:template_name) context[:template_path] = template_location unless context.keys.include?(:template_path) context._extend_modules(new_resource.helper_modules) output = context.render_template(template_location) tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile tempfile.binmode tempfile.write(output) tempfile.close tempfile end def template_finder @template_finder ||= begin TemplateFinder.new(run_context, new_resource.cookbook_name, run_context.node) end end end end end end chef-12.14.60/lib/chef/provider/template_finder.rb000066400000000000000000000032031276456504500216620ustar00rootroot00000000000000#-- # Author:: Andrea Campi () # 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 Provider class TemplateFinder def initialize(run_context, cookbook_name, node) @run_context = run_context @cookbook_name = cookbook_name @node = node end def find(template_name, options = {}) template_name = template_source_name(template_name, options) if options[:local] return template_name end cookbook_name = find_cookbook_name(options) cookbook = @run_context.cookbook_collection[cookbook_name] cookbook.preferred_filename_on_disk_location(@node, :templates, template_name) end protected def template_source_name(name, options) if options[:source] options[:source] else name end end def find_cookbook_name(options) if options[:cookbook] options[:cookbook] else @cookbook_name end end end end end chef-12.14.60/lib/chef/provider/user.rb000066400000000000000000000152321276456504500175030ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "chef/mixin/command" require "etc" class Chef class Provider class User < Chef::Provider include Chef::Mixin::Command attr_accessor :user_exists, :locked def initialize(new_resource, run_context) super @user_exists = true @locked = nil @shadow_lib_ok = true @group_name_resolved = true end def convert_group_name if @new_resource.gid.is_a? String @new_resource.gid(Etc.getgrnam(@new_resource.gid).gid) end rescue ArgumentError => e @group_name_resolved = false end def whyrun_supported? true end def load_current_resource @current_resource = Chef::Resource::User.new(@new_resource.name) @current_resource.username(@new_resource.username) begin user_info = Etc.getpwnam(@new_resource.username) rescue ArgumentError => e @user_exists = false Chef::Log.debug("#{@new_resource} user does not exist") user_info = nil end if user_info @current_resource.uid(user_info.uid) @current_resource.gid(user_info.gid) @current_resource.home(user_info.dir) @current_resource.shell(user_info.shell) @current_resource.password(user_info.passwd) if @new_resource.comment user_info.gecos.force_encoding(@new_resource.comment.encoding) end @current_resource.comment(user_info.gecos) if @new_resource.password && @current_resource.password == "x" begin require "shadow" rescue LoadError @shadow_lib_ok = false else shadow_info = Shadow::Passwd.getspnam(@new_resource.username) @current_resource.password(shadow_info.sp_pwdp) end end convert_group_name if @new_resource.gid end @current_resource end def define_resource_requirements requirements.assert(:create, :modify, :manage, :lock, :unlock) do |a| a.assertion { @group_name_resolved } a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{@new_resource.gid}" a.whyrun "group name #{@new_resource.gid} does not exist. This will cause group assignment to fail. Assuming this group will have been created previously." end requirements.assert(:all_actions) do |a| a.assertion { @shadow_lib_ok } a.failure_message Chef::Exceptions::MissingLibrary, "You must have ruby-shadow installed for password support!" a.whyrun "ruby-shadow is not installed. Attempts to set user password will cause failure. Assuming that this gem will have been previously installed." + "Note that user update converge may report false-positive on the basis of mismatched password. " end requirements.assert(:modify, :lock, :unlock) do |a| a.assertion { @user_exists } a.failure_message(Chef::Exceptions::User, "Cannot modify user #{@new_resource.username} - does not exist!") a.whyrun("Assuming user #{@new_resource.username} would have been created") end end # Check to see if the user needs any changes # # === Returns # :: If a change is required # :: If the users are identical def compare_user changed = [ :comment, :home, :shell, :password ].select do |user_attrib| !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib) end changed += [ :uid, :gid ].select do |user_attrib| !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib).to_s != @current_resource.send(user_attrib).to_s end changed.any? end def action_create if !@user_exists converge_by("create user #{@new_resource.username}") do create_user Chef::Log.info("#{@new_resource} created") end elsif compare_user converge_by("alter user #{@new_resource.username}") do manage_user Chef::Log.info("#{@new_resource} altered") end end end def action_remove if @user_exists converge_by("remove user #{@new_resource.username}") do remove_user Chef::Log.info("#{@new_resource} removed") end end end def action_manage if @user_exists && compare_user converge_by("manage user #{@new_resource.username}") do manage_user Chef::Log.info("#{@new_resource} managed") end end end def action_modify if compare_user converge_by("modify user #{@new_resource.username}") do manage_user Chef::Log.info("#{@new_resource} modified") end end end def action_lock if check_lock() == false converge_by("lock the user #{@new_resource.username}") do lock_user Chef::Log.info("#{@new_resource} locked") end else Chef::Log.debug("#{@new_resource} already locked - nothing to do") end end def action_unlock if check_lock() == true converge_by("unlock user #{@new_resource.username}") do unlock_user Chef::Log.info("#{@new_resource} unlocked") end else Chef::Log.debug("#{@new_resource} already unlocked - nothing to do") end end def create_user raise NotImplementedError end def remove_user raise NotImplementedError end def manage_user raise NotImplementedError end def lock_user raise NotImplementedError end def unlock_user raise NotImplementedError end def check_lock raise NotImplementedError end end end end chef-12.14.60/lib/chef/provider/user/000077500000000000000000000000001276456504500171535ustar00rootroot00000000000000chef-12.14.60/lib/chef/provider/user/aix.rb000066400000000000000000000064421276456504500202670ustar00rootroot00000000000000# # 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 "chef/provider/user/useradd" class Chef class Provider class User class Aix < Chef::Provider::User::Useradd provides :user, os: "aix" provides :aix_user UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]] def create_user super add_password end def manage_user add_password manage_home super end # Aix does not support -r like other unix, sytem account is created by adding to 'system' group def useradd_options opts = [] opts << "-g" << "system" if new_resource.system opts end def check_lock lock_info = shell_out!("lsuser -a account_locked #{new_resource.username}") if whyrun_mode? && passwd_s.stdout.empty? && lock_info.stderr.match(/does not exist/) # if we're in whyrun mode and the user is not yet created we assume it would be return false end raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if lock_info.stdout.empty? status = /\S+\s+account_locked=(\S+)/.match(lock_info.stdout) if status && status[1] == "true" @locked = true else @locked = false end @locked end def lock_user shell_out!("chuser account_locked=true #{new_resource.username}") end def unlock_user shell_out!("chuser account_locked=false #{new_resource.username}") end private def add_password if @current_resource.password != @new_resource.password && @new_resource.password Chef::Log.debug("#{@new_resource.username} setting password to #{@new_resource.password}") command = "echo '#{@new_resource.username}:#{@new_resource.password}' | chpasswd -e" shell_out!(command) end end # Aix specific handling to update users home directory. def manage_home # -m option does not work on aix, so move dir. if updating_home? && managing_home_dir? universal_options.delete("-m") if ::File.directory?(@current_resource.home) Chef::Log.debug("Changing users home directory from #{@current_resource.home} to #{new_resource.home}") shell_out!("mv #{@current_resource.home} #{new_resource.home}") else Chef::Log.debug("Creating users home directory #{new_resource.home}") shell_out!("mkdir -p #{new_resource.home}") end end end end end end end chef-12.14.60/lib/chef/provider/user/dscl.rb000066400000000000000000000655761276456504500204500ustar00rootroot00000000000000# # Author:: Dreamcat4 () # Copyright:: Copyright 2009-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 "mixlib/shellout" require "chef/provider/user" require "openssl" require "plist" require "chef/util/path_helper" class Chef class Provider class User # # The most tricky bit of this provider is the way it deals with user passwords. # Mac OS X has different password shadow calculations based on the version. # < 10.7 => password shadow calculation format SALTED-SHA1 # => stored in: /var/db/shadow/hash/#{guid} # => shadow binary length 68 bytes # => First 4 bytes salt / Next 64 bytes shadow value # = 10.7 => password shadow calculation format SALTED-SHA512 # => stored in: /var/db/dslocal/nodes/Default/users/#{name}.plist # => shadow binary length 68 bytes # => First 4 bytes salt / Next 64 bytes shadow value # > 10.7 => password shadow calculation format SALTED-SHA512-PBKDF2 # => stored in: /var/db/dslocal/nodes/Default/users/#{name}.plist # => shadow binary length 128 bytes # => Salt / Iterations are stored separately in the same file # # This provider only supports Mac OSX versions 10.7 and above class Dscl < Chef::Provider::User attr_accessor :user_info attr_accessor :authentication_authority attr_accessor :password_shadow_conversion_algorithm provides :dscl_user provides :user, os: "darwin" def define_resource_requirements super requirements.assert(:all_actions) do |a| a.assertion { mac_osx_version_less_than_10_7? == false } a.failure_message(Chef::Exceptions::User, "Chef::Provider::User::Dscl only supports Mac OS X versions 10.7 and above.") end requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?("/usr/bin/dscl") } a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{new_resource}!") end requirements.assert(:all_actions) do |a| a.assertion { ::File.exists?("/usr/bin/plutil") } a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{new_resource}!") end requirements.assert(:create, :modify, :manage) do |a| a.assertion do if new_resource.password && mac_osx_version_greater_than_10_7? # SALTED-SHA512 password shadow hashes are not supported on 10.8 and above. !salted_sha512?(new_resource.password) else true end end a.failure_message(Chef::Exceptions::User, "SALTED-SHA512 passwords are not supported on Mac 10.8 and above. \ If you want to set the user password using shadow info make sure you specify a SALTED-SHA512-PBKDF2 shadow hash \ in 'password', with the associated 'salt' and 'iterations'.") end requirements.assert(:create, :modify, :manage) do |a| a.assertion do if new_resource.password && mac_osx_version_greater_than_10_7? && salted_sha512_pbkdf2?(new_resource.password) # salt and iterations should be specified when # SALTED-SHA512-PBKDF2 password shadow hash is given !new_resource.salt.nil? && !new_resource.iterations.nil? else true end end a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hash is given without associated \ 'salt' and 'iterations'. Please specify 'salt' and 'iterations' in order to set the user password using shadow hash.") end requirements.assert(:create, :modify, :manage) do |a| a.assertion do if new_resource.password && !mac_osx_version_greater_than_10_7? # On 10.7 SALTED-SHA512-PBKDF2 is not supported !salted_sha512_pbkdf2?(new_resource.password) else true end end a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hashes are not supported on \ Mac OS X version 10.7. Please specify a SALTED-SHA512 shadow hash in 'password' attribute to set the \ user password using shadow hash.") end end def load_current_resource @current_resource = Chef::Resource::User.new(new_resource.username) current_resource.username(new_resource.username) @user_info = read_user_info if user_info current_resource.uid(dscl_get(user_info, :uid)) current_resource.gid(dscl_get(user_info, :gid)) current_resource.home(dscl_get(user_info, :home)) current_resource.shell(dscl_get(user_info, :shell)) current_resource.comment(dscl_get(user_info, :comment)) @authentication_authority = dscl_get(user_info, :auth_authority) if new_resource.password && dscl_get(user_info, :password) == "********" # A password is set. Let's get the password information from shadow file shadow_hash_binary = dscl_get(user_info, :shadow_hash) # Calling shell_out directly since we want to give an input stream shadow_hash_xml = convert_binary_plist_to_xml(shadow_hash_binary.string) shadow_hash = Plist.parse_xml(shadow_hash_xml) if shadow_hash["SALTED-SHA512"] # Convert the shadow value from Base64 encoding to hex before consuming them @password_shadow_conversion_algorithm = "SALTED-SHA512" current_resource.password(shadow_hash["SALTED-SHA512"].string.unpack("H*").first) elsif shadow_hash["SALTED-SHA512-PBKDF2"] @password_shadow_conversion_algorithm = "SALTED-SHA512-PBKDF2" # Convert the entropy from Base64 encoding to hex before consuming them current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack("H*").first) current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"]) # Convert the salt from Base64 encoding to hex before consuming them current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack("H*").first) else raise(Chef::Exceptions::User, "Unknown shadow_hash format: #{shadow_hash.keys.join(' ')}") end end convert_group_name if new_resource.gid else @user_exists = false Chef::Log.debug("#{new_resource} user does not exist") end current_resource end # # Provider Actions # def create_user dscl_create_user # set_password modifies the plist file of the user directly. So update # the password first before making any modifications to the user. set_password dscl_create_comment dscl_set_uid dscl_set_gid dscl_set_home dscl_set_shell end def manage_user # set_password modifies the plist file of the user directly. So update # the password first before making any modifications to the user. set_password if diverged_password? dscl_create_user if diverged?(:username) dscl_create_comment if diverged?(:comment) dscl_set_uid if diverged?(:uid) dscl_set_gid if diverged?(:gid) dscl_set_home if diverged?(:home) dscl_set_shell if diverged?(:shell) end # # Action Helpers # # # Create a user using dscl # def dscl_create_user run_dscl("create /Users/#{new_resource.username}") end # # Saves the specified Chef user `comment` into RealName attribute # of Mac user. If `comment` is not specified, it takes `username` value. # def dscl_create_comment comment = new_resource.comment || new_resource.username run_dscl("create /Users/#{new_resource.username} RealName '#{comment}'") end # # Sets the user id for the user using dscl. # If a `uid` is not specified, it finds the next available one starting # from 200 if `system` is set, 500 otherwise. # def dscl_set_uid # XXX: mutates the new resource new_resource.uid(get_free_uid) if new_resource.uid.nil? || new_resource.uid == "" if uid_used?(new_resource.uid) raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{new_resource.uid} is already in use") end run_dscl("create /Users/#{new_resource.username} UniqueID #{new_resource.uid}") end # # Find the next available uid on the system. starting with 200 if `system` is set, # 500 otherwise. # def get_free_uid(search_limit = 1000) uid = nil base_uid = new_resource.system ? 200 : 500 next_uid_guess = base_uid users_uids = run_dscl("list /Users uid") while next_uid_guess < search_limit + base_uid if users_uids =~ Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n") next_uid_guess += 1 else uid = next_uid_guess break end end return uid || raise("uid not found. Exhausted. Searched #{search_limit} times") end # # Returns true if uid is in use by a different account, false otherwise. # def uid_used?(uid) return false unless uid users_uids = run_dscl("list /Users uid").split("\n") uid_map = users_uids.inject({}) do |tmap, tuid| x = tuid.split tmap[x[1]] = x[0] tmap end if uid_map[uid.to_s] unless uid_map[uid.to_s] == new_resource.username.to_s return true end end return false end # # Sets the group id for the user using dscl. Fails if a group doesn't # exist on the system with given group id. If `gid` is not specified, it # sets a default Mac user group "staff", with id 20. # def dscl_set_gid if new_resource.gid.nil? # XXX: mutates the new resource new_resource.gid(20) elsif !new_resource.gid.to_s.match(/^\d+$/) begin possible_gid = run_dscl("read /Groups/#{new_resource.gid} PrimaryGroupID").split(" ").last rescue Chef::Exceptions::DsclCommandFailed => e raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{new_resource.gid} when creating user #{new_resource.username}") end # XXX: mutates the new resource new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/) end run_dscl("create /Users/#{new_resource.username} PrimaryGroupID '#{new_resource.gid}'") end # # Sets the home directory for the user. If `:manage_home` is set home # directory is managed (moved / created) for the user. # def dscl_set_home if new_resource.home.nil? || new_resource.home.empty? run_dscl("delete /Users/#{new_resource.username} NFSHomeDirectory") return end if new_resource.supports[:manage_home] validate_home_dir_specification! if (current_resource.home == new_resource.home) && !new_home_exists? ditto_home elsif !current_home_exists? && !new_home_exists? ditto_home elsif current_home_exists? move_home end end run_dscl("create /Users/#{new_resource.username} NFSHomeDirectory '#{new_resource.home}'") end def validate_home_dir_specification! unless new_resource.home =~ /^\// raise(Chef::Exceptions::InvalidHomeDirectory, "invalid path spec for User: '#{new_resource.username}', home directory: '#{new_resource.home}'") end end def current_home_exists? ::File.exist?("#{current_resource.home}") end def new_home_exists? ::File.exist?("#{new_resource.home}") end def ditto_home skel = "/System/Library/User Template/English.lproj" raise(Chef::Exceptions::User, "can't find skel at: #{skel}") unless ::File.exists?(skel) shell_out! "ditto '#{skel}' '#{new_resource.home}'" ::FileUtils.chown_R(new_resource.username, new_resource.gid.to_s, new_resource.home) end def move_home Chef::Log.debug("#{new_resource} moving #{self} home from #{current_resource.home} to #{new_resource.home}") src = current_resource.home FileUtils.mkdir_p(new_resource.home) files = ::Dir.glob("#{Chef::Util::PathHelper.escape_glob_dir(src)}/*", ::File::FNM_DOTMATCH) - ["#{src}/.", "#{src}/.."] ::FileUtils.mv(files, new_resource.home, :force => true) ::FileUtils.rmdir(src) ::FileUtils.chown_R(new_resource.username, new_resource.gid.to_s, new_resource.home) end # # Sets the shell for the user using dscl. # def dscl_set_shell if new_resource.shell || ::File.exists?("#{new_resource.shell}") run_dscl("create /Users/#{new_resource.username} UserShell '#{new_resource.shell}'") else run_dscl("create /Users/#{new_resource.username} UserShell '/usr/bin/false'") end end # # Sets the password for the user based on given password parameters. # Chef supports specifying plain-text passwords and password shadow # hash data. # def set_password # Return if there is no password to set return if new_resource.password.nil? shadow_info = prepare_password_shadow_info # Shadow info is saved as binary plist. Convert the info to binary plist. shadow_info_binary = StringIO.new command = Mixlib::ShellOut.new("plutil -convert binary1 -o - -", :input => shadow_info.to_plist, :live_stream => shadow_info_binary) command.run_command if user_info.nil? # User is just created. read_user_info() will read the fresh information # for the user with a cache flush. However with experimentation we've seen # that dscl cache is not immediately updated after the creation of the user # This is odd and needs to be investigated further. sleep 3 @user_info = read_user_info end # Replace the shadow info in user's plist dscl_set(user_info, :shadow_hash, shadow_info_binary) save_user_info(user_info) end # # Prepares the password shadow info based on the platform version. # def prepare_password_shadow_info shadow_info = {} entropy = nil salt = nil iterations = nil if mac_osx_version_10_7? hash_value = if salted_sha512?(new_resource.password) new_resource.password else # Create a random 4 byte salt salt = OpenSSL::Random.random_bytes(4) encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + new_resource.password) hash_value = salt.unpack("H*").first + encoded_password end shadow_info["SALTED-SHA512"] = StringIO.new shadow_info["SALTED-SHA512"].string = convert_to_binary(hash_value) shadow_info else if salted_sha512_pbkdf2?(new_resource.password) entropy = convert_to_binary(new_resource.password) salt = convert_to_binary(new_resource.salt) iterations = new_resource.iterations else salt = OpenSSL::Random.random_bytes(32) iterations = new_resource.iterations # Use the default if not specified by the user entropy = OpenSSL::PKCS5.pbkdf2_hmac( new_resource.password, salt, iterations, 128, OpenSSL::Digest::SHA512.new ) end pbkdf_info = {} pbkdf_info["entropy"] = StringIO.new pbkdf_info["entropy"].string = entropy pbkdf_info["salt"] = StringIO.new pbkdf_info["salt"].string = salt pbkdf_info["iterations"] = iterations shadow_info["SALTED-SHA512-PBKDF2"] = pbkdf_info end shadow_info end # # Removes the user from the system after removing user from his groups # and deleting home directory if needed. # def remove_user if new_resource.supports[:manage_home] # Remove home directory FileUtils.rm_rf(current_resource.home) end # Remove the user from its groups run_dscl("list /Groups").each_line do |group| if member_of_group?(group.chomp) run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{new_resource.username}'") end end # Remove user account run_dscl("delete /Users/#{new_resource.username}") end # # Locks the user. # def lock_user run_dscl("append /Users/#{new_resource.username} AuthenticationAuthority ';DisabledUser;'") end # # Unlocks the user # def unlock_user auth_string = authentication_authority.gsub(/AuthenticationAuthority: /, "").gsub(/;DisabledUser;/, "").strip run_dscl("create /Users/#{new_resource.username} AuthenticationAuthority '#{auth_string}'") end # # Returns true if the user is locked, false otherwise. # def locked? if authentication_authority !!(authentication_authority =~ /DisabledUser/ ) else false end end # # This is the interface base User provider requires to provide idempotency. # def check_lock return @locked = locked? end # # Helper functions # # # Returns true if the system state and desired state is different for # given attribute. # def diverged?(parameter) parameter_updated?(parameter) && (not new_resource.send(parameter).nil?) end def parameter_updated?(parameter) not (new_resource.send(parameter) == current_resource.send(parameter)) end # # We need a special check function for password since we support both # plain text and shadow hash data. # # Checks if password needs update based on platform version and the # type of the password specified. # def diverged_password? return false if new_resource.password.nil? # Dscl provider supports both plain text passwords and shadow hashes. if mac_osx_version_10_7? if salted_sha512?(new_resource.password) diverged?(:password) else !salted_sha512_password_match? end else # When a system is upgraded to a version 10.7+ shadow hashes of the users # will be updated when the user logs in. So it's possible that we will have # SALTED-SHA512 password in the current_resource. In that case we will force # password to be updated. return true if salted_sha512?(current_resource.password) # Some system users don't have salts; this can happen if the system is # upgraded and the user hasn't logged in yet. In this case, we will force # the password to be updated. return true if current_resource.salt.nil? if salted_sha512_pbkdf2?(new_resource.password) diverged?(:password) || diverged?(:salt) || diverged?(:iterations) else !salted_sha512_pbkdf2_password_match? end end end # # Returns true if user is member of the specified group, false otherwise. # def member_of_group?(group_name) membership_info = "" begin membership_info = run_dscl("read /Groups/#{group_name}") rescue Chef::Exceptions::DsclCommandFailed # Raised if the group doesn't contain any members end # Output is something like: # GroupMembership: root admin etc members = membership_info.split(" ") members.shift # Get rid of GroupMembership: string members.include?(new_resource.username) end # # DSCL Helper functions # # A simple map of Chef's terms to DSCL's terms. DSCL_PROPERTY_MAP = { :uid => "uid", :gid => "gid", :home => "home", :shell => "shell", :comment => "realname", :password => "passwd", :auth_authority => "authentication_authority", :shadow_hash => "ShadowHashData", }.freeze # Directory where the user plist files are stored for versions 10.7 and above USER_PLIST_DIRECTORY = "/var/db/dslocal/nodes/Default/users".freeze # # Reads the user plist and returns a hash keyed with DSCL properties specified # in DSCL_PROPERTY_MAP. Return nil if the user is not found. # def read_user_info user_info = nil # We flush the cache here in order to make sure that we read fresh information # for the user. shell_out("dscacheutil '-flushcache'") begin user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist" user_plist_info = run_plutil("convert xml1 -o - #{user_plist_file}") user_info = Plist.parse_xml(user_plist_info) rescue Chef::Exceptions::PlistUtilCommandFailed end user_info end # # Saves the given hash keyed with DSCL properties specified # in DSCL_PROPERTY_MAP to the disk. # def save_user_info(user_info) user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist" Plist::Emit.save_plist(user_info, user_plist_file) run_plutil("convert binary1 #{user_plist_file}") end # # Sets a value in user information hash using Chef attributes as keys. # def dscl_set(user_hash, key, value) raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.keys.include?(key) user_hash[DSCL_PROPERTY_MAP[key]] = [ value ] user_hash end # # Gets a value from user information hash using Chef attributes as keys. # def dscl_get(user_hash, key) raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.keys.include?(key) # DSCL values are set as arrays value = user_hash[DSCL_PROPERTY_MAP[key]] value.nil? ? value : value.first end # # System Helpets # def mac_osx_version # This provider will only be invoked on node[:platform] == "mac_os_x" # We do not check or assert that here. node[:platform_version] end def mac_osx_version_10_7? mac_osx_version.start_with?("10.7.") end def mac_osx_version_less_than_10_7? versions = mac_osx_version.split(".") # Make integer comparison in order not to report 10.10 less than 10.7 (versions[0].to_i <= 10 && versions[1].to_i < 7) end def mac_osx_version_greater_than_10_7? versions = mac_osx_version.split(".") # Make integer comparison in order not to report 10.10 less than 10.7 (versions[0].to_i >= 10 && versions[1].to_i > 7) end def run_dscl(*args) result = shell_out("dscl . -#{args.join(' ')}") return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 ) raise(Chef::Exceptions::DsclCommandFailed, "dscl error: #{result.inspect}") unless result.exitstatus == 0 raise(Chef::Exceptions::DsclCommandFailed, "dscl error: #{result.inspect}") if result.stdout =~ /No such key: / result.stdout end def run_plutil(*args) result = shell_out("plutil -#{args.join(' ')}") raise(Chef::Exceptions::PlistUtilCommandFailed, "plutil error: #{result.inspect}") unless result.exitstatus == 0 if result.stdout.encoding == Encoding::ASCII_8BIT result.stdout.encode("utf-8", "binary", :undef => :replace, :invalid => :replace, :replace => "?") else result.stdout end end def convert_binary_plist_to_xml(binary_plist_string) Mixlib::ShellOut.new("plutil -convert xml1 -o - -", :input => binary_plist_string).run_command.stdout end def convert_to_binary(string) string.unpack("a2" * (string.size / 2)).collect { |i| i.hex.chr }.join end def salted_sha512?(string) !!(string =~ /^[[:xdigit:]]{136}$/) end def salted_sha512_password_match? # Salt is included in the first 4 bytes of shadow data salt = current_resource.password.slice(0, 8) shadow = OpenSSL::Digest::SHA512.hexdigest(convert_to_binary(salt) + new_resource.password) current_resource.password == salt + shadow end def salted_sha512_pbkdf2?(string) !!(string =~ /^[[:xdigit:]]{256}$/) end def salted_sha512_pbkdf2_password_match? salt = convert_to_binary(current_resource.salt) OpenSSL::PKCS5.pbkdf2_hmac( new_resource.password, salt, current_resource.iterations, 128, OpenSSL::Digest::SHA512.new ).unpack("H*").first == current_resource.password end end end end end chef-12.14.60/lib/chef/provider/user/linux.rb000066400000000000000000000102641276456504500206420ustar00rootroot00000000000000# # 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/provider/user" class Chef class Provider class User class Linux < Chef::Provider::User provides :linux_user provides :user, os: "linux" def create_user shell_out!(*clean_array("useradd", universal_options, useradd_options, new_resource.username)) end def manage_user shell_out!(*clean_array("usermod", universal_options, usermod_options, new_resource.username)) end def remove_user shell_out!(*clean_array("userdel", userdel_options, new_resource.username)) end def lock_user shell_out!(*clean_array("usermod", "-L", new_resource.username)) end def unlock_user shell_out!(*clean_array("usermod", "-U", new_resource.username)) end # common to usermod and useradd def universal_options opts = [] opts << "-c" << new_resource.comment if should_set?(:comment) opts << "-g" << new_resource.gid if should_set?(:gid) opts << "-p" << new_resource.password if should_set?(:password) opts << "-s" << new_resource.shell if should_set?(:shell) opts << "-u" << new_resource.uid if should_set?(:uid) opts << "-d" << new_resource.home if updating_home? opts << "-o" if new_resource.non_unique opts end def usermod_options opts = [] if updating_home? if new_resource.manage_home opts << "-m" end end opts end def useradd_options opts = [] opts << "-r" if new_resource.system if new_resource.manage_home opts << "-m" else opts << "-M" end opts end def userdel_options opts = [] opts << "-r" if new_resource.manage_home opts << "-f" if new_resource.force opts end def should_set?(sym) current_resource.send(sym).to_s != new_resource.send(sym).to_s && new_resource.send(sym) end def updating_home? return false unless new_resource.home return true unless current_resource.home new_resource.home && Pathname.new(current_resource.home).cleanpath != Pathname.new(new_resource.home).cleanpath end def check_lock # there's an old bug in rhel (https://bugzilla.redhat.com/show_bug.cgi?id=578534) # which means that both 0 and 1 can be success. passwd_s = shell_out("passwd", "-S", new_resource.username, returns: [ 0, 1 ]) # checking "does not exist" has to come before exit code handling since centos and ubuntu differ in exit codes if passwd_s.stderr =~ /does not exist/ if whyrun_mode? return false else raise Chef::Exceptions::User, "User #{new_resource.username} does not exist when checking lock status for #{new_resource}" end end # now raise if we didn't get a 0 or 1 (see above) passwd_s.error! # now the actual output parsing @locked = nil status_line = passwd_s.stdout.split(" ") @locked = false if status_line[1] =~ /^[PN]/ @locked = true if status_line[1] =~ /^L/ raise Chef::Exceptions::User, "Cannot determine if user #{new_resource.username} is locked for #{new_resource}" if @locked.nil? # FIXME: should probably go on the current_resource @locked end end end end end chef-12.14.60/lib/chef/provider/user/pw.rb000066400000000000000000000071371276456504500201360ustar00rootroot00000000000000# # Author:: Stephen Haynes () # Copyright:: Copyright 2009-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/user" class Chef class Provider class User class Pw < Chef::Provider::User provides :pw_user provides :user, os: "freebsd" def load_current_resource super raise Chef::Exceptions::User, "Could not find binary /usr/sbin/pw for #{@new_resource}" unless ::File.exists?("/usr/sbin/pw") end def create_user command = "pw useradd" command << set_options run_command(:command => command) modify_password end def manage_user command = "pw usermod" command << set_options run_command(:command => command) modify_password end def remove_user command = "pw userdel #{@new_resource.username}" command << " -r" if @new_resource.supports[:manage_home] run_command(:command => command) end def check_lock case @current_resource.password when /^\*LOCKED\*/ @locked = true else @locked = false end @locked end def lock_user run_command(:command => "pw lock #{@new_resource.username}") end def unlock_user run_command(:command => "pw unlock #{@new_resource.username}") end def set_options opts = " #{@new_resource.username}" field_list = { "comment" => "-c", "home" => "-d", "gid" => "-g", "uid" => "-u", "shell" => "-s", } field_list.sort { |a, b| a[0] <=> b[0] }.each do |field, option| field_symbol = field.to_sym if @current_resource.send(field_symbol) != @new_resource.send(field_symbol) if @new_resource.send(field_symbol) Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field_symbol)}") opts << " #{option} '#{@new_resource.send(field_symbol)}'" end end end if @new_resource.supports[:manage_home] Chef::Log.debug("#{@new_resource} is managing the users home directory") opts << " -m" end opts end def modify_password if (not @new_resource.password.nil?) && (@current_resource.password != @new_resource.password) Chef::Log.debug("#{new_resource} updating password") command = "pw usermod #{@new_resource.username} -H 0" status = popen4(command, :waitlast => true) do |pid, stdin, stdout, stderr| stdin.puts "#{@new_resource.password}" end unless status.exitstatus == 0 raise Chef::Exceptions::User, "pw failed - #{status.inspect}!" end else Chef::Log.debug("#{new_resource} no change needed to password") end end end end end end chef-12.14.60/lib/chef/provider/user/solaris.rb000066400000000000000000000071161276456504500211610ustar00rootroot00000000000000# # Author:: Stephen Nelson-Smith () # Author:: Jon Ramsey () # Author:: Dave Eddy () # Copyright:: Copyright 2012-2016, Chef Software Inc. # Copyright:: Copyright 2015-2016, Dave Eddy # 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/user/useradd" class Chef class Provider class User class Solaris < Chef::Provider::User::Useradd provides :solaris_user provides :user, os: %w{omnios solaris2} UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]] attr_writer :password_file def initialize(new_resource, run_context) @password_file = "/etc/shadow" super end def create_user super manage_password end def manage_user manage_password super end def check_lock shadow_line = shell_out!("getent", "shadow", new_resource.username).stdout.strip rescue nil # if the command fails we return nil, this can happen if the user # in question doesn't exist return nil if shadow_line.nil? # convert "dave:NP:16507::::::\n" to "NP" fields = shadow_line.split(":") # '*LK*...' and 'LK' are both considered locked, # so look for LK at the beginning of the shadow entry # optionally surrounded by '*' @locked = !!fields[1].match(/^\*?LK\*?/) @locked end def lock_user shell_out!("passwd", "-l", new_resource.username) end def unlock_user shell_out!("passwd", "-u", new_resource.username) end private def manage_password if @current_resource.password != @new_resource.password && @new_resource.password Chef::Log.debug("#{@new_resource} setting password to #{@new_resource.password}") write_shadow_file end end def write_shadow_file buffer = Tempfile.new("shadow", "/etc") ::File.open(@password_file) do |shadow_file| shadow_file.each do |entry| user = entry.split(":").first if user == @new_resource.username buffer.write(updated_password(entry)) else buffer.write(entry) end end end buffer.close # FIXME: mostly duplicates code with file provider deploying a file s = ::File.stat(@password_file) mode = s.mode & 07777 uid = s.uid gid = s.gid FileUtils.chown uid, gid, buffer.path FileUtils.chmod mode, buffer.path FileUtils.mv buffer.path, @password_file end def updated_password(entry) fields = entry.split(":") fields[1] = @new_resource.password fields[2] = days_since_epoch fields.join(":") end def days_since_epoch (Time.now.to_i / 86400).floor end end end end end chef-12.14.60/lib/chef/provider/user/useradd.rb000066400000000000000000000130021276456504500211230ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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 "pathname" require "chef/provider/user" class Chef class Provider class User class Useradd < Chef::Provider::User # MAJOR XXX: this should become the base class of all Useradd providers instead of the linux implementation UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:password, "-p"], [:shell, "-s"], [:uid, "-u"]] def create_user command = compile_command("useradd") do |useradd| useradd.concat(universal_options) useradd.concat(useradd_options) end shell_out!(*command) end def manage_user unless universal_options.empty? command = compile_command("usermod") do |u| u.concat(universal_options) end shell_out!(*command) end end def remove_user command = [ "userdel" ] command << "-r" if managing_home_dir? command << "-f" if new_resource.force command << new_resource.username shell_out!(*command) end def check_lock # we can get an exit code of 1 even when it's successful on # rhel/centos (redhat bug 578534). See additional error checks below. passwd_s = shell_out!("passwd", "-S", new_resource.username, :returns => [0, 1]) if whyrun_mode? && passwd_s.stdout.empty? && passwd_s.stderr.match(/does not exist/) # if we're in whyrun mode and the user is not yet created we assume it would be return false end raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if passwd_s.stdout.empty? status_line = passwd_s.stdout.split(" ") case status_line[1] when /^P/ @locked = false when /^N/ @locked = false when /^L/ @locked = true end unless passwd_s.exitstatus == 0 raise_lock_error = false if %w{redhat centos}.include?(node[:platform]) passwd_version_check = shell_out!("rpm -q passwd") passwd_version = passwd_version_check.stdout.chomp unless passwd_version == "passwd-0.73-1" raise_lock_error = true end else raise_lock_error = true end raise Chef::Exceptions::User, "Cannot determine if #{new_resource} is locked!" if raise_lock_error end @locked end def lock_user shell_out!("usermod", "-L", new_resource.username) end def unlock_user shell_out!("usermod", "-U", new_resource.username) end def compile_command(base_command) base_command = Array(base_command) yield base_command base_command << new_resource.username base_command end def universal_options @universal_options ||= begin opts = [] # magic allows UNIVERSAL_OPTIONS to be overridden in a subclass self.class::UNIVERSAL_OPTIONS.each do |field, option| update_options(field, option, opts) end if updating_home? opts << "-d" << new_resource.home if managing_home_dir? Chef::Log.debug("#{new_resource} managing the users home directory") opts << "-m" else Chef::Log.debug("#{new_resource} setting home to #{new_resource.home}") end end opts << "-o" if new_resource.non_unique opts end end def update_options(field, option, opts) if @current_resource.send(field).to_s != new_resource.send(field).to_s if new_resource.send(field) Chef::Log.debug("#{new_resource} setting #{field} to #{new_resource.send(field)}") opts << option << new_resource.send(field).to_s end end end def useradd_options opts = [] opts << "-r" if new_resource.system opts << "-M" unless managing_home_dir? opts end def updating_home? # will return false if paths are equivalent # Pathname#cleanpath does a better job than ::File::expand_path (on both unix and windows) # ::File.expand_path("///tmp") == ::File.expand_path("/tmp") => false # ::File.expand_path("\\tmp") => "C:/tmp" return true if @current_resource.home.nil? && new_resource.home new_resource.home && Pathname.new(@current_resource.home).cleanpath != Pathname.new(new_resource.home).cleanpath end def managing_home_dir? new_resource.manage_home || new_resource.supports[:manage_home] end end end end end chef-12.14.60/lib/chef/provider/user/windows.rb000066400000000000000000000100121276456504500211640ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/provider/user" require "chef/exceptions" if RUBY_PLATFORM =~ /mswin|mingw32|windows/ require "chef/util/windows/net_user" end class Chef class Provider class User class Windows < Chef::Provider::User provides :windows_user provides :user, os: "windows" def initialize(new_resource, run_context) super @net_user = Chef::Util::Windows::NetUser.new(@new_resource.username) end def load_current_resource if @new_resource.gid Chef::Log.warn("The 'gid' attribute is not implemented by the Windows platform. Please use the 'group' resource to assign a user to a group.") end @current_resource = Chef::Resource::User.new(@new_resource.name) @current_resource.username(@new_resource.username) user_info = nil begin user_info = @net_user.get_info @current_resource.uid(user_info[:user_id]) @current_resource.comment(user_info[:full_name]) @current_resource.home(user_info[:home_dir]) @current_resource.shell(user_info[:script_path]) rescue Chef::Exceptions::UserIDNotFound => e # e.message should be "The user name could not be found" but checking for that could cause a localization bug @user_exists = false Chef::Log.debug("#{@new_resource} does not exist (#{e.message})") end @current_resource end # Check to see if the user needs any changes # # === Returns # :: If a change is required # :: If the users are identical def compare_user unless @net_user.validate_credentials(@new_resource.password) Chef::Log.debug("#{@new_resource} password has changed") return true end [ :uid, :comment, :home, :shell ].any? do |user_attrib| !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib) end end def create_user @net_user.add(set_options) end def manage_user @net_user.update(set_options) end def remove_user @net_user.delete end def check_lock @net_user.check_enabled end def lock_user @net_user.disable_account end def unlock_user @net_user.enable_account end def set_options opts = { :name => @new_resource.username } field_list = { "comment" => "full_name", "home" => "home_dir", "uid" => "user_id", "shell" => "script_path", "password" => "password", } field_list.sort { |a, b| a[0] <=> b[0] }.each do |field, option| field_symbol = field.to_sym if @current_resource.send(field_symbol) != @new_resource.send(field_symbol) if @new_resource.send(field_symbol) unless field_symbol == :password Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field_symbol)}") end opts[option.to_sym] = @new_resource.send(field_symbol) end end end opts end end end end end chef-12.14.60/lib/chef/provider/whyrun_safe_ruby_block.rb000066400000000000000000000021421276456504500232660ustar00rootroot00000000000000# # Author:: Phil Dibowitz () # Copyright:: Copyright 2013-2016, Facebook # 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 Provider class WhyrunSafeRubyBlock < Chef::Provider::RubyBlock provides :whyrun_safe_ruby_block def action_run @new_resource.block.call @new_resource.updated_by_last_action(true) @run_context.events.resource_update_applied(@new_resource, :create, "execute the whyrun_safe_ruby_block #{@new_resource.name}") Chef::Log.info("#{@new_resource} called") end end end end chef-12.14.60/lib/chef/provider/windows_script.rb000066400000000000000000000043121276456504500216000ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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/script" require "chef/mixin/windows_architecture_helper" class Chef class Provider class WindowsScript < Chef::Provider::Script attr_reader :is_forced_32bit protected include Chef::Mixin::WindowsArchitectureHelper def initialize( new_resource, run_context, script_extension = "") super( new_resource, run_context ) @script_extension = script_extension target_architecture = if new_resource.architecture.nil? node_windows_architecture(run_context.node) else new_resource.architecture end @is_wow64 = wow64_architecture_override_required?(run_context.node, target_architecture) @is_forced_32bit = forced_32bit_override_required?(run_context.node, target_architecture) end public def action_run wow64_redirection_state = nil if @is_wow64 wow64_redirection_state = disable_wow64_file_redirection(@run_context.node) end begin super rescue raise ensure if ! wow64_redirection_state.nil? restore_wow64_file_redirection(@run_context.node, wow64_redirection_state) end end end def script_file base_script_name = "chef-script" temp_file_arguments = [ base_script_name, @script_extension ] @script_file ||= Tempfile.open(temp_file_arguments) end end end end chef-12.14.60/lib/chef/provider/yum_repository.rb000066400000000000000000000105521276456504500216360ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/dsl/declare_resource" require "chef/mixin/shell_out" require "chef/mixin/which" require "chef/http/simple" require "chef/provider/noop" class Chef class Provider class YumRepository < Chef::Provider use_inline_resources extend Chef::Mixin::Which provides :yum_repository do which "yum" end def whyrun_supported?; true; end def load_current_resource; end action :create do declare_resource(:template, "/etc/yum.repos.d/#{new_resource.repositoryid}.repo") do if template_available?(new_resource.source) source new_resource.source else source ::File.expand_path("../support/yum_repo.erb", __FILE__) local true end sensitive new_resource.sensitive variables(config: new_resource) mode new_resource.mode if new_resource.make_cache notifies :run, "execute[yum clean metadata #{new_resource.repositoryid}]", :immediately if new_resource.clean_metadata || new_resource.clean_headers notifies :run, "execute[yum-makecache-#{new_resource.repositoryid}]", :immediately notifies :create, "ruby_block[yum-cache-reload-#{new_resource.repositoryid}]", :immediately end end declare_resource(:execute, "yum clean metadata #{new_resource.repositoryid}") do command "yum clean metadata --disablerepo=* --enablerepo=#{new_resource.repositoryid}" action :nothing end # get the metadata for this repo only declare_resource(:execute, "yum-makecache-#{new_resource.repositoryid}") do command "yum -q -y makecache --disablerepo=* --enablerepo=#{new_resource.repositoryid}" action :nothing only_if { new_resource.enabled } end # reload internal Chef yum cache declare_resource(:ruby_block, "yum-cache-reload-#{new_resource.repositoryid}") do block { Chef::Provider::Package::Yum::YumCache.instance.reload } action :nothing end end action :delete do declare_resource(:file, "/etc/yum.repos.d/#{new_resource.repositoryid}.repo") do action :delete notifies :run, "execute[yum clean all #{new_resource.repositoryid}]", :immediately notifies :create, "ruby_block[yum-cache-reload-#{new_resource.repositoryid}]", :immediately end declare_resource(:execute, "yum clean all #{new_resource.repositoryid}") do command "yum clean all --disablerepo=* --enablerepo=#{new_resource.repositoryid}" only_if "yum repolist | grep -P '^#{new_resource.repositoryid}([ \t]|$)'" action :nothing end declare_resource(:ruby_block, "yum-cache-reload-#{new_resource.repositoryid}") do block { Chef::Provider::Package::Yum::YumCache.instance.reload } action :nothing end end action :makecache do declare_resource(:execute, "yum-makecache-#{new_resource.repositoryid}") do command "yum -q -y makecache --disablerepo=* --enablerepo=#{new_resource.repositoryid}" action :run only_if { new_resource.enabled } end declare_resource(:ruby_block, "yum-cache-reload-#{new_resource.repositoryid}") do block { Chef::Provider::Package::Yum::YumCache.instance.reload } action :run end end alias_method :action_add, :action_create alias_method :action_remove, :action_delete def template_available?(path) !path.nil? && run_context.has_template_in_cookbook?(new_resource.cookbook_name, path) end end end end Chef::Provider::Noop.provides :yum_repository chef-12.14.60/lib/chef/provider_resolver.rb000066400000000000000000000143231276456504500204460ustar00rootroot00000000000000# # Author:: Richard Manyanza () # Copyright:: Copyright 2014-2016, Richard Manyanza. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/exceptions" require "chef/platform/priority_map" class Chef # # Provider Resolution # =================== # # Provider resolution is the process of taking a Resource object and an # action, and determining the Provider class that should be instantiated to # handle the action. # # If the resource has its `provider` set, that is used. # # Otherwise, we take the lists of Providers that have registered as # providing the DSL through `provides :dsl_name, ` or # `Chef.set_resource_priority_array :dsl_name, `. We filter each # list of Providers through: # # 1. The filters it was registered with (such as `os: 'linux'` or # `platform_family: 'debian'`) # 2. `provides?(node, resource)` # 3. `supports?(resource, action)` # # Anything that passes the filter and returns `true` to provides and supports, # is considered a match. The first matching Provider in the *most recently # registered list* is selected and returned. # class ProviderResolver attr_reader :node attr_reader :resource attr_reader :action def initialize(node, resource, action) @node = node @resource = resource @action = action end def resolve maybe_explicit_provider(resource) || maybe_dynamic_provider_resolution(resource, action) || maybe_chef_platform_lookup(resource) end # Does NOT call provides? on the resource (it is assumed this is being # called *from* provides?). def provided_by?(provider_class) potential_handlers.include?(provider_class) end def enabled_handlers @enabled_handlers ||= potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource) } end # TODO deprecate this and allow actions to be passed as a filter to # `provides` so we don't have to have two separate things. # @api private def supported_handlers enabled_handlers.select { |handler| handler.supports?(resource, action) } end private def potential_handlers handler_map.list(node, resource.resource_name).uniq end # The list of handlers, with any in the priority_map moved to the front def prioritized_handlers @prioritized_handlers ||= begin supported_handlers = self.supported_handlers if supported_handlers.empty? # if none of the providers specifically support the resource, we still need to pick one of the providers that are # enabled on the node to handle the why-run use case. FIXME we should only do this in why-run mode then. Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway." supported_handlers = enabled_handlers end prioritized = priority_map.list(node, resource.resource_name).flatten(1) prioritized &= supported_handlers # Filter the priority map by the actual enabled handlers prioritized |= supported_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set) prioritized end end # if resource.provider is set, just return one of those objects def maybe_explicit_provider(resource) return nil unless resource.provider resource.provider end # try dynamically finding a provider based on querying the providers to see what they support def maybe_dynamic_provider_resolution(resource, action) Chef::Log.debug "Providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}" handler = prioritized_handlers.first if handler Chef::Log.debug "Provider for action #{action} on resource #{resource} is #{handler}" else Chef::Log.debug "Dynamic provider resolver FAILED to resolve a provider for action #{action} on resource #{resource}" end handler end # try the old static lookup of providers by platform def maybe_chef_platform_lookup(resource) Chef::Platform.find_provider_for_node(node, resource) end def priority_map Chef.provider_priority_map end def handler_map Chef.provider_handler_map end def overrode_provides?(handler) handler.method(:provides?).owner != Chef::Provider.method(:provides?).owner end module Deprecated # return a deterministically sorted list of Chef::Provider subclasses def providers @providers ||= Chef::Provider.descendants end def enabled_handlers @enabled_handlers ||= begin handlers = super if handlers.empty? # Look through all providers, and find ones that return true to provides. # Don't bother with ones that don't override provides?, since they # would have been in enabled_handlers already if that were so. (It's a # perf concern otherwise.) handlers = providers.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource) } handlers.each do |handler| Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!") Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") end end handlers end end end prepend Deprecated end end chef-12.14.60/lib/chef/providers.rb000066400000000000000000000117521276456504500167130ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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/apt_update" require "chef/provider/apt_repository" require "chef/provider/batch" require "chef/provider/breakpoint" require "chef/provider/cookbook_file" require "chef/provider/cron" require "chef/provider/cron/solaris" require "chef/provider/cron/aix" require "chef/provider/deploy" require "chef/provider/directory" require "chef/provider/dsc_script" require "chef/provider/dsc_resource" require "chef/provider/env" require "chef/provider/erl_call" require "chef/provider/execute" require "chef/provider/file" require "chef/provider/git" require "chef/provider/group" require "chef/provider/http_request" require "chef/provider/ifconfig" require "chef/provider/launchd" require "chef/provider/link" require "chef/provider/log" require "chef/provider/ohai" require "chef/provider/mdadm" require "chef/provider/mount" require "chef/provider/noop" require "chef/provider/package" require "chef/provider/powershell_script" require "chef/provider/osx_profile" require "chef/provider/reboot" require "chef/provider/remote_directory" require "chef/provider/remote_file" require "chef/provider/route" require "chef/provider/ruby_block" require "chef/provider/script" require "chef/provider/service" require "chef/provider/subversion" require "chef/provider/systemd_unit" require "chef/provider/template" require "chef/provider/user" require "chef/provider/whyrun_safe_ruby_block" require "chef/provider/yum_repository" require "chef/provider/env/windows" require "chef/provider/package/apt" require "chef/provider/package/chocolatey" require "chef/provider/package/dpkg" require "chef/provider/package/easy_install" require "chef/provider/package/freebsd/port" require "chef/provider/package/freebsd/pkg" require "chef/provider/package/freebsd/pkgng" require "chef/provider/package/homebrew" require "chef/provider/package/ips" require "chef/provider/package/macports" require "chef/provider/package/openbsd" require "chef/provider/package/pacman" require "chef/provider/package/portage" require "chef/provider/package/paludis" require "chef/provider/package/rpm" require "chef/provider/package/rubygems" require "chef/provider/package/yum" require "chef/provider/package/zypper" require "chef/provider/package/solaris" require "chef/provider/package/smartos" require "chef/provider/package/aix" require "chef/provider/service/arch" require "chef/provider/service/freebsd" require "chef/provider/service/gentoo" require "chef/provider/service/init" require "chef/provider/service/invokercd" require "chef/provider/service/debian" require "chef/provider/service/openbsd" require "chef/provider/service/redhat" require "chef/provider/service/insserv" require "chef/provider/service/simple" require "chef/provider/service/systemd" require "chef/provider/service/upstart" require "chef/provider/service/windows" require "chef/provider/service/solaris" require "chef/provider/service/macosx" require "chef/provider/service/aixinit" require "chef/provider/service/aix" require "chef/provider/user/aix" require "chef/provider/user/dscl" require "chef/provider/user/linux" require "chef/provider/user/pw" require "chef/provider/user/solaris" require "chef/provider/user/useradd" require "chef/provider/user/windows" require "chef/provider/group/aix" require "chef/provider/group/dscl" require "chef/provider/group/gpasswd" require "chef/provider/group/groupadd" require "chef/provider/group/groupmod" require "chef/provider/group/pw" require "chef/provider/group/suse" require "chef/provider/group/usermod" require "chef/provider/group/windows" require "chef/provider/mount/mount" require "chef/provider/mount/aix" require "chef/provider/mount/solaris" require "chef/provider/mount/windows" require "chef/provider/deploy/revision" require "chef/provider/deploy/timestamped" require "chef/provider/remote_file/ftp" require "chef/provider/remote_file/sftp" require "chef/provider/remote_file/http" require "chef/provider/remote_file/local_file" require "chef/provider/remote_file/network_file" require "chef/provider/remote_file/fetcher" require "chef/provider/lwrp_base" require "chef/provider/registry_key" require "chef/provider/file/content" require "chef/provider/remote_file/content" require "chef/provider/cookbook_file/content" require "chef/provider/template/content" require "chef/provider/ifconfig/redhat" require "chef/provider/ifconfig/debian" require "chef/provider/ifconfig/aix" chef-12.14.60/lib/chef/recipe.rb000066400000000000000000000062401276456504500161410ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Christopher Walters () # 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/dsl/recipe" require "chef/mixin/from_file" require "chef/mixin/deprecation" class Chef # == Chef::Recipe # A Recipe object is the context in which Chef recipes are evaluated. class Recipe attr_accessor :cookbook_name, :recipe_name, :recipe, :params, :run_context include Chef::DSL::Recipe include Chef::Mixin::FromFile include Chef::Mixin::Deprecation # Parses a potentially fully-qualified recipe name into its # cookbook name and recipe short name. # # For example: # "aws::elastic_ip" returns [:aws, "elastic_ip"] # "aws" returns [:aws, "default"] # "::elastic_ip" returns [ current_cookbook, "elastic_ip" ] #-- # TODO: Duplicates functionality of RunListItem def self.parse_recipe_name(recipe_name, current_cookbook: nil) case recipe_name when /(.+?)::(.+)/ [ $1.to_sym, $2 ] when /^::(.+)/ raise "current_cookbook is nil, cannot resolve #{recipe_name}" if current_cookbook.nil? [ current_cookbook.to_sym, $1 ] else [ recipe_name.to_sym, "default" ] end end def initialize(cookbook_name, recipe_name, run_context) @cookbook_name = cookbook_name @recipe_name = recipe_name @run_context = run_context # TODO: 5/19/2010 cw/tim: determine whether this can be removed @params = Hash.new end # Used in DSL mixins def node run_context.node end # Used by the DSL to look up resources when executing in the context of a # recipe. def resources(*args) run_context.resource_collection.find(*args) end # This was moved to Chef::Node#tag, redirecting here for compatibility def tag(*tags) run_context.node.tag(*tags) end # Returns true if the node is tagged with *all* of the supplied +tags+. # # === Parameters # tags:: A list of tags # # === Returns # true:: If all the parameters are present # false:: If any of the parameters are missing def tagged?(*tags) tags.each do |tag| return false unless run_context.node.tags.include?(tag) end true end # Removes the list of tags from the node. # # === Parameters # tags:: A list of tags # # === Returns # tags:: The current list of run_context.node.tags def untag(*tags) tags.each do |tag| run_context.node.tags.delete(tag) end end end end chef-12.14.60/lib/chef/request_id.rb000066400000000000000000000017321276456504500170370ustar00rootroot00000000000000# Author:: Prajakta Purohit () # Copyright:: Copyright 2009-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 "securerandom" require "singleton" class Chef class RequestID include Singleton def reset_request_id @request_id = nil end def request_id @request_id ||= generate_request_id end def generate_request_id SecureRandom.uuid end end end chef-12.14.60/lib/chef/reserved_names.rb000066400000000000000000000004671276456504500177010ustar00rootroot00000000000000class Chef # This module exists to hide conflicting constant names from the DSL. # Hopefully we'll have a better/prettier/more sustainable solution in the # future, but for now this will fix a regression introduced in Chef 0.10.10 # (conflict with the Win32 namespace) module ReservedNames end end chef-12.14.60/lib/chef/resource.rb000066400000000000000000001516341276456504500165310ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: John Keiser ( "foobar") # find(:file => [ "foobar", "baz" ]) # find("file[foobar]", "file[baz]") # find("file[foobar,baz]") # # Calls `run_context.resource_collection.find(*args)` # # @return the matching resource, or an Array of matching resources. # # @raise ArgumentError if you feed it bad lookup information # @raise RuntimeError if it can't find the resources you are looking for. # def resources(*args) run_context.resource_collection.find(*args) end # # Resource User Interface (for users) # # # Create a new Resource. # # @param name The name of this resource (corresponds to the #name attribute, # used for notifications to this resource). # @param run_context The context of the Chef run. Corresponds to #run_context. # def initialize(name, run_context = nil) name(name) unless name.nil? @run_context = run_context @noop = nil @before = nil @params = Hash.new @provider = nil @allowed_actions = self.class.allowed_actions.to_a @action = self.class.default_action @updated = false @updated_by_last_action = false @supports = {} @ignore_failure = false @retries = 0 @retry_delay = 2 @not_if = [] @only_if = [] @source_line = nil # We would like to raise an error when the user gives us a guard # interpreter and a ruby_block to the guard. In order to achieve this # we need to understand when the user overrides the default guard # interpreter. Therefore we store the default separately in a different # attribute. @guard_interpreter = nil @default_guard_interpreter = :default @elapsed_time = 0 @sensitive = false end # # The action or actions that will be taken when this resource is run. # # @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`) # @return [Array[Symbol]] the list of actions. # def action(arg = nil) if arg arg = Array(arg).map(&:to_sym) arg.each do |action| validate( { action: action }, { action: { kind_of: Symbol, equal_to: allowed_actions } } ) end @action = arg else @action end end # Alias for normal assigment syntax. alias_method :action=, :action # # Sets up a notification that will run a particular action on another resource # if and when *this* resource is updated by an action. # # If the action does not update this resource, the notification never triggers. # # Only one resource may be specified per notification. # # `delayed` notifications will only *ever* happen once per resource, so if # multiple resources all notify a single resource to perform the same action, # the action will only happen once. However, if they ask for different # actions, each action will happen once, in the order they were updated. # # `immediate` notifications will cause the action to be triggered once per # notification, regardless of how many other resources have triggered the # notification as well. # # @param action The action to run on the other resource. # @param resource_spec [String, Hash, Chef::Resource] The resource to run. # @param timing [String, Symbol] When to notify. Has these values: # - `delayed`: Will run the action on the other resource after all other # actions have been run. This is the default. # - `immediate`, `immediately`: Will run the action on the other resource # immediately (before any other action is run). # - `before`: Will run the action on the other resource # immediately *before* the action is actually run. # # @example Resource by string # file '/foo.txt' do # content 'hi' # notifies :create, 'file[/bar.txt]' # end # file '/bar.txt' do # action :nothing # content 'hi' # end # @example Resource by hash # file '/foo.txt' do # content 'hi' # notifies :create, file: '/bar.txt' # end # file '/bar.txt' do # action :nothing # content 'hi' # end # @example Direct Resource # bar = file '/bar.txt' do # action :nothing # content 'hi' # end # file '/foo.txt' do # content 'hi' # notifies :create, bar # end # def notifies(action, resource_spec, timing = :delayed) # when using old-style resources(:template => "/foo.txt") style, you # could end up with multiple resources. validate_resource_spec!(resource_spec) resources = [ resource_spec ].flatten resources.each do |resource| case timing.to_s when "delayed" notifies_delayed(action, resource) when "immediate", "immediately" notifies_immediately(action, resource) when "before" notifies_before(action, resource) else raise ArgumentError, "invalid timing: #{timing} for notifies(#{action}, #{resources.inspect}, #{timing}) resource #{self} "\ "Valid timings are: :delayed, :immediate, :immediately, :before" end end true end # # Token class to hold an unresolved subscribes call with an associated # run context. # # @api private # @see Resource#subscribes class UnresolvedSubscribes < self # The full key ise given as the name in {Resource#subscribes} alias_method :to_s, :name alias_method :declared_key, :name end # # Subscribes to updates from other resources, causing a particular action to # run on *this* resource when the other resource is updated. # # If multiple resources are specified, this resource action will be run if # *any* of them change. # # This notification will only trigger *once*, no matter how many other # resources are updated (or how many actions are run by a particular resource). # # @param action The action to run on the other resource. # @param resources [String, Resource, Array[String, Resource]] The resources to subscribe to. # @param timing [String, Symbol] When to notify. Has these values: # - `delayed`: An update will cause the action to run after all other # actions have been run. This is the default. # - `immediate`, `immediately`: The action will run immediately following # the other resource being updated. # - `before`: The action will run immediately before the # other resource is updated. # # @example Resources by string # file '/foo.txt' do # content 'hi' # action :nothing # subscribes :create, 'file[/bar.txt]' # end # file '/bar.txt' do # content 'hi' # end # @example Direct resource # bar = file '/bar.txt' do # content 'hi' # end # file '/foo.txt' do # content 'hi' # action :nothing # subscribes :create, '/bar.txt' # end # @example Multiple resources by string # file '/foo.txt' do # content 'hi' # action :nothing # subscribes :create, [ 'file[/bar.txt]', 'file[/baz.txt]' ] # end # file '/bar.txt' do # content 'hi' # end # file '/baz.txt' do # content 'hi' # end # @example Multiple resources # bar = file '/bar.txt' do # content 'hi' # end # baz = file '/bar.txt' do # content 'hi' # end # file '/foo.txt' do # content 'hi' # action :nothing # subscribes :create, [ bar, baz ] # end # def subscribes(action, resources, timing = :delayed) resources = [resources].flatten resources.each do |resource| if resource.is_a?(String) resource = UnresolvedSubscribes.new(resource, run_context) end if resource.run_context.nil? resource.run_context = run_context end resource.notifies(action, self, timing) end true end # # A command or block that indicates whether to actually run this resource. # The command or block is run just before the action actually executes, and # the action will be skipped if the block returns false. # # If a block is specified, it must return `true` in order for the Resource # to be executed. # # If a command is specified, the resource's #guard_interpreter will run the # command and interpret the results according to `opts`. For example, the # default `execute` resource will be treated as `false` if the command # returns a non-zero exit code, and `true` if it returns 0. Thus, in the # default case: # # - `only_if "your command"` will perform the action only if `your command` # returns 0. # - `only_if "your command", valid_exit_codes: [ 1, 2, 3 ]` will perform the # action only if `your command` returns 1, 2, or 3. # # @param command [String] A string to execute. # @param opts [Hash] Options control the execution of the command # @param block [Proc] A ruby block to run. Ignored if a command is given. # def only_if(command = nil, opts = {}, &block) if command || block_given? @only_if << Conditional.only_if(self, command, opts, &block) end @only_if end # # A command or block that indicates whether to actually run this resource. # The command or block is run just before the action actually executes, and # the action will be skipped if the block returns true. # # If a block is specified, it must return `false` in order for the Resource # to be executed. # # If a command is specified, the resource's #guard_interpreter will run the # command and interpret the results according to `opts`. For example, the # default `execute` resource will be treated as `false` if the command # returns a non-zero exit code, and `true` if it returns 0. Thus, in the # default case: # # - `not_if "your command"` will perform the action only if `your command` # returns a non-zero code. # - `only_if "your command", valid_exit_codes: [ 1, 2, 3 ]` will perform the # action only if `your command` returns something other than 1, 2, or 3. # # @param command [String] A string to execute. # @param opts [Hash] Options control the execution of the command # @param block [Proc] A ruby block to run. Ignored if a command is given. # def not_if(command = nil, opts = {}, &block) if command || block_given? @not_if << Conditional.not_if(self, command, opts, &block) end @not_if end # # The number of times to retry this resource if it fails by throwing an # exception while running an action. Default: 0 # # When the retries have run out, the Resource will throw the last # exception. # # @param arg [Integer] The number of retries. # @return [Integer] The number of retries. # def retries(arg = nil) set_or_return(:retries, arg, kind_of: Integer) end attr_writer :retries # # The number of seconds to wait between retries. Default: 2. # # @param arg [Integer] The number of seconds to wait between retries. # @return [Integer] The number of seconds to wait between retries. # def retry_delay(arg = nil) set_or_return(:retry_delay, arg, kind_of: Integer) end attr_writer :retry_delay # # Whether to treat this resource's data as sensitive. If set, no resource # data will be displayed in log output. # # @param arg [Boolean] Whether this resource is sensitive or not. # @return [Boolean] Whether this resource is sensitive or not. # def sensitive(arg = nil) set_or_return(:sensitive, arg, :kind_of => [ TrueClass, FalseClass ]) end attr_writer :sensitive # ??? TODO unreferenced. Delete? attr_reader :not_if_args # ??? TODO unreferenced. Delete? attr_reader :only_if_args # # The time it took (in seconds) to run the most recently-run action. Not # cumulative across actions. This is set to 0 as soon as a new action starts # running, and set to the elapsed time at the end of the action. # # @return [Integer] The time (in seconds) it took to process the most recent # action. Not cumulative. # attr_reader :elapsed_time # # The guard interpreter that will be used to process `only_if` and `not_if` # statements. If left unset, the #default_guard_interpreter will be used. # # Must be a resource class like `Chef::Resource::Execute`, or # a corresponding to the name of a resource. The resource must descend from # `Chef::Resource::Execute`. # # TODO this needs to be coerced on input so that retrieval is consistent. # # @param arg [Class, Symbol, String] The Guard interpreter resource class/ # symbol/name. # @return [Class, Symbol, String] The Guard interpreter resource. # def guard_interpreter(arg = nil) if arg.nil? @guard_interpreter || @default_guard_interpreter else set_or_return(:guard_interpreter, arg, :kind_of => Symbol) end end # # Get the value of the state attributes in this resource as a hash. # # Does not include properties that are not set (unless they are identity # properties). # # @return [Hash{Symbol => Object}] A Hash of attribute => value for the # Resource class's `state_attrs`. # def state_for_resource_reporter state = {} state_properties = self.class.state_properties state_properties.each do |property| if property.identity? || property.is_set?(self) state[property.name] = property.sensitive? ? "*sensitive value suppressed*" : send(property.name) end end state end # # Since there are collisions with LWRP parameters named 'state' this # method is not used by the resource_reporter and is most likely unused. # It certainly cannot be relied upon and cannot be fixed. # # @deprecated # alias_method :state, :state_for_resource_reporter # # The value of the identity of this resource. # # - If there are no identity properties on the resource, `name` is returned. # - If there is exactly one identity property on the resource, it is returned. # - If there are more than one, they are returned in a hash. # # @return [Object,Hash] The identity of this resource. # def identity result = {} identity_properties = self.class.identity_properties identity_properties.each do |property| result[property.name] = send(property.name) end return result.values.first if identity_properties.size == 1 result end # # Whether to ignore failures. If set to `true`, and this resource when an # action is run, the resource will be marked as failed but no exception will # be thrown (and no error will be output). Defaults to `false`. # # TODO ignore_failure and retries seem to be mutually exclusive; I doubt # that was intended. # # @param arg [Boolean] Whether to ignore failures. # @return Whether this resource will ignore failures. # def ignore_failure(arg = nil) set_or_return(:ignore_failure, arg, kind_of: [ TrueClass, FalseClass ]) end attr_writer :ignore_failure # # Equivalent to #ignore_failure. # alias :epic_fail :ignore_failure # # Make this resource into an exact (shallow) copy of the other resource. # # @param resource [Chef::Resource] The resource to copy from. # def load_from(resource) resource.instance_variables.each do |iv| unless iv == :@source_line || iv == :@action || iv == :@not_if || iv == :@only_if self.instance_variable_set(iv, resource.instance_variable_get(iv)) end end end # # Runs the given action on this resource, immediately. # # @param action The action to run (e.g. `:create`) # @param notification_type The notification type that triggered this (if any) # @param notifying_resource The resource that triggered this notification (if any) # # @raise Any error that occurs during the actual action. # def run_action(action, notification_type = nil, notifying_resource = nil) # reset state in case of multiple actions on the same resource. @elapsed_time = 0 start_time = Time.now events.resource_action_start(self, action, notification_type, notifying_resource) # Try to resolve lazy/forward references in notifications again to handle # the case where the resource was defined lazily (ie. in a ruby_block) resolve_notification_references validate_action(action) if Chef::Config[:verbose_logging] || Chef::Log.level == :debug # This can be noisy Chef::Log.info("Processing #{self} action #{action} (#{defined_at})") end # ensure that we don't leave @updated_by_last_action set to true # on accident updated_by_last_action(false) # Don't modify @retries directly and keep it intact, so that the # recipe_snippet from ResourceFailureInspector can print the value # that was set in the resource block initially. remaining_retries = retries begin return if should_skip?(action) provider_for_action(action).run_action rescue Exception => e if ignore_failure Chef::Log.error("#{custom_exception_message(e)}; ignore_failure is set, continuing") events.resource_failed(self, action, e) elsif remaining_retries > 0 events.resource_failed_retriable(self, action, remaining_retries, e) remaining_retries -= 1 Chef::Log.info("Retrying execution of #{self}, #{remaining_retries} attempt(s) left") sleep retry_delay retry else events.resource_failed(self, action, e) raise customize_exception(e) end end ensure @elapsed_time = Time.now - start_time # Reporting endpoint doesn't accept a negative resource duration so set it to 0. # A negative value can occur when a resource changes the system time backwards @elapsed_time = 0 if @elapsed_time < 0 events.resource_completed(self) end # # If we are currently initializing the resource, this will be true. # # Do NOT use this. It may be removed. It is for internal purposes only. # @api private attr_reader :resource_initializing def resource_initializing=(value) if value @resource_initializing = true else remove_instance_variable(:@resource_initializing) end end # # Generic Ruby and Data Structure Stuff (for user) # def to_s "#{resource_name}[#{name}]" end def to_text return "suppressed sensitive resource output" if sensitive ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS text = "# Declared in #{@source_line}\n\n" text << "#{resource_name}(\"#{name}\") do\n" ivars.each do |ivar| if (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?) value_string = value.respond_to?(:to_text) ? value.to_text : value.inspect text << " #{ivar.to_s.sub(/^@/, '')} #{value_string}\n" end end [@not_if, @only_if].flatten.each do |conditional| text << " #{conditional.to_text}\n" end text << "end\n" end def inspect ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS ivars.inject("<#{self}") do |str, ivar| str << " #{ivar}: #{instance_variable_get(ivar).inspect}" end << ">" end # as_json does most of the to_json heavy lifted. It exists here in case activesupport # is loaded. activesupport will call as_json and skip over to_json. This ensure # json is encoded as expected def as_json(*a) safe_ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS instance_vars = Hash.new safe_ivars.each do |iv| instance_vars[iv.to_s.sub(/^@/, "")] = instance_variable_get(iv) end { "json_class" => self.class.name, "instance_vars" => instance_vars, } end # Serialize this object as a hash def to_json(*a) results = as_json Chef::JSONCompat.to_json(results, *a) end def to_hash # Grab all current state, then any other ivars (backcompat) result = {} self.class.state_properties.each do |p| result[p.name] = p.get(self) end safe_ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS safe_ivars.each do |iv| key = iv.to_s.sub(/^@/, "").to_sym next if result.has_key?(key) result[key] = instance_variable_get(iv) end result end def self.json_create(o) resource = self.new(o["instance_vars"]["@name"]) o["instance_vars"].each do |k, v| resource.instance_variable_set("@#{k}".to_sym, v) end resource end # # Resource Definition Interface (for resource developers) # include Chef::Mixin::Deprecation # # The provider class for this resource. # # If `action :x do ... end` has been declared on this resource or its # superclasses, this will return the `action_class`. # # If this is not set, `provider_for_action` will dynamically determine the # provider. # # @param arg [String, Symbol, Class] Sets the provider class for this resource. # If passed a String or Symbol, e.g. `:file` or `"file"`, looks up the # provider based on the name. # # @return The provider class for this resource. # # @see Chef::Resource.action_class # def provider(arg = nil) klass = if arg.kind_of?(String) || arg.kind_of?(Symbol) lookup_provider_constant(arg) else arg end set_or_return(:provider, klass, kind_of: [ Class ]) || self.class.action_class end def provider=(arg) provider(arg) end # # Set or return the list of "state properties" implemented by the Resource # subclass. # # Equivalent to calling #state_properties and getting `state_properties.keys`. # # @deprecated Use state_properties.keys instead. Note that when you declare # properties with `property`: properties are added to state_properties by # default, and can be turned off with `desired_state: false` # # ```ruby # property :x # part of desired state # property :y, desired_state: false # not part of desired state # ``` # # @param names [Array] A list of property names to set as desired # state. # # @return [Array] All property names with desired state. # def self.state_attrs(*names) state_properties(*names).map { |property| property.name } end # # Set the identity of this resource to a particular property. # # This drives #identity, which returns data that uniquely refers to a given # resource on the given node (in such a way that it can be correlated # across Chef runs). # # This method is unnecessary when declaring properties with `property`; # properties can be added to identity during declaration with # `identity: true`. # # ```ruby # property :x, identity: true # part of identity # property :y # not part of identity # ``` # # @param name [Symbol] A list of property names to set as the identity. # # @return [Symbol] The identity property if there is only one; or `nil` if # there are more than one. # # @raise [ArgumentError] If no arguments are passed and the resource has # more than one identity property. # def self.identity_property(name = nil) result = identity_properties(*Array(name)) if result.size > 1 raise Chef::Exceptions::MultipleIdentityError, "identity_property cannot be called on an object with more than one identity property (#{result.map { |r| r.name }.join(", ")})." end result.first end # # Set a property as the "identity attribute" for this resource. # # Identical to calling #identity_property.first.key. # # @param name [Symbol] The name of the property to set. # # @return [Symbol] # # @deprecated `identity_property` should be used instead. # # @raise [ArgumentError] If no arguments are passed and the resource has # more than one identity property. # def self.identity_attr(name = nil) property = identity_property(name) return nil if !property property.name end # # The guard interpreter that will be used to process `only_if` and `not_if` # statements by default. If left unset, or set to `:default`, the guard # interpreter used will be Chef::GuardInterpreter::DefaultGuardInterpreter. # # Must be a resource class like `Chef::Resource::Execute`, or # a corresponding to the name of a resource. The resource must descend from # `Chef::Resource::Execute`. # # TODO this needs to be coerced on input so that retrieval is consistent. # # @return [Class, Symbol, String] the default Guard interpreter resource. # attr_reader :default_guard_interpreter # # The list of actions this Resource is allowed to have. Setting `action` # will fail unless it is in this list. Default: [ :nothing ] # # @return [Array] The list of actions this Resource is allowed to # have. # attr_accessor :allowed_actions def allowed_actions(value = NOT_PASSED) if value != NOT_PASSED self.allowed_actions = value end @allowed_actions end # # Whether or not this resource was updated during an action. If multiple # actions are set on the resource, this will be `true` if *any* action # caused an update to happen. # # @return [Boolean] Whether the resource was updated during an action. # attr_reader :updated # # Whether or not this resource was updated during an action. If multiple # actions are set on the resource, this will be `true` if *any* action # caused an update to happen. # # @return [Boolean] Whether the resource was updated during an action. # def updated? updated end # # Whether or not this resource was updated during the most recent action. # This is set to `false` at the beginning of each action. # # @param true_or_false [Boolean] Whether the resource was updated during the # current / most recent action. # @return [Boolean] Whether the resource was updated during the most recent action. # def updated_by_last_action(true_or_false) @updated ||= true_or_false @updated_by_last_action = true_or_false end # # Whether or not this resource was updated during the most recent action. # This is set to `false` at the beginning of each action. # # @return [Boolean] Whether the resource was updated during the most recent action. # def updated_by_last_action? @updated_by_last_action end # # Set whether this class was updated during an action. # # @deprecated Multiple actions are supported by resources. Please call {}#updated_by_last_action} instead. # def updated=(true_or_false) Chef::Log.warn("Chef::Resource#updated=(true|false) is deprecated. Please call #updated_by_last_action(true|false) instead.") Chef::Log.warn("Called from:") caller[0..3].each { |line| Chef::Log.warn(line) } updated_by_last_action(true_or_false) @updated = true_or_false end # # The display name of this resource type, for printing purposes. # # Will be used to print out the resource in messages, e.g. resource_name[name] # # @return [Symbol] The name of this resource type (e.g. `:execute`). # def resource_name @resource_name || self.class.resource_name end # # Sets a list of capabilities of the real resource. For example, `:remount` # (for filesystems) and `:restart` (for services). # # TODO Calling resource.supports({}) will not set this to empty; it will do # a get instead. That's wrong. # # @param args Hash{Symbol=>Boolean} If non-empty, sets the capabilities of # this resource. Default: {} # @return Hash{Symbol=>Boolean} An array of things this resource supports. # def supports(args = {}) if args.any? @supports = args else @supports end end def supports=(args) supports(args) end # # A hook called after a resource is created. Meant to be overriden by # subclasses. # def after_created nil end # # The DSL name of this resource (e.g. `package` or `yum_package`) # # @return [String] The DSL name of this resource. # # @deprecated Use resource_name instead. # def self.dsl_name Chef.log_deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead." if name name = self.name.split("::")[-1] convert_to_snake_case(name) end end # # The display name of this resource type, for printing purposes. # # This also automatically calls "provides" to provide DSL with the given # name. # # resource_name defaults to your class name. # # Call `resource_name nil` to remove the resource name (and any # corresponding DSL). # # @param value [Symbol] The desired name of this resource type (e.g. # `execute`), or `nil` if this class is abstract and has no resource_name. # # @return [Symbol] The name of this resource type (e.g. `:execute`). # def self.resource_name(name = NOT_PASSED) # Setter if name != NOT_PASSED remove_canonical_dsl # Set the resource_name and call provides if name name = name.to_sym # If our class is not already providing this name, provide it. if !Chef::ResourceResolver.includes_handler?(name, self) provides name, canonical: true end @resource_name = name else @resource_name = nil end end @resource_name end def self.resource_name=(name) resource_name(name) end # # Use the class name as the resource name. # # Munges the last part of the class name from camel case to snake case, # and sets the resource_name to that: # # A::B::BlahDBlah -> blah_d_blah # def self.use_automatic_resource_name automatic_name = convert_to_snake_case(self.name.split("::")[-1]) resource_name automatic_name end # # The module where Chef should look for providers for this resource. # The provider for `MyResource` will be looked up using # `provider_base::MyResource`. Defaults to `Chef::Provider`. # # @param arg [Module] The module containing providers for this resource # @return [Module] The module containing providers for this resource # # @example # class MyResource < Chef::Resource # provider_base Chef::Provider::Deploy # # ...other stuff # end # # @deprecated Use `provides` on the provider, or `provider` on the resource, instead. # def self.provider_base(arg = nil) if arg Chef.log_deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.") end @provider_base ||= arg || Chef::Provider end # # The list of allowed actions for the resource. # # @param actions [Array] The list of actions to add to allowed_actions. # # @return [Array] The list of actions, as symbols. # def self.allowed_actions(*actions) @allowed_actions ||= if superclass.respond_to?(:allowed_actions) superclass.allowed_actions.dup else [ :nothing ] end @allowed_actions |= actions.flatten end def self.allowed_actions=(value) @allowed_actions = value.uniq end # # The action that will be run if no other action is specified. # # Setting default_action will automatially add the action to # allowed_actions, if it isn't already there. # # Defaults to [:nothing]. # # @param action_name [Symbol,Array] The default action (or series # of actions) to use. # # @return [Array] The default actions for the resource. # def self.default_action(action_name = NOT_PASSED) unless action_name.equal?(NOT_PASSED) @default_action = Array(action_name).map(&:to_sym) self.allowed_actions |= @default_action end if @default_action @default_action elsif superclass.respond_to?(:default_action) superclass.default_action else [:nothing] end end def self.default_action=(action_name) default_action action_name end # # Define an action on this resource. # # The action is defined as a *recipe* block that will be compiled and then # converged when the action is taken (when Resource is converged). The recipe # has access to the resource's attributes and methods, as well as the Chef # recipe DSL. # # Resources in the action recipe may notify and subscribe to other resources # within the action recipe, but cannot notify or subscribe to resources # in the main Chef run. # # Resource actions are *inheritable*: if resource A defines `action :create` # and B is a subclass of A, B gets all of A's actions. Additionally, # resource B can define `action :create` and call `super()` to invoke A's # action code. # # The first action defined (besides `:nothing`) will become the default # action for the resource. # # @param name [Symbol] The action name to define. # @param recipe_block The recipe to run when the action is taken. This block # takes no parameters, and will be evaluated in a new context containing: # # - The resource's public and protected methods (including attributes) # - The Chef Recipe DSL (file, etc.) # - super() referring to the parent version of the action (if any) # # @return The Action class implementing the action # def self.action(action, &recipe_block) action = action.to_sym declare_action_class action_class.action(action, &recipe_block) self.allowed_actions += [ action ] default_action action if Array(default_action) == [:nothing] end # # Define a method to load up this resource's properties with the current # actual values. # # @param load_block The block to load. Will be run in the context of a newly # created resource with its identity values filled in. # def self.load_current_value(&load_block) define_method(:load_current_value!, &load_block) end # # Call this in `load_current_value` to indicate that the value does not # exist and that `current_resource` should therefore be `nil`. # # @raise Chef::Exceptions::CurrentValueDoesNotExist # def current_value_does_not_exist! raise Chef::Exceptions::CurrentValueDoesNotExist end # # Get the current actual value of this resource. # # This does not cache--a new value will be returned each time. # # @return A new copy of the resource, with values filled in from the actual # current value. # def current_value provider = provider_for_action(Array(action).first) if provider.whyrun_mode? && !provider.whyrun_supported? raise "Cannot retrieve #{self.class.current_resource} in why-run mode: #{provider} does not support why-run" end provider.load_current_resource provider.current_resource end # # The action class is an automatic `Provider` created to handle # actions declared by `action :x do ... end`. # # This class will be returned by `resource.provider` if `resource.provider` # is not set. `provider_for_action` will also use this instead of calling # out to `Chef::ProviderResolver`. # # If the user has not declared actions on this class or its superclasses # using `action :x do ... end`, then there is no need for this class and # `action_class` will be `nil`. # # If a block is passed, the action_class is always created and the block is # run inside it. # # @api private # def self.action_class(&block) return @action_class if @action_class && !block # If the superclass needed one, then we need one as well. if block || (superclass.respond_to?(:action_class) && superclass.action_class) @action_class = declare_action_class(&block) end @action_class end # # Ensure the action class actually gets created. This is called # when the user does `action :x do ... end`. # # If a block is passed, it is run inside the action_class. # # @api private def self.declare_action_class(&block) @action_class ||= begin if superclass.respond_to?(:action_class) base_provider = superclass.action_class end base_provider ||= Chef::Provider resource_class = self Class.new(base_provider) do include ActionClass self.resource_class = resource_class end end @action_class.class_eval(&block) if block @action_class end # # Internal Resource Interface (for Chef) # FORBIDDEN_IVARS = [:@run_context, :@not_if, :@only_if, :@enclosing_provider] HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@not_if, :@only_if, :@elapsed_time, :@enclosing_provider] include Chef::Mixin::ConvertToClassName extend Chef::Mixin::ConvertToClassName # XXX: this is required for definition params inside of the scope of a # subresource to work correctly. attr_accessor :params # @return [Chef::RunContext] The run context for this Resource. This is # where the context for the current Chef run is stored, including the node # and the resource collection. attr_accessor :run_context # @return [String] The cookbook this resource was declared in. attr_accessor :cookbook_name # @return [String] The recipe this resource was declared in. attr_accessor :recipe_name # @return [Chef::Provider] The provider this resource was declared in (if # it was declared in an LWRP). When you call methods that do not exist # on this Resource, Chef will try to call the method on the provider # as well before giving up. attr_accessor :enclosing_provider # @return [String] The source line where this resource was declared. # Expected to come from caller() or a stack trace, it usually follows one # of these formats: # /some/path/to/file.rb:80:in `wombat_tears' # C:/some/path/to/file.rb:80 in 1`wombat_tears' attr_accessor :source_line # @return [String] The actual name that was used to create this resource. # Sometimes, when you say something like `package 'blah'`, the system will # create a different resource (i.e. `YumPackage`). When this happens, the # user will expect to see the thing they wrote, not the type that was # returned. May be `nil`, in which case callers should read #resource_name. # See #declared_key. attr_accessor :declared_type # # Iterates over all immediate and delayed notifications, calling # resolve_resource_reference on each in turn, causing them to # resolve lazy/forward references. def resolve_notification_references run_context.before_notifications(self).each do |n| n.resolve_resource_reference(run_context.resource_collection) end run_context.immediate_notifications(self).each do |n| n.resolve_resource_reference(run_context.resource_collection) end run_context.delayed_notifications(self).each do |n| n.resolve_resource_reference(run_context.resource_collection) end end # Helper for #notifies def notifies_before(action, resource_spec) run_context.notifies_before(Notification.new(resource_spec, action, self)) end # Helper for #notifies def notifies_immediately(action, resource_spec) run_context.notifies_immediately(Notification.new(resource_spec, action, self)) end # Helper for #notifies def notifies_delayed(action, resource_spec) run_context.notifies_delayed(Notification.new(resource_spec, action, self)) end class << self # back-compat # NOTE: that we do not support unregistering classes as descendants like # we used to for LWRP unloading because that was horrible and removed in # Chef-12. # @deprecated # @api private alias :resource_classes :descendants # @deprecated # @api private alias :find_subclass_by_name :find_descendants_by_name end # @deprecated # @api private # We memoize a sorted version of descendants so that resource lookups don't # have to sort all the things, all the time. # This was causing performance issues in test runs, and probably in real # life as well. @@sorted_descendants = nil def self.sorted_descendants @@sorted_descendants ||= descendants.sort_by { |x| x.to_s } end def self.inherited(child) super @@sorted_descendants = nil # set resource_name automatically if it's not set if child.name && !child.resource_name if child.name =~ /^Chef::Resource::(\w+)$/ child.resource_name(convert_to_snake_case($1)) end end end # If an unknown method is invoked, determine whether the enclosing Provider's # lexical scope can fulfill the request. E.g. This happens when the Resource's # block invokes new_resource. def method_missing(method_symbol, *args, &block) if enclosing_provider && enclosing_provider.respond_to?(method_symbol) enclosing_provider.send(method_symbol, *args, &block) else raise NoMethodError, "undefined method `#{method_symbol}' for #{self.class}" end end # # Mark this resource as providing particular DSL. # # Resources have an automatic DSL based on their resource_name, equivalent to # `provides :resource_name` (providing the resource on all OS's). If you # declare a `provides` with the given resource_name, it *replaces* that # provides (so that you can provide your resource DSL only on certain OS's). # def self.provides(name, **options, &block) name = name.to_sym # `provides :resource_name, os: 'linux'`) needs to remove the old # canonical DSL before adding the new one. if @resource_name && name == @resource_name remove_canonical_dsl end result = Chef.resource_handler_map.set(name, self, options, &block) Chef::DSL::Resources.add_resource_dsl(name) result end def self.provides?(node, resource_name) Chef::ResourceResolver.new(node, resource_name).provided_by?(self) end # Helper for #notifies def validate_resource_spec!(resource_spec) run_context.resource_collection.validate_lookup_spec!(resource_spec) end # We usually want to store and reference resources by their declared type and not the actual type that # was looked up by the Resolver (IE, "package" becomes YumPackage class). If we have not been provided # the declared key we want to fall back on the old to_s key. def declared_key return to_s if declared_type.nil? "#{declared_type}[#{@name}]" end def before_notifications run_context.before_notifications(self) end def immediate_notifications run_context.immediate_notifications(self) end def delayed_notifications run_context.delayed_notifications(self) end def source_line_file if source_line source_line.match(/(.*):(\d+):?.*$/).to_a[1] else nil end end def source_line_number if source_line source_line.match(/(.*):(\d+):?.*$/).to_a[2] else nil end end def defined_at # The following regexp should match these two sourceline formats: # /some/path/to/file.rb:80:in `wombat_tears' # C:/some/path/to/file.rb:80 in 1`wombat_tears' # extracting the path to the source file and the line number. if cookbook_name && recipe_name && source_line "#{cookbook_name}::#{recipe_name} line #{source_line_number}" elsif source_line "#{source_line_file} line #{source_line_number}" else "dynamically defined" end end # # The cookbook in which this Resource was defined (if any). # # @return Chef::CookbookVersion The cookbook in which this Resource was defined. # def cookbook_version if cookbook_name run_context.cookbook_collection[cookbook_name] end end def events run_context.events end def validate_action(action) raise ArgumentError, "nil is not a valid action for resource #{self}" if action.nil? end def provider_for_action(action) provider_class = Chef::ProviderResolver.new(node, self, action).resolve provider = provider_class.new(self, run_context) provider.action = action provider end # ??? TODO Seems unused. Delete? def noop(tf = nil) if !tf.nil? raise ArgumentError, "noop must be true or false!" unless tf == true || tf == false @noop = tf end @noop end # TODO Seems unused. Delete? def is(*args) if args.size == 1 args.first else return *args end end # # Preface an exception message with generic Resource information. # # @param e [StandardError] An exception with `e.message` # @return [String] An exception message customized with class name. # def custom_exception_message(e) "#{self} (#{defined_at}) had an error: #{e.class.name}: #{e.message}" end def customize_exception(e) new_exception = e.exception(custom_exception_message(e)) new_exception.set_backtrace(e.backtrace) new_exception end # Evaluates not_if and only_if conditionals. Returns a falsey value if any # of the conditionals indicate that this resource should be skipped, i.e., # if an only_if evaluates to false or a not_if evaluates to true. # # If this resource should be skipped, returns the first conditional that # "fails" its check. Subsequent conditionals are not evaluated, so in # general it's not a good idea to rely on side effects from not_if or # only_if commands/blocks being evaluated. # # Also skips conditional checking when the action is :nothing def should_skip?(action) conditional_action = ConditionalActionNotNothing.new(action) conditionals = [ conditional_action ] + only_if + not_if conditionals.find do |conditional| if conditional.continue? false else events.resource_skipped(self, action, conditional) Chef::Log.debug("Skipping #{self} due to #{conditional.description}") true end end end # Returns a resource based on a short_name and node # # ==== Parameters # short_name:: short_name of the resource (ie :directory) # node:: Node object to look up platform and version in # # === Returns # :: returns the proper Chef::Resource class def self.resource_for_node(short_name, node) klass = Chef::ResourceResolver.resolve(short_name, node: node) raise Chef::Exceptions::NoSuchResourceType.new(short_name, node) if klass.nil? klass end # # Returns the class with the given resource_name. # # ==== Parameters # short_name:: short_name of the resource (ie :directory) # # === Returns # :: returns the proper Chef::Resource class # def self.resource_matching_short_name(short_name) Chef::ResourceResolver.resolve(short_name, canonical: true) end # @api private def lookup_provider_constant(name, action = :nothing) begin self.class.provider_base.const_get(convert_to_class_name(name.to_s)) rescue NameError => e if e.to_s =~ /#{Regexp.escape(self.class.provider_base.to_s)}/ raise ArgumentError, "No provider found to match '#{name}'" else raise e end end end module DeprecatedLWRPClass # @api private def register_deprecated_lwrp_class(resource_class, class_name) if Chef::Resource.const_defined?(class_name, false) Chef::Log.warn "#{class_name} already exists! Deprecation class overwrites #{resource_class}" Chef::Resource.send(:remove_const, class_name) end if !Chef::Config[:treat_deprecation_warnings_as_errors] Chef::Resource.const_set(class_name, resource_class) Chef::Resource.deprecated_constants[class_name.to_sym] = resource_class end end def deprecated_constants raise "Deprecated constants should be called only on Chef::Resource" unless self == Chef::Resource @deprecated_constants ||= {} end end def self.remove_canonical_dsl if @resource_name remaining = Chef.resource_handler_map.delete_canonical(@resource_name, self) if !remaining Chef::DSL::Resources.remove_resource_dsl(@resource_name) end end end extend DeprecatedLWRPClass end end # Requiring things at the bottom breaks cycles require "chef/chef_class" chef-12.14.60/lib/chef/resource/000077500000000000000000000000001276456504500161725ustar00rootroot00000000000000chef-12.14.60/lib/chef/resource/action_class.rb000066400000000000000000000056301276456504500211650ustar00rootroot00000000000000# # Author:: John Keiser ("} action #{action ? action.inspect : ""}" end # # If load_current_value! is defined on the resource, use that. # def load_current_resource if new_resource.respond_to?(:load_current_value!) # dup the resource and then reset desired-state properties. current_resource = new_resource.dup # We clear desired state in the copy, because it is supposed to be actual state. # We keep identity properties and non-desired-state, which are assumed to be # "control" values like `recurse: true` current_resource.class.properties.each do |name, property| if property.desired_state? && !property.identity? && !property.name_property? property.reset(current_resource) end end # Call the actual load_current_value! method. If it raises # CurrentValueDoesNotExist, set current_resource to `nil`. begin # If the user specifies load_current_value do |desired_resource|, we # pass in the desired resource as well as the current one. if current_resource.method(:load_current_value!).arity > 0 current_resource.load_current_value!(new_resource) else current_resource.load_current_value! end rescue Chef::Exceptions::CurrentValueDoesNotExist current_resource = nil end end @current_resource = current_resource end def self.included(other) other.extend(ClassMethods) other.use_inline_resources other.include_resource_dsl true end module ClassMethods # # The Chef::Resource class this ActionClass was declared against. # # @return [Class] The Chef::Resource class this ActionClass was declared against. # attr_accessor :resource_class def to_s "#{resource_class} action provider" end def inspect to_s end end end end end chef-12.14.60/lib/chef/resource/apt_package.rb000066400000000000000000000017631276456504500207650ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package/apt" class Chef class Resource class AptPackage < Chef::Resource::Package resource_name :apt_package provides :package, os: "linux", platform_family: [ "debian" ] property :default_release, String, desired_state: false end end end chef-12.14.60/lib/chef/resource/apt_repository.rb000066400000000000000000000041701276456504500216040ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class AptRepository < Chef::Resource resource_name :apt_repository provides :apt_repository property :repo_name, String, name_property: true property :uri, String property :distribution, [ String, nil, false ], default: lazy { node["lsb"]["codename"] }, nillable: true, coerce: proc { |x| x ? x : nil } property :components, Array, default: [] property :arch, [String, nil, false], default: nil, nillable: true, coerce: proc { |x| x ? x : nil } property :trusted, [TrueClass, FalseClass], default: false # whether or not to add the repository as a source repo, too property :deb_src, [TrueClass, FalseClass], default: false property :keyserver, [String, nil, false], default: "keyserver.ubuntu.com", nillable: true, coerce: proc { |x| x ? x : nil } property :key, [String, nil, false], default: nil, nillable: true, coerce: proc { |x| x ? x : nil } property :key_proxy, [String, nil, false], default: nil, nillable: true, coerce: proc { |x| x ? x : nil } property :cookbook, [String, nil, false], default: nil, desired_state: false, nillable: true, coerce: proc { |x| x ? x : nil } property :cache_rebuild, [TrueClass, FalseClass], default: true, desired_state: false property :sensitive, [TrueClass, FalseClass], default: false, desired_state: false default_action :add allowed_actions :add, :remove end end end chef-12.14.60/lib/chef/resource/apt_update.rb000066400000000000000000000017331276456504500206510ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class AptUpdate < Chef::Resource resource_name :apt_update provides :apt_update, os: "linux" property :frequency, Integer, default: 86_400 default_action :periodic allowed_actions :update, :periodic end end end chef-12.14.60/lib/chef/resource/bash.rb000066400000000000000000000016471276456504500174440ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/script" require "chef/provider/script" class Chef class Resource class Bash < Chef::Resource::Script def initialize(name, run_context = nil) super @interpreter = "bash" end end end end chef-12.14.60/lib/chef/resource/batch.rb000066400000000000000000000017071276456504500176050ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/windows_script" class Chef class Resource class Batch < Chef::Resource::WindowsScript provides :batch, os: "windows" def initialize(name, run_context = nil) super(name, run_context, nil, "cmd.exe") end end end end chef-12.14.60/lib/chef/resource/bff_package.rb000066400000000000000000000015401276456504500207270ustar00rootroot00000000000000# # Author:: Deepali Jagtap () # Copyright:: Copyright 2013-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package/aix" class Chef class Resource class BffPackage < Chef::Resource::Package end end end chef-12.14.60/lib/chef/resource/breakpoint.rb000066400000000000000000000016311276456504500206560ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class Breakpoint < Chef::Resource default_action :break def initialize(action = "break", *args) super(caller.first, *args) end end end end chef-12.14.60/lib/chef/resource/chef_gem.rb000066400000000000000000000041411276456504500202540ustar00rootroot00000000000000# # Author:: Bryan McLellan # 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 "chef/resource/package" require "chef/resource/gem_package" class Chef class Resource class ChefGem < Chef::Resource::Package::GemPackage resource_name :chef_gem property :gem_binary, default: "#{RbConfig::CONFIG['bindir']}/gem", callbacks: { "The chef_gem resource is restricted to the current gem environment, use gem_package to install to other environments." => proc { |v| v == "#{RbConfig::CONFIG['bindir']}/gem" }, } property :compile_time, [ true, false, nil ], default: lazy { Chef::Config[:chef_gem_compile_time] }, desired_state: false def after_created # Chef::Resource.run_action: Caveat: this skips Chef::Runner.run_action, where notifications are handled # Action could be an array of symbols, but probably won't (think install + enable for a package) if compile_time.nil? Chef.log_deprecation "#{self} chef_gem compile_time installation is deprecated" Chef.log_deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior." Chef.log_deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required." end if compile_time || compile_time.nil? Array(action).each do |action| self.run_action(action) end Gem.clear_paths end end end end end chef-12.14.60/lib/chef/resource/chocolatey_package.rb000066400000000000000000000023101276456504500223200ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" class Chef class Resource class ChocolateyPackage < Chef::Resource::Package provides :chocolatey_package, os: "windows" allowed_actions :install, :upgrade, :remove, :uninstall, :purge, :reconfig def initialize(name, run_context = nil) super @resource_name = :chocolatey_package end property :package_name, [String, Array], coerce: proc { |x| [x].flatten } property :version, [String, Array], coerce: proc { |x| [x].flatten } end end end chef-12.14.60/lib/chef/resource/conditional.rb000066400000000000000000000110631276456504500210230ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/shell_out" require "chef/guard_interpreter" class Chef class Resource class Conditional include Chef::Mixin::ShellOut # We only create these via the `not_if` or `only_if` constructors, and # not the default constructor class << self private :new end def self.not_if(parent_resource, command = nil, command_opts = {}, &block) new(:not_if, parent_resource, command, command_opts, &block) end def self.only_if(parent_resource, command = nil, command_opts = {}, &block) new(:only_if, parent_resource, command, command_opts, &block) end attr_reader :positivity attr_reader :command attr_reader :command_opts attr_reader :block def initialize(positivity, parent_resource, command = nil, command_opts = {}, &block) @positivity = positivity @command, @command_opts = command, command_opts @block = block @block_given = block_given? @parent_resource = parent_resource raise ArgumentError, "only_if/not_if requires either a command or a block" unless command || block_given? end def configure case @command when String, Array @guard_interpreter = Chef::GuardInterpreter.for_resource(@parent_resource, @command, @command_opts) @block = nil when nil # We should have a block if we get here # Check to see if the user set the guard_interpreter on the parent resource. Note that # this error will not be raised when using the default_guard_interpreter if @parent_resource.guard_interpreter != @parent_resource.default_guard_interpreter msg = "#{@parent_resource.name} was given a guard_interpreter of #{@parent_resource.guard_interpreter}, " msg << "but not given a command as a string. guard_interpreter does not support blocks (because they just contain ruby)." raise ArgumentError, msg end @guard_interpreter = nil @command, @command_opts = nil, nil else # command was passed, but it wasn't a String raise ArgumentError, "Invalid only_if/not_if command, expected a string: #{command.inspect} (#{command.class})" end end # this is run during convergence via Chef::Resource#run_action -> Chef::Resource#should_skip? def continue? # configure late in case guard_interpreter is specified on the resource after the conditional configure case @positivity when :only_if evaluate when :not_if !evaluate else raise "Cannot evaluate resource conditional of type #{@positivity}" end end def evaluate @guard_interpreter ? evaluate_command : evaluate_block end def evaluate_command @guard_interpreter.evaluate rescue Chef::Exceptions::CommandTimeout Chef::Log.warn "Command '#{@command}' timed out" false end def evaluate_block @block.call.tap do |rv| if rv.is_a?(String) && !rv.empty? # This is probably a mistake: # not_if { "command" } sanitized_rv = @parent_resource.sensitive ? "a string" : rv.inspect Chef::Log.warn("#{@positivity} block for #{@parent_resource} returned #{sanitized_rv}, did you mean to run a command?" + (@parent_resource.sensitive ? "" : " If so use '#{@positivity} #{sanitized_rv}' in your code.")) end end end def short_description @positivity end def description cmd_or_block = @command ? "command `#{@command}`" : "ruby block" "#{@positivity} #{cmd_or_block}" end def to_text if @command "#{positivity} \"#{@command}\"" else "#{@positivity} { #code block }" end end end end end chef-12.14.60/lib/chef/resource/conditional_action_not_nothing.rb000066400000000000000000000022411276456504500247640ustar00rootroot00000000000000# # Author:: Xabier de Zuazo () # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Resource class ConditionalActionNotNothing attr_reader :current_action def initialize(current_action) @current_action = current_action end def continue? # @positivity == not_if @current_action != :nothing end def short_description description end def description "action :nothing" end def to_text "not_if { action == :nothing }" end end end end chef-12.14.60/lib/chef/resource/cookbook_file.rb000066400000000000000000000026701276456504500213310ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Chisamore () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/file" require "chef/provider/cookbook_file" require "chef/mixin/securable" class Chef class Resource class CookbookFile < Chef::Resource::File include Chef::Mixin::Securable default_action :create def initialize(name, run_context = nil) super @provider = Chef::Provider::CookbookFile @source = ::File.basename(name) @cookbook = nil end def source(source_filename = nil) set_or_return(:source, source_filename, :kind_of => [ String, Array ]) end def cookbook(cookbook_name = nil) set_or_return(:cookbook, cookbook_name, :kind_of => String) end end end end chef-12.14.60/lib/chef/resource/cron.rb000066400000000000000000000116061276456504500174640ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Author:: Tyler Cloke () # Copyright:: Copyright 2009-2016, Bryan McLellan # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class Cron < Chef::Resource identity_attr :command state_attrs :minute, :hour, :day, :month, :weekday, :user default_action :create allowed_actions :create, :delete def initialize(name, run_context = nil) super @minute = "*" @hour = "*" @day = "*" @month = "*" @weekday = "*" @command = nil @user = "root" @mailto = nil @path = nil @shell = nil @home = nil @time = nil @environment = {} end def minute(arg = nil) if arg.is_a?(Integer) converted_arg = arg.to_s else converted_arg = arg end begin if integerize(arg) > 59 then raise RangeError end rescue ArgumentError end set_or_return( :minute, converted_arg, :kind_of => String ) end def hour(arg = nil) if arg.is_a?(Integer) converted_arg = arg.to_s else converted_arg = arg end begin if integerize(arg) > 23 then raise RangeError end rescue ArgumentError end set_or_return( :hour, converted_arg, :kind_of => String ) end def day(arg = nil) if arg.is_a?(Integer) converted_arg = arg.to_s else converted_arg = arg end begin if integerize(arg) > 31 then raise RangeError end rescue ArgumentError end set_or_return( :day, converted_arg, :kind_of => String ) end def month(arg = nil) if arg.is_a?(Integer) converted_arg = arg.to_s else converted_arg = arg end begin if integerize(arg) > 12 then raise RangeError end rescue ArgumentError end set_or_return( :month, converted_arg, :kind_of => String ) end def weekday(arg = nil) if arg.is_a?(Integer) converted_arg = arg.to_s else converted_arg = arg end begin error_message = "You provided '#{arg}' as a weekday, acceptable values are " error_message << Provider::Cron::WEEKDAY_SYMBOLS.map { |sym| ":#{sym}" }.join(", ") error_message << " and a string in crontab format" if (arg.is_a?(Symbol) && !Provider::Cron::WEEKDAY_SYMBOLS.include?(arg)) || (!arg.is_a?(Symbol) && integerize(arg) > 7) || (!arg.is_a?(Symbol) && integerize(arg) < 0) raise RangeError, error_message end rescue ArgumentError end set_or_return( :weekday, converted_arg, :kind_of => [String, Symbol] ) end def time(arg = nil) set_or_return( :time, arg, :equal_to => Chef::Provider::Cron::SPECIAL_TIME_VALUES ) end def mailto(arg = nil) set_or_return( :mailto, arg, :kind_of => String ) end def path(arg = nil) set_or_return( :path, arg, :kind_of => String ) end def home(arg = nil) set_or_return( :home, arg, :kind_of => String ) end def shell(arg = nil) set_or_return( :shell, arg, :kind_of => String ) end def command(arg = nil) set_or_return( :command, arg, :kind_of => String ) end def user(arg = nil) set_or_return( :user, arg, :kind_of => String ) end def environment(arg = nil) set_or_return( :environment, arg, :kind_of => Hash ) end private # On Ruby 1.8, Kernel#Integer will happily do this for you. On 1.9, no. def integerize(integerish) Integer(integerish) rescue TypeError 0 end end end end chef-12.14.60/lib/chef/resource/csh.rb000066400000000000000000000016451276456504500173020ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/script" require "chef/provider/script" class Chef class Resource class Csh < Chef::Resource::Script def initialize(name, run_context = nil) super @interpreter = "csh" end end end end chef-12.14.60/lib/chef/resource/deploy.rb000066400000000000000000000301311276456504500200110ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Tyler Cloke () # 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. # # EX: # deploy "/my/deploy/dir" do # repo "git@github.com/whoami/project" # revision "abc123" # or "HEAD" or "TAG_for_1.0" or (subversion) "1234" # user "deploy_ninja" # enable_submodules true # migrate true # migration_command "rake db:migrate" # environment "RAILS_ENV" => "production", "OTHER_ENV" => "foo" # shallow_clone true # depth 1 # action :deploy # or :rollback # restart_command "touch tmp/restart.txt" # git_ssh_wrapper "wrap-ssh4git.sh" # scm_provider Chef::Provider::Git # is the default, for svn: Chef::Provider::Subversion # svn_username "whoami" # svn_password "supersecret" # end require "chef/resource/scm" class Chef class Resource # Deploy: Deploy apps from a source control repository. # # Callbacks: # Callbacks can be a block or a string. If given a block, the code # is evaluated as an embedded recipe, and run at the specified # point in the deploy process. If given a string, the string is taken as # a path to a callback file/recipe. Paths are evaluated relative to the # release directory. Callback files can contain chef code (resources, etc.) # class Deploy < Chef::Resource identity_attr :repository state_attrs :deploy_to, :revision default_action :deploy allowed_actions :force_deploy, :deploy, :rollback def initialize(name, run_context = nil) super @deploy_to = name @environment = nil @repository_cache = "cached-copy" @copy_exclude = [] @purge_before_symlink = %w{log tmp/pids public/system} @create_dirs_before_symlink = %w{tmp public config} @symlink_before_migrate = { "config/database.yml" => "config/database.yml" } @symlinks = { "system" => "public/system", "pids" => "tmp/pids", "log" => "log" } @revision = "HEAD" @migrate = false @rollback_on_error = false @remote = "origin" @enable_submodules = false @shallow_clone = false @depth = nil @scm_provider = Chef::Provider::Git @svn_force_export = false @additional_remotes = Hash[] @keep_releases = 5 @enable_checkout = true @checkout_branch = "deploy" end # where the checked out/cloned code goes def destination @destination ||= shared_path + "/#{@repository_cache}" end # where shared stuff goes, i.e., logs, tmp, etc. goes here def shared_path @shared_path ||= @deploy_to + "/shared" end # where the deployed version of your code goes def current_path @current_path ||= @deploy_to + "/current" end def depth(arg = @shallow_clone ? 5 : nil) set_or_return( :depth, arg, :kind_of => [ Integer ] ) end # note: deploy_to is your application "meta-root." def deploy_to(arg = nil) set_or_return( :deploy_to, arg, :kind_of => [ String ] ) end def repo(arg = nil) set_or_return( :repo, arg, :kind_of => [ String ] ) end alias :repository :repo def remote(arg = nil) set_or_return( :remote, arg, :kind_of => [ String ] ) end def role(arg = nil) set_or_return( :role, arg, :kind_of => [ String ] ) end def restart_command(arg = nil, &block) arg ||= block set_or_return( :restart_command, arg, :kind_of => [ String, Proc ] ) end alias :restart :restart_command def migrate(arg = nil) set_or_return( :migrate, arg, :kind_of => [ TrueClass, FalseClass ] ) end def migration_command(arg = nil) set_or_return( :migration_command, arg, :kind_of => [ String ] ) end def rollback_on_error(arg = nil) set_or_return( :rollback_on_error, arg, :kind_of => [ TrueClass, FalseClass ] ) end def user(arg = nil) set_or_return( :user, arg, :kind_of => [ String ] ) end def group(arg = nil) set_or_return( :group, arg, :kind_of => [ String ] ) end def enable_submodules(arg = nil) set_or_return( :enable_submodules, arg, :kind_of => [ TrueClass, FalseClass ] ) end def shallow_clone(arg = nil) set_or_return( :shallow_clone, arg, :kind_of => [ TrueClass, FalseClass ] ) end def repository_cache(arg = nil) set_or_return( :repository_cache, arg, :kind_of => [ String ] ) end def copy_exclude(arg = nil) set_or_return( :copy_exclude, arg, :kind_of => [ String ] ) end def revision(arg = nil) set_or_return( :revision, arg, :kind_of => [ String ] ) end alias :branch :revision def git_ssh_wrapper(arg = nil) set_or_return( :git_ssh_wrapper, arg, :kind_of => [ String ] ) end alias :ssh_wrapper :git_ssh_wrapper def svn_username(arg = nil) set_or_return( :svn_username, arg, :kind_of => [ String ] ) end def svn_password(arg = nil) set_or_return( :svn_password, arg, :kind_of => [ String ] ) end def svn_arguments(arg = nil) set_or_return( :svn_arguments, arg, :kind_of => [ String ] ) end def svn_info_args(arg = nil) set_or_return( :svn_arguments, arg, :kind_of => [ String ]) end def scm_provider(arg = nil) klass = if arg.kind_of?(String) || arg.kind_of?(Symbol) lookup_provider_constant(arg) else arg end set_or_return( :scm_provider, klass, :kind_of => [ Class ] ) end # This is to support "provider :revision" without deprecation warnings. # Do NOT copy this. def self.provider_base Chef::Provider::Deploy end def svn_force_export(arg = nil) set_or_return( :svn_force_export, arg, :kind_of => [ TrueClass, FalseClass ] ) end def environment(arg = nil) if arg.is_a?(String) Chef::Log.debug "Setting RAILS_ENV, RACK_ENV, and MERB_ENV to `#{arg}'" Chef::Log.warn "[DEPRECATED] please modify your deploy recipe or attributes to set the environment using a hash" arg = { "RAILS_ENV" => arg, "MERB_ENV" => arg, "RACK_ENV" => arg } end set_or_return( :environment, arg, :kind_of => [ Hash ] ) end # The number of old release directories to keep around after cleanup def keep_releases(arg = nil) [set_or_return( :keep_releases, arg, :kind_of => [ Integer ]), 1].max end # An array of paths, relative to your app's root, to be purged from a # SCM clone/checkout before symlinking. Use this to get rid of files and # directories you want to be shared between releases. # Default: ["log", "tmp/pids", "public/system"] def purge_before_symlink(arg = nil) set_or_return( :purge_before_symlink, arg, :kind_of => Array ) end # An array of paths, relative to your app's root, where you expect dirs to # exist before symlinking. This runs after #purge_before_symlink, so you # can use this to recreate dirs that you had previously purged. # For example, if you plan to use a shared directory for pids, and you # want it to be located in $APP_ROOT/tmp/pids, you could purge tmp, # then specify tmp here so that the tmp directory will exist when you # symlink the pids directory in to the current release. # Default: ["tmp", "public", "config"] def create_dirs_before_symlink(arg = nil) set_or_return( :create_dirs_before_symlink, arg, :kind_of => Array ) end # A Hash of shared/dir/path => release/dir/path. This attribute determines # which files and dirs in the shared directory get symlinked to the current # release directory, and where they go. If you have a directory # $shared/pids that you would like to symlink as $current_release/tmp/pids # you specify it as "pids" => "tmp/pids" # Default {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"} def symlinks(arg = nil) set_or_return( :symlinks, arg, :kind_of => Hash ) end # A Hash of shared/dir/path => release/dir/path. This attribute determines # which files in the shared directory get symlinked to the current release # directory and where they go. Unlike map_shared_files, these are symlinked # *before* any migration is run. # For a rails/merb app, this is used to link in a known good database.yml # (with the production db password) before running migrate. # Default {"config/database.yml" => "config/database.yml"} def symlink_before_migrate(arg = nil) set_or_return( :symlink_before_migrate, arg, :kind_of => Hash ) end # Callback fires before migration is run. def before_migrate(arg = nil, &block) arg ||= block set_or_return(:before_migrate, arg, :kind_of => [Proc, String]) end # Callback fires before symlinking def before_symlink(arg = nil, &block) arg ||= block set_or_return(:before_symlink, arg, :kind_of => [Proc, String]) end # Callback fires before restart def before_restart(arg = nil, &block) arg ||= block set_or_return(:before_restart, arg, :kind_of => [Proc, String]) end # Callback fires after restart def after_restart(arg = nil, &block) arg ||= block set_or_return(:after_restart, arg, :kind_of => [Proc, String]) end def additional_remotes(arg = nil) set_or_return( :additional_remotes, arg, :kind_of => Hash ) end def enable_checkout(arg = nil) set_or_return( :enable_checkout, arg, :kind_of => [TrueClass, FalseClass] ) end def checkout_branch(arg = nil) set_or_return( :checkout_branch, arg, :kind_of => String ) end # FIXME The Deploy resource may be passed to an SCM provider as its # resource. The SCM provider knows that SCM resources can specify a # timeout for SCM operations. The deploy resource must therefore support # a timeout method, but the timeout it describes is for SCM operations, # not the overall deployment. This is potentially confusing. def timeout(arg = nil) set_or_return( :timeout, arg, :kind_of => Integer ) end end end end chef-12.14.60/lib/chef/resource/deploy_revision.rb000066400000000000000000000016751276456504500217420ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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 Resource # Convenience class for using the deploy resource with the revision # deployment strategy (provider) class DeployRevision < Chef::Resource::Deploy end class DeployBranch < Chef::Resource::DeployRevision end end end chef-12.14.60/lib/chef/resource/directory.rb000066400000000000000000000027731276456504500205340ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Chisamore () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/provider/directory" require "chef/mixin/securable" class Chef class Resource class Directory < Chef::Resource identity_attr :path state_attrs :group, :mode, :owner include Chef::Mixin::Securable default_action :create allowed_actions :create, :delete def initialize(name, run_context = nil) super @path = name @recursive = false end def recursive(arg = nil) set_or_return( :recursive, arg, :kind_of => [ TrueClass, FalseClass ] ) end def path(arg = nil) set_or_return( :path, arg, :kind_of => String ) end end end end chef-12.14.60/lib/chef/resource/dpkg_package.rb000066400000000000000000000016501276456504500211210ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" class Chef class Resource class DpkgPackage < Chef::Resource::Package resource_name :dpkg_package provides :dpkg_package, os: "linux" property :source, [ String, Array, nil ] end end end chef-12.14.60/lib/chef/resource/dsc_resource.rb000066400000000000000000000061671276456504500212110ustar00rootroot00000000000000# # Author:: Adam Edwards () # # Copyright:: Copyright 2014-2016, Chef Software Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/dsl/powershell" class Chef class Resource class DscResource < Chef::Resource provides :dsc_resource, os: "windows" # This class will check if the object responds to # to_text. If it does, it will call that as opposed # to inspect. This is useful for properties that hold # objects such as PsCredential, where we do not want # to dump the actual ivars class ToTextHash < Hash def to_text descriptions = self.map do |(property, obj)| obj_text = if obj.respond_to?(:to_text) obj.to_text else obj.inspect end "#{property}=>#{obj_text}" end "{#{descriptions.join(', ')}}" end end include Chef::DSL::Powershell default_action :run def initialize(name, run_context) super @properties = ToTextHash.new @resource = nil @reboot_action = :nothing end def resource(value = nil) if value @resource = value else @resource end end def module_name(value = nil) if value @module_name = value else @module_name end end def property(property_name, value = nil) if not property_name.is_a?(Symbol) raise TypeError, "A property name of type Symbol must be specified, '#{property_name}' of type #{property_name.class} was given" end if value.nil? value_of(@properties[property_name]) else @properties[property_name] = value end end def properties @properties.reduce({}) do |memo, (k, v)| memo[k] = value_of(v) memo end end # This property takes the action message for the reboot resource # If the set method of the DSC resource indicate that a reboot # is necessary, reboot_action provides the mechanism for a reboot to # be requested. def reboot_action(value = nil) if value @reboot_action = value else @reboot_action end end def timeout(arg = nil) set_or_return( :timeout, arg, :kind_of => [ Integer ] ) end private def value_of(value) if value.is_a?(DelayedEvaluator) value.call else value end end end end end chef-12.14.60/lib/chef/resource/dsc_script.rb000066400000000000000000000067531276456504500206670ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/exceptions" require "chef/dsl/powershell" class Chef class Resource class DscScript < Chef::Resource include Chef::DSL::Powershell provides :dsc_script, os: "windows" default_action :run def initialize(name, run_context = nil) super @imports = {} end def code(arg = nil) if arg && command raise ArgumentError, "Only one of 'code' and 'command' attributes may be specified" end if arg && configuration_name raise ArgumentError, "The 'code' and 'command' attributes may not be used together" end set_or_return( :code, arg, :kind_of => [ String ] ) end def configuration_name(arg = nil) if arg && code raise ArgumentError, "Attribute `configuration_name` may not be set if `code` is set" end set_or_return( :configuration_name, arg, :kind_of => [ String ] ) end def command(arg = nil) if arg && code raise ArgumentError, "The 'code' and 'command' attributes may not be used together" end set_or_return( :command, arg, :kind_of => [ String ] ) end def configuration_data(arg = nil) if arg && configuration_data_script raise ArgumentError, "The 'configuration_data' and 'configuration_data_script' attributes may not be used together" end set_or_return( :configuration_data, arg, :kind_of => [ String ] ) end def configuration_data_script(arg = nil) if arg && configuration_data raise ArgumentError, "The 'configuration_data' and 'configuration_data_script' attributes may not be used together" end set_or_return( :configuration_data_script, arg, :kind_of => [ String ] ) end def imports(module_name = nil, *args) if module_name @imports[module_name] ||= [] if args.length == 0 @imports[module_name] << "*" else @imports[module_name].push(*args) end else @imports end end def flags(arg = nil) set_or_return( :flags, arg, :kind_of => [ Hash ] ) end def cwd(arg = nil) set_or_return( :cwd, arg, :kind_of => [ String ] ) end def environment(arg = nil) set_or_return( :environment, arg, :kind_of => [ Hash ] ) end def timeout(arg = nil) set_or_return( :timeout, arg, :kind_of => [ Integer ] ) end end end end chef-12.14.60/lib/chef/resource/easy_install_package.rb000066400000000000000000000020251276456504500226600ustar00rootroot00000000000000# # Author:: Joe Williams () # Copyright:: Copyright 2009-2016, Joe Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" class Chef class Resource class EasyInstallPackage < Chef::Resource::Package resource_name :easy_install_package property :easy_install_binary, String, desired_state: false property :python_binary, String, desired_state: false property :module_name, String, desired_state: false end end end chef-12.14.60/lib/chef/resource/env.rb000066400000000000000000000027731276456504500173200ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Author:: Tyler Cloke () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Resource class Env < Chef::Resource identity_attr :key_name state_attrs :value provides :env, os: "windows" default_action :create allowed_actions :create, :delete, :modify def initialize(name, run_context = nil) super @key_name = name @value = nil @delim = nil end def key_name(arg = nil) set_or_return( :key_name, arg, :kind_of => [ String ] ) end def value(arg = nil) set_or_return( :value, arg, :kind_of => [ String ] ) end def delim(arg = nil) set_or_return( :delim, arg, :kind_of => [ String ] ) end end end end chef-12.14.60/lib/chef/resource/erl_call.rb000066400000000000000000000040531276456504500202760ustar00rootroot00000000000000# # Author:: Joe Williams () # Author:: Tyler Cloke () # Copyright:: Copyright 2009-2016, Joe Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/provider/erl_call" class Chef class Resource class ErlCall < Chef::Resource # erl_call : http://erlang.org/doc/man/erl_call.html identity_attr :code default_action :run def initialize(name, run_context = nil) super @code = "q()." # your erlang code goes here @cookie = nil # cookie of the erlang node @distributed = false # if you want to have a distributed erlang node @name_type = "sname" # type of erlang hostname name or sname @node_name = "chef@localhost" # the erlang node hostname end def code(arg = nil) set_or_return( :code, arg, :kind_of => [ String ] ) end def cookie(arg = nil) set_or_return( :cookie, arg, :kind_of => [ String ] ) end def distributed(arg = nil) set_or_return( :distributed, arg, :kind_of => [ TrueClass, FalseClass ] ) end def name_type(arg = nil) set_or_return( :name_type, arg, :kind_of => [ String ] ) end def node_name(arg = nil) set_or_return( :node_name, arg, :kind_of => [ String ] ) end end end end chef-12.14.60/lib/chef/resource/execute.rb000066400000000000000000000104101276456504500201550ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/provider/execute" class Chef class Resource class Execute < Chef::Resource identity_attr :command # The ResourceGuardInterpreter wraps a resource's guards in another resource. That inner resource # needs to behave differently during (for example) why_run mode, so we flag it here. For why_run mode # we still want to execute the guard resource even if we are not executing the wrapping resource. # Only execute resources (and subclasses) can be guard interpreters. attr_accessor :is_guard_interpreter default_action :run def initialize(name, run_context = nil) super @command = name @backup = 5 @creates = nil @cwd = nil @environment = nil @group = nil @path = nil @returns = 0 @timeout = nil @user = nil @umask = nil @default_guard_interpreter = :execute @is_guard_interpreter = false @live_stream = false end def umask(arg = nil) set_or_return( :umask, arg, :kind_of => [ String, Integer ] ) end def command(arg = nil) set_or_return( :command, arg, :kind_of => [ String, Array ] ) end def creates(arg = nil) set_or_return( :creates, arg, :kind_of => [ String ] ) end def cwd(arg = nil) set_or_return( :cwd, arg, :kind_of => [ String ] ) end def environment(arg = nil) set_or_return( :environment, arg, :kind_of => [ Hash ] ) end alias :env :environment def group(arg = nil) set_or_return( :group, arg, :kind_of => [ String, Integer ] ) end def live_stream(arg = nil) set_or_return( :live_stream, arg, :kind_of => [ TrueClass, FalseClass ]) end def path(arg = nil) Chef::Log.warn "The 'path' attribute of 'execute' is not used by any provider in Chef 11 or Chef 12. Use 'environment' attribute to configure 'PATH'. This attribute will be removed in Chef 13." set_or_return( :path, arg, :kind_of => [ Array ] ) end def returns(arg = nil) set_or_return( :returns, arg, :kind_of => [ Integer, Array ] ) end def timeout(arg = nil) set_or_return( :timeout, arg, :kind_of => [ Integer, Float ] ) end def user(arg = nil) set_or_return( :user, arg, :kind_of => [ String, Integer ] ) end def self.set_guard_inherited_attributes(*inherited_attributes) @class_inherited_attributes = inherited_attributes end def self.guard_inherited_attributes(*inherited_attributes) # Similar to patterns elsewhere, return attributes from this # class and superclasses as a form of inheritance ancestor_attributes = [] if superclass.respond_to?(:guard_inherited_attributes) ancestor_attributes = superclass.guard_inherited_attributes end ancestor_attributes.concat(@class_inherited_attributes ? @class_inherited_attributes : []).uniq end set_guard_inherited_attributes( :cwd, :environment, :group, :user, :umask ) end end end chef-12.14.60/lib/chef/resource/file.rb000066400000000000000000000063321276456504500174420ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Chisamore () # Copyright:: Copyright 2008-2016, 2011-2015 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/platform/query_helpers" require "chef/mixin/securable" require "chef/resource/file/verification" require "pathname" class Chef class Resource class File < Chef::Resource include Chef::Mixin::Securable if Platform.windows? # Use Windows rights instead of standard *nix permissions state_attrs :checksum, :rights, :deny_rights else state_attrs :checksum, :owner, :group, :mode end attr_writer :checksum # # The checksum of the rendered file. This has to be saved on the # new_resource for the 'after' state for reporting but we cannot # mutate the new_resource.checksum which would change the # user intent in the new_resource if the resource is reused. # # @returns [String] Checksum of the file we actually rendered attr_accessor :final_checksum default_action :create 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 { |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 property :diff, [ String, nil ], desired_state: false property :force_unlink, [ true, false ], desired_state: false, default: false property :manage_symlink_source, [ true, false ], desired_state: false property :verifications, Array, default: lazy { [] } def verify(command = nil, opts = {}, &block) if ! (command.nil? || [String, Symbol].include?(command.class)) raise ArgumentError, "verify requires either a string, symbol, or a block" end if command || block_given? verifications << Verification.new(self, command, opts, &block) else verifications end end def state_for_resource_reporter state_attrs = super() # fix up checksum state with final_checksum saved by the provider if checksum.nil? && final_checksum state_attrs[:checksum] = final_checksum 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 chef-12.14.60/lib/chef/resource/file/000077500000000000000000000000001276456504500171115ustar00rootroot00000000000000chef-12.14.60/lib/chef/resource/file/verification.rb000066400000000000000000000104461276456504500221250ustar00rootroot00000000000000# # Author:: Steven Danna () # Copyright:: Copyright 2014-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/exceptions" require "chef/guard_interpreter" require "chef/mixin/descendants_tracker" class Chef class Resource class File < Chef::Resource # # See RFC 027 for a full specification # # File verifications allow user-supplied commands a means of # preventing file resource content deploys. Their intended use # is to verify the contents of a temporary file before it is # deployed onto the system. # # Similar to not_if and only_if, file verifications can take a # ruby block, which will be called, or a string, which will be # executed as a Shell command. # # Additonally, Chef or third-party verifications can ship # "registered verifications" that the user can use by specifying # a :symbol as the command name. # # To create a registered verification, create a class that # inherits from Chef::Resource::File::Verification and use the # provides class method to give it name. Registered # verifications are expected to supply a verify instance method # that takes 2 arguments. # # Example: # class Chef # class Resource # class File::Verification::Foo < Chef::Resource::File::Verification # provides :noop # def verify(path, opts) # #yolo # true # end # end # end # end # # class Verification extend Chef::Mixin::DescendantsTracker def self.provides(name) @provides = name end def self.provides?(name) @provides == name end def self.lookup(name) c = descendants.find { |d| d.provides?(name) } if c.nil? raise Chef::Exceptions::VerificationNotFound.new "No file verification for #{name} found." end c end def initialize(parent_resource, command, opts, &block) @command, @command_opts = command, opts @block = block @parent_resource = parent_resource end def verify(path, opts = {}) Chef::Log.debug("Running verification[#{self}] on #{path}") if @block verify_block(path, opts) elsif @command.is_a?(Symbol) verify_registered_verification(path, opts) elsif @command.is_a?(String) verify_command(path, opts) end end # opts is currently unused, but included in the API # to support future extensions def verify_block(path, opts) @block.call(path) end # We reuse Chef::GuardInterpreter in order to support # the same set of options that the not_if/only_if blocks do def verify_command(path, opts) # First implementation interpolated `file`; docs & RFC claim `path` # is interpolated. Until `file` can be deprecated, interpolate both. Chef.log_deprecation( "%{file} is deprecated in verify command and will not be "\ "supported in Chef 13. Please use %{path} instead." ) if @command.include?("%{file}") command = @command % { :file => path, :path => path } interpreter = Chef::GuardInterpreter.for_resource(@parent_resource, command, @command_opts) interpreter.evaluate end def verify_registered_verification(path, opts) verification_class = Chef::Resource::File::Verification.lookup(@command) v = verification_class.new(@parent_resource, @command, @command_opts, &@block) v.verify(path, opts) end end end end end chef-12.14.60/lib/chef/resource/freebsd_package.rb000066400000000000000000000037361276456504500216150ustar00rootroot00000000000000# # Authors:: AJ Christensen () # Richard Manyanza () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Copyright:: Copyright 2014-2016, Richard Manyanza. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package/freebsd/port" require "chef/provider/package/freebsd/pkg" require "chef/provider/package/freebsd/pkgng" require "chef/mixin/shell_out" class Chef class Resource class FreebsdPackage < Chef::Resource::Package include Chef::Mixin::ShellOut resource_name :freebsd_package provides :package, platform: "freebsd" def after_created assign_provider end def supports_pkgng? ships_with_pkgng? || !!shell_out!("make -V WITH_PKGNG", :env => nil).stdout.match(/yes/i) end private def ships_with_pkgng? # It was not until __FreeBSD_version 1000017 that pkgng became # the default binary package manager. See '/usr/ports/Mk/bsd.port.mk'. node[:os_version].to_i >= 1000017 end def assign_provider @provider = if source.to_s =~ /^ports$/i Chef::Provider::Package::Freebsd::Port elsif supports_pkgng? Chef::Provider::Package::Freebsd::Pkgng else Chef::Provider::Package::Freebsd::Pkg end end end end end chef-12.14.60/lib/chef/resource/gem_package.rb000066400000000000000000000030311276456504500207370ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" class Chef class Resource class GemPackage < Chef::Resource::Package resource_name :gem_package property :source, [ String, Array ] property :clear_sources, [ true, false ], default: false, desired_state: false # Sets a custom gem_binary to run for gem commands. property :gem_binary, String, desired_state: false ## # Options for the gem install, either a Hash or a String. When a hash is # given, the options are passed to Gem::DependencyInstaller.new, and the # gem will be installed via the gems API. When a String is given, the gem # will be installed by shelling out to the gem command. Using a Hash of # options with an explicit gem_binary will result in undefined behavior. property :options, [ String, Hash, nil ], desired_state: false end end end chef-12.14.60/lib/chef/resource/git.rb000066400000000000000000000022121276456504500172770ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/scm" class Chef class Resource class Git < Chef::Resource::Scm def initialize(name, run_context = nil) super @additional_remotes = Hash[] end def additional_remotes(arg = nil) set_or_return( :additional_remotes, arg, :kind_of => Hash ) end alias :branch :revision alias :reference :revision alias :repo :repository end end end chef-12.14.60/lib/chef/resource/group.rb000066400000000000000000000046021276456504500176550ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Resource class Group < Chef::Resource identity_attr :group_name state_attrs :members allowed_actions :create, :remove, :modify, :manage default_action :create def initialize(name, run_context = nil) super @group_name = name @gid = nil @members = [] @excluded_members = [] @append = false @non_unique = false end def group_name(arg = nil) set_or_return( :group_name, arg, :kind_of => [ String ] ) end def gid(arg = nil) set_or_return( :gid, arg, :kind_of => [ String, Integer ] ) end def members(arg = nil) converted_members = arg.is_a?(String) ? arg.split(",") : arg set_or_return( :members, converted_members, :kind_of => [ Array ] ) end alias_method :users, :members def excluded_members(arg = nil) converted_members = arg.is_a?(String) ? arg.split(",") : arg set_or_return( :excluded_members, converted_members, :kind_of => [ Array ] ) end def append(arg = nil) set_or_return( :append, arg, :kind_of => [ TrueClass, FalseClass ] ) end def system(arg = nil) set_or_return( :system, arg, :kind_of => [ TrueClass, FalseClass ] ) end def non_unique(arg = nil) set_or_return( :non_unique, arg, :kind_of => [ TrueClass, FalseClass ] ) end end end end chef-12.14.60/lib/chef/resource/homebrew_package.rb000066400000000000000000000020211276456504500217750ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Author:: Graeme Mathieson () # # Copyright 2011-2016, Chef Software Inc. # Copyright 2014-2016, Chef Software, Inc # # 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" require "chef/resource/package" class Chef class Resource class HomebrewPackage < Chef::Resource::Package resource_name :homebrew_package provides :package, os: "darwin" property :homebrew_user, [ String, Integer ] end end end chef-12.14.60/lib/chef/resource/http_request.rb000066400000000000000000000030541276456504500212500ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/provider/http_request" class Chef class Resource class HttpRequest < Chef::Resource identity_attr :url default_action :get allowed_actions :get, :put, :post, :delete, :head, :options def initialize(name, run_context = nil) super @message = name @url = nil @headers = {} end def url(args = nil) set_or_return( :url, args, :kind_of => String ) end def message(args = nil, &block) args = block if block_given? set_or_return( :message, args, :kind_of => Object ) end def headers(args = nil) set_or_return( :headers, args, :kind_of => Hash ) end end end end chef-12.14.60/lib/chef/resource/ifconfig.rb000066400000000000000000000056161276456504500203130ustar00rootroot00000000000000# # Author:: Jason K. Jackson (jasonjackson@gmail.com) # Author:: Tyler Cloke () # Copyright:: Copyright 2009-2016, Jason K. Jackson # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class Ifconfig < Chef::Resource identity_attr :device state_attrs :inet_addr, :mask default_action :add allowed_actions :add, :delete, :enable, :disable def initialize(name, run_context = nil) super @target = name @hwaddr = nil @mask = nil @inet_addr = nil @bcast = nil @mtu = nil @metric = nil @device = nil @onboot = nil @network = nil @bootproto = nil @onparent = nil end def target(arg = nil) set_or_return( :target, arg, :kind_of => String ) end def device(arg = nil) set_or_return( :device, arg, :kind_of => String ) end def hwaddr(arg = nil) set_or_return( :hwaddr, arg, :kind_of => String ) end def inet_addr(arg = nil) set_or_return( :inet_addr, arg, :kind_of => String ) end def bcast(arg = nil) set_or_return( :bcast, arg, :kind_of => String ) end def mask(arg = nil) set_or_return( :mask, arg, :kind_of => String ) end def mtu(arg = nil) set_or_return( :mtu, arg, :kind_of => String ) end def metric(arg = nil) set_or_return( :metric, arg, :kind_of => String ) end def onboot(arg = nil) set_or_return( :onboot, arg, :kind_of => String ) end def network(arg = nil) set_or_return( :network, arg, :kind_of => String ) end def bootproto(arg = nil) set_or_return( :bootproto, arg, :kind_of => String ) end def onparent(arg = nil) set_or_return( :onparent, arg, :kind_of => String ) end end end end chef-12.14.60/lib/chef/resource/ips_package.rb000066400000000000000000000021351276456504500207660ustar00rootroot00000000000000# # Author:: Jason Williams () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package/ips" class Chef class Resource class IpsPackage < ::Chef::Resource::Package resource_name :ips_package provides :package, os: "solaris2" provides :ips_package, os: "solaris2" allowed_actions :install, :remove, :upgrade property :accept_license, [ true, false ], default: false, desired_state: false end end end chef-12.14.60/lib/chef/resource/ksh.rb000066400000000000000000000016271276456504500173120ustar00rootroot00000000000000# # Author:: Nolan Davidson () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/script" class Chef class Resource class Ksh < Chef::Resource::Script def initialize(name, run_context = nil) super @interpreter = "ksh" end end end end chef-12.14.60/lib/chef/resource/launchd.rb000066400000000000000000000072741276456504500201470ustar00rootroot00000000000000# # Author:: Mike Dodge () # Copyright:: Copyright (c) 2015 Facebook, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/provider/launchd" class Chef class Resource class Launchd < Chef::Resource provides :launchd, os: "darwin" identity_attr :label default_action :create allowed_actions :create, :create_if_missing, :delete, :enable, :disable def initialize(name, run_context = nil) super provider = Chef::Provider::Launchd resource_name = :launchd end property :label, String, default: lazy { name }, identity: true property :backup, [Integer, FalseClass] property :cookbook, String property :group, [String, Integer] property :hash, Hash property :mode, [String, Integer] property :owner, [String, Integer] property :path, String property :source, String property :session_type, String property :type, String, default: "daemon", coerce: proc { |type| type = type ? type.downcase : "daemon" types = %w{daemon agent} unless types.include?(type) error_msg = "type must be daemon or agent" raise Chef::Exceptions::ValidationFailed, error_msg end type } # Apple LaunchD Keys property :abandon_process_group, [ TrueClass, FalseClass ] property :debug, [ TrueClass, FalseClass ] property :disabled, [ TrueClass, FalseClass ], default: false property :enable_globbing, [ TrueClass, FalseClass ] property :enable_transactions, [ TrueClass, FalseClass ] property :environment_variables, Hash property :exit_timeout, Integer property :hard_resource_limits, Hash property :inetd_compatibility, Hash property :init_groups, [ TrueClass, FalseClass ] property :keep_alive, [ TrueClass, FalseClass, Hash ] property :launch_only_once, [ TrueClass, FalseClass ] property :ld_group, String property :limit_load_from_hosts, Array property :limit_load_to_hosts, Array property :limit_load_to_session_type, String property :low_priority_io, [ TrueClass, FalseClass ] property :mach_services, Hash property :nice, Integer property :on_demand, [ TrueClass, FalseClass ] property :process_type, String property :program, String property :program_arguments, Array property :queue_directories, Array property :root_directory, String property :run_at_load, [ TrueClass, FalseClass ] property :sockets, Hash property :soft_resource_limits, Array property :standard_error_path, String property :standard_in_path, String property :standard_out_path, String property :start_calendar_interval, Hash property :start_interval, Integer property :start_on_mount, [ TrueClass, FalseClass ] property :throttle_interval, Integer property :time_out, Integer property :umask, Integer property :username, String property :wait_for_debugger, [ TrueClass, FalseClass ] property :watch_paths, Array property :working_directory, String end end end chef-12.14.60/lib/chef/resource/link.rb000066400000000000000000000051671276456504500174650ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/mixin/securable" class Chef class Resource class Link < Chef::Resource include Chef::Mixin::Securable identity_attr :target_file state_attrs :to, :owner, :group default_action :create allowed_actions :create, :delete def initialize(name, run_context = nil) verify_links_supported! super @to = nil @link_type = :symbolic @target_file = name end def to(arg = nil) set_or_return( :to, arg, :kind_of => String ) end def target_file(arg = nil) set_or_return( :target_file, arg, :kind_of => String ) end def link_type(arg = nil) real_arg = arg.kind_of?(String) ? arg.to_sym : arg set_or_return( :link_type, real_arg, :equal_to => [ :symbolic, :hard ] ) end def group(arg = nil) set_or_return( :group, arg, :regex => Chef::Config[:group_valid_regex] ) end def owner(arg = nil) set_or_return( :owner, arg, :regex => Chef::Config[:user_valid_regex] ) end # make link quack like a file (XXX: not for public consumption) def path target_file end private def verify_links_supported! # On certain versions of windows links are not supported. Make # sure we are not on such a platform. if Chef::Platform.windows? require "chef/win32/file" begin Chef::ReservedNames::Win32::File.verify_links_supported! rescue Chef::Exceptions::Win32APIFunctionNotImplemented => e Chef::Log.fatal("Link resource is not supported on this version of Windows") raise e end end end end end end chef-12.14.60/lib/chef/resource/log.rb000066400000000000000000000037251276456504500173070ustar00rootroot00000000000000# # Author:: Cary Penniman () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/provider/log" class Chef class Resource class Log < Chef::Resource identity_attr :message default_action :write # Sends a string from a recipe to a log provider # # log "some string to log" do # level :info # (default) also supports :warn, :debug, and :error # end # # === Example # log "your string to log" # # or # # log "a debug string" { level :debug } # # Initialize log resource with a name as the string to log # # === Parameters # name:: Message to log # collection:: Collection of included recipes # node:: Node where resource will be used def initialize(name, run_context = nil) super @level = :info @message = name end def message(arg = nil) set_or_return( :message, arg, :kind_of => String ) end # Log level, one of :debug, :info, :warn, :error or :fatal def level(arg = nil) set_or_return( :level, arg, :equal_to => [ :debug, :info, :warn, :error, :fatal ] ) end end end end chef-12.14.60/lib/chef/resource/lwrp_base.rb000066400000000000000000000104501276456504500204750ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/resource_resolver" require "chef/node" require "chef/log" require "chef/exceptions" require "chef/mixin/convert_to_class_name" require "chef/mixin/from_file" require "chef/mixin/params_validate" # for DelayedEvaluator class Chef class Resource # == Chef::Resource::LWRPBase # Base class for LWRP resources. Adds DSL sugar on top of Chef::Resource, # so attributes, default action, etc. can be defined with pleasing syntax. class LWRPBase < Resource # Class methods class <) resource_class.instance_eval do define_singleton_method(:to_s) do "Custom resource #{resource_name} from cookbook #{cookbook_name}" end define_singleton_method(:inspect) { to_s } end Chef::Log.debug("Loaded contents of #{filename} into resource #{resource_name} (#{resource_class})") LWRPBase.loaded_lwrps[filename] = true # Create the deprecated Chef::Resource::LwrpFoo class Chef::Resource.register_deprecated_lwrp_class(resource_class, convert_to_class_name(resource_name)) resource_class end alias :attribute :property # Adds +action_names+ to the list of valid actions for this resource. # Does not include superclass's action list when appending. def actions(*action_names) action_names = action_names.flatten if !action_names.empty? && !@allowed_actions self.allowed_actions = ([ :nothing ] + action_names).uniq else allowed_actions(*action_names) end end alias :actions= :allowed_actions= # @deprecated def valid_actions(*args) Chef::Log.warn("`valid_actions' is deprecated, please use allowed_actions `instead'!") allowed_actions(*args) end # Set the run context on the class. Used to provide access to the node # during class definition. attr_accessor :run_context def node run_context ? run_context.node : nil end protected def loaded_lwrps @loaded_lwrps ||= {} end private # Get the value from the superclass, if it responds, otherwise return # +nil+. Since class instance variables are **not** inherited upon # subclassing, this is a required check to ensure Chef pulls the # +default_action+ and other DSL-y methods when extending LWRP::Base. def from_superclass(m, default = nil) return default if superclass == Chef::Resource::LWRPBase superclass.respond_to?(m) ? superclass.send(m) : default end end end end end chef-12.14.60/lib/chef/resource/macosx_service.rb000066400000000000000000000027311276456504500215340ustar00rootroot00000000000000# # Author:: Mike Dodge () # Copyright:: Copyright 2015-2016, Facebook, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/service" class Chef class Resource class MacosxService < Chef::Resource::Service provides :macosx_service, os: "darwin" provides :service, os: "darwin" identity_attr :service_name state_attrs :enabled, :running def initialize(name, run_context = nil) super @plist = nil @session_type = nil end # This will enable user to pass a plist in the case # that the filename and label for the service dont match def plist(arg = nil) set_or_return( :plist, arg, :kind_of => String ) end def session_type(arg = nil) set_or_return( :session_type, arg, :kind_of => String ) end end end end chef-12.14.60/lib/chef/resource/macports_package.rb000066400000000000000000000015401276456504500220220ustar00rootroot00000000000000# # Author:: David Balatero () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" class Chef class Resource class MacportsPackage < Chef::Resource::Package resource_name :macports_package end end end chef-12.14.60/lib/chef/resource/mdadm.rb000066400000000000000000000045151276456504500176060ustar00rootroot00000000000000# # Author:: Joe Williams () # Author:: Tyler Cloke () # Copyright:: Copyright 2009-2016, Joe Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class Mdadm < Chef::Resource identity_attr :raid_device state_attrs :devices, :level, :chunk default_action :create allowed_actions :create, :assemble, :stop def initialize(name, run_context = nil) super @chunk = 16 @devices = [] @exists = false @level = 1 @metadata = "0.90" @bitmap = nil @raid_device = name @layout = nil end def chunk(arg = nil) set_or_return( :chunk, arg, :kind_of => [ Integer ] ) end def devices(arg = nil) set_or_return( :devices, arg, :kind_of => [ Array ] ) end def exists(arg = nil) set_or_return( :exists, arg, :kind_of => [ TrueClass, FalseClass ] ) end def level(arg = nil) set_or_return( :level, arg, :kind_of => [ Integer ] ) end def metadata(arg = nil) set_or_return( :metadata, arg, :kind_of => [ String ] ) end def bitmap(arg = nil) set_or_return( :bitmap, arg, :kind_of => [ String ] ) end def raid_device(arg = nil) set_or_return( :raid_device, arg, :kind_of => [ String ] ) end def layout(arg = nil) set_or_return( :layout, arg, :kind_of => [ String ] ) end end end end chef-12.14.60/lib/chef/resource/mount.rb000066400000000000000000000102341276456504500176610ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Author:: Tyler Cloke () # Copyright:: Copyright 2009-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class Mount < Chef::Resource identity_attr :device state_attrs :mount_point, :device_type, :fstype, :username, :password, :domain default_action :mount allowed_actions :mount, :umount, :remount, :enable, :disable def initialize(name, run_context = nil) super @mount_point = name @device = nil @device_type = :device @fsck_device = "-" @fstype = "auto" @options = ["defaults"] @dump = 0 @pass = 2 @mounted = false @enabled = false @supports = { :remount => false } @username = nil @password = nil @domain = nil end def mount_point(arg = nil) set_or_return( :mount_point, arg, :kind_of => [ String ] ) end def device(arg = nil) set_or_return( :device, arg, :kind_of => [ String ] ) end def device_type(arg = nil) real_arg = arg.kind_of?(String) ? arg.to_sym : arg valid_devices = if RUBY_PLATFORM =~ /solaris/i [ :device ] else [ :device, :label, :uuid ] end set_or_return( :device_type, real_arg, :equal_to => valid_devices ) end def fsck_device(arg = nil) set_or_return( :fsck_device, arg, :kind_of => [ String ] ) end def fstype(arg = nil) set_or_return( :fstype, arg, :kind_of => [ String ] ) end def options(arg = nil) ret = set_or_return( :options, arg, :kind_of => [ Array, String ] ) if ret.is_a? String ret.tr(",", " ").split(/ /) else ret end end def dump(arg = nil) set_or_return( :dump, arg, :kind_of => [ Integer, FalseClass ] ) end def pass(arg = nil) set_or_return( :pass, arg, :kind_of => [ Integer, FalseClass ] ) end def mounted(arg = nil) set_or_return( :mounted, arg, :kind_of => [ TrueClass, FalseClass ] ) end def enabled(arg = nil) set_or_return( :enabled, arg, :kind_of => [ TrueClass, FalseClass ] ) end def supports(args = {}) if args.is_a? Array args.each { |arg| @supports[arg] = true } elsif args.any? @supports = args else @supports end end def username(arg = nil) set_or_return( :username, arg, :kind_of => [ String ] ) end def password(arg = nil) set_or_return( :password, arg, :kind_of => [ String ] ) end def domain(arg = nil) set_or_return( :domain, arg, :kind_of => [ String ] ) end private # Used by the AIX provider to set fstype to nil. # TODO use property to make nil a valid value for fstype def clear_fstype @fstype = nil end end end end chef-12.14.60/lib/chef/resource/ohai.rb000066400000000000000000000024111276456504500174350ustar00rootroot00000000000000# # Author:: Michael Leinartas () # Author:: Tyler Cloke () # Copyright:: Copyright 2010-2016, Michael Leinartas # 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 Resource class Ohai < Chef::Resource identity_attr :name state_attrs :plugin default_action :reload def initialize(name, run_context = nil) super @name = name @plugin = nil end def plugin(arg = nil) set_or_return( :plugin, arg, :kind_of => [ String ] ) end def name(arg = nil) set_or_return( :name, arg, :kind_of => [ String ] ) end end end end chef-12.14.60/lib/chef/resource/openbsd_package.rb000066400000000000000000000022171276456504500216260ustar00rootroot00000000000000# # Authors:: AJ Christensen () # Richard Manyanza () # Scott Bonds () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Copyright:: Copyright 2014-2016, Richard Manyanza, Scott Bonds # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package/openbsd" require "chef/mixin/shell_out" class Chef class Resource class OpenbsdPackage < Chef::Resource::Package include Chef::Mixin::ShellOut resource_name :openbsd_package provides :package, os: "openbsd" end end end chef-12.14.60/lib/chef/resource/osx_profile.rb000066400000000000000000000033201276456504500210460ustar00rootroot00000000000000# # Author:: Nate Walck () # Copyright:: Copyright 2015-2016, Facebook, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class OsxProfile < Chef::Resource provides :osx_profile, os: "darwin" provides :osx_config_profile, os: "darwin" identity_attr :profile_name default_action :install allowed_actions :install, :remove def initialize(name, run_context = nil) super @profile_name = name @profile = nil @identifier = nil @path = nil end def profile_name(arg = nil) set_or_return( :profile_name, arg, :kind_of => [ String ] ) end def profile(arg = nil) set_or_return( :profile, arg, :kind_of => [ String, Hash ] ) end def identifier(arg = nil) set_or_return( :identifier, arg, :kind_of => [ String ] ) end def path(arg = nil) set_or_return( :path, arg, :kind_of => [ String ] ) end end end end chef-12.14.60/lib/chef/resource/package.rb000066400000000000000000000027611276456504500201200ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class Package < Chef::Resource resource_name :package default_action :install allowed_actions :install, :upgrade, :remove, :purge, :reconfig def initialize(name, *args) # We capture name here, before it gets coerced to name package_name name super end property :package_name, [ String, Array ], identity: true property :version, [ String, Array ] property :options, String property :response_file, String, desired_state: false property :response_file_variables, Hash, default: lazy { {} }, desired_state: false property :source, String, desired_state: false property :timeout, [ String, Integer ], desired_state: false end end end chef-12.14.60/lib/chef/resource/pacman_package.rb000066400000000000000000000015721276456504500214360ustar00rootroot00000000000000# # Author:: Jan Zimmek () # Copyright:: Copyright 2010-2016, Jan Zimmek # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" class Chef class Resource class PacmanPackage < Chef::Resource::Package resource_name :pacman_package provides :pacman_package, os: "linux" end end end chef-12.14.60/lib/chef/resource/paludis_package.rb000066400000000000000000000020171276456504500216330ustar00rootroot00000000000000# # Author:: Vasiliy Tolstov () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package/paludis" class Chef class Resource class PaludisPackage < Chef::Resource::Package resource_name :paludis_package provides :paludis_package, os: "linux" allowed_actions :install, :remove, :upgrade property :timeout, default: 3600 end end end chef-12.14.60/lib/chef/resource/perl.rb000066400000000000000000000016461276456504500174700ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/script" require "chef/provider/script" class Chef class Resource class Perl < Chef::Resource::Script def initialize(name, run_context = nil) super @interpreter = "perl" end end end end chef-12.14.60/lib/chef/resource/portage_package.rb000066400000000000000000000017171276456504500216410ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" class Chef class Resource class PortagePackage < Chef::Resource::Package resource_name :portage_package def initialize(name, run_context = nil) super @provider = Chef::Provider::Package::Portage end end end end chef-12.14.60/lib/chef/resource/powershell_script.rb000066400000000000000000000032711276456504500222720ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/windows_script" class Chef class Resource class PowershellScript < Chef::Resource::WindowsScript provides :powershell_script, os: "windows" def initialize(name, run_context = nil) super(name, run_context, nil, "powershell.exe") @convert_boolean_return = false end def convert_boolean_return(arg = nil) set_or_return( :convert_boolean_return, arg, :kind_of => [ FalseClass, TrueClass ] ) end # Allow callers evaluating guards to request default # attribute values. This is needed to allow # convert_boolean_return to be true in guard context by default, # and false by default otherwise. When this mode becomes the # default for this resource, this method can be removed since # guard context and recipe resource context will have the # same behavior. def self.get_default_attributes(opts) { :convert_boolean_return => true } end end end end chef-12.14.60/lib/chef/resource/python.rb000066400000000000000000000016501276456504500200420ustar00rootroot00000000000000# Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/script" require "chef/provider/script" class Chef class Resource class Python < Chef::Resource::Script def initialize(name, run_context = nil) super @interpreter = "python" end end end end chef-12.14.60/lib/chef/resource/reboot.rb000066400000000000000000000026211276456504500200120ustar00rootroot00000000000000# # Author:: Chris Doherty ) # Copyright:: Copyright 2014-2016, Chef, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" # In using this resource via notifications, it's important to *only* use # immediate notifications. Delayed notifications produce unintuitive and # probably undesired results. class Chef class Resource class Reboot < Chef::Resource allowed_actions :request_reboot, :reboot_now, :cancel def initialize(name, run_context = nil) super @provider = Chef::Provider::Reboot @reason = "Reboot by Chef" @delay_mins = 0 # no default action. end def reason(arg = nil) set_or_return(:reason, arg, :kind_of => String) end def delay_mins(arg = nil) set_or_return(:delay_mins, arg, :kind_of => Fixnum) end end end end chef-12.14.60/lib/chef/resource/registry_key.rb000066400000000000000000000115511276456504500212420ustar00rootroot00000000000000# Author:: Prajakta Purohit () # Author:: Lamont Granquist () # # Copyright:: Copyright 2011-2016, Chef Software Inc. # # 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/registry_key" require "chef/resource" require "chef/digester" class Chef class Resource class RegistryKey < Chef::Resource identity_attr :key state_attrs :values default_action :create allowed_actions :create, :create_if_missing, :delete, :delete_key # Some registry key data types may not be safely reported as json. # Example (CHEF-5323): # # registry_key 'HKEY_CURRENT_USER\\ChefTest2014' do # values [{ # :name => "ValueWithBadData", # :type => :binary, # :data => 255.chr * 1 # }] # action :create # end # # will raise Encoding::UndefinedConversionError: "\xFF" from ASCII-8BIT to UTF-8. # # To avoid sending data that cannot be nicely converted for json, we have # the values method return "safe" data if the data type is "unsafe". Known "unsafe" # data types are :binary, :dword, :dword-big-endian, and :qword. If other # criteria generate data that cannot reliably be sent as json, add that criteria # to the needs_checksum? method. When unsafe data is detected, the values method # returns an md5 checksum of the listed data. # # :unscrubbed_values returns the values exactly as provided in the resource (i.e., # data is not checksummed, regardless of the data type/"unsafe" criteria). # # Future: # If we have conflicts with other resources reporting json incompatible state, we # may want to extend the state_attrs API with the ability to rename POST'd attrs. # # See lib/chef/resource_reporter.rb for more information. attr_reader :unscrubbed_values def initialize(name, run_context = nil) super @architecture = :machine @recursive = false @key = name @values, @unscrubbed_values = [], [] end def key(arg = nil) set_or_return( :key, arg, :kind_of => String ) end def values(arg = nil) if not arg.nil? if arg.is_a?(Hash) @values = [ arg ] elsif arg.is_a?(Array) @values = arg else raise ArgumentError, "Bad type for RegistryKey resource, use Hash or Array" end @values.each do |v| raise ArgumentError, "Missing name key in RegistryKey values hash" unless v.has_key?(:name) raise ArgumentError, "Missing type key in RegistryKey values hash" unless v.has_key?(:type) raise ArgumentError, "Missing data key in RegistryKey values hash" unless v.has_key?(:data) v.each_key do |key| raise ArgumentError, "Bad key #{key} in RegistryKey values hash" unless [:name, :type, :data].include?(key) end raise ArgumentError, "Type of name => #{v[:name]} should be string" unless v[:name].is_a?(String) raise ArgumentError, "Type of type => #{v[:type]} should be symbol" unless v[:type].is_a?(Symbol) end @unscrubbed_values = @values elsif self.instance_variable_defined?(:@values) scrub_values(@values) end end def recursive(arg = nil) set_or_return( :recursive, arg, :kind_of => [TrueClass, FalseClass] ) end def architecture(arg = nil) set_or_return( :architecture, arg, :kind_of => Symbol ) end private def scrub_values(values) scrubbed = [] values.each do |value| scrubbed_value = value.dup if needs_checksum?(scrubbed_value) data_io = StringIO.new(scrubbed_value[:data].to_s) scrubbed_value[:data] = Chef::Digester.instance.generate_checksum(data_io) end scrubbed << scrubbed_value end scrubbed end # Some data types may raise errors when sent as json. Returns true if this # value's data may need to be converted to a checksum. def needs_checksum?(value) unsafe_types = [:binary, :dword, :dword_big_endian, :qword] unsafe_types.include?(value[:type]) end end end end chef-12.14.60/lib/chef/resource/remote_directory.rb000066400000000000000000000055501276456504500221030ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/directory" require "chef/provider/remote_directory" require "chef/mixin/securable" class Chef class Resource class RemoteDirectory < Chef::Resource::Directory include Chef::Mixin::Securable identity_attr :path state_attrs :files_owner, :files_group, :files_mode default_action :create allowed_actions :create, :create_if_missing, :delete def initialize(name, run_context = nil) super @path = name @source = ::File.basename(name) @delete = false @recursive = true @purge = false @files_backup = 5 @files_owner = nil @files_group = nil @files_mode = 0644 unless Chef::Platform.windows? @overwrite = true @cookbook = nil end if Chef::Platform.windows? # create a second instance of the 'rights' attribute rights_attribute(:files_rights) end def source(args = nil) set_or_return( :source, args, :kind_of => String ) end def files_backup(arg = nil) set_or_return( :files_backup, arg, :kind_of => [ Integer, FalseClass ] ) end def purge(arg = nil) set_or_return( :purge, arg, :kind_of => [ TrueClass, FalseClass ] ) end def files_group(arg = nil) set_or_return( :files_group, arg, :regex => Chef::Config[:group_valid_regex] ) end def files_mode(arg = nil) set_or_return( :files_mode, arg, :regex => /^\d{3,4}$/ ) end def files_owner(arg = nil) set_or_return( :files_owner, arg, :regex => Chef::Config[:user_valid_regex] ) end def overwrite(arg = nil) set_or_return( :overwrite, arg, :kind_of => [ TrueClass, FalseClass ] ) end def cookbook(args = nil) set_or_return( :cookbook, args, :kind_of => String ) end end end end chef-12.14.60/lib/chef/resource/remote_file.rb000066400000000000000000000105201276456504500210070ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Chisamore () # 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 "uri" require "chef/resource/file" require "chef/provider/remote_file" require "chef/mixin/securable" require "chef/mixin/uris" class Chef class Resource class RemoteFile < Chef::Resource::File include Chef::Mixin::Securable def initialize(name, run_context = nil) super @source = [] @use_etag = true @use_last_modified = true @ftp_active_mode = false @headers = {} @provider = Chef::Provider::RemoteFile end # source can take any of the following as arguments # - A single string argument # - Multiple string arguments # - An array or strings # - A delayed evaluator that evaluates to a string # or array of strings # All strings must be parsable as URIs. # source returns an array of strings. def source(*args) arg = parse_source_args(args) ret = set_or_return(:source, arg, { :callbacks => { :validate_source => method(:validate_source), } }) if ret.is_a? String Array(ret) else ret end end def parse_source_args(args) if args.empty? nil elsif args[0].is_a?(Chef::DelayedEvaluator) && args.count == 1 args[0] elsif args.any? { |a| a.is_a?(Chef::DelayedEvaluator) } && args.count > 1 raise Exceptions::InvalidRemoteFileURI, "Only 1 source argument allowed when using a lazy evaluator" else Array(args).flatten end end def checksum(args = nil) set_or_return( :checksum, args, :kind_of => String ) end # Disable or enable ETag and Last Modified conditional GET. Equivalent to # use_etag(true_or_false) # use_last_modified(true_or_false) def use_conditional_get(true_or_false) use_etag(true_or_false) use_last_modified(true_or_false) end def use_etag(args = nil) set_or_return( :use_etag, args, :kind_of => [ TrueClass, FalseClass ] ) end alias :use_etags :use_etag def use_last_modified(args = nil) set_or_return( :use_last_modified, args, :kind_of => [ TrueClass, FalseClass ] ) end def ftp_active_mode(args = nil) set_or_return( :ftp_active_mode, args, :kind_of => [ TrueClass, FalseClass ] ) end def headers(args = nil) set_or_return( :headers, args, :kind_of => Hash ) end def show_progress(args = nil) set_or_return( :show_progress, args, :default => false, :kind_of => [ TrueClass, FalseClass ] ) end private include Chef::Mixin::Uris def validate_source(source) source = Array(source).flatten raise ArgumentError, "#{resource_name} has an empty source" if source.empty? source.each do |src| unless absolute_uri?(src) raise Exceptions::InvalidRemoteFileURI, "#{src.inspect} is not a valid `source` parameter for #{resource_name}. `source` must be an absolute URI or an array of URIs." end end true end def absolute_uri?(source) Chef::Provider::RemoteFile::Fetcher.network_share?(source) || (source.kind_of?(String) && as_uri(source).absolute?) rescue URI::InvalidURIError false end end end end chef-12.14.60/lib/chef/resource/resource_notification.rb000066400000000000000000000125751276456504500231260ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class Notification attr_accessor :resource, :action, :notifying_resource def initialize(resource, action, notifying_resource) @resource = resource @action = action @notifying_resource = notifying_resource end def duplicates?(other_notification) unless other_notification.respond_to?(:resource) && other_notification.respond_to?(:action) msg = "only duck-types of Chef::Resource::Notification can be checked for duplication "\ "you gave #{other_notification.inspect}" raise ArgumentError, msg end other_notification.resource == resource && other_notification.action == action end # If resource and/or notifying_resource is not a resource object, this will look them up in the resource collection # and fix the references from strings to actual Resource objects. def resolve_resource_reference(resource_collection) return resource if resource.kind_of?(Chef::Resource) && notifying_resource.kind_of?(Chef::Resource) if not(resource.kind_of?(Chef::Resource)) fix_resource_reference(resource_collection) end if not(notifying_resource.kind_of?(Chef::Resource)) fix_notifier_reference(resource_collection) end end # This will look up the resource if it is not a Resource Object. It will complain if it finds multiple # resources, can't find a resource, or gets invalid syntax. def fix_resource_reference(resource_collection) matching_resource = resource_collection.find(resource) if Array(matching_resource).size > 1 msg = "Notification #{self} from #{notifying_resource} was created with a reference to multiple resources, "\ "but can only notify one resource. Notifying resource was defined on #{notifying_resource.source_line}" raise Chef::Exceptions::InvalidResourceReference, msg end self.resource = matching_resource rescue Chef::Exceptions::ResourceNotFound => e err = Chef::Exceptions::ResourceNotFound.new(<<-FAIL) resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \ but #{resource} cannot be found in the resource collection. #{notifying_resource} is defined in \ #{notifying_resource.source_line} FAIL err.set_backtrace(e.backtrace) raise err rescue Chef::Exceptions::InvalidResourceSpecification => e err = Chef::Exceptions::InvalidResourceSpecification.new(<<-F) Resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \ but #{resource.inspect} is not valid syntax to look up a resource in the resource collection. Notification \ is defined near #{notifying_resource.source_line} F err.set_backtrace(e.backtrace) raise err end # This will look up the notifying_resource if it is not a Resource Object. It will complain if it finds multiple # resources, can't find a resource, or gets invalid syntax. def fix_notifier_reference(resource_collection) matching_notifier = resource_collection.find(notifying_resource) if Array(matching_notifier).size > 1 msg = "Notification #{self} from #{notifying_resource} was created with a reference to multiple notifying "\ "resources, but can only originate from one resource. Destination resource was defined "\ "on #{resource.source_line}" raise Chef::Exceptions::InvalidResourceReference, msg end self.notifying_resource = matching_notifier rescue Chef::Exceptions::ResourceNotFound => e err = Chef::Exceptions::ResourceNotFound.new(<<-FAIL) Resource #{resource} is configured to receive notifications from #{notifying_resource} with action #{action}, \ but #{notifying_resource} cannot be found in the resource collection. #{resource} is defined in \ #{resource.source_line} FAIL err.set_backtrace(e.backtrace) raise err rescue Chef::Exceptions::InvalidResourceSpecification => e err = Chef::Exceptions::InvalidResourceSpecification.new(<<-F) Resource #{resource} is configured to receive notifications from #{notifying_resource} with action #{action}, \ but #{notifying_resource.inspect} is not valid syntax to look up a resource in the resource collection. Notification \ is defined near #{resource.source_line} F err.set_backtrace(e.backtrace) raise err end def ==(other) return false unless other.is_a?(self.class) other.resource == resource && other.action == action && other.notifying_resource == notifying_resource end end end end chef-12.14.60/lib/chef/resource/route.rb000066400000000000000000000055411276456504500176620ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Author:: Tyler Cloke () # Copyright:: Copyright 2009-2016, Bryan McLellan # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class Route < Chef::Resource identity_attr :target state_attrs :netmask, :gateway default_action :add allowed_actions :add, :delete def initialize(name, run_context = nil) super @target = name @netmask = nil @gateway = nil @metric = nil @device = nil @route_type = :host @networking = nil @networking_ipv6 = nil @hostname = nil @domainname = nil @domain = nil end def networking(arg = nil) set_or_return( :networking, arg, :kind_of => String ) end def networking_ipv6(arg = nil) set_or_return( :networking_ipv6, arg, :kind_of => String ) end def hostname(arg = nil) set_or_return( :hostname, arg, :kind_of => String ) end def domainname(arg = nil) set_or_return( :domainname, arg, :kind_of => String ) end def domain(arg = nil) set_or_return( :domain, arg, :kind_of => String ) end def target(arg = nil) set_or_return( :target, arg, :kind_of => String ) end def netmask(arg = nil) set_or_return( :netmask, arg, :kind_of => String ) end def gateway(arg = nil) set_or_return( :gateway, arg, :kind_of => String ) end def metric(arg = nil) set_or_return( :metric, arg, :kind_of => Integer ) end def device(arg = nil) set_or_return( :device, arg, :kind_of => String ) end def route_type(arg = nil) real_arg = arg.kind_of?(String) ? arg.to_sym : arg set_or_return( :route_type, real_arg, :equal_to => [ :host, :net ] ) end end end end chef-12.14.60/lib/chef/resource/rpm_package.rb000066400000000000000000000020001276456504500207600ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2010-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package/rpm" class Chef class Resource class RpmPackage < Chef::Resource::Package resource_name :rpm_package provides :rpm_package, os: %w{linux aix} property :allow_downgrade, [ true, false ], default: false, desired_state: false end end end chef-12.14.60/lib/chef/resource/ruby.rb000066400000000000000000000016451276456504500175060ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/script" require "chef/provider/script" class Chef class Resource class Ruby < Chef::Resource::Script def initialize(name, run_context = nil) super @interpreter = "ruby" end end end end chef-12.14.60/lib/chef/resource/ruby_block.rb000066400000000000000000000024731276456504500206600ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: AJ Christensen () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/provider/ruby_block" class Chef class Resource class RubyBlock < Chef::Resource default_action :run allowed_actions :create, :run identity_attr :block_name def initialize(name, run_context = nil) super @block_name = name end def block(&block) if block_given? && block @block = block else @block end end def block_name(arg = nil) set_or_return( :block_name, arg, :kind_of => String ) end end end end chef-12.14.60/lib/chef/resource/scm.rb000066400000000000000000000075741276456504500173160ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class Scm < Chef::Resource identity_attr :destination state_attrs :revision default_action :sync allowed_actions :checkout, :export, :sync, :diff, :log def initialize(name, run_context = nil) super @destination = name @enable_submodules = false @enable_checkout = true @revision = "HEAD" @remote = "origin" @ssh_wrapper = nil @depth = nil @checkout_branch = "deploy" @environment = nil end def destination(arg = nil) set_or_return( :destination, arg, :kind_of => String ) end def repository(arg = nil) set_or_return( :repository, arg, :kind_of => String ) end def revision(arg = nil) set_or_return( :revision, arg, :kind_of => String ) end def user(arg = nil) set_or_return( :user, arg, :kind_of => [String, Integer] ) end def group(arg = nil) set_or_return( :group, arg, :kind_of => [String, Integer] ) end def svn_username(arg = nil) set_or_return( :svn_username, arg, :kind_of => String ) end def svn_password(arg = nil) set_or_return( :svn_password, arg, :kind_of => String ) end def svn_arguments(arg = nil) @svn_arguments, arg = nil, nil if arg == false set_or_return( :svn_arguments, arg, :kind_of => String ) end def svn_info_args(arg = nil) @svn_info_args, arg = nil, nil if arg == false set_or_return( :svn_info_args, arg, :kind_of => String) end # Capistrano and git-deploy use ``shallow clone'' def depth(arg = nil) set_or_return( :depth, arg, :kind_of => Integer ) end def enable_submodules(arg = nil) set_or_return( :enable_submodules, arg, :kind_of => [TrueClass, FalseClass] ) end def enable_checkout(arg = nil) set_or_return( :enable_checkout, arg, :kind_of => [TrueClass, FalseClass] ) end def remote(arg = nil) set_or_return( :remote, arg, :kind_of => String ) end def ssh_wrapper(arg = nil) set_or_return( :ssh_wrapper, arg, :kind_of => String ) end def timeout(arg = nil) set_or_return( :timeout, arg, :kind_of => Integer ) end def checkout_branch(arg = nil) set_or_return( :checkout_branch, arg, :kind_of => String ) end def environment(arg = nil) set_or_return( :environment, arg, :kind_of => [ Hash ] ) end alias :env :environment end end end chef-12.14.60/lib/chef/resource/script.rb000066400000000000000000000040601276456504500200230ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/execute" require "chef/provider/script" class Chef class Resource class Script < Chef::Resource::Execute # Chef-13: go back to using :name as the identity attr identity_attr :command def initialize(name, run_context = nil) super # Chef-13: the command variable should be initialized to nil @command = name @code = nil @interpreter = nil @flags = nil @default_guard_interpreter = :default end def command(arg = nil) unless arg.nil? # Chef-13: change this to raise if the user is trying to set a value here Chef::Log.warn "Specifying command attribute on a script resource is a coding error, use the 'code' attribute, or the execute resource" Chef::Log.warn "This attribute is deprecated and must be fixed or this code will fail on Chef 13" end super end def code(arg = nil) set_or_return( :code, arg, :kind_of => [ String ] ) end def interpreter(arg = nil) set_or_return( :interpreter, arg, :kind_of => [ String ] ) end def flags(arg = nil) set_or_return( :flags, arg, :kind_of => [ String ] ) end end end end chef-12.14.60/lib/chef/resource/service.rb000066400000000000000000000125261276456504500201650ustar00rootroot00000000000000# # Author:: AJ Christensen () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class Service < Chef::Resource identity_attr :service_name state_attrs :enabled, :running, :masked default_action :nothing allowed_actions :enable, :disable, :start, :stop, :restart, :reload, :mask, :unmask def initialize(name, run_context = nil) super @service_name = name @enabled = nil @running = nil @masked = nil @parameters = nil @pattern = service_name @start_command = nil @stop_command = nil @status_command = nil @restart_command = nil @reload_command = nil @init_command = nil @priority = nil @timeout = nil @run_levels = nil @user = nil @supports = { :restart => nil, :reload => nil, :status => nil } end def service_name(arg = nil) set_or_return( :service_name, arg, :kind_of => [ String ] ) end # regex for match against ps -ef when !supports[:has_status] && status == nil def pattern(arg = nil) set_or_return( :pattern, arg, :kind_of => [ String ] ) end # command to call to start service def start_command(arg = nil) set_or_return( :start_command, arg, :kind_of => [ String ] ) end # command to call to stop service def stop_command(arg = nil) set_or_return( :stop_command, arg, :kind_of => [ String ] ) end # command to call to get status of service def status_command(arg = nil) set_or_return( :status_command, arg, :kind_of => [ String ] ) end # command to call to restart service def restart_command(arg = nil) set_or_return( :restart_command, arg, :kind_of => [ String ] ) end def reload_command(arg = nil) set_or_return( :reload_command, arg, :kind_of => [ String ] ) end # The path to the init script associated with the service. On many # distributions this is '/etc/init.d/SERVICE_NAME' by default. In # non-standard configurations setting this value will save having to # specify overrides for the start_command, stop_command and # restart_command attributes. def init_command(arg = nil) set_or_return( :init_command, arg, :kind_of => [ String ] ) end # if the service is enabled or not def enabled(arg = nil) set_or_return( :enabled, arg, :kind_of => [ TrueClass, FalseClass ] ) end # if the service is running or not def running(arg = nil) set_or_return( :running, arg, :kind_of => [ TrueClass, FalseClass ] ) end # if the service is masked or not def masked(arg = nil) set_or_return( :masked, arg, :kind_of => [ TrueClass, FalseClass ] ) end # Priority arguments can have two forms: # # - a simple number, in which the default start runlevels get # that as the start value and stop runlevels get 100 - value. # # - a hash like { 2 => [:start, 20], 3 => [:stop, 55] }, where # the service will be marked as started with priority 20 in # runlevel 2, stopped in 3 with priority 55 and no symlinks or # similar for other runlevels # def priority(arg = nil) set_or_return( :priority, arg, :kind_of => [ Integer, String, Hash ] ) end # timeout only applies to the windows service manager def timeout(arg = nil) set_or_return( :timeout, arg, :kind_of => Integer ) end def parameters(arg = nil) set_or_return( :parameters, arg, :kind_of => [ Hash ] ) end def run_levels(arg = nil) set_or_return( :run_levels, arg, :kind_of => [ Array ] ) end def user(arg = nil) set_or_return( :user, arg, :kind_of => [ String ] ) end def supports(args = {}) if args.is_a? Array args.each { |arg| @supports[arg] = true } elsif args.any? @supports = args else @supports end end end end end chef-12.14.60/lib/chef/resource/smartos_package.rb000066400000000000000000000017061276456504500216660ustar00rootroot00000000000000# # Author:: Toomas Pelberg () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package/smartos" class Chef class Resource class SmartosPackage < Chef::Resource::Package resource_name :smartos_package provides :package, os: "solaris2", platform_family: "smartos" end end end chef-12.14.60/lib/chef/resource/solaris_package.rb000066400000000000000000000021351276456504500216470ustar00rootroot00000000000000# # Author:: Toomas Pelberg () # Author:: Prabhu Das () # Copyright:: Copyright 2013-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package/solaris" class Chef class Resource class SolarisPackage < Chef::Resource::Package resource_name :solaris_package provides :package, os: "solaris2", platform_family: "nexentacore" provides :package, os: "solaris2", platform_family: "solaris2", platform_version: "<= 5.10" end end end chef-12.14.60/lib/chef/resource/subversion.rb000066400000000000000000000027111276456504500207170ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/scm" class Chef class Resource class Subversion < Chef::Resource::Scm allowed_actions :force_export def initialize(name, run_context = nil) super @svn_arguments = "--no-auth-cache" @svn_info_args = "--no-auth-cache" @svn_binary = nil end # Override exception to strip password if any, so it won't appear in logs and different Chef notifications def custom_exception_message(e) "#{self} (#{defined_at}) had an error: #{e.class.name}: #{svn_password ? e.message.gsub(svn_password, "[hidden_password]") : e.message}" end def svn_binary(arg = nil) set_or_return(:svn_binary, arg, :kind_of => [String]) end end end end chef-12.14.60/lib/chef/resource/systemd_unit.rb000066400000000000000000000037111276456504500212500ustar00rootroot00000000000000# # Author:: Nathan Williams () # Copyright:: Copyright 2016, Nathan Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "iniparse" class Chef class Resource class SystemdUnit < Chef::Resource resource_name :systemd_unit default_action :nothing allowed_actions :create, :delete, :enable, :disable, :mask, :unmask, :start, :stop, :restart, :reload, :try_restart, :reload_or_restart, :reload_or_try_restart property :enabled, [TrueClass, FalseClass] property :active, [TrueClass, FalseClass] property :masked, [TrueClass, FalseClass] property :static, [TrueClass, FalseClass] property :user, String, desired_state: false property :content, [String, Hash] property :triggers_reload, [TrueClass, FalseClass], default: true, desired_state: false def to_ini case content when Hash IniParse.gen do |doc| content.each_pair do |sect, opts| doc.section(sect) do |section| opts.each_pair do |opt, val| section.option(opt, val) end end end end.to_s else content.to_s end end end end end chef-12.14.60/lib/chef/resource/template.rb000066400000000000000000000165541276456504500203450ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Chisamore () # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/file" require "chef/provider/template" require "chef/mixin/securable" class Chef class Resource class Template < Chef::Resource::File include Chef::Mixin::Securable attr_reader :inline_helper_blocks attr_reader :inline_helper_modules def initialize(name, run_context = nil) super @source = "#{::File.basename(name)}.erb" @cookbook = nil @local = false @variables = Hash.new @inline_helper_blocks = {} @inline_helper_modules = [] @helper_modules = [] end def source(file = nil) set_or_return( :source, file, :kind_of => [ String, Array ] ) end def variables(args = nil) set_or_return( :variables, args, :kind_of => [ Hash ] ) end def cookbook(args = nil) set_or_return( :cookbook, args, :kind_of => [ String ] ) end def local(args = nil) set_or_return( :local, args, :kind_of => [ TrueClass, FalseClass ] ) end # Declares a helper method to be defined in the template context when # rendering. # # === Example: # # ==== Basic usage: # Given the following helper: # helper(:static_value) { "hello from helper" } # A template with the following code: # <%= static_value %> # Will render as; # hello from helper # # ==== Referencing Instance Variables: # Any instance variables available to the template can be referenced in # the method body. For example, you can simplify accessing app-specific # node attributes like this: # helper(:app) { @node[:my_app_attributes] } # And use it in a template like this: # <%= app[:listen_ports] %> # This is equivalent to the non-helper template code: # <%= @node[:my_app_attributes][:listen_ports] %> # # ==== Method Arguments: # Helper methods can also take arguments. The syntax available for # argument specification supports full syntax available for method # definition. # # Continuing the above example of simplifying attribute access, we can # define a helper to look up app-specific attributes like this: # helper(:app) { |setting| @node[:my_app_attributes][setting] } # The template can then look up attributes like this: # <%= app(:listen_ports) %> def helper(method_name, &block) unless block_given? raise Exceptions::ValidationFailed, "`helper(:method)` requires a block argument (e.g., `helper(:method) { code }`)" end unless method_name.kind_of?(Symbol) raise Exceptions::ValidationFailed, "method_name argument to `helper(method_name)` must be a symbol (e.g., `helper(:method) { code }`)" end @inline_helper_blocks[method_name] = block end # Declares a module to define helper methods in the template's context # when rendering. There are two primary forms. # # === Inline Module Definition # When a block is given, the block is used to define a module which is # then mixed in to the template context w/ `extend`. # # ==== Inline Module Example # Given the following code in the template resource: # helpers do # # Add "syntax sugar" for referencing app-specific attributes # def app(attribute) # @node[:my_app_attributes][attribute] # end # end # You can use it in the template like so: # <%= app(:listen_ports) %> # Which is equivalent to: # <%= @node[:my_app_attributes][:listen_ports] %> # # === External Module Form # When a module name is given, the template context will be extended with # that module. This is the recommended way to customize template contexts # when you need to define more than an handful of helper functions (but # also try to keep your template helpers from getting out of hand--if you # have very complex logic in your template helpers, you should further # extract your code into separate libraries). # # ==== External Module Example # To extract the above inline module code to a library, you'd create a # library file like this: # module MyTemplateHelper # # Add "syntax sugar" for referencing app-specific attributes # def app(attribute) # @node[:my_app_attributes][attribute] # end # end # And in the template resource: # helpers(MyTemplateHelper) # The template code in the above example will work unmodified. def helpers(module_name = nil, &block) if block_given? && !module_name.nil? raise Exceptions::ValidationFailed, "Passing both a module and block to #helpers is not supported. Call #helpers multiple times instead" elsif block_given? @inline_helper_modules << block elsif module_name.kind_of?(::Module) @helper_modules << module_name elsif module_name.nil? raise Exceptions::ValidationFailed, "#helpers requires either a module name or inline module code as a block.\n" + "e.g.: helpers do; helper_code; end;\n" + "OR: helpers(MyHelpersModule)" else raise Exceptions::ValidationFailed, "Argument to #helpers must be a module. You gave #{module_name.inspect} (#{module_name.class})" end end # Compiles all helpers from inline method definitions, inline module # definitions, and external modules into an Array of Modules. The context # object for the template is extended with these modules to provide # per-resource template logic. def helper_modules compiled_helper_methods + compiled_helper_modules + @helper_modules end private # compiles helper methods into a module that can be included in template context def compiled_helper_methods if inline_helper_blocks.empty? [] else resource_helper_blocks = inline_helper_blocks helper_mod = Module.new do resource_helper_blocks.each do |method_name, method_body| define_method(method_name, &method_body) end end [ helper_mod ] end end def compiled_helper_modules @inline_helper_modules.map do |module_body| Module.new(&module_body) end end end end end chef-12.14.60/lib/chef/resource/timestamped_deploy.rb000066400000000000000000000016001276456504500224040ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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 Resource # Convenience class for using the deploy resource with the timestamped # deployment strategy (provider) class TimestampedDeploy < Chef::Resource::Deploy end end end chef-12.14.60/lib/chef/resource/user.rb000066400000000000000000000065771276456504500175140ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class User < Chef::Resource resource_name :user_resource_abstract_base_class # this prevents magickal class name DSL wiring identity_attr :username state_attrs :uid, :gid, :home default_action :create allowed_actions :create, :remove, :modify, :manage, :lock, :unlock def initialize(name, run_context = nil) super @username = name @comment = nil @uid = nil @gid = nil @home = nil @shell = nil @password = nil @system = false @manage_home = false @force = false @non_unique = false @supports = { manage_home: false, non_unique: false, } @iterations = 27855 @salt = nil end def username(arg = nil) set_or_return( :username, arg, :kind_of => [ String ] ) end def comment(arg = nil) set_or_return( :comment, arg, :kind_of => [ String ] ) end def uid(arg = nil) set_or_return( :uid, arg, :kind_of => [ String, Integer ] ) end def gid(arg = nil) set_or_return( :gid, arg, :kind_of => [ String, Integer ] ) end alias_method :group, :gid def home(arg = nil) set_or_return( :home, arg, :kind_of => [ String ] ) end def shell(arg = nil) set_or_return( :shell, arg, :kind_of => [ String ] ) end def password(arg = nil) set_or_return( :password, arg, :kind_of => [ String ] ) end def salt(arg = nil) set_or_return( :salt, arg, :kind_of => [ String ] ) end def iterations(arg = nil) set_or_return( :iterations, arg, :kind_of => [ Integer ] ) end def system(arg = nil) set_or_return( :system, arg, :kind_of => [ TrueClass, FalseClass ] ) end def manage_home(arg = nil) set_or_return( :manage_home, arg, :kind_of => [ TrueClass, FalseClass ] ) end def force(arg = nil) set_or_return( :force, arg, :kind_of => [ TrueClass, FalseClass ] ) end def non_unique(arg = nil) set_or_return( :non_unique, arg, :kind_of => [ TrueClass, FalseClass ] ) end end end end chef-12.14.60/lib/chef/resource/user/000077500000000000000000000000001276456504500171505ustar00rootroot00000000000000chef-12.14.60/lib/chef/resource/user/aix_user.rb000066400000000000000000000015561276456504500213230ustar00rootroot00000000000000# # 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/resource/user" class Chef class Resource class User class AixUser < Chef::Resource::User resource_name :aix_user provides :aix_user provides :user, os: "aix" end end end end chef-12.14.60/lib/chef/resource/user/dscl_user.rb000066400000000000000000000015641276456504500214660ustar00rootroot00000000000000# # 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/resource/user" class Chef class Resource class User class DsclUser < Chef::Resource::User resource_name :dscl_user provides :dscl_user provides :user, os: "darwin" end end end end chef-12.14.60/lib/chef/resource/user/linux_user.rb000066400000000000000000000025711276456504500216770ustar00rootroot00000000000000# # 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/resource/user" class Chef class Resource class User class LinuxUser < Chef::Resource::User resource_name :linux_user provides :linux_user provides :user, os: "linux" def initialize(name, run_context = nil) super @supports = { manage_home: true, non_unique: true, } @manage_home = false end def supports(args = {}) Chef.log_deprecation "setting supports on the linux_user resource is deprecated" # setting is deliberately disabled super({}) end def supports=(args) # setting is deliberately disabled supports({}) end end end end end chef-12.14.60/lib/chef/resource/user/pw_user.rb000066400000000000000000000015571276456504500211710ustar00rootroot00000000000000# # 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/resource/user" class Chef class Resource class User class PwUser < Chef::Resource::User resource_name :pw_user provides :pw_user provides :user, os: "freebsd" end end end end chef-12.14.60/lib/chef/resource/user/solaris_user.rb000066400000000000000000000016101276456504500222050ustar00rootroot00000000000000# # 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/resource/user" class Chef class Resource class User class SolarisUser < Chef::Resource::User resource_name :solaris_user provides :solaris_user provides :user, os: %w{omnios solaris2} end end end end chef-12.14.60/lib/chef/resource/user/windows_user.rb000066400000000000000000000015761276456504500222360ustar00rootroot00000000000000# # 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/resource/user" class Chef class Resource class User class WindowsUser < Chef::Resource::User resource_name :windows_user provides :windows_user provides :user, os: "windows" end end end end chef-12.14.60/lib/chef/resource/whyrun_safe_ruby_block.rb000066400000000000000000000014151276456504500232650ustar00rootroot00000000000000# # Author:: Phil Dibowitz () # Copyright:: Copyright 2013-2016, Facebook # 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 Resource class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock end end end chef-12.14.60/lib/chef/resource/windows_package.rb000066400000000000000000000036761276456504500217000ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/uris" require "chef/resource/package" require "chef/provider/package/windows" require "chef/win32/error" if RUBY_PLATFORM =~ /mswin|mingw|windows/ class Chef class Resource class WindowsPackage < Chef::Resource::Package include Chef::Mixin::Uris resource_name :windows_package provides :windows_package, os: "windows" provides :package, os: "windows" allowed_actions :install, :remove def initialize(name, run_context = nil) super @source ||= source(@package_name) if @package_name.downcase.end_with?(".msi") end # Unique to this resource property :installer_type, Symbol property :timeout, [ String, Integer ], default: 600 # In the past we accepted return code 127 for an unknown reason and 42 because of a bug property :returns, [ String, Integer, Array ], default: [ 0 ], desired_state: false property :source, String, coerce: (proc do |s| unless s.nil? uri_scheme?(s) ? s : Chef::Util::PathHelper.canonical_path(s, false) end end) property :checksum, String, desired_state: false property :remote_file_attributes, Hash, desired_state: false end end end chef-12.14.60/lib/chef/resource/windows_script.rb000066400000000000000000000043111276456504500215740ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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/platform/query_helpers" require "chef/resource/script" require "chef/mixin/windows_architecture_helper" class Chef class Resource class WindowsScript < Chef::Resource::Script # This is an abstract resource meant to be subclasses; thus no 'provides' set_guard_inherited_attributes(:architecture) protected def initialize(name, run_context, resource_name, interpreter_command) super(name, run_context) @interpreter = interpreter_command @resource_name = resource_name if resource_name @default_guard_interpreter = self.resource_name end include Chef::Mixin::WindowsArchitectureHelper public def architecture(arg = nil) assert_architecture_compatible!(arg) if ! arg.nil? result = set_or_return( :architecture, arg, :kind_of => Symbol ) end protected def assert_architecture_compatible!(desired_architecture) if desired_architecture == :i386 && Chef::Platform.windows_nano_server? raise Chef::Exceptions::Win32ArchitectureIncorrect, "cannot execute script with requested architecture 'i386' on Windows Nano Server" elsif ! node_supports_windows_architecture?(node, desired_architecture) raise Chef::Exceptions::Win32ArchitectureIncorrect, "cannot execute script with requested architecture '#{desired_architecture}' on a system with architecture '#{node_windows_architecture(node)}'" end end end end end chef-12.14.60/lib/chef/resource/windows_service.rb000066400000000000000000000037161276456504500217400ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/service" class Chef class Resource class WindowsService < Chef::Resource::Service # Until #1773 is resolved, you need to manually specify the windows_service resource # to use action :configure_startup and attribute startup_type provides :windows_service, os: "windows" provides :service, os: "windows" allowed_actions :configure_startup identity_attr :service_name state_attrs :enabled, :running def initialize(name, run_context = nil) super @startup_type = :automatic @run_as_user = "" @run_as_password = "" end def startup_type(arg = nil) # Set-Service arguments are automatic and manual # Win32::Service returns 'auto start' or 'demand start' respectively, which the provider currently uses set_or_return( :startup_type, arg, :equal_to => [ :automatic, :manual, :disabled ] ) end def run_as_user(arg = nil) set_or_return( :run_as_user, arg, :kind_of => [ String ] ) end def run_as_password(arg = nil) set_or_return( :run_as_password, arg, :kind_of => [ String ] ) end end end end chef-12.14.60/lib/chef/resource/yum_package.rb000066400000000000000000000031031276456504500210010ustar00rootroot00000000000000# # Author:: AJ Christensen () # Copyright:: Copyright 2008-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" require "chef/provider/package/yum" class Chef class Resource class YumPackage < Chef::Resource::Package resource_name :yum_package provides :package, os: "linux", platform_family: %w{rhel fedora} # Install a specific arch property :arch, [ String, Array ] # the {} on the proc here is because rspec chokes if it's do...end property :flush_cache, Hash, default: { before: false, after: false }, coerce: proc { |v| if v.is_a?(Array) v.each_with_object({}) { |arg, obj| obj[arg] = true } elsif v.any? v else { before: v, after: v } end } property :allow_downgrade, [ true, false ], default: false property :yum_binary, String end end end chef-12.14.60/lib/chef/resource/yum_repository.rb000066400000000000000000000064351276456504500216400ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 2016 Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class YumRepository < Chef::Resource resource_name :yum_repository provides :yum_repository # http://linux.die.net/man/5/yum.conf property :baseurl, String, regex: /.*/ property :cost, String, regex: /^\d+$/ property :clean_headers, [TrueClass, FalseClass], default: false # deprecated property :clean_metadata, [TrueClass, FalseClass], default: true property :description, String, regex: /.*/, default: "Ye Ole Rpm Repo" property :enabled, [TrueClass, FalseClass], default: true property :enablegroups, [TrueClass, FalseClass] property :exclude, String, regex: /.*/ property :failovermethod, String, equal_to: %w{priority roundrobin} property :fastestmirror_enabled, [TrueClass, FalseClass] property :gpgcheck, [TrueClass, FalseClass] property :gpgkey, [String, Array], regex: /.*/ property :http_caching, String, equal_to: %w{packages all none} property :include_config, String, regex: /.*/ property :includepkgs, String, regex: /.*/ property :keepalive, [TrueClass, FalseClass] property :make_cache, [TrueClass, FalseClass], default: true property :max_retries, [String, Integer] property :metadata_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/, /never/] property :mirrorexpire, String, regex: /.*/ property :mirrorlist, String, regex: /.*/ property :mirror_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/] property :mirrorlist_expire, String, regex: [/^\d+$/, /^\d+[mhd]$/] property :mode, default: "0644" property :priority, String, regex: /^(\d?[0-9]|[0-9][0-9])$/ property :proxy, String, regex: /.*/ property :proxy_username, String, regex: /.*/ property :proxy_password, String, regex: /.*/ property :username, String, regex: /.*/ property :password, String, regex: /.*/ property :repo_gpgcheck, [TrueClass, FalseClass] property :report_instanceid, [TrueClass, FalseClass] property :repositoryid, String, regex: /.*/, name_attribute: true property :sensitive, [TrueClass, FalseClass], default: false property :skip_if_unavailable, [TrueClass, FalseClass] property :source, String, regex: /.*/ property :sslcacert, String, regex: /.*/ property :sslclientcert, String, regex: /.*/ property :sslclientkey, String, regex: /.*/ property :sslverify, [TrueClass, FalseClass] property :timeout, String, regex: /^\d+$/ property :options, Hash default_action :create allowed_actions :create, :remove, :make_cache, :add end end end chef-12.14.60/lib/chef/resource/zypper_package.rb000066400000000000000000000016011276456504500215210ustar00rootroot00000000000000# # Author:: Joe Williams () # Copyright:: Copyright 2009-2016, Joe Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/package" class Chef class Resource class ZypperPackage < Chef::Resource::Package resource_name :zypper_package provides :package, platform_family: "suse" end end end chef-12.14.60/lib/chef/resource_builder.rb000066400000000000000000000135401276456504500202300ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # NOTE: this was extracted from the Recipe DSL mixin, relevant specs are in spec/unit/recipe_spec.rb class Chef class ResourceBuilder attr_reader :type attr_reader :name attr_reader :created_at attr_reader :params attr_reader :run_context attr_reader :cookbook_name attr_reader :recipe_name attr_reader :enclosing_provider attr_reader :resource # FIXME (ruby-2.1 syntax): most of these are mandatory def initialize(type: nil, name: nil, created_at: nil, params: nil, run_context: nil, cookbook_name: nil, recipe_name: nil, enclosing_provider: nil) @type = type @name = name @created_at = created_at @params = params @run_context = run_context @cookbook_name = cookbook_name @recipe_name = recipe_name @enclosing_provider = enclosing_provider end def build(&block) raise ArgumentError, "You must supply a name when declaring a #{type} resource" if name.nil? @resource = resource_class.new(name, run_context) if resource.resource_name.nil? raise Chef::Exceptions::InvalidResourceSpecification, "#{resource}.resource_name is `nil`! Did you forget to put `provides :blah` or `resource_name :blah` in your resource class?" end resource.source_line = created_at resource.declared_type = type # If we have a resource like this one, we want to steal its state # This behavior is very counter-intuitive and should be removed. # See CHEF-3694, https://tickets.opscode.com/browse/CHEF-3694 # Moved to this location to resolve CHEF-5052, https://tickets.opscode.com/browse/CHEF-5052 if prior_resource resource.load_from(prior_resource) end resource.cookbook_name = cookbook_name resource.recipe_name = recipe_name # Determine whether this resource is being created in the context of an enclosing Provider resource.enclosing_provider = enclosing_provider # XXX: this is required for definition params inside of the scope of a # subresource to work correctly. resource.params = params # Evaluate resource attribute DSL if block_given? resource.resource_initializing = true begin resource.instance_eval(&block) ensure resource.resource_initializing = false end end # emit a cloned resource warning if it is warranted if prior_resource if is_trivial_resource?(prior_resource) && identicalish_resources?(prior_resource, resource) emit_harmless_cloning_debug else emit_cloned_resource_warning end end # Run optional resource hook resource.after_created resource end private def resource_class # Checks the new platform => short_name => resource mapping initially # then fall back to the older approach (Chef::Resource.const_get) for # backward compatibility @resource_class ||= Chef::Resource.resource_for_node(type, run_context.node) end def is_trivial_resource?(resource) trivial_resource = resource_class.new(name, run_context) # force un-lazy the name property on the created trivial resource name_property = resource_class.properties.find { |sym, p| p.name_property? } trivial_resource.send(name_property[0]) unless name_property.nil? identicalish_resources?(trivial_resource, resource) end # this is an equality test specific to checking for 3694 cloning warnings def identicalish_resources?(first, second) skipped_ivars = [ :@source_line, :@cookbook_name, :@recipe_name, :@params, :@elapsed_time, :@declared_type ] checked_ivars = ( first.instance_variables | second.instance_variables ) - skipped_ivars non_matching_ivars = checked_ivars.reject do |iv| if iv == :@action && ( [first.instance_variable_get(iv)].flatten == [:nothing] || [second.instance_variable_get(iv)].flatten == [:nothing] ) # :nothing action on either side of the comparison always matches true else first.instance_variable_get(iv) == second.instance_variable_get(iv) end end Chef::Log.debug("ivars which did not match with the prior resource: #{non_matching_ivars}") non_matching_ivars.empty? end def emit_cloned_resource_warning message = "Cloning resource attributes for #{resource} from prior resource (CHEF-3694)" message << "\nPrevious #{prior_resource}: #{prior_resource.source_line}" if prior_resource.source_line message << "\nCurrent #{resource}: #{resource.source_line}" if resource.source_line Chef.log_deprecation(message) end def emit_harmless_cloning_debug Chef::Log.debug("Harmless resource cloning from #{prior_resource}:#{prior_resource.source_line} to #{resource}:#{resource.source_line}") end def prior_resource @prior_resource ||= begin key = "#{type}[#{name}]" run_context.resource_collection.lookup_local(key) rescue Chef::Exceptions::ResourceNotFound nil end end end end require "chef/exceptions" require "chef/resource" require "chef/log" chef-12.14.60/lib/chef/resource_collection.rb000066400000000000000000000103311276456504500207300ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource_collection/resource_set" require "chef/resource_collection/resource_list" require "chef/resource_collection/resource_collection_serialization" require "chef/log" require "forwardable" ## # ResourceCollection currently handles two tasks: # 1) Keeps an ordered list of resources to use when converging the node # 2) Keeps a unique list of resources (keyed as `type[name]`) used for notifications class Chef class ResourceCollection include ResourceCollectionSerialization extend Forwardable attr_reader :resource_set, :resource_list attr_accessor :run_context protected :resource_set, :resource_list def initialize(run_context = nil) @run_context = run_context @resource_set = ResourceSet.new @resource_list = ResourceList.new end # @param resource [Chef::Resource] The resource to insert # @param resource_type [String,Symbol] If known, the resource type used in the recipe, Eg `package`, `execute` # @param instance_name [String] If known, the recource name as used in the recipe, IE `vim` in `package 'vim'` # This method is meant to be the 1 insert method necessary in the future. It should support all known use cases # for writing into the ResourceCollection. def insert(resource, opts = {}) resource_type ||= opts[:resource_type] # Would rather use Ruby 2.x syntax, but oh well instance_name ||= opts[:instance_name] resource_list.insert(resource) if !(resource_type.nil? && instance_name.nil?) resource_set.insert_as(resource, resource_type, instance_name) else resource_set.insert_as(resource) end end def delete(key) resource_list.delete(key) resource_set.delete(key) end # @deprecated def []=(index, resource) Chef::Log.warn("`[]=` is deprecated, use `insert` (which only inserts at the end)") resource_list[index] = resource resource_set.insert_as(resource) end # @deprecated def push(*resources) Chef::Log.warn("`push` is deprecated, use `insert`") resources.flatten.each do |res| insert(res) end self end # @deprecated alias_method :<<, :insert # Read-only methods are simple to delegate - doing that below resource_list_methods = Enumerable.instance_methods + [:iterator, :all_resources, :[], :each, :execute_each_resource, :each_index, :empty?] - [:find] # find overridden below resource_set_methods = [:resources, :keys, :validate_lookup_spec!] def_delegators :resource_list, *resource_list_methods def_delegators :resource_set, *resource_set_methods def lookup_local(key) resource_set.lookup(key) end def find_local(*args) resource_set.find(*args) end def lookup(key) if run_context.nil? lookup_local(key) else lookup_recursive(run_context, key) end end def find(*args) if run_context.nil? find_local(*args) else find_recursive(run_context, *args) end end private def lookup_recursive(rc, key) rc.resource_collection.resource_set.lookup(key) rescue Chef::Exceptions::ResourceNotFound raise if rc.parent_run_context.nil? lookup_recursive(rc.parent_run_context, key) end def find_recursive(rc, *args) rc.resource_collection.resource_set.find(*args) rescue Chef::Exceptions::ResourceNotFound raise if rc.parent_run_context.nil? find_recursive(rc.parent_run_context, *args) end end end chef-12.14.60/lib/chef/resource_collection/000077500000000000000000000000001276456504500204055ustar00rootroot00000000000000chef-12.14.60/lib/chef/resource_collection/resource_collection_serialization.rb000066400000000000000000000033331276456504500277330ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class ResourceCollection module ResourceCollectionSerialization # Serialize this object as a hash def to_hash instance_vars = Hash.new self.instance_variables.each do |iv| instance_vars[iv] = self.instance_variable_get(iv) end { "json_class" => self.class.name, "instance_vars" => instance_vars, } end def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def self.included(base) base.extend(ClassMethods) end module ClassMethods def json_create(o) collection = self.new() o["instance_vars"].each do |k, v| collection.instance_variable_set(k.to_sym, v) end collection end end def is_chef_resource!(arg) unless arg.kind_of?(Chef::Resource) raise ArgumentError, "Cannot insert a #{arg.class} into a resource collection: must be a subclass of Chef::Resource" end true end end end end chef-12.14.60/lib/chef/resource_collection/resource_list.rb000066400000000000000000000103071276456504500236150ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/resource_collection/stepable_iterator" require "chef/resource_collection/resource_collection_serialization" require "forwardable" # This class keeps the list of all known Resources in the order they are to be executed in. It also keeps a pointer # to the most recently executed resource so we can add resources-to-execute after this point. class Chef class ResourceCollection class ResourceList include ResourceCollection::ResourceCollectionSerialization include Enumerable extend Forwardable attr_reader :iterator attr_reader :resources private :resources # Delegate direct access methods to the @resources array # 4 extra methods here are not included in the Enumerable's instance methods direct_access_methods = Enumerable.instance_methods + [ :[], :each, :each_index, :empty? ] def_delegators :resources, *(direct_access_methods) def initialize @resources = Array.new @insert_after_idx = nil end # @param resource [Chef::Resource] The resource to insert # If @insert_after_idx is nil, we are not currently executing a converge so the Resource is appended to the # end of the list. If @insert_after_idx is NOT nil, we ARE currently executing a converge so the resource # is inserted into the middle of the list after the last resource that was converged. If it is called multiple # times (when an LWRP contains multiple resources) it keeps track of that. See this example ResourceList: # [File1, LWRP1, File2] # The iterator starts and points to File1. It is executed and @insert_after_idx=0 # [File1, LWRP1, File2] # The iterator moves to LWRP1. It is executed and @insert_after_idx=1 # [File1, LWRP1, Service1, File2] # The LWRP execution inserts Service1 and @insert_after_idx=2 # [File1, LWRP1, Service1, Service2, File2] # The LWRP inserts Service2 and @insert_after_idx=3. The LWRP # finishes executing # [File1, LWRP1, Service1, Service2, File2] # The iterator moves to Service1 since it is the next non-executed # resource. The execute_each_resource call below resets @insert_after_idx=2 # If Service1 was another LWRP, it would insert its resources between Service1 and Service2. The iterator keeps # track of executed resources and @insert_after_idx keeps track of where the next resource to insert should be. def insert(resource) is_chef_resource!(resource) if @insert_after_idx @resources.insert(@insert_after_idx += 1, resource) else @resources << resource end end def delete(key) raise ArgumentError, "Must pass a Chef::Resource or String to delete" unless key.is_a?(String) || key.is_a?(Chef::Resource) key = key.to_s ret = @resources.reject! { |r| r.to_s == key } if ret.nil? raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{key} (did you define it first?)" end ret end # @deprecated - can be removed when it is removed from resource_collection.rb def []=(index, resource) @resources[index] = resource end def all_resources @resources end # FIXME: yard with @yield def execute_each_resource @iterator = ResourceCollection::StepableIterator.for_collection(@resources) @iterator.each_with_index do |resource, idx| @insert_after_idx = idx yield resource end end end end end chef-12.14.60/lib/chef/resource_collection/resource_set.rb000066400000000000000000000141421276456504500234360ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" require "chef/resource_collection/resource_collection_serialization" class Chef class ResourceCollection class ResourceSet include ResourceCollection::ResourceCollectionSerialization # Matches a multiple resource lookup specification, # e.g., "service[nginx,unicorn]" MULTIPLE_RESOURCE_MATCH = /^(.+)\[(.+?),(.+)\]$/ # Matches a single resource lookup specification, # e.g., "service[nginx]" SINGLE_RESOURCE_MATCH = /^(.+)\[(.+)\]$/ def initialize @resources_by_key = Hash.new end def keys @resources_by_key.keys end def insert_as(resource, resource_type = nil, instance_name = nil) is_chef_resource!(resource) resource_type ||= resource.resource_name instance_name ||= resource.name key = create_key(resource_type, instance_name) @resources_by_key[key] = resource end def lookup(key) raise ArgumentError, "Must pass a Chef::Resource or String to lookup" unless key.is_a?(String) || key.is_a?(Chef::Resource) key = key.to_s res = @resources_by_key[key] unless res raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{key} (did you define it first?)" end res end def delete(key) raise ArgumentError, "Must pass a Chef::Resource or String to delete" unless key.is_a?(String) || key.is_a?(Chef::Resource) key = key.to_s res = @resources_by_key.delete(key) if res == @resources_by_key.default raise Chef::Exceptions::ResourceNotFound, "Cannot find a resource matching #{key} (did you define it first?)" end res end # Find existing resources by searching the list of existing resources. Possible # forms are: # # find(:file => "foobar") # find(:file => [ "foobar", "baz" ]) # find("file[foobar]", "file[baz]") # find("file[foobar,baz]") # # Returns the matching resource, or an Array of matching resources. # # Raises an ArgumentError if you feed it bad lookup information # Raises a Runtime Error if it can't find the resources you are looking for. def find(*args) results = Array.new args.each do |arg| case arg when Hash results << find_resource_by_hash(arg) when String results << find_resource_by_string(arg) else msg = "arguments to #{self.class.name}#find should be of the form :resource => 'name' or 'resource[name]'" raise Chef::Exceptions::InvalidResourceSpecification, msg end end flat_results = results.flatten flat_results.length == 1 ? flat_results[0] : flat_results end # @deprecated # resources is a poorly named, but we have to maintain it for back # compat. alias_method :resources, :find # Returns true if +query_object+ is a valid string for looking up a # resource, or raises InvalidResourceSpecification if not. # === Arguments # * query_object should be a string of the form # "resource_type[resource_name]", a single element Hash (e.g., :service => # "apache2"), or a Chef::Resource (this is the happy path). Other arguments # will raise an exception. # === Returns # * true returns true for all valid input. # === Raises # * Chef::Exceptions::InvalidResourceSpecification for all invalid input. def validate_lookup_spec!(query_object) case query_object when Chef::Resource true when SINGLE_RESOURCE_MATCH, MULTIPLE_RESOURCE_MATCH true when Hash true when String raise Chef::Exceptions::InvalidResourceSpecification, "The string `#{query_object}' is not valid for resource collection lookup. Correct syntax is `resource_type[resource_name]'" else raise Chef::Exceptions::InvalidResourceSpecification, "The object `#{query_object.inspect}' is not valid for resource collection lookup. " + "Use a String like `resource_type[resource_name]' or a Chef::Resource object" end end private def create_key(resource_type, instance_name) "#{resource_type}[#{instance_name}]" end def find_resource_by_hash(arg) results = Array.new arg.each do |resource_type, name_list| instance_names = name_list.kind_of?(Array) ? name_list : [ name_list ] instance_names.each do |instance_name| results << lookup(create_key(resource_type, instance_name)) end end return results end def find_resource_by_string(arg) results = Array.new case arg when MULTIPLE_RESOURCE_MATCH resource_type = $1 arg =~ /^.+\[(.+)\]$/ resource_list = $1 resource_list.split(",").each do |instance_name| results << lookup(create_key(resource_type, instance_name)) end when SINGLE_RESOURCE_MATCH resource_type = $1 name = $2 results << lookup(create_key(resource_type, name)) else raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!" end return results end end end end chef-12.14.60/lib/chef/resource_collection/stepable_iterator.rb000066400000000000000000000052641276456504500244510ustar00rootroot00000000000000# Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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 ResourceCollection class StepableIterator def self.for_collection(new_collection) instance = new(new_collection) instance end attr_accessor :collection attr_reader :position def initialize(collection = []) @position = 0 @paused = false @collection = collection end def size collection.size end def each(&block) reset_iteration(block) @iterator_type = :element iterate end def each_index(&block) reset_iteration(block) @iterator_type = :index iterate end def each_with_index(&block) reset_iteration(block) @iterator_type = :element_with_index iterate end def paused? @paused end def pause @paused = true end def resume @paused = false iterate end def rewind @position = 0 end def skip_back(skips = 1) @position -= skips end def skip_forward(skips = 1) @position += skips end def step return nil if @position == size call_iterator_block @position += 1 end def iterate_on(iteration_type, &block) @iterator_type = iteration_type @iterator_block = block end private def reset_iteration(iterator_block) @iterator_block = iterator_block @position = 0 @paused = false end def iterate step while @position < size && !paused? collection end def call_iterator_block case @iterator_type when :element @iterator_block.call(collection[@position]) when :index @iterator_block.call(@position) when :element_with_index @iterator_block.call(collection[@position], @position) else raise "42error: someone forgot to set @iterator_type, wtf?" end end end end end chef-12.14.60/lib/chef/resource_definition.rb000066400000000000000000000037741276456504500207420ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/from_file" require "chef/mixin/params_validate" class Chef class ResourceDefinition include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate attr_accessor :name, :params, :recipe, :node def initialize(node = nil) @name = nil @params = Hash.new @recipe = nil @node = node end def define(resource_name, prototype_params = nil, &block) unless resource_name.kind_of?(Symbol) raise ArgumentError, "You must use a symbol when defining a new resource!" end @name = resource_name if prototype_params unless prototype_params.kind_of?(Hash) raise ArgumentError, "You must pass a hash as the prototype parameters for a definition." end @params = prototype_params end if Kernel.block_given? @recipe = block else raise ArgumentError, "You must pass a block to a definition." end Chef::DSL::Definitions.add_definition(name) true end # When we do the resource definition, we're really just setting new values for # the parameters we prototyped at the top. This method missing is as simple as # it gets. def method_missing(symbol, *args) @params[symbol] = args.length == 1 ? args[0] : args end def to_s "#{name}" end end end chef-12.14.60/lib/chef/resource_definition_list.rb000066400000000000000000000021561276456504500217660ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/from_file" require "chef/resource_definition" class Chef class ResourceDefinitionList include Chef::Mixin::FromFile attr_accessor :defines def initialize @defines = Hash.new end def define(resource_name, prototype_params = nil, &block) @defines[resource_name] = ResourceDefinition.new @defines[resource_name].define(resource_name, prototype_params, &block) true end end end chef-12.14.60/lib/chef/resource_reporter.rb000066400000000000000000000260051276456504500204440ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Prajakta Purohit (prajakta@chef.io>) # Auther:: Tyler Cloke () # # 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 "securerandom" require "chef/event_dispatch/base" class Chef class ResourceReporter < EventDispatch::Base ResourceReport = Struct.new(:new_resource, :current_resource, :action, :exception, :elapsed_time) do def self.new_with_current_state(new_resource, action, current_resource) report = new report.new_resource = new_resource report.action = action report.current_resource = current_resource report end def self.new_for_exception(new_resource, action) report = new report.new_resource = new_resource report.action = action report end # Future: Some resources store state information that does not convert nicely # to json. We can't call a resource's state method here, since there are conflicts # with some LWRPs, so we can't override a resource's state method to return # json-friendly state data. # # The registry key resource returns json-friendly state data through its state # attribute, and uses a read-only variable for fetching true state data. If # we have conflicts with other resources reporting json incompatible state, we # may want to extend the state_attrs API with the ability to rename POST'd # attrs. def for_json as_hash = {} as_hash["type"] = new_resource.resource_name.to_sym as_hash["name"] = new_resource.name.to_s as_hash["id"] = new_resource.identity.to_s as_hash["after"] = new_resource.state_for_resource_reporter as_hash["before"] = current_resource ? current_resource.state_for_resource_reporter : {} as_hash["duration"] = (elapsed_time * 1000).to_i.to_s as_hash["delta"] = new_resource.diff if new_resource.respond_to?("diff") as_hash["delta"] = "" if as_hash["delta"].nil? # TODO: rename as "action" as_hash["result"] = action.to_s if success? else #as_hash["result"] = "failed" end if new_resource.cookbook_name as_hash["cookbook_name"] = new_resource.cookbook_name as_hash["cookbook_version"] = new_resource.cookbook_version.version end as_hash end def finish self.elapsed_time = new_resource.elapsed_time end def success? !self.exception end end # End class ResouceReport attr_reader :updated_resources attr_reader :status attr_reader :exception attr_reader :run_id attr_reader :error_descriptions PROTOCOL_VERSION = "0.1.0" def initialize(rest_client) if Chef::Config[:enable_reporting] && !Chef::Config[:why_run] @reporting_enabled = true else @reporting_enabled = false end @updated_resources = [] @total_res_count = 0 @pending_update = nil @status = "success" @exception = nil @rest_client = rest_client @error_descriptions = {} @expanded_run_list = {} end def run_started(run_status) @run_status = run_status if reporting_enabled? begin resource_history_url = "reports/nodes/#{node_name}/runs" server_response = @rest_client.post(resource_history_url, { :action => :start, :run_id => run_id, :start_time => start_time.to_s }, headers) rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e handle_error_starting_run(e, resource_history_url) end end end def handle_error_starting_run(e, url) message = "Reporting error starting run. URL: #{url} " code = if e.response.code e.response.code.to_s else "Exception Code Empty" end if !e.response || (code != "404" && code != "406") exception = "Exception: #{code} " if Chef::Config[:enable_reporting_url_fatals] reporting_status = "Reporting fatals enabled. Aborting run. " Chef::Log.error(message + exception + reporting_status) raise else reporting_status = "Disabling reporting for run." Chef::Log.info(message + exception + reporting_status) end else reason = "Received #{code}. " if code == "406" reporting_status = "Client version not supported. Please update the client. Disabling reporting for run." Chef::Log.info(message + reason + reporting_status) else reporting_status = "Disabling reporting for run." Chef::Log.debug(message + reason + reporting_status) end end @reporting_enabled = false end def run_id @run_status.run_id end def resource_current_state_loaded(new_resource, action, current_resource) unless nested_resource?(new_resource) @pending_update = ResourceReport.new_with_current_state(new_resource, action, current_resource) end end def resource_up_to_date(new_resource, action) @total_res_count += 1 @pending_update = nil unless nested_resource?(new_resource) end def resource_skipped(resource, action, conditional) @total_res_count += 1 @pending_update = nil unless nested_resource?(resource) end def resource_updated(new_resource, action) @total_res_count += 1 end def resource_failed(new_resource, action, exception) @total_res_count += 1 unless nested_resource?(new_resource) @pending_update ||= ResourceReport.new_for_exception(new_resource, action) @pending_update.exception = exception end description = Formatters::ErrorMapper.resource_failed(new_resource, action, exception) @error_descriptions = description.for_json end def resource_completed(new_resource) if @pending_update && !nested_resource?(new_resource) @pending_update.finish @updated_resources << @pending_update @pending_update = nil end end def run_completed(node) @status = "success" post_reporting_data end def run_failed(exception) @exception = exception @status = "failure" # If we failed before we received the run_started callback, there's not much we can do # in terms of reporting if @run_status post_reporting_data end end def run_list_expanded(run_list_expansion) @expanded_run_list = run_list_expansion end def post_reporting_data if reporting_enabled? run_data = prepare_run_data resource_history_url = "reports/nodes/#{node_name}/runs/#{run_id}" Chef::Log.info("Sending resource update report (run-id: #{run_id})") Chef::Log.debug run_data.inspect compressed_data = encode_gzip(Chef::JSONCompat.to_json(run_data)) Chef::Log.debug("Sending compressed run data...") # Since we're posting compressed data we can not directly call post which expects JSON begin @rest_client.raw_request(:POST, resource_history_url, headers({ "Content-Encoding" => "gzip" }), compressed_data) rescue StandardError => e if e.respond_to? :response Chef::FileCache.store("failed-reporting-data.json", Chef::JSONCompat.to_json_pretty(run_data), 0640) Chef::Log.error("Failed to post reporting data to server (HTTP #{e.response.code}), saving to #{Chef::FileCache.load("failed-reporting-data.json", false)}") else Chef::Log.error("Failed to post reporting data to server (#{e})") end end else Chef::Log.debug("Server doesn't support resource history, skipping resource report.") end end def headers(additional_headers = {}) options = { "X-Ops-Reporting-Protocol-Version" => PROTOCOL_VERSION } options.merge(additional_headers) end def node_name @run_status.node.name end def start_time @run_status.start_time end def end_time @run_status.end_time end def prepare_run_data run_data = {} run_data["action"] = "end" run_data["resources"] = updated_resources.map do |resource_record| resource_record.for_json end run_data["status"] = @status run_data["run_list"] = Chef::JSONCompat.to_json(@run_status.node.run_list) run_data["total_res_count"] = @total_res_count.to_s run_data["data"] = {} run_data["start_time"] = start_time.to_s run_data["end_time"] = end_time.to_s run_data["expanded_run_list"] = Chef::JSONCompat.to_json(@expanded_run_list) if exception exception_data = {} exception_data["class"] = exception.inspect exception_data["message"] = exception.message exception_data["backtrace"] = Chef::JSONCompat.to_json(exception.backtrace) exception_data["description"] = @error_descriptions run_data["data"]["exception"] = exception_data end run_data end def run_list_expand_failed(node, exception) description = Formatters::ErrorMapper.run_list_expand_failed(node, exception) @error_descriptions = description.for_json end def cookbook_resolution_failed(expanded_run_list, exception) description = Formatters::ErrorMapper.cookbook_resolution_failed(expanded_run_list, exception) @error_descriptions = description.for_json end def cookbook_sync_failed(cookbooks, exception) description = Formatters::ErrorMapper.cookbook_sync_failed(cookbooks, exception) @error_descriptions = description.for_json end def reporting_enabled? @reporting_enabled end private # 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) @pending_update && @pending_update.new_resource != new_resource end def encode_gzip(data) "".tap do |out| Zlib::GzipWriter.wrap(StringIO.new(out)) { |gz| gz << data } end end end end chef-12.14.60/lib/chef/resource_resolver.rb000066400000000000000000000137551276456504500204530ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/exceptions" require "chef/platform/resource_priority_map" require "chef/mixin/convert_to_class_name" class Chef class ResourceResolver # # Resolve a resource by name. # # @param resource_name [Symbol] The resource DSL name (e.g. `:file`). # @param node [Chef::Node] The node against which to resolve. `nil` causes # platform filters to be ignored. # def self.resolve(resource_name, node: nil, canonical: nil) new(node, resource_name, canonical: canonical).resolve end # # Resolve a list of all resources that implement the given DSL (in order of # preference). # # @param resource_name [Symbol] The resource DSL name (e.g. `:file`). # @param node [Chef::Node] The node against which to resolve. `nil` causes # platform filters to be ignored. # @param canonical [Boolean] `true` or `false` to match canonical or # non-canonical values only. `nil` to ignore canonicality. # def self.list(resource_name, node: nil, canonical: nil) new(node, resource_name, canonical: canonical).list end include Chef::Mixin::ConvertToClassName # @api private attr_reader :node # @api private attr_reader :resource_name # @api private def resource Chef.log_deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.") resource_name end # @api private attr_reader :action # @api private attr_reader :canonical # # Create a resolver. # # @param node [Chef::Node] The node against which to resolve. `nil` causes # platform filters to be ignored. # @param resource_name [Symbol] The resource DSL name (e.g. `:file`). # @param canonical [Boolean] `true` or `false` to match canonical or # non-canonical values only. `nil` to ignore canonicality. Default: `nil` # # @api private use Chef::ResourceResolver.resolve or .list instead. def initialize(node, resource_name, canonical: nil) @node = node @resource_name = resource_name.to_sym @canonical = canonical end # @api private use Chef::ResourceResolver.resolve instead. def resolve # log this so we know what resources will work for the generic resource on the node (early cut) Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}" handler = prioritized_handlers.first if handler Chef::Log.debug "Resource for #{resource_name} is #{handler}" else Chef::Log.debug "Dynamic resource resolver FAILED to resolve a resource for #{resource_name}" end handler end # @api private def list Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}" prioritized_handlers end # # Whether this DSL is provided by the given resource_class. # # Does NOT call provides? on the resource (it is assumed this is being # called *from* provides?). # # @api private def provided_by?(resource_class) potential_handlers.include?(resource_class) end # # Whether the given handler attempts to provide the resource class at all. # # @api private def self.includes_handler?(resource_name, resource_class) handler_map.list(nil, resource_name).include?(resource_class) end protected def self.priority_map Chef.resource_priority_map end def self.handler_map Chef.resource_handler_map end def priority_map Chef.resource_priority_map end def handler_map Chef.resource_handler_map end # @api private def potential_handlers handler_map.list(node, resource_name, canonical: canonical).uniq end def enabled_handlers potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource_name) } end def prioritized_handlers @prioritized_handlers ||= begin enabled_handlers = self.enabled_handlers prioritized = priority_map.list(node, resource_name, canonical: canonical).flatten(1) prioritized &= enabled_handlers # Filter the priority map by the actual enabled handlers prioritized |= enabled_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set) prioritized end end def overrode_provides?(handler) handler.method(:provides?).owner != Chef::Resource.method(:provides?).owner end module Deprecated # return a deterministically sorted list of Chef::Resource subclasses def resources Chef::Resource.sorted_descendants end def enabled_handlers handlers = super if handlers.empty? handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) } handlers.each do |handler| Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!") Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.") end end handlers end end prepend Deprecated end end chef-12.14.60/lib/chef/resources.rb000066400000000000000000000066171276456504500167140ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource/apt_package" require "chef/resource/apt_repository" require "chef/resource/apt_update" require "chef/resource/bash" require "chef/resource/batch" require "chef/resource/breakpoint" require "chef/resource/cookbook_file" require "chef/resource/chef_gem" require "chef/resource/chocolatey_package" require "chef/resource/cron" require "chef/resource/csh" require "chef/resource/deploy" require "chef/resource/deploy_revision" require "chef/resource/directory" require "chef/resource/dpkg_package" require "chef/resource/dsc_script" require "chef/resource/dsc_resource" require "chef/resource/easy_install_package" require "chef/resource/env" require "chef/resource/erl_call" require "chef/resource/execute" require "chef/resource/file" require "chef/resource/freebsd_package" require "chef/resource/ips_package" require "chef/resource/gem_package" require "chef/resource/git" require "chef/resource/group" require "chef/resource/http_request" require "chef/resource/homebrew_package" require "chef/resource/ifconfig" require "chef/resource/ksh" require "chef/resource/launchd" require "chef/resource/link" require "chef/resource/log" require "chef/resource/macports_package" require "chef/resource/mdadm" require "chef/resource/mount" require "chef/resource/ohai" require "chef/resource/openbsd_package" require "chef/resource/package" require "chef/resource/pacman_package" require "chef/resource/paludis_package" require "chef/resource/perl" require "chef/resource/portage_package" require "chef/resource/powershell_script" require "chef/resource/osx_profile" require "chef/resource/python" require "chef/resource/reboot" require "chef/resource/registry_key" require "chef/resource/remote_directory" require "chef/resource/remote_file" require "chef/resource/rpm_package" require "chef/resource/solaris_package" require "chef/resource/route" require "chef/resource/ruby" require "chef/resource/ruby_block" require "chef/resource/scm" require "chef/resource/script" require "chef/resource/service" require "chef/resource/systemd_unit" require "chef/resource/windows_service" require "chef/resource/subversion" require "chef/resource/smartos_package" require "chef/resource/template" require "chef/resource/timestamped_deploy" require "chef/resource/user" require "chef/resource/user/aix_user" require "chef/resource/user/dscl_user" require "chef/resource/user/linux_user" require "chef/resource/user/pw_user" require "chef/resource/user/solaris_user" require "chef/resource/user/windows_user" require "chef/resource/whyrun_safe_ruby_block" require "chef/resource/windows_package" require "chef/resource/yum_package" require "chef/resource/yum_repository" require "chef/resource/lwrp_base" require "chef/resource/bff_package" require "chef/resource/zypper_package" chef-12.14.60/lib/chef/rest.rb000066400000000000000000000151141276456504500156470ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Thom May () # Author:: Nuo Yan () # Author:: Christopher Brown () # Author:: Christopher Walters () # Copyright:: Copyright 2009-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 "tempfile" require "chef/http" class Chef class HTTP; end class REST < HTTP; end end require "chef/http/authenticator" require "chef/http/decompressor" require "chef/http/json_input" require "chef/http/json_to_model_output" require "chef/http/cookie_manager" require "chef/http/validate_content_length" require "chef/config" require "chef/exceptions" require "chef/platform/query_helpers" require "chef/http/remote_request_id" class Chef # == Chef::REST # Chef's custom REST client with built-in JSON support and RSA signed header # authentication. class REST < HTTP # Backwards compatibility for things that use # Chef::REST::RESTRequest or its constants RESTRequest = HTTP::HTTPRequest attr_accessor :url, :cookies, :sign_on_redirect, :redirect_limit attr_reader :authenticator # Create a REST client object. The supplied +url+ is used as the base for # all subsequent requests. For example, when initialized with a base url # http://localhost:4000, a call to +get_rest+ with 'nodes' will make an # HTTP GET request to http://localhost:4000/nodes def initialize(url, client_name = Chef::Config[:node_name], signing_key_filename = Chef::Config[:client_key], options = {}) Chef.log_deprecation("Chef::REST is deprecated. Please use Chef::ServerAPI, or investigate Ridley or ChefAPI.") signing_key_filename = nil if chef_zero_uri?(url) options = options.dup options[:client_name] = client_name options[:signing_key_filename] = signing_key_filename super(url, options) @decompressor = Decompressor.new(options) @authenticator = Authenticator.new(options) @request_id = RemoteRequestID.new(options) @middlewares << JSONInput.new(options) @middlewares << JSONToModelOutput.new(options) @middlewares << CookieManager.new(options) @middlewares << @decompressor @middlewares << @authenticator @middlewares << @request_id # ValidateContentLength should come after Decompressor # because the order of middlewares is reversed when handling # responses. @middlewares << ValidateContentLength.new(options) end def signing_key_filename authenticator.signing_key_filename end def auth_credentials authenticator.auth_credentials end def client_name authenticator.client_name end def signing_key authenticator.raw_key end def sign_requests? authenticator.sign_requests? end # Send an HTTP GET request to the path # # Using this method to +fetch+ a file is considered deprecated. # # === Parameters # path:: The path to GET # raw:: Whether you want the raw body returned, or JSON inflated. Defaults # to JSON inflated. def get(path, raw = false, headers = {}) if raw streaming_request(path, headers) else request(:GET, path, headers) end end alias :get_rest :get alias :delete_rest :delete alias :post_rest :post alias :put_rest :put # Streams a download to a tempfile, then yields the tempfile to a block. # After the download, the tempfile will be closed and unlinked. # If you rename the tempfile, it will not be deleted. # Beware that if the server streams infinite content, this method will # stream it until you run out of disk space. def fetch(path, headers = {}) streaming_request(create_url(path), headers) { |tmp_file| yield tmp_file } end alias :api_request :request # Do a HTTP request where no middleware is loaded (e.g. JSON input/output # conversion) but the standard Chef Authentication headers are added to the # request. def raw_http_request(method, path, headers, data) url = create_url(path) method, url, headers, data = @authenticator.handle_request(method, url, headers, data) method, url, headers, data = @request_id.handle_request(method, url, headers, data) response, rest_request, return_value = send_http_request(method, url, headers, data) response.error! unless success_response?(response) return_value rescue Exception => exception log_failed_request(response, return_value) unless response.nil? if exception.respond_to?(:chef_rest_request=) exception.chef_rest_request = rest_request end raise end # Deprecated: # Responsibilities of this method have been split up. The #http_client is # now responsible for making individual requests, while # #retrying_http_errors handles error/retry logic. def retriable_http_request(method, url, req_body, headers) rest_request = Chef::HTTP::HTTPRequest.new(method, url, req_body, headers) Chef::Log.debug("Sending HTTP request via #{method} to #{url.host}:#{url.port}#{rest_request.path}") retrying_http_errors(url) do yield rest_request end end # Customized streaming behavior; sets the accepted content type to "*/*" # if not otherwise specified for compatibility purposes def streaming_request(url, headers, &block) headers["Accept"] ||= "*/*" super end alias :retriable_rest_request :retriable_http_request def follow_redirect unless @sign_on_redirect @authenticator.sign_request = false end super ensure @authenticator.sign_request = true end public :create_url ############################################################################ # DEPRECATED ############################################################################ def decompress_body(body) @decompressor.decompress_body(body) end def authentication_headers(method, url, json_body = nil) authenticator.authentication_headers(method, url, json_body) end end end chef-12.14.60/lib/chef/role.rb000066400000000000000000000175301276456504500156370ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Nuo Yan () # Author:: Christopher Brown () # 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/mixin/params_validate" require "chef/mixin/from_file" require "chef/run_list" require "chef/mash" require "chef/json_compat" require "chef/server_api" require "chef/search/query" class Chef class Role include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate attr_accessor :chef_server_rest # Create a new Chef::Role object. def initialize(chef_server_rest: nil) @name = "" @description = "" @default_attributes = Mash.new @override_attributes = Mash.new @env_run_lists = { "_default" => Chef::RunList.new } @chef_server_rest = chef_server_rest end def chef_server_rest @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def self.chef_server_rest Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end def name(arg = nil) set_or_return( :name, arg, :regex => /^[\-[:alnum:]_]+$/ ) end def description(arg = nil) set_or_return( :description, arg, :kind_of => String ) end def run_list(*args) if args.length > 0 @env_run_lists["_default"].reset!(args) end @env_run_lists["_default"] end alias_method :recipes, :run_list # For run_list expansion def run_list_for(environment) if env_run_lists[environment].nil? env_run_lists["_default"] else env_run_lists[environment] end end def active_run_list_for(environment) @env_run_lists.has_key?(environment) ? environment : "_default" end # Per environment run lists def env_run_lists(env_run_lists = nil) if !env_run_lists.nil? unless env_run_lists.key?("_default") msg = "_default key is required in env_run_lists.\n" msg << "(env_run_lists: #{env_run_lists.inspect})" raise Chef::Exceptions::InvalidEnvironmentRunListSpecification, msg end @env_run_lists.clear env_run_lists.each { |k, v| @env_run_lists[k] = Chef::RunList.new(*Array(v)) } end @env_run_lists end alias :env_run_list :env_run_lists def env_run_lists_add(env_run_lists = nil) if !env_run_lists.nil? env_run_lists.each { |k, v| @env_run_lists[k] = Chef::RunList.new(*Array(v)) } end @env_run_lists end alias :env_run_list_add :env_run_lists_add def default_attributes(arg = nil) set_or_return( :default_attributes, arg, :kind_of => Hash ) end def override_attributes(arg = nil) set_or_return( :override_attributes, arg, :kind_of => Hash ) end def to_hash env_run_lists_without_default = @env_run_lists.dup env_run_lists_without_default.delete("_default") result = { "name" => @name, "description" => @description, "json_class" => self.class.name, "default_attributes" => @default_attributes, "override_attributes" => @override_attributes, "chef_type" => "role", #Render to_json correctly for run_list items (both run_list and evn_run_lists) #so malformed json does not result "run_list" => run_list.run_list.map { |item| item.to_s }, "env_run_lists" => env_run_lists_without_default.inject({}) do |accumulator, (k, v)| accumulator[k] = v.map { |x| x.to_s } accumulator end, } result end # Serialize this object as a hash def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def update_from!(o) description(o.description) recipes(o.recipes) if defined?(o.recipes) default_attributes(o.default_attributes) override_attributes(o.override_attributes) env_run_lists(o.env_run_lists) unless o.env_run_lists.nil? self end # Create a Chef::Role from JSON def self.json_create(o) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::Role#from_hash") from_hash(o) end def self.from_hash(o) role = new role.name(o["name"]) role.description(o["description"]) role.default_attributes(o["default_attributes"]) role.override_attributes(o["override_attributes"]) # _default run_list is in 'run_list' for newer clients, and # 'recipes' for older clients. env_run_list_hash = { "_default" => (o.has_key?("run_list") ? o["run_list"] : o["recipes"]) } # Clients before 0.10 do not include env_run_lists, so only # merge if it's there. if o["env_run_lists"] env_run_list_hash.merge!(o["env_run_lists"]) end role.env_run_lists(env_run_list_hash) role end # Get the list of all roles from the API. def self.list(inflate = false) if inflate response = Hash.new Chef::Search::Query.new.search(:role) do |n| response[n.name] = n unless n.nil? end response else chef_server_rest.get("roles") end end # Load a role by name from the API def self.load(name) from_hash(chef_server_rest.get("roles/#{name}")) end def environment(env_name) chef_server_rest.get("roles/#{@name}/environments/#{env_name}") end def environments chef_server_rest.get("roles/#{@name}/environments") end # Remove this role via the REST API def destroy chef_server_rest.delete("roles/#{@name}") end # Save this role via the REST API def save begin chef_server_rest.put("roles/#{@name}", self) rescue Net::HTTPServerException => e raise e unless e.response.code == "404" chef_server_rest.post("roles", self) end self end # Create the role via the REST API def create chef_server_rest.post("roles", self) self end # As a string def to_s "role[#{@name}]" end # Load a role from disk - prefers to load the JSON, but will happily load # the raw rb files as well. Can search within directories in the role_path. def self.from_disk(name) paths = Array(Chef::Config[:role_path]) paths.each do |path| roles_files = Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(path), "**", "**")) js_files = roles_files.select { |file| file.match(/\/#{name}\.json$/) } rb_files = roles_files.select { |file| file.match(/\/#{name}\.rb$/) } if js_files.count > 1 || rb_files.count > 1 raise Chef::Exceptions::DuplicateRole, "Multiple roles of same type found named #{name}" end js_path, rb_path = js_files.first, rb_files.first if js_path && File.exists?(js_path) # from_json returns object.class => json_class in the JSON. hsh = Chef::JSONCompat.parse(IO.read(js_path)) return from_hash(hsh) elsif rb_path && File.exists?(rb_path) role = Chef::Role.new role.name(name) role.from_file(rb_path) return role end end raise Chef::Exceptions::RoleNotFound, "Role '#{name}' could not be loaded from disk" end end end chef-12.14.60/lib/chef/run_context.rb000066400000000000000000000523711276456504500172500ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: Tim Hinderliter () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/resource_collection" require "chef/cookbook_version" require "chef/node" require "chef/role" require "chef/log" require "chef/recipe" require "chef/run_context/cookbook_compiler" require "chef/event_dispatch/events_output_stream" require "forwardable" class Chef # == Chef::RunContext # Value object that loads and tracks the context of a Chef run class RunContext # # Global state # # # The node for this run # # @return [Chef::Node] # attr_reader :node # # The set of cookbooks involved in this run # # @return [Chef::CookbookCollection] # attr_reader :cookbook_collection # # Resource Definitions for this run. Populated when the files in # +definitions/+ are evaluated (this is triggered by #load). # # @return [Array[Chef::ResourceDefinition]] # attr_reader :definitions # # Event dispatcher for this run. # # @return [Chef::EventDispatch::Dispatcher] # attr_reader :events # # Hash of factoids for a reboot request. # # @return [Hash] # attr_accessor :reboot_info # # Scoped state # # # The parent run context. # # @return [Chef::RunContext] The parent run context, or `nil` if this is the # root context. # attr_reader :parent_run_context # # The collection of resources intended to be converged (and able to be # notified). # # @return [Chef::ResourceCollection] # # @see CookbookCompiler # attr_reader :resource_collection # # The list of control groups to execute during the audit phase # attr_reader :audits # # Notification handling # # # A Hash containing the before notifications triggered by resources # during the converge phase of the chef run. # # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from # => # attr_reader :before_notification_collection # # A Hash containing the immediate notifications triggered by resources # during the converge phase of the chef run. # # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from # => # attr_reader :immediate_notification_collection # # A Hash containing the delayed (end of run) notifications triggered by # resources during the converge phase of the chef run. # # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from # => # attr_reader :delayed_notification_collection # # An Array containing the delayed (end of run) notifications triggered by # resources during the converge phase of the chef run. # # @return [Array[Chef::Resource::Notification]] An array of notification objects # attr_reader :delayed_actions # Creates a new Chef::RunContext object and populates its fields. This object gets # used by the Chef Server to generate a fully compiled recipe list for a node. # # @param node [Chef::Node] The node to run against. # @param cookbook_collection [Chef::CookbookCollection] The cookbooks # involved in this run. # @param events [EventDispatch::Dispatcher] The event dispatcher for this # run. # def initialize(node, cookbook_collection, events) @node = node @cookbook_collection = cookbook_collection @events = events node.run_context = self node.set_cookbook_attribute @definitions = Hash.new @loaded_recipes_hash = {} @loaded_attributes_hash = {} @reboot_info = {} @cookbook_compiler = nil @delayed_actions = [] initialize_child_state end # # Triggers the compile phase of the chef run. # # @param run_list_expansion [Chef::RunList::RunListExpansion] The run list. # @see Chef::RunContext::CookbookCompiler # def load(run_list_expansion) @cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events) cookbook_compiler.compile end # # Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext # def initialize_child_state @audits = {} @resource_collection = Chef::ResourceCollection.new(self) @before_notification_collection = Hash.new { |h, k| h[k] = [] } @immediate_notification_collection = Hash.new { |h, k| h[k] = [] } @delayed_notification_collection = Hash.new { |h, k| h[k] = [] } @delayed_actions = [] end # # Adds an before notification to the +before_notification_collection+. # # @param [Chef::Resource::Notification] The notification to add. # def notifies_before(notification) # Note for the future, notification.notifying_resource may be an instance # of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes} # with a string value. before_notification_collection[notification.notifying_resource.declared_key] << notification end # # Adds an immediate notification to the +immediate_notification_collection+. # # @param [Chef::Resource::Notification] The notification to add. # def notifies_immediately(notification) # Note for the future, notification.notifying_resource may be an instance # of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes} # with a string value. immediate_notification_collection[notification.notifying_resource.declared_key] << notification end # # Adds a delayed notification to the +delayed_notification_collection+. # # @param [Chef::Resource::Notification] The notification to add. # def notifies_delayed(notification) # Note for the future, notification.notifying_resource may be an instance # of Chef::Resource::UnresolvedSubscribes when calling {Resource#subscribes} # with a string value. delayed_notification_collection[notification.notifying_resource.declared_key] << notification end # # Adds a delayed action to the +delayed_actions+. # def add_delayed_action(notification) if delayed_actions.any? { |existing_notification| existing_notification.duplicates?(notification) } Chef::Log.info( "#{notification.notifying_resource} not queuing delayed action #{notification.action} on #{notification.resource}"\ " (delayed), as it's already been queued") else delayed_actions << notification end end # # Get the list of before notifications sent by the given resource. # # @return [Array[Notification]] # def before_notifications(resource) return before_notification_collection[resource.declared_key] end # # Get the list of immediate notifications sent by the given resource. # # @return [Array[Notification]] # def immediate_notifications(resource) return immediate_notification_collection[resource.declared_key] end # # Get the list of delayed (end of run) notifications sent by the given # resource. # # @return [Array[Notification]] # def delayed_notifications(resource) return delayed_notification_collection[resource.declared_key] end # # Cookbook and recipe loading # # # Evaluates the recipes +recipe_names+. Used by DSL::IncludeRecipe # # @param recipe_names [Array[String]] The list of recipe names (e.g. # 'my_cookbook' or 'my_cookbook::my_resource'). # @param current_cookbook The cookbook we are currently running in. # # @see DSL::IncludeRecipe#include_recipe # def include_recipe(*recipe_names, current_cookbook: nil) result_recipes = Array.new recipe_names.flatten.each do |recipe_name| if result = load_recipe(recipe_name, current_cookbook: current_cookbook) result_recipes << result end end result_recipes end # # Evaluates the recipe +recipe_name+. Used by DSL::IncludeRecipe # # TODO I am sort of confused why we have both this and include_recipe ... # I don't see anything different beyond accepting and returning an # array of recipes. # # @param recipe_names [Array[String]] The recipe name (e.g 'my_cookbook' or # 'my_cookbook::my_resource'). # @param current_cookbook The cookbook we are currently running in. # # @return A truthy value if the load occurred; `false` if already loaded. # # @see DSL::IncludeRecipe#load_recipe # def load_recipe(recipe_name, current_cookbook: nil) Chef::Log.debug("Loading recipe #{recipe_name} via include_recipe") cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name, current_cookbook: current_cookbook) if unreachable_cookbook?(cookbook_name) # CHEF-4367 Chef::Log.warn(<<-ERROR_MESSAGE) MissingCookbookDependency: Recipe `#{recipe_name}` is not in the run_list, and cookbook '#{cookbook_name}' is not a dependency of any cookbook in the run_list. To load this recipe, first add a dependency on cookbook '#{cookbook_name}' in the cookbook you're including it from in that cookbook's metadata. ERROR_MESSAGE end if loaded_fully_qualified_recipe?(cookbook_name, recipe_short_name) Chef::Log.debug("I am not loading #{recipe_name}, because I have already seen it.") false else loaded_recipe(cookbook_name, recipe_short_name) node.loaded_recipe(cookbook_name, recipe_short_name) cookbook = cookbook_collection[cookbook_name] cookbook.load_recipe(recipe_short_name, self) end end # # Load the given recipe from a filename. # # @param recipe_file [String] The recipe filename. # # @return [Chef::Recipe] The loaded recipe. # # @raise [Chef::Exceptions::RecipeNotFound] If the file does not exist. # def load_recipe_file(recipe_file) if !File.exist?(recipe_file) raise Chef::Exceptions::RecipeNotFound, "could not find recipe file #{recipe_file}" end Chef::Log.debug("Loading recipe file #{recipe_file}") recipe = Chef::Recipe.new("@recipe_files", recipe_file, self) recipe.from_file(recipe_file) recipe end # # Look up an attribute filename. # # @param cookbook_name [String] The cookbook name of the attribute file. # @param attr_file_name [String] The attribute file's name (not path). # # @return [String] The filename. # # @see DSL::IncludeAttribute#include_attribute # # @raise [Chef::Exceptions::CookbookNotFound] If the cookbook could not be found. # @raise [Chef::Exceptions::AttributeNotFound] If the attribute file could not be found. # def resolve_attribute(cookbook_name, attr_file_name) cookbook = cookbook_collection[cookbook_name] raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{cookbook_name} while loading attribute #{name}" unless cookbook attribute_filename = cookbook.attribute_filenames_by_short_filename[attr_file_name] raise Chef::Exceptions::AttributeNotFound, "could not find filename for attribute #{attr_file_name} in cookbook #{cookbook_name}" unless attribute_filename attribute_filename end # # A list of all recipes that have been loaded. # # This is stored internally as a Hash, so ordering is predictable. # # TODO is the above statement true in a 1.9+ ruby world? Is it relevant? # # @return [Array[String]] A list of recipes in fully qualified form, e.g. # the recipe "nginx" will be given as "nginx::default". # # @see #loaded_recipe? To determine if a particular recipe has been loaded. # def loaded_recipes loaded_recipes_hash.keys end # # A list of all attributes files that have been loaded. # # Stored internally using a Hash, so order is predictable. # # TODO is the above statement true in a 1.9+ ruby world? Is it relevant? # # @return [Array[String]] A list of attribute file names in fully qualified # form, e.g. the "nginx" will be given as "nginx::default". # def loaded_attributes loaded_attributes_hash.keys end # # Find out if a given recipe has been loaded. # # @param cookbook [String] Cookbook name. # @param recipe [String] Recipe name. # # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise. # def loaded_fully_qualified_recipe?(cookbook, recipe) loaded_recipes_hash.has_key?("#{cookbook}::#{recipe}") end # # Find out if a given recipe has been loaded. # # @param recipe [String] Recipe name. "nginx" and "nginx::default" yield # the same results. # # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise. # def loaded_recipe?(recipe) cookbook, recipe_name = Chef::Recipe.parse_recipe_name(recipe) loaded_fully_qualified_recipe?(cookbook, recipe_name) end # # Mark a given recipe as having been loaded. # # @param cookbook [String] Cookbook name. # @param recipe [String] Recipe name. # def loaded_recipe(cookbook, recipe) loaded_recipes_hash["#{cookbook}::#{recipe}"] = true end # # Find out if a given attribute file has been loaded. # # @param cookbook [String] Cookbook name. # @param attribute_file [String] Attribute file name. # # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise. # def loaded_fully_qualified_attribute?(cookbook, attribute_file) loaded_attributes_hash.has_key?("#{cookbook}::#{attribute_file}") end # # Mark a given attribute file as having been loaded. # # @param cookbook [String] Cookbook name. # @param attribute_file [String] Attribute file name. # def loaded_attribute(cookbook, attribute_file) loaded_attributes_hash["#{cookbook}::#{attribute_file}"] = true end ## # Cookbook File Introspection # # Find out if the cookbook has the given template. # # @param cookbook [String] Cookbook name. # @param template_name [String] Template name. # # @return [Boolean] `true` if the template is in the cookbook, `false` # otherwise. # @see Chef::CookbookVersion#has_template_for_node? # def has_template_in_cookbook?(cookbook, template_name) cookbook = cookbook_collection[cookbook] cookbook.has_template_for_node?(node, template_name) end # # Find out if the cookbook has the given file. # # @param cookbook [String] Cookbook name. # @param cb_file_name [String] File name. # # @return [Boolean] `true` if the file is in the cookbook, `false` # otherwise. # @see Chef::CookbookVersion#has_cookbook_file_for_node? # def has_cookbook_file_in_cookbook?(cookbook, cb_file_name) cookbook = cookbook_collection[cookbook] cookbook.has_cookbook_file_for_node?(node, cb_file_name) end # # Find out whether the given cookbook is in the cookbook dependency graph. # # @param cookbook_name [String] Cookbook name. # # @return [Boolean] `true` if the cookbook is reachable, `false` otherwise. # # @see Chef::CookbookCompiler#unreachable_cookbook? def unreachable_cookbook?(cookbook_name) cookbook_compiler.unreachable_cookbook?(cookbook_name) end # # Open a stream object that can be printed into and will dispatch to events # # @param name [String] The name of the stream. # @param options [Hash] Other options for the stream. # # @return [EventDispatch::EventsOutputStream] The created stream. # # @yield If a block is passed, it will be run and the stream will be closed # afterwards. # @yieldparam stream [EventDispatch::EventsOutputStream] The created stream. # def open_stream(name: nil, **options) stream = EventDispatch::EventsOutputStream.new(events, name: name, **options) if block_given? begin yield stream ensure stream.close end else stream end end # there are options for how to handle multiple calls to these functions: # 1. first call always wins (never change reboot_info once set). # 2. last call always wins (happily change reboot_info whenever). # 3. raise an exception on the first conflict. # 4. disable reboot after this run if anyone ever calls :cancel. # 5. raise an exception on any second call. # 6. ? def request_reboot(reboot_info) Chef::Log.info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}" @reboot_info = reboot_info end def cancel_reboot Chef::Log.info "Changing reboot status from #{reboot_info.inspect} to {}" @reboot_info = {} end def reboot_requested? reboot_info.size > 0 end # # Create a child RunContext. # def create_child ChildRunContext.new(self) end # @api private attr_writer :resource_collection protected attr_reader :cookbook_compiler attr_reader :loaded_attributes_hash attr_reader :loaded_recipes_hash module Deprecated ### # These need to be settable so deploy can run a resource_collection # independent of any cookbooks via +recipe_eval+ def audits=(value) Chef.log_deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @audits = value end def immediate_notification_collection=(value) Chef.log_deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @immediate_notification_collection = value end def delayed_notification_collection=(value) Chef.log_deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.") @delayed_notification_collection = value end end prepend Deprecated # # A child run context. Delegates all root context calls to its parent. # # @api private # class ChildRunContext < RunContext extend Forwardable def_delegators :parent_run_context, *%w{ cancel_reboot config cookbook_collection cookbook_compiler definitions events has_cookbook_file_in_cookbook? has_template_in_cookbook? load loaded_attribute loaded_attributes loaded_attributes_hash loaded_fully_qualified_attribute? loaded_fully_qualified_recipe? loaded_recipe loaded_recipe? loaded_recipes loaded_recipes_hash node open_stream reboot_info reboot_info= reboot_requested? request_reboot resolve_attribute unreachable_cookbook? } def initialize(parent_run_context) @parent_run_context = parent_run_context # We don't call super, because we don't bother initializing stuff we're # going to delegate to the parent anyway. Just initialize things that # every instance needs. initialize_child_state end CHILD_STATE = %w{ audits audits= create_child add_delayed_action delayed_actions delayed_notification_collection delayed_notification_collection= delayed_notifications immediate_notification_collection immediate_notification_collection= immediate_notifications before_notification_collection before_notifications include_recipe initialize_child_state load_recipe load_recipe_file notifies_before notifies_immediately notifies_delayed parent_run_context resource_collection resource_collection= }.map { |x| x.to_sym } # Verify that we didn't miss any methods unless @__skip_method_checking # hook specifically for compat_resource missing_methods = superclass.instance_methods(false) - instance_methods(false) - CHILD_STATE if !missing_methods.empty? raise "ERROR: not all methods of RunContext accounted for in ChildRunContext! All methods must be marked as child methods with CHILD_STATE or delegated to the parent_run_context. Missing #{missing_methods.join(", ")}." end end end end end chef-12.14.60/lib/chef/run_context/000077500000000000000000000000001276456504500167135ustar00rootroot00000000000000chef-12.14.60/lib/chef/run_context/cookbook_compiler.rb000066400000000000000000000254611276456504500227500ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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 "set" require "chef/log" require "chef/recipe" require "chef/resource/lwrp_base" require "chef/provider/lwrp_base" require "chef/resource_definition_list" class Chef class RunContext # Implements the compile phase of the chef run by loading/eval-ing files # from cookbooks in the correct order and in the correct context. class CookbookCompiler attr_reader :events attr_reader :run_list_expansion def initialize(run_context, run_list_expansion, events) @run_context = run_context @events = events @run_list_expansion = run_list_expansion @cookbook_order = nil end # Chef::Node object for the current run. def node @run_context.node end # Chef::CookbookCollection object for the current run def cookbook_collection @run_context.cookbook_collection end # Resource Definitions from the compiled cookbooks. This is populated by # calling #compile_resource_definitions (which is called by #compile) def definitions @run_context.definitions end # Run the compile phase of the chef run. Loads files in the following order: # * Libraries # * Attributes # * LWRPs # * Resource Definitions # * Recipes # # Recipes are loaded in precisely the order specified by the expanded run_list. # # Other files are loaded in an order derived from the expanded run_list # and the dependencies declared by cookbooks' metadata. See # #cookbook_order for more information. def compile compile_libraries compile_attributes compile_lwrps compile_resource_definitions compile_recipes end # Extracts the cookbook names from the expanded run list, then iterates # over the list, recursing through dependencies to give a run_list # ordered array of cookbook names with no duplicates. Dependencies appear # before the cookbook(s) that depend on them. def cookbook_order @cookbook_order ||= begin ordered_cookbooks = [] seen_cookbooks = {} run_list_expansion.recipes.each do |recipe| cookbook = Chef::Recipe.parse_recipe_name(recipe).first add_cookbook_with_deps(ordered_cookbooks, seen_cookbooks, cookbook) end Chef::Log.debug("Cookbooks to compile: #{ordered_cookbooks.inspect}") ordered_cookbooks end end # Loads library files from cookbooks according to #cookbook_order. def compile_libraries @events.library_load_start(count_files_by_segment(:libraries)) cookbook_order.each do |cookbook| load_libraries_from_cookbook(cookbook) end @events.library_load_complete end # Loads attributes files from cookbooks. Attributes files are loaded # according to #cookbook_order; within a cookbook, +default.rb+ is loaded # first, then the remaining attributes files in lexical sort order. def compile_attributes @events.attribute_load_start(count_files_by_segment(:attributes)) cookbook_order.each do |cookbook| load_attributes_from_cookbook(cookbook) end @events.attribute_load_complete end # Loads LWRPs according to #cookbook_order. Providers are loaded before # resources on a cookbook-wise basis. def compile_lwrps lwrp_file_count = count_files_by_segment(:providers) + count_files_by_segment(:resources) @events.lwrp_load_start(lwrp_file_count) cookbook_order.each do |cookbook| load_lwrps_from_cookbook(cookbook) end @events.lwrp_load_complete end # Loads resource definitions according to #cookbook_order def compile_resource_definitions @events.definition_load_start(count_files_by_segment(:definitions)) cookbook_order.each do |cookbook| load_resource_definitions_from_cookbook(cookbook) end @events.definition_load_complete end # Iterates over the expanded run_list, loading each recipe in turn. def compile_recipes @events.recipe_load_start(run_list_expansion.recipes.size) run_list_expansion.recipes.each do |recipe| begin path = resolve_recipe(recipe) @run_context.load_recipe(recipe) @events.recipe_file_loaded(path, recipe) rescue Chef::Exceptions::RecipeNotFound => e @events.recipe_not_found(e) raise rescue Exception => e @events.recipe_file_load_failed(path, e, recipe) raise end end @events.recipe_load_complete end # Whether or not a cookbook is reachable from the set of cookbook given # by the run_list plus those cookbooks' dependencies. def unreachable_cookbook?(cookbook_name) !reachable_cookbooks.include?(cookbook_name) end # All cookbooks in the dependency graph, returned as a Set. def reachable_cookbooks @reachable_cookbooks ||= Set.new(cookbook_order) end private def load_attributes_from_cookbook(cookbook_name) list_of_attr_files = files_in_cookbook_by_segment(cookbook_name, :attributes).dup if default_file = list_of_attr_files.find { |path| File.basename(path) == "default.rb" } list_of_attr_files.delete(default_file) load_attribute_file(cookbook_name.to_s, default_file) end list_of_attr_files.each do |filename| load_attribute_file(cookbook_name.to_s, filename) end end def load_attribute_file(cookbook_name, filename) Chef::Log.debug("Node #{node.name} loading cookbook #{cookbook_name}'s attribute file #{filename}") attr_file_basename = ::File.basename(filename, ".rb") node.include_attribute("#{cookbook_name}::#{attr_file_basename}") rescue Exception => e @events.attribute_file_load_failed(filename, e) raise end def load_libraries_from_cookbook(cookbook_name) files_in_cookbook_by_segment(cookbook_name, :libraries).each do |filename| next unless File.extname(filename) == ".rb" begin Chef::Log.debug("Loading cookbook #{cookbook_name}'s library file: #{filename}") Kernel.load(filename) @events.library_file_loaded(filename) rescue Exception => e @events.library_file_load_failed(filename, e) raise end end end def load_lwrps_from_cookbook(cookbook_name) files_in_cookbook_by_segment(cookbook_name, :providers).each do |filename| load_lwrp_provider(cookbook_name, filename) end files_in_cookbook_by_segment(cookbook_name, :resources).each do |filename| load_lwrp_resource(cookbook_name, filename) end end def load_lwrp_provider(cookbook_name, filename) Chef::Log.debug("Loading cookbook #{cookbook_name}'s providers from #{filename}") Chef::Provider::LWRPBase.build_from_file(cookbook_name, filename, self) @events.lwrp_file_loaded(filename) rescue Exception => e @events.lwrp_file_load_failed(filename, e) raise end def load_lwrp_resource(cookbook_name, filename) Chef::Log.debug("Loading cookbook #{cookbook_name}'s resources from #{filename}") Chef::Resource::LWRPBase.build_from_file(cookbook_name, filename, self) @events.lwrp_file_loaded(filename) rescue Exception => e @events.lwrp_file_load_failed(filename, e) raise end def load_resource_definitions_from_cookbook(cookbook_name) files_in_cookbook_by_segment(cookbook_name, :definitions).each do |filename| begin Chef::Log.debug("Loading cookbook #{cookbook_name}'s definitions from #{filename}") resourcelist = Chef::ResourceDefinitionList.new resourcelist.from_file(filename) definitions.merge!(resourcelist.defines) do |key, oldval, newval| Chef::Log.info("Overriding duplicate definition #{key}, new definition found in #{filename}") newval end @events.definition_file_loaded(filename) rescue Exception => e @events.definition_file_load_failed(filename, e) raise end end end # Builds up the list of +ordered_cookbooks+ by first recursing through the # dependencies of +cookbook+, and then adding +cookbook+ to the list of # +ordered_cookbooks+. A cookbook is skipped if it appears in # +seen_cookbooks+, otherwise it is added to the set of +seen_cookbooks+ # before its dependencies are processed. def add_cookbook_with_deps(ordered_cookbooks, seen_cookbooks, cookbook) return false if seen_cookbooks.key?(cookbook) seen_cookbooks[cookbook] = true each_cookbook_dep(cookbook) do |dependency| add_cookbook_with_deps(ordered_cookbooks, seen_cookbooks, dependency) end ordered_cookbooks << cookbook end def count_files_by_segment(segment) cookbook_collection.inject(0) do |count, cookbook_by_name| count + cookbook_by_name[1].segment_filenames(segment).size end end # Lists the local paths to files in +cookbook+ of type +segment+ # (attribute, recipe, etc.), sorted lexically. def files_in_cookbook_by_segment(cookbook, segment) cookbook_collection[cookbook].segment_filenames(segment).sort end # Yields the name, as a symbol, of each cookbook depended on by # +cookbook_name+ in lexical sort order. def each_cookbook_dep(cookbook_name, &block) cookbook = cookbook_collection[cookbook_name] cookbook.metadata.dependencies.keys.sort.map { |x| x.to_sym }.each(&block) end # Given a +recipe_name+, finds the file associated with the recipe. def resolve_recipe(recipe_name) cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name) cookbook = cookbook_collection[cookbook_name] cookbook.recipe_filenames_by_name[recipe_short_name] end end end end chef-12.14.60/lib/chef/run_list.rb000066400000000000000000000111751276456504500165340ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Nuo Yan () # Author:: Tim Hinderliter () # Author:: Christopher Walters () # Author:: Seth Falcon () # 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/run_list/run_list_item" require "chef/run_list/run_list_expansion" require "chef/run_list/versioned_recipe_list" require "chef/mixin/params_validate" class Chef class RunList include Enumerable include Chef::Mixin::ParamsValidate # @run_list_items is an array of RunListItems that describe the items to # execute in order. RunListItems can load from and convert to the string # forms users set on roles and nodes. # For example: # @run_list_items = ['recipe[foo::bar]', 'role[webserver]'] # Thus, # self.role_names would return ['webserver'] # self.recipe_names would return ['foo::bar'] attr_reader :run_list_items # For backwards compat alias :run_list :run_list_items def initialize(*run_list_items) @run_list_items = run_list_items.map { |i| coerce_to_run_list_item(i) } end def role_names @run_list_items.inject([]) { |memo, run_list_item| memo << run_list_item.name if run_list_item.role?; memo } end alias :roles :role_names def recipe_names @run_list_items.inject([]) { |memo, run_list_item| memo << run_list_item.name if run_list_item.recipe?; memo } end alias :recipes :recipe_names # Add an item of the form "recipe[foo::bar]" or "role[webserver]"; # takes a String or a RunListItem def <<(run_list_item) run_list_item = coerce_to_run_list_item(run_list_item) @run_list_items << run_list_item unless @run_list_items.include?(run_list_item) self end alias :push :<< alias :add :<< def ==(other) if other.kind_of?(Chef::RunList) other.run_list_items == @run_list_items else return false unless other.respond_to?(:size) && (other.size == @run_list_items.size) other_run_list_items = other.dup other_run_list_items.map! { |item| coerce_to_run_list_item(item) } other_run_list_items == @run_list_items end end def to_s @run_list_items.join(", ") end def for_json to_a.map { |item| item.to_s } end def to_json(*a) Chef::JSONCompat.to_json(for_json, *a) end def empty? @run_list_items.length == 0 ? true : false end def [](pos) @run_list_items[pos] end def []=(pos, item) @run_list_items[pos] = parse_entry(item) end # FIXME: yard with @yield def each @run_list_items.each { |i| yield(i) } end # FIXME: yard with @yield def each_index @run_list_items.each_index { |i| yield(i) } end def include?(item) @run_list_items.include?(parse_entry(item)) end def reset!(*args) @run_list_items.clear args.flatten.each do |item| if item.kind_of?(Chef::RunList) item.each { |r| self << r } else self << item end end self end def remove(item) @run_list_items.delete_if { |i| i == item } self end alias :delete :remove # Expands this run_list: recursively expand roles into their included # recipes. # Returns a RunListExpansion object. def expand(environment, data_source = "server", expansion_opts = {}) expansion = expansion_for_data_source(environment, data_source, expansion_opts) expansion.expand expansion end # Converts a string run list entry to a RunListItem object. def parse_entry(entry) RunListItem.new(entry) end def coerce_to_run_list_item(item) item.kind_of?(RunListItem) ? item : parse_entry(item) end def expansion_for_data_source(environment, data_source, opts = {}) case data_source.to_s when "disk" RunListExpansionFromDisk.new(environment, @run_list_items) when "server" RunListExpansionFromAPI.new(environment, @run_list_items, opts[:rest]) end end end end chef-12.14.60/lib/chef/run_list/000077500000000000000000000000001276456504500162025ustar00rootroot00000000000000chef-12.14.60/lib/chef/run_list/run_list_expansion.rb000066400000000000000000000167551276456504500224700ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Tim Hinderliter () # Copyright:: Copyright 2010-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/mash" require "chef/mixin/deep_merge" require "chef/role" require "chef/server_api" require "chef/json_compat" class Chef class RunList # Abstract Base class for expanding a run list. Subclasses must handle # fetching roles from a data source by defining +fetch_role+ class RunListExpansion attr_reader :run_list_items # A VersionedRecipeList of recipes. Populated only after #expand # is called. attr_reader :recipes attr_reader :default_attrs attr_reader :override_attrs attr_reader :environment attr_reader :missing_roles_with_including_role # The data source passed to the constructor. Not used in this class. # In subclasses, this is a Chef::ServerAPI object pre-configured # to fetch roles from their correct location. attr_reader :source # Returns a Hash of the form "including_role" => "included_role_or_recipe". # This can be used to show the expanded run list (ordered) graph. # ==== Caveats # * Duplicate roles are not shown. attr_reader :run_list_trace # Like run list trace but instead of saving the entries as strings it saves their objects # The to_json method uses this list to construct json. attr_reader :better_run_list_trace attr_reader :all_missing_roles attr_reader :role_errors def initialize(environment, run_list_items, source = nil) @environment = environment @missing_roles_with_including_role = Array.new @run_list_items = run_list_items.dup @source = source @default_attrs = Mash.new @override_attrs = Mash.new @recipes = Chef::RunList::VersionedRecipeList.new @applied_roles = {} @run_list_trace = Hash.new { |h, key| h[key] = [] } @better_run_list_trace = Hash.new { |h, key| h[key] = [] } @all_missing_roles = {} @role_errors = {} end # Did we find any errors (expanding roles)? def errors? @missing_roles_with_including_role.length > 0 end alias :invalid? :errors? # Recurses over the run list items, expanding roles. After this, # +recipes+ will contain the fully expanded recipe list def expand # Sure do miss function arity when being recursive expand_run_list_items(@run_list_items) end # Fetches and inflates a role # === Returns # Chef::Role in most cases # false if the role has already been applied # nil if the role does not exist def inflate_role(role_name, included_by) return false if applied_role?(role_name) # Prevent infinite loops applied_role(role_name) fetch_role(role_name, included_by) end def apply_role_attributes(role) @default_attrs = Chef::Mixin::DeepMerge.merge(@default_attrs, role.default_attributes) @override_attrs = Chef::Mixin::DeepMerge.merge(@override_attrs, role.override_attributes) end def applied_role?(role_name) @applied_roles.has_key?(role_name) end # Returns an array of role names that were expanded; this # includes any roles that were in the original, pre-expansion # run_list as well as roles processed during # expansion. Populated only after #expand is called. def roles @applied_roles.keys end # In subclasses, this method will fetch the role from the data source. def fetch_role(name, included_by) raise NotImplementedError end # When a role is not found, an error message is logged, but no # exception is raised. We do add an entry in the errors collection. # === Returns # nil def role_not_found(name, included_by) Chef::Log.error("Role #{name} (included by '#{included_by}') is in the runlist but does not exist. Skipping expand.") @missing_roles_with_including_role << [name, included_by] @all_missing_roles[name] = true nil end def errors @missing_roles_with_including_role.map { |item| item.first } end def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def to_hash seen_items = { :recipe => {}, :role => {} } { :id => @environment, :run_list => convert_run_list_trace("top level", seen_items) } end private # these methods modifies internal state based on arguments, so hide it. def applied_role(role_name) @applied_roles[role_name] = true end def expand_run_list_items(items, included_by = "top level") if entry = items.shift @run_list_trace[included_by.to_s] << entry.to_s @better_run_list_trace[included_by.to_s] << entry case entry.type when :recipe recipes.add_recipe(entry.name, entry.version) when :role if role = inflate_role(entry.name, included_by) expand_run_list_items(role.run_list_for(@environment).run_list_items, role) apply_role_attributes(role) end end expand_run_list_items(items, included_by) end end # Recursive helper to decode the non-nested hash form back into a tree def convert_run_list_trace(base, seen_items) @better_run_list_trace[base].map do |item| skipped = seen_items[item.type][item.name] seen_items[item.type][item.name] = true case item.type when :recipe { :type => "recipe", :name => item.name, :version => item.version, :skipped => !!skipped } when :role error = @role_errors[item.name] missing = @all_missing_roles[item.name] { :type => :role, :name => item.name, :children => (missing || error || skipped) ? [] : convert_run_list_trace(item.to_s, seen_items), :missing => missing, :error => error, :skipped => skipped } end end end end # Expand a run list from disk. Suitable for chef-solo class RunListExpansionFromDisk < RunListExpansion def fetch_role(name, included_by) Chef::Role.from_disk(name) rescue Chef::Exceptions::RoleNotFound role_not_found(name, included_by) end end # Expand a run list from the chef-server API. class RunListExpansionFromAPI < RunListExpansion def rest @rest ||= (source || Chef::ServerAPI.new(Chef::Config[:chef_server_url])) end def fetch_role(name, included_by) Chef::Role.from_hash(rest.get("roles/#{name}")) rescue Net::HTTPServerException => e if e.message == '404 "Not Found"' role_not_found(name, included_by) else raise end rescue Exception => e @role_errors[name] = e.to_s raise end end end end chef-12.14.60/lib/chef/run_list/run_list_item.rb000066400000000000000000000064331276456504500214120ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 RunList class RunListItem QUALIFIED_RECIPE = %r{^recipe\[([^\]@]+)(@([0-9]+(\.[0-9]+){1,2}))?\]$} QUALIFIED_ROLE = %r{^role\[([^\]]+)\]$} VERSIONED_UNQUALIFIED_RECIPE = %r{^([^@]+)(@([0-9]+(\.[0-9]+){1,2}))$} FALSE_FRIEND = %r{[\[\]]} attr_reader :name, :type, :version def initialize(item) @version = nil case item when Hash assert_hash_is_valid_run_list_item!(item) @type = (item["type"] || item[:type]).to_sym @name = item["name"] || item[:name] if item.has_key?("version") || item.has_key?(:version) @version = item["version"] || item[:version] end when String if match = QUALIFIED_RECIPE.match(item) # recipe[recipe_name] # recipe[recipe_name@1.0.0] @type = :recipe @name = match[1] @version = match[3] if match[3] elsif match = QUALIFIED_ROLE.match(item) # role[role_name] @type = :role @name = match[1] elsif match = VERSIONED_UNQUALIFIED_RECIPE.match(item) # recipe_name@1.0.0 @type = :recipe @name = match[1] @version = match[3] if match[3] elsif match = FALSE_FRIEND.match(item) # Recipe[recipe_name] # roles[role_name] name = match[1] raise ArgumentError, "Unable to create #{self.class} from #{item.class}:#{item.inspect}: must be recipe[#{name}] or role[#{name}]" else # recipe_name @type = :recipe @name = item end else raise ArgumentError, "Unable to create #{self.class} from #{item.class}:#{item.inspect}: must be a Hash or String" end end def to_s "#{@type}[#{@name}#{@version ? "@#{@version}" : ""}]" end def role? @type == :role end def recipe? @type == :recipe end def ==(other) if other.kind_of?(String) self.to_s == other.to_s else other.respond_to?(:type) && other.respond_to?(:name) && other.respond_to?(:version) && other.type == @type && other.name == @name && other.version == @version end end def assert_hash_is_valid_run_list_item!(item) unless (item.key?("type") || item.key?(:type)) && (item.key?("name") || item.key?(:name)) raise ArgumentError, "Initializing a #{self.class} from a hash requires that it have a 'type' and 'name' key" end end end end end chef-12.14.60/lib/chef/run_list/versioned_recipe_list.rb000066400000000000000000000067521276456504500231210ustar00rootroot00000000000000# # Author:: Stephen Delano () # Author:: Seth Falcon () # Copyright:: Copyright 2010-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/version_class" require "chef/version_constraint" # Why does this class exist? # Why did we not just modify RunList/RunListItem? class Chef class RunList class VersionedRecipeList < Array def initialize super @versions = Hash.new end def add_recipe(name, version = nil) if version && @versions.has_key?(name) unless Chef::Version.new(@versions[name]) == Chef::Version.new(version) raise Chef::Exceptions::CookbookVersionConflict, "Run list requires #{name} at versions #{@versions[name]} and #{version}" end end @versions[name] = version if version self << name unless self.include?(name) end def with_versions self.map { |recipe_name| { :name => recipe_name, :version => @versions[recipe_name] } } end # Return an Array of Hashes, each of the form: # {:name => RECIPE_NAME, :version_constraint => Chef::VersionConstraint } def with_version_constraints self.map do |recipe_name| constraint = Chef::VersionConstraint.new(@versions[recipe_name]) { :name => recipe_name, :version_constraint => constraint } end end # Return an Array of Strings, each of the form: # "NAME@VERSION" def with_version_constraints_strings self.map do |recipe_name| if @versions[recipe_name] "#{recipe_name}@#{@versions[recipe_name]}" else recipe_name end end end # Get an array of strings of the fully-qualified recipe names (with ::default appended) and # with the versions in "NAME@VERSION" format. # # @return [Array] Array of strings with fully-qualified recipe names def with_fully_qualified_names_and_version_constraints self.map do |recipe_name| qualified_recipe = if recipe_name.include?("::") recipe_name else "#{recipe_name}::default" end version = @versions[recipe_name] qualified_recipe = "#{qualified_recipe}@#{version}" if version qualified_recipe end end # Get an array of strings of both fully-qualified and unexpanded recipe names # in response to chef/chef#3767 # Chef-13 will revert to the behaviour of just including the fully-qualified name # # @return [Array] Array of strings with fully-qualified and unexpanded recipe names def with_duplicate_names self.map do |recipe_name| if recipe_name.include?("::") recipe_name else [recipe_name, "#{recipe_name}::default"] end end.flatten end end end end chef-12.14.60/lib/chef/run_lock.rb000066400000000000000000000127741276456504500165170ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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 "chef/mixin/create_path" require "fcntl" if Chef::Platform.windows? require "chef/win32/mutex" end require "chef/config" require "chef/exceptions" require "timeout" class Chef # == Chef::RunLock # Provides an interface for acquiring and releasing a system-wide exclusive # lock. # # Used by Chef::Client to ensure only one instance of chef-client (or solo) # is modifying the system at a time. class RunLock include Chef::Mixin::CreatePath attr_reader :runlock attr_reader :mutex attr_reader :runlock_file # Create a new instance of RunLock # === Arguments # * :lockfile::: the full path to the lockfile. def initialize(lockfile) @runlock_file = lockfile @runlock = nil @mutex = nil @runpid = nil end # Acquire the system-wide lock. Will block indefinitely if another process # already has the lock and Chef::Config[:run_lock_timeout] is # not set. Otherwise will block for Chef::Config[:run_lock_timeout] # seconds and exit if the lock is not acquired. # # Each call to acquire should have a corresponding call to #release. # # The implementation is based on File#flock (see also: flock(2)). # # Either acquire() or test() methods should be called in order to # get the ownership of run_lock. def acquire if timeout_given? begin Timeout.timeout(time_to_wait) do unless test if time_to_wait > 0.0 wait else exit_from_timeout end end end rescue Timeout::Error exit_from_timeout end else wait unless test end end # # Tests and if successful acquires the system-wide lock. # Returns true if the lock is acquired, false otherwise. # # Either acquire() or test() methods should be called in order to # get the ownership of run_lock. def test create_lock acquire_lock end # # Waits until acquiring the system-wide lock. # def wait Chef::Log.warn("Chef client #{runpid} is running, will wait for it to finish and then run.") if Chef::Platform.windows? mutex.wait else runlock.flock(File::LOCK_EX) end end def save_pid runlock.truncate(0) runlock.rewind # truncate doesn't reset position to 0. runlock.write(Process.pid.to_s) # flush the file fsync flushes the system buffers # in addition to ruby buffers runlock.fsync end # Release the system-wide lock. def release if runlock if Chef::Platform.windows? mutex.release else runlock.flock(File::LOCK_UN) end runlock.close # Don't unlink the pid file, if another chef-client was waiting, it # won't be recreated. Better to leave a "dead" pid file than not have # it available if you need to break the lock. reset end end # @api private solely for race condition tests def create_lock # ensure the runlock_file path exists create_path(File.dirname(runlock_file)) @runlock = File.open(runlock_file, "a+") end # @api private solely for race condition tests def acquire_lock if Chef::Platform.windows? acquire_win32_mutex else # If we support FD_CLOEXEC, then use it. # NB: ruby-2.0.0-p195 sets FD_CLOEXEC by default, but not # ruby-1.8.7/1.9.3 if Fcntl.const_defined?("F_SETFD") && Fcntl.const_defined?("FD_CLOEXEC") runlock.fcntl(Fcntl::F_SETFD, runlock.fcntl(Fcntl::F_GETFD, 0) | Fcntl::FD_CLOEXEC) end # Flock will return 0 if it can acquire the lock otherwise it # will return false if runlock.flock(File::LOCK_NB | File::LOCK_EX) == 0 true else false end end end private def reset @runlock = nil @mutex = nil @runpid = nil end # Since flock mechanism doesn't exist on windows we are using # platform Mutex. # We are creating a "Global" mutex here so that non-admin # users can not DoS chef-client by creating the same named # mutex we are using locally. # Mutex name is case-sensitive contrary to other things in # windows. "\" is the only invalid character. def acquire_win32_mutex @mutex = Chef::ReservedNames::Win32::Mutex.new("Global\\#{runlock_file.gsub(/[\\]/, "/").downcase}") mutex.test end def runpid @runpid ||= runlock.read.strip end def timeout_given? !time_to_wait.nil? end def time_to_wait Chef::Config[:run_lock_timeout] end def exit_from_timeout rp = runpid release # Just to be on the safe side... raise Chef::Exceptions::RunLockTimeout.new(time_to_wait, rp) end end end chef-12.14.60/lib/chef/run_status.rb000066400000000000000000000062501276456504500171020ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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. # # == Chef::RunStatus # Tracks various aspects of a Chef run, including the Node and RunContext, # start and end time, and any Exception that stops the run. RunStatus objects # are passed to any notification or exception handlers at the completion of a # Chef run. class Chef::RunStatus attr_reader :events attr_reader :run_context attr_writer :run_context attr_reader :start_time attr_reader :end_time attr_reader :exception attr_writer :exception attr_accessor :run_id attr_accessor :node def initialize(node, events) @node = node @events = events end # sets +start_time+ to the current time. def start_clock @start_time = Time.now end # sets +end_time+ to the current time def stop_clock @end_time = Time.now end # The elapsed time between +start_time+ and +end_time+. Returns +nil+ if # either value is not set. def elapsed_time if @start_time && @end_time @end_time - @start_time else nil end end # The list of all resources in the current run context's +resource_collection+ def all_resources @run_context && @run_context.resource_collection.all_resources end # The list of all resources in the current run context's +resource_collection+ # that are marked as updated def updated_resources @run_context && @run_context.resource_collection.select { |r| r.updated } end # The backtrace from +exception+, if any def backtrace @exception && @exception.backtrace end # Did the Chef run fail? def failed? !success? end # Did the chef run succeed? returns +true+ if no exception has been set. def success? @exception.nil? end # A Hash representation of the RunStatus, with the following (Symbol) keys: # * :node # * :success # * :start_time # * :end_time # * :elapsed_time # * :all_resources # * :updated_resources # * :exception # * :backtrace def to_hash # use a flat hash here so we can't errors from intermediate values being nil { :node => node, :success => success?, :start_time => start_time, :end_time => end_time, :elapsed_time => elapsed_time, :all_resources => all_resources, :updated_resources => updated_resources, :exception => formatted_exception, :backtrace => backtrace, :run_id => run_id } end # Returns a string of the format "ExceptionClass: message" or +nil+ if no # +exception+ is set. def formatted_exception @exception && "#{@exception.class.name}: #{@exception.message}" end end chef-12.14.60/lib/chef/runner.rb000066400000000000000000000116511276456504500162050ustar00rootroot00000000000000#-- # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: Tim Hinderliter () # 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/exceptions" require "chef/mixin/params_validate" require "chef/node" require "chef/resource_collection" class Chef # == Chef::Runner # This class is responsible for executing the steps in a Chef run. class Runner attr_reader :run_context include Chef::Mixin::ParamsValidate def initialize(run_context) @run_context = run_context end def delayed_actions @run_context.delayed_actions end def events @run_context.events end # Determine the appropriate provider for the given resource, then # execute it. def run_action(resource, action, notification_type = nil, notifying_resource = nil) # If there are any before notifications, why-run the resource # and notify anyone who needs notifying before_notifications = run_context.before_notifications(resource) || [] unless before_notifications.empty? forced_why_run do Chef::Log.info("#{resource} running why-run #{action} action to support before action") resource.run_action(action, notification_type, notifying_resource) end if resource.updated_by_last_action? before_notifications.each do |notification| Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (before)") run_action(notification.resource, notification.action, :before, resource) end resource.updated_by_last_action(false) end end # Actually run the action for realsies resource.run_action(action, notification_type, notifying_resource) # Execute any immediate and queue up any delayed notifications # associated with the resource, but only if it was updated *this time* # we ran an action on it. if resource.updated_by_last_action? run_context.immediate_notifications(resource).each do |notification| Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (immediate)") run_action(notification.resource, notification.action, :immediate, resource) end run_context.delayed_notifications(resource).each do |notification| # send the notification to the run_context of the receiving resource notification.resource.run_context.add_delayed_action(notification) end end end # Iterates over the +resource_collection+ in the +run_context+ calling # +run_action+ for each resource in turn. def converge # Resolve all lazy/forward references in notifications run_context.resource_collection.each do |resource| resource.resolve_notification_references end # Execute each resource. run_context.resource_collection.execute_each_resource do |resource| Array(resource.action).each { |action| run_action(resource, action) } end rescue Exception => e Chef::Log.info "Running queued delayed notifications before re-raising exception" run_delayed_notifications(e) else run_delayed_notifications(nil) true end private # Run all our :delayed actions def run_delayed_notifications(error = nil) collected_failures = Exceptions::MultipleFailures.new collected_failures.client_run_failure(error) unless error.nil? delayed_actions.each do |notification| result = run_delayed_notification(notification) if result.kind_of?(Exception) collected_failures.notification_failure(result) end end collected_failures.raise! end def run_delayed_notification(notification) Chef::Log.info( "#{notification.notifying_resource} sending #{notification.action}"\ " action to #{notification.resource} (delayed)") # Struct of resource/action to call run_action(notification.resource, notification.action, :delayed) true rescue Exception => e e end # helper to run a block of code with why_run forced to true and then restore it correctly def forced_why_run saved = Chef::Config[:why_run] Chef::Config[:why_run] = true yield ensure Chef::Config[:why_run] = saved end end end chef-12.14.60/lib/chef/sandbox.rb000066400000000000000000000016241276456504500163310ustar00rootroot00000000000000class Chef class Sandbox # I DO NOTHING!! # So, the reason we have a completely empty class here is so that # Chef 11 clients do not choke when interacting with Chef 10 # servers. The original Chef::Sandbox class (that actually did # things) has been removed since its functionality is no longer # needed for Chef 11. However, since we still use the JSON gem # and make use of its "auto-inflation" of classes (driven by the # contents of the 'json_class' key in all of our JSON), any # sandbox responses from a Chef 10 server to a Chef 11 client # would cause knife to crash. The JSON gem would attempt to # auto-inflate based on a "json_class": "Chef::Sandbox" hash # entry, but would not be able to find a Chef::Sandbox class! # # This is a workaround until such time as we can completely remove # the reliance on the "json_class" field. end end chef-12.14.60/lib/chef/scan_access_control.rb000066400000000000000000000107051276456504500207000ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # 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 # == ScanAccessControl # Reads Access Control Settings on a file and writes them out to a resource # (should be the current_resource), attempting to match the style used by the # new resource, that is, if users are specified with usernames in # new_resource, then the uids from stat will be looked up and usernames will # be added to current_resource. # # === Why? # FileAccessControl objects may operate on a temporary file, in which case we # won't know if the access control settings changed (ex: rendering a template # with both a change in content and ownership). For auditing purposes, we # need to record the current state of a file system entity. #-- # Not yet sure if this is the optimal way to solve the problem. But it's # progress towards the end goal. # # TODO: figure out if all this works with OS X's negative uids # TODO: windows class ScanAccessControl attr_reader :new_resource attr_reader :current_resource def initialize(new_resource, current_resource) @new_resource, @current_resource = new_resource, current_resource end # Modifies @current_resource, setting the current access control state. def set_all! if ::File.exist?(new_resource.path) set_owner set_group set_mode else # leave the values as nil. end end # Set the owner attribute of +current_resource+ to whatever the current # state is. Attempts to match the format given in new_resource: if the # new_resource specifies the owner as a string, the username for the uid # will be looked up and owner will be set to the username, and vice versa. def set_owner @current_resource.owner(current_owner) end def current_owner case new_resource.owner when String, nil lookup_uid when Integer stat.uid else Chef::Log.error("The `owner` parameter of the #@new_resource resource is set to an invalid value (#{new_resource.owner.inspect})") raise ArgumentError, "cannot resolve #{new_resource.owner.inspect} to uid, owner must be a string or integer" end end def lookup_uid unless (pwent = Etc.getpwuid(stat.uid)).nil? pwent.name else stat.uid end rescue ArgumentError stat.uid end # Set the group attribute of +current_resource+ to whatever the current state is. def set_group @current_resource.group(current_group) end def current_group case new_resource.group when String, nil lookup_gid when Integer stat.gid else Chef::Log.error("The `group` parameter of the #@new_resource resource is set to an invalid value (#{new_resource.owner.inspect})") raise ArgumentError, "cannot resolve #{new_resource.group.inspect} to gid, group must be a string or integer" end end def lookup_gid unless (pwent = Etc.getgrgid(stat.gid)).nil? pwent.name else stat.gid end rescue ArgumentError stat.gid end def set_mode @current_resource.mode(current_mode) end def current_mode case new_resource.mode when String, Integer, nil "0#{(stat.mode & 07777).to_s(8)}" else Chef::Log.error("The `mode` parameter of the #@new_resource resource is set to an invalid value (#{new_resource.mode.inspect})") raise ArgumentError, "Invalid value #{new_resource.mode.inspect} for `mode` on resource #@new_resource" end end def stat @stat ||= if @new_resource.instance_of?(Chef::Resource::Link) ::File.lstat(@new_resource.path) else realpath = ::File.realpath(@new_resource.path) ::File.stat(realpath) end end end end chef-12.14.60/lib/chef/search/000077500000000000000000000000001276456504500156105ustar00rootroot00000000000000chef-12.14.60/lib/chef/search/query.rb000066400000000000000000000141321276456504500173030ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/exceptions" require "chef/server_api" require "uri" require "addressable/uri" class Chef class Search class Query attr_accessor :rest attr_reader :config def initialize(url = nil, config: Chef::Config) @config = config @url = url end def rest @rest ||= Chef::ServerAPI.new(@url || @config[:chef_server_url]) end # Backwards compatability for cookbooks. # This can be removed in Chef > 12. def partial_search(type, query = "*:*", *args, &block) Chef::Log.warn(<<-WARNDEP) DEPRECATED: The 'partial_search' API is deprecated and will be removed in future releases. Please use 'search' with a :filter_result argument to get partial search data. WARNDEP if !args.empty? && args.first.is_a?(Hash) # partial_search uses :keys instead of :filter_result for # result filtering. args_h = args.first.dup args_h[:filter_result] = args_h[:keys] args_h.delete(:keys) search(type, query, args_h, &block) else search(type, query, *args, &block) end end # # New search input, designed to be backwards compatible with the old method signature # 'type' and 'query' are the same as before, args now will accept either a Hash of # search arguments with symbols as the keys (ie :sort, :start, :rows) and a :filter_result # option. # # :filter_result should be in the format of another Hash with the structure of: # { # :returned_name1 => ["path", "to", "variable"], # :returned_name2 => ["shorter", "path"] # } # a real world example might be something like: # { # :ip_address => ["ipaddress"], # :ruby_version => ["languages", "ruby", "version"] # } # this will bring back 2 variables 'ip_address' and 'ruby_version' with whatever value was found # an example of the returned json may be: # {"ip_address":"127.0.0.1", "ruby_version": "1.9.3"} # def search(type, query = "*:*", *args, &block) validate_type(type) args_h = hashify_args(*args) response = call_rest_service(type, query: query, **args_h) if block response["rows"].each { |row| yield(row) if row } # # args_h[:rows] and args_h[:start] are the page size and # start position requested of the search index backing the # search API. # # The response may contain fewer rows than arg_h[:rows] if # the page of index results included deleted nodes which # have been filtered from the returned data. In this case, # we still want to start the next page at start + # args_h[:rows] to avoid asking the search backend for # overlapping pages (which could result in duplicates). # next_start = response["start"] + (args_h[:rows] || response["rows"].length) unless next_start >= response["total"] args_h[:start] = next_start search(type, query, args_h, &block) end true else [ response["rows"], response["start"], response["total"] ] end end private def validate_type(t) unless t.kind_of?(String) || t.kind_of?(Symbol) msg = "Invalid search object type #{t.inspect} (#{t.class}), must be a String or Symbol." + "Usage: search(:node, QUERY[, OPTIONAL_ARGS])" + " `knife search environment QUERY (options)`" raise Chef::Exceptions::InvalidSearchQuery, msg end end def hashify_args(*args) return Hash.new if args.empty? return args.first if args.first.is_a?(Hash) args_h = Hash.new args_h[:sort] = args[0] if args[0] args_h[:start] = args[1] if args[1] args_h[:rows] = args[2] args_h[:filter_result] = args[3] args_h end QUERY_PARAM_VALUE = Addressable::URI::CharacterClasses::QUERY + "\\&\\;" def escape_value(s) s && Addressable::URI.encode_component(s.to_s, QUERY_PARAM_VALUE) end def create_query_string(type, query, rows, start, sort) qstr = "search/#{type}?q=#{escape_value(query)}" qstr += "&sort=#{escape_value(sort)}" if sort qstr += "&start=#{escape_value(start)}" if start qstr += "&rows=#{escape_value(rows)}" if rows qstr end def call_rest_service(type, query: "*:*", rows: nil, start: 0, sort: "X_CHEF_id_CHEF_X asc", filter_result: nil) query_string = create_query_string(type, query, rows, start, sort) if filter_result response = rest.post(query_string, filter_result) # response returns rows in the format of # { "url" => url_to_node, "data" => filter_result_hash } response["rows"].map! { |row| row["data"] } else response = rest.get(query_string) response["rows"].map! do |row| case type.to_s when "node" Chef::Node.from_hash(row) when "role" Chef::Role.from_hash(row) when "environment" Chef::Environment.from_hash(row) when "client" Chef::ApiClient.from_hash(row) else Chef::DataBagItem.from_hash(row) end end end response end end end end chef-12.14.60/lib/chef/server_api.rb000066400000000000000000000054601276456504500170340ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/http" require "chef/http/authenticator" require "chef/http/cookie_manager" require "chef/http/decompressor" require "chef/http/json_input" require "chef/http/json_output" require "chef/http/remote_request_id" require "chef/http/validate_content_length" class Chef class ServerAPI < Chef::HTTP def initialize(url = Chef::Config[:chef_server_url], options = {}) options[:client_name] ||= Chef::Config[:node_name] options[:signing_key_filename] ||= Chef::Config[:client_key] options[:signing_key_filename] = nil if chef_zero_uri?(url) options[:inflate_json_class] = false super(url, options) end use Chef::HTTP::JSONInput use Chef::HTTP::JSONOutput use Chef::HTTP::CookieManager use Chef::HTTP::Decompressor use Chef::HTTP::Authenticator use Chef::HTTP::RemoteRequestID # ValidateContentLength should come after Decompressor # because the order of middlewares is reversed when handling # responses. use Chef::HTTP::ValidateContentLength # for back compat with Chef::REST, expose `_rest` as an alias to `` alias :get_rest :get alias :delete_rest :delete alias :post_rest :post alias :put_rest :put # Makes an HTTP request to +path+ with the given +method+, +headers+, and # +data+ (if applicable). Does not apply any middleware, besides that # needed for Authentication. def raw_request(method, path, headers = {}, data = false) url = create_url(path) method, url, headers, data = Chef::HTTP::Authenticator.new(options).handle_request(method, url, headers, data) method, url, headers, data = Chef::HTTP::RemoteRequestID.new(options).handle_request(method, url, headers, data) response, rest_request, return_value = send_http_request(method, url, headers, data) response.error! unless success_response?(response) return_value rescue Exception => exception log_failed_request(response, return_value) unless response.nil? if exception.respond_to?(:chef_rest_request=) exception.chef_rest_request = rest_request end raise end end end require "chef/config" chef-12.14.60/lib/chef/shell.rb000066400000000000000000000226031276456504500160020ustar00rootroot00000000000000# Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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 "singleton" require "pp" require "etc" require "mixlib/cli" require "chef" require "chef/version" require "chef/client" require "chef/config" require "chef/config_fetcher" require "chef/shell/shell_session" require "chef/shell/ext" require "chef/json_compat" require "chef/util/path_helper" # = Shell # Shell is Chef in an IRB session. Shell can interact with a Chef server via the # REST API, and run and debug recipes interactively. module Shell LEADERS = Hash.new("") LEADERS[Chef::Recipe] = ":recipe" LEADERS[Chef::Node] = ":attributes" class << self attr_accessor :client_type attr_accessor :options attr_accessor :env attr_writer :editor end # Start the irb REPL with chef-shell's customizations def self.start setup_logger # FUGLY HACK: irb gives us no other choice. irb_help = [:help, :irb_help, IRB::ExtendCommandBundle::NO_OVERRIDE] IRB::ExtendCommandBundle.instance_variable_get(:@ALIASES).delete(irb_help) parse_opts Chef::Config[:shell_config] = options.config # HACK: this duplicates the functions of IRB.start, but we have to do it # to get access to the main object before irb starts. ::IRB.setup(nil) irb = IRB::Irb.new init(irb.context.main) irb_conf[:IRB_RC].call(irb.context) if irb_conf[:IRB_RC] irb_conf[:MAIN_CONTEXT] = irb.context trap("SIGINT") do irb.signal_handle end catch(:IRB_EXIT) do irb.eval_input end end def self.setup_logger Chef::Config[:log_level] ||= :warn # If log_level is auto, change it to warn Chef::Config[:log_level] = :warn if Chef::Config[:log_level] == :auto Chef::Log.init(STDERR) Mixlib::Authentication::Log.logger = Ohai::Log.logger = Chef::Log.logger Chef::Log.level = Chef::Config[:log_level] || :warn end # Shell assumes it's running whenever it is defined def self.running? true end # Set the irb_conf object to something other than IRB.conf # usful for testing. def self.irb_conf=(conf_hash) @irb_conf = conf_hash end def self.irb_conf @irb_conf || IRB.conf end def self.configure_irb irb_conf[:HISTORY_FILE] = Chef::Util::PathHelper.home(".chef", "chef_shell_history") irb_conf[:SAVE_HISTORY] = 1000 irb_conf[:IRB_RC] = lambda do |conf| m = conf.main conf.prompt_c = "chef#{leader(m)} > " conf.return_format = " => %s \n" conf.prompt_i = "chef#{leader(m)} (#{Chef::VERSION})> " conf.prompt_n = "chef#{leader(m)} ?> " conf.prompt_s = "chef#{leader(m)}%l> " conf.use_tracer = false end end def self.leader(main_object) env_string = Shell.env ? " (#{Shell.env})" : "" LEADERS[main_object.class] + env_string end def self.session unless client_type.instance.node_built? puts "Session type: #{client_type.session_type}" client_type.instance.reset! end client_type.instance end def self.init(main) parse_json configure_irb session # trigger ohai run + session load session.node.consume_attributes(@json_attribs) Extensions.extend_context_object(main) main.version puts puts "run `help' for help, `exit' or ^D to quit." puts puts "Ohai2u#{greeting}!" end def self.greeting " #{Etc.getlogin}@#{Shell.session.node["fqdn"]}" rescue NameError, ArgumentError "" end def self.parse_json if Chef::Config[:json_attribs] config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs]) @json_attribs = config_fetcher.fetch_json end end def self.fatal!(message, exit_status) Chef::Log.fatal(message) exit exit_status end def self.client_type type = Shell::StandAloneSession type = Shell::SoloSession if Chef::Config[:shell_solo] type = Shell::ClientSession if Chef::Config[:client] type = Shell::DoppelGangerSession if Chef::Config[:doppelganger] type end def self.parse_opts @options = Options.new @options.parse_opts end def self.editor @editor || Chef::Config[:editor] || ENV["EDITOR"] end class Options include Mixlib::CLI def self.footer(text = nil) @footer = text if text @footer end banner("chef-shell #{Chef::VERSION}\n\nUsage: chef-shell [NAMED_CONF] (OPTIONS)") footer(<<-FOOTER) When no CONFIG is specified, chef-shell attempts to load a default configuration file: * If a NAMED_CONF is given, chef-shell will load ~/.chef/NAMED_CONF/chef_shell.rb * If no NAMED_CONF is given chef-shell will load ~/.chef/chef_shell.rb if it exists * chef-shell falls back to loading /etc/chef/client.rb or /etc/chef/solo.rb if -z or -s options are given and no chef_shell.rb can be found. FOOTER option :config_file, :short => "-c CONFIG", :long => "--config CONFIG", :description => "The configuration file to use" option :help, :short => "-h", :long => "--help", :description => "Show this message", :on => :tail, :boolean => true, :proc => proc { print_help } option :log_level, :short => "-l LOG_LEVEL", :long => "--log-level LOG_LEVEL", :description => "Set the logging level", :proc => proc { |level| Chef::Config.log_level = level.to_sym; Shell.setup_logger } option :standalone, :short => "-a", :long => "--standalone", :description => "standalone session", :default => true, :boolean => true option :shell_solo, :short => "-s", :long => "--solo", :description => "chef-solo session", :boolean => true, :proc => proc { Chef::Config[:solo] = true } option :client, :short => "-z", :long => "--client", :description => "chef-client session", :boolean => true option :json_attribs, :short => "-j JSON_ATTRIBS", :long => "--json-attributes JSON_ATTRIBS", :description => "Load attributes from a JSON file or URL", :proc => nil option :chef_server_url, :short => "-S CHEFSERVERURL", :long => "--server CHEFSERVERURL", :description => "The chef server URL", :proc => nil option :version, :short => "-v", :long => "--version", :description => "Show chef version", :boolean => true, :proc => lambda { |v| puts "Chef: #{::Chef::VERSION}" }, :exit => 0 option :override_runlist, :short => "-o RunlistItem,RunlistItem...", :long => "--override-runlist RunlistItem,RunlistItem...", :description => "Replace current run list with specified items", :proc => lambda { |items| items.split(",").map { |item| Chef::RunList::RunListItem.new(item) } } option :skip_cookbook_sync, :long => "--[no-]skip-cookbook-sync", :description => "Use cached cookbooks without overwriting local differences from the server", :boolean => false def self.print_help instance = new instance.parse_options([]) puts instance.opt_parser puts puts footer puts exit 1 end def self.setup! self.new.parse_opts end def parse_opts remainder = parse_options environment = remainder.first # We have to nuke ARGV to make sure irb's option parser never sees it. # otherwise, IRB complains about command line switches it doesn't recognize. ARGV.clear config[:config_file] = config_file_for_shell_mode(environment) config_msg = config[:config_file] || "none (standalone session)" puts "loading configuration: #{config_msg}" Chef::Config.from_file(config[:config_file]) if !config[:config_file].nil? && File.exists?(config[:config_file]) && File.readable?(config[:config_file]) Chef::Config.merge!(config) end private def config_file_for_shell_mode(environment) dot_chef_dir = Chef::Util::PathHelper.home(".chef") if config[:config_file] config[:config_file] elsif environment Shell.env = environment config_file_to_try = ::File.join(dot_chef_dir, environment, "chef_shell.rb") unless ::File.exist?(config_file_to_try) puts "could not find chef-shell config for environment #{environment} at #{config_file_to_try}" exit 1 end config_file_to_try elsif dot_chef_dir && ::File.exist?(File.join(dot_chef_dir, "chef_shell.rb")) File.join(dot_chef_dir, "chef_shell.rb") elsif config[:solo] Chef::Config.platform_specific_path("/etc/chef/solo.rb") elsif config[:client] Chef::Config.platform_specific_path("/etc/chef/client.rb") else nil end end end end chef-12.14.60/lib/chef/shell/000077500000000000000000000000001276456504500154525ustar00rootroot00000000000000chef-12.14.60/lib/chef/shell/ext.rb000066400000000000000000000422751276456504500166110ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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 "tempfile" require "chef/recipe" require "fileutils" require "chef/dsl/platform_introspection" require "chef/version" require "chef/shell/shell_session" require "chef/shell/model_wrapper" require "chef/server_api" require "chef/json_compat" module Shell module Extensions Help = Struct.new(:cmd, :desc, :explanation) # Extensions to be included in every 'main' object in chef-shell. # These objects are extended with this module. module ObjectCoreExtensions def ensure_session_select_defined # irb breaks if you prematurely define IRB::JobMangager # so these methods need to be defined at the latest possible time. unless jobs.respond_to?(:select_session_by_context) def jobs.select_session_by_context(&block) # rubocop:disable Lint/NestedMethodDefinition @jobs.select { |job| block.call(job[1].context.main) } end end unless jobs.respond_to?(:session_select) def jobs.select_shell_session(target_context) # rubocop:disable Lint/NestedMethodDefinition session = if target_context.kind_of?(Class) select_session_by_context { |main| main.kind_of?(target_context) } else select_session_by_context { |main| main.equal?(target_context) } end Array(session.first)[1] end end end def find_or_create_session_for(context_obj) ensure_session_select_defined if subsession = jobs.select_shell_session(context_obj) jobs.switch(subsession) else irb(context_obj) end end def help_banner banner = [] banner << "" banner << "chef-shell Help" banner << "".ljust(80, "=") banner << "| " + "Command".ljust(25) + "| " + "Description" banner << "".ljust(80, "=") self.all_help_descriptions.each do |help_text| banner << "| " + help_text.cmd.ljust(25) + "| " + help_text.desc end banner << "".ljust(80, "=") banner << "\n" banner << "Use help(:command) to get detailed help with individual commands" banner << "\n" banner.join("\n") end def explain_command(method_name) help = self.all_help_descriptions.find { |h| h.cmd.to_s == method_name.to_s } if help puts "" puts "Command: #{method_name}" puts "".ljust(80, "=") puts help.explanation || help.desc puts "".ljust(80, "=") puts "" else puts "" puts "command #{method_name} not found or no help available" puts "" end end # helpfully returns +:on+ so we can have sugary syntax like `tracing on' def on :on end # returns +:off+ so you can just do `tracing off' def off :off end def help_descriptions @help_descriptions ||= [] end def all_help_descriptions help_descriptions end def desc(help_text) @desc = help_text end def explain(explain_text) @explain = explain_text end def subcommands(subcommand_help = {}) @subcommand_help = subcommand_help end def singleton_method_added(mname) if @desc help_descriptions << Help.new(mname.to_s, @desc.to_s, @explain) @desc, @explain = nil, nil end if @subcommand_help @subcommand_help.each do |subcommand, text| help_descriptions << Help.new("#{mname}.#{subcommand}", text.to_s, nil) end end @subcommand_help = {} end end module String def on_off_to_bool case self when "on" true when "off" false else self end end end module Symbol def on_off_to_bool self.to_s.on_off_to_bool end end module TrueClass def to_on_off_str "on" end def on_off_to_bool self end end module FalseClass def to_on_off_str "off" end def on_off_to_bool self end end # Methods that have associated help text need to be dynamically added # to the main irb objects, so we define them in a proc and later # instance_eval the proc in the object. ObjectUIExtensions = Proc.new do extend Shell::Extensions::ObjectCoreExtensions desc "prints this help message" explain(<<-E) ## SUMMARY ## When called with no argument, +help+ prints a table of all chef-shell commands. When called with an argument COMMAND, +help+ prints a detailed explanation of the command if available, or the description if no explanation is available. E def help(commmand = nil) if commmand explain_command(commmand) else puts help_banner end :ucanhaz_halp end alias :halp :help desc "prints information about chef" def version puts "This is the chef-shell.\n" + " Chef Version: #{::Chef::VERSION}\n" + " http://www.chef.io/\n" + " http://docs.chef.io/" :ucanhaz_automation end alias :shell :version desc "switch to recipe mode" def recipe_mode find_or_create_session_for Shell.session.recipe :recipe end desc "switch to attributes mode" def attributes_mode find_or_create_session_for Shell.session.node :attributes end desc "run chef using the current recipe" def run_chef Chef::Log.level = :debug session = Shell.session runrun = Chef::Runner.new(session.run_context).converge Chef::Log.level = :info runrun end desc "returns an object to control a paused chef run" subcommands :resume => "resume the chef run", :step => "run only the next resource", :skip_back => "move back in the run list", :skip_forward => "move forward in the run list" def chef_run Shell.session.resource_collection.iterator end desc "resets the current recipe" def reset Shell.session.reset! end desc "assume the identity of another node." def become_node(node_name) Shell::DoppelGangerSession.instance.assume_identity(node_name) :doppelganger end alias :doppelganger :become_node desc "turns printout of return values on or off" def echo(on_or_off) conf.echo = on_or_off.on_off_to_bool end desc "says if echo is on or off" def echo? puts "echo is #{conf.echo.to_on_off_str}" end desc "turns on or off tracing of execution. *verbose*" def tracing(on_or_off) conf.use_tracer = on_or_off.on_off_to_bool tracing? end alias :trace :tracing desc "says if tracing is on or off" def tracing? puts "tracing is #{conf.use_tracer.to_on_off_str}" end alias :trace? :tracing? desc "simple ls style command" def ls(directory) Dir.entries(directory) end end MainContextExtensions = Proc.new do desc "returns the current node (i.e., this host)" def node Shell.session.node end desc "pretty print the node's attributes" def ohai(key = nil) pp(key ? node.attribute[key] : node.attribute) end end RESTApiExtensions = Proc.new do desc "edit an object in your EDITOR" explain(<<-E) ## SUMMARY ## +edit(object)+ allows you to edit any object that can be converted to JSON. When finished editing, this method will return the edited object: new_node = edit(existing_node) ## EDITOR SELECTION ## chef-shell looks for an editor using the following logic 1. Looks for an EDITOR set by Shell.editor = "EDITOR" 2. Looks for an EDITOR configured in your chef-shell config file 3. Uses the value of the EDITOR environment variable E def edit(object) unless Shell.editor puts "Please set your editor with Shell.editor = \"vim|emacs|mate|ed\"" return :failburger end filename = "chef-shell-edit-#{object.class.name}-" if object.respond_to?(:name) filename += object.name elsif object.respond_to?(:id) filename += object.id end edited_data = Tempfile.open([filename, ".js"]) do |tempfile| tempfile.sync = true tempfile.puts Chef::JSONCompat.to_json(object) system("#{Shell.editor} #{tempfile.path}") tempfile.rewind tempfile.read end Chef::JSONCompat.from_json(edited_data) end desc "Find and edit API clients" explain(<<-E) ## SUMMARY ## +clients+ allows you to query you chef server for information about your api clients. ## LIST ALL CLIENTS ## To see all clients on the system, use clients.all #=> [, ...] If the output from all is too verbose, or you're only interested in a specific value from each of the objects, you can give a code block to +all+: clients.all { |client| client.name } #=> [CLIENT1_NAME, CLIENT2_NAME, ...] ## SHOW ONE CLIENT ## To see a specific client, use clients.show(CLIENT_NAME) ## SEARCH FOR CLIENTS ## You can also search for clients using +find+ or +search+. You can use the familiar string search syntax: clients.search("KEY:VALUE") Just as the +all+ subcommand, the +search+ subcommand can use a code block to filter or transform the information returned from the search: clients.search("KEY:VALUE") { |c| c.name } You can also use a Hash based syntax, multiple search conditions will be joined with AND. clients.find :KEY => :VALUE, :KEY2 => :VALUE2, ... ## BULK-EDIT CLIENTS ## **BE CAREFUL, THIS IS DESTRUCTIVE** You can bulk edit API Clients using the +transform+ subcommand, which requires a code block. Each client will be saved after the code block is run. If the code block returns +nil+ or +false+, that client will be skipped: clients.transform("*:*") do |client| if client.name =~ /borat/i client.admin(false) true else nil end end This will strip the admin privileges from any client named after borat. E subcommands :all => "list all api clients", :show => "load an api client by name", :search => "search for API clients", :transform => "edit all api clients via a code block and save them" def clients @clients ||= Shell::ModelWrapper.new(Chef::ApiClient, :client) end desc "Find and edit cookbooks" subcommands :all => "list all cookbooks", :show => "load a cookbook by name", :transform => "edit all cookbooks via a code block and save them" def cookbooks @cookbooks ||= Shell::ModelWrapper.new(Chef::CookbookVersion) end desc "Find and edit nodes via the API" explain(<<-E) ## SUMMARY ## +nodes+ Allows you to query your chef server for information about your nodes. ## LIST ALL NODES ## You can list all nodes using +all+ or +list+ nodes.all #=> [, , ...] To limit the information returned for each node, pass a code block to the +all+ subcommand: nodes.all { |node| node.name } #=> [NODE1_NAME, NODE2_NAME, ...] ## SHOW ONE NODE ## You can show the data for a single node using the +show+ subcommand: nodes.show("NODE_NAME") => ## SEARCH FOR NODES ## You can search for nodes using the +search+ or +find+ subcommands: nodes.find(:name => "app*") #=> [, ...] Similarly to +all+, you can pass a code block to limit or transform the information returned: nodes.find(:name => "app#") { |node| node.ec2 } ## BULK EDIT NODES ## **BE CAREFUL, THIS OPERATION IS DESTRUCTIVE** Bulk edit nodes by passing a code block to the +transform+ or +bulk_edit+ subcommand. The block will be applied to each matching node, and then the node will be saved. If the block returns +nil+ or +false+, that node will be skipped. nodes.transform do |node| if node.fqdn =~ /.*\\.preprod\\.example\\.com/ node.set[:environment] = "preprod" end end This will assign the attribute to every node with a FQDN matching the regex. E subcommands :all => "list all nodes", :show => "load a node by name", :search => "search for nodes", :transform => "edit all nodes via a code block and save them" def nodes @nodes ||= Shell::ModelWrapper.new(Chef::Node) end desc "Find and edit roles via the API" explain(<<-E) ## SUMMARY ## +roles+ allows you to query and edit roles on your Chef server. ## SUBCOMMANDS ## * all (list) * show (load) * search (find) * transform (bulk_edit) ## SEE ALSO ## See the help for +nodes+ for more information about the subcommands. E subcommands :all => "list all roles", :show => "load a role by name", :search => "search for roles", :transform => "edit all roles via a code block and save them" def roles @roles ||= Shell::ModelWrapper.new(Chef::Role) end desc "Find and edit +databag_name+ via the api" explain(<<-E) ## SUMMARY ## +databags(DATABAG_NAME)+ allows you to query and edit data bag items on your Chef server. Unlike other commands for working with data on the server, +databags+ requires the databag name as an argument, for example: databags(:users).all ## SUBCOMMANDS ## * all (list) * show (load) * search (find) * transform (bulk_edit) ## SEE ALSO ## See the help for +nodes+ for more information about the subcommands. E subcommands :all => "list all items in the data bag", :show => "load a data bag item by id", :search => "search for items in the data bag", :transform => "edit all items via a code block and save them" def databags(databag_name) @named_databags_wrappers ||= {} @named_databags_wrappers[databag_name] ||= Shell::NamedDataBagWrapper.new(databag_name) end desc "Find and edit environments via the API" explain(<<-E) ## SUMMARY ## +environments+ allows you to query and edit environments on your Chef server. ## SUBCOMMANDS ## * all (list) * show (load) * search (find) * transform (bulk_edit) ## SEE ALSO ## See the help for +nodes+ for more information about the subcommands. E subcommands :all => "list all environments", :show => "load an environment by name", :search => "search for environments", :transform => "edit all environments via a code block and save them" def environments @environments ||= Shell::ModelWrapper.new(Chef::Environment) end desc "A REST Client configured to authenticate with the API" def api @rest = Chef::ServerAPI.new(Chef::Config[:chef_server_url]) end end RecipeUIExtensions = Proc.new do alias :original_resources :resources desc "list all the resources on the current recipe" def resources(*args) if args.empty? pp run_context.resource_collection.keys else pp resources = original_resources(*args) resources end end end def self.extend_context_object(obj) obj.instance_eval(&ObjectUIExtensions) obj.instance_eval(&MainContextExtensions) obj.instance_eval(&RESTApiExtensions) obj.extend(FileUtils) obj.extend(Chef::DSL::PlatformIntrospection) obj.extend(Chef::DSL::DataQuery) end def self.extend_context_node(node_obj) node_obj.instance_eval(&ObjectUIExtensions) end def self.extend_context_recipe(recipe_obj) recipe_obj.instance_eval(&ObjectUIExtensions) recipe_obj.instance_eval(&RecipeUIExtensions) end end end class String include Shell::Extensions::String end class Symbol include Shell::Extensions::Symbol end class TrueClass include Shell::Extensions::TrueClass end class FalseClass include Shell::Extensions::FalseClass end chef-12.14.60/lib/chef/shell/model_wrapper.rb000066400000000000000000000056001276456504500206400ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/convert_to_class_name" require "chef/mixin/language" module Shell class ModelWrapper include Chef::Mixin::ConvertToClassName attr_reader :model_symbol def initialize(model_class, symbol = nil) @model_class = model_class @model_symbol = symbol || convert_to_snake_case(model_class.name, "Chef").to_sym end def search(query) return all if query.to_s == "all" results = [] Chef::Search::Query.new.search(@model_symbol, format_query(query)) do |obj| if block_given? results << yield(obj) else results << obj end end results end alias :find :search def all(&block) all_objects = list_objects block_given? ? all_objects.map(&block) : all_objects end alias :list :all def show(obj_id) @model_class.load(obj_id) end alias :load :show # FIXME: yard with @yield def transform(what_to_transform) if what_to_transform == :all objects_to_transform = list_objects else objects_to_transform = search(what_to_transform) end objects_to_transform.each do |obj| if result = yield(obj) obj.save end end end alias :bulk_edit :transform private # paper over inconsistencies in the model classes APIs, and return the objects # the user wanted instead of the URI=>object stuff def list_objects objects = @model_class.method(:list).arity == 0 ? @model_class.list : @model_class.list(true) objects.map { |obj| Array(obj).find { |o| o.kind_of?(@model_class) } } end def format_query(query) if query.respond_to?(:keys) query.map { |key, value| "#{key}:#{value}" }.join(" AND ") else query end end end class NamedDataBagWrapper < ModelWrapper def initialize(databag_name) @model_symbol = @databag_name = databag_name end alias :list :all def show(item) Chef::DataBagItem.load(@databag_name, item) end private def list_objects all_items = [] Chef::Search::Query.new.search(@databag_name) do |item| all_items << item end all_items end end end chef-12.14.60/lib/chef/shell/shell_session.rb000066400000000000000000000201361276456504500206530ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Author:: Tim Hinderliter () # Copyright:: Copyright 2009-2016, Daniel DeLeo # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/recipe" require "chef/run_context" require "chef/config" require "chef/client" require "chef/cookbook/cookbook_collection" require "chef/cookbook_loader" require "chef/run_list/run_list_expansion" require "chef/formatters/base" require "chef/formatters/doc" require "chef/formatters/minimal" module Shell class ShellSession include Singleton def self.session_type(type = nil) @session_type = type if type @session_type end attr_accessor :node, :compile, :recipe, :run_context attr_reader :node_attributes, :client def initialize @node_built = false formatter = Chef::Formatters.new(Chef::Config.formatter, STDOUT, STDERR) @events = Chef::EventDispatch::Dispatcher.new(formatter) end def node_built? !!@node_built end def reset! loading do rebuild_node @node = client.node shorten_node_inspect Shell::Extensions.extend_context_node(@node) rebuild_context node.consume_attributes(node_attributes) if node_attributes @recipe = Chef::Recipe.new(nil, nil, run_context) Shell::Extensions.extend_context_recipe(@recipe) @node_built = true end end def node_attributes=(attrs) @node_attributes = attrs @node.consume_attributes(@node_attributes) end def resource_collection run_context.resource_collection end def run_context @run_context ||= rebuild_context end def definitions nil end def cookbook_loader nil end def save_node raise "Not Supported! #{self.class.name} doesn't support #save_node, maybe you need to run chef-shell in client mode?" end def rebuild_context raise "Not Implemented! :rebuild_collection should be implemented by subclasses" end private def loading show_loading_progress begin yield rescue => e loading_complete(false) raise e else loading_complete(true) end end def show_loading_progress print "Loading" @loading = true @dot_printer = Thread.new do while @loading print "." sleep 0.5 end end end def loading_complete(success) @loading = false @dot_printer.join msg = success ? "done.\n\n" : "epic fail!\n\n" print msg end def shorten_node_inspect def @node.inspect # rubocop:disable Lint/NestedMethodDefinition "" end end def rebuild_node raise "Not Implemented! :rebuild_node should be implemented by subclasses" end end class StandAloneSession < ShellSession session_type :standalone def rebuild_context cookbook_collection = Chef::CookbookCollection.new({}) @run_context = Chef::RunContext.new(@node, cookbook_collection, @events) # no recipes @run_context.load(Chef::RunList::RunListExpansionFromDisk.new("_default", [])) # empty recipe list end private def rebuild_node Chef::Config[:solo_legacy_mode] = true @client = Chef::Client.new(nil, Chef::Config[:shell_config]) @client.run_ohai @client.load_node @client.build_node end end class SoloSession < ShellSession session_type :solo def definitions @run_context.definitions end def rebuild_context @run_status = Chef::RunStatus.new(@node, @events) Chef::Cookbook::FileVendor.fetch_from_disk(Chef::Config[:cookbook_path]) cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path]) cl.load_cookbooks cookbook_collection = Chef::CookbookCollection.new(cl) @run_context = Chef::RunContext.new(node, cookbook_collection, @events) @run_context.load(Chef::RunList::RunListExpansionFromDisk.new("_default", [])) @run_status.run_context = run_context end private def rebuild_node # Tell the client we're chef solo so it won't try to contact the server Chef::Config[:solo_legacy_mode] = true @client = Chef::Client.new(nil, Chef::Config[:shell_config]) @client.run_ohai @client.load_node @client.build_node end end class ClientSession < SoloSession session_type :client def save_node @client.save_node end def rebuild_context @run_status = Chef::RunStatus.new(@node, @events) Chef::Cookbook::FileVendor.fetch_from_remote(Chef::ServerAPI.new(Chef::Config[:chef_server_url])) cookbook_hash = @client.sync_cookbooks cookbook_collection = Chef::CookbookCollection.new(cookbook_hash) @run_context = Chef::RunContext.new(node, cookbook_collection, @events) @run_context.load(@node.run_list.expand(@node.chef_environment)) @run_status.run_context = run_context end private def rebuild_node # Make sure the client knows this is not chef solo Chef::Config[:solo_legacy_mode] = false @client = Chef::Client.new(nil, Chef::Config[:shell_config]) @client.run_ohai @client.register @client.load_node @client.build_node end end class DoppelGangerClient < Chef::Client attr_reader :node_name def initialize(node_name) @node_name = node_name @ohai = Ohai::System.new end # Run the very smallest amount of ohai we can get away with and still # hope to have things work. Otherwise we're not very good doppelgangers def run_ohai @ohai.require_plugin("os") end # DoppelGanger implementation of build_node. preserves as many of the node's # attributes, and does not save updates to the server def build_node Chef::Log.debug("Building node object for #{@node_name}") @node = Chef::Node.find_or_create(node_name) ohai_data = @ohai.data.merge(@node.automatic_attrs) @node.consume_external_attrs(ohai_data, nil) @run_list_expansion = @node.expand!("server") @expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings Chef::Log.info("Run List is [#{@node.run_list}]") Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]") @node end def register @rest = Chef::ServerAPI.new(Chef::Config[:chef_server_url], :client_name => Chef::Config[:node_name], :signing_key_filename => Chef::Config[:client_key]) end end class DoppelGangerSession < ClientSession session_type "doppelganger client" def save_node puts "A doppelganger should think twice before saving the node" end def assume_identity(node_name) Chef::Config[:doppelganger] = @node_name = node_name reset! rescue Exception => e puts "#{e.class.name}: #{e.message}" puts Array(e.backtrace).join("\n") puts puts "* " * 40 puts "failed to assume the identity of node '#{node_name}', resetting" puts "* " * 40 puts Chef::Config[:doppelganger] = false @node_built = false Shell.session end def rebuild_node # Make sure the client knows this is not chef solo Chef::Config[:solo] = false @client = DoppelGangerClient.new(@node_name) @client.run_ohai @client.register @client.load_node @client.build_node @client.sync_cookbooks end end end chef-12.14.60/lib/chef/shell_out.rb000066400000000000000000000005421276456504500166670ustar00rootroot00000000000000require "mixlib/shellout" class Chef class ShellOut < Mixlib::ShellOut def initialize(*args) Chef::Log.warn("Chef::ShellOut is deprecated, please use Mixlib::ShellOut") called_from = caller[0..3].inject("Called from:\n") { |msg, trace_line| msg << " #{trace_line}\n" } Chef::Log.warn(called_from) super end end end chef-12.14.60/lib/chef/tasks/000077500000000000000000000000001276456504500154705ustar00rootroot00000000000000chef-12.14.60/lib/chef/tasks/chef_repo.rake000066400000000000000000000151211276456504500202660ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # TOPDIR = "." require "rake" desc "By default, print deprecation notice" task :default do puts deprecation_notice end desc "Install the latest copy of the repository on this Chef Server" task :install do puts deprecation_notice puts "The `install` rake task, which included the `update`, `roles`, and" puts "`upload_cookbooks` rake tasks is replaced by the `knife upload`" puts 'sub-command. The notion of "installing" the chef-repo to the Chef' puts "Server. Previously the `install` task would manage server and" puts "client configuration. This will not work at all on Chef Server 11+" puts "and client configuration should be managed with the `chef-client`" puts "cookbook." end desc "Update your repository from source control" task :update do puts deprecation_notice puts "The `update` rake task previously updated the chef-repo from" puts "the detected version control system, either svn or git. However," puts "it has not been recommended for users for years. Most users in" puts "the community use `git`, so the Subversion functionality is not" puts "required, and `git pull` is sufficient for many workflows. The" puts "world of git workflows is rather different now than it was when" puts "this rake task was created." end desc "Create a new cookbook (with COOKBOOK=name, optional CB_PREFIX=site-)" task :new_cookbook do cb = ENV["COOKBOOK"] || "my_cookbook_name" puts deprecation_notice puts "The `new_cookbook` rake task is replaced by the ChefDK cookbook" puts "generator. To generate a new cookbook run:" puts puts "chef generate cookbook #{ENV['COOKBOOK']}" puts puts "Or, if you are not using ChefDK, use `knife cookbook create`:" puts puts "knife cookbook create #{ENV['COOKBOOK']}" end desc "Create a new self-signed SSL certificate for FQDN=foo.example.com" task :ssl_cert do puts deprecation_notice puts "The `ssl_cert` rake task is superseded by using the CHEF-maintained" puts '`openssl` cookbook\'s `openssl_x509` resource which can generate' puts "self-signed certificate chains as convergent resources." puts puts "https://supermarket.getchef.com/cookbooks/openssl" end desc "Build cookbook metadata.json from metadata.rb" task :metadata do puts deprecation_notice puts "The `metadata` rake task is not recommended. Cookbook" puts "`metadata.json` is automatically generated from `metadata.rb`" puts "by `knife` when uploading cookbooks to the Chef Server." end desc "Update roles" task :roles do puts deprecation_notice puts "The `roles` rake task is not recommended. If you are using Ruby" puts "role files (roles/*.rb), you can upload them all with:" puts puts "knife role from file roles/*" puts puts "If you are using JSON role files (roles/*.json), you can upload" puts "them all with:" puts puts "knife upload roles/*.json" end desc "Update a specific role" task :role do puts deprecation_notice puts "The `role` rake task is not recommended. If you are using Ruby" puts "role files, you can upload a single role with:" puts puts "knife role from file rolename.rb" puts puts "If you are using JSON role files, you can upload a single role with" puts puts "knife upload roles/rolename.json" end desc "Upload all cookbooks" task :upload_cookbooks do puts deprecation_notice puts deprecated_cookbook_upload end desc "Upload a single cookbook" task :upload_cookbook do puts deprecation_notice puts deprecated_cookbook_upload end desc "Test all cookbooks" task :test_cookbooks do puts deprecation_notice puts "The `test_cookbooks` rake task is no longer recommended. Previously" puts "it only performed a syntax check, and did no other kind of testing," puts "and the Chef Community has a rich ecosystem of testing tools for" puts "various purposes:" puts puts "- knife cookbook test will perform a syntax check, as this task did" puts " before." puts "- rubocop and foodcritic will perform lint checking for Ruby and" puts " Chef cookbook style according to community standards." puts "- ChefSpec will perform unit testing" puts "- Test Kitchen will perform convergence and post-convergence" puts " testing on virtual machines." end desc "Test a single cookbook" task :test_cookbook => [:test_cookbooks] namespace :databag do desc "Upload a single databag" task :upload do puts deprecation_notice puts "The `data_bags:upload` task is not recommended. You should use" puts "the `knife upload` sub-command for uploading data bag items." puts puts "knife upload data_bags/bagname/itemname.json" end desc "Upload all databags" task :upload_all do puts deprecation_notice puts "The `data_bags:upload_all` task is not recommended. You should" puts "use the `knife upload` sub-command for uploading data bag items." puts puts "knife upload data_bags/*" end desc "Create a databag" task :create do puts deprecation_notice puts deprecated_data_bag_creation end desc "Create a databag item stub" task :create_item do puts deprecation_notice puts deprecated_data_bag_creation end end def deprecation_notice %Q{************************************************* NOTICE: Chef Repository Rake Tasks Are Deprecated ************************************************* } end def deprecated_cookbook_upload %Q{ The `upload_cookbook` and `upload_cookbooks` rake tasks are not recommended. These tasks are replaced by other, better workflow tools, such as `knife cookbook upload`, `knife upload`, or `berks` } end def deprecated_data_bag_creation %Q{ The `data_bags:create` and `data_bags:create_item` tasks are not recommended. You should create data bag items as JSON files in the data_bags directory, with a sub-directory for each bag, and use `knife upload` to upload them. For example, if you have a data bags named `users`, with `finn`, and `jake` items, you would have: ./data_bags/users/finn.json ./data-bags/users/jake.json } end chef-12.14.60/lib/chef/user.rb000066400000000000000000000132611276456504500156510ustar00rootroot00000000000000# # Author:: Steven Danna (steve@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 "chef/config" require "chef/mixin/params_validate" require "chef/mixin/from_file" require "chef/mash" require "chef/json_compat" require "chef/search/query" require "chef/server_api" # TODO # DEPRECATION NOTE # This class will be replaced by Chef::UserV1 in Chef 13. It is the code to support the User object # corrosponding to the Open Source Chef Server 11 and only still exists to support # users still on OSC 11. # # Chef::UserV1 now supports Chef Server 12 and will be moved to this namespace in Chef 13. # # New development should occur in Chef::UserV1. # This file and corrosponding osc_user knife files # should be removed once client support for Open Source Chef Server 11 expires. class Chef class User include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate def initialize @name = "" @public_key = nil @private_key = nil @password = nil @admin = false end def chef_rest_v0 @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0" }) end def name(arg = nil) set_or_return(:name, arg, :regex => /^[a-z0-9\-_]+$/) end def admin(arg = nil) set_or_return(:admin, arg, :kind_of => [TrueClass, FalseClass]) end def public_key(arg = nil) set_or_return(:public_key, arg, :kind_of => String) end def private_key(arg = nil) set_or_return(:private_key, arg, :kind_of => String) end def password(arg = nil) set_or_return(:password, arg, :kind_of => String) end def to_hash result = { "name" => @name, "public_key" => @public_key, "admin" => @admin, } result["private_key"] = @private_key if @private_key result["password"] = @password if @password result end def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def destroy chef_rest_v0.delete("users/#{@name}") end def create payload = { :name => self.name, :admin => self.admin, :password => self.password } payload[:public_key] = public_key if public_key new_user = chef_rest_v0.post("users", payload) Chef::User.from_hash(self.to_hash.merge(new_user)) end def update(new_key = false) payload = { :name => name, :admin => admin } payload[:private_key] = new_key if new_key payload[:password] = password if password updated_user = chef_rest_v0.put("users/#{name}", payload) Chef::User.from_hash(self.to_hash.merge(updated_user)) end def save(new_key = false) begin create rescue Net::HTTPServerException => e if e.response.code == "409" update(new_key) else raise e end end end def reregister reregistered_self = chef_rest_v0.put("users/#{name}", { :name => name, :admin => admin, :private_key => true }) private_key(reregistered_self["private_key"]) self end def to_s "user[#{@name}]" end def inspect "Chef::User name:'#{name}' admin:'#{admin.inspect}'" + "public_key:'#{public_key}' private_key:#{private_key}" end # Class Methods def self.from_hash(user_hash) user = Chef::User.new user.name user_hash["name"] user.private_key user_hash["private_key"] if user_hash.key?("private_key") user.password user_hash["password"] if user_hash.key?("password") user.public_key user_hash["public_key"] user.admin user_hash["admin"] user end def self.from_json(json) Chef::User.from_hash(Chef::JSONCompat.from_json(json)) end def self.json_create(json) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::User#from_json or Chef::User#load.") Chef::User.from_json(json) end def self.list(inflate = false) response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0" }).get("users") users = if response.is_a?(Array) transform_ohc_list_response(response) # OHC/OPC else response # OSC end if inflate users.inject({}) do |user_map, (name, _url)| user_map[name] = Chef::User.load(name) user_map end else users end end def self.load(name) response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], { :api_version => "0" }).get("users/#{name}") Chef::User.from_hash(response) end # Gross. Transforms an API response in the form of: # [ { "user" => { "username" => USERNAME }}, ...] # into the form # { "USERNAME" => "URI" } def self.transform_ohc_list_response(response) new_response = Hash.new response.each do |u| name = u["user"]["username"] new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}" end new_response end private_class_method :transform_ohc_list_response end end chef-12.14.60/lib/chef/user_v1.rb000066400000000000000000000267541276456504500162720ustar00rootroot00000000000000# # Author:: Steven Danna (steve@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 "chef/config" require "chef/mixin/params_validate" require "chef/mixin/from_file" require "chef/mash" require "chef/json_compat" require "chef/search/query" require "chef/mixin/api_version_request_handling" require "chef/exceptions" require "chef/server_api" # OSC 11 BACKWARDS COMPATIBILITY NOTE (remove after OSC 11 support ends) # # In general, Chef::UserV1 is no longer expected to support Open Source Chef 11 Server requests. # The object that handles those requests remain in the Chef::User namespace. # This code will be moved to the Chef::User namespace as of Chef 13. # # Exception: self.list is backwards compatible with OSC 11 class Chef class UserV1 include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate include Chef::Mixin::ApiVersionRequestHandling SUPPORTED_API_VERSIONS = [0, 1] def initialize @username = nil @display_name = nil @first_name = nil @middle_name = nil @last_name = nil @email = nil @password = nil @public_key = nil @private_key = nil @create_key = nil end def chef_root_rest_v0 @chef_root_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], { :api_version => "0" }) end def chef_root_rest_v1 @chef_root_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], { :api_version => "1" }) end def username(arg = nil) set_or_return(:username, arg, :regex => /^[a-z0-9\-_]+$/) end def display_name(arg = nil) set_or_return(:display_name, arg, :kind_of => String) end def first_name(arg = nil) set_or_return(:first_name, arg, :kind_of => String) end def middle_name(arg = nil) set_or_return(:middle_name, arg, :kind_of => String) end def last_name(arg = nil) set_or_return(:last_name, arg, :kind_of => String) end def email(arg = nil) set_or_return(:email, arg, :kind_of => String) end def create_key(arg = nil) set_or_return(:create_key, arg, :kind_of => [TrueClass, FalseClass]) end def public_key(arg = nil) set_or_return(:public_key, arg, :kind_of => String) end def private_key(arg = nil) set_or_return(:private_key, arg, :kind_of => String) end def password(arg = nil) set_or_return(:password, arg, :kind_of => String) end def to_hash result = { "username" => @username, } result["display_name"] = @display_name unless @display_name.nil? result["first_name"] = @first_name unless @first_name.nil? result["middle_name"] = @middle_name unless @middle_name.nil? result["last_name"] = @last_name unless @last_name.nil? result["email"] = @email unless @email.nil? result["password"] = @password unless @password.nil? result["public_key"] = @public_key unless @public_key.nil? result["private_key"] = @private_key unless @private_key.nil? result["create_key"] = @create_key unless @create_key.nil? result end def to_json(*a) Chef::JSONCompat.to_json(to_hash, *a) end def destroy # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION) Chef::ServerAPI.new(Chef::Config[:chef_server_url]).delete("users/#{@username}") end def create # try v1, fail back to v0 if v1 not supported begin payload = { :username => @username, :display_name => @display_name, :first_name => @first_name, :last_name => @last_name, :email => @email, :password => @password, } payload[:public_key] = @public_key unless @public_key.nil? payload[:create_key] = @create_key unless @create_key.nil? payload[:middle_name] = @middle_name unless @middle_name.nil? raise Chef::Exceptions::InvalidUserAttribute, "You cannot set both public_key and create_key for create." if !@create_key.nil? && !@public_key.nil? new_user = chef_root_rest_v1.post("users", payload) # get the private_key out of the chef_key hash if it exists if new_user["chef_key"] if new_user["chef_key"]["private_key"] new_user["private_key"] = new_user["chef_key"]["private_key"] end new_user["public_key"] = new_user["chef_key"]["public_key"] new_user.delete("chef_key") end rescue Net::HTTPServerException => e # rescue API V0 if 406 and the server supports V0 supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) raise e unless supported_versions && supported_versions.include?(0) payload = { :username => @username, :display_name => @display_name, :first_name => @first_name, :last_name => @last_name, :email => @email, :password => @password, } payload[:middle_name] = @middle_name unless @middle_name.nil? payload[:public_key] = @public_key unless @public_key.nil? # under API V0, the server will create a key pair if public_key isn't passed new_user = chef_root_rest_v0.post("users", payload) end Chef::UserV1.from_hash(self.to_hash.merge(new_user)) end def update(new_key = false) begin payload = { :username => username } payload[:display_name] = display_name unless display_name.nil? payload[:first_name] = first_name unless first_name.nil? payload[:middle_name] = middle_name unless middle_name.nil? payload[:last_name] = last_name unless last_name.nil? payload[:email] = email unless email.nil? payload[:password] = password unless password.nil? # API V1 will fail if these key fields are defined, and try V0 below if relevant 400 is returned payload[:public_key] = public_key unless public_key.nil? payload[:private_key] = new_key if new_key updated_user = chef_root_rest_v1.put("users/#{username}", payload) rescue Net::HTTPServerException => e if e.response.code == "400" # if a 400 is returned but the error message matches the error related to private / public key fields, try V0 # else, raise the 400 error = Chef::JSONCompat.from_json(e.response.body)["error"].first error_match = /Since Server API v1, all keys must be updated via the keys endpoint/.match(error) if error_match.nil? raise e end else # for other types of errors, test for API versioning errors right away supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS) raise e unless supported_versions && supported_versions.include?(0) end updated_user = chef_root_rest_v0.put("users/#{username}", payload) end Chef::UserV1.from_hash(self.to_hash.merge(updated_user)) end def save(new_key = false) begin create rescue Net::HTTPServerException => e if e.response.code == "409" update(new_key) else raise e end end end # Note: remove after API v0 no longer supported by client (and knife command). def reregister begin payload = self.to_hash.merge({ "private_key" => true }) reregistered_self = chef_root_rest_v0.put("users/#{username}", payload) private_key(reregistered_self["private_key"]) # only V0 supported for reregister rescue Net::HTTPServerException => e # if there was a 406 related to versioning, give error explaining that # only API version 0 is supported for reregister command if e.response.code == "406" && e.response["x-ops-server-api-version"] version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"]) min_version = version_header["min_version"] max_version = version_header["max_version"] error_msg = reregister_only_v0_supported_error_msg(max_version, min_version) raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg) else raise e end end self end def to_s "user[#{@username}]" end # Class Methods def self.from_hash(user_hash) user = Chef::UserV1.new user.username user_hash["username"] user.display_name user_hash["display_name"] if user_hash.key?("display_name") user.first_name user_hash["first_name"] if user_hash.key?("first_name") user.middle_name user_hash["middle_name"] if user_hash.key?("middle_name") user.last_name user_hash["last_name"] if user_hash.key?("last_name") user.email user_hash["email"] if user_hash.key?("email") user.password user_hash["password"] if user_hash.key?("password") user.public_key user_hash["public_key"] if user_hash.key?("public_key") user.private_key user_hash["private_key"] if user_hash.key?("private_key") user.create_key user_hash["create_key"] if user_hash.key?("create_key") user end def self.from_json(json) Chef::UserV1.from_hash(Chef::JSONCompat.from_json(json)) end def self.json_create(json) Chef.log_deprecation("Auto inflation of JSON data is deprecated. Please use Chef::UserV1#from_json or Chef::UserV1#load.") Chef::UserV1.from_json(json) end def self.list(inflate = false) response = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("users") users = if response.is_a?(Array) # EC 11 / CS 12 V0, V1 # GET /organizations//users transform_list_response(response) else # OSC 11 # GET /users # EC 11 / CS 12 V0, V1 # GET /users response # OSC end if inflate users.inject({}) do |user_map, (name, _url)| user_map[name] = Chef::UserV1.load(name) user_map end else users end end def self.load(username) # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION) response = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("users/#{username}") Chef::UserV1.from_hash(response) end # Gross. Transforms an API response in the form of: # [ { "user" => { "username" => USERNAME }}, ...] # into the form # { "USERNAME" => "URI" } def self.transform_list_response(response) new_response = Hash.new response.each do |u| name = u["user"]["username"] new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}" end new_response end private_class_method :transform_list_response end end chef-12.14.60/lib/chef/util/000077500000000000000000000000001276456504500153205ustar00rootroot00000000000000chef-12.14.60/lib/chef/util/backup.rb000066400000000000000000000061631276456504500171200ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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/util/path_helper" class Chef class Util class Backup attr_reader :new_resource attr_accessor :path def initialize(new_resource, path = nil) @new_resource = new_resource @path = path.nil? ? new_resource.path : path end def backup! if @new_resource.backup != false && @new_resource.backup > 0 && ::File.exist?(path) do_backup # Clean up after the number of backups slice_number = @new_resource.backup backup_files = sorted_backup_files if backup_files.length >= @new_resource.backup remainder = backup_files.slice(slice_number..-1) remainder.each do |backup_to_delete| delete_backup(backup_to_delete) end end end end private def backup_filename @backup_filename ||= begin time = Time.now nanoseconds = sprintf("%6f", time.to_f).split(".")[1] savetime = time.strftime("%Y%m%d%H%M%S.#{nanoseconds}") backup_filename = "#{path}.chef-#{savetime}" backup_filename = backup_filename.sub(/^([A-Za-z]:)/, "") #strip drive letter on Windows end end def prefix # if :file_backup_path is nil, we fallback to the old behavior of # keeping the backup in the same directory. We also need to to_s it # so we don't get a type error around implicit to_str conversions. @prefix ||= Chef::Config[:file_backup_path].to_s end def backup_path @backup_path ||= ::File.join(prefix, backup_filename) end def do_backup FileUtils.mkdir_p(::File.dirname(backup_path)) if Chef::Config[:file_backup_path] FileUtils.cp(path, backup_path, :preserve => true) Chef::Log.info("#{@new_resource} backed up to #{backup_path}") end def delete_backup(backup_file) FileUtils.rm(backup_file) Chef::Log.info("#{@new_resource} removed backup at #{backup_file}") end def unsorted_backup_files # If you replace this with Dir[], you will probably break Windows. fn = Regexp.escape(::File.basename(path)) Dir.entries(::File.dirname(backup_path)).select do |f| !!(f =~ /\A#{fn}.chef-[0-9.]*\B/) end.map { |f| ::File.join(::File.dirname(backup_path), f) } end def sorted_backup_files unsorted_backup_files.sort { |a, b| b <=> a } end end end end chef-12.14.60/lib/chef/util/diff.rb000066400000000000000000000154401276456504500165610ustar00rootroot00000000000000# Author:: Lamont Granquist () # Copyright:: Copyright 2013-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. # # Some portions of this file are derived from material in the diff-lcs # project licensed under the terms of the MIT license, provided below. # # Copyright:: Copyright 2004-2016, Austin Ziegler # License:: MIT # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of this Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OF OTHER DEALINGS IN THE # SOFTWARE. require "diff/lcs" require "diff/lcs/hunk" class Chef class Util class Diff # @todo: to_a, to_s, to_json, inspect defs, accessors for @diff and @error # @todo: move coercion to UTF-8 into to_json # @todo: replace shellout to diff -u with diff-lcs gem def for_output # formatted output to a terminal uses arrays of strings and returns error strings @diff.nil? ? [ @error ] : @diff end def for_reporting # caller needs to ensure that new files aren't posted to resource reporting return nil if @diff.nil? @diff.join("\\n") end def use_tempfile_if_missing(file) tempfile = nil unless File.exists?(file) Chef::Log.debug("File #{file} does not exist to diff against, using empty tempfile") tempfile = Tempfile.new("chef-diff") file = tempfile.path end yield file unless tempfile.nil? tempfile.close tempfile.unlink end end def diff(old_file, new_file) use_tempfile_if_missing(old_file) do |old_file| use_tempfile_if_missing(new_file) do |new_file| @error = do_diff(old_file, new_file) end end end # produces a unified-output-format diff with 3 lines of context # ChefFS uses udiff() directly def udiff(old_file, new_file) diff_str = "" file_length_difference = 0 old_data = IO.readlines(old_file).map { |e| e.chomp } new_data = IO.readlines(new_file).map { |e| e.chomp } diff_data = ::Diff::LCS.diff(old_data, new_data) return diff_str if old_data.empty? && new_data.empty? return "No differences encountered\n" if diff_data.empty? # write diff header (standard unified format) ft = File.stat(old_file).mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.%N %z") diff_str << "--- #{old_file}\t#{ft}\n" ft = File.stat(new_file).mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.%N %z") diff_str << "+++ #{new_file}\t#{ft}\n" # loop over diff hunks. if a hunk overlaps with the last hunk, # join them. otherwise, print out the old one. old_hunk = hunk = nil diff_data.each do |piece| begin hunk = ::Diff::LCS::Hunk.new(old_data, new_data, piece, 3, file_length_difference) file_length_difference = hunk.file_length_difference next unless old_hunk next if hunk.merge(old_hunk) diff_str << old_hunk.diff(:unified) << "\n" ensure old_hunk = hunk end end diff_str << old_hunk.diff(:unified) << "\n" return diff_str end private def do_diff(old_file, new_file) if Chef::Config[:diff_disabled] return "(diff output suppressed by config)" end diff_filesize_threshold = Chef::Config[:diff_filesize_threshold] diff_output_threshold = Chef::Config[:diff_output_threshold] if ::File.size(old_file) > diff_filesize_threshold || ::File.size(new_file) > diff_filesize_threshold return "(file sizes exceed #{diff_filesize_threshold} bytes, diff output suppressed)" end # MacOSX(BSD?) diff will *sometimes* happily spit out nasty binary diffs return "(current file is binary, diff output suppressed)" if is_binary?(old_file) return "(new content is binary, diff output suppressed)" if is_binary?(new_file) begin Chef::Log.debug("Running: diff -u #{old_file} #{new_file}") diff_str = udiff(old_file, new_file) rescue Exception => e # Should *not* receive this, but in some circumstances it seems that # an exception can be thrown even using shell_out instead of shell_out! return "Could not determine diff. Error: #{e.message}" end if !diff_str.empty? && diff_str != "No differences encountered\n" if diff_str.length > diff_output_threshold return "(long diff of over #{diff_output_threshold} characters, diff output suppressed)" else diff_str = encode_diff_for_json(diff_str) @diff = diff_str.split("\n") return "(diff available)" end else return "(no diff)" end end def is_binary?(path) File.open(path) do |file| # XXX: this slurps into RAM, but we should have already checked our diff has a reasonable size buff = file.read buff = "" if buff.nil? begin return buff !~ /\A[\s[:print:]]*\z/m rescue ArgumentError => e return true if e.message =~ /invalid byte sequence/ raise end end end def encode_diff_for_json(diff_str) diff_str.encode!("UTF-8", :invalid => :replace, :undef => :replace, :replace => "?") end end end end chef-12.14.60/lib/chef/util/dsc/000077500000000000000000000000001276456504500160715ustar00rootroot00000000000000chef-12.14.60/lib/chef/util/dsc/configuration_generator.rb000066400000000000000000000120761276456504500233410ustar00rootroot00000000000000# # Author:: Adam Edwards () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # 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/util/powershell/cmdlet" class Chef::Util::DSC class ConfigurationGenerator def initialize(node, config_directory) @node = node @config_directory = config_directory end def configuration_document_from_script_code(code, configuration_flags, imports, shellout_flags) Chef::Log.debug("DSC: DSC code:\n '#{code}'") generated_script_path = write_document_generation_script(code, "chef_dsc", imports) begin configuration_document_from_script_path(generated_script_path, "chef_dsc", configuration_flags, shellout_flags) ensure ::FileUtils.rm(generated_script_path) end end def configuration_document_from_script_path(script_path, configuration_name, configuration_flags, shellout_flags) validate_configuration_name!(configuration_name) document_generation_cmdlet = Chef::Util::Powershell::Cmdlet.new( @node, configuration_document_generation_code(script_path, configuration_name)) merged_configuration_flags = get_merged_configuration_flags!(configuration_flags, configuration_name) document_generation_cmdlet.run!(merged_configuration_flags, shellout_flags) configuration_document_location = find_configuration_document(configuration_name) if ! configuration_document_location raise "No DSC configuration for '#{configuration_name}' was generated from supplied DSC script" end configuration_document = get_configuration_document(configuration_document_location) ::FileUtils.rm_rf(configuration_document_location) configuration_document end protected # From PowerShell error help for the Configuration language element: # Standard names may only contain letters (a-z, A-Z), numbers (0-9), and underscore (_). # The name may not be null or empty, and should start with a letter. def validate_configuration_name!(configuration_name) if !!(configuration_name =~ /\A[A-Za-z]+[_a-zA-Z0-9]*\Z/) == false raise ArgumentError, 'Configuration `#{configuration_name}` is not a valid PowerShell cmdlet name' end end def get_merged_configuration_flags!(configuration_flags, configuration_name) merged_configuration_flags = { :outputpath => configuration_document_directory(configuration_name) } if configuration_flags configuration_flags.map do |switch, value| if merged_configuration_flags.key?(switch.to_s.downcase.to_sym) raise ArgumentError, "The `flags` attribute for the dsc_script resource contained a command line switch :#{switch} that is disallowed." end merged_configuration_flags[switch.to_s.downcase.to_sym] = value end end merged_configuration_flags end def configuration_code(code, configuration_name, imports) <<-EOF $ProgressPreference = 'SilentlyContinue'; Configuration '#{configuration_name}' { #{generate_import_resource_statements(imports).join(" \n")} node 'localhost' { #{code} } } EOF end def generate_import_resource_statements(imports) if imports imports.map do |resource_module, resources| if resources.length == 0 || resources.include?("*") "Import-DscResource -ModuleName #{resource_module}" else "Import-DscResource -ModuleName #{resource_module} -Name #{resources.join(',')}" end end else [] end end def configuration_document_generation_code(configuration_script, configuration_name) ". '#{configuration_script}';#{configuration_name}" end def write_document_generation_script(code, configuration_name, imports) script_path = "#{@config_directory}/chef_dsc_config.ps1" ::File.open(script_path, "wt") do |script| script.write(configuration_code(code, configuration_name, imports)) end script_path end def find_configuration_document(configuration_name) document_directory = configuration_document_directory(configuration_name) document_file_name = ::Dir.entries(document_directory).find { |path| path =~ /.*.mof/ } ::File.join(document_directory, document_file_name) if document_file_name end def configuration_document_directory(configuration_name) ::File.join(@config_directory, configuration_name) end def get_configuration_document(document_path) ::File.open(document_path, "rb") do |file| file.read end end end end chef-12.14.60/lib/chef/util/dsc/lcm_output_parser.rb000066400000000000000000000114441276456504500221710ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/log" require "chef/util/dsc/resource_info" require "chef/exceptions" class Chef class Util class DSC class LocalConfigurationManager module Parser # Parses the output from LCM and returns a list of Chef::Util::DSC::ResourceInfo objects # that describe how the resources affected the system # # Example: # parse <<-EOF # What if: [Machine]: LCM: [Start Set ] # What if: [Machine]: LCM: [Start Resource ] [[File]FileToNotBeThere] # What if: [Machine]: LCM: [Start Set ] [[File]FileToNotBeThere] # What if: [C:\ShouldNotExist.txt] removed # What if: [Machine]: LCM: [End Set ] [[File]FileToNotBeThere] in 0.1 seconds # What if: [Machine]: LCM: [End Resource ] [[File]FileToNotBeThere] # What if: [Machine]: LCM: [End Set ] # EOF # # would return # # [ # Chef::Util::DSC::ResourceInfo.new( # '[[File]FileToNotBeThere]', # true, # [ # '[[File]FileToNotBeThere]', # '[C:\Shouldnotexist.txt]', # '[[File]FileToNotBeThere] in 0.1 seconds' # ] # ) # ] # def self.parse(lcm_output) lcm_output ||= "" current_resource = Hash.new resources = [] lcm_output.lines.each do |line| op_action, op_type, info = parse_line(line) case op_action when :start case op_type when :set if current_resource[:name] current_resource[:context] = :logging current_resource[:logs] = [info] end when :resource if current_resource[:name] resources.push(current_resource) end current_resource = { :name => info } else Chef::Log.debug("Ignoring op_action #{op_action}: Read line #{line}") end when :end # Make sure we log the last line if current_resource[:context] == :logging && info.include?(current_resource[:name]) current_resource[:logs].push(info) end current_resource[:context] = nil when :skip current_resource[:skipped] = true when :info if current_resource[:context] == :logging current_resource[:logs].push(info) end end end if current_resource[:name] resources.push(current_resource) end if resources.length > 0 build_resource_info(resources) else raise Chef::Exceptions::LCMParser, "Could not parse:\n#{lcm_output}" end end def self.parse_line(line) if match = line.match(/^.*?:.*?:\s*LCM:\s*\[(.*?)\](.*)/) # If the line looks like # What If: [machinename]: LCM: [op_action op_type] message # extract op_action, op_type, and message operation, info = match.captures op_action, op_type = operation.strip.split(" ").map { |m| m.downcase.to_sym } else op_action = op_type = :info if match = line.match(/^.*?:.*?: \s+(.*)/) info = match.captures[0] else info = line end end info.strip! # Because this was formatted for humans return [op_action, op_type, info] end private_class_method :parse_line def self.build_resource_info(resources) resources.map do |r| Chef::Util::DSC::ResourceInfo.new(r[:name], !r[:skipped], r[:logs]) end end private_class_method :build_resource_info end end end end end chef-12.14.60/lib/chef/util/dsc/local_configuration_manager.rb000066400000000000000000000122151276456504500241320ustar00rootroot00000000000000# # Author:: Adam Edwards () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # 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/util/powershell/cmdlet" require "chef/util/dsc/lcm_output_parser" class Chef::Util::DSC class LocalConfigurationManager def initialize(node, configuration_path) @node = node @configuration_path = configuration_path clear_execution_time end def test_configuration(configuration_document, shellout_flags) status = run_configuration_cmdlet(configuration_document, false, shellout_flags) log_what_if_exception(status.stderr) unless status.succeeded? configuration_update_required?(status.return_value) end def set_configuration(configuration_document, shellout_flags) run_configuration_cmdlet(configuration_document, true, shellout_flags) end def last_operation_execution_time_seconds if @operation_start_time && @operation_end_time @operation_end_time - @operation_start_time end end private def run_configuration_cmdlet(configuration_document, apply_configuration, shellout_flags) Chef::Log.debug("DSC: Calling DSC Local Config Manager to #{apply_configuration ? "set" : "test"} configuration document.") test_only_parameters = ! apply_configuration ? "-whatif; if (! $?) { exit 1 }" : "" start_operation_timing command_code = lcm_command_code(@configuration_path, test_only_parameters) status = nil begin save_configuration_document(configuration_document) cmdlet = ::Chef::Util::Powershell::Cmdlet.new(@node, "#{command_code}") if apply_configuration status = cmdlet.run!({}, shellout_flags) else status = cmdlet.run({}, shellout_flags) end ensure end_operation_timing remove_configuration_document if last_operation_execution_time_seconds Chef::Log.debug("DSC: DSC operation completed in #{last_operation_execution_time_seconds} seconds.") end end Chef::Log.debug("DSC: Completed call to DSC Local Config Manager") status end def lcm_command_code(configuration_path, test_only_parameters) <<-EOH $ProgressPreference = 'SilentlyContinue';start-dscconfiguration -path #{@configuration_path} -wait -erroraction 'continue' -force #{test_only_parameters} EOH end def log_what_if_exception(what_if_exception_output) if whatif_not_supported?(what_if_exception_output) # LCM returns an error if any of the resources do not support the opptional What-If Chef::Log.warn("Received error while testing configuration due to resource not supporting 'WhatIf'") elsif dsc_module_import_failure?(what_if_exception_output) Chef::Log.warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") else Chef::Log.warn("Received error while testing configuration:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") end end def whatif_not_supported?(what_if_exception_output) !! (what_if_exception_output.gsub(/[\r\n]+/, "").gsub(/\s+/, " ") =~ /A parameter cannot be found that matches parameter name 'Whatif'/i) end def dsc_module_import_failure?(what_if_output) !! (what_if_output =~ /\sCimException/ && what_if_output =~ /ProviderOperationExecutionFailure/ && what_if_output =~ /\smodule\s+is\s+installed/) end def configuration_update_required?(what_if_output) Chef::Log.debug("DSC: DSC returned the following '-whatif' output from test operation:\n#{what_if_output}") begin Parser.parse(what_if_output) rescue Chef::Exceptions::LCMParser => e Chef::Log.warn("Could not parse LCM output: #{e}") [Chef::Util::DSC::ResourceInfo.new("Unknown DSC Resources", true, ["Unknown changes because LCM output was not parsable."])] end end def save_configuration_document(configuration_document) ::FileUtils.mkdir_p(@configuration_path) ::File.open(configuration_document_path, "wb") do |file| file.write(configuration_document) end end def remove_configuration_document ::FileUtils.rm(configuration_document_path) end def configuration_document_path File.join(@configuration_path, "..mof") end def clear_execution_time @operation_start_time = nil @operation_end_time = nil end def start_operation_timing clear_execution_time @operation_start_time = Time.now end def end_operation_timing @operation_end_time = Time.now end end end chef-12.14.60/lib/chef/util/dsc/resource_info.rb000066400000000000000000000011131276456504500212540ustar00rootroot00000000000000 class Chef class Util class DSC class ResourceInfo # The name is the text following [Start Set] attr_reader :name # A list of all log messages between [Start Set] and [End Set]. # Each line is an element in the list. attr_reader :change_log def initialize(name, sets, change_log) @name = name @sets = sets @change_log = change_log || [] end # Does this resource change the state of the system? def changes_state? @sets end end end end end chef-12.14.60/lib/chef/util/dsc/resource_store.rb000066400000000000000000000056461276456504500214740ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # # Copyright:: Copyright 2015-2016, Chef Software, Inc. # # 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/util/powershell/cmdlet" require "chef/util/powershell/cmdlet_result" require "chef/exceptions" class Chef class Util class DSC class ResourceStore def self.instance @@instance ||= ResourceStore.new.tap do |store| store.send(:populate_cache) end end def resources @resources ||= [] end def find(name, module_name = nil) found = find_resources(name, module_name, resources) # We don't have it, query for the resource...it might # have been added since we last queried if found.length == 0 rs = query_resource(name) add_resources(rs) found = find_resources(name, module_name, rs) end found end private def add_resource(new_r) count = resources.count do |r| r["ResourceType"].casecmp(new_r["ResourceType"]) == 0 end if count == 0 resources << new_r end end def add_resources(rs) rs.each do |r| add_resource(r) end end def populate_cache @resources = query_resources end def find_resources(name, module_name, rs) found = rs.find_all do |r| name_matches = r["Name"].casecmp(name) == 0 if name_matches module_name == nil || (r["Module"] && r["Module"]["Name"].casecmp(module_name) == 0) else false end end end # Returns a list of dsc resources def query_resources cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, "get-dscresource", :object) result = cmdlet.run result.return_value end # Returns a list of dsc resources matching the provided name def query_resource(resource_name) cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, "get-dscresource #{resource_name}", :object) result = cmdlet.run ret_val = result.return_value if ret_val.nil? [] elsif ret_val.is_a? Array ret_val else [ret_val] end end end end end end chef-12.14.60/lib/chef/util/editor.rb000066400000000000000000000037471276456504500171460ustar00rootroot00000000000000# # Author:: Chris Bandy () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Util class Editor attr_reader :lines def initialize(lines) @lines = lines.to_a.clone end def append_line_after(search, line_to_append) lines = [] @lines.each do |line| lines << line lines << line_to_append if line.match(search) end (lines.length - @lines.length).tap { @lines = lines } end def append_line_if_missing(search, line_to_append) count = 0 unless @lines.find { |line| line.match(search) } count = 1 @lines << line_to_append end count end def remove_lines(search) count = 0 @lines.delete_if do |line| count += 1 if line.match(search) end count end def replace(search, replace) count = 0 @lines.map! do |line| if line.match(search) count += 1 line.gsub!(search, replace) else line end end count end def replace_lines(search, replace) count = 0 @lines.map! do |line| if line.match(search) count += 1 replace else line end end count end end end end chef-12.14.60/lib/chef/util/file_edit.rb000066400000000000000000000064471276456504500176040ustar00rootroot00000000000000# # Author:: Nuo Yan () # Copyright:: Copyright 2009-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/util/editor" require "fileutils" class Chef class Util class FileEdit private attr_reader :editor, :original_pathname public def initialize(filepath) raise ArgumentError, "File '#{filepath}' does not exist" unless File.exist?(filepath) @editor = Editor.new(File.open(filepath, &:readlines)) @original_pathname = filepath @file_edited = false end # return if file has been edited def file_edited? @file_edited end #search the file line by line and match each line with the given regex #if matched, replace the whole line with newline. def search_file_replace_line(regex, newline) @changes = (editor.replace_lines(regex, newline) > 0) || @changes end #search the file line by line and match each line with the given regex #if matched, replace the match (all occurrences) with the replace parameter def search_file_replace(regex, replace) @changes = (editor.replace(regex, replace) > 0) || @changes end #search the file line by line and match each line with the given regex #if matched, delete the line def search_file_delete_line(regex) @changes = (editor.remove_lines(regex) > 0) || @changes end #search the file line by line and match each line with the given regex #if matched, delete the match (all occurrences) from the line def search_file_delete(regex) search_file_replace(regex, "") end #search the file line by line and match each line with the given regex #if matched, insert newline after each matching line def insert_line_after_match(regex, newline) @changes = (editor.append_line_after(regex, newline) > 0) || @changes end #search the file line by line and match each line with the given regex #if not matched, insert newline at the end of the file def insert_line_if_no_match(regex, newline) @changes = (editor.append_line_if_missing(regex, newline) > 0) || @changes end def unwritten_changes? !!@changes end #Make a copy of old_file and write new file out (only if file changed) def write_file if @changes backup_pathname = original_pathname + ".old" FileUtils.cp(original_pathname, backup_pathname, :preserve => true) File.open(original_pathname, "w") do |newfile| editor.lines.each do |line| newfile.puts(line) end newfile.flush end @file_edited = true end @changes = false end end end end chef-12.14.60/lib/chef/util/path_helper.rb000066400000000000000000000014421276456504500201410ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef-config/path_helper" class Chef class Util PathHelper = ChefConfig::PathHelper end end chef-12.14.60/lib/chef/util/powershell/000077500000000000000000000000001276456504500175045ustar00rootroot00000000000000chef-12.14.60/lib/chef/util/powershell/cmdlet.rb000066400000000000000000000132651276456504500213100ustar00rootroot00000000000000# # Author:: Adam Edwards () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # 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 "mixlib/shellout" require "chef/mixin/windows_architecture_helper" require "chef/util/powershell/cmdlet_result" class Chef class Util class Powershell class Cmdlet def initialize(node, cmdlet, output_format = nil, output_format_options = {}) @output_format = output_format @node = node case output_format when nil @json_format = false when :json @json_format = true when :text @json_format = false when :object @json_format = true else raise ArgumentError, "Invalid output format #{output_format} specified" end @cmdlet = cmdlet @output_format_options = output_format_options end attr_reader :output_format def run(switches = {}, execution_options = {}, *arguments) streams = { :json => CmdletStream.new("json"), :verbose => CmdletStream.new("verbose"), } arguments_string = arguments.join(" ") switches_string = command_switches_string(switches) json_depth = 5 if @json_format && @output_format_options.has_key?(:depth) json_depth = @output_format_options[:depth] end json_command = if @json_format " | convertto-json -compress -depth #{json_depth} > #{streams[:json].path}" else "" end redirections = "4> '#{streams[:verbose].path}'" command_string = "powershell.exe -executionpolicy bypass -noprofile -noninteractive "\ "-command \"trap [Exception] {write-error -exception "\ "($_.Exception.Message);exit 1};#{@cmdlet} #{switches_string} "\ "#{arguments_string} #{redirections}"\ "#{json_command}\";if ( ! $? ) { exit 1 }" augmented_options = { :returns => [0], :live_stream => false }.merge(execution_options) command = Mixlib::ShellOut.new(command_string, augmented_options) status = nil with_os_architecture(@node) do status = command.run_command end CmdletResult.new(status, streams, @output_format) end def run!(switches = {}, execution_options = {}, *arguments) result = run(switches, execution_options, arguments) if ! result.succeeded? raise Chef::Exceptions::PowershellCmdletException, "Powershell Cmdlet failed: #{result.stderr}" end result end protected include Chef::Mixin::WindowsArchitectureHelper def validate_switch_name!(switch_parameter_name) if !!(switch_parameter_name =~ /\A[A-Za-z]+[_a-zA-Z0-9]*\Z/) == false raise ArgumentError, "`#{switch_parameter_name}` is not a valid PowerShell cmdlet switch parameter name" end end def escape_parameter_value(parameter_value) parameter_value.gsub(/(`|'|"|#)/, '`\1') end def escape_string_parameter_value(parameter_value) "'#{escape_parameter_value(parameter_value)}'" end def command_switches_string(switches) command_switches = switches.map do |switch_name, switch_value| if switch_name.class != Symbol raise ArgumentError, "Invalid type `#{switch_name} `for PowerShell switch '#{switch_name}'. The switch must be specified as a Symbol'" end validate_switch_name!(switch_name) switch_argument = "" switch_present = true case switch_value when Numeric switch_argument = switch_value.to_s when Float switch_argument = switch_value.to_s when FalseClass switch_present = false when TrueClass when String switch_argument = escape_string_parameter_value(switch_value) else raise ArgumentError, "Invalid argument type `#{switch_value.class}` specified for PowerShell switch `:#{switch_name}`. Arguments to PowerShell must be of type `String`, `Numeric`, `Float`, `FalseClass`, or `TrueClass`" end switch_present ? ["-#{switch_name.to_s.downcase}", switch_argument].join(" ").strip : "" end command_switches.join(" ") end class CmdletStream def initialize(name) @filename = Dir::Tmpname.create(name) {} ObjectSpace.define_finalizer(self, self.class.destroy(@filename)) end def path @filename end def read if File.exist? @filename File.open(@filename, "rb:bom|UTF-16LE") do |f| f.read.encode("UTF-8") end end end def self.destroy(name) proc { File.delete(name) if File.exists? name } end end end end end end chef-12.14.60/lib/chef/util/powershell/cmdlet_result.rb000066400000000000000000000027301276456504500227010ustar00rootroot00000000000000# # Author:: Adam Edwards () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/json_compat" class Chef class Util class Powershell class CmdletResult attr_reader :output_format def initialize(status, streams, output_format) @status = status @output_format = output_format @streams = streams end def stdout @status.stdout end def stderr @status.stderr end def stream(name) @streams[name].read end def return_value if output_format == :object Chef::JSONCompat.parse(stream(:json)) elsif output_format == :json stream(:json) else @status.stdout end end def succeeded? @succeeded = @status.status.exitstatus == 0 end end end end end chef-12.14.60/lib/chef/util/powershell/ps_credential.rb000066400000000000000000000022331276456504500226450ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # # Copyright:: Copyright 2015-2016, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/crypto" if Chef::Platform.windows? class Chef::Util::Powershell class PSCredential def initialize(username, password) @username = username @password = password end def to_psobject "New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))" end alias to_s to_psobject alias to_text to_psobject private def encrypt(str) Chef::ReservedNames::Win32::Crypto.encrypt(str) end end end chef-12.14.60/lib/chef/util/selinux.rb000066400000000000000000000055251276456504500173430ustar00rootroot00000000000000# # Author:: Sean O'Meara # Author:: Kevin Keane # Author:: Lamont Granquist () # # Copyright:: Copyright 2011-2016, Chef Software Inc. # Copyright:: Copyright 2013-2016, North County Tech Center, LLC # # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/mixin/shell_out" require "chef/mixin/which" class Chef class Util # # IMPORTANT: We assume that selinux utilities are installed on an # selinux enabled server. Provisioning an selinux enabled server # without selinux utilities is not supported. # module Selinux include Chef::Mixin::ShellOut include Chef::Mixin::Which # We want to initialize below variables once during a # chef-client run therefore they are class variables. @@selinux_enabled = nil @@restorecon_path = nil @@selinuxenabled_path = nil def selinux_enabled? @@selinux_enabled = check_selinux_enabled? if @@selinux_enabled.nil? @@selinux_enabled end def restore_security_context(file_path, recursive = false) if restorecon_path restorecon_command = recursive ? "#{restorecon_path} -R -r" : "#{restorecon_path} -R" restorecon_command += " \"#{file_path}\"" Chef::Log.debug("Restoring selinux security content with #{restorecon_command}") shell_out!(restorecon_command) else Chef::Log.warn "Can not find 'restorecon' on the system. Skipping selinux security context restore." end end private def restorecon_path @@restorecon_path = which("restorecon") if @@restorecon_path.nil? @@restorecon_path end def selinuxenabled_path @@selinuxenabled_path = which("selinuxenabled") if @@selinuxenabled_path.nil? @@selinuxenabled_path end def check_selinux_enabled? if selinuxenabled_path cmd = shell_out!(selinuxenabled_path, :returns => [0, 1]) case cmd.exitstatus when 1 return false when 0 return true else raise "Unknown exit code from command #{selinuxenabled_path}: #{cmd.exitstatus}" end else # We assume selinux is not enabled if selinux utils are not # installed. return false end end end end end chef-12.14.60/lib/chef/util/threaded_job_queue.rb000066400000000000000000000031141276456504500214620ustar00rootroot00000000000000# Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "thread" class Chef class Util # A simple threaded job queue # # Create a queue: # # queue = ThreadedJobQueue.new # # Add jobs: # # queue << lambda { |lock| foo.the_bar } # # A job is a callable that optionally takes a Mutex instance as its only # parameter. # # Then start processing jobs with +n+ threads: # # queue.process(n) # class ThreadedJobQueue def initialize @queue = Queue.new @lock = Mutex.new end def <<(job) @queue << job end def process(concurrency = 10) workers = (1..concurrency).map do Thread.new do loop do fn = @queue.pop fn.arity == 1 ? fn.call(@lock) : fn.call end end end workers.each { |worker| self << Thread.method(:exit) } workers.each { |worker| worker.join } end end end end chef-12.14.60/lib/chef/util/windows.rb000066400000000000000000000013531276456504500173410ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Util class Windows end end end chef-12.14.60/lib/chef/util/windows/000077500000000000000000000000001276456504500170125ustar00rootroot00000000000000chef-12.14.60/lib/chef/util/windows/net_group.rb000066400000000000000000000043121276456504500213410ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/util/windows" require "chef/win32/net" #wrapper around a subset of the NetGroup* APIs. class Chef::Util::Windows::NetGroup private def groupname @groupname end public def initialize(groupname) @groupname = groupname end def local_get_members begin Chef::ReservedNames::Win32::NetUser.net_local_group_get_members(nil, groupname) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def local_add begin Chef::ReservedNames::Win32::NetUser.net_local_group_add(nil, groupname) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def local_set_members(members) begin Chef::ReservedNames::Win32::NetUser.net_local_group_set_members(nil, groupname, members) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def local_add_members(members) begin Chef::ReservedNames::Win32::NetUser.net_local_group_add_members(nil, groupname, members) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def local_delete_members(members) begin Chef::ReservedNames::Win32::NetUser.net_local_group_del_members(nil, groupname, members) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def local_delete begin Chef::ReservedNames::Win32::NetUser.net_local_group_del(nil, groupname) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end end chef-12.14.60/lib/chef/util/windows/net_use.rb000066400000000000000000000042041276456504500210010ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # #the Win32 Volume APIs do not support mapping network drives. not supported by WMI either. #see also: WNetAddConnection2 and WNetAddConnection3 #see also cmd.exe: net use /? require "chef/util/windows" require "chef/win32/net" class Chef::Util::Windows::NetUse < Chef::Util::Windows def initialize(localname) @use_name = localname end def to_ui2_struct(use_info) use_info.inject({}) do |memo, (k, v)| memo["ui2_#{k}".to_sym] = v memo end end def add(args) if args.class == String remote = args args = Hash.new args[:remote] = remote end args[:local] ||= use_name ui2_hash = to_ui2_struct(args) begin Chef::ReservedNames::Win32::Net.net_use_add_l2(nil, ui2_hash) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def from_use_info_struct(ui2_hash) ui2_hash.inject({}) do |memo, (k, v)| memo[k.to_s.sub("ui2_", "").to_sym] = v memo end end def get_info begin ui2 = Chef::ReservedNames::Win32::Net.net_use_get_info_l2(nil, use_name) from_use_info_struct(ui2) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def device get_info()[:remote] end def delete begin Chef::ReservedNames::Win32::Net.net_use_del(nil, use_name, :use_noforce) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def use_name @use_name end end chef-12.14.60/lib/chef/util/windows/net_user.rb000066400000000000000000000115571276456504500211740ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/util/windows" require "chef/exceptions" require "chef/win32/net" require "chef/win32/security" #wrapper around a subset of the NetUser* APIs. #nothing Chef specific, but not complete enough to be its own gem, so util for now. class Chef::Util::Windows::NetUser < Chef::Util::Windows private NetUser = Chef::ReservedNames::Win32::NetUser Security = Chef::ReservedNames::Win32::Security USER_INFO_3_TRANSFORM = { name: :usri3_name, password: :usri3_password, password_age: :usri3_password_age, priv: :usri3_priv, home_dir: :usri3_home_dir, comment: :usri3_comment, flags: :usri3_flags, script_path: :usri3_script_path, auth_flags: :usri3_auth_flags, full_name: :usri3_full_name, user_comment: :usri3_usr_comment, parms: :usri3_parms, workstations: :usri3_workstations, last_logon: :usri3_last_logon, last_logoff: :usri3_last_logoff, acct_expires: :usri3_acct_expires, max_storage: :usri3_max_storage, units_per_week: :usri3_units_per_week, logon_hours: :usri3_logon_hours, bad_pw_count: :usri3_bad_pw_count, num_logons: :usri3_num_logons, logon_server: :usri3_logon_server, country_code: :usri3_country_code, code_page: :usri3_code_page, user_id: :usri3_user_id, primary_group_id: :usri3_primary_group_id, profile: :usri3_profile, home_dir_drive: :usri3_home_dir_drive, password_expired: :usri3_password_expired, } def transform_usri3(args) args.inject({}) do |memo, (k, v)| memo[USER_INFO_3_TRANSFORM[k]] = v memo end end def usri3_to_hash(usri3) t = USER_INFO_3_TRANSFORM.invert usri3.inject({}) do |memo, (k, v)| memo[t[k]] = v memo end end def set_info(args) begin rc = NetUser.net_user_set_info_l3(nil, @username, transform_usri3(args)) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end public def initialize(username) @username = username end LOGON32_PROVIDER_DEFAULT = Security::LOGON32_PROVIDER_DEFAULT LOGON32_LOGON_NETWORK = Security::LOGON32_LOGON_NETWORK #XXX for an extra painful alternative, see: http://support.microsoft.com/kb/180548 def validate_credentials(passwd) begin token = Security.logon_user(@username, nil, passwd, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT) return true rescue Chef::Exceptions::Win32APIError return false end end def get_info begin ui3 = NetUser.net_user_get_info_l3(nil, @username) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end usri3_to_hash(ui3) end def add(args) transformed_args = transform_usri3(args) NetUser.net_user_add_l3(nil, transformed_args) NetUser.net_local_group_add_member(nil, "Users", args[:name]) end # FIXME: yard with @yield def user_modify user = get_info user[:last_logon] = user[:units_per_week] = 0 #ignored as per USER_INFO_3 doc user[:logon_hours] = nil #PBYTE field; \0 == no changes yield(user) set_info(user) end def update(args) user_modify do |user| args.each do |key, val| user[key] = val end end end def delete begin NetUser.net_user_del(nil, @username) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def disable_account user_modify do |user| user[:flags] |= NetUser::UF_ACCOUNTDISABLE #This does not set the password to nil. It (for some reason) means to ignore updating the field. #See similar behavior for the logon_hours field documented at #http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx user[:password] = nil end end def enable_account user_modify do |user| user[:flags] &= ~NetUser::UF_ACCOUNTDISABLE #This does not set the password to nil. It (for some reason) means to ignore updating the field. #See similar behavior for the logon_hours field documented at #http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx user[:password] = nil end end def check_enabled (get_info()[:flags] & NetUser::UF_ACCOUNTDISABLE) != 0 end end chef-12.14.60/lib/chef/util/windows/volume.rb000066400000000000000000000032331276456504500206470ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # #simple wrapper around Volume APIs. might be possible with WMI, but possibly more complex. require "chef/win32/api/file" require "chef/util/windows" class Chef::Util::Windows::Volume < Chef::Util::Windows attr_reader :mount_point def initialize(name) name += "\\" unless name =~ /\\$/ #trailing slash required @mount_point = name end def device begin Chef::ReservedNames::Win32::File.get_volume_name_for_volume_mount_point(mount_point) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def delete begin Chef::ReservedNames::Win32::File.delete_volume_mount_point(mount_point) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def add(args) begin Chef::ReservedNames::Win32::File.set_volume_mount_point(mount_point, args[:remote]) rescue Chef::Exceptions::Win32APIError => e raise ArgumentError, e end end def mount_point @mount_point end end chef-12.14.60/lib/chef/version.rb000066400000000000000000000025451276456504500163630ustar00rootroot00000000000000# Copyright:: Copyright 2010-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. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # NOTE: This file is generated by running `rake version` in the top level of # this repo. Do not edit this manually. Edit the VERSION file and run the rake # task instead. #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! class Chef CHEF_ROOT = File.expand_path("../..", __FILE__) VERSION = "12.14.60" end # # NOTE: the Chef::Version class is defined in version_class.rb # # NOTE: DO NOT Use the Chef::Version class on Chef::VERSIONs. The # Chef::Version class is for _cookbooks_ only, and cannot handle # pre-release versions like "10.14.0.rc.2". Please use Rubygem's # Gem::Version class instead. # chef-12.14.60/lib/chef/version/000077500000000000000000000000001276456504500160305ustar00rootroot00000000000000chef-12.14.60/lib/chef/version/platform.rb000066400000000000000000000025651276456504500202110ustar00rootroot00000000000000# Author:: Xabier de Zuazo () # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/version_class" class Chef class Version class Platform < Chef::Version protected def parse(str = "") @major, @minor, @patch = case str.to_s when /^(\d+)\.(\d+)\.(\d+)$/ [ $1.to_i, $2.to_i, $3.to_i ] when /^(\d+)\.(\d+)$/ [ $1.to_i, $2.to_i, 0 ] when /^(\d+)$/ [ $1.to_i, 0, 0 ] when /^(\d+).(\d+)-[a-z]+\d?(-p(\d+))?$/i # Match FreeBSD [ $1.to_i, $2.to_i, ($4 ? $4.to_i : 0)] else msg = "'#{str}' does not match 'x.y.z', 'x.y' or 'x'" raise Chef::Exceptions::InvalidPlatformVersion.new( msg ) end end end end end chef-12.14.60/lib/chef/version_class.rb000066400000000000000000000036511276456504500175470ustar00rootroot00000000000000# Author:: Seth Falcon () # Author:: Christopher Walters () # Copyright:: Copyright 2010-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 Version include Comparable attr_reader :major, :minor, :patch def initialize(str = "") parse(str) end def inspect "#{@major}.#{@minor}.#{@patch}" end def to_s "#{@major}.#{@minor}.#{@patch}" end def <=>(other) [:major, :minor, :patch].each do |method| version = self.send(method) begin ans = (version <=> other.send(method)) rescue NoMethodError # if the other thing isn't a version object, return nil return nil end return ans unless ans == 0 end 0 end def hash # Didn't put any thought or research into this, probably can be # done better to_s.hash end # For hash def eql?(other) other.is_a?(Version) && self == other end protected def parse(str = "") @major, @minor, @patch = case str.to_s when /^(\d+)\.(\d+)\.(\d+)$/ [ $1.to_i, $2.to_i, $3.to_i ] when /^(\d+)\.(\d+)$/ [ $1.to_i, $2.to_i, 0 ] else msg = "'#{str}' does not match 'x.y.z' or 'x.y'" raise Chef::Exceptions::InvalidCookbookVersion.new( msg ) end end end end chef-12.14.60/lib/chef/version_constraint.rb000066400000000000000000000070041276456504500206220ustar00rootroot00000000000000# Author:: Seth Falcon () # Author:: Christopher Walters () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/version_class" class Chef class VersionConstraint DEFAULT_CONSTRAINT = ">= 0.0.0" STANDARD_OPS = %w{< > <= >=} OPS = %w{< > = <= >= ~>} PATTERN = /^(#{OPS.join('|')}) *([0-9].*)$/ VERSION_CLASS = Chef::Version attr_reader :op, :version def initialize(constraint_spec = DEFAULT_CONSTRAINT) case constraint_spec when nil parse(DEFAULT_CONSTRAINT) when Array parse_from_array(constraint_spec) when String parse(constraint_spec) else msg = "VersionConstraint should be created from a String. You gave: #{constraint_spec.inspect}" raise Chef::Exceptions::InvalidVersionConstraint, msg end end def include?(v) version = if v.respond_to? :version # a CookbookVersion-like object self.class::VERSION_CLASS.new(v.version.to_s) else self.class::VERSION_CLASS.new(v.to_s) end do_op(version) end def inspect "(#{self})" end def to_s "#{@op} #{@raw_version}" end def eql?(other) other.class == self.class && @op == other.op && @version == other.version end alias_method :==, :eql? private def do_op(other_version) if STANDARD_OPS.include? @op other_version.send(@op.to_sym, @version) elsif @op == "=" other_version == @version elsif @op == "~>" if @missing_patch_level (other_version.major == @version.major && other_version.minor >= @version.minor) else (other_version.major == @version.major && other_version.minor == @version.minor && other_version.patch >= @version.patch) end else # should never happen raise "bad op #{@op}" end end def parse_from_array(constraint_spec) if constraint_spec.empty? parse(DEFAULT_CONSTRAINT) elsif constraint_spec.size == 1 parse(constraint_spec.first) else msg = "only one version constraint operation is supported, but you gave #{constraint_spec.size} " msg << "['#{constraint_spec.join(', ')}']" raise Chef::Exceptions::InvalidVersionConstraint, msg end end def parse(str) @missing_patch_level = false if str.index(" ").nil? && str =~ /^[0-9]/ # try for lone version, implied '=' @raw_version = str @version = self.class::VERSION_CLASS.new(@raw_version) @op = "=" elsif PATTERN.match str @op = $1 @raw_version = $2 @version = self.class::VERSION_CLASS.new(@raw_version) if @raw_version.split(".").size <= 2 @missing_patch_level = true end else raise Chef::Exceptions::InvalidVersionConstraint, "'#{str}'" end end end end chef-12.14.60/lib/chef/version_constraint/000077500000000000000000000000001276456504500202745ustar00rootroot00000000000000chef-12.14.60/lib/chef/version_constraint/platform.rb000066400000000000000000000016031276456504500224450ustar00rootroot00000000000000# Author:: Xabier de Zuazo () # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "chef/version_constraint" require "chef/version/platform" class Chef class VersionConstraint class Platform < Chef::VersionConstraint VERSION_CLASS = Chef::Version::Platform end end end chef-12.14.60/lib/chef/whitelist.rb000066400000000000000000000045761276456504500167200ustar00rootroot00000000000000 require "chef/exceptions" class Chef class Whitelist # filter takes two arguments - the data you want to filter, and a whitelisted array # of keys you want included. You can capture a subtree of the data to filter by # providing a "/"-delimited string of keys. If some key includes "/"-characters, # you must provide an array of keys instead. # # Whitelist.filter( # { "filesystem" => { # "/dev/disk" => { # "size" => "10mb" # }, # "map - autohome" => { # "size" => "10mb" # } # }, # "network" => { # "interfaces" => { # "eth0" => {...}, # "eth1" => {...} # } # } # }, # ["network/interfaces/eth0", ["filesystem", "/dev/disk"]]) # will capture the eth0 and /dev/disk subtrees. def self.filter(data, whitelist = nil) return data if whitelist.nil? new_data = {} whitelist.each do |item| add_data(data, new_data, item) end new_data end # Walk the data has according to the keys provided by the whitelisted item # and add the data to the whitelisting result. def self.add_data(data, new_data, item) parts = to_array(item) all_data = data filtered_data = new_data parts[0..-2].each do |part| unless all_data[part] Chef::Log.warn("Could not find whitelist attribute #{item}.") return nil end filtered_data[part] ||= {} filtered_data = filtered_data[part] all_data = all_data[part] end # Note: You can't do all_data[parts[-1]] here because the value # may be false-y unless all_data.key?(parts[-1]) Chef::Log.warn("Could not find whitelist attribute #{item}.") return nil end filtered_data[parts[-1]] = all_data[parts[-1]] new_data end private_class_method :add_data # Accepts a String or an Array, and returns an Array of String keys that # are used to traverse the data hash. Strings are split on "/", Arrays are # assumed to contain exact keys (that is, Array elements will not be split # by "/"). def self.to_array(item) return item if item.kind_of? Array parts = item.split("/") parts.shift if !parts.empty? && parts[0].empty? parts end private_class_method :to_array end end chef-12.14.60/lib/chef/win32/000077500000000000000000000000001276456504500153055ustar00rootroot00000000000000chef-12.14.60/lib/chef/win32/api.rb000066400000000000000000000552631276456504500164160ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "ffi" require "chef/reserved_names" require "chef/exceptions" class Chef module ReservedNames::Win32 module API # Attempts to use FFI's attach_function method to link a native Win32 # function into the calling module. If this fails a dummy method is # defined which when called, raises a helpful exception to the end-user. def safe_attach_function(win32_func, *args) begin attach_function(win32_func.to_sym, *args) rescue FFI::NotFoundError define_method(win32_func.to_sym) do |*margs| raise Chef::Exceptions::Win32APIFunctionNotImplemented, "This version of Windows does not implement the Win32 function [#{win32_func}]." end end end # put shared stuff (like constants) for all raw Win32 API calls def self.extended(host) host.extend FFI::Library host.extend Macros host.ffi_convention :stdcall # Windows-specific type defs (ms-help://MS.MSDNQTR.v90.en/winprog/winprog/windows_data_types.htm): host.typedef :ushort, :ATOM # Atom ~= Symbol: Atom table stores strings and corresponding identifiers. Application # places a string in an atom table and receives a 16-bit integer, called an atom, that # can be used to access the string. Placed string is called an atom name. # See: http://msdn.microsoft.com/en-us/library/ms648708%28VS.85%29.aspx host.typedef :bool, :BOOL host.typedef :bool, :BOOLEAN host.typedef :uchar, :BYTE # Byte (8 bits). Declared as unsigned char #CALLBACK: K, # Win32.API gem-specific ?? MSDN: #define CALLBACK __stdcall host.typedef :char, :CHAR # 8-bit Windows (ANSI) character. See http://msdn.microsoft.com/en-us/library/dd183415%28VS.85%29.aspx host.typedef :uint32, :COLORREF # Red, green, blue (RGB) color value (32 bits). See COLORREF for more info. host.typedef :uint32, :DWORD # 32-bit unsigned integer. The range is 0 through 4,294,967,295 decimal. host.typedef :uint64, :DWORDLONG # 64-bit unsigned integer. The range is 0 through 18,446,744,073,709,551,615 decimal. host.typedef :ulong, :DWORD_PTR # Unsigned long type for pointer precision. Use when casting a pointer to a long type # to perform pointer arithmetic. (Also commonly used for general 32-bit parameters that have # been extended to 64 bits in 64-bit Windows.) BaseTsd.h: #host.typedef ULONG_PTR DWORD_PTR; host.typedef :uint32, :DWORD32 host.typedef :uint64, :DWORD64 host.typedef :int, :HALF_PTR # Half the size of a pointer. Use within a structure that contains a pointer and two small fields. # BaseTsd.h: #ifdef (_WIN64) host.typedef int HALF_PTR; #else host.typedef short HALF_PTR; host.typedef :ulong, :HACCEL # (L) Handle to an accelerator table. WinDef.h: #host.typedef HANDLE HACCEL; # See http://msdn.microsoft.com/en-us/library/ms645526%28VS.85%29.aspx host.typedef :size_t, :HANDLE # (L) Handle to an object. WinNT.h: #host.typedef PVOID HANDLE; # todo: Platform-dependent! Need to change to :uint64 for Win64 host.typedef :ulong, :HBITMAP # (L) Handle to a bitmap: http://msdn.microsoft.com/en-us/library/dd183377%28VS.85%29.aspx host.typedef :ulong, :HBRUSH # (L) Handle to a brush. http://msdn.microsoft.com/en-us/library/dd183394%28VS.85%29.aspx host.typedef :ulong, :HCOLORSPACE # (L) Handle to a color space. http://msdn.microsoft.com/en-us/library/ms536546%28VS.85%29.aspx host.typedef :ulong, :HCURSOR # (L) Handle to a cursor. http://msdn.microsoft.com/en-us/library/ms646970%28VS.85%29.aspx host.typedef :ulong, :HCONV # (L) Handle to a dynamic data exchange (DDE) conversation. host.typedef :ulong, :HCONVLIST # (L) Handle to a DDE conversation list. HANDLE - L ? host.typedef :ulong, :HDDEDATA # (L) Handle to DDE data (structure?) host.typedef :ulong, :HDC # (L) Handle to a device context (DC). http://msdn.microsoft.com/en-us/library/dd183560%28VS.85%29.aspx host.typedef :ulong, :HDESK # (L) Handle to a desktop. http://msdn.microsoft.com/en-us/library/ms682573%28VS.85%29.aspx host.typedef :ulong, :HDROP # (L) Handle to an internal drop structure. host.typedef :ulong, :HDWP # (L) Handle to a deferred window position structure. host.typedef :ulong, :HENHMETAFILE #(L) Handle to an enhanced metafile. http://msdn.microsoft.com/en-us/library/dd145051%28VS.85%29.aspx host.typedef :uint, :HFILE # (I) Special file handle to a file opened by OpenFile, not CreateFile. # WinDef.h: #host.typedef int HFILE; host.typedef :ulong, :HFONT # (L) Handle to a font. http://msdn.microsoft.com/en-us/library/dd162470%28VS.85%29.aspx host.typedef :ulong, :HGDIOBJ # (L) Handle to a GDI object. host.typedef :ulong, :HGLOBAL # (L) Handle to a global memory block. host.typedef :ulong, :HHOOK # (L) Handle to a hook. http://msdn.microsoft.com/en-us/library/ms632589%28VS.85%29.aspx host.typedef :ulong, :HICON # (L) Handle to an icon. http://msdn.microsoft.com/en-us/library/ms646973%28VS.85%29.aspx host.typedef :ulong, :HINSTANCE # (L) Handle to an instance. This is the base address of the module in memory. # HMODULE and HINSTANCE are the same today, but were different in 16-bit Windows. host.typedef :ulong, :HKEY # (L) Handle to a registry key. host.typedef :ulong, :HKL # (L) Input locale identifier. host.typedef :ulong, :HLOCAL # (L) Handle to a local memory block. host.typedef :ulong, :HMENU # (L) Handle to a menu. http://msdn.microsoft.com/en-us/library/ms646977%28VS.85%29.aspx host.typedef :ulong, :HMETAFILE # (L) Handle to a metafile. http://msdn.microsoft.com/en-us/library/dd145051%28VS.85%29.aspx host.typedef :ulong, :HMODULE # (L) Handle to an instance. Same as HINSTANCE today, but was different in 16-bit Windows. host.typedef :ulong, :HMONITOR # (L) Рandle to a display monitor. WinDef.h: if(WINVER >= 0x0500) host.typedef HANDLE HMONITOR; host.typedef :ulong, :HPALETTE # (L) Handle to a palette. host.typedef :ulong, :HPEN # (L) Handle to a pen. http://msdn.microsoft.com/en-us/library/dd162786%28VS.85%29.aspx host.typedef :long, :HRESULT # Return code used by COM interfaces. For more info, Structure of the COM Error Codes. # To test an HRESULT value, use the FAILED and SUCCEEDED macros. host.typedef :ulong, :HRGN # (L) Handle to a region. http://msdn.microsoft.com/en-us/library/dd162913%28VS.85%29.aspx host.typedef :ulong, :HRSRC # (L) Handle to a resource. host.typedef :ulong, :HSZ # (L) Handle to a DDE string. host.typedef :ulong, :HWINSTA # (L) Handle to a window station. http://msdn.microsoft.com/en-us/library/ms687096%28VS.85%29.aspx host.typedef :ulong, :HWND # (L) Handle to a window. http://msdn.microsoft.com/en-us/library/ms632595%28VS.85%29.aspx host.typedef :int, :INT # 32-bit signed integer. The range is -2147483648 through 2147483647 decimal. host.typedef :int, :INT_PTR # Signed integer type for pointer precision. Use when casting a pointer to an integer # to perform pointer arithmetic. BaseTsd.h: #if defined(_WIN64) host.typedef __int64 INT_PTR; #else host.typedef int INT_PTR; host.typedef :int32, :INT32 # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal. host.typedef :int64, :INT64 # 64-bit signed integer. The range is –9,223,372,036,854,775,808 through +...807 host.typedef :ushort, :LANGID # Language identifier. For more information, see Locales. WinNT.h: #host.typedef WORD LANGID; # See http://msdn.microsoft.com/en-us/library/dd318716%28VS.85%29.aspx host.typedef :uint32, :LCID # Locale identifier. For more information, see Locales. host.typedef :uint32, :LCTYPE # Locale information type. For a list, see Locale Information Constants. host.typedef :uint32, :LGRPID # Language group identifier. For a list, see EnumLanguageGroupLocales. host.typedef :pointer, :LMSTR # Pointer to null termiated string of unicode characters host.typedef :long, :LONG # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal. host.typedef :int32, :LONG32 # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal. host.typedef :int64, :LONG64 # 64-bit signed integer. The range is –9,223,372,036,854,775,808 through +...807 host.typedef :int64, :LONGLONG # 64-bit signed integer. The range is –9,223,372,036,854,775,808 through +...807 host.typedef :long, :LONG_PTR # Signed long type for pointer precision. Use when casting a pointer to a long to # perform pointer arithmetic. BaseTsd.h: #if defined(_WIN64) host.typedef __int64 LONG_PTR; #else host.typedef long LONG_PTR; host.typedef :long, :LPARAM # Message parameter. WinDef.h as follows: #host.typedef LONG_PTR LPARAM; host.typedef :pointer, :LPBOOL # Pointer to a BOOL. WinDef.h as follows: #host.typedef BOOL far *LPBOOL; host.typedef :pointer, :LPBYTE # Pointer to a BYTE. WinDef.h as follows: #host.typedef BYTE far *LPBYTE; host.typedef :pointer, :LPCOLORREF # Pointer to a COLORREF value. WinDef.h as follows: #host.typedef DWORD *LPCOLORREF; host.typedef :pointer, :LPCSTR # Pointer to a constant null-terminated string of 8-bit Windows (ANSI) characters. # See Character Sets Used By Fonts. http://msdn.microsoft.com/en-us/library/dd183415%28VS.85%29.aspx host.typedef :pointer, :LPCTSTR # An LPCWSTR if UNICODE is defined, an LPCSTR otherwise. host.typedef :pointer, :LPCVOID # Pointer to a constant of any type. WinDef.h as follows: host.typedef CONST void *LPCVOID; host.typedef :pointer, :LPCWSTR # Pointer to a constant null-terminated string of 16-bit Unicode characters. host.typedef :pointer, :LPDWORD # Pointer to a DWORD. WinDef.h as follows: host.typedef DWORD *LPDWORD; host.typedef :pointer, :LPHANDLE # Pointer to a HANDLE. WinDef.h as follows: host.typedef HANDLE *LPHANDLE; host.typedef :pointer, :LPINT # Pointer to an INT. host.typedef :pointer, :LPLONG # Pointer to an LONG. host.typedef :pointer, :LPSECURITY_ATTRIBUTES # Pointer to SECURITY_ATTRIBUTES struct host.typedef :pointer, :LPSTR # Pointer to a null-terminated string of 8-bit Windows (ANSI) characters. host.typedef :pointer, :LPTSTR # An LPWSTR if UNICODE is defined, an LPSTR otherwise. host.typedef :pointer, :LPVOID # Pointer to any type. host.typedef :pointer, :LPWORD # Pointer to a WORD. host.typedef :pointer, :LPWSTR # Pointer to a null-terminated string of 16-bit Unicode characters. host.typedef :long, :LRESULT # Signed result of message processing. WinDef.h: host.typedef LONG_PTR LRESULT; host.typedef :pointer, :LPWIN32_FIND_DATA # Pointer to WIN32_FIND_DATA struct host.typedef :pointer, :LPBY_HANDLE_FILE_INFORMATION # Point to a BY_HANDLE_FILE_INFORMATION struct host.typedef :pointer, :LSA_HANDLE # A handle to a Policy object host.typedef :ulong, :NTSTATUS # An NTSTATUS code returned by an LSA function call. host.typedef :pointer, :PBOOL # Pointer to a BOOL. host.typedef :pointer, :PBOOLEAN # Pointer to a BOOL. host.typedef :pointer, :PBYTE # Pointer to a BYTE. host.typedef :pointer, :PCHAR # Pointer to a CHAR. host.typedef :pointer, :PCSTR # Pointer to a constant null-terminated string of 8-bit Windows (ANSI) characters. host.typedef :pointer, :PCTSTR # A PCWSTR if UNICODE is defined, a PCSTR otherwise. host.typedef :pointer, :PCWSTR # Pointer to a constant null-terminated string of 16-bit Unicode characters. host.typedef :pointer, :PDWORD # Pointer to a DWORD. host.typedef :pointer, :PDWORDLONG # Pointer to a DWORDLONG. host.typedef :pointer, :PDWORD_PTR # Pointer to a DWORD_PTR. host.typedef :pointer, :PDWORD32 # Pointer to a DWORD32. host.typedef :pointer, :PDWORD64 # Pointer to a DWORD64. host.typedef :pointer, :PFLOAT # Pointer to a FLOAT. host.typedef :pointer, :PGENERICMAPPING #Pointer to GENERIC_MAPPING host.typedef :pointer, :PHALF_PTR # Pointer to a HALF_PTR. host.typedef :pointer, :PHANDLE # Pointer to a HANDLE. host.typedef :pointer, :PHKEY # Pointer to an HKEY. host.typedef :pointer, :PINT # Pointer to an INT. host.typedef :pointer, :PINT_PTR # Pointer to an INT_PTR. host.typedef :pointer, :PINT32 # Pointer to an INT32. host.typedef :pointer, :PINT64 # Pointer to an INT64. host.typedef :pointer, :PLCID # Pointer to an LCID. host.typedef :pointer, :PLONG # Pointer to a LONG. host.typedef :pointer, :PLONGLONG # Pointer to a LONGLONG. host.typedef :pointer, :PLONG_PTR # Pointer to a LONG_PTR. host.typedef :pointer, :PLONG32 # Pointer to a LONG32. host.typedef :pointer, :PLONG64 # Pointer to a LONG64. host.typedef :pointer, :PLSA_HANDLE # Pointer to an LSA_HANDLE host.typedef :pointer, :PLSA_OBJECT_ATTRIBUTES # Pointer to an LSA_OBJECT_ATTRIBUTES host.typedef :pointer, :PLSA_UNICODE_STRING # Pointer to LSA_UNICODE_STRING host.typedef :pointer, :PLUID # Pointer to a LUID. host.typedef :pointer, :POINTER_32 # 32-bit pointer. On a 32-bit system, this is a native pointer. On a 64-bit system, this is a truncated 64-bit pointer. host.typedef :pointer, :POINTER_64 # 64-bit pointer. On a 64-bit system, this is a native pointer. On a 32-bit system, this is a sign-extended 32-bit pointer. host.typedef :pointer, :POINTER_SIGNED # A signed pointer. host.typedef :pointer, :POINTER_UNSIGNED # An unsigned pointer. host.typedef :pointer, :PSHORT # Pointer to a SHORT. host.typedef :pointer, :PSID # Pointer to an account SID host.typedef :pointer, :PSIZE_T # Pointer to a SIZE_T. host.typedef :pointer, :PSSIZE_T # Pointer to a SSIZE_T. host.typedef :pointer, :PSTR # Pointer to a null-terminated string of 8-bit Windows (ANSI) characters. For more information, see Character Sets Used By Fonts. host.typedef :pointer, :PTBYTE # Pointer to a TBYTE. host.typedef :pointer, :PTCHAR # Pointer to a TCHAR. host.typedef :pointer, :PCRYPTPROTECT_PROMPTSTRUCT # Pointer to a CRYPTOPROTECT_PROMPTSTRUCT. host.typedef :pointer, :PDATA_BLOB # Pointer to a DATA_BLOB. host.typedef :pointer, :PTSTR # A PWSTR if UNICODE is defined, a PSTR otherwise. host.typedef :pointer, :PUCHAR # Pointer to a UCHAR. host.typedef :pointer, :PUHALF_PTR # Pointer to a UHALF_PTR. host.typedef :pointer, :PUINT # Pointer to a UINT. host.typedef :pointer, :PUINT_PTR # Pointer to a UINT_PTR. host.typedef :pointer, :PUINT32 # Pointer to a UINT32. host.typedef :pointer, :PUINT64 # Pointer to a UINT64. host.typedef :pointer, :PULONG # Pointer to a ULONG. host.typedef :pointer, :PULONGLONG # Pointer to a ULONGLONG. host.typedef :pointer, :PULONG_PTR # Pointer to a ULONG_PTR. host.typedef :pointer, :PULONG32 # Pointer to a ULONG32. host.typedef :pointer, :PULONG64 # Pointer to a ULONG64. host.typedef :pointer, :PUSHORT # Pointer to a USHORT. host.typedef :pointer, :PVOID # Pointer to any type. host.typedef :pointer, :PWCHAR # Pointer to a WCHAR. host.typedef :pointer, :PWORD # Pointer to a WORD. host.typedef :pointer, :PWSTR # Pointer to a null- terminated string of 16-bit Unicode characters. # For more information, see Character Sets Used By Fonts. host.typedef :ulong, :SC_HANDLE # (L) Handle to a service control manager database. # See SCM Handles http://msdn.microsoft.com/en-us/library/ms685104%28VS.85%29.aspx host.typedef :pointer, :SC_LOCK # Lock to a service control manager database. For more information, see SCM Handles. host.typedef :ulong, :SERVICE_STATUS_HANDLE # (L) Handle to a service status value. See SCM Handles. host.typedef :short, :SHORT # A 16-bit integer. The range is –32768 through 32767 decimal. host.typedef :ulong, :SIZE_T # The maximum number of bytes to which a pointer can point. Use for a count that must span the full range of a pointer. host.typedef :long, :SSIZE_T # Signed SIZE_T. host.typedef :char, :TBYTE # A WCHAR if UNICODE is defined, a CHAR otherwise.TCHAR: # http://msdn.microsoft.com/en-us/library/c426s321%28VS.80%29.aspx host.typedef :char, :TCHAR # A WCHAR if UNICODE is defined, a CHAR otherwise.TCHAR: host.typedef :uchar, :UCHAR # Unsigned CHAR (8 bit) host.typedef :uint, :UHALF_PTR # Unsigned HALF_PTR. Use within a structure that contains a pointer and two small fields. host.typedef :uint, :UINT # Unsigned INT. The range is 0 through 4294967295 decimal. host.typedef :uint, :UINT_PTR # Unsigned INT_PTR. host.typedef :uint32, :UINT32 # Unsigned INT32. The range is 0 through 4294967295 decimal. host.typedef :uint64, :UINT64 # Unsigned INT64. The range is 0 through 18446744073709551615 decimal. host.typedef :ulong, :ULONG # Unsigned LONG. The range is 0 through 4294967295 decimal. host.typedef :ulong_long, :ULONGLONG # 64-bit unsigned integer. The range is 0 through 18446744073709551615 decimal. host.typedef :ulong, :ULONG_PTR # Unsigned LONG_PTR. host.typedef :uint32, :ULONG32 # Unsigned INT32. The range is 0 through 4294967295 decimal. host.typedef :uint64, :ULONG64 # Unsigned LONG64. The range is 0 through 18446744073709551615 decimal. host.typedef :pointer, :UNICODE_STRING # Pointer to some string structure?? host.typedef :ushort, :USHORT # Unsigned SHORT. The range is 0 through 65535 decimal. host.typedef :ulong_long, :USN # Update sequence number (USN). host.typedef :ushort, :WCHAR # 16-bit Unicode character. For more information, see Character Sets Used By Fonts. # In WinNT.h: host.typedef wchar_t WCHAR; #WINAPI: K, # Calling convention for system functions. WinDef.h: define WINAPI __stdcall host.typedef :ushort, :WORD # 16-bit unsigned integer. The range is 0 through 65535 decimal. host.typedef :uint, :WPARAM # Message parameter. WinDef.h as follows: host.typedef UINT_PTR WPARAM; end module Macros ############################################### # winbase.h ############################################### def LocalDiscard(pointer) LocalReAlloc(pointer, 0, LMEM_MOVEABLE) end ############################################### # windef.h ############################################### # Creates a WORD value by concatenating the specified values. # # http://msdn.microsoft.com/en-us/library/windows/desktop/ms632663(v=VS.85).aspx def MAKEWORD(low, high) ((low & 0xff) | (high & 0xff)) << 8 end # Creates a LONG value by concatenating the specified values. # # http://msdn.microsoft.com/en-us/library/windows/desktop/ms632660(v=vs.85).aspx def MAKELONG(low, high) ((low & 0xffff) | (high & 0xffff)) << 16 end # Retrieves the low-order word from the specified value. # # http://msdn.microsoft.com/en-us/library/windows/desktop/ms632659(v=VS.85).aspx def LOWORD(l) l & 0xffff end # Retrieves the high-order word from the specified 32-bit value. # # http://msdn.microsoft.com/en-us/library/windows/desktop/ms632657(v=VS.85).aspx def HIWORD(l) l >> 16 end # Retrieves the low-order byte from the specified value. # # http://msdn.microsoft.com/en-us/library/windows/desktop/ms632658(v=VS.85).aspx def LOBYTE(w) w & 0xff end # Retrieves the high-order byte from the given 16-bit value. # # http://msdn.microsoft.com/en-us/library/windows/desktop/ms632656(v=VS.85).aspx def HIBYTE(w) w >> 8 end ############################################### # winerror.h ############################################### def IS_ERROR(status) status >> 31 == 1 end def MAKE_HRESULT(sev, fac, code) sev << 31 | fac << 16 | code end def MAKE_SCODE(sev, fac, code) sev << 31 | fac << 16 | code end def HRESULT_CODE(hr) hr & 0xFFFF end def HRESULT_FACILITY(hr) (hr >> 16) & 0x1fff end def HRESULT_FROM_NT(x) x | 0x10000000 # FACILITY_NT_BIT end def HRESULT_FROM_WIN32(x) if x <= 0 x else (x & 0x0000FFFF) | (7 << 16) | 0x80000000 end end def HRESULT_SEVERITY(hr) (hr >> 31) & 0x1 end def FAILED(status) status < 0 end def SUCCEEDED(status) status >= 0 end end # Represents a 64-bit unsigned integer value. # # http://msdn.microsoft.com/en-us/library/windows/desktop/aa383742(v=vs.85).aspx def make_uint64(low, high) low + (high * (2**32)) end # http://blogs.msdn.com/b/oldnewthing/archive/2009/03/06/9461176.aspx # January 1, 1601 WIN32_EPOC_MINUS_POSIX_EPOC = 116444736000000000 # Convert 64-bit FILETIME integer into Time object. # # FILETIME structure contains a 64-bit value representing the number # of 100-nanosecond intervals since January 1, 1601 (UTC). # # http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx # def wtime_to_time(wtime) Time.at((wtime - WIN32_EPOC_MINUS_POSIX_EPOC) / 10000000) end end end end chef-12.14.60/lib/chef/win32/api/000077500000000000000000000000001276456504500160565ustar00rootroot00000000000000chef-12.14.60/lib/chef/win32/api/crypto.rb000066400000000000000000000034041276456504500177240ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" class Chef module ReservedNames::Win32 module API module Crypto extend Chef::ReservedNames::Win32::API ############################################### # Win32 API Bindings ############################################### ffi_lib "Crypt32" CRYPTPROTECT_UI_FORBIDDEN = 0x1 CRYPTPROTECT_LOCAL_MACHINE = 0x4 CRYPTPROTECT_AUDIT = 0x10 class CRYPT_INTEGER_BLOB < FFI::Struct layout :cbData, :DWORD, # Count, in bytes, of data :pbData, :pointer # Pointer to data buffer def initialize(str = nil) super(nil) if str self[:pbData] = FFI::MemoryPointer.from_string(str) self[:cbData] = str.bytesize end end end safe_attach_function :CryptProtectData, [ :PDATA_BLOB, :LPCWSTR, :PDATA_BLOB, :pointer, :PCRYPTPROTECT_PROMPTSTRUCT, :DWORD, :PDATA_BLOB, ], :BOOL end end end end chef-12.14.60/lib/chef/win32/api/error.rb000066400000000000000000001323301276456504500175360ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" class Chef module ReservedNames::Win32 module API module Error extend Chef::ReservedNames::Win32::API ############################################### # Win32 API Constants ############################################### S_OK = 0 NO_ERROR = 0 ERROR_SUCCESS = 0 ERROR_INVALID_FUNCTION = 1 ERROR_FILE_NOT_FOUND = 2 ERROR_PATH_NOT_FOUND = 3 ERROR_TOO_MANY_OPEN_FILES = 4 ERROR_ACCESS_DENIED = 5 ERROR_INVALID_HANDLE = 6 ERROR_ARENA_TRASHED = 7 ERROR_NOT_ENOUGH_MEMORY = 8 ERROR_INVALID_BLOCK = 9 ERROR_BAD_ENVIRONMENT = 10 ERROR_BAD_FORMAT = 11 ERROR_INVALID_ACCESS = 12 ERROR_INVALID_DATA = 13 ERROR_INVALID_DRIVE = 15 ERROR_CURRENT_DIRECTORY = 16 ERROR_NOT_SAME_DEVICE = 17 ERROR_NO_MORE_FILES = 18 ERROR_WRITE_PROTECT = 19 ERROR_BAD_UNIT = 20 ERROR_NOT_READY = 21 ERROR_BAD_COMMAND = 22 ERROR_CRC = 23 ERROR_BAD_LENGTH = 24 ERROR_SEEK = 25 ERROR_NOT_DOS_DISK = 26 ERROR_SECTOR_NOT_FOUND = 27 ERROR_OUT_OF_PAPER = 28 ERROR_WRITE_FAULT = 29 ERROR_READ_FAULT = 30 ERROR_GEN_FAILURE = 31 ERROR_SHARING_VIOLATION = 32 ERROR_LOCK_VIOLATION = 33 ERROR_WRONG_DISK = 34 ERROR_FCB_UNAVAILABLE = 35 # gets returned for some unsuccessful DeviceIoControl calls ERROR_SHARING_BUFFER_EXCEEDED = 36 ERROR_HANDLE_EOF = 38 ERROR_HANDLE_DISK_FULL = 39 ERROR_NOT_SUPPORTED = 50 ERROR_REM_NOT_LIST = 51 ERROR_DUP_NAME = 52 ERROR_BAD_NETPATH = 53 ERROR_NETWORK_BUSY = 54 ERROR_DEV_NOT_EXIST = 55 ERROR_TOO_MANY_CMDS = 56 ERROR_ADAP_HDW_ERR = 57 ERROR_BAD_NET_RESP = 58 ERROR_UNEXP_NET_ERR = 59 ERROR_BAD_REM_ADAP = 60 ERROR_PRINTQ_FULL = 61 ERROR_NO_SPOOL_SPACE = 62 ERROR_PRINT_CANCELLED = 63 ERROR_NETNAME_DELETED = 64 ERROR_NETWORK_ACCESS_DENIED = 65 ERROR_BAD_DEV_TYPE = 66 ERROR_BAD_NET_NAME = 67 ERROR_TOO_MANY_NAMES = 68 ERROR_TOO_MANY_SESS = 69 ERROR_SHARING_PAUSED = 70 ERROR_REQ_NOT_ACCEP = 71 ERROR_REDIR_PAUSED = 72 ERROR_FILE_EXISTS = 80 ERROR_DUP_FCB = 81 ERROR_CANNOT_MAKE = 82 ERROR_FAIL_I24 = 83 ERROR_OUT_OF_STRUCTURES = 84 ERROR_ALREADY_ASSIGNED = 85 ERROR_INVALID_PASSWORD = 86 ERROR_INVALID_PARAMETER = 87 ERROR_NET_WRITE_FAULT = 88 ERROR_NO_PROC_SLOTS = 89 # no process slots available ERROR_NOT_FROZEN = 90 ERR_TSTOVFL = 91 # timer service table overflow ERR_TSTDUP = 92 # timer service table duplicate ERROR_NO_ITEMS = 93 # There were no items to operate upon ERROR_INTERRUPT = 95 # interrupted system call ERROR_TOO_MANY_SEMAPHORES = 100 ERROR_EXCL_SEM_ALREADY_OWNED = 101 ERROR_SEM_IS_SET = 102 ERROR_TOO_MANY_SEM_REQUESTS = 103 ERROR_INVALID_AT_INTERRUPT_TIME = 104 ERROR_SEM_OWNER_DIED = 105 # waitsem found owner died ERROR_SEM_USER_LIMIT = 106 # too many procs have this sem ERROR_DISK_CHANGE = 107 # insert disk b into drive a ERROR_DRIVE_LOCKED = 108 # drive locked by another process ERROR_BROKEN_PIPE = 109 # write on pipe with no reader ERROR_OPEN_FAILED = 110 # open/created failed ERROR_DISK_FULL = 112 # not enough space ERROR_NO_MORE_SEARCH_HANDLES = 113 # can't allocate ERROR_INVALID_TARGET_HANDLE = 114 # handle in DOSDUPHANDLE is invalid ERROR_PROTECTION_VIOLATION = 115 # bad user virtual address ERROR_VIOKBD_REQUEST = 116 ERROR_INVALID_CATEGORY = 117 # category for DEVIOCTL not defined ERROR_INVALID_VERIFY_SWITCH = 118 # invalid value ERROR_BAD_DRIVER_LEVEL = 119 # DosDevIOCTL not level four ERROR_CALL_NOT_IMPLEMENTED = 120 ERROR_SEM_TIMEOUT = 121 # timeout from semaphore function ERROR_INSUFFICIENT_BUFFER = 122 ERROR_INVALID_NAME = 123 # illegal char or malformed file system name ERROR_INVALID_LEVEL = 124 # unimplemented level for info retrieval ERROR_NO_VOLUME_LABEL = 125 # no volume label found ERROR_MOD_NOT_FOUND = 126 # w_getprocaddr, w_getmodhandle ERROR_PROC_NOT_FOUND = 127 # w_getprocaddr ERROR_WAIT_NO_CHILDREN = 128 # CWait finds to children ERROR_CHILD_NOT_COMPLETE = 129 # CWait children not dead yet ERROR_DIRECT_ACCESS_HANDLE = 130 # invalid for direct disk access ERROR_NEGATIVE_SEEK = 131 # tried to seek negative offset ERROR_SEEK_ON_DEVICE = 132 # tried to seek on device or pipe ERROR_IS_JOIN_TARGET = 133 ERROR_IS_JOINED = 134 ERROR_IS_SUBSTED = 135 ERROR_NOT_JOINED = 136 ERROR_NOT_SUBSTED = 137 ERROR_JOIN_TO_JOIN = 138 ERROR_SUBST_TO_SUBST = 139 ERROR_JOIN_TO_SUBST = 140 ERROR_SUBST_TO_JOIN = 141 ERROR_BUSY_DRIVE = 142 ERROR_SAME_DRIVE = 143 ERROR_DIR_NOT_ROOT = 144 ERROR_DIR_NOT_EMPTY = 145 ERROR_IS_SUBST_PATH = 146 ERROR_IS_JOIN_PATH = 147 ERROR_PATH_BUSY = 148 ERROR_IS_SUBST_TARGET = 149 ERROR_SYSTEM_TRACE = 150 # system trace error ERROR_INVALID_EVENT_COUNT = 151 # DosMuxSemWait errors ERROR_TOO_MANY_MUXWAITERS = 152 ERROR_INVALID_LIST_FORMAT = 153 ERROR_LABEL_TOO_LONG = 154 ERROR_TOO_MANY_TCBS = 155 ERROR_SIGNAL_REFUSED = 156 ERROR_DISCARDED = 157 ERROR_NOT_LOCKED = 158 ERROR_BAD_THREADID_ADDR = 159 ERROR_BAD_ARGUMENTS = 160 ERROR_BAD_PATHNAME = 161 ERROR_SIGNAL_PENDING = 162 ERROR_UNCERTAIN_MEDIA = 163 ERROR_MAX_THRDS_REACHED = 164 ERROR_MONITORS_NOT_SUPPORTED = 165 ERROR_LOCK_FAILED = 167 ERROR_BUSY = 170 ERROR_CANCEL_VIOLATION = 173 ERROR_ATOMIC_LOCKS_NOT_SUPPORTED = 174 ERROR_INVALID_SEGMENT_NUMBER = 180 ERROR_INVALID_CALLGATE = 181 ERROR_INVALID_ORDINAL = 182 ERROR_ALREADY_EXISTS = 183 ERROR_NO_CHILD_PROCESS = 184 ERROR_CHILD_ALIVE_NOWAIT = 185 ERROR_INVALID_FLAG_NUMBER = 186 ERROR_SEM_NOT_FOUND = 187 ERROR_INVALID_STARTING_CODESEG = 188 ERROR_INVALID_STACKSEG = 189 ERROR_INVALID_MODULETYPE = 190 ERROR_INVALID_EXE_SIGNATURE = 191 ERROR_EXE_MARKED_INVALID = 192 ERROR_BAD_EXE_FORMAT = 193 ERROR_ITERATED_DATA_EXCEEDS_64k = 194 # rubocop:disable Style/ConstantName ERROR_INVALID_MINALLOCSIZE = 195 ERROR_DYNLINK_FROM_INVALID_RING = 196 ERROR_IOPL_NOT_ENABLED = 197 ERROR_INVALID_SEGDPL = 198 ERROR_AUTODATASEG_EXCEEDS_64k = 199 # rubocop:disable Style/ConstantName ERROR_RING2SEG_MUST_BE_MOVABLE = 200 ERROR_RELOC_CHAIN_XEEDS_SEGLIM = 201 ERROR_INFLOOP_IN_RELOC_CHAIN = 202 ERROR_ENVVAR_NOT_FOUND = 203 ERROR_NOT_CURRENT_CTRY = 204 ERROR_NO_SIGNAL_SENT = 205 ERROR_FILENAME_EXCED_RANGE = 206 # if filename > 8.3 ERROR_RING2_STACK_IN_USE = 207 # for FAPI ERROR_META_EXPANSION_TOO_LONG = 208 # if "*a" > 8.3 ERROR_INVALID_SIGNAL_NUMBER = 209 ERROR_THREAD_1_INACTIVE = 210 ERROR_INFO_NOT_AVAIL = 211 #@@ PTM 5550 ERROR_LOCKED = 212 ERROR_BAD_DYNALINK = 213 #@@ PTM 5760 ERROR_TOO_MANY_MODULES = 214 ERROR_NESTING_NOT_ALLOWED = 215 ERROR_EXE_MACHINE_TYPE_MISMATCH = 216 ERROR_BAD_PIPE = 230 ERROR_PIPE_BUSY = 231 ERROR_NO_DATA = 232 ERROR_PIPE_NOT_CONNECTED = 233 ERROR_MORE_DATA = 234 ERROR_VC_DISCONNECTED = 240 ERROR_INVALID_EA_NAME = 254 ERROR_EA_LIST_INCONSISTENT = 255 ERROR_NO_MORE_ITEMS = 259 ERROR_CANNOT_COPY = 266 ERROR_DIRECTORY = 267 ERROR_EAS_DIDNT_FIT = 275 ERROR_EA_FILE_CORRUPT = 276 ERROR_EA_TABLE_FULL = 277 ERROR_INVALID_EA_HANDLE = 278 ERROR_EAS_NOT_SUPPORTED = 282 ERROR_NOT_OWNER = 288 ERROR_TOO_MANY_POSTS = 298 ERROR_PARTIAL_COPY = 299 ERROR_OPLOCK_NOT_GRANTED = 300 ERROR_INVALID_OPLOCK_PROTOCOL = 301 ERROR_DISK_TOO_FRAGMENTED = 302 ERROR_MR_MID_NOT_FOUND = 317 ERROR_SCOPE_NOT_FOUND = 318 ERROR_FAIL_NOACTION_REBOOT = 350 ERROR_FAIL_SHUTDOWN = 351 ERROR_FAIL_RESTART = 352 ERROR_MAX_SESSIONS_REACHED = 353 ERROR_INVALID_ADDRESS = 487 ERROR_USER_PROFILE_LOAD = 500 ERROR_ARITHMETIC_OVERFLOW = 534 ERROR_PIPE_CONNECTED = 535 ERROR_PIPE_LISTENING = 536 ERROR_EA_ACCESS_DENIED = 994 ERROR_OPERATION_ABORTED = 995 ERROR_IO_INCOMPLETE = 996 ERROR_IO_PENDING = 997 ERROR_NOACCESS = 998 ERROR_SWAPERROR = 999 ERROR_STACK_OVERFLOW = 1001 ERROR_INVALID_MESSAGE = 1002 ERROR_CAN_NOT_COMPLETE = 1003 ERROR_INVALID_FLAGS = 1004 ERROR_UNRECOGNIZED_VOLUME = 1005 ERROR_FILE_INVALID = 1006 ERROR_FULLSCREEN_MODE = 1007 ERROR_NO_TOKEN = 1008 ERROR_BADDB = 1009 ERROR_BADKEY = 1010 ERROR_CANTOPEN = 1011 ERROR_CANTREAD = 1012 ERROR_CANTWRITE = 1013 ERROR_REGISTRY_RECOVERED = 1014 ERROR_REGISTRY_CORRUPT = 1015 ERROR_REGISTRY_IO_FAILED = 1016 ERROR_NOT_REGISTRY_FILE = 1017 ERROR_KEY_DELETED = 1018 ERROR_NO_LOG_SPACE = 1019 ERROR_KEY_HAS_CHILDREN = 1020 ERROR_CHILD_MUST_BE_VOLATILE = 1021 ERROR_NOTIFY_ENUM_DIR = 1022 ERROR_DEPENDENT_SERVICES_RUNNING = 1051 ERROR_INVALID_SERVICE_CONTROL = 1052 ERROR_SERVICE_REQUEST_TIMEOUT = 1053 ERROR_SERVICE_NO_THREAD = 1054 ERROR_SERVICE_DATABASE_LOCKED = 1055 ERROR_SERVICE_ALREADY_RUNNING = 1056 ERROR_INVALID_SERVICE_ACCOUNT = 1057 ERROR_SERVICE_DISABLED = 1058 ERROR_CIRCULAR_DEPENDENCY = 1059 ERROR_SERVICE_DOES_NOT_EXIST = 1060 ERROR_SERVICE_CANNOT_ACCEPT_CTRL = 1061 ERROR_SERVICE_NOT_ACTIVE = 1062 ERROR_FAILED_SERVICE_CONTROLLER_CONNECT = 1063 ERROR_EXCEPTION_IN_SERVICE = 1064 ERROR_DATABASE_DOES_NOT_EXIST = 1065 ERROR_SERVICE_SPECIFIC_ERROR = 1066 ERROR_PROCESS_ABORTED = 1067 ERROR_SERVICE_DEPENDENCY_FAIL = 1068 ERROR_SERVICE_LOGON_FAILED = 1069 ERROR_SERVICE_START_HANG = 1070 ERROR_INVALID_SERVICE_LOCK = 1071 ERROR_SERVICE_MARKED_FOR_DELETE = 1072 ERROR_SERVICE_EXISTS = 1073 ERROR_ALREADY_RUNNING_LKG = 1074 ERROR_SERVICE_DEPENDENCY_DELETED = 1075 ERROR_BOOT_ALREADY_ACCEPTED = 1076 ERROR_SERVICE_NEVER_STARTED = 1077 ERROR_DUPLICATE_SERVICE_NAME = 1078 ERROR_DIFFERENT_SERVICE_ACCOUNT = 1079 ERROR_CANNOT_DETECT_DRIVER_FAILURE = 1080 ERROR_CANNOT_DETECT_PROCESS_ABORT = 1081 ERROR_NO_RECOVERY_PROGRAM = 1082 ERROR_SERVICE_NOT_IN_EXE = 1083 ERROR_END_OF_MEDIA = 1100 ERROR_FILEMARK_DETECTED = 1101 ERROR_BEGINNING_OF_MEDIA = 1102 ERROR_SETMARK_DETECTED = 1103 ERROR_NO_DATA_DETECTED = 1104 ERROR_PARTITION_FAILURE = 1105 ERROR_INVALID_BLOCK_LENGTH = 1106 ERROR_DEVICE_NOT_PARTITIONED = 1107 ERROR_UNABLE_TO_LOCK_MEDIA = 1108 ERROR_UNABLE_TO_UNLOAD_MEDIA = 1109 ERROR_MEDIA_CHANGED = 1110 ERROR_BUS_RESET = 1111 ERROR_NO_MEDIA_IN_DRIVE = 1112 ERROR_NO_UNICODE_TRANSLATION = 1113 ERROR_DLL_INIT_FAILED = 1114 ERROR_SHUTDOWN_IN_PROGRESS = 1115 ERROR_NO_SHUTDOWN_IN_PROGRESS = 1116 ERROR_IO_DEVICE = 1117 ERROR_SERIAL_NO_DEVICE = 1118 ERROR_IRQ_BUSY = 1119 ERROR_MORE_WRITES = 1120 ERROR_COUNTER_TIMEOUT = 1121 ERROR_FLOPPY_ID_MARK_NOT_FOUND = 1122 ERROR_FLOPPY_WRONG_CYLINDER = 1123 ERROR_FLOPPY_UNKNOWN_ERROR = 1124 ERROR_FLOPPY_BAD_REGISTERS = 1125 ERROR_DISK_RECALIBRATE_FAILED = 1126 ERROR_DISK_OPERATION_FAILED = 1127 ERROR_DISK_RESET_FAILED = 1128 ERROR_EOM_OVERFLOW = 1129 ERROR_NOT_ENOUGH_SERVER_MEMORY = 1130 ERROR_POSSIBLE_DEADLOCK = 1131 ERROR_MAPPED_ALIGNMENT = 1132 ERROR_SET_POWER_STATE_VETOED = 1140 ERROR_SET_POWER_STATE_FAILED = 1141 ERROR_TOO_MANY_LINKS = 1142 ERROR_OLD_WIN_VERSION = 1150 ERROR_APP_WRONG_OS = 1151 ERROR_SINGLE_INSTANCE_APP = 1152 ERROR_RMODE_APP = 1153 ERROR_INVALID_DLL = 1154 ERROR_NO_ASSOCIATION = 1155 ERROR_DDE_FAIL = 1156 ERROR_DLL_NOT_FOUND = 1157 ERROR_NO_MORE_USER_HANDLES = 1158 ERROR_MESSAGE_SYNC_ONLY = 1159 ERROR_SOURCE_ELEMENT_EMPTY = 1160 ERROR_DESTINATION_ELEMENT_FULL = 1161 ERROR_ILLEGAL_ELEMENT_ADDRESS = 1162 ERROR_MAGAZINE_NOT_PRESENT = 1163 ERROR_DEVICE_REINITIALIZATION_NEEDED = 1164 ERROR_DEVICE_REQUIRES_CLEANING = 1165 ERROR_DEVICE_DOOR_OPEN = 1166 ERROR_DEVICE_NOT_CONNECTED = 1167 ERROR_NOT_FOUND = 1168 ERROR_NO_MATCH = 1169 ERROR_SET_NOT_FOUND = 1170 ERROR_POINT_NOT_FOUND = 1171 ERROR_NO_TRACKING_SERVICE = 1172 ERROR_NO_VOLUME_ID = 1173 ERROR_UNABLE_TO_REMOVE_REPLACED = 1175 ERROR_UNABLE_TO_MOVE_REPLACEMENT = 1176 ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 = 1177 ERROR_JOURNAL_DELETE_IN_PROGRESS = 1178 ERROR_JOURNAL_NOT_ACTIVE = 1179 ERROR_POTENTIAL_FILE_FOUND = 1180 ERROR_JOURNAL_ENTRY_DELETED = 1181 ERROR_BAD_DEVICE = 1200 ERROR_CONNECTION_UNAVAIL = 1201 ERROR_DEVICE_ALREADY_REMEMBERED = 1202 ERROR_NO_NET_OR_BAD_PATH = 1203 ERROR_BAD_PROVIDER = 1204 ERROR_CANNOT_OPEN_PROFILE = 1205 ERROR_BAD_PROFILE = 1206 ERROR_NOT_CONTAINER = 1207 ERROR_EXTENDED_ERROR = 1208 ERROR_INVALID_GROUPNAME = 1209 ERROR_INVALID_COMPUTERNAME = 1210 ERROR_INVALID_EVENTNAME = 1211 ERROR_INVALID_DOMAINNAME = 1212 ERROR_INVALID_SERVICENAME = 1213 ERROR_INVALID_NETNAME = 1214 ERROR_INVALID_SHARENAME = 1215 ERROR_INVALID_PASSWORDNAME = 1216 ERROR_INVALID_MESSAGENAME = 1217 ERROR_INVALID_MESSAGEDEST = 1218 ERROR_SESSION_CREDENTIAL_CONFLICT = 1219 ERROR_REMOTE_SESSION_LIMIT_EXCEEDED = 1220 ERROR_DUP_DOMAINNAME = 1221 ERROR_NO_NETWORK = 1222 ERROR_CANCELLED = 1223 ERROR_USER_MAPPED_FILE = 1224 ERROR_CONNECTION_REFUSED = 1225 ERROR_GRACEFUL_DISCONNECT = 1226 ERROR_ADDRESS_ALREADY_ASSOCIATED = 1227 ERROR_ADDRESS_NOT_ASSOCIATED = 1228 ERROR_CONNECTION_INVALID = 1229 ERROR_CONNECTION_ACTIVE = 1230 ERROR_NETWORK_UNREACHABLE = 1231 ERROR_HOST_UNREACHABLE = 1232 ERROR_PROTOCOL_UNREACHABLE = 1233 ERROR_PORT_UNREACHABLE = 1234 ERROR_REQUEST_ABORTED = 1235 ERROR_CONNECTION_ABORTED = 1236 ERROR_RETRY = 1237 ERROR_CONNECTION_COUNT_LIMIT = 1238 ERROR_LOGIN_TIME_RESTRICTION = 1239 ERROR_LOGIN_WKSTA_RESTRICTION = 1240 ERROR_INCORRECT_ADDRESS = 1241 ERROR_ALREADY_REGISTERED = 1242 ERROR_SERVICE_NOT_FOUND = 1243 ERROR_NOT_AUTHENTICATED = 1244 ERROR_NOT_LOGGED_ON = 1245 ERROR_CONTINUE = 1246 ERROR_ALREADY_INITIALIZED = 1247 ERROR_NO_MORE_DEVICES = 1248 ERROR_NO_SUCH_SITE = 1249 ERROR_DOMAIN_CONTROLLER_EXISTS = 1250 ERROR_ONLY_IF_CONNECTED = 1251 ERROR_OVERRIDE_NOCHANGES = 1252 ERROR_BAD_USER_PROFILE = 1253 ERROR_NOT_SUPPORTED_ON_SBS = 1254 ERROR_SERVER_SHUTDOWN_IN_PROGRESS = 1255 ERROR_HOST_DOWN = 1256 ERROR_ACCESS_DISABLED_BY_POLICY = 1260 ERROR_REG_NAT_CONSUMPTION = 1261 ERROR_PKINIT_FAILURE = 1263 ERROR_SMARTCARD_SUBSYSTEM_FAILURE = 1264 ERROR_DOWNGRADE_DETECTED = 1265 ERROR_MACHINE_LOCKED = 1271 ERROR_CALLBACK_SUPPLIED_INVALID_DATA = 1273 ERROR_SYNC_FOREGROUND_REFRESH_REQUIRED = 1274 ERROR_DRIVER_BLOCKED = 1275 ERROR_INVALID_IMPORT_OF_NON_DLL = 1276 ERROR_NOT_ALL_ASSIGNED = 1300 ERROR_SOME_NOT_MAPPED = 1301 ERROR_NO_QUOTAS_FOR_ACCOUNT = 1302 ERROR_LOCAL_USER_SESSION_KEY = 1303 ERROR_NULL_LM_PASSWORD = 1304 ERROR_UNKNOWN_REVISION = 1305 ERROR_REVISION_MISMATCH = 1306 ERROR_INVALID_OWNER = 1307 ERROR_INVALID_PRIMARY_GROUP = 1308 ERROR_NO_IMPERSONATION_TOKEN = 1309 ERROR_CANT_DISABLE_MANDATORY = 1310 ERROR_NO_LOGON_SERVERS = 1311 ERROR_NO_SUCH_LOGON_SESSION = 1312 ERROR_NO_SUCH_PRIVILEGE = 1313 ERROR_PRIVILEGE_NOT_HELD = 1314 ERROR_INVALID_ACCOUNT_NAME = 1315 ERROR_USER_EXISTS = 1316 ERROR_NO_SUCH_USER = 1317 ERROR_GROUP_EXISTS = 1318 ERROR_NO_SUCH_GROUP = 1319 ERROR_MEMBER_IN_GROUP = 1320 ERROR_MEMBER_NOT_IN_GROUP = 1321 ERROR_LAST_ADMIN = 1322 ERROR_WRONG_PASSWORD = 1323 ERROR_ILL_FORMED_PASSWORD = 1324 ERROR_PASSWORD_RESTRICTION = 1325 ERROR_LOGON_FAILURE = 1326 ERROR_ACCOUNT_RESTRICTION = 1327 ERROR_INVALID_LOGON_HOURS = 1328 ERROR_INVALID_WORKSTATION = 1329 ERROR_PASSWORD_EXPIRED = 1330 ERROR_ACCOUNT_DISABLED = 1331 ERROR_NONE_MAPPED = 1332 ERROR_TOO_MANY_LUIDS_REQUESTED = 1333 ERROR_LUIDS_EXHAUSTED = 1334 ERROR_INVALID_SUB_AUTHORITY = 1335 ERROR_INVALID_ACL = 1336 ERROR_INVALID_SID = 1337 ERROR_INVALID_SECURITY_DESCR = 1338 ERROR_BAD_INHERITANCE_ACL = 1340 ERROR_SERVER_DISABLED = 1341 ERROR_SERVER_NOT_DISABLED = 1342 ERROR_INVALID_ID_AUTHORITY = 1343 ERROR_ALLOTTED_SPACE_EXCEEDED = 1344 ERROR_INVALID_GROUP_ATTRIBUTES = 1345 ERROR_BAD_IMPERSONATION_LEVEL = 1346 ERROR_CANT_OPEN_ANONYMOUS = 1347 ERROR_BAD_VALIDATION_CLASS = 1348 ERROR_BAD_TOKEN_TYPE = 1349 ERROR_NO_SECURITY_ON_OBJECT = 1350 ERROR_CANT_ACCESS_DOMAIN_INFO = 1351 ERROR_INVALID_SERVER_STATE = 1352 ERROR_INVALID_DOMAIN_STATE = 1353 ERROR_INVALID_DOMAIN_ROLE = 1354 ERROR_NO_SUCH_DOMAIN = 1355 ERROR_DOMAIN_EXISTS = 1356 ERROR_DOMAIN_LIMIT_EXCEEDED = 1357 ERROR_INTERNAL_DB_CORRUPTION = 1358 ERROR_INTERNAL_ERROR = 1359 ERROR_GENERIC_NOT_MAPPED = 1360 ERROR_BAD_DESCRIPTOR_FORMAT = 1361 ERROR_NOT_LOGON_PROCESS = 1362 ERROR_LOGON_SESSION_EXISTS = 1363 ERROR_NO_SUCH_PACKAGE = 1364 ERROR_BAD_LOGON_SESSION_STATE = 1365 ERROR_LOGON_SESSION_COLLISION = 1366 ERROR_INVALID_LOGON_TYPE = 1367 ERROR_CANNOT_IMPERSONATE = 1368 ERROR_RXACT_INVALID_STATE = 1369 ERROR_RXACT_COMMIT_FAILURE = 1370 ERROR_SPECIAL_ACCOUNT = 1371 ERROR_SPECIAL_GROUP = 1372 ERROR_SPECIAL_USER = 1373 ERROR_MEMBERS_PRIMARY_GROUP = 1374 ERROR_TOKEN_ALREADY_IN_USE = 1375 ERROR_NO_SUCH_ALIAS = 1376 ERROR_MEMBER_NOT_IN_ALIAS = 1377 ERROR_MEMBER_IN_ALIAS = 1378 ERROR_ALIAS_EXISTS = 1379 ERROR_LOGON_NOT_GRANTED = 1380 ERROR_TOO_MANY_SECRETS = 1381 ERROR_SECRET_TOO_LONG = 1382 ERROR_INTERNAL_DB_ERROR = 1383 ERROR_TOO_MANY_CONTEXT_IDS = 1384 ERROR_LOGON_TYPE_NOT_GRANTED = 1385 ERROR_NT_CROSS_ENCRYPTION_REQUIRED = 1386 ERROR_NO_SUCH_MEMBER = 1387 ERROR_INVALID_MEMBER = 1388 ERROR_TOO_MANY_SIDS = 1389 ERROR_LM_CROSS_ENCRYPTION_REQUIRED = 1390 ERROR_NO_INHERITANCE = 1391 ERROR_FILE_CORRUPT = 1392 ERROR_DISK_CORRUPT = 1393 ERROR_NO_USER_SESSION_KEY = 1394 ERROR_LICENSE_QUOTA_EXCEEDED = 1395 ERROR_WRONG_TARGET_NAME = 1396 ERROR_MUTUAL_AUTH_FAILED = 1397 ERROR_TIME_SKEW = 1398 ERROR_CURRENT_DOMAIN_NOT_ALLOWED = 1399 ERROR_INVALID_WINDOW_HANDLE = 1400 ERROR_INVALID_MENU_HANDLE = 1401 ERROR_INVALID_CURSOR_HANDLE = 1402 ERROR_INVALID_ACCEL_HANDLE = 1403 ERROR_INVALID_HOOK_HANDLE = 1404 ERROR_INVALID_DWP_HANDLE = 1405 ERROR_TLW_WITH_WSCHILD = 1406 ERROR_CANNOT_FIND_WND_CLASS = 1407 ERROR_WINDOW_OF_OTHER_THREAD = 1408 ERROR_HOTKEY_ALREADY_REGISTERED = 1409 ERROR_CLASS_ALREADY_EXISTS = 1410 ERROR_CLASS_DOES_NOT_EXIST = 1411 ERROR_CLASS_HAS_WINDOWS = 1412 ERROR_INVALID_INDEX = 1413 ERROR_INVALID_ICON_HANDLE = 1414 ERROR_PRIVATE_DIALOG_INDEX = 1415 ERROR_LISTBOX_ID_NOT_FOUND = 1416 ERROR_NO_WILDCARD_CHARACTERS = 1417 ERROR_CLIPBOARD_NOT_OPEN = 1418 ERROR_HOTKEY_NOT_REGISTERED = 1419 ERROR_WINDOW_NOT_DIALOG = 1420 ERROR_CONTROL_ID_NOT_FOUND = 1421 ERROR_INVALID_COMBOBOX_MESSAGE = 1422 ERROR_WINDOW_NOT_COMBOBOX = 1423 ERROR_INVALID_EDIT_HEIGHT = 1424 ERROR_DC_NOT_FOUND = 1425 ERROR_INVALID_HOOK_FILTER = 1426 ERROR_INVALID_FILTER_PROC = 1427 ERROR_HOOK_NEEDS_HMOD = 1428 ERROR_GLOBAL_ONLY_HOOK = 1429 ERROR_JOURNAL_HOOK_SET = 1430 ERROR_HOOK_NOT_INSTALLED = 1431 ERROR_INVALID_LB_MESSAGE = 1432 ERROR_SETCOUNT_ON_BAD_LB = 1433 ERROR_LB_WITHOUT_TABSTOPS = 1434 ERROR_DESTROY_OBJECT_OF_OTHER_THREAD = 1435 ERROR_CHILD_WINDOW_MENU = 1436 ERROR_NO_SYSTEM_MENU = 1437 ERROR_INVALID_MSGBOX_STYLE = 1438 ERROR_INVALID_SPI_VALUE = 1439 ERROR_SCREEN_ALREADY_LOCKED = 1440 ERROR_HWNDS_HAVE_DIFF_PARENT = 1441 ERROR_NOT_CHILD_WINDOW = 1442 ERROR_INVALID_GW_COMMAND = 1443 ERROR_INVALID_THREAD_ID = 1444 ERROR_NON_MDICHILD_WINDOW = 1445 ERROR_POPUP_ALREADY_ACTIVE = 1446 ERROR_NO_SCROLLBARS = 1447 ERROR_INVALID_SCROLLBAR_RANGE = 1448 ERROR_INVALID_SHOWWIN_COMMAND = 1449 ERROR_NO_SYSTEM_RESOURCES = 1450 ERROR_NONPAGED_SYSTEM_RESOURCES = 1451 ERROR_PAGED_SYSTEM_RESOURCES = 1452 ERROR_WORKING_SET_QUOTA = 1453 ERROR_PAGEFILE_QUOTA = 1454 ERROR_COMMITMENT_LIMIT = 1455 ERROR_MENU_ITEM_NOT_FOUND = 1456 ERROR_INVALID_KEYBOARD_HANDLE = 1457 ERROR_HOOK_TYPE_NOT_ALLOWED = 1458 ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION = 1459 ERROR_TIMEOUT = 1460 ERROR_INVALID_MONITOR_HANDLE = 1461 ERROR_EVENTLOG_FILE_CORRUPT = 1500 ERROR_EVENTLOG_CANT_START = 1501 ERROR_LOG_FILE_FULL = 1502 ERROR_EVENTLOG_FILE_CHANGED = 1503 ERROR_INVALID_TASK_NAME = 1550 ERROR_INVALID_TASK_INDEX = 1551 ERROR_THREAD_ALREADY_IN_TASK = 1552 ERROR_INSTALL_SERVICE_FAILURE = 1601 ERROR_INSTALL_USEREXIT = 1602 ERROR_INSTALL_FAILURE = 1603 ERROR_INSTALL_SUSPEND = 1604 ERROR_UNKNOWN_PRODUCT = 1605 ERROR_UNKNOWN_FEATURE = 1606 ERROR_UNKNOWN_COMPONENT = 1607 ERROR_UNKNOWN_PROPERTY = 1608 ERROR_INVALID_HANDLE_STATE = 1609 ERROR_BAD_CONFIGURATION = 1610 ERROR_INDEX_ABSENT = 1611 ERROR_INSTALL_SOURCE_ABSENT = 1612 ERROR_INSTALL_PACKAGE_VERSION = 1613 ERROR_PRODUCT_UNINSTALLED = 1614 ERROR_BAD_QUERY_SYNTAX = 1615 ERROR_INVALID_FIELD = 1616 ERROR_DEVICE_REMOVED = 1617 ERROR_INSTALL_ALREADY_RUNNING = 1618 ERROR_INSTALL_PACKAGE_OPEN_FAILED = 1619 ERROR_INSTALL_PACKAGE_INVALID = 1620 ERROR_INSTALL_UI_FAILURE = 1621 ERROR_INSTALL_LOG_FAILURE = 1622 ERROR_INSTALL_LANGUAGE_UNSUPPORTED = 1623 ERROR_INSTALL_TRANSFORM_FAILURE = 1624 ERROR_INSTALL_PACKAGE_REJECTED = 1625 ERROR_FUNCTION_NOT_CALLED = 1626 ERROR_FUNCTION_FAILED = 1627 ERROR_INVALID_TABLE = 1628 ERROR_DATATYPE_MISMATCH = 1629 ERROR_UNSUPPORTED_TYPE = 1630 ERROR_CREATE_FAILED = 1631 ERROR_INSTALL_TEMP_UNWRITABLE = 1632 ERROR_INSTALL_PLATFORM_UNSUPPORTED = 1633 ERROR_INSTALL_NOTUSED = 1634 ERROR_PATCH_PACKAGE_OPEN_FAILED = 1635 ERROR_PATCH_PACKAGE_INVALID = 1636 ERROR_PATCH_PACKAGE_UNSUPPORTED = 1637 ERROR_PRODUCT_VERSION = 1638 ERROR_INVALID_COMMAND_LINE = 1639 ERROR_INSTALL_REMOTE_DISALLOWED = 1640 ERROR_SUCCESS_REBOOT_INITIATED = 1641 ERROR_UNKNOWN_PATCH = 1647 RPC_S_INVALID_STRING_BINDING = 1700 RPC_S_WRONG_KIND_OF_BINDING = 1701 RPC_S_INVALID_BINDING = 1702 RPC_S_PROTSEQ_NOT_SUPPORTED = 1703 RPC_S_INVALID_RPC_PROTSEQ = 1704 RPC_S_INVALID_STRING_UUID = 1705 RPC_S_INVALID_ENDPOINT_FORMAT = 1706 RPC_S_INVALID_NET_ADDR = 1707 RPC_S_NO_ENDPOINT_FOUND = 1708 RPC_S_INVALID_TIMEOUT = 1709 RPC_S_OBJECT_NOT_FOUND = 1710 RPC_S_ALREADY_REGISTERED = 1711 RPC_S_TYPE_ALREADY_REGISTERED = 1712 RPC_S_ALREADY_LISTENING = 1713 RPC_S_NO_PROTSEQS_REGISTERED = 1714 RPC_S_NOT_LISTENING = 1715 RPC_S_UNKNOWN_MGR_TYPE = 1716 RPC_S_UNKNOWN_IF = 1717 RPC_S_NO_BINDINGS = 1718 RPC_S_NO_PROTSEQS = 1719 RPC_S_CANT_CREATE_ENDPOINT = 1720 RPC_S_OUT_OF_RESOURCES = 1721 RPC_S_SERVER_UNAVAILABLE = 1722 RPC_S_SERVER_TOO_BUSY = 1723 RPC_S_INVALID_NETWORK_OPTIONS = 1724 RPC_S_NO_CALL_ACTIVE = 1725 RPC_S_CALL_FAILED = 1726 RPC_S_CALL_FAILED_DNE = 1727 RPC_S_PROTOCOL_ERROR = 1728 RPC_S_UNSUPPORTED_TRANS_SYN = 1730 RPC_S_UNSUPPORTED_TYPE = 1732 RPC_S_INVALID_TAG = 1733 RPC_S_INVALID_BOUND = 1734 RPC_S_NO_ENTRY_NAME = 1735 RPC_S_INVALID_NAME_SYNTAX = 1736 RPC_S_UNSUPPORTED_NAME_SYNTAX = 1737 RPC_S_UUID_NO_ADDRESS = 1739 RPC_S_DUPLICATE_ENDPOINT = 1740 RPC_S_UNKNOWN_AUTHN_TYPE = 1741 RPC_S_MAX_CALLS_TOO_SMALL = 1742 RPC_S_STRING_TOO_LONG = 1743 RPC_S_PROTSEQ_NOT_FOUND = 1744 RPC_S_PROCNUM_OUT_OF_RANGE = 1745 RPC_S_BINDING_HAS_NO_AUTH = 1746 RPC_S_UNKNOWN_AUTHN_SERVICE = 1747 RPC_S_UNKNOWN_AUTHN_LEVEL = 1748 RPC_S_INVALID_AUTH_IDENTITY = 1749 RPC_S_UNKNOWN_AUTHZ_SERVICE = 1750 EPT_S_INVALID_ENTRY = 1751 EPT_S_CANT_PERFORM_OP = 1752 EPT_S_NOT_REGISTERED = 1753 RPC_S_NOTHING_TO_EXPORT = 1754 RPC_S_INCOMPLETE_NAME = 1755 RPC_S_INVALID_VERS_OPTION = 1756 RPC_S_NO_MORE_MEMBERS = 1757 RPC_S_NOT_ALL_OBJS_UNEXPORTED = 1758 RPC_S_INTERFACE_NOT_FOUND = 1759 RPC_S_ENTRY_ALREADY_EXISTS = 1760 RPC_S_ENTRY_NOT_FOUND = 1761 RPC_S_NAME_SERVICE_UNAVAILABLE = 1762 RPC_S_INVALID_NAF_ID = 1763 RPC_S_CANNOT_SUPPORT = 1764 RPC_S_NO_CONTEXT_AVAILABLE = 1765 RPC_S_INTERNAL_ERROR = 1766 RPC_S_ZERO_DIVIDE = 1767 RPC_S_ADDRESS_ERROR = 1768 RPC_S_FP_DIV_ZERO = 1769 RPC_S_FP_UNDERFLOW = 1770 RPC_S_FP_OVERFLOW = 1771 RPC_X_NO_MORE_ENTRIES = 1772 RPC_X_SS_CHAR_TRANS_OPEN_FAIL = 1773 RPC_X_SS_CHAR_TRANS_SHORT_FILE = 1774 RPC_X_SS_IN_NULL_CONTEXT = 1775 RPC_X_SS_CONTEXT_DAMAGED = 1777 RPC_X_SS_HANDLES_MISMATCH = 1778 RPC_X_SS_CANNOT_GET_CALL_HANDLE = 1779 RPC_X_NULL_REF_POINTER = 1780 RPC_X_ENUM_VALUE_OUT_OF_RANGE = 1781 RPC_X_BYTE_COUNT_TOO_SMALL = 1782 RPC_X_BAD_STUB_DATA = 1783 ERROR_INVALID_USER_BUFFER = 1784 ERROR_UNRECOGNIZED_MEDIA = 1785 ERROR_NO_TRUST_LSA_SECRET = 1786 ERROR_NO_TRUST_SAM_ACCOUNT = 1787 ERROR_TRUSTED_DOMAIN_FAILURE = 1788 ERROR_TRUSTED_RELATIONSHIP_FAILURE = 1789 ERROR_TRUST_FAILURE = 1790 RPC_S_CALL_IN_PROGRESS = 1791 ERROR_NETLOGON_NOT_STARTED = 1792 ERROR_ACCOUNT_EXPIRED = 1793 ERROR_REDIRECTOR_HAS_OPEN_HANDLES = 1794 ERROR_PRINTER_DRIVER_ALREADY_INSTALLED = 1795 ERROR_UNKNOWN_PORT = 1796 ERROR_UNKNOWN_PRINTER_DRIVER = 1797 ERROR_UNKNOWN_PRINTPROCESSOR = 1798 ERROR_INVALID_SEPARATOR_FILE = 1799 ERROR_INVALID_PRIORITY = 1800 ERROR_INVALID_PRINTER_NAME = 1801 ERROR_PRINTER_ALREADY_EXISTS = 1802 ERROR_INVALID_PRINTER_COMMAND = 1803 ERROR_INVALID_DATATYPE = 1804 ERROR_INVALID_ENVIRONMENT = 1805 RPC_S_NO_MORE_BINDINGS = 1806 ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT = 1807 ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT = 1808 ERROR_NOLOGON_SERVER_TRUST_ACCOUNT = 1809 ERROR_DOMAIN_TRUST_INCONSISTENT = 1810 ERROR_SERVER_HAS_OPEN_HANDLES = 1811 ERROR_RESOURCE_DATA_NOT_FOUND = 1812 ERROR_RESOURCE_TYPE_NOT_FOUND = 1813 ERROR_RESOURCE_NAME_NOT_FOUND = 1814 ERROR_RESOURCE_LANG_NOT_FOUND = 1815 ERROR_NOT_ENOUGH_QUOTA = 1816 RPC_S_NO_INTERFACES = 1817 RPC_S_CALL_CANCELLED = 1818 RPC_S_BINDING_INCOMPLETE = 1819 RPC_S_COMM_FAILURE = 1820 RPC_S_UNSUPPORTED_AUTHN_LEVEL = 1821 RPC_S_NO_PRINC_NAME = 1822 RPC_S_NOT_RPC_ERROR = 1823 RPC_S_UUID_LOCAL_ONLY = 1824 RPC_S_SEC_PKG_ERROR = 1825 RPC_S_NOT_CANCELLED = 1826 RPC_X_INVALID_ES_ACTION = 1827 RPC_X_WRONG_ES_VERSION = 1828 RPC_X_WRONG_STUB_VERSION = 1829 RPC_X_INVALID_PIPE_OBJECT = 1830 RPC_X_WRONG_PIPE_ORDER = 1831 RPC_X_WRONG_PIPE_VERSION = 1832 RPC_S_GROUP_MEMBER_NOT_FOUND = 1898 EPT_S_CANT_CREATE = 1899 RPC_S_INVALID_OBJECT = 1900 ERROR_INVALID_TIME = 1901 ERROR_INVALID_FORM_NAME = 1902 ERROR_INVALID_FORM_SIZE = 1903 ERROR_ALREADY_WAITING = 1904 ERROR_PRINTER_DELETED = 1905 ERROR_INVALID_PRINTER_STATE = 1906 ERROR_PASSWORD_MUST_CHANGE = 1907 ERROR_DOMAIN_CONTROLLER_NOT_FOUND = 1908 ERROR_ACCOUNT_LOCKED_OUT = 1909 OR_INVALID_OXID = 1910 OR_INVALID_OID = 1911 OR_INVALID_SET = 1912 RPC_S_SEND_INCOMPLETE = 1913 RPC_S_INVALID_ASYNC_HANDLE = 1914 RPC_S_INVALID_ASYNC_CALL = 1915 RPC_X_PIPE_CLOSED = 1916 RPC_X_PIPE_DISCIPLINE_ERROR = 1917 RPC_X_PIPE_EMPTY = 1918 ERROR_NO_SITENAME = 1919 ERROR_CANT_ACCESS_FILE = 1920 ERROR_CANT_RESOLVE_FILENAME = 1921 RPC_S_ENTRY_TYPE_MISMATCH = 1922 RPC_S_NOT_ALL_OBJS_EXPORTED = 1923 RPC_S_INTERFACE_NOT_EXPORTED = 1924 RPC_S_PROFILE_NOT_ADDED = 1925 RPC_S_PRF_ELT_NOT_ADDED = 1926 RPC_S_PRF_ELT_NOT_REMOVED = 1927 RPC_S_GRP_ELT_NOT_ADDED = 1928 RPC_S_GRP_ELT_NOT_REMOVED = 1929 ERROR_KM_DRIVER_BLOCKED = 1930 ERROR_CONTEXT_EXPIRED = 1931 ERROR_PER_USER_TRUST_QUOTA_EXCEEDED = 1932 ERROR_ALL_USER_TRUST_QUOTA_EXCEEDED = 1933 ERROR_USER_DELETE_TRUST_QUOTA_EXCEEDED = 1934 ERROR_AUTHENTICATION_FIREWALL_FAILED = 1935 ERROR_REMOTE_PRINT_CONNECTIONS_BLOCKED = 1936 ERROR_INVALID_PIXEL_FORMAT = 2000 ERROR_BAD_DRIVER = 2001 ERROR_INVALID_WINDOW_STYLE = 2002 ERROR_METAFILE_NOT_SUPPORTED = 2003 ERROR_TRANSFORM_NOT_SUPPORTED = 2004 ERROR_CLIPPING_NOT_SUPPORTED = 2005 ERROR_INVALID_CMM = 2010 ERROR_INVALID_PROFILE = 2011 ERROR_TAG_NOT_FOUND = 2012 ERROR_TAG_NOT_PRESENT = 2013 ERROR_DUPLICATE_TAG = 2014 ERROR_PROFILE_NOT_ASSOCIATED_WITH_DEVICE = 2015 ERROR_PROFILE_NOT_FOUND = 2016 ERROR_INVALID_COLORSPACE = 2017 ERROR_ICM_NOT_ENABLED = 2018 ERROR_DELETING_ICM_XFORM = 2019 ERROR_INVALID_TRANSFORM = 2020 ERROR_COLORSPACE_MISMATCH = 2021 ERROR_INVALID_COLORINDEX = 2022 ERROR_CONNECTED_OTHER_PASSWORD = 2108 ERROR_BAD_USERNAME = 2202 ERROR_USER_NOT_FOUND = 2221 ERROR_NOT_CONNECTED = 2250 ERROR_OPEN_FILES = 2401 ERROR_ACTIVE_CONNECTIONS = 2402 ERROR_DEVICE_IN_USE = 2404 ERROR_UNKNOWN_PRINT_MONITOR = 3000 ERROR_USER_DEFINED_BASE = 0xF000 # Flags for FormatMessage function: FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100 FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 FORMAT_MESSAGE_FROM_STRING = 0x00000400 FORMAT_MESSAGE_FROM_HMODULE = 0x00000800 FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000 FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF # Set/GetErrorMode values: SEM_FAILCRITICALERRORS = 0x0001 SEM_NOALIGNMENTFAULTEXCEPT = 0x0004 SEM_NOGPFAULTERRORBOX = 0x0002 SEM_NOOPENFILEERRORBOX = 0x8000 # Flags for LoadLibraryEx DONT_RESOLVE_DLL_REFERENCES = 0x00000001 LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010 LOAD_LIBRARY_AS_DATAFILE = 0x00000002 LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040 LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020 LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200 LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000 LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100 LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800 LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400 LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008 ############################################### # Win32 API Bindings ############################################### ffi_lib "kernel32", "user32" =begin DWORD WINAPI FormatMessage( __in DWORD dwFlags, __in_opt LPCVOID lpSource, __in DWORD dwMessageId, __in DWORD dwLanguageId, __out LPTSTR lpBuffer, __in DWORD nSize, __in_opt va_list *Arguments ); =end safe_attach_function :FormatMessageA, [:DWORD, :HANDLE, :DWORD, :DWORD, :LPTSTR, :DWORD, :varargs], :DWORD safe_attach_function :FormatMessageW, [:DWORD, :HANDLE, :DWORD, :DWORD, :LPWSTR, :DWORD, :varargs], :DWORD =begin DWORD WINAPI GetLastError(void); =end safe_attach_function :GetLastError, [], :DWORD =begin void WINAPI SetLastError( __in DWORD dwErrCode ); =end safe_attach_function :SetLastError, [:DWORD], :void safe_attach_function :SetLastErrorEx, [:DWORD, :DWORD], :void =begin UINT WINAPI GetErrorMode(void);s =end safe_attach_function :GetErrorMode, [], :uint =begin UINT WINAPI SetErrorMode( __in UINT uMode ); =end safe_attach_function :SetErrorMode, [:UINT], :UINT =begin https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx HMODULE WINAPI LoadLibraryEx( _In_ LPCTSTR lpFileName, _Reserved_ HANDLE hFile, _In_ DWORD dwFlags ); =end safe_attach_function :LoadLibraryExW, [:LPCTSTR, :HANDLE, :DWORD], :HANDLE =begin https://msdn.microsoft.com/en-us/library/windows/desktop/ms683152(v=vs.85).aspx BOOL WINAPI FreeLibrary( _In_ HMODULE hModule ); =end safe_attach_function :FreeLibrary, [:HANDLE], :BOOL end end end end chef-12.14.60/lib/chef/win32/api/file.rb000066400000000000000000000537321276456504500173340ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Author:: Mark Mzyk () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" require "chef/win32/api/security" require "chef/win32/api/system" require "chef/win32/unicode" class Chef module ReservedNames::Win32 module API module File extend Chef::ReservedNames::Win32::API include Chef::ReservedNames::Win32::API::Security include Chef::ReservedNames::Win32::API::System ############################################### # Win32 API Constants ############################################### FILE_ATTRIBUTE_READONLY = 0x00000001 FILE_ATTRIBUTE_HIDDEN = 0x00000002 FILE_ATTRIBUTE_SYSTEM = 0x00000004 FILE_ATTRIBUTE_DIRECTORY = 0x00000010 FILE_ATTRIBUTE_ARCHIVE = 0x00000020 FILE_ATTRIBUTE_DEVICE = 0x00000040 FILE_ATTRIBUTE_NORMAL = 0x00000080 FILE_ATTRIBUTE_TEMPORARY = 0x00000100 FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200 FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400 FILE_ATTRIBUTE_COMPRESSED = 0x00000800 FILE_ATTRIBUTE_OFFLINE = 0x00001000 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000 FILE_ATTRIBUTE_ENCRYPTED = 0x00004000 FILE_ATTRIBUTE_VIRTUAL = 0x00010000 INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF FILE_FLAG_WRITE_THROUGH = 0x80000000 FILE_FLAG_OVERLAPPED = 0x40000000 FILE_FLAG_NO_BUFFERING = 0x20000000 FILE_FLAG_RANDOM_ACCESS = 0x10000000 FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000 FILE_FLAG_DELETE_ON_CLOSE = 0x04000000 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 FILE_FLAG_POSIX_SEMANTICS = 0x01000000 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 FILE_FLAG_OPEN_NO_RECALL = 0x00100000 FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000 INVALID_HANDLE_VALUE = 0xFFFFFFFF MAX_PATH = 260 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1 FILE_NAME_NORMALIZED = 0x0 FILE_NAME_OPENED = 0x8 # TODO add the rest of these CONSTS FILE_SHARE_READ = 0x00000001 OPEN_EXISTING = 3 # DeviceIoControl control codes # ----------------------------- FILE_DEVICE_BEEP = 0x00000001 FILE_DEVICE_CD_ROM = 0x00000002 FILE_DEVICE_CD_ROM_FILE_SYSTEM = 0x00000003 FILE_DEVICE_CONTROLLER = 0x00000004 FILE_DEVICE_DATALINK = 0x00000005 FILE_DEVICE_DFS = 0x00000006 FILE_DEVICE_DISK = 0x00000007 FILE_DEVICE_DISK_FILE_SYSTEM = 0x00000008 FILE_DEVICE_FILE_SYSTEM = 0x00000009 FILE_DEVICE_INPORT_PORT = 0x0000000a FILE_DEVICE_KEYBOARD = 0x0000000b FILE_DEVICE_MAILSLOT = 0x0000000c FILE_DEVICE_MIDI_IN = 0x0000000d FILE_DEVICE_MIDI_OUT = 0x0000000e FILE_DEVICE_MOUSE = 0x0000000f FILE_DEVICE_MULTI_UNC_PROVIDER = 0x00000010 FILE_DEVICE_NAMED_PIPE = 0x00000011 FILE_DEVICE_NETWORK = 0x00000012 FILE_DEVICE_NETWORK_BROWSER = 0x00000013 FILE_DEVICE_NETWORK_FILE_SYSTEM = 0x00000014 FILE_DEVICE_NULL = 0x00000015 FILE_DEVICE_PARALLEL_PORT = 0x00000016 FILE_DEVICE_PHYSICAL_NETCARD = 0x00000017 FILE_DEVICE_PRINTER = 0x00000018 FILE_DEVICE_SCANNER = 0x00000019 FILE_DEVICE_SERIAL_MOUSE_PORT = 0x0000001a FILE_DEVICE_SERIAL_PORT = 0x0000001b FILE_DEVICE_SCREEN = 0x0000001c FILE_DEVICE_SOUND = 0x0000001d FILE_DEVICE_STREAMS = 0x0000001e FILE_DEVICE_TAPE = 0x0000001f FILE_DEVICE_TAPE_FILE_SYSTEM = 0x00000020 FILE_DEVICE_TRANSPORT = 0x00000021 FILE_DEVICE_UNKNOWN = 0x00000022 FILE_DEVICE_VIDEO = 0x00000023 FILE_DEVICE_VIRTUAL_DISK = 0x00000024 FILE_DEVICE_WAVE_IN = 0x00000025 FILE_DEVICE_WAVE_OUT = 0x00000026 FILE_DEVICE_8042_PORT = 0x00000027 FILE_DEVICE_NETWORK_REDIRECTOR = 0x00000028 FILE_DEVICE_BATTERY = 0x00000029 FILE_DEVICE_BUS_EXTENDER = 0x0000002a FILE_DEVICE_MODEM = 0x0000002b FILE_DEVICE_VDM = 0x0000002c FILE_DEVICE_MASS_STORAGE = 0x0000002d FILE_DEVICE_SMB = 0x0000002e FILE_DEVICE_KS = 0x0000002f FILE_DEVICE_CHANGER = 0x00000030 FILE_DEVICE_SMARTCARD = 0x00000031 FILE_DEVICE_ACPI = 0x00000032 FILE_DEVICE_DVD = 0x00000033 FILE_DEVICE_FULLSCREEN_VIDEO = 0x00000034 FILE_DEVICE_DFS_FILE_SYSTEM = 0x00000035 FILE_DEVICE_DFS_VOLUME = 0x00000036 FILE_DEVICE_SERENUM = 0x00000037 FILE_DEVICE_TERMSRV = 0x00000038 FILE_DEVICE_KSEC = 0x00000039 FILE_DEVICE_FIPS = 0x0000003A FILE_DEVICE_INFINIBAND = 0x0000003B FILE_DEVICE_VMBUS = 0x0000003E FILE_DEVICE_CRYPT_PROVIDER = 0x0000003F FILE_DEVICE_WPD = 0x00000040 FILE_DEVICE_BLUETOOTH = 0x00000041 FILE_DEVICE_MT_COMPOSITE = 0x00000042 FILE_DEVICE_MT_TRANSPORT = 0x00000043 FILE_DEVICE_BIOMETRIC = 0x00000044 FILE_DEVICE_PMI = 0x00000045 # Methods METHOD_BUFFERED = 0 METHOD_IN_DIRECT = 1 METHOD_OUT_DIRECT = 2 METHOD_NEITHER = 3 METHOD_DIRECT_TO_HARDWARE = METHOD_IN_DIRECT METHOD_DIRECT_FROM_HARDWARE = METHOD_OUT_DIRECT # Access FILE_ANY_ACCESS = 0 FILE_SPECIAL_ACCESS = FILE_ANY_ACCESS FILE_READ_ACCESS = 0x0001 FILE_WRITE_ACCESS = 0x0002 def self.CTL_CODE( device_type, function, method, access ) (device_type << 16) | (access << 14) | (function << 2) | method end FSCTL_GET_REPARSE_POINT = CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) # Reparse point tags IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 IO_REPARSE_TAG_HSM = 0xC0000004 IO_REPARSE_TAG_HSM2 = 0x80000006 IO_REPARSE_TAG_SIS = 0x80000007 IO_REPARSE_TAG_WIM = 0x80000008 IO_REPARSE_TAG_CSV = 0x80000009 IO_REPARSE_TAG_DFS = 0x8000000A IO_REPARSE_TAG_SYMLINK = 0xA000000C IO_REPARSE_TAG_DFSR = 0x80000012 MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024 ############################################### # Win32 API Bindings ############################################### ffi_lib "kernel32", "version" # Does not map directly to a win32 struct # see https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx class Translation < FFI::Struct layout :w_lang, :WORD, :w_code_page, :WORD end =begin typedef struct _FILETIME { DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME, *PFILETIME; =end class FILETIME < FFI::Struct layout :dw_low_date_time, :DWORD, :dw_high_date_time, :DWORD end =begin typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; =end class SECURITY_ATTRIBUTES < FFI::Struct layout :n_length, :DWORD, :lp_security_descriptor, :LPVOID, :b_inherit_handle, :DWORD end =begin typedef struct _WIN32_FIND_DATA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; TCHAR cFileName[MAX_PATH]; TCHAR cAlternateFileName[14]; } WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA; =end class WIN32_FIND_DATA < FFI::Struct layout :dw_file_attributes, :DWORD, :ft_creation_time, FILETIME, :ft_last_access_time, FILETIME, :ft_last_write_time, FILETIME, :n_file_size_high, :DWORD, :n_file_size_low, :DWORD, :dw_reserved_0, :DWORD, :dw_reserved_1, :DWORD, :c_file_name, [:BYTE, MAX_PATH * 2], :c_alternate_file_name, [:BYTE, 14] end =begin typedef struct _BY_HANDLE_FILE_INFORMATION { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD dwVolumeSerialNumber; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD nNumberOfLinks; DWORD nFileIndexHigh; DWORD nFileIndexLow; } BY_HANDLE_FILE_INFORMATION, *PBY_HANDLE_FILE_INFORMATION; =end class BY_HANDLE_FILE_INFORMATION < FFI::Struct layout :dw_file_attributes, :DWORD, :ft_creation_time, FILETIME, :ft_last_access_time, FILETIME, :ft_last_write_time, FILETIME, :dw_volume_serial_number, :DWORD, :n_file_size_high, :DWORD, :n_file_size_low, :DWORD, :n_number_of_links, :DWORD, :n_file_index_high, :DWORD, :n_file_index_low, :DWORD end =begin typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; }; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; =end class REPARSE_DATA_BUFFER_SYMBOLIC_LINK < FFI::Struct layout :SubstituteNameOffset, :ushort, :SubstituteNameLength, :ushort, :PrintNameOffset, :ushort, :PrintNameLength, :ushort, :Flags, :uint32, :PathBuffer, :ushort def substitute_name string_pointer = FFI::Pointer.new(pointer.address) + offset_of(:PathBuffer) + self[:SubstituteNameOffset] string_pointer.read_wstring(self[:SubstituteNameLength] / 2) end def print_name string_pointer = FFI::Pointer.new(pointer.address) + offset_of(:PathBuffer) + self[:PrintNameOffset] string_pointer.read_wstring(self[:PrintNameLength] / 2) end end class REPARSE_DATA_BUFFER_MOUNT_POINT < FFI::Struct layout :SubstituteNameOffset, :ushort, :SubstituteNameLength, :ushort, :PrintNameOffset, :ushort, :PrintNameLength, :ushort, :PathBuffer, :ushort def substitute_name string_pointer = FFI::Pointer.new(pointer.address) + offset_of(:PathBuffer) + self[:SubstituteNameOffset] string_pointer.read_wstring(self[:SubstituteNameLength] / 2) end def print_name string_pointer = FFI::Pointer.new(pointer.address) + offset_of(:PathBuffer) + self[:PrintNameOffset] string_pointer.read_wstring(self[:PrintNameLength] / 2) end end class REPARSE_DATA_BUFFER_GENERIC < FFI::Struct layout :DataBuffer, :uchar end class REPARSE_DATA_BUFFER_UNION < FFI::Union layout :SymbolicLinkReparseBuffer, REPARSE_DATA_BUFFER_SYMBOLIC_LINK, :MountPointReparseBuffer, REPARSE_DATA_BUFFER_MOUNT_POINT, :GenericReparseBuffer, REPARSE_DATA_BUFFER_GENERIC end class REPARSE_DATA_BUFFER < FFI::Struct layout :ReparseTag, :uint32, :ReparseDataLength, :ushort, :Reserved, :ushort, :ReparseBuffer, REPARSE_DATA_BUFFER_UNION def reparse_buffer if self[:ReparseTag] == IO_REPARSE_TAG_SYMLINK self[:ReparseBuffer][:SymbolicLinkReparseBuffer] elsif self[:ReparseTag] == IO_REPARSE_TAG_MOUNT_POINT self[:ReparseBuffer][:MountPointReparseBuffer] else self[:ReparseBuffer][:GenericReparseBuffer] end end end =begin HANDLE WINAPI CreateFile( __in LPCTSTR lpFileName, __in DWORD dwDesiredAccess, __in DWORD dwShareMode, __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, __in DWORD dwCreationDisposition, __in DWORD dwFlagsAndAttributes, __in_opt HANDLE hTemplateFile ); =end safe_attach_function :CreateFileW, [:LPCTSTR, :DWORD, :DWORD, :LPSECURITY_ATTRIBUTES, :DWORD, :DWORD, :pointer], :HANDLE =begin BOOL WINAPI FindClose( __inout HANDLE hFindFile ); =end safe_attach_function :FindClose, [:HANDLE], :BOOL =begin DWORD WINAPI GetFileAttributes( __in LPCTSTR lpFileName ); =end safe_attach_function :GetFileAttributesW, [:LPCWSTR], :DWORD =begin DWORD WINAPI GetFinalPathNameByHandle( __in HANDLE hFile, __out LPTSTR lpszFilePath, __in DWORD cchFilePath, __in DWORD dwFlags ); =end safe_attach_function :GetFinalPathNameByHandleW, [:HANDLE, :LPTSTR, :DWORD, :DWORD], :DWORD =begin BOOL WINAPI GetFileInformationByHandle( __in HANDLE hFile, __out LPBY_HANDLE_FILE_INFORMATION lpFileInformation ); =end safe_attach_function :GetFileInformationByHandle, [:HANDLE, :LPBY_HANDLE_FILE_INFORMATION], :BOOL =begin HANDLE WINAPI FindFirstFile( __in LPCTSTR lpFileName, __out LPWIN32_FIND_DATA lpFindFileData ); =end safe_attach_function :FindFirstFileW, [:LPCTSTR, :LPWIN32_FIND_DATA], :HANDLE =begin BOOL WINAPI CreateHardLink( __in LPCTSTR lpFileName, __in LPCTSTR lpExistingFileName, __reserved LPSECURITY_ATTRIBUTES lpSecurityAttributes ); =end safe_attach_function :CreateHardLinkW, [:LPCTSTR, :LPCTSTR, :LPSECURITY_ATTRIBUTES], :BOOLEAN =begin BOOLEAN WINAPI CreateSymbolicLink( __in LPTSTR lpSymlinkFileName, __in LPTSTR lpTargetFileName, __in DWORD dwFlags ); =end safe_attach_function :CreateSymbolicLinkW, [:LPTSTR, :LPTSTR, :DWORD], :BOOLEAN =begin DWORD WINAPI GetLongPathName( __in LPCTSTR lpszShortPath, __out LPTSTR lpszLongPath, __in DWORD cchBuffer ); =end safe_attach_function :GetLongPathNameW, [:LPCTSTR, :LPTSTR, :DWORD], :DWORD =begin DWORD WINAPI GetShortPathName( __in LPCTSTR lpszLongPath, __out LPTSTR lpszShortPath, __in DWORD cchBuffer ); =end safe_attach_function :GetShortPathNameW, [:LPCTSTR, :LPTSTR, :DWORD], :DWORD =begin BOOL WINAPI DeviceIoControl( __in HANDLE hDevice, __in DWORD dwIoControlCode, __in_opt LPVOID lpInBuffer, __in DWORD nInBufferSize, __out_opt LPVOID lpOutBuffer, __in DWORD nOutBufferSize, __out_opt LPDWORD lpBytesReturned, __inout_opt LPOVERLAPPED lpOverlapped ); =end safe_attach_function :DeviceIoControl, [:HANDLE, :DWORD, :LPVOID, :DWORD, :LPVOID, :DWORD, :LPDWORD, :pointer], :BOOL #BOOL WINAPI DeleteVolumeMountPoint( #_In_ LPCTSTR lpszVolumeMountPoint #); safe_attach_function :DeleteVolumeMountPointW, [:LPCTSTR], :BOOL #BOOL WINAPI SetVolumeMountPoint( #_In_ LPCTSTR lpszVolumeMountPoint, #_In_ LPCTSTR lpszVolumeName #); safe_attach_function :SetVolumeMountPointW, [:LPCTSTR, :LPCTSTR], :BOOL #BOOL WINAPI GetVolumeNameForVolumeMountPoint( #_In_ LPCTSTR lpszVolumeMountPoint, #_Out_ LPTSTR lpszVolumeName, #_In_ DWORD cchBufferLength #); safe_attach_function :GetVolumeNameForVolumeMountPointW, [:LPCTSTR, :LPTSTR, :DWORD], :BOOL =begin BOOL WINAPI GetFileVersionInfo( _In_ LPCTSTR lptstrFilename, _Reserved_ DWORD dwHandle, _In_ DWORD dwLen, _Out_ LPVOID lpData ); =end safe_attach_function :GetFileVersionInfoW, [:LPCTSTR, :DWORD, :DWORD, :LPVOID], :BOOL =begin DWORD WINAPI GetFileVersionInfoSize( _In_ LPCTSTR lptstrFilename, _Out_opt_ LPDWORD lpdwHandle ); =end safe_attach_function :GetFileVersionInfoSizeW, [:LPCTSTR, :LPDWORD], :DWORD =begin BOOL WINAPI VerQueryValue( _In_ LPCVOID pBlock, _In_ LPCTSTR lpSubBlock, _Out_ LPVOID *lplpBuffer, _Out_ PUINT puLen ); =end safe_attach_function :VerQueryValueW, [:LPCVOID, :LPCTSTR, :LPVOID, :PUINT], :BOOL ############################################### # Helpers ############################################### # takes the given path pre-pends "\\?\" and # UTF-16LE encodes it. Used to prepare paths # to be passed to the *W vesion of WinAPI File # functions. # This function is used by the "Link" resources where we need # preserve relative paths because symbolic links can actually # point to a relative path (relative to the link itself). def encode_path(path) (path_prepender << path.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)).to_wstring end # Expands the path, prepends "\\?\" and UTF-16LE encodes it. # This function is used by the "File" resources where we need # convert relative paths to fully qualified paths. def canonical_encode_path(path) Chef::Util::PathHelper.canonical_path(path).to_wstring end def path_prepender "\\\\?\\" end # retrieves a file search handle and passes it # to +&block+ along with the find_data. also # ensures the handle is closed on exit of the block # FIXME: yard with @yield def file_search_handle(path) begin # Workaround for CHEF-4419: # Make sure paths starting with "/" has a drive letter # assigned from the current working diretory. # Note: With CHEF-4427 this issue will be fixed with a # broader fix to map all the paths starting with "/" to # SYSTEM_DRIVE on windows. path = ::File.expand_path(path) if path.start_with? "/" path = canonical_encode_path(path) find_data = WIN32_FIND_DATA.new handle = FindFirstFileW(path, find_data) if handle == INVALID_HANDLE_VALUE Chef::ReservedNames::Win32::Error.raise! end yield(handle, find_data) ensure FindClose(handle) if handle && handle != INVALID_HANDLE_VALUE end end # retrieves a file handle and passes it # to +&block+ along with the find_data. also # ensures the handle is closed on exit of the block # FIXME: yard with @yield def file_handle(path) begin path = canonical_encode_path(path) handle = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nil) if handle == INVALID_HANDLE_VALUE Chef::ReservedNames::Win32::Error.raise! end yield(handle) ensure CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE end end # FIXME: yard with @yield def symlink_file_handle(path) begin path = encode_path(path) handle = CreateFileW(path, FILE_READ_EA, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nil) if handle == INVALID_HANDLE_VALUE Chef::ReservedNames::Win32::Error.raise! end yield(handle) ensure CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE end end def retrieve_file_info(file_name) file_information = nil file_handle(file_name) do |handle| file_information = BY_HANDLE_FILE_INFORMATION.new success = GetFileInformationByHandle(handle, file_information) if success == 0 Chef::ReservedNames::Win32::Error.raise! end end file_information end def retrieve_file_version_info(file_name) file_name = encode_path(file_name) file_size = GetFileVersionInfoSizeW(file_name, nil) if file_size == 0 Chef::ReservedNames::Win32::Error.raise! end version_info = FFI::MemoryPointer.new(file_size) unless GetFileVersionInfoW(file_name, 0, file_size, version_info) Chef::ReservedNames::Win32::Error.raise! end version_info end end end end end chef-12.14.60/lib/chef/win32/api/installer.rb000066400000000000000000000140621276456504500204030ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/exceptions" require "chef/win32/api" require "chef/win32/error" require "pathname" class Chef module ReservedNames::Win32 module API module Installer extend Chef::ReservedNames::Win32 extend Chef::ReservedNames::Win32::API ############################################### # Win32 API Constants ############################################### ############################################### # Win32 API Bindings ############################################### ffi_lib "msi" =begin UINT MsiOpenPackage( _In_ LPCTSTR szPackagePath, _Out_ MSIHANDLE *hProduct ); =end safe_attach_function :msi_open_package, :MsiOpenPackageExA, [ :string, :int, :pointer ], :int =begin UINT MsiGetProductProperty( _In_ MSIHANDLE hProduct, _In_ LPCTSTR szProperty, _Out_ LPTSTR lpValueBuf, _Inout_ DWORD *pcchValueBuf ); =end safe_attach_function :msi_get_product_property, :MsiGetProductPropertyA, [ :pointer, :pointer, :pointer, :pointer ], :int =begin UINT MsiGetProductInfo( _In_ LPCTSTR szProduct, _In_ LPCTSTR szProperty, _Out_ LPTSTR lpValueBuf, _Inout_ DWORD *pcchValueBuf ); =end safe_attach_function :msi_get_product_info, :MsiGetProductInfoA, [ :pointer, :pointer, :pointer, :pointer ], :int =begin UINT MsiCloseHandle( _In_ MSIHANDLE hAny ); =end safe_attach_function :msi_close_handle, :MsiCloseHandle, [ :pointer ], :int ############################################### # Helpers ############################################### # Opens a Microsoft Installer (MSI) file from an absolute path and returns the specified property def get_product_property(package_path, property_name) pkg_ptr = open_package(package_path) buffer = 0.chr buffer_length = FFI::Buffer.new(:long).write_long(0) # Fetch the length of the property status = msi_get_product_property(pkg_ptr.read_pointer, property_name, buffer, buffer_length) # We expect error ERROR_MORE_DATA (234) here because we passed a buffer length of 0 if status != 234 msg = "msi_get_product_property: returned unknown error #{status} when retrieving #{property_name}: " msg << Chef::ReservedNames::Win32::Error.format_message(status) raise Chef::Exceptions::Package, msg end buffer_length = FFI::Buffer.new(:long).write_long(buffer_length.read_long + 1) buffer = 0.chr * buffer_length.read_long # Fetch the property status = msi_get_product_property(pkg_ptr.read_pointer, property_name, buffer, buffer_length) if status != 0 msg = "msi_get_product_property: returned unknown error #{status} when retrieving #{property_name}: " msg << Chef::ReservedNames::Win32::Error.format_message(status) raise Chef::Exceptions::Package, msg end msi_close_handle(pkg_ptr.read_pointer) return buffer.chomp(0.chr) end # Opens a Microsoft Installer (MSI) file from an absolute path and returns a pointer to a handle # Remember to close the handle with msi_close_handle() def open_package(package_path) # MsiOpenPackage expects a perfect absolute Windows path to the MSI raise ArgumentError, "Provided path '#{package_path}' must be an absolute path" unless Pathname.new(package_path).absolute? pkg_ptr = FFI::MemoryPointer.new(:pointer, 4) status = msi_open_package(package_path, 1, pkg_ptr) case status when 0 # success else raise Chef::Exceptions::Package, "msi_open_package: unexpected status #{status}: #{Chef::ReservedNames::Win32::Error.format_message(status)}" end return pkg_ptr end # All installed product_codes should have a VersionString # Returns a version if installed, nil if not installed def get_installed_version(product_code) version = 0.chr version_length = FFI::Buffer.new(:long).write_long(0) status = msi_get_product_info(product_code, "VersionString", version, version_length) return nil if status == 1605 # ERROR_UNKNOWN_PRODUCT (0x645) # We expect error ERROR_MORE_DATA (234) here because we passed a buffer length of 0 if status != 234 msg = "msi_get_product_info: product code '#{product_code}' returned unknown error #{status} when retrieving VersionString: " msg << Chef::ReservedNames::Win32::Error.format_message(status) raise Chef::Exceptions::Package, msg end # We could fetch the product version now that we know the variable length, but we don't need it here. version_length = FFI::Buffer.new(:long).write_long(version_length.read_long + 1) version = 0.chr * version_length.read_long status = msi_get_product_info(product_code, "VersionString", version, version_length) if status != 0 msg = "msi_get_product_info: product code '#{product_code}' returned unknown error #{status} when retrieving VersionString: " msg << Chef::ReservedNames::Win32::Error.format_message(status) raise Chef::Exceptions::Package, msg end version.chomp(0.chr) end end end end end chef-12.14.60/lib/chef/win32/api/memory.rb000066400000000000000000000056661276456504500177300ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" class Chef module ReservedNames::Win32 module API module Memory extend Chef::ReservedNames::Win32::API ############################################### # Win32 API Constants ############################################### LMEM_FIXED = 0x0000 LMEM_MOVEABLE = 0x0002 LMEM_NOCOMPACT = 0x0010 LMEM_NODISCARD = 0x0020 LMEM_ZEROINIT = 0x0040 LMEM_MODIFY = 0x0080 LMEM_DISCARDABLE = 0x0F00 LMEM_VALID_FLAGS = 0x0F72 LMEM_INVALID_HANDLE = 0x8000 LHND = LMEM_MOVEABLE | LMEM_ZEROINIT LPTR = LMEM_FIXED | LMEM_ZEROINIT NONZEROLHND = LMEM_MOVEABLE NONZEROLPTR = LMEM_FIXED LMEM_DISCARDED = 0x4000 LMEM_LOCKCOUNT = 0x00FF ############################################### # Win32 API Bindings ############################################### ffi_lib "kernel32" =begin HLOCAL WINAPI LocalAlloc( __in UINT uFlags, __in SIZE_T uBytes ); =end safe_attach_function :LocalAlloc, [ :UINT, :SIZE_T ], :pointer =begin UINT WINAPI LocalFlags( __in HLOCAL hMem ); =end safe_attach_function :LocalFlags, [ :pointer ], :UINT =begin HLOCAL WINAPI LocalFree( __in HLOCAL hMem ); =end safe_attach_function :LocalFree, [ :pointer ], :pointer =begin HLOCAL WINAPI LocalReAlloc( __in HLOCAL hMem, __in SIZE_T uBytes, __in UINT uFlags ); =end safe_attach_function :LocalReAlloc, [ :pointer, :SIZE_T, :UINT ], :pointer =begin UINT WINAPI LocalSize( __in HLOCAL hMem ); =end safe_attach_function :LocalSize, [ :pointer ], :SIZE_T ############################################### # FFI API Bindings ############################################### ffi_lib FFI::Library::LIBC safe_attach_function :malloc, [:size_t], :pointer safe_attach_function :calloc, [:size_t], :pointer safe_attach_function :realloc, [:pointer, :size_t], :pointer safe_attach_function :free, [:pointer], :void safe_attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer end end end end chef-12.14.60/lib/chef/win32/api/net.rb000066400000000000000000000227601276456504500172000ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" require "chef/win32/unicode" class Chef module ReservedNames::Win32 module API module Net extend Chef::ReservedNames::Win32::API FILTER_TEMP_DUPLICATE_ACCOUNT = 0x0001 FILTER_NORMAL_ACCOUNT = 0x0002 FILTER_INTERDOMAIN_TRUST_ACCOUNT = 0x0008 FILTER_WORKSTATION_TRUST_ACCOUNT = 0x0010 FILTER_SERVER_TRUST_ACCOUNT = 0x0020 MAX_PREFERRED_LENGTH = 0xFFFF DOMAIN_GROUP_RID_USERS = 0x00000201 UF_SCRIPT = 0x000001 UF_ACCOUNTDISABLE = 0x000002 UF_PASSWD_CANT_CHANGE = 0x000040 UF_NORMAL_ACCOUNT = 0x000200 UF_DONT_EXPIRE_PASSWD = 0x010000 USE_NOFORCE = 0 USE_FORCE = 1 USE_LOTS_OF_FORCE = 2 #every windows API should support this flag NERR_Success = 0 # rubocop:disable Style/ConstantName ERROR_MORE_DATA = 234 ffi_lib "netapi32" module StructHelpers def set(key, val) val = if val.is_a? String encoded = if val.encoding == Encoding::UTF_16LE val else val.to_wstring end FFI::MemoryPointer.from_string(encoded) else val end self[key] = val end def get(key) if respond_to? key send(key) else val = self[key] if val.is_a? FFI::Pointer if val.null? nil else val.read_wstring end else val end end end def as_ruby members.inject({}) do |memo, key| memo[key] = get(key) memo end end end class USER_INFO_3 < FFI::Struct include StructHelpers layout :usri3_name, :LPWSTR, :usri3_password, :LPWSTR, :usri3_password_age, :DWORD, :usri3_priv, :DWORD, :usri3_home_dir, :LPWSTR, :usri3_comment, :LPWSTR, :usri3_flags, :DWORD, :usri3_script_path, :LPWSTR, :usri3_auth_flags, :DWORD, :usri3_full_name, :LPWSTR, :usri3_usr_comment, :LPWSTR, :usri3_parms, :LPWSTR, :usri3_workstations, :LPWSTR, :usri3_last_logon, :DWORD, :usri3_last_logoff, :DWORD, :usri3_acct_expires, :DWORD, :usri3_max_storage, :DWORD, :usri3_units_per_week, :DWORD, :usri3_logon_hours, :PBYTE, :usri3_bad_pw_count, :DWORD, :usri3_num_logons, :DWORD, :usri3_logon_server, :LPWSTR, :usri3_country_code, :DWORD, :usri3_code_page, :DWORD, :usri3_user_id, :DWORD, :usri3_primary_group_id, :DWORD, :usri3_profile, :LPWSTR, :usri3_home_dir_drive, :LPWSTR, :usri3_password_expired, :DWORD def usri3_logon_hours val = self[:usri3_logon_hours] if !val.nil? && !val.null? val.read_bytes(21) else nil end end end class LOCALGROUP_MEMBERS_INFO_0 < FFI::Struct layout :lgrmi0_sid, :PSID end class LOCALGROUP_MEMBERS_INFO_3 < FFI::Struct layout :lgrmi3_domainandname, :LPWSTR end class LOCALGROUP_INFO_0 < FFI::Struct layout :lgrpi0_name, :LPWSTR end class USE_INFO_2 < FFI::Struct include StructHelpers layout :ui2_local, :LMSTR, :ui2_remote, :LMSTR, :ui2_password, :LMSTR, :ui2_status, :DWORD, :ui2_asg_type, :DWORD, :ui2_refcount, :DWORD, :ui2_usecount, :DWORD, :ui2_username, :LPWSTR, :ui2_domainname, :LMSTR end #NET_API_STATUS NetLocalGroupAdd( #_In_ LPCWSTR servername, #_In_ DWORD level, #_In_ LPBYTE buf, #_Out_ LPDWORD parm_err #); safe_attach_function :NetLocalGroupAdd, [ :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD ], :DWORD #NET_API_STATUS NetLocalGroupDel( #_In_ LPCWSTR servername, #_In_ LPCWSTR groupname #); safe_attach_function :NetLocalGroupDel, [:LPCWSTR, :LPCWSTR], :DWORD #NET_API_STATUS NetLocalGroupGetMembers( #_In_ LPCWSTR servername, #_In_ LPCWSTR localgroupname, #_In_ DWORD level, #_Out_ LPBYTE *bufptr, #_In_ DWORD prefmaxlen, #_Out_ LPDWORD entriesread, #_Out_ LPDWORD totalentries, #_Inout_ PDWORD_PTR resumehandle #); safe_attach_function :NetLocalGroupGetMembers, [ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD, :LPDWORD, :LPDWORD, :PDWORD_PTR ], :DWORD # NET_API_STATUS NetUserEnum( # _In_ LPCWSTR servername, # _In_ DWORD level, # _In_ DWORD filter, # _Out_ LPBYTE *bufptr, # _In_ DWORD prefmaxlen, # _Out_ LPDWORD entriesread, # _Out_ LPDWORD totalentries, # _Inout_ LPDWORD resume_handle # ); safe_attach_function :NetUserEnum, [ :LPCWSTR, :DWORD, :DWORD, :LPBYTE, :DWORD, :LPDWORD, :LPDWORD, :LPDWORD ], :DWORD # NET_API_STATUS NetApiBufferFree( # _In_ LPVOID Buffer # ); safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD #NET_API_STATUS NetUserAdd( #_In_ LMSTR servername, #_In_ DWORD level, #_In_ LPBYTE buf, #_Out_ LPDWORD parm_err #); safe_attach_function :NetUserAdd, [ :LMSTR, :DWORD, :LPBYTE, :LPDWORD ], :DWORD #NET_API_STATUS NetLocalGroupAddMembers( # _In_ LPCWSTR servername, # _In_ LPCWSTR groupname, # _In_ DWORD level, # _In_ LPBYTE buf, # _In_ DWORD totalentries #); safe_attach_function :NetLocalGroupAddMembers, [ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD ], :DWORD #NET_API_STATUS NetLocalGroupSetMembers( # _In_ LPCWSTR servername, # _In_ LPCWSTR groupname, # _In_ DWORD level, # _In_ LPBYTE buf, # _In_ DWORD totalentries #); safe_attach_function :NetLocalGroupSetMembers, [ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD ], :DWORD #NET_API_STATUS NetLocalGroupDelMembers( # _In_ LPCWSTR servername, # _In_ LPCWSTR groupname, # _In_ DWORD level, # _In_ LPBYTE buf, # _In_ DWORD totalentries #); safe_attach_function :NetLocalGroupDelMembers, [ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD ], :DWORD #NET_API_STATUS NetUserGetInfo( # _In_ LPCWSTR servername, # _In_ LPCWSTR username, # _In_ DWORD level, # _Out_ LPBYTE *bufptr #); safe_attach_function :NetUserGetInfo, [ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE ], :DWORD #NET_API_STATUS NetApiBufferFree( # _In_ LPVOID Buffer #); safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD #NET_API_STATUS NetUserSetInfo( # _In_ LPCWSTR servername, # _In_ LPCWSTR username, # _In_ DWORD level, # _In_ LPBYTE buf, # _Out_ LPDWORD parm_err #); safe_attach_function :NetUserSetInfo, [ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD ], :DWORD #NET_API_STATUS NetUserDel( # _In_ LPCWSTR servername, # _In_ LPCWSTR username #); safe_attach_function :NetUserDel, [:LPCWSTR, :LPCWSTR], :DWORD #NET_API_STATUS NetUseDel( #_In_ LMSTR UncServerName, #_In_ LMSTR UseName, #_In_ DWORD ForceCond #); safe_attach_function :NetUseDel, [:LMSTR, :LMSTR, :DWORD], :DWORD #NET_API_STATUS NetUseGetInfo( #_In_ LMSTR UncServerName, #_In_ LMSTR UseName, #_In_ DWORD Level, #_Out_ LPBYTE *BufPtr #); safe_attach_function :NetUseGetInfo, [:LMSTR, :LMSTR, :DWORD, :pointer], :DWORD #NET_API_STATUS NetUseAdd( #_In_ LMSTR UncServerName, #_In_ DWORD Level, #_In_ LPBYTE Buf, #_Out_ LPDWORD ParmError #); safe_attach_function :NetUseAdd, [:LMSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD end end end end chef-12.14.60/lib/chef/win32/api/process.rb000066400000000000000000000025441276456504500200660ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" class Chef module ReservedNames::Win32 module API module Process extend Chef::ReservedNames::Win32::API ############################################### # Win32 API Bindings ############################################### ffi_lib "kernel32" safe_attach_function :GetCurrentProcess, [], :HANDLE safe_attach_function :GetProcessHandleCount, [ :HANDLE, :LPDWORD ], :BOOL safe_attach_function :GetProcessId, [ :HANDLE ], :DWORD safe_attach_function :CloseHandle, [ :HANDLE ], :BOOL safe_attach_function :IsWow64Process, [ :HANDLE, :PBOOL ], :BOOL end end end end chef-12.14.60/lib/chef/win32/api/psapi.rb000066400000000000000000000031011276456504500175120ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" class Chef module ReservedNames::Win32 module API module PSAPI extend Chef::ReservedNames::Win32::API ############################################### # Win32 API Bindings ############################################### class PROCESS_MEMORY_COUNTERS < FFI::Struct layout :cb, :DWORD, :PageFaultCount, :DWORD, :PeakWorkingSetSize, :SIZE_T, :WorkingSetSize, :SIZE_T, :QuotaPeakPagedPoolUsage, :SIZE_T, :QuotaPagedPoolUsage, :SIZE_T, :QuotaPeakNonPagedPoolUsage, :SIZE_T, :QuotaNonPagedPoolUsage, :SIZE_T, :PagefileUsage, :SIZE_T, :PeakPagefileUsage, :SIZE_T end ffi_lib "psapi" safe_attach_function :GetProcessMemoryInfo, [ :HANDLE, :pointer, :DWORD ], :BOOL end end end end chef-12.14.60/lib/chef/win32/api/registry.rb000066400000000000000000000031351276456504500202550ustar00rootroot00000000000000# # Author:: Salim Alam () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" class Chef module ReservedNames::Win32 module API module Registry extend Chef::ReservedNames::Win32::API ############################################### # Win32 API Bindings ############################################### ffi_lib "advapi32" # LONG WINAPI RegDeleteKeyEx( # _In_ HKEY hKey, # _In_ LPCTSTR lpSubKey, # _In_ REGSAM samDesired, # _Reserved_ DWORD Reserved # ); safe_attach_function :RegDeleteKeyExW, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG safe_attach_function :RegDeleteKeyExA, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG # LONG WINAPI RegDeleteValue( # _In_ HKEY hKey, # _In_opt_ LPCTSTR lpValueName # ); safe_attach_function :RegDeleteValueW, [ :HKEY, :LPCTSTR ], :LONG end end end end chef-12.14.60/lib/chef/win32/api/security.rb000066400000000000000000000477161276456504500202710ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" class Chef module ReservedNames::Win32 module API module Security extend Chef::ReservedNames::Win32::API ############################################### # Win32 API Constants ############################################### # ACE_HEADER AceType ACCESS_MIN_MS_ACE_TYPE = 0x0 ACCESS_ALLOWED_ACE_TYPE = 0x0 ACCESS_DENIED_ACE_TYPE = 0x1 SYSTEM_AUDIT_ACE_TYPE = 0x2 SYSTEM_ALARM_ACE_TYPE = 0x3 ACCESS_MAX_MS_V2_ACE_TYPE = 0x3 ACCESS_ALLOWED_COMPOUND_ACE_TYPE = 0x4 ACCESS_MAX_MS_V3_ACE_TYPE = 0x4 ACCESS_MIN_MS_OBJECT_ACE_TYPE = 0x5 ACCESS_ALLOWED_OBJECT_ACE_TYPE = 0x5 ACCESS_DENIED_OBJECT_ACE_TYPE = 0x6 SYSTEM_AUDIT_OBJECT_ACE_TYPE = 0x7 SYSTEM_ALARM_OBJECT_ACE_TYPE = 0x8 ACCESS_MAX_MS_OBJECT_ACE_TYPE = 0x8 ACCESS_MAX_MS_V4_ACE_TYPE = 0x8 ACCESS_MAX_MS_ACE_TYPE = 0x8 ACCESS_ALLOWED_CALLBACK_ACE_TYPE = 0x9 ACCESS_DENIED_CALLBACK_ACE_TYPE = 0xA ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE = 0xB ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE = 0xC SYSTEM_AUDIT_CALLBACK_ACE_TYPE = 0xD SYSTEM_ALARM_CALLBACK_ACE_TYPE = 0xE SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE = 0xF SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE = 0x10 SYSTEM_MANDATORY_LABEL_ACE_TYPE = 0x11 ACCESS_MAX_MS_V5_ACE_TYPE = 0x11 # ACE_HEADER AceFlags OBJECT_INHERIT_ACE = 0x1 CONTAINER_INHERIT_ACE = 0x2 NO_PROPAGATE_INHERIT_ACE = 0x4 INHERIT_ONLY_ACE = 0x8 INHERITED_ACE = 0x10 VALID_INHERIT_FLAGS = 0x1F SUCCESSFUL_ACCESS_ACE_FLAG = 0x40 FAILED_ACCESS_ACE_FLAG = 0x80 # SECURITY_INFORMATION flags (DWORD) OWNER_SECURITY_INFORMATION = 0x01 GROUP_SECURITY_INFORMATION = 0x02 DACL_SECURITY_INFORMATION = 0x04 SACL_SECURITY_INFORMATION = 0x08 LABEL_SECURITY_INFORMATION = 0x10 UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000 UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000 PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000 PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 # SECURITY_DESCRIPTOR_REVISION SECURITY_DESCRIPTOR_REVISION = 1 SECURITY_DESCRIPTOR_REVISION1 = 1 # SECURITY_DESCRIPTOR_CONTROL SE_OWNER_DEFAULTED = 0x0001 SE_GROUP_DEFAULTED = 0x0002 SE_DACL_PRESENT = 0x0004 SE_DACL_DEFAULTED = 0x0008 SE_SACL_PRESENT = 0x0010 SE_SACL_DEFAULTED = 0x0020 SE_DACL_AUTO_INHERIT_REQ = 0x0100 SE_SACL_AUTO_INHERIT_REQ = 0x0200 SE_DACL_AUTO_INHERITED = 0x0400 SE_SACL_AUTO_INHERITED = 0x0800 SE_DACL_PROTECTED = 0x1000 SE_SACL_PROTECTED = 0x2000 SE_RM_CONTROL_VALID = 0x4000 SE_SELF_RELATIVE = 0x8000 # ACCESS_RIGHTS_MASK # Generic Access Rights GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 GENERIC_EXECUTE = 0x20000000 GENERIC_ALL = 0x10000000 # Standard Access Rights DELETE = 0x00010000 READ_CONTROL = 0x00020000 WRITE_DAC = 0x00040000 WRITE_OWNER = 0x00080000 SYNCHRONIZE = 0x00100000 STANDARD_RIGHTS_REQUIRED = 0x000F0000 STANDARD_RIGHTS_READ = READ_CONTROL STANDARD_RIGHTS_WRITE = READ_CONTROL STANDARD_RIGHTS_EXECUTE = READ_CONTROL STANDARD_RIGHTS_ALL = 0x001F0000 SPECIFIC_RIGHTS_ALL = 0x0000FFFF # Access System Security Right ACCESS_SYSTEM_SECURITY = 0x01000000 # File/Directory Specific Rights FILE_READ_DATA = 0x0001 FILE_LIST_DIRECTORY = 0x0001 FILE_WRITE_DATA = 0x0002 FILE_ADD_FILE = 0x0002 FILE_APPEND_DATA = 0x0004 FILE_ADD_SUBDIRECTORY = 0x0004 FILE_CREATE_PIPE_INSTANCE = 0x0004 FILE_READ_EA = 0x0008 FILE_WRITE_EA = 0x0010 FILE_EXECUTE = 0x0020 FILE_TRAVERSE = 0x0020 FILE_DELETE_CHILD = 0x0040 FILE_READ_ATTRIBUTES = 0x0080 FILE_WRITE_ATTRIBUTES = 0x0100 FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF FILE_GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE # Access Token Rights (for OpenProcessToken) # Access Rights for Access-Token Objects (used in OpenProcessToken) TOKEN_ASSIGN_PRIMARY = 0x0001 TOKEN_DUPLICATE = 0x0002 TOKEN_IMPERSONATE = 0x0004 TOKEN_QUERY = 0x0008 TOKEN_QUERY_SOURCE = 0x0010 TOKEN_ADJUST_PRIVILEGES = 0x0020 TOKEN_ADJUST_GROUPS = 0x0040 TOKEN_ADJUST_DEFAULT = 0x0080 TOKEN_ADJUST_SESSIONID = 0x0100 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY) TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID) # AdjustTokenPrivileges SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001 SE_PRIVILEGE_ENABLED = 0x00000002 SE_PRIVILEGE_REMOVED = 0X00000004 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000 SE_PRIVILEGE_VALID_ATTRIBUTES = SE_PRIVILEGE_ENABLED_BY_DEFAULT | SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_REMOVED | SE_PRIVILEGE_USED_FOR_ACCESS # Minimum size of a SECURITY_DESCRIPTOR. TODO: this is probably platform dependent. # Make it work on 64 bit. SECURITY_DESCRIPTOR_MIN_LENGTH = 20 # ACL revisions ACL_REVISION = 2 ACL_REVISION_DS = 4 ACL_REVISION1 = 1 ACL_REVISION2 = 2 ACL_REVISION3 = 3 ACL_REVISION4 = 4 MIN_ACL_REVISION = ACL_REVISION2 MAX_ACL_REVISION = ACL_REVISION4 MAXDWORD = 0xffffffff # LOGON32 constants for LogonUser LOGON32_LOGON_INTERACTIVE = 2; LOGON32_LOGON_NETWORK = 3; LOGON32_LOGON_BATCH = 4; LOGON32_LOGON_SERVICE = 5; LOGON32_LOGON_UNLOCK = 7; LOGON32_LOGON_NETWORK_CLEARTEXT = 8; LOGON32_LOGON_NEW_CREDENTIALS = 9; LOGON32_PROVIDER_DEFAULT = 0; LOGON32_PROVIDER_WINNT35 = 1; LOGON32_PROVIDER_WINNT40 = 2; LOGON32_PROVIDER_WINNT50 = 3; # LSA access policy POLICY_VIEW_LOCAL_INFORMATION = 0x00000001 POLICY_VIEW_AUDIT_INFORMATION = 0x00000002 POLICY_GET_PRIVATE_INFORMATION = 0x00000004 POLICY_TRUST_ADMIN = 0x00000008 POLICY_CREATE_ACCOUNT = 0x00000010 POLICY_CREATE_SECRET = 0x00000020 POLICY_CREATE_PRIVILEGE = 0x00000040 POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080 POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100 POLICY_AUDIT_LOG_ADMIN = 0x00000200 POLICY_SERVER_ADMIN = 0x00000400 POLICY_LOOKUP_NAMES = 0x00000800 POLICY_NOTIFICATION = 0x00001000 ############################################### # Win32 API Bindings ############################################### SE_OBJECT_TYPE = enum :SE_OBJECT_TYPE, [ :SE_UNKNOWN_OBJECT_TYPE, :SE_FILE_OBJECT, :SE_SERVICE, :SE_PRINTER, :SE_REGISTRY_KEY, :SE_LMSHARE, :SE_KERNEL_OBJECT, :SE_WINDOW_OBJECT, :SE_DS_OBJECT, :SE_DS_OBJECT_ALL, :SE_PROVIDER_DEFINED_OBJECT, :SE_WMIGUID_OBJECT, :SE_REGISTRY_WOW64_32KEY, ] SID_NAME_USE = enum :SID_NAME_USE, [ :SidTypeUser, 1, :SidTypeGroup, :SidTypeDomain, :SidTypeAlias, :SidTypeWellKnownGroup, :SidTypeDeletedAccount, :SidTypeInvalid, :SidTypeUnknown, :SidTypeComputer, :SidTypeLabel ] TOKEN_INFORMATION_CLASS = enum :TOKEN_INFORMATION_CLASS, [ :TokenUser, 1, :TokenGroups, :TokenPrivileges, :TokenOwner, :TokenPrimaryGroup, :TokenDefaultDacl, :TokenSource, :TokenType, :TokenImpersonationLevel, :TokenStatistics, :TokenRestrictedSids, :TokenSessionId, :TokenGroupsAndPrivileges, :TokenSessionReference, :TokenSandBoxInert, :TokenAuditPolicy, :TokenOrigin, :TokenElevationType, :TokenLinkedToken, :TokenElevation, :TokenHasRestrictions, :TokenAccessInformation, :TokenVirtualizationAllowed, :TokenVirtualizationEnabled, :TokenIntegrityLevel, :TokenUIAccess, :TokenMandatoryPolicy, :TokenLogonSid, :TokenIsAppContainer, :TokenCapabilities, :TokenAppContainerSid, :TokenAppContainerNumber, :TokenUserClaimAttributes, :TokenDeviceClaimAttributes, :TokenRestrictedUserClaimAttributes, :TokenRestrictedDeviceClaimAttributes, :TokenDeviceGroups, :TokenRestrictedDeviceGroups, :TokenSecurityAttributes, :TokenIsRestricted, :MaxTokenInfoClass ] class TOKEN_OWNER < FFI::Struct layout :Owner, :pointer end class TOKEN_PRIMARY_GROUP < FFI::Struct layout :PrimaryGroup, :pointer end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379572%28v=vs.85%29.aspx SECURITY_IMPERSONATION_LEVEL = enum :SECURITY_IMPERSONATION_LEVEL, [ :SecurityAnonymous, :SecurityIdentification, :SecurityImpersonation, :SecurityDelegation, ] # SECURITY_DESCRIPTOR is an opaque structure whose contents can vary. Pass the # pointer around and free it with LocalFree. # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379561(v=vs.85).aspx # SID is an opaque structure. Pass the pointer around. # ACL type is a header with some information, followed by an array of ACEs # http://msdn.microsoft.com/en-us/library/windows/desktop/aa374931(v=VS.85).aspx class ACLStruct < FFI::Struct layout :AclRevision, :uchar, :Sbzl, :uchar, :AclSize, :ushort, :AceCount, :ushort, :Sbz2, :ushort end class ACE_HEADER < FFI::Struct layout :AceType, :uchar, :AceFlags, :uchar, :AceSize, :ushort end class ACE_WITH_MASK_AND_SID < FFI::Struct layout :AceType, :uchar, :AceFlags, :uchar, :AceSize, :ushort, :Mask, :uint32, :SidStart, :uint32 # The AceTypes this structure supports def self.supports?(ace_type) [ ACCESS_ALLOWED_ACE_TYPE, ACCESS_DENIED_ACE_TYPE, SYSTEM_AUDIT_ACE_TYPE, SYSTEM_ALARM_ACE_TYPE, ].include?(ace_type) end end class LUID < FFI::Struct layout :LowPart, :DWORD, :HighPart, :LONG end class LUID_AND_ATTRIBUTES < FFI::Struct layout :Luid, LUID, :Attributes, :DWORD end class GENERIC_MAPPING < FFI::Struct layout :GenericRead, :DWORD, :GenericWrite, :DWORD, :GenericExecute, :DWORD, :GenericAll, :DWORD end class PRIVILEGE_SET < FFI::Struct layout :PrivilegeCount, :DWORD, :Control, :DWORD, :Privilege, [LUID_AND_ATTRIBUTES, 1] end class TOKEN_PRIVILEGES < FFI::Struct layout :PrivilegeCount, :DWORD, :Privileges, LUID_AND_ATTRIBUTES def self.size_with_privileges(num_privileges) offset_of(:Privileges) + LUID_AND_ATTRIBUTES.size * num_privileges end def size_with_privileges TOKEN_PRIVILEGES.size_with_privileges(self[:PrivilegeCount]) end def privilege(index) LUID_AND_ATTRIBUTES.new(pointer + offset_of(:Privileges) + (index * LUID_AND_ATTRIBUTES.size)) end end # https://msdn.microsoft.com/en-us/library/windows/desktop/ms721829(v=vs.85).aspx class LSA_OBJECT_ATTRIBUTES < FFI::Struct layout :Length, :ULONG, :RootDirectory, :HANDLE, :ObjectName, :pointer, :Attributes, :ULONG, :SecurityDescriptor, :PVOID, :SecurityQualityOfService, :PVOID end # https://msdn.microsoft.com/en-us/library/windows/desktop/ms721841(v=vs.85).aspx class LSA_UNICODE_STRING < FFI::Struct layout :Length, :USHORT, :MaximumLength, :USHORT, :Buffer, :PWSTR end ffi_lib "advapi32" safe_attach_function :AccessCheck, [:pointer, :HANDLE, :DWORD, :pointer, :pointer, :pointer, :pointer, :pointer], :BOOL safe_attach_function :AddAce, [ :pointer, :DWORD, :DWORD, :LPVOID, :DWORD ], :BOOL safe_attach_function :AddAccessAllowedAce, [ :pointer, :DWORD, :DWORD, :pointer ], :BOOL safe_attach_function :AddAccessAllowedAceEx, [ :pointer, :DWORD, :DWORD, :DWORD, :pointer ], :BOOL safe_attach_function :AddAccessDeniedAce, [ :pointer, :DWORD, :DWORD, :pointer ], :BOOL safe_attach_function :AddAccessDeniedAceEx, [ :pointer, :DWORD, :DWORD, :DWORD, :pointer ], :BOOL safe_attach_function :AdjustTokenPrivileges, [ :HANDLE, :BOOL, :pointer, :DWORD, :pointer, :PDWORD ], :BOOL safe_attach_function :ConvertSidToStringSidA, [ :pointer, :pointer ], :BOOL safe_attach_function :ConvertStringSidToSidW, [ :pointer, :pointer ], :BOOL safe_attach_function :DeleteAce, [ :pointer, :DWORD ], :BOOL safe_attach_function :DuplicateToken, [:HANDLE, :SECURITY_IMPERSONATION_LEVEL, :PHANDLE], :BOOL safe_attach_function :EqualSid, [ :pointer, :pointer ], :BOOL safe_attach_function :FreeSid, [ :pointer ], :pointer safe_attach_function :GetAce, [ :pointer, :DWORD, :pointer ], :BOOL safe_attach_function :GetFileSecurityW, [:LPCWSTR, :DWORD, :pointer, :DWORD, :pointer], :BOOL safe_attach_function :GetLengthSid, [ :pointer ], :DWORD safe_attach_function :GetNamedSecurityInfoW, [ :LPWSTR, :SE_OBJECT_TYPE, :DWORD, :pointer, :pointer, :pointer, :pointer, :pointer ], :DWORD safe_attach_function :GetSecurityDescriptorControl, [ :pointer, :PWORD, :LPDWORD], :BOOL safe_attach_function :GetSecurityDescriptorDacl, [ :pointer, :LPBOOL, :pointer, :LPBOOL ], :BOOL safe_attach_function :GetSecurityDescriptorGroup, [ :pointer, :pointer, :LPBOOL], :BOOL safe_attach_function :GetSecurityDescriptorOwner, [ :pointer, :pointer, :LPBOOL], :BOOL safe_attach_function :GetSecurityDescriptorSacl, [ :pointer, :LPBOOL, :pointer, :LPBOOL ], :BOOL safe_attach_function :InitializeAcl, [ :pointer, :DWORD, :DWORD ], :BOOL safe_attach_function :InitializeSecurityDescriptor, [ :pointer, :DWORD ], :BOOL safe_attach_function :IsValidAcl, [ :pointer ], :BOOL safe_attach_function :IsValidSecurityDescriptor, [ :pointer ], :BOOL safe_attach_function :IsValidSid, [ :pointer ], :BOOL safe_attach_function :LookupAccountNameW, [ :LPCWSTR, :LPCWSTR, :pointer, :LPDWORD, :LPWSTR, :LPDWORD, :pointer ], :BOOL safe_attach_function :LookupAccountSidW, [ :LPCWSTR, :pointer, :LPWSTR, :LPDWORD, :LPWSTR, :LPDWORD, :pointer ], :BOOL safe_attach_function :LookupPrivilegeNameW, [ :LPCWSTR, :PLUID, :LPWSTR, :LPDWORD ], :BOOL safe_attach_function :LookupPrivilegeDisplayNameW, [ :LPCWSTR, :LPCWSTR, :LPWSTR, :LPDWORD, :LPDWORD ], :BOOL safe_attach_function :LookupPrivilegeValueW, [ :LPCWSTR, :LPCWSTR, :PLUID ], :BOOL safe_attach_function :LsaAddAccountRights, [ :pointer, :pointer, :pointer, :ULONG ], :NTSTATUS safe_attach_function :LsaClose, [ :LSA_HANDLE ], :NTSTATUS safe_attach_function :LsaEnumerateAccountRights, [ :LSA_HANDLE, :PSID, :PLSA_UNICODE_STRING, :PULONG ], :NTSTATUS safe_attach_function :LsaFreeMemory, [ :PVOID ], :NTSTATUS safe_attach_function :LsaNtStatusToWinError, [ :NTSTATUS ], :ULONG safe_attach_function :LsaOpenPolicy, [ :PLSA_UNICODE_STRING, :PLSA_OBJECT_ATTRIBUTES, :DWORD, :PLSA_HANDLE ], :NTSTATUS safe_attach_function :MakeAbsoluteSD, [ :pointer, :pointer, :LPDWORD, :pointer, :LPDWORD, :pointer, :LPDWORD, :pointer, :LPDWORD, :pointer, :LPDWORD], :BOOL safe_attach_function :MapGenericMask, [ :PDWORD, :PGENERICMAPPING ], :void safe_attach_function :OpenProcessToken, [ :HANDLE, :DWORD, :PHANDLE ], :BOOL safe_attach_function :QuerySecurityAccessMask, [ :DWORD, :LPDWORD ], :void safe_attach_function :SetFileSecurityW, [ :LPWSTR, :DWORD, :pointer ], :BOOL safe_attach_function :SetNamedSecurityInfoW, [ :LPWSTR, :SE_OBJECT_TYPE, :DWORD, :pointer, :pointer, :pointer, :pointer ], :DWORD safe_attach_function :SetSecurityAccessMask, [ :DWORD, :LPDWORD ], :void safe_attach_function :SetSecurityDescriptorDacl, [ :pointer, :BOOL, :pointer, :BOOL ], :BOOL safe_attach_function :SetSecurityDescriptorGroup, [ :pointer, :pointer, :BOOL ], :BOOL safe_attach_function :SetSecurityDescriptorOwner, [ :pointer, :pointer, :BOOL ], :BOOL safe_attach_function :SetSecurityDescriptorSacl, [ :pointer, :BOOL, :pointer, :BOOL ], :BOOL safe_attach_function :GetTokenInformation, [ :HANDLE, :TOKEN_INFORMATION_CLASS, :pointer, :DWORD, :PDWORD ], :BOOL safe_attach_function :LogonUserW, [:LPTSTR, :LPTSTR, :LPTSTR, :DWORD, :DWORD, :PHANDLE], :BOOL end end end end chef-12.14.60/lib/chef/win32/api/synchronization.rb000066400000000000000000000051121276456504500216430ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" class Chef module ReservedNames::Win32 module API module Synchronization extend Chef::ReservedNames::Win32::API ffi_lib "kernel32" # Constant synchronization functions use to indicate wait # forever. INFINITE = 0xFFFFFFFF # Return codes # http://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx WAIT_FAILED = 0xFFFFFFFF WAIT_TIMEOUT = 0x00000102 WAIT_OBJECT_0 = 0x00000000 WAIT_ABANDONED = 0x00000080 # Security and access rights for synchronization objects # http://msdn.microsoft.com/en-us/library/windows/desktop/ms686670(v=vs.85).aspx DELETE = 0x00010000 READ_CONTROL = 0x00020000 SYNCHRONIZE = 0x00100000 WRITE_DAC = 0x00040000 WRITE_OWNER = 0x00080000 # Mutex specific rights MUTEX_ALL_ACCESS = 0x001F0001 MUTEX_MODIFY_STATE = 0x00000001 =begin HANDLE WINAPI CreateMutex( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, _In_ BOOL bInitialOwner, _In_opt_ LPCTSTR lpName ); =end safe_attach_function :CreateMutexW, [ :LPSECURITY_ATTRIBUTES, :BOOL, :LPCTSTR ], :HANDLE safe_attach_function :CreateMutexA, [ :LPSECURITY_ATTRIBUTES, :BOOL, :LPCTSTR ], :HANDLE =begin DWORD WINAPI WaitForSingleObject( _In_ HANDLE hHandle, _In_ DWORD dwMilliseconds ); =end safe_attach_function :WaitForSingleObject, [ :HANDLE, :DWORD ], :DWORD =begin BOOL WINAPI ReleaseMutex( _In_ HANDLE hMutex ); =end safe_attach_function :ReleaseMutex, [ :HANDLE ], :BOOL =begin HANDLE WINAPI OpenMutex( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ LPCTSTR lpName ); =end safe_attach_function :OpenMutexW, [ :DWORD, :BOOL, :LPCTSTR ], :HANDLE safe_attach_function :OpenMutexA, [ :DWORD, :BOOL, :LPCTSTR ], :HANDLE end end end end chef-12.14.60/lib/chef/win32/api/system.rb000066400000000000000000000256061276456504500177400ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" class Chef module ReservedNames::Win32 module API module System extend Chef::ReservedNames::Win32::API ############################################### # Win32 API Constants ############################################### # http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx # Suite Masks # Microsoft BackOffice components are installed. VER_SUITE_BACKOFFICE = 0x00000004 # Windows Server 2003, Web Edition is installed. VER_SUITE_BLADE = 0x00000400 # Windows Server 2003, Compute Cluster Edition is installed. VER_SUITE_COMPUTE_SERVER = 0x00004000 # Windows Server 2008 Datacenter, Windows Server 2003, Datacenter Edition, or Windows 2000 Datacenter Server is installed. VER_SUITE_DATACENTER = 0x00000080 # Windows Server 2008 Enterprise, Windows Server 2003, Enterprise Edition, or Windows 2000 Advanced Server is installed. Refer to the Remarks section for more information about this bit flag. VER_SUITE_ENTERPRISE = 0x00000002 # Windows XP Embedded is installed. VER_SUITE_EMBEDDEDNT = 0x00000040 # Windows Vista Home Premium, Windows Vista Home Basic, or Windows XP Home Edition is installed. VER_SUITE_PERSONAL = 0x00000200 # Remote Desktop is supported, but only one interactive session is supported. This value is set unless the system is running in application server mode. VER_SUITE_SINGLEUSERTS = 0x00000100 # Microsoft Small Business Server was once installed on the system, but may have been upgraded to another version of Windows. Refer to the Remarks section for more information about this bit flag. VER_SUITE_SMALLBUSINESS = 0x00000001 # Microsoft Small Business Server is installed with the restrictive client license in force. Refer to the Remarks section for more information about this bit flag. VER_SUITE_SMALLBUSINESS_RESTRICTED = 0x00000020 # Windows Storage Server 2003 R2 or Windows Storage Server 2003is installed. VER_SUITE_STORAGE_SERVER = 0x00002000 # Terminal Services is installed. This value is always set. # If VER_SUITE_TERMINAL is set but VER_SUITE_SINGLEUSERTS is not set, the system is running in application server mode. VER_SUITE_TERMINAL = 0x00000010 # Windows Home Server is installed. VER_SUITE_WH_SERVER = 0x00008000 # Product Type # The system is a domain controller and the operating system is Windows Server 2008 R2, Windows Server 2008, Windows Server 2003, or Windows 2000 Server. VER_NT_DOMAIN_CONTROLLER = 0x0000002 # The operating system is Windows Server 2008 R2, Windows Server 2008, Windows Server 2003, or Windows 2000 Server. # Note that a server that is also a domain controller is reported as VER_NT_DOMAIN_CONTROLLER, not VER_NT_SERVER. VER_NT_SERVER = 0x0000003 # The operating system is Windows 7, Windows Vista, Windows XP Professional, Windows XP Home Edition, or Windows 2000 Professional. VER_NT_WORKSTATION = 0x0000001 # Product Info # http://msdn.microsoft.com/en-us/library/ms724358(v=vs.85).aspx PRODUCT_BUSINESS = 0x00000006 # Business PRODUCT_BUSINESS_N = 0x00000010 # Business N PRODUCT_CLUSTER_SERVER = 0x00000012 # HPC Edition PRODUCT_DATACENTER_SERVER = 0x00000008 # Server Datacenter (full installation) PRODUCT_DATACENTER_SERVER_CORE = 0x0000000C # Server Datacenter (core installation) PRODUCT_DATACENTER_SERVER_CORE_V = 0x00000027 # Server Datacenter without Hyper-V (core installation) PRODUCT_DATACENTER_SERVER_V = 0x00000025 # Server Datacenter without Hyper-V (full installation) PRODUCT_ENTERPRISE = 0x00000004 # Enterprise PRODUCT_ENTERPRISE_E = 0x00000046 # Not supported PRODUCT_ENTERPRISE_N = 0x0000001B # Enterprise N PRODUCT_ENTERPRISE_SERVER = 0x0000000A # Server Enterprise (full installation) PRODUCT_ENTERPRISE_SERVER_CORE = 0x0000000E # Server Enterprise (core installation) PRODUCT_ENTERPRISE_SERVER_CORE_V = 0x00000029 # Server Enterprise without Hyper-V (core installation) PRODUCT_ENTERPRISE_SERVER_IA64 = 0x0000000F # Server Enterprise for Itanium-based Systems PRODUCT_ENTERPRISE_SERVER_V = 0x00000026 # Server Enterprise without Hyper-V (full installation) PRODUCT_HOME_BASIC = 0x00000002 # Home Basic PRODUCT_HOME_BASIC_E = 0x00000043 # Not supported PRODUCT_HOME_BASIC_N = 0x00000005 # Home Basic N PRODUCT_HOME_PREMIUM = 0x00000003 # Home Premium PRODUCT_HOME_PREMIUM_E = 0x00000044 # Not supported PRODUCT_HOME_PREMIUM_N = 0x0000001A # Home Premium N PRODUCT_HYPERV = 0x0000002A # Microsoft Hyper-V Server PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT = 0x0000001E # Windows Essential Business Server Management Server PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING = 0x00000020 # Windows Essential Business Server Messaging Server PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY = 0x0000001F # Windows Essential Business Server Security Server PRODUCT_PROFESSIONAL = 0x00000030 # Professional PRODUCT_PROFESSIONAL_E = 0x00000045 # Not supported PRODUCT_PROFESSIONAL_N = 0x00000031 # Professional N PRODUCT_SERVER_FOR_SMALLBUSINESS = 0x00000018 # Windows Server 2008 for Windows Essential Server Solutions PRODUCT_SERVER_FOR_SMALLBUSINESS_V = 0x00000023 # Windows Server 2008 without Hyper-V for Windows Essential Server Solutions PRODUCT_SERVER_FOUNDATION = 0x00000021 # Server Foundation PRODUCT_HOME_PREMIUM_SERVER = 0x00000022 # Windows Home Server 2011 PRODUCT_SB_SOLUTION_SERVER = 0x00000032 # Windows Small Business Server 2011 Essentials PRODUCT_HOME_SERVER = 0x00000013 # Windows Storage Server 2008 R2 Essentials PRODUCT_SMALLBUSINESS_SERVER = 0x00000009 # Windows Small Business Server PRODUCT_SOLUTION_EMBEDDEDSERVER = 0x00000038 # Windows MultiPoint Server PRODUCT_STANDARD_SERVER = 0x00000007 # Server Standard (full installation) PRODUCT_STANDARD_SERVER_CORE = 0x0000000D # Server Standard (core installation) PRODUCT_STANDARD_SERVER_CORE_V = 0x00000028 # Server Standard without Hyper-V (core installation) PRODUCT_STANDARD_SERVER_V = 0x00000024 # Server Standard without Hyper-V (full installation) PRODUCT_STARTER = 0x0000000B # Starter PRODUCT_STARTER_E = 0x00000042 # Not supported PRODUCT_STARTER_N = 0x0000002F # Starter N PRODUCT_STORAGE_ENTERPRISE_SERVER = 0x00000017 # Storage Server Enterprise PRODUCT_STORAGE_EXPRESS_SERVER = 0x00000014 # Storage Server Express PRODUCT_STORAGE_STANDARD_SERVER = 0x00000015 # Storage Server Standard PRODUCT_STORAGE_WORKGROUP_SERVER = 0x00000016 # Storage Server Workgroup PRODUCT_UNDEFINED = 0x00000000 # An unknown product PRODUCT_ULTIMATE = 0x00000001 # Ultimate PRODUCT_ULTIMATE_E = 0x00000047 # Not supported PRODUCT_ULTIMATE_N = 0x0000001C # Ultimate N PRODUCT_WEB_SERVER = 0x00000011 # Web Server (full installation) PRODUCT_WEB_SERVER_CORE = 0x0000001D # Web Server (core installation) # GetSystemMetrics # The build number if the system is Windows Server 2003 R2; otherwise, 0. SM_SERVERR2 = 89 ############################################### # Win32 API Bindings ############################################### ffi_lib "kernel32", "user32" class OSVERSIONINFOEX < FFI::Struct layout :dw_os_version_info_size, :DWORD, :dw_major_version, :DWORD, :dw_minor_version, :DWORD, :dw_build_number, :DWORD, :dw_platform_id, :DWORD, :sz_csd_version, [:BYTE, 256], :w_service_pack_major, :WORD, :w_service_pack_minor, :WORD, :w_suite_mask, :WORD, :w_product_type, :BYTE, :w_reserved, :BYTE end =begin BOOL WINAPI CloseHandle( __in HANDLE hObject ); =end safe_attach_function :CloseHandle, [ :HANDLE ], :BOOL =begin DWORD WINAPI GetVersion(void); =end safe_attach_function :GetVersion, [], :DWORD =begin BOOL WINAPI GetVersionEx( __inout LPOSVERSIONINFO lpVersionInfo ); =end safe_attach_function :GetVersionExW, [:pointer], :BOOL safe_attach_function :GetVersionExA, [:pointer], :BOOL =begin BOOL WINAPI GetProductInfo( __in DWORD dwOSMajorVersion, __in DWORD dwOSMinorVersion, __in DWORD dwSpMajorVersion, __in DWORD dwSpMinorVersion, __out PDWORD pdwReturnedProductType ); =end safe_attach_function :GetProductInfo, [:DWORD, :DWORD, :DWORD, :DWORD, :PDWORD], :BOOL =begin int WINAPI GetSystemMetrics( __in int nIndex ); =end safe_attach_function :GetSystemMetrics, [:int], :int =begin UINT WINAPI GetSystemWow64Directory( _Out_ LPTSTR lpBuffer, _In_ UINT uSize ); =end safe_attach_function :GetSystemWow64DirectoryW, [:LPTSTR, :UINT], :UINT safe_attach_function :GetSystemWow64DirectoryA, [:LPTSTR, :UINT], :UINT =begin BOOL WINAPI Wow64DisableWow64FsRedirection( _Out_ PVOID *OldValue ); =end safe_attach_function :Wow64DisableWow64FsRedirection, [:PVOID], :BOOL =begin BOOL WINAPI Wow64RevertWow64FsRedirection( _In_ PVOID OldValue ); =end safe_attach_function :Wow64RevertWow64FsRedirection, [:PVOID], :BOOL =begin LRESULT WINAPI SendMessageTimeout( _In_ HWND hWnd, _In_ UINT Msg, _In_ WPARAM wParam, _In_ LPARAM lParam, _In_ UINT fuFlags, _In_ UINT uTimeout, _Out_opt_ PDWORD_PTR lpdwResult ); =end safe_attach_function :SendMessageTimeoutW, [:HWND, :UINT, :WPARAM, :LPARAM, :UINT, :UINT, :PDWORD_PTR], :LRESULT safe_attach_function :SendMessageTimeoutA, [:HWND, :UINT, :WPARAM, :LPARAM, :UINT, :UINT, :PDWORD_PTR], :LRESULT =begin DWORD WINAPI ExpandEnvironmentStrings( _In_ LPCTSTR lpSrc, _Out_opt_ LPTSTR lpDst, _In_ DWORD nSize ); =end safe_attach_function :ExpandEnvironmentStringsW, [:pointer, :pointer, :DWORD], :DWORD safe_attach_function :ExpandEnvironmentStringsA, [:pointer, :pointer, :DWORD], :DWORD end end end end chef-12.14.60/lib/chef/win32/api/unicode.rb000066400000000000000000000101721276456504500200320ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" class Chef module ReservedNames::Win32 module API module Unicode extend Chef::ReservedNames::Win32::API ############################################### # Win32 API Constants ############################################### CP_ACP = 0 CP_OEMCP = 1 CP_MACCP = 2 CP_THREAD_ACP = 3 CP_SYMBOL = 42 CP_UTF7 = 65000 CP_UTF8 = 65001 MB_PRECOMPOSED = 0x00000001 MB_COMPOSITE = 0x00000002 MB_USEGLYPHCHARS = 0x00000004 MB_ERR_INVALID_CHARS = 0x00000008 WC_COMPOSITECHECK = 0x00000200 WC_DISCARDNS = 0x00000010 WC_SEPCHARS = 0x00000020 WC_DEFAULTCHAR = 0x00000040 WC_NO_BEST_FIT_CHARS = 0x00000400 ANSI_CHARSET = 0 DEFAULT_CHARSET = 1 SYMBOL_CHARSET = 2 SHIFTJIS_CHARSET = 128 HANGEUL_CHARSET = 129 HANGUL_CHARSET = 129 GB2312_CHARSET = 134 CHINESEBIG5_CHARSET = 136 OEM_CHARSET = 255 JOHAB_CHARSET = 130 HEBREW_CHARSET = 177 ARABIC_CHARSET = 178 GREEK_CHARSET = 161 TURKISH_CHARSET = 162 VIETNAMESE_CHARSET = 163 THAI_CHARSET = 222 EASTEUROPE_CHARSET = 238 RUSSIAN_CHARSET = 204 IS_TEXT_UNICODE_ASCII16 = 0x0001 IS_TEXT_UNICODE_REVERSE_ASCII16 = 0x0010 IS_TEXT_UNICODE_STATISTICS = 0x0002 IS_TEXT_UNICODE_REVERSE_STATISTICS = 0x0020 IS_TEXT_UNICODE_CONTROLS = 0x0004 IS_TEXT_UNICODE_REVERSE_CONTROLS = 0x0040 IS_TEXT_UNICODE_SIGNATURE = 0x0008 IS_TEXT_UNICODE_REVERSE_SIGNATURE = 0x0080 IS_TEXT_UNICODE_ILLEGAL_CHARS = 0x0100 IS_TEXT_UNICODE_ODD_LENGTH = 0x0200 IS_TEXT_UNICODE_DBCS_LEADBYTE = 0x0400 IS_TEXT_UNICODE_NULL_BYTES = 0x1000 IS_TEXT_UNICODE_UNICODE_MASK = 0x000F IS_TEXT_UNICODE_REVERSE_MASK = 0x00F0 IS_TEXT_UNICODE_NOT_UNICODE_MASK = 0x0F00 IS_TEXT_UNICODE_NOT_ASCII_MASK = 0xF000 TCI_SRCCHARSET = 1 TCI_SRCCODEPAGE = 2 TCI_SRCFONTSIG = 3 TCI_SRCLOCALE = 0x100 ############################################### # Win32 API Bindings ############################################### ffi_lib "kernel32", "advapi32" =begin BOOL IsTextUnicode( __in const VOID *lpv, __in int iSize, __inout LPINT lpiResult ); =end safe_attach_function :IsTextUnicode, [:pointer, :int, :LPINT], :BOOL =begin int MultiByteToWideChar( __in UINT CodePage, __in DWORD dwFlags, __in LPCSTR lpMultiByteStr, __in int cbMultiByte, __out LPWSTR lpWideCharStr, __in int cchWideChar ); =end safe_attach_function :MultiByteToWideChar, [:UINT, :DWORD, :LPCSTR, :int, :LPWSTR, :int], :int =begin int WideCharToMultiByte( __in UINT CodePage, __in DWORD dwFlags, __in LPCWSTR lpWideCharStr, __in int cchWideChar, __out LPSTR lpMultiByteStr, __in int cbMultiByte, __in LPCSTR lpDefaultChar, __out LPBOOL lpUsedDefaultChar ); =end safe_attach_function :WideCharToMultiByte, [:UINT, :DWORD, :LPCWSTR, :int, :LPSTR, :int, :LPCSTR, :LPBOOL], :int end end end end chef-12.14.60/lib/chef/win32/crypto.rb000066400000000000000000000031071276456504500171530ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/error" require "chef/win32/api/memory" require "chef/win32/api/crypto" require "chef/win32/unicode" require "digest" class Chef module ReservedNames::Win32 class Crypto include Chef::ReservedNames::Win32::API::Crypto extend Chef::ReservedNames::Win32::API::Crypto def self.encrypt(str, &block) data_blob = CRYPT_INTEGER_BLOB.new unless CryptProtectData(CRYPT_INTEGER_BLOB.new(str.to_wstring), nil, nil, nil, nil, CRYPTPROTECT_LOCAL_MACHINE, data_blob) Chef::ReservedNames::Win32::Error.raise! end bytes = data_blob[:pbData].get_bytes(0, data_blob[:cbData]) if block block.call(bytes) else Digest.hexencode(bytes) end ensure unless data_blob[:pbData].null? Chef::ReservedNames::Win32::Memory.local_free(data_blob[:pbData]) end end end end end chef-12.14.60/lib/chef/win32/error.rb000066400000000000000000000060521276456504500167660ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api/error" require "chef/win32/memory" require "chef/win32/unicode" require "chef/exceptions" class Chef module ReservedNames::Win32 class Error include Chef::ReservedNames::Win32::API::Error extend Chef::ReservedNames::Win32::API::Error def self.format_message(message_id = 0, args = {}) flags = args[:flags] || FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY flags |= FORMAT_MESSAGE_ALLOCATE_BUFFER source = args[:source] || 0 language_id = args[:language_id] || 0 varargs = args[:varargs] || [:int, 0] buffer = FFI::MemoryPointer.new :pointer num_chars = FormatMessageW(flags, source, message_id, language_id, buffer, 0, *varargs) if num_chars == 0 source = LoadLibraryExW("netmsg.dll".to_wstring, 0, LOAD_LIBRARY_AS_DATAFILE) begin num_chars = FormatMessageW(flags | FORMAT_MESSAGE_FROM_HMODULE, source, message_id, language_id, buffer, 0, *varargs) ensure FreeLibrary(source) end end if num_chars == 0 raise! end # Extract the string begin return buffer.read_pointer.read_wstring(num_chars) ensure Chef::ReservedNames::Win32::Memory.local_free(buffer.read_pointer) end end def self.get_last_error FFI::LastError.error end # Raises the last error. This should only be called by # Win32 API wrapper functions, and then only when wrapped # in an if() statement (since it unconditionally exits) # === Returns # nil::: always returns nil when it does not raise # === Raises # Chef::Exceptions::Win32APIError::: def self.raise!(message = nil, code = get_last_error) msg = format_message(code).strip if code == ERROR_USER_NOT_FOUND raise Chef::Exceptions::UserIDNotFound, msg else formatted_message = "" formatted_message << message if message formatted_message << "---- Begin Win32 API output ----\n" formatted_message << "System Error Code: #{code}\n" formatted_message << "System Error Message: #{msg}\n" formatted_message << "---- End Win32 API output ----\n" raise Chef::Exceptions::Win32APIError, msg + "\n" + formatted_message end end end end end chef-12.14.60/lib/chef/win32/eventlog.rb000066400000000000000000000022251276456504500174560ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # # Copyright:: Copyright 2015-2016, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # if Chef::Platform.windows? && (not Chef::Platform.windows_server_2003?) if !defined? Chef::Win32EventLogLoaded if defined? Windows::Constants [:INFINITE, :WAIT_FAILED, :FORMAT_MESSAGE_IGNORE_INSERTS, :ERROR_INSUFFICIENT_BUFFER].each do |c| # These are redefined in 'win32/eventlog' Windows::Constants.send(:remove_const, c) if Windows::Constants.const_defined? c end end require "win32/eventlog" Chef::Win32EventLogLoaded = true # rubocop:disable Style/ConstantName end end chef-12.14.60/lib/chef/win32/file.rb000066400000000000000000000202671276456504500165600ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Author:: Mark Mzyk () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/wide_string" require "chef/win32/api/file" require "chef/win32/api/security" require "chef/win32/error" require "chef/win32/unicode" class Chef module ReservedNames::Win32 class File include Chef::ReservedNames::Win32::API::File extend Chef::ReservedNames::Win32::API::File include Chef::Mixin::WideString extend Chef::Mixin::WideString # Creates a symbolic link called +new_name+ for the file or directory # +old_name+. # # This method requires Windows Vista or later to work. Otherwise, it # returns nil as per MRI. # def self.link(old_name, new_name) raise Errno::ENOENT, "(#{old_name}, #{new_name})" unless ::File.exist?(old_name) || ::File.symlink?(old_name) # TODO do a check for CreateHardLinkW and # raise NotImplemented exception on older Windows old_name = encode_path(old_name) new_name = encode_path(new_name) unless CreateHardLinkW(new_name, old_name, nil) Chef::ReservedNames::Win32::Error.raise! end end # Creates a symbolic link called +new_name+ for the file or directory # +old_name+. # # This method requires Windows Vista or later to work. Otherwise, it # returns nil as per MRI. # def self.symlink(old_name, new_name) # raise Errno::ENOENT, "(#{old_name}, #{new_name})" unless ::File.exist?(old_name) || ::File.symlink?(old_name) # TODO do a check for CreateSymbolicLinkW and # raise NotImplemented exception on older Windows flags = ::File.directory?(old_name) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0 old_name = encode_path(old_name) new_name = encode_path(new_name) unless CreateSymbolicLinkW(new_name, old_name, flags) Chef::ReservedNames::Win32::Error.raise! end end # Return true if the named file is a symbolic link, false otherwise. # # This method requires Windows Vista or later to work. Otherwise, it # always returns false as per MRI. # def self.symlink?(file_name) is_symlink = false path = encode_path(file_name) if ::File.exists?(file_name) || ::File.symlink?(file_name) if (GetFileAttributesW(path) & FILE_ATTRIBUTE_REPARSE_POINT) > 0 file_search_handle(file_name) do |handle, find_data| if find_data[:dw_reserved_0] == IO_REPARSE_TAG_SYMLINK is_symlink = true end end end end is_symlink end # Returns the path of the of the symbolic link referred to by +file+. # # Requires Windows Vista or later. On older versions of Windows it # will raise a NotImplementedError, as per MRI. # def self.readlink(link_name) raise Errno::ENOENT, link_name unless ::File.exists?(link_name) || ::File.symlink?(link_name) symlink_file_handle(link_name) do |handle| # Go to DeviceIoControl to get the symlink information # http://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx reparse_buffer = FFI::MemoryPointer.new(MAXIMUM_REPARSE_DATA_BUFFER_SIZE) parsed_size = FFI::Buffer.new(:long).write_long(0) if DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nil, 0, reparse_buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, parsed_size, nil) == 0 Chef::ReservedNames::Win32::Error.raise! end # Ensure it's a symbolic link reparse_buffer = REPARSE_DATA_BUFFER.new(reparse_buffer) if reparse_buffer[:ReparseTag] != IO_REPARSE_TAG_SYMLINK raise Errno::EACCES, "#{link_name} is not a symlink" end # Return the link destination (strip off \??\ at the beginning, which is a local filesystem thing) link_dest = reparse_buffer.reparse_buffer.substitute_name if link_dest =~ /^\\\?\?\\/ link_dest = link_dest[4..-1] end link_dest end end # Gets the short form of a path (Administrator -> ADMINI~1) def self.get_short_path_name(path) path = path.to_wstring size = GetShortPathNameW(path, nil, 0) if size == 0 Chef::ReservedNames::Win32::Error.raise! end result = FFI::MemoryPointer.new :char, (size + 1) * 2 if GetShortPathNameW(path, result, size + 1) == 0 Chef::ReservedNames::Win32::Error.raise! end result.read_wstring(size) end # Gets the long form of a path (ADMINI~1 -> Administrator) def self.get_long_path_name(path) path = path.to_wstring size = GetLongPathNameW(path, nil, 0) if size == 0 Chef::ReservedNames::Win32::Error.raise! end result = FFI::MemoryPointer.new :char, (size + 1) * 2 if GetLongPathNameW(path, result, size + 1) == 0 Chef::ReservedNames::Win32::Error.raise! end result.read_wstring(size) end def self.info(file_name) Info.new(file_name) end def self.version_info(file_name) VersionInfo.new(file_name) end def self.verify_links_supported! begin CreateSymbolicLinkW(nil) rescue Chef::Exceptions::Win32APIFunctionNotImplemented => e raise e rescue Exception # things are ok. end end def self.file_access_check(path, desired_access) security_descriptor = Chef::ReservedNames::Win32::Security.get_file_security(path) token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE | Chef::ReservedNames::Win32::Security::TOKEN_QUERY | Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE | Chef::ReservedNames::Win32::Security::STANDARD_RIGHTS_READ token = Chef::ReservedNames::Win32::Security.open_process_token( Chef::ReservedNames::Win32::Process.get_current_process, token_rights) duplicate_token = token.duplicate_token(:SecurityImpersonation) mapping = Chef::ReservedNames::Win32::Security::GENERIC_MAPPING.new mapping[:GenericRead] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_READ mapping[:GenericWrite] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_WRITE mapping[:GenericExecute] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_EXECUTE mapping[:GenericAll] = Chef::ReservedNames::Win32::Security::FILE_ALL_ACCESS Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token, desired_access, mapping) end def self.delete_volume_mount_point(mount_point) unless DeleteVolumeMountPointW(wstring(mount_point)) Chef::ReservedNames::Win32::Error.raise! end end def self.set_volume_mount_point(mount_point, name) unless SetVolumeMountPointW(wstring(mount_point), wstring(name)) Chef::ReservedNames::Win32::Error.raise! end end def self.get_volume_name_for_volume_mount_point(mount_point) buffer = FFI::MemoryPointer.new(2, 128) unless GetVolumeNameForVolumeMountPointW(wstring(mount_point), buffer, buffer.size / buffer.type_size) Chef::ReservedNames::Win32::Error.raise! end buffer.read_wstring end # ::File compat class << self alias :stat :info end end end end require "chef/win32/file/info" require "chef/win32/file/version_info" chef-12.14.60/lib/chef/win32/file/000077500000000000000000000000001276456504500162245ustar00rootroot00000000000000chef-12.14.60/lib/chef/win32/file/info.rb000066400000000000000000000062401276456504500175060ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/file" class Chef module ReservedNames::Win32 class File # Objects of class Chef::ReservedNames::Win32::File::Stat encapsulate common status # information for Chef::ReservedNames::Win32::File objects. The information # is recorded at the moment the Chef::ReservedNames::Win32::File::Stat object is # created; changes made to the file after that point will not be reflected. class Info include Chef::ReservedNames::Win32::API::File include Chef::ReservedNames::Win32::API # http://msdn.microsoft.com/en-us/library/windows/desktop/aa363788(v=vs.85).aspx def initialize(file_name) raise Errno::ENOENT, file_name unless ::File.exist?(file_name) @file_info = retrieve_file_info(file_name) end def volume_serial_number @file_info[:dw_volume_serial_number] end def index make_uint64(@file_info[:n_file_index_low], @file_info[:n_file_index_high]) end def last_access_time parse_time(@file_info[:ft_last_access_time]) end def creation_time parse_time(@file_info[:ft_creation_time]) end def last_write_time parse_time(@file_info[:ft_last_write_time]) end def links @file_info[:n_number_of_links] end def size make_uint64(@file_info[:n_file_size_low], @file_info[:n_file_size_high]) end ############################## # ::File::Stat compat alias :atime :last_access_time alias :mtime :last_write_time alias :ctime :creation_time # we're faking it here, but this is in the spirit of ino in *nix # # from MSDN: # # "The identifier (low and high parts) and the volume serial number # uniquely identify a file on a single computer. To determine whether # two open handles represent the same file, combine the identifier # and the volume serial number for each file and compare them."" # def ino volume_serial_number + index end ############################## # given a +Chef::ReservedNames::Win32::API::File::FILETIME+ structure convert into a # Ruby +Time+ object. # def parse_time(file_time_struct) wtime_to_time(make_uint64(file_time_struct[:dw_low_date_time], file_time_struct[:dw_high_date_time])) end end end end end chef-12.14.60/lib/chef/win32/file/version_info.rb000066400000000000000000000060611276456504500212540ustar00rootroot00000000000000# # Author:: Matt Wrock () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/file" class Chef module ReservedNames::Win32 class File class VersionInfo include Chef::ReservedNames::Win32::API::File def initialize(file_name) raise Errno::ENOENT, file_name unless ::File.exist?(file_name) @file_version_info = retrieve_file_version_info(file_name) end # defining method for each predefined version resource string # see https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx [ :Comments, :CompanyName, :FileDescription, :FileVersion, :InternalName, :LegalCopyright, :LegalTrademarks, :OriginalFilename, :ProductName, :ProductVersion, :PrivateBuild, :SpecialBuild, ].each do |method| define_method method do begin get_version_info_string(method.to_s) rescue Chef::Exceptions::Win32APIError return nil end end end private def translation @translation ||= begin info_ptr = FFI::MemoryPointer.new(:pointer) unless VerQueryValueW(@file_version_info, "\\VarFileInfo\\Translation".to_wstring, info_ptr, FFI::MemoryPointer.new(:int)) Chef::ReservedNames::Win32::Error.raise! end # there can potentially be multiple translations but most installers just have one # we use the first because we use this for the version strings which are language # agnostic. If/when we need other fields, we should we should add logic to find # the "best" translation trans = Translation.new(info_ptr.read_pointer) to_hex(trans[:w_lang]) + to_hex(trans[:w_code_page]) end end def to_hex(integer) integer.to_s(16).rjust(4, "0") end def get_version_info_string(string_key) info_ptr = FFI::MemoryPointer.new(:pointer) size_ptr = FFI::MemoryPointer.new(:int) unless VerQueryValueW(@file_version_info, "\\StringFileInfo\\#{translation}\\#{string_key}".to_wstring, info_ptr, size_ptr) Chef::ReservedNames::Win32::Error.raise! end info_ptr.read_pointer.read_wstring(size_ptr.read_uint) end end end end end chef-12.14.60/lib/chef/win32/handle.rb000066400000000000000000000037451276456504500170760ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api/process" require "chef/win32/api/psapi" require "chef/win32/api/system" require "chef/win32/error" class Chef module ReservedNames::Win32 class Handle extend Chef::ReservedNames::Win32::API::Process # See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx # The handle value returned by the GetCurrentProcess function is the pseudo handle (HANDLE)-1 (which is 0xFFFFFFFF) CURRENT_PROCESS_HANDLE = 4294967295 def initialize(handle) @handle = handle ObjectSpace.define_finalizer(self, Handle.close_handle_finalizer(handle)) end attr_reader :handle def self.close_handle_finalizer(handle) # According to http://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx, it is not necessary # to close the pseudo handle returned by the GetCurrentProcess function. The docs also say that it doesn't hurt to call # CloseHandle on it. However, doing so from inside of Ruby always seems to produce an invalid handle error. proc { close_handle(handle) unless handle == CURRENT_PROCESS_HANDLE } end def self.close_handle(handle) unless CloseHandle(handle) Chef::ReservedNames::Win32::Error.raise! end end end end end chef-12.14.60/lib/chef/win32/memory.rb000066400000000000000000000060501276456504500171430ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/error" require "chef/win32/api/memory" class Chef module ReservedNames::Win32 class Memory include Chef::ReservedNames::Win32::API::Memory extend Chef::ReservedNames::Win32::API::Memory # local_alloc(length[, flags]) [BLOCK] # Allocates memory using LocalAlloc # If BLOCK is specified, the memory will be passed # to the block and freed afterwards. def self.local_alloc(length, flags = LPTR, &block) result = LocalAlloc(flags, length) if result.null? Chef::ReservedNames::Win32::Error.raise! end # If a block is passed, handle freeing the memory at the end if block != nil begin yield result ensure local_free(result) end else result end end # local_discard(pointer) # Discard memory. Equivalent to local_realloc(pointer, 0) def self.local_discard(pointer) local_realloc(pointer, 0, LMEM_MOVEABLE) end # local_flags(pointer) # Get lock count and Windows flags for local_alloc allocated memory. # Use: flags, lock_count = local_flags(pointer) def self.local_flags(pointer) result = LocalFlags(pointer) if result == LMEM_INVALID_HANDLE Chef::ReservedNames::Win32::Error.raise! end [ result & ~LMEM_LOCKCOUNT, result & LMEM_LOCKCOUNT ] end # local_free(pointer) # Free memory allocated using local_alloc def self.local_free(pointer) result = LocalFree(pointer) if !result.null? Chef::ReservedNames::Win32::Error.raise! end end # local_realloc(pointer, size[, flags]) # Resizes memory allocated using LocalAlloc. def self.local_realloc(pointer, size, flags = LMEM_MOVEABLE | LMEM_ZEROINIT) result = LocalReAlloc(pointer, size, flags) if result.null? Chef::ReservedNames::Win32::Error.raise! end result end # local_size(pointer) # Gets the size of memory allocated using LocalAlloc. def self.local_size(pointer) result = LocalSize(pointer) if result == 0 Chef::ReservedNames::Win32::Error.raise! end result end def self.local_free_finalizer(pointer) proc { local_free(pointer) } end end end end chef-12.14.60/lib/chef/win32/mutex.rb000066400000000000000000000102701276456504500167740ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-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/win32/api/synchronization" require "chef/win32/unicode" class Chef module ReservedNames::Win32 class Mutex include Chef::ReservedNames::Win32::API::Synchronization def initialize(name) @name = name create_system_mutex end attr_reader :handle attr_reader :name ##################################################### # Attempts to grab the mutex. # Returns true if the mutex is grabbed or if it's already # owned; false otherwise. def test WaitForSingleObject(handle, 0) == WAIT_OBJECT_0 end ##################################################### # Attempts to grab the mutex and waits until it is acquired. def wait loop do wait_result = WaitForSingleObject(handle, 1000) case wait_result when WAIT_TIMEOUT # We are periodically waking up in order to give ruby a # chance to process any signal it got while we were # sleeping. This condition shouldn't contain any logic # other than sleeping. sleep 0.1 when WAIT_ABANDONED # Previous owner of the mutex died before it can release the # mutex. Log a warning and continue. Chef::Log.debug "Existing owner of the mutex exited prematurely." break when WAIT_OBJECT_0 # Mutex is successfully acquired. break else Chef::Log.error("Failed to acquire system mutex '#{name}'. Return code: #{wait_result}") Chef::ReservedNames::Win32::Error.raise! end end end ##################################################### # Releaes the mutex def release # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685066(v=vs.85).aspx # Note that release method needs to be called more than once # if mutex is acquired more than once. unless ReleaseMutex(handle) # Don't fail things in here if we can't release the mutex. # Because it will be automatically released when the owner # of the process goes away and this class is only being used # to synchronize chef-clients runs on a node. Chef::Log.error("Can not release mutex '#{name}'. This might cause issues \ if other threads attempt to acquire the mutex.") Chef::ReservedNames::Win32::Error.raise! end end private def create_system_mutex # First check if there exists a mutex in the system with the # given name. We need only synchronize rights if a mutex is # already created. # InheritHandle is set to true so that subprocesses can # inherit the ownership of the mutex. @handle = OpenMutexW(SYNCHRONIZE, true, name.to_wstring) if @handle == 0 # Mutext doesn't exist so create one. # In the initial creation of the mutex initial_owner is set to # false so that mutex will not be acquired until someone calls # acquire. # In order to call "*W" windows apis, strings needs to be # encoded as wide strings. @handle = CreateMutexW(nil, false, name.to_wstring) # Looks like we can't create the mutex for some reason. # Fail early. if @handle == 0 Chef::Log.error("Failed to create system mutex with name'#{name}'") Chef::ReservedNames::Win32::Error.raise! end end end end end end chef-12.14.60/lib/chef/win32/net.rb000066400000000000000000000232101276456504500164160ustar00rootroot00000000000000# # Author:: Jay Mundrawala() # Copyright:: Copyright 2015-2016, Chef Software # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api/net" require "chef/win32/error" require "chef/mixin/wide_string" class Chef module ReservedNames::Win32 class Net include Chef::ReservedNames::Win32::API::Error extend Chef::ReservedNames::Win32::API::Error include Chef::ReservedNames::Win32::API::Net extend Chef::ReservedNames::Win32::API::Net include Chef::Mixin::WideString extend Chef::Mixin::WideString def self.default_user_info_3 ui3 = USER_INFO_3.new.tap do |s| { usri3_name: nil, usri3_password: nil, usri3_password_age: 0, usri3_priv: 0, usri3_home_dir: nil, usri3_comment: nil, usri3_flags: UF_SCRIPT | UF_DONT_EXPIRE_PASSWD | UF_NORMAL_ACCOUNT, usri3_script_path: nil, usri3_auth_flags: 0, usri3_full_name: nil, usri3_usr_comment: nil, usri3_parms: nil, usri3_workstations: nil, usri3_last_logon: 0, usri3_last_logoff: 0, usri3_acct_expires: -1, usri3_max_storage: -1, usri3_units_per_week: 0, usri3_logon_hours: nil, usri3_bad_pw_count: 0, usri3_num_logons: 0, usri3_logon_server: nil, usri3_country_code: 0, usri3_code_page: 0, usri3_user_id: 0, usri3_primary_group_id: DOMAIN_GROUP_RID_USERS, usri3_profile: nil, usri3_home_dir_drive: nil, usri3_password_expired: 0, }.each do |(k, v)| s.set(k, v) end end end def self.net_local_group_add(server_name, group_name) server_name = wstring(server_name) group_name = wstring(group_name) buf = LOCALGROUP_INFO_0.new buf[:lgrpi0_name] = FFI::MemoryPointer.from_string(group_name) rc = NetLocalGroupAdd(server_name, 0, buf, nil) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end def self.net_local_group_del(server_name, group_name) server_name = wstring(server_name) group_name = wstring(group_name) rc = NetLocalGroupDel(server_name, group_name) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end def self.net_local_group_get_members(server_name, group_name) server_name = wstring(server_name) group_name = wstring(group_name) buf = FFI::MemoryPointer.new(:pointer) entries_read_ptr = FFI::MemoryPointer.new(:long) total_read_ptr = FFI::MemoryPointer.new(:long) resume_handle_ptr = FFI::MemoryPointer.new(:pointer) rc = ERROR_MORE_DATA group_members = [] while rc == ERROR_MORE_DATA rc = NetLocalGroupGetMembers( server_name, group_name, 0, buf, -1, entries_read_ptr, total_read_ptr, resume_handle_ptr ) nread = entries_read_ptr.read_long nread.times do |i| member = LOCALGROUP_MEMBERS_INFO_0.new(buf.read_pointer + (i * LOCALGROUP_MEMBERS_INFO_0.size)) member_sid = Chef::ReservedNames::Win32::Security::SID.new(member[:lgrmi0_sid]) group_members << member_sid.to_s end NetApiBufferFree(buf.read_pointer) end if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end group_members end def self.net_user_add_l3(server_name, args) buf = default_user_info_3 args.each do |k, v| buf.set(k, v) end server_name = wstring(server_name) rc = NetUserAdd(server_name, 3, buf, nil) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end def self.net_user_get_info_l3(server_name, user_name) server_name = wstring(server_name) user_name = wstring(user_name) ui3_p = FFI::MemoryPointer.new(:pointer) rc = NetUserGetInfo(server_name, user_name, 3, ui3_p) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end ui3 = USER_INFO_3.new(ui3_p.read_pointer).as_ruby rc = NetApiBufferFree(ui3_p.read_pointer) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end ui3 end def self.net_user_set_info_l3(server_name, user_name, info) buf = default_user_info_3 info.each do |k, v| buf.set(k, v) end server_name = wstring(server_name) user_name = wstring(user_name) rc = NetUserSetInfo(server_name, user_name, 3, buf, nil) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end def self.net_user_del(server_name, user_name) server_name = wstring(server_name) user_name = wstring(user_name) rc = NetUserDel(server_name, user_name) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end def self.net_local_group_add_member(server_name, group_name, domain_user) server_name = wstring(server_name) group_name = wstring(group_name) domain_user = wstring(domain_user) buf = LOCALGROUP_MEMBERS_INFO_3.new buf[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(domain_user) rc = NetLocalGroupAddMembers(server_name, group_name, 3, buf, 1) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end def self.members_to_lgrmi3(members) buf = FFI::MemoryPointer.new(LOCALGROUP_MEMBERS_INFO_3, members.size) Array.new(members.size) do |i| member_info = LOCALGROUP_MEMBERS_INFO_3.new( buf + i * LOCALGROUP_MEMBERS_INFO_3.size) member_info[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(wstring(members[i])) member_info end end def self.net_local_group_add_members(server_name, group_name, members) server_name = wstring(server_name) group_name = wstring(group_name) lgrmi3s = members_to_lgrmi3(members) rc = NetLocalGroupAddMembers( server_name, group_name, 3, lgrmi3s[0], members.size) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end def self.net_local_group_set_members(server_name, group_name, members) server_name = wstring(server_name) group_name = wstring(group_name) lgrmi3s = members_to_lgrmi3(members) rc = NetLocalGroupSetMembers( server_name, group_name, 3, lgrmi3s[0], members.size) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end def self.net_local_group_del_members(server_name, group_name, members) server_name = wstring(server_name) group_name = wstring(group_name) lgrmi3s = members_to_lgrmi3(members) rc = NetLocalGroupDelMembers( server_name, group_name, 3, lgrmi3s[0], members.size) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end def self.net_use_del(server_name, use_name, force = :use_noforce) server_name = wstring(server_name) use_name = wstring(use_name) force_const = case force when :use_noforce USE_NOFORCE when :use_force USE_FORCE when :use_lots_of_force USE_LOTS_OF_FORCE else raise ArgumentError, "force must be one of [:use_noforce, :use_force, or :use_lots_of_force]" end rc = NetUseDel(server_name, use_name, force_const) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end def self.net_use_get_info_l2(server_name, use_name) server_name = wstring(server_name) use_name = wstring(use_name) ui2_p = FFI::MemoryPointer.new(:pointer) rc = NetUseGetInfo(server_name, use_name, 2, ui2_p) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end ui2 = USE_INFO_2.new(ui2_p.read_pointer).as_ruby NetApiBufferFree(ui2_p.read_pointer) ui2 end def self.net_use_add_l2(server_name, ui2_hash) server_name = wstring(server_name) group_name = wstring(group_name) buf = USE_INFO_2.new ui2_hash.each do |(k, v)| buf.set(k, v) end rc = NetUseAdd(server_name, 2, buf, nil) if rc != NERR_Success Chef::ReservedNames::Win32::Error.raise!(nil, rc) end end end NetUser = Net # For backwards compatibility end end chef-12.14.60/lib/chef/win32/process.rb000066400000000000000000000061161276456504500173140ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api/process" require "chef/win32/api/psapi" require "chef/win32/error" require "chef/win32/handle" require "ffi" class Chef module ReservedNames::Win32 class Process include Chef::ReservedNames::Win32::API::Process extend Chef::ReservedNames::Win32::API::Process include Chef::ReservedNames::Win32::API::PSAPI extend Chef::ReservedNames::Win32::API::PSAPI def initialize(handle) @handle = handle end attr_reader :handle def id Process.get_process_id(handle) end def handle_count Process.get_process_handle_count(handle) end def memory_info Process.get_process_memory_info(handle) end def self.get_current_process Process.new(Handle.new(GetCurrentProcess())) end def self.get_process_handle_count(handle) handle_count = FFI::MemoryPointer.new :uint32 unless GetProcessHandleCount(handle.handle, handle_count) Chef::ReservedNames::Win32::Error.raise! end handle_count.read_uint32 end def self.get_process_id(handle) # Must have PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION rights result = GetProcessId(handle.handle) if result == 0 Chef::ReservedNames::Win32::Error.raise! end result end def self.is_wow64_process is_64_bit_process_result = FFI::MemoryPointer.new(:int) # The return value of IsWow64Process is nonzero value if the API call succeeds. # The result data are returned in the last parameter, not the return value. call_succeeded = IsWow64Process(GetCurrentProcess(), is_64_bit_process_result) # The result is nonzero if IsWow64Process's calling process, in the case here # this process, is running under WOW64, i.e. the result is nonzero if this # process is 32-bit (aka :i386). (call_succeeded != 0) && (is_64_bit_process_result.get_int(0) != 0) end # Must have PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION rights, # AND the PROCESS_VM_READ right def self.get_process_memory_info(handle) memory_info = PROCESS_MEMORY_COUNTERS.new unless GetProcessMemoryInfo(handle.handle, memory_info, memory_info.size) Chef::ReservedNames::Win32::Error.raise! end memory_info end end end end chef-12.14.60/lib/chef/win32/registry.rb000066400000000000000000000320321276456504500175020ustar00rootroot00000000000000# # Author:: Prajakta Purohit () # Author:: Lamont Granquist () # # Copyright:: Copyright 2012-2016, Chef Software Inc. # # 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/reserved_names" require "chef/win32/api" require "chef/mixin/wide_string" if RUBY_PLATFORM =~ /mswin|mingw32|windows/ require "chef/monkey_patches/win32/registry" require "chef/win32/api/registry" require "win32/registry" require "win32/api" end class Chef class Win32 class Registry if RUBY_PLATFORM =~ /mswin|mingw32|windows/ include Chef::ReservedNames::Win32::API::Registry extend Chef::ReservedNames::Win32::API::Registry end include Chef::Mixin::WideString extend Chef::Mixin::WideString attr_accessor :run_context attr_accessor :architecture def initialize(run_context = nil, user_architecture = :machine) @run_context = run_context self.architecture = user_architecture end def architecture=(user_architecture) @architecture = user_architecture.to_sym assert_architecture! end def get_values(key_path) hive, key = get_hive_and_key(key_path) key_exists!(key_path) values = hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg| reg.map { |name, type, data| { :name => name, :type => get_name_from_type(type), :data => data } } end end def set_value(key_path, value) data = value[:data] data = data.to_s if value[:type] == :string Chef::Log.debug("Updating value #{value[:name]} in registry key #{key_path} with type #{value[:type]} and data #{data}") key_exists!(key_path) hive, key = get_hive_and_key(key_path) if value_exists?(key_path, value) if data_exists?(key_path, value) Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} already had those values, not updated") return false else hive.open(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry_system_architecture) do |reg| reg.write(value[:name], get_type_from_name(value[:type]), data) end Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} updated") end else hive.open(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry_system_architecture) do |reg| reg.write(value[:name], get_type_from_name(value[:type]), data) end Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} created") end true end def delete_value(key_path, value) Chef::Log.debug("Deleting value #{value[:name]} from registry key #{key_path}") if value_exists?(key_path, value) begin hive, key = get_hive_and_key(key_path) rescue Chef::Exceptions::Win32RegKeyMissing return true end hive.open(key, ::Win32::Registry::KEY_SET_VALUE | registry_system_architecture) do |reg| reg.delete_value(value[:name]) Chef::Log.debug("Deleted value #{value[:name]} from registry key #{key_path}") end else Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} does not exist, not updated") end true end def create_key(key_path, recursive) Chef::Log.debug("Creating registry key #{key_path}") if keys_missing?(key_path) if recursive == true Chef::Log.debug("Registry key #{key_path} has missing subkeys, and recursive specified, creating them....") create_missing(key_path) else raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has missing subkeys, and recursive not specified" end end if key_exists?(key_path) Chef::Log.debug("Registry key #{key_path} already exists, doing nothing") else hive, key = get_hive_and_key(key_path) hive.create(key, ::Win32::Registry::KEY_WRITE | registry_system_architecture) Chef::Log.debug("Registry key #{key_path} created") end true end def delete_key(key_path, recursive) Chef::Log.debug("Deleting registry key #{key_path}") unless key_exists?(key_path) Chef::Log.debug("Registry key #{key_path}, does not exist, not deleting") return true end if has_subkeys?(key_path) && !recursive raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has subkeys, and recursive not specified" end hive, key_including_parent = get_hive_and_key(key_path) # key_including_parent: Software\\Root\\Branch\\Fruit # key => Fruit # key_parent => Software\\Root\\Branch key_parts = key_including_parent.split("\\") key = key_parts.pop key_parent = key_parts.join("\\") hive.open(key_parent, ::Win32::Registry::KEY_WRITE | registry_system_architecture) do |reg| reg.delete_key(key, recursive) end Chef::Log.debug("Registry key #{key_path} deleted") true end def key_exists?(key_path) hive, key = get_hive_and_key(key_path) begin hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |current_key| return true end rescue ::Win32::Registry::Error => e return false end end def key_exists!(key_path) unless key_exists?(key_path) raise Chef::Exceptions::Win32RegKeyMissing, "Registry key #{key_path} does not exist" end true end def hive_exists?(key_path) begin hive, key = get_hive_and_key(key_path) rescue Chef::Exceptions::Win32RegHiveMissing => e return false end return true end def has_subkeys?(key_path) key_exists!(key_path) hive, key = get_hive_and_key(key_path) hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg| reg.each_key { |key| return true } end return false end def get_subkeys(key_path) subkeys = [] key_exists!(key_path) hive, key = get_hive_and_key(key_path) hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg| reg.each_key { |current_key| subkeys << current_key } end return subkeys end # 32-bit chef clients running on 64-bit machines will default to reading the 64-bit registry def registry_system_architecture applied_arch = ( architecture == :machine ) ? machine_architecture : architecture ( applied_arch == :x86_64 ) ? 0x0100 : 0x0200 end def value_exists?(key_path, value) key_exists!(key_path) hive, key = get_hive_and_key(key_path) hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg| return true if reg.any? { |val| safely_downcase(val) == safely_downcase(value[:name]) } end return false end def data_exists?(key_path, value) key_exists!(key_path) hive, key = get_hive_and_key(key_path) hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg| reg.each do |val_name, val_type, val_data| if safely_downcase(val_name) == safely_downcase(value[:name]) && val_type == get_type_from_name(value[:type]) && val_data == value[:data] return true end end end return false end def value_exists!(key_path, value) unless value_exists?(key_path, value) raise Chef::Exceptions::Win32RegValueMissing, "Registry key #{key_path} has no value named #{value[:name]}" end true end def data_exists!(key_path, value) unless data_exists?(key_path, value) raise Chef::Exceptions::Win32RegDataMissing, "Registry key #{key_path} has no value named #{value[:name]}, containing type #{value[:type]} and data #{value[:data]}" end true end def type_matches?(key_path, value) value_exists!(key_path, value) hive, key = get_hive_and_key(key_path) hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg| reg.each do |val_name, val_type| if val_name == value[:name] type_new = get_type_from_name(value[:type]) if val_type == type_new return true end end end end return false end def type_matches!(key_path, value) unless type_matches?(key_path, value) raise Chef::Exceptions::Win32RegTypesMismatch, "Registry key #{key_path} has a value #{value[:name]} with a type that is not #{value[:type]}" end end def keys_missing?(key_path) missing_key_arr = key_path.split("\\") missing_key_arr.pop key = missing_key_arr.join("\\") !key_exists?(key) end def get_type_from_name(val_type) _type_name_map[val_type] end def get_name_from_type(val_class) _name_type_map[val_class] end private def safely_downcase(val) if val.is_a? String return val.downcase end return val end def node run_context && run_context.node end def machine_architecture node[:kernel][:machine].to_sym end def assert_architecture! if machine_architecture == :i386 && architecture == :x86_64 raise Chef::Exceptions::Win32RegArchitectureIncorrect, "cannot access 64-bit registry on a 32-bit windows instance" end end def get_hive_and_key(path) reg_path = path.split("\\") hive_name = reg_path.shift key = reg_path.join("\\") hive = { "HKLM" => ::Win32::Registry::HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE" => ::Win32::Registry::HKEY_LOCAL_MACHINE, "HKU" => ::Win32::Registry::HKEY_USERS, "HKEY_USERS" => ::Win32::Registry::HKEY_USERS, "HKCU" => ::Win32::Registry::HKEY_CURRENT_USER, "HKEY_CURRENT_USER" => ::Win32::Registry::HKEY_CURRENT_USER, "HKCR" => ::Win32::Registry::HKEY_CLASSES_ROOT, "HKEY_CLASSES_ROOT" => ::Win32::Registry::HKEY_CLASSES_ROOT, "HKCC" => ::Win32::Registry::HKEY_CURRENT_CONFIG, "HKEY_CURRENT_CONFIG" => ::Win32::Registry::HKEY_CURRENT_CONFIG, }[hive_name] raise Chef::Exceptions::Win32RegHiveMissing, "Registry Hive #{hive_name} does not exist" unless hive return hive, key end def _type_name_map { :binary => ::Win32::Registry::REG_BINARY, :string => ::Win32::Registry::REG_SZ, :multi_string => ::Win32::Registry::REG_MULTI_SZ, :expand_string => ::Win32::Registry::REG_EXPAND_SZ, :dword => ::Win32::Registry::REG_DWORD, :dword_big_endian => ::Win32::Registry::REG_DWORD_BIG_ENDIAN, :qword => ::Win32::Registry::REG_QWORD, } end def _name_type_map @_name_type_map ||= _type_name_map.invert end def get_type_from_num(val_type) value = { 3 => ::Win32::Registry::REG_BINARY, 1 => ::Win32::Registry::REG_SZ, 7 => ::Win32::Registry::REG_MULTI_SZ, 2 => ::Win32::Registry::REG_EXPAND_SZ, 4 => ::Win32::Registry::REG_DWORD, 5 => ::Win32::Registry::REG_DWORD_BIG_ENDIAN, 11 => ::Win32::Registry::REG_QWORD, }[val_type] return value end def create_missing(key_path) missing_key_arr = key_path.split("\\") hivename = missing_key_arr.shift missing_key_arr.pop existing_key_path = hivename hive, key = get_hive_and_key(key_path) missing_key_arr.each do |intermediate_key| existing_key_path = existing_key_path << "\\" << intermediate_key if !key_exists?(existing_key_path) Chef::Log.debug("Recursively creating registry key #{existing_key_path}") hive.create(get_key(existing_key_path), ::Win32::Registry::KEY_ALL_ACCESS | registry_system_architecture) end end end def get_key(path) reg_path = path.split("\\") hive_name = reg_path.shift key = reg_path.join("\\") end end end end chef-12.14.60/lib/chef/win32/security.rb000066400000000000000000000677321276456504500175200ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api/security" require "chef/win32/error" require "chef/win32/memory" require "chef/win32/process" require "chef/win32/unicode" require "chef/win32/security/token" require "chef/mixin/wide_string" class Chef module ReservedNames::Win32 class Security include Chef::ReservedNames::Win32::API::Error extend Chef::ReservedNames::Win32::API::Error include Chef::ReservedNames::Win32::API::Security extend Chef::ReservedNames::Win32::API::Security extend Chef::ReservedNames::Win32::API::Macros include Chef::Mixin::WideString extend Chef::Mixin::WideString def self.access_check(security_descriptor, token, desired_access, generic_mapping) token_handle = token.handle.handle security_descriptor_ptr = security_descriptor.pointer rights_ptr = FFI::MemoryPointer.new(:ulong) rights_ptr.write_ulong(desired_access) # This function takes care of calling MapGenericMask, so you don't have to MapGenericMask(rights_ptr, generic_mapping) result_ptr = FFI::MemoryPointer.new(:ulong) # Because optional actually means required privileges = PRIVILEGE_SET.new privileges[:PrivilegeCount] = 0 privileges_length_ptr = FFI::MemoryPointer.new(:ulong) privileges_length_ptr.write_ulong(privileges.size) granted_access_ptr = FFI::MemoryPointer.new(:ulong) unless AccessCheck(security_descriptor_ptr, token_handle, rights_ptr.read_ulong, generic_mapping, privileges, privileges_length_ptr, granted_access_ptr, result_ptr) Chef::ReservedNames::Win32::Error.raise! end result_ptr.read_ulong == 1 end def self.add_ace(acl, ace, insert_position = MAXDWORD, revision = ACL_REVISION) acl = acl.pointer if acl.respond_to?(:pointer) ace = ace.pointer if ace.respond_to?(:pointer) ace_size = ACE_HEADER.new(ace)[:AceSize] unless AddAce(acl, revision, insert_position, ace, ace_size) Chef::ReservedNames::Win32::Error.raise! end end def self.add_access_allowed_ace(acl, sid, access_mask, revision = ACL_REVISION) acl = acl.pointer if acl.respond_to?(:pointer) sid = sid.pointer if sid.respond_to?(:pointer) unless AddAccessAllowedAce(acl, revision, access_mask, sid) Chef::ReservedNames::Win32::Error.raise! end end def self.add_access_allowed_ace_ex(acl, sid, access_mask, flags = 0, revision = ACL_REVISION) acl = acl.pointer if acl.respond_to?(:pointer) sid = sid.pointer if sid.respond_to?(:pointer) unless AddAccessAllowedAceEx(acl, revision, flags, access_mask, sid) Chef::ReservedNames::Win32::Error.raise! end end def self.add_access_denied_ace(acl, sid, access_mask, revision = ACL_REVISION) acl = acl.pointer if acl.respond_to?(:pointer) sid = sid.pointer if sid.respond_to?(:pointer) unless AddAccessDeniedAce(acl, revision, access_mask, sid) Chef::ReservedNames::Win32::Error.raise! end end def self.add_access_denied_ace_ex(acl, sid, access_mask, flags = 0, revision = ACL_REVISION) acl = acl.pointer if acl.respond_to?(:pointer) sid = sid.pointer if sid.respond_to?(:pointer) unless AddAccessDeniedAceEx(acl, revision, flags, access_mask, sid) Chef::ReservedNames::Win32::Error.raise! end end def self.add_account_right(name, privilege) privilege_pointer = FFI::MemoryPointer.new LSA_UNICODE_STRING, 1 privilege_lsa_string = LSA_UNICODE_STRING.new(privilege_pointer) privilege_lsa_string[:Buffer] = FFI::MemoryPointer.from_string(privilege.to_wstring) privilege_lsa_string[:Length] = privilege.length * 2 privilege_lsa_string[:MaximumLength] = (privilege.length + 1) * 2 with_lsa_policy(name) do |policy_handle, sid| result = LsaAddAccountRights(policy_handle.read_pointer, sid, privilege_pointer, 1) win32_error = LsaNtStatusToWinError(result) if win32_error != 0 Chef::ReservedNames::Win32::Error.raise!(nil, win32_error) end end end def self.adjust_token_privileges(token, privileges) token = token.handle if token.respond_to?(:handle) old_privileges_size = FFI::Buffer.new(:long).write_long(privileges.size_with_privileges) old_privileges = TOKEN_PRIVILEGES.new(FFI::Buffer.new(old_privileges_size.read_long)) unless AdjustTokenPrivileges(token.handle, false, privileges, privileges.size_with_privileges, old_privileges, old_privileges_size) Chef::ReservedNames::Win32::Error.raise! end old_privileges end def self.convert_sid_to_string_sid(sid) sid = sid.pointer if sid.respond_to?(:pointer) result = FFI::MemoryPointer.new :pointer # TODO: use the W version unless ConvertSidToStringSidA(sid, result) Chef::ReservedNames::Win32::Error.raise! end result_string = result.read_pointer.read_string Chef::ReservedNames::Win32::Memory.local_free(result.read_pointer) result_string end def self.convert_string_sid_to_sid(string_sid) result = FFI::MemoryPointer.new :pointer unless ConvertStringSidToSidW(string_sid.to_wstring, result) Chef::ReservedNames::Win32::Error.raise! end result_pointer = result.read_pointer sid = SID.new(result_pointer) # The result pointer must be freed with local_free ObjectSpace.define_finalizer(sid, Memory.local_free_finalizer(result_pointer)) sid end def self.delete_ace(acl, index) acl = acl.pointer if acl.respond_to?(:pointer) unless DeleteAce(acl, index) Chef::ReservedNames::Win32::Error.raise! end end def self.equal_sid(sid1, sid2) sid1 = sid1.pointer if sid1.respond_to?(:pointer) sid2 = sid2.pointer if sid2.respond_to?(:pointer) EqualSid(sid1, sid2) end def self.free_sid(sid) sid = sid.pointer if sid.respond_to?(:pointer) unless FreeSid(sid).null? Chef::ReservedNames::Win32::Error.raise! end end def self.get_account_right(name) privileges = [] privilege_pointer = FFI::MemoryPointer.new(:pointer) privilege_length = FFI::MemoryPointer.new(:ulong) with_lsa_policy(name) do |policy_handle, sid| result = LsaEnumerateAccountRights(policy_handle.read_pointer, sid, privilege_pointer, privilege_length) win32_error = LsaNtStatusToWinError(result) return [] if win32_error == 2 # FILE_NOT_FOUND - No rights assigned if win32_error != 0 Chef::ReservedNames::Win32::Error.raise!(nil, win32_error) end privilege_length.read_ulong.times do |i| privilege = LSA_UNICODE_STRING.new(privilege_pointer.read_pointer + i * LSA_UNICODE_STRING.size) privileges << privilege[:Buffer].read_wstring end LsaFreeMemory(privilege_pointer) end privileges end def self.get_ace(acl, index) acl = acl.pointer if acl.respond_to?(:pointer) ace = FFI::Buffer.new :pointer unless GetAce(acl, index, ace) Chef::ReservedNames::Win32::Error.raise! end ACE.new(ace.read_pointer, acl) end def self.get_length_sid(sid) sid = sid.pointer if sid.respond_to?(:pointer) GetLengthSid(sid) end def self.get_file_security(path, info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION) size_ptr = FFI::MemoryPointer.new(:ulong) success = GetFileSecurityW(path.to_wstring, info, nil, 0, size_ptr) if !success && FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER Chef::ReservedNames::Win32::Error.raise! end security_descriptor_ptr = FFI::MemoryPointer.new(size_ptr.read_ulong) unless GetFileSecurityW(path.to_wstring, info, security_descriptor_ptr, size_ptr.read_ulong, size_ptr) Chef::ReservedNames::Win32::Error.raise! end SecurityDescriptor.new(security_descriptor_ptr) end def self.get_named_security_info(path, type = :SE_FILE_OBJECT, info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION) security_descriptor = FFI::MemoryPointer.new :pointer hr = GetNamedSecurityInfoW(path.to_wstring, type, info, nil, nil, nil, nil, security_descriptor) if hr != ERROR_SUCCESS Chef::ReservedNames::Win32::Error.raise!("get_named_security_info(#{path}, #{type}, #{info})") end result_pointer = security_descriptor.read_pointer result = SecurityDescriptor.new(result_pointer) # This memory has to be freed with LocalFree. ObjectSpace.define_finalizer(result, Memory.local_free_finalizer(result_pointer)) result end def self.get_security_descriptor_control(security_descriptor) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) result = FFI::Buffer.new :ushort version = FFI::Buffer.new :uint32 unless GetSecurityDescriptorControl(security_descriptor, result, version) Chef::ReservedNames::Win32::Error.raise! end [ result.read_ushort, version.read_uint32 ] end def self.get_security_descriptor_dacl(security_descriptor) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) present = FFI::Buffer.new :bool defaulted = FFI::Buffer.new :bool acl = FFI::Buffer.new :pointer unless GetSecurityDescriptorDacl(security_descriptor, present, acl, defaulted) Chef::ReservedNames::Win32::Error.raise! end acl = acl.read_pointer [ present.read_char != 0, acl.null? ? nil : ACL.new(acl, security_descriptor), defaulted.read_char != 0 ] end def self.get_security_descriptor_group(security_descriptor) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) result = FFI::Buffer.new :pointer defaulted = FFI::Buffer.new :long unless GetSecurityDescriptorGroup(security_descriptor, result, defaulted) Chef::ReservedNames::Win32::Error.raise! end sid = SID.new(result.read_pointer, security_descriptor) defaulted = defaulted.read_char != 0 [ sid, defaulted ] end def self.get_security_descriptor_owner(security_descriptor) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) result = FFI::Buffer.new :pointer defaulted = FFI::Buffer.new :long unless GetSecurityDescriptorOwner(security_descriptor, result, defaulted) Chef::ReservedNames::Win32::Error.raise! end sid = SID.new(result.read_pointer, security_descriptor) defaulted = defaulted.read_char != 0 [ sid, defaulted ] end def self.get_security_descriptor_sacl(security_descriptor) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) present = FFI::Buffer.new :bool defaulted = FFI::Buffer.new :bool acl = FFI::Buffer.new :pointer unless GetSecurityDescriptorSacl(security_descriptor, present, acl, defaulted) Chef::ReservedNames::Win32::Error.raise! end acl = acl.read_pointer [ present.read_char != 0, acl.null? ? nil : ACL.new(acl, security_descriptor), defaulted.read_char != 0 ] end def self.get_token_information_owner(token) owner_result_size = FFI::MemoryPointer.new(:ulong) if GetTokenInformation(token.handle.handle, :TokenOwner, nil, 0, owner_result_size) raise "Expected ERROR_INSUFFICIENT_BUFFER from GetTokenInformation, and got no error!" elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER Chef::ReservedNames::Win32::Error.raise! end owner_result_storage = FFI::MemoryPointer.new owner_result_size.read_ulong unless GetTokenInformation(token.handle.handle, :TokenOwner, owner_result_storage, owner_result_size.read_ulong, owner_result_size) Chef::ReservedNames::Win32::Error.raise! end owner_result = TOKEN_OWNER.new owner_result_storage SID.new(owner_result[:Owner], owner_result_storage) end def self.get_token_information_primary_group(token) group_result_size = FFI::MemoryPointer.new(:ulong) if GetTokenInformation(token.handle.handle, :TokenPrimaryGroup, nil, 0, group_result_size) raise "Expected ERROR_INSUFFICIENT_BUFFER from GetTokenInformation, and got no error!" elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER Chef::ReservedNames::Win32::Error.raise! end group_result_storage = FFI::MemoryPointer.new group_result_size.read_ulong unless GetTokenInformation(token.handle.handle, :TokenPrimaryGroup, group_result_storage, group_result_size.read_ulong, group_result_size) Chef::ReservedNames::Win32::Error.raise! end group_result = TOKEN_PRIMARY_GROUP.new group_result_storage SID.new(group_result[:PrimaryGroup], group_result_storage) end def self.initialize_acl(acl_size) acl = FFI::MemoryPointer.new acl_size unless InitializeAcl(acl, acl_size, ACL_REVISION) Chef::ReservedNames::Win32::Error.raise! end ACL.new(acl) end def self.initialize_security_descriptor(revision = SECURITY_DESCRIPTOR_REVISION) security_descriptor = FFI::MemoryPointer.new SECURITY_DESCRIPTOR_MIN_LENGTH unless InitializeSecurityDescriptor(security_descriptor, revision) Chef::ReservedNames::Win32::Error.raise! end SecurityDescriptor.new(security_descriptor) end def self.is_valid_acl(acl) acl = acl.pointer if acl.respond_to?(:pointer) IsValidAcl(acl) != 0 end def self.is_valid_security_descriptor(security_descriptor) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) IsValidSecurityDescriptor(security_descriptor) != 0 end def self.is_valid_sid(sid) sid = sid.pointer if sid.respond_to?(:pointer) IsValidSid(sid) != 0 end def self.lookup_account_name(name, system_name = nil) # Figure out how big the buffers need to be sid_size = FFI::Buffer.new(:long).write_long(0) referenced_domain_name_size = FFI::Buffer.new(:long).write_long(0) system_name = system_name.to_wstring if system_name if LookupAccountNameW(system_name, name.to_wstring, nil, sid_size, nil, referenced_domain_name_size, nil) raise "Expected ERROR_INSUFFICIENT_BUFFER from LookupAccountName, and got no error!" elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER Chef::ReservedNames::Win32::Error.raise! end sid = FFI::MemoryPointer.new :char, sid_size.read_long referenced_domain_name = FFI::MemoryPointer.new :char, (referenced_domain_name_size.read_long * 2) use = FFI::Buffer.new(:long).write_long(0) unless LookupAccountNameW(system_name, name.to_wstring, sid, sid_size, referenced_domain_name, referenced_domain_name_size, use) Chef::ReservedNames::Win32::Error.raise! end [ referenced_domain_name.read_wstring(referenced_domain_name_size.read_long), SID.new(sid), use.read_long ] end def self.lookup_account_sid(sid, system_name = nil) sid = sid.pointer if sid.respond_to?(:pointer) # Figure out how big the buffer needs to be name_size = FFI::Buffer.new(:long).write_long(0) referenced_domain_name_size = FFI::Buffer.new(:long).write_long(0) system_name = system_name.to_wstring if system_name if LookupAccountSidW(system_name, sid, nil, name_size, nil, referenced_domain_name_size, nil) raise "Expected ERROR_INSUFFICIENT_BUFFER from LookupAccountSid, and got no error!" elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER Chef::ReservedNames::Win32::Error.raise! end name = FFI::MemoryPointer.new :char, (name_size.read_long * 2) referenced_domain_name = FFI::MemoryPointer.new :char, (referenced_domain_name_size.read_long * 2) use = FFI::Buffer.new(:long).write_long(0) unless LookupAccountSidW(system_name, sid, name, name_size, referenced_domain_name, referenced_domain_name_size, use) Chef::ReservedNames::Win32::Error.raise! end [ referenced_domain_name.read_wstring(referenced_domain_name_size.read_long), name.read_wstring(name_size.read_long), use.read_long ] end def self.lookup_privilege_name(system_name, luid) system_name = system_name.to_wstring if system_name name_size = FFI::Buffer.new(:long).write_long(0) if LookupPrivilegeNameW(system_name, luid, nil, name_size) raise "Expected ERROR_INSUFFICIENT_BUFFER from LookupPrivilegeName, and got no error!" elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER Chef::ReservedNames::Win32::Error.raise! end name = FFI::MemoryPointer.new :char, (name_size.read_long * 2) unless LookupPrivilegeNameW(system_name, luid, name, name_size) Chef::ReservedNames::Win32::Error.raise! end name.read_wstring(name_size.read_long) end def self.lookup_privilege_display_name(system_name, name) system_name = system_name.to_wstring if system_name display_name_size = FFI::Buffer.new(:long).write_long(0) language_id = FFI::Buffer.new(:long) if LookupPrivilegeDisplayNameW(system_name, name.to_wstring, nil, display_name_size, language_id) raise "Expected ERROR_INSUFFICIENT_BUFFER from LookupPrivilegeDisplayName, and got no error!" elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER Chef::ReservedNames::Win32::Error.raise! end display_name = FFI::MemoryPointer.new :char, (display_name_size.read_long * 2) unless LookupPrivilegeDisplayNameW(system_name, name.to_wstring, display_name, display_name_size, language_id) Chef::ReservedNames::Win32::Error.raise! end [ display_name.read_wstring(display_name_size.read_long), language_id.read_long ] end def self.lookup_privilege_value(system_name, name) luid = FFI::Buffer.new(:uint64).write_uint64(0) system_name = system_name.to_wstring if system_name unless LookupPrivilegeValueW(system_name, name.to_wstring, luid) Win32::Error.raise! end luid.read_uint64 end def self.make_absolute_sd(security_descriptor) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) # Figure out buffer sizes absolute_sd_size = FFI::Buffer.new(:long).write_long(0) dacl_size = FFI::Buffer.new(:long).write_long(0) sacl_size = FFI::Buffer.new(:long).write_long(0) owner_size = FFI::Buffer.new(:long).write_long(0) group_size = FFI::Buffer.new(:long).write_long(0) if MakeAbsoluteSD(security_descriptor, nil, absolute_sd_size, nil, dacl_size, nil, sacl_size, nil, owner_size, nil, group_size) raise "Expected ERROR_INSUFFICIENT_BUFFER from MakeAbsoluteSD, and got no error!" elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER Chef::ReservedNames::Win32::Error.raise! end absolute_sd = FFI::MemoryPointer.new absolute_sd_size.read_long owner = FFI::MemoryPointer.new owner_size.read_long group = FFI::MemoryPointer.new group_size.read_long dacl = FFI::MemoryPointer.new dacl_size.read_long sacl = FFI::MemoryPointer.new sacl_size.read_long unless MakeAbsoluteSD(security_descriptor, absolute_sd, absolute_sd_size, dacl, dacl_size, sacl, sacl_size, owner, owner_size, group, group_size) Chef::ReservedNames::Win32::Error.raise! end [ SecurityDescriptor.new(absolute_sd), SID.new(owner), SID.new(group), ACL.new(dacl), ACL.new(sacl) ] end def self.open_current_process_token(desired_access = TOKEN_READ) open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, desired_access) end def self.open_process_token(process, desired_access) process = process.handle if process.respond_to?(:handle) process = process.handle if process.respond_to?(:handle) token = FFI::Buffer.new(:ulong) unless OpenProcessToken(process, desired_access, token) Chef::ReservedNames::Win32::Error.raise! end Token.new(Handle.new(token.read_ulong)) end def self.query_security_access_mask(security_information) result = FFI::Buffer.new(:long) QuerySecurityAccessMask(security_information, result) result.read_long end def self.set_file_security(path, security_information, security_descriptor) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) unless SetFileSecurityW(path.to_wstring, security_information, security_descriptor) Chef::ReservedNames::Win32::Error.raise! end end def self.set_named_security_info(path, type, args) owner = args[:owner] group = args[:group] dacl = args[:dacl] sacl = args[:sacl] owner = owner.pointer if owner && owner.respond_to?(:pointer) group = group.pointer if group && group.respond_to?(:pointer) dacl = dacl.pointer if dacl && dacl.respond_to?(:pointer) sacl = sacl.pointer if sacl && sacl.respond_to?(:pointer) # Determine the security_information flags security_information = 0 security_information |= OWNER_SECURITY_INFORMATION if args.has_key?(:owner) security_information |= GROUP_SECURITY_INFORMATION if args.has_key?(:group) security_information |= DACL_SECURITY_INFORMATION if args.has_key?(:dacl) security_information |= SACL_SECURITY_INFORMATION if args.has_key?(:sacl) if args.has_key?(:dacl_inherits) security_information |= (args[:dacl_inherits] ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION) end if args.has_key?(:sacl_inherits) security_information |= (args[:sacl_inherits] ? UNPROTECTED_SACL_SECURITY_INFORMATION : PROTECTED_SACL_SECURITY_INFORMATION) end hr = SetNamedSecurityInfoW(path.to_wstring, type, security_information, owner, group, dacl, sacl) if hr != ERROR_SUCCESS Chef::ReservedNames::Win32::Error.raise! end end def self.set_security_access_mask(security_information) result = FFI::Buffer.new(:long) SetSecurityAccessMask(security_information, result) result.read_long end def set_security_descriptor_dacl(security_descriptor, acl, defaulted = false, present = nil) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) acl = acl.pointer if acl.respond_to?(:pointer) present = !security_descriptor.null? if present == nil unless SetSecurityDescriptorDacl(security_descriptor, present, acl, defaulted) Chef::ReservedNames::Win32::Error.raise! end end def self.set_security_descriptor_group(security_descriptor, sid, defaulted = false) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) sid = sid.pointer if sid.respond_to?(:pointer) unless SetSecurityDescriptorGroup(security_descriptor, sid, defaulted) Chef::ReservedNames::Win32::Error.raise! end end def self.set_security_descriptor_owner(security_descriptor, sid, defaulted = false) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) sid = sid.pointer if sid.respond_to?(:pointer) unless SetSecurityDescriptorOwner(security_descriptor, sid, defaulted) Chef::ReservedNames::Win32::Error.raise! end end def self.set_security_descriptor_sacl(security_descriptor, acl, defaulted = false, present = nil) security_descriptor = security_descriptor.pointer if security_descriptor.respond_to?(:pointer) acl = acl.pointer if acl.respond_to?(:pointer) present = !security_descriptor.null? if present == nil unless SetSecurityDescriptorSacl(security_descriptor, present, acl, defaulted) Chef::ReservedNames::Win32::Error.raise! end end def self.with_lsa_policy(username) sid = lookup_account_name(username)[1] access = 0 access |= POLICY_CREATE_ACCOUNT access |= POLICY_LOOKUP_NAMES policy_handle = FFI::MemoryPointer.new(:pointer) result = LsaOpenPolicy(nil, LSA_OBJECT_ATTRIBUTES.new, access, policy_handle) win32_error = LsaNtStatusToWinError(result) if win32_error != 0 Chef::ReservedNames::Win32::Error.raise!(nil, win32_error) end begin yield policy_handle, sid.pointer ensure win32_error = LsaNtStatusToWinError(LsaClose(policy_handle.read_pointer)) if win32_error != 0 Chef::ReservedNames::Win32::Error.raise!(nil, win32_error) end end end def self.with_privileges(*privilege_names) # Set privileges token = open_current_process_token(TOKEN_READ | TOKEN_ADJUST_PRIVILEGES) old_privileges = token.enable_privileges(*privilege_names) # Let the caller do their privileged stuff begin yield ensure # Set privileges back to what they were before token.adjust_privileges(old_privileges) end end # Checks if the caller has the admin privileges in their # security token def self.has_admin_privileges? if Chef::Platform.windows_server_2003? # Admin privileges do not exist on Windows Server 2003 true else process_token = open_current_process_token(TOKEN_READ) elevation_result = FFI::Buffer.new(:ulong) elevation_result_size = FFI::MemoryPointer.new(:uint32) success = GetTokenInformation(process_token.handle.handle, :TokenElevation, elevation_result, 4, elevation_result_size) # Assume process is not elevated if the call fails. # Process is elevated if the result is different than 0. success && (elevation_result.read_ulong != 0) end end def self.logon_user(username, domain, password, logon_type, logon_provider) username = wstring(username) domain = wstring(domain) password = wstring(password) token = FFI::Buffer.new(:pointer) unless LogonUserW(username, domain, password, logon_type, logon_provider, token) Chef::ReservedNames::Win32::Error.raise! end Token.new(Handle.new(token.read_pointer)) end end end end require "chef/win32/security/ace" require "chef/win32/security/acl" require "chef/win32/security/securable_object" require "chef/win32/security/security_descriptor" require "chef/win32/security/sid" chef-12.14.60/lib/chef/win32/security/000077500000000000000000000000001276456504500171545ustar00rootroot00000000000000chef-12.14.60/lib/chef/win32/security/ace.rb000066400000000000000000000073271276456504500202420ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/security" require "chef/win32/security/sid" require "chef/win32/memory" require "ffi" class Chef module ReservedNames::Win32 class Security class ACE def initialize(pointer, owner = nil) if Chef::ReservedNames::Win32::API::Security::ACE_WITH_MASK_AND_SID.supports?(pointer.read_uchar) @struct = Chef::ReservedNames::Win32::API::Security::ACE_WITH_MASK_AND_SID.new pointer else # TODO Support ALL the things @struct = Chef::ReservedNames::Win32::API::Security::ACE_HEADER.new pointer end # Keep a reference to the actual owner of this memory so we don't get freed @owner = owner end def self.size_with_sid(sid) Chef::ReservedNames::Win32::API::Security::ACE_WITH_MASK_AND_SID.offset_of(:SidStart) + sid.size end def self.access_allowed(sid, mask, flags = 0) create_ace_with_mask_and_sid(Chef::ReservedNames::Win32::API::Security::ACCESS_ALLOWED_ACE_TYPE, flags, mask, sid) end def self.access_denied(sid, mask, flags = 0) create_ace_with_mask_and_sid(Chef::ReservedNames::Win32::API::Security::ACCESS_DENIED_ACE_TYPE, flags, mask, sid) end attr_reader :struct def ==(other) type == other.type && flags == other.flags && mask == other.mask && sid == other.sid end def dup ACE.create_ace_with_mask_and_sid(type, flags, mask, sid) end def flags struct[:AceFlags] end def flags=(val) struct[:AceFlags] = val end def explicit? ! inherited? end def inherited? (struct[:AceFlags] & Chef::ReservedNames::Win32::API::Security::INHERITED_ACE) != 0 end def mask struct[:Mask] end def mask=(val) struct[:Mask] = val end def pointer struct.pointer end def size struct[:AceSize] end def sid # The SID runs off the end of the structure, starting at :SidStart. # Use pointer arithmetic to get a pointer to that location. Chef::ReservedNames::Win32::Security::SID.new(struct.pointer + struct.offset_of(:SidStart)) end def to_s "#{sid.account_name}/flags:#{flags.to_s(16)}/mask:#{mask.to_s(16)}" end def type struct[:AceType] end def self.create_ace_with_mask_and_sid(type, flags, mask, sid) size_needed = size_with_sid(sid) pointer = FFI::MemoryPointer.new size_needed struct = Chef::ReservedNames::Win32::API::Security::ACE_WITH_MASK_AND_SID.new pointer struct[:AceType] = type struct[:AceFlags] = flags struct[:AceSize] = size_needed struct[:Mask] = mask Chef::ReservedNames::Win32::Memory.memcpy(struct.pointer + struct.offset_of(:SidStart), sid.pointer, sid.size) ACE.new(struct.pointer) end end end end end chef-12.14.60/lib/chef/win32/security/acl.rb000066400000000000000000000056671276456504500202560ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/security" require "chef/win32/security/ace" require "ffi" class Chef module ReservedNames::Win32 class Security class ACL include Enumerable def initialize(pointer, owner = nil) @struct = Chef::ReservedNames::Win32::API::Security::ACLStruct.new pointer # Keep a reference to the actual owner of this memory so that it isn't freed out from under us # TODO this could be avoided if we could mark a pointer's parent manually @owner = owner end def self.create(aces) aces_size = aces.inject(0) { |sum, ace| sum + ace.size } acl_size = align_dword(Chef::ReservedNames::Win32::API::Security::ACLStruct.size + aces_size) # What the heck is 94??? acl = Chef::ReservedNames::Win32::Security.initialize_acl(acl_size) aces.each { |ace| Chef::ReservedNames::Win32::Security.add_ace(acl, ace) } acl end attr_reader :struct def ==(other) return false if length != other.length 0.upto(length - 1) do |i| return false if self[i] != other[i] end return true end def pointer struct.pointer end def [](index) Chef::ReservedNames::Win32::Security.get_ace(self, index) end def delete_at(index) Chef::ReservedNames::Win32::Security.delete_ace(self, index) end def each 0.upto(length - 1) { |i| yield self[i] } end def insert(index, *aces) aces.reverse_each { |ace| add_ace(self, ace, index) } end def length struct[:AceCount] end def push(*aces) aces.each { |ace| Chef::ReservedNames::Win32::Security.add_ace(self, ace) } end def unshift(*aces) aces.each { |ace| Chef::ReservedNames::Win32::Security.add_ace(self, ace, 0) } end def valid? Chef::ReservedNames::Win32::Security.is_valid_acl(self) end def to_s "[#{self.collect { |ace| ace.to_s }.join(", ")}]" end def self.align_dword(size) (size + 4 - 1) & 0xfffffffc end private_class_method :align_dword end end end end chef-12.14.60/lib/chef/win32/security/securable_object.rb000066400000000000000000000123041276456504500227740ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/security" require "chef/win32/security/acl" require "chef/win32/security/sid" class Chef module ReservedNames::Win32 class Security class SecurableObject def initialize(path, type = :SE_FILE_OBJECT) @path = path @type = type end attr_reader :path attr_reader :type SecurityConst = Chef::ReservedNames::Win32::API::Security # This method predicts what the rights mask would be on an object # if you created an ACE with the given mask. Specifically, it looks for # generic attributes like GENERIC_READ, and figures out what specific # attributes will be set. This is important if you want to try to # compare an existing ACE with one you want to create. def predict_rights_mask(generic_mask) mask = generic_mask #mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_READ if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_READ) != 0 #mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_WRITE if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE) != 0 #mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_EXECUTE if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE) != 0 #mask |= Chef::ReservedNames::Win32::API::Security::STANDARD_RIGHTS_ALL if (mask | Chef::ReservedNames::Win32::API::Security::GENERIC_ALL) != 0 if type == :SE_FILE_OBJECT mask |= Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ if (mask & Chef::ReservedNames::Win32::API::Security::GENERIC_READ) != 0 mask |= Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE if (mask & Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE) != 0 mask |= Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE if (mask & Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE) != 0 mask |= Chef::ReservedNames::Win32::API::Security::FILE_ALL_ACCESS if (mask & Chef::ReservedNames::Win32::API::Security::GENERIC_ALL) != 0 else raise "Unimplemented object type for predict_security_mask: #{type}" end mask &= ~(Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::GENERIC_ALL) mask end def security_descriptor(include_sacl = false) security_information = Chef::ReservedNames::Win32::API::Security::OWNER_SECURITY_INFORMATION | Chef::ReservedNames::Win32::API::Security::GROUP_SECURITY_INFORMATION | Chef::ReservedNames::Win32::API::Security::DACL_SECURITY_INFORMATION if include_sacl security_information |= Chef::ReservedNames::Win32::API::Security::SACL_SECURITY_INFORMATION Security.with_privileges("SeSecurityPrivilege") do Security.get_named_security_info(path, type, security_information) end else Security.get_named_security_info(path, type, security_information) end end def dacl=(val) Security.set_named_security_info(path, type, :dacl => val) end # You don't set dacl_inherits without also setting dacl, # because Windows gets angry and denies you access. So # if you want to do that, you may as well do both at once. def set_dacl(dacl, dacl_inherits) Security.set_named_security_info(path, type, :dacl => dacl, :dacl_inherits => dacl_inherits) end def group=(val) Security.set_named_security_info(path, type, :group => val) end def owner=(val) # TODO to fix serious permissions problems, we may need to enable SeBackupPrivilege. But we might need it (almost) everywhere else, too. Security.with_privileges("SeTakeOwnershipPrivilege", "SeRestorePrivilege") do Security.set_named_security_info(path, type, :owner => val) end end def sacl=(val) Security.with_privileges("SeSecurityPrivilege") do Security.set_named_security_info(path, type, :sacl => val) end end def set_sacl(sacl, sacl_inherits) Security.with_privileges("SeSecurityPrivilege") do Security.set_named_security_info(path, type, :sacl => sacl, :sacl_inherits => sacl_inherits) end end end end end end chef-12.14.60/lib/chef/win32/security/security_descriptor.rb000066400000000000000000000053141276456504500236110ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/security" require "chef/win32/security/acl" require "chef/win32/security/sid" class Chef module ReservedNames::Win32 class Security class SecurityDescriptor def initialize(pointer) @pointer = pointer end attr_reader :pointer def absolute? !self_relative? end def control control, version = Chef::ReservedNames::Win32::Security.get_security_descriptor_control(self) control end def dacl raise "DACL not present" if !dacl_present? present, acl, defaulted = Chef::ReservedNames::Win32::Security.get_security_descriptor_dacl(self) acl end def dacl_inherits? (control & Chef::ReservedNames::Win32::API::Security::SE_DACL_PROTECTED) == 0 end def dacl_present? (control & Chef::ReservedNames::Win32::API::Security::SE_DACL_PRESENT) != 0 end def group result, defaulted = Chef::ReservedNames::Win32::Security.get_security_descriptor_group(self) result end def owner result, defaulted = Chef::ReservedNames::Win32::Security.get_security_descriptor_owner(self) result end def sacl raise "SACL not present" if !sacl_present? Security.with_privileges("SeSecurityPrivilege") do present, acl, defaulted = Chef::ReservedNames::Win32::Security.get_security_descriptor_sacl(self) acl end end def sacl_inherits? (control & Chef::ReservedNames::Win32::API::Security::SE_SACL_PROTECTED) == 0 end def sacl_present? (control & Chef::ReservedNames::Win32::API::Security::SE_SACL_PRESENT) != 0 end def self_relative? (control & Chef::ReservedNames::Win32::API::Security::SE_SELF_RELATIVE) != 0 end def valid? Chef::ReservedNames::Win32::Security.is_valid_security_descriptor(self) end end end end end chef-12.14.60/lib/chef/win32/security/sid.rb000066400000000000000000000215251276456504500202650ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/security" require "chef/win32/api/net" require "chef/win32/api/error" require "wmi-lite/wmi" class Chef module ReservedNames::Win32 class Security class SID include Chef::ReservedNames::Win32::API::Net include Chef::ReservedNames::Win32::API::Error class << self include Chef::ReservedNames::Win32::API::Net include Chef::ReservedNames::Win32::API::Error end def initialize(pointer, owner = nil) @pointer = pointer # Keep a reference to the actual owner of this memory so we don't get freed @owner = owner end def self.from_account(name) domain, sid, use = Chef::ReservedNames::Win32::Security.lookup_account_name(name) sid end def self.from_string_sid(string_sid) Chef::ReservedNames::Win32::Security.convert_string_sid_to_sid(string_sid) end def ==(other) other != nil && Chef::ReservedNames::Win32::Security.equal_sid(self, other) end attr_reader :pointer def account Chef::ReservedNames::Win32::Security.lookup_account_sid(self) end def account_name domain, name, use = account (domain != nil && domain.length > 0) ? "#{domain}\\#{name}" : name end def size Chef::ReservedNames::Win32::Security.get_length_sid(self) end def to_s Chef::ReservedNames::Win32::Security.convert_sid_to_string_sid(self) end def valid? Chef::ReservedNames::Win32::Security.is_valid_sid(self) end # Well-known SIDs def self.Null SID.from_string_sid("S-1-0") end def self.Nobody SID.from_string_sid("S-1-0-0") end def self.World SID.from_string_sid("S-1-1") end def self.Everyone SID.from_string_sid("S-1-1-0") end def self.Local SID.from_string_sid("S-1-2") end def self.Creator SID.from_string_sid("S-1-3") end def self.CreatorOwner SID.from_string_sid("S-1-3-0") end def self.CreatorGroup SID.from_string_sid("S-1-3-1") end def self.CreatorOwnerServer SID.from_string_sid("S-1-3-2") end def self.CreatorGroupServer SID.from_string_sid("S-1-3-3") end def self.NonUnique SID.from_string_sid("S-1-4") end def self.Nt SID.from_string_sid("S-1-5") end def self.Dialup SID.from_string_sid("S-1-5-1") end def self.Network SID.from_string_sid("S-1-5-2") end def self.Batch SID.from_string_sid("S-1-5-3") end def self.Interactive SID.from_string_sid("S-1-5-4") end def self.Service SID.from_string_sid("S-1-5-6") end def self.Anonymous SID.from_string_sid("S-1-5-7") end def self.Proxy SID.from_string_sid("S-1-5-8") end def self.EnterpriseDomainControllers SID.from_string_sid("S-1-5-9") end def self.PrincipalSelf SID.from_string_sid("S-1-5-10") end def self.AuthenticatedUsers SID.from_string_sid("S-1-5-11") end def self.RestrictedCode SID.from_string_sid("S-1-5-12") end def self.TerminalServerUsers SID.from_string_sid("S-1-5-13") end def self.LocalSystem SID.from_string_sid("S-1-5-18") end def self.NtLocal SID.from_string_sid("S-1-5-19") end def self.NtNetwork SID.from_string_sid("S-1-5-20") end def self.BuiltinAdministrators SID.from_string_sid("S-1-5-32-544") end def self.BuiltinUsers SID.from_string_sid("S-1-5-32-545") end def self.Guests SID.from_string_sid("S-1-5-32-546") end def self.PowerUsers SID.from_string_sid("S-1-5-32-547") end def self.AccountOperators SID.from_string_sid("S-1-5-32-548") end def self.ServerOperators SID.from_string_sid("S-1-5-32-549") end def self.PrintOperators SID.from_string_sid("S-1-5-32-550") end def self.BackupOperators SID.from_string_sid("S-1-5-32-551") end def self.Replicators SID.from_string_sid("S-1-5-32-552") end def self.Administrators SID.from_string_sid("S-1-5-32-544") end def self.None SID.from_account("#{::ENV['COMPUTERNAME']}\\None") end def self.Administrator SID.from_account("#{::ENV['COMPUTERNAME']}\\#{SID.admin_account_name}") end def self.Guest SID.from_account("#{::ENV['COMPUTERNAME']}\\Guest") end def self.current_user SID.from_account("#{::ENV['USERDOMAIN']}\\#{::ENV['USERNAME']}") end # See https://technet.microsoft.com/en-us/library/cc961992.aspx # In practice, this is SID.Administrators if the current_user is an admin (even if not # running elevated), and is current_user otherwise. On win2k3, it technically can be # current_user in all cases if a certain group policy is set. def self.default_security_object_owner token = Chef::ReservedNames::Win32::Security.open_current_process_token Chef::ReservedNames::Win32::Security.get_token_information_owner(token) end # See https://technet.microsoft.com/en-us/library/cc961996.aspx # In practice, this seems to be SID.current_user for Microsoft Accounts, the current # user's Domain Users group for domain accounts, and SID.None otherwise. def self.default_security_object_group token = Chef::ReservedNames::Win32::Security.open_current_process_token Chef::ReservedNames::Win32::Security.get_token_information_primary_group(token) end def self.admin_account_name @admin_account_name ||= begin admin_account_name = nil # Call NetUserEnum to enumerate the users without hitting network # http://msdn.microsoft.com/en-us/library/windows/desktop/aa370652(v=vs.85).aspx servername = nil # We are querying the local server level = 3 # We want USER_INFO_3 structure which contains the SID filter = FILTER_NORMAL_ACCOUNT # Only query the user accounts bufptr = FFI::MemoryPointer.new(:pointer) # Buffer which will receive the data prefmaxlen = MAX_PREFERRED_LENGTH # Let the system allocate the needed amount of memory entriesread = FFI::Buffer.new(:long).write_long(0) totalentries = FFI::Buffer.new(:long).write_long(0) resume_handle = FFI::Buffer.new(:long).write_long(0) status = ERROR_MORE_DATA while status == ERROR_MORE_DATA status = NetUserEnum(servername, level, filter, bufptr, prefmaxlen, entriesread, totalentries, resume_handle) if status == NERR_Success || status == ERROR_MORE_DATA Array.new(entriesread.read_long) do |i| user_info = USER_INFO_3.new(bufptr.read_pointer + i * USER_INFO_3.size) # Check if the account is the Administrator account # RID for the Administrator account is always 500 and it's privilage is set to USER_PRIV_ADMIN if user_info[:usri3_user_id] == 500 && user_info[:usri3_priv] == 2 # USER_PRIV_ADMIN (2) - Administrator admin_account_name = user_info[:usri3_name].read_wstring break end end # Free the memory allocated by the system NetApiBufferFree(bufptr.read_pointer) end end raise "Can not determine the administrator account name." if admin_account_name.nil? admin_account_name end end end end end end chef-12.14.60/lib/chef/win32/security/token.rb000066400000000000000000000056461276456504500206340ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/security" require "chef/win32/api/security" require "chef/win32/unicode" require "ffi" class Chef module ReservedNames::Win32 class Security class Token def initialize(handle) @handle = handle end attr_reader :handle def enable_privileges(*privilege_names) # Build the list of privileges we want to set new_privileges = Chef::ReservedNames::Win32::API::Security::TOKEN_PRIVILEGES.new( FFI::MemoryPointer.new(Chef::ReservedNames::Win32::API::Security::TOKEN_PRIVILEGES.size_with_privileges(privilege_names.length))) new_privileges[:PrivilegeCount] = 0 privilege_names.each do |privilege_name| luid = Chef::ReservedNames::Win32::API::Security::LUID.new # Ignore failure (with_privileges TRIES but does not guarantee success-- # APIs down the line will fail if privilege escalation fails) if Chef::ReservedNames::Win32::API::Security.LookupPrivilegeValueW(nil, privilege_name.to_wstring, luid) new_privilege = new_privileges.privilege(new_privileges[:PrivilegeCount]) new_privilege[:Luid][:LowPart] = luid[:LowPart] new_privilege[:Luid][:HighPart] = luid[:HighPart] new_privilege[:Attributes] = Chef::ReservedNames::Win32::API::Security::SE_PRIVILEGE_ENABLED new_privileges[:PrivilegeCount] = new_privileges[:PrivilegeCount] + 1 end end old_privileges = Chef::ReservedNames::Win32::Security.adjust_token_privileges(self, new_privileges) end def adjust_privileges(privileges_struct) if privileges_struct[:PrivilegeCount] > 0 Chef::ReservedNames::Win32::Security.adjust_token_privileges(self, privileges_struct) end end def duplicate_token(security_impersonation_level) duplicate_token_handle = FFI::Buffer.new(:ulong) unless Chef::ReservedNames::Win32::API::Security.DuplicateToken(handle.handle, security_impersonation_level, duplicate_token_handle) raise Chef::ReservedNames::Win32::Error.raise! end Token.new(Handle.new(duplicate_token_handle.read_ulong)) end end end end end chef-12.14.60/lib/chef/win32/system.rb000077500000000000000000000035341276456504500171660ustar00rootroot00000000000000# # Author:: Salim Alam () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api/system" require "chef/win32/error" require "ffi" class Chef module ReservedNames::Win32 class System include Chef::ReservedNames::Win32::API::System extend Chef::ReservedNames::Win32::API::System def self.get_system_wow64_directory ptr = FFI::MemoryPointer.new(:char, 255, true) succeeded = GetSystemWow64DirectoryA(ptr, 255) if succeeded == 0 raise Win32APIError, "Failed to get Wow64 system directory" end ptr.read_string.strip end def self.wow64_disable_wow64_fs_redirection original_redirection_state = FFI::MemoryPointer.new(:pointer) succeeded = Wow64DisableWow64FsRedirection(original_redirection_state) if succeeded == 0 raise Win32APIError, "Failed to disable Wow64 file redirection" end original_redirection_state end def self.wow64_revert_wow64_fs_redirection(original_redirection_state) succeeded = Wow64RevertWow64FsRedirection(original_redirection_state) if succeeded == 0 raise Win32APIError, "Failed to revert Wow64 file redirection" end end end end end chef-12.14.60/lib/chef/win32/unicode.rb000066400000000000000000000030071276456504500172600ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/mixin/wide_string" require "chef/win32/api/unicode" class Chef module ReservedNames::Win32 class Unicode include Chef::ReservedNames::Win32::API::Unicode extend Chef::ReservedNames::Win32::API::Unicode end end end module FFI class Pointer include Chef::Mixin::WideString def read_wstring(num_wchars = nil) if num_wchars.nil? # Find the length of the string length = 0 last_char = nil while last_char != "\000\000" length += 1 last_char = self.get_bytes(0, length * 2)[-2..-1] end num_wchars = length end wide_to_utf8(self.get_bytes(0, num_wchars * 2)) end end end class String include Chef::Mixin::WideString def to_wstring utf8_to_wide(self) end end chef-12.14.60/lib/chef/win32/version.rb000066400000000000000000000155121276456504500173230ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/api" require "chef/win32/api/system" require "wmi-lite/wmi" class Chef module ReservedNames::Win32 class Version class << self include Chef::ReservedNames::Win32::API::System end include Chef::ReservedNames::Win32::API::Macros include Chef::ReservedNames::Win32::API::System # Ruby implementation of # http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx # http://msdn.microsoft.com/en-us/library/ms724358(v=vs.85).aspx def self.get_system_metrics(n_index) GetSystemMetrics(n_index) end private_class_method :get_system_metrics def self.method_name_from_marketing_name(marketing_name) "#{marketing_name.gsub(/\s/, '_').tr('.', '_').downcase}?" # "#{marketing_name.gsub(/\s/, '_').gsub(//, '_').downcase}?" end private_class_method :method_name_from_marketing_name WIN_VERSIONS = { "Windows 10" => { :major => 10, :minor => 0, :callable => lambda { |product_type, suite_mask| product_type == VER_NT_WORKSTATION } }, "Windows Server 2016" => { :major => 10, :minor => 0, :callable => lambda { |product_type, suite_mask| product_type != VER_NT_WORKSTATION } }, "Windows 8.1" => { :major => 6, :minor => 3, :callable => lambda { |product_type, suite_mask| product_type == VER_NT_WORKSTATION } }, "Windows Server 2012 R2" => { :major => 6, :minor => 3, :callable => lambda { |product_type, suite_mask| product_type != VER_NT_WORKSTATION } }, "Windows 8" => { :major => 6, :minor => 2, :callable => lambda { |product_type, suite_mask| product_type == VER_NT_WORKSTATION } }, "Windows Server 2012" => { :major => 6, :minor => 2, :callable => lambda { |product_type, suite_mask| product_type != VER_NT_WORKSTATION } }, "Windows 7" => { :major => 6, :minor => 1, :callable => lambda { |product_type, suite_mask| product_type == VER_NT_WORKSTATION } }, "Windows Server 2008 R2" => { :major => 6, :minor => 1, :callable => lambda { |product_type, suite_mask| product_type != VER_NT_WORKSTATION } }, "Windows Server 2008" => { :major => 6, :minor => 0, :callable => lambda { |product_type, suite_mask| product_type != VER_NT_WORKSTATION } }, "Windows Vista" => { :major => 6, :minor => 0, :callable => lambda { |product_type, suite_mask| product_type == VER_NT_WORKSTATION } }, "Windows Server 2003 R2" => { :major => 5, :minor => 2, :callable => lambda { |product_type, suite_mask| get_system_metrics(SM_SERVERR2) != 0 } }, "Windows Home Server" => { :major => 5, :minor => 2, :callable => lambda { |product_type, suite_mask| (suite_mask & VER_SUITE_WH_SERVER) == VER_SUITE_WH_SERVER } }, "Windows Server 2003" => { :major => 5, :minor => 2, :callable => lambda { |product_type, suite_mask| get_system_metrics(SM_SERVERR2) == 0 } }, "Windows XP" => { :major => 5, :minor => 1 }, "Windows 2000" => { :major => 5, :minor => 0 }, } def initialize @major_version, @minor_version, @build_number = get_version ver_info = get_version_ex @product_type = ver_info[:w_product_type] @suite_mask = ver_info[:w_suite_mask] @sp_major_version = ver_info[:w_service_pack_major] @sp_minor_version = ver_info[:w_service_pack_minor] # Obtain sku information for the purpose of identifying # datacenter, cluster, and core skus, the latter 2 only # exist in releases after Windows Server 2003 if ! Chef::Platform.windows_server_2003? @sku = get_product_info(@major_version, @minor_version, @sp_major_version, @sp_minor_version) else # The get_product_info API is not supported on Win2k3, # use an alternative to identify datacenter skus @sku = get_datacenter_product_info_windows_server_2003(ver_info) end end marketing_names = Array.new # General Windows checks WIN_VERSIONS.each do |k, v| method_name = method_name_from_marketing_name(k) define_method(method_name) do (@major_version == v[:major]) && (@minor_version == v[:minor]) && (v[:callable] ? v[:callable].call(@product_type, @suite_mask) : true) end marketing_names << [k, method_name] end define_method(:marketing_name) do marketing_names.each do |mn| break mn[0] if self.send(mn[1]) end end # Server Type checks %w{ cluster core datacenter }.each do |m| define_method("#{m}?") do self.class.constants.any? do |c| (self.class.const_get(c) == @sku) && (c.to_s =~ /#{m}/i ) end end end private def get_version # Use WMI here because API's like GetVersion return faked # version numbers on Windows Server 2012 R2 and Windows 8.1 -- # WMI always returns the truth. See article at # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx wmi = WmiLite::Wmi.new os_info = wmi.first_of("Win32_OperatingSystem") os_version = os_info["version"] # The operating system version is a string in the following form # that can be split into components based on the '.' delimiter: # MajorVersionNumber.MinorVersionNumber.BuildNumber os_version.split(".").collect { |version_string| version_string.to_i } end def get_version_ex lp_version_info = OSVERSIONINFOEX.new lp_version_info[:dw_os_version_info_size] = OSVERSIONINFOEX.size unless GetVersionExW(lp_version_info) Chef::ReservedNames::Win32::Error.raise! end lp_version_info end def get_product_info(major, minor, sp_major, sp_minor) out = FFI::MemoryPointer.new(:uint32) GetProductInfo(major, minor, sp_major, sp_minor, out) out.get_uint(0) end def get_datacenter_product_info_windows_server_2003(ver_info) # The intent is not to get the actual sku, just identify # Windows Server 2003 datacenter sku = (ver_info[:w_suite_mask] & VER_SUITE_DATACENTER) ? PRODUCT_DATACENTER_SERVER : 0 end end end end chef-12.14.60/lib/chef/workstation_config_loader.rb000066400000000000000000000014621276456504500221320ustar00rootroot00000000000000# # Author:: Claire McQuin () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef-config/workstation_config_loader" class Chef WorkstationConfigLoader = ChefConfig::WorkstationConfigLoader end chef-12.14.60/omnibus/000077500000000000000000000000001276456504500143445ustar00rootroot00000000000000chef-12.14.60/omnibus/.gitignore000066400000000000000000000002621276456504500163340ustar00rootroot00000000000000vendor/bundle pkg/* .kitchen.local.yml bin/* files/chef-server-cookbooks/cache/ files/msi/ChefClient-Config.wxi cookbooks vendor/cookbooks build_timestamp ldd.out Berksfile.lock chef-12.14.60/omnibus/.kitchen.vmware.yml000066400000000000000000000001411276456504500200660ustar00rootroot00000000000000driver: name: vagrant provider: vmware_fusion customize: numvcpus: 4 memsize: 4096 chef-12.14.60/omnibus/.kitchen.yml000066400000000000000000000104671276456504500166020ustar00rootroot00000000000000# # NOTE: this runs the omnibus cookbook, but does not actually run Omnibus. Use # 'kichen converge' to setup the virtual machine and then `kitchen login` to # SSH into the machine and run Omnibus. # driver: name: vagrant forward_agent: yes customize: cpus: 4 memory: 4096 synced_folders: - ['../..', '/vagrant/code'] - ['../../omnibus', '/home/vagrant/omnibus'] - ['../../omnibus-software', '/home/vagrant/omnibus-software'] provisioner: name: chef_zero # Always install the latest version of Chef. # This is not the version of chef that we're building - this is the version # of chef that omnibus needs to build chef/chef. require_chef_omnibus: true attributes: vagrant: this_key_exists_so_we_have_a_vagrant_key: true omnibus: build_user: vagrant build_user_group: vagrant build_user_password: vagrant product_name: angrychef product_version: latest chef_omnibus_root: /opt/angrychef platforms: - name: centos-5.11 run_list: yum-epel::default - name: centos-6.7 run_list: yum-epel::default - name: centos-7.2 run_list: yum-epel::default - name: debian-6.0.8 run_list: apt::default - name: debian-7.9 run_list: apt::default - name: debian-8.2 run_list: apt::default - name: freebsd-9.3 run_list: - freebsd::portsnap - freebsd::pkgng - name: freebsd-10.2 run_list: freebsd::portsnap - name: ubuntu-10.04 run_list: apt::default - name: ubuntu-12.04 run_list: apt::default - name: ubuntu-14.04 run_list: apt::default # The following (private) boxes are shared via Atlas and are only # available to users working for Chef. Sorry, it's about software licensing. # # Chef-internal users, you will need to: # 1. Create an Atlas account: https://atlas.hashicorp.com/ # 2. Ping #eng-services-support with your Atlas account name # to be added to the relevant team in Atlas, # 3. Do `vagrant login` with your Atlas creds so that you can download # the private boxes. # # The Mac OS X boxes are VMware only also. You can enable VMware Fusion # by activating the `.kitchen.vmware.yml` file with the `KITCHEN_LOCAL_YAML` # environment variable: # # KITCHEN_LOCAL_YAML=.kitchen.vmware.yml kitchen converge chefdk-macosx-109 # <% %w( 10.9 10.10 10.11 ).each do |mac_version| %> - name: macosx-<%= mac_version %> driver: box: chef/macosx-<%= mac_version %> # private synced_folders: - ['..', '/Users/vagrant/chef'] - ['../../omnibus', '/Users/vagrant/omnibus'] - ['../../omnibus-software', '/Users/vagrant/omnibus-software'] <% end %> - name: windows-2012r2-standard driver: box: chef/windows-server-2012r2-standard # private synced_folders: # We have to mount this repos enclosing folder as the Omnibus build # gets cranky if the mounted Chef source folder is a symlink. This # mounts at `C:\vagrant\code` and the Chef source folder is available # at `C:\vagrant\code\chef` - ['../..', '/vagrant/code'] provisioner: attributes: omnibus: build_user: vagrant build_user_group: Administrators build_user_password: vagrant chef_omnibus_root: /opscode/angrychef # By adding an `i386` to the name the Omnibus cookbook's `load-omnibus-toolchain.bat` # will load the 32-bit version of the MinGW toolchain. - name: windows-2012r2-standard-i386 driver: box: chef/windows-server-2012r2-standard # private synced_folders: # We have to mount this repos enclosing folder as the Omnibus build # gets cranky if the mounted ChefDK source folder is a symlink. This # mounts at `C:\vagrant\code` and the ChefDK source folder is available # at `C:\vagrant\code\chef-dk` - ['../..', '/vagrant/code'] provisioner: attributes: omnibus: build_user: vagrant build_user_group: Administrators build_user_password: vagrant chef_omnibus_root: /opscode/angrychef suites: # - name: angrychef # attributes: # omnibus: # <<: *attribute_defaults # install_dir: /opt/angrychef # run_list: # - omnibus::default - name: chef attributes: omnibus: install_dir: /opt/chef run_list: - omnibus::default chef-12.14.60/omnibus/Berksfile000066400000000000000000000004711276456504500161770ustar00rootroot00000000000000source "https://supermarket.chef.io" cookbook "omnibus" # Uncomment to use the latest version of the Omnibus cookbook from GitHub # cookbook 'omnibus', github: 'opscode-cookbooks/omnibus' group :integration do cookbook "apt", "~> 2.3" cookbook "freebsd", "~> 0.1" cookbook "yum-epel", "~> 0.3" end chef-12.14.60/omnibus/Gemfile000066400000000000000000000021621276456504500156400ustar00rootroot00000000000000source "https://rubygems.org" gem "omnibus", github: "chef/omnibus", branch: "rhass/COOL-502_with_gcc_investigate" gem "omnibus-software", github: "chef/omnibus-software", branch: "lcg/ruby23" gem "license_scout", github: "chef/license_scout" # pedump pessimistically pins multipart-post to a version from 2013 which makes # bundler very unhappy. Remove this when upstream has merged zed-0xff/pedump#6 . gem "pedump", git: "https://github.com/ksubrama/pedump.git", branch: "patch-1" # This development group is installed by default when you run `bundle install`, # but if you are using Omnibus in a CI-based infrastructure, you do not need # the Test Kitchen-based build lab. You can skip these unnecessary dependencies # by running `bundle install --without development` to speed up build times. group :development do # Use Berkshelf for resolving cookbook dependencies gem "berkshelf", "~> 4.0" # Use Test Kitchen with Vagrant for converging the build environment gem "test-kitchen", "~> 1.9" gem "kitchen-vagrant", "~> 0.19.0" gem "winrm-fs", "~> 0.4.0" gem "pry" gem "pry-byebug" gem "pry-stack_explorer" end chef-12.14.60/omnibus/Gemfile.lock000066400000000000000000000145021276456504500165700ustar00rootroot00000000000000GIT remote: git://github.com/chef/license_scout.git revision: 6e501d31d3957a95388422595db8fe5937092b15 specs: license_scout (0.1.2) ffi-yajl (~> 2.2) mixlib-shellout (~> 2.2) GIT remote: git://github.com/chef/omnibus-software.git revision: f0f4bb4beab18a9b6adbc0a34bcd2f0caf10be5c branch: lcg/ruby23 specs: omnibus-software (4.0.0) chef-sugar (>= 3.4.0) omnibus (>= 5.5.0) GIT remote: git://github.com/chef/omnibus.git revision: 98c9af20a0f79b6ac205613f509b4725eea47f7b branch: rhass/COOL-502_with_gcc_investigate specs: omnibus (5.5.0) aws-sdk (~> 2) chef-sugar (~> 3.3) cleanroom (~> 1.0) ffi-yajl (~> 2.2) license_scout mixlib-shellout (~> 2.0) mixlib-versioning ohai (~> 8.0) ruby-progressbar (~> 1.7) thor (~> 0.18) GIT remote: https://github.com/ksubrama/pedump.git revision: b4319556e18c80d2cba064ffe57fe0dea549dfe2 branch: patch-1 specs: pedump (0.5.0) awesome_print iostruct (>= 0.0.4) multipart-post (~> 1.2) progressbar zhexdump (>= 0.0.2) GEM remote: https://rubygems.org/ specs: addressable (2.4.0) artifactory (2.3.3) awesome_print (1.7.0) aws-sdk (2.5.8) aws-sdk-resources (= 2.5.8) aws-sdk-core (2.5.8) jmespath (~> 1.0) aws-sdk-resources (2.5.8) aws-sdk-core (= 2.5.8) berkshelf (4.3.5) addressable (~> 2.3, >= 2.3.4) berkshelf-api-client (~> 2.0, >= 2.0.2) buff-config (~> 1.0) buff-extensions (~> 1.0) buff-shell_out (~> 0.1) celluloid (= 0.16.0) celluloid-io (~> 0.16.1) cleanroom (~> 1.0) faraday (~> 0.9) httpclient (~> 2.7) minitar (~> 0.5, >= 0.5.4) mixlib-archive (~> 0.1) octokit (~> 4.0) retryable (~> 2.0) ridley (~> 4.5) solve (~> 2.0) thor (~> 0.19) berkshelf-api-client (2.0.2) faraday (~> 0.9.1) httpclient (~> 2.7.0) ridley (~> 4.5) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) buff-config (1.0.1) buff-extensions (~> 1.0) varia_model (~> 0.4) buff-extensions (1.0.0) buff-ignore (1.1.1) buff-ruby_engine (0.1.0) buff-shell_out (0.2.0) buff-ruby_engine (~> 0.1.0) builder (3.2.2) byebug (9.0.5) celluloid (0.16.0) timers (~> 4.0.0) celluloid-io (0.16.2) celluloid (>= 0.16.0) nio4r (>= 1.1.0) chef-config (12.13.37) fuzzyurl mixlib-config (~> 2.0) mixlib-shellout (~> 2.0) chef-sugar (3.4.0) cleanroom (1.0.0) coderay (1.1.1) debug_inspector (0.0.2) erubis (2.7.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) ffi (1.9.14) ffi (1.9.14-x86-mingw32) ffi-yajl (2.3.0) libyajl2 (~> 1.2) fuzzyurl (0.9.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) iostruct (0.0.4) ipaddress (0.8.3) jmespath (1.3.1) json (2.0.2) kitchen-vagrant (0.19.0) test-kitchen (~> 1.4) libyajl2 (1.2.0) 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-archive (0.2.0) mixlib-log mixlib-authentication (1.4.1) mixlib-log mixlib-cli (1.7.0) mixlib-config (2.2.4) mixlib-install (1.1.0) artifactory mixlib-shellout mixlib-versioning mixlib-log (1.7.1) mixlib-shellout (2.2.7) mixlib-shellout (2.2.7-universal-mingw32) win32-process (~> 0.8.2) wmi-lite (~> 1.0) mixlib-versioning (1.1.0) molinillo (0.4.5) multi_json (1.12.1) multipart-post (1.2.0) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (3.2.0) net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) nio4r (1.2.1) nori (2.6.0) octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) ohai (8.19.2) chef-config (>= 12.5.0.alpha.1, < 13) ffi (~> 1.9) ffi-yajl (~> 2.2) ipaddress mixlib-cli mixlib-config (~> 2.0) mixlib-log (>= 1.7.1, < 2.0) mixlib-shellout (~> 2.0) plist (~> 3.1) systemu (~> 2.6.4) wmi-lite (~> 1.0) plist (3.2.0) progressbar (0.21.0) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) pry-byebug (3.4.0) byebug (~> 9.0) pry (~> 0.10) pry-stack_explorer (0.4.9.2) binding_of_caller (>= 0.7) pry (>= 0.9.11) retryable (2.0.4) ridley (4.6.1) addressable buff-config (~> 1.0) buff-extensions (~> 1.0) buff-ignore (~> 1.1.1) buff-shell_out (~> 0.1) celluloid (~> 0.16.0) celluloid-io (~> 0.16.1) chef-config (>= 12.5.0) erubis faraday (~> 0.9.0) hashie (>= 2.0.2, < 4.0.0) httpclient (~> 2.7) json (>= 1.7.7) mixlib-authentication (>= 1.3.0) retryable (~> 2.0) semverse (~> 1.1) varia_model (~> 0.4.0) ruby-progressbar (1.8.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) systemu (2.6.5) test-kitchen (1.12.0) mixlib-install (~> 1.0, >= 1.0.4) mixlib-shellout (>= 1.2, < 3.0) net-scp (~> 1.1) net-ssh (>= 2.9, < 4.0) net-ssh-gateway (~> 1.2.0) safe_yaml (~> 1.0) thor (~> 0.18) thor (0.19.1) timers (4.0.4) hitimes 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) 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.3) erubis (~> 2.7) logging (>= 1.6.1, < 3.0) rubyzip (~> 1.1) winrm (~> 1.5) wmi-lite (1.0.0) zhexdump (0.0.2) PLATFORMS ruby x86-mingw32 DEPENDENCIES berkshelf (~> 4.0) kitchen-vagrant (~> 0.19.0) license_scout! omnibus! omnibus-software! pedump! pry pry-byebug pry-stack_explorer test-kitchen (~> 1.9) winrm-fs (~> 0.4.0) BUNDLED WITH 1.12.5 chef-12.14.60/omnibus/README.md000066400000000000000000000115671276456504500156350ustar00rootroot00000000000000Client Tools Omnibus project ============================ This project creates full-stack platform-specific packages for the following projects: * AngryChef * Chef * Chef with FIPS enabled Installation ------------ You must have a sane Ruby 1.9+ environment with Bundler installed. Ensure all the required gems are installed: ```shell $ bundle install --without development ``` Usage ----- ### Build You create a platform-specific package using the `build project` command: ```shell $ bundle exec omnibus build ``` The platform/architecture type of the package created will match the platform where the `build project` command is invoked. For example, running this command on a MacBook Pro will generate a Mac OS X package. After the build completes packages will be available in the `pkg/` folder. ### Clean You can clean up all temporary files generated during the build process with the `clean` command: ```shell $ bundle exec omnibus clean ``` Adding the `--purge` purge option removes __ALL__ files generated during the build including the project install directory (`/opt/chef`) and the package cache directory (`/var/cache/omnibus/pkg`): ```shell $ bundle exec omnibus clean --purge ``` ### Publish Omnibus has a built-in mechanism for releasing to a variety of "backends", such as Amazon S3 and Artifactory. You must set the proper credentials in your `omnibus.rb` config file or specify them via the command line. ```shell $ bundle exec omnibus publish path/to/*.deb --backend s3 ``` ### Help Full help for the Omnibus command line interface can be accessed with the `help` command: ```shell $ bundle exec omnibus help ``` Kitchen-based Build Environment ------------------------------- Every Omnibus project ships will a project-specific [Berksfile](http://berkshelf.com/) that will allow you to build your omnibus projects on all of the projects listed in the `.kitchen.yml`. You can add/remove additional platforms as needed by changing the list found in the `.kitchen.yml` `platforms` YAML stanza. This build environment is designed to get you up-and-running quickly. However, there is nothing that restricts you to building on other platforms. Simply use the [omnibus cookbook](https://github.com/opscode-cookbooks/omnibus) to setup your desired platform and execute the build steps listed above. The default build environment requires Test Kitchen and VirtualBox for local development. Test Kitchen also exposes the ability to provision instances using various cloud providers like AWS, DigitalOcean, or OpenStack. For more information, please see the [Test Kitchen documentation](http://kitchen.ci). Once you have tweaked your `.kitchen.yml` (or `.kitchen.local.yml`) to your liking, you can bring up an individual build environment using the `kitchen` command. ```shell $ bundle exec kitchen converge chef-ubuntu-1404 ``` Then login to the instance and build the project as described in the Usage section: ```shell $ bundle exec kitchen login -ubuntu-1204 [vagrant@ubuntu...] $ cd chef/omnibus [vagrant@ubuntu...] $ bundle install --without development # Don't install dev tools! [vagrant@ubuntu...] $ ... [vagrant@ubuntu...] $ bundle exec omnibus build -l internal ``` ```shell $ kitchen login chef-ubuntu-1404 [vagrant@ubuntu...] $ source load-omnibus-toolchain.sh [vagrant@ubuntu...] $ cd chef/omnibus [vagrant@ubuntu...] $ bundle install --without development # Don't install dev tools! [vagrant@ubuntu...] $ ... [vagrant@ubuntu...] $ bundle exec omnibus build chef -l internal ``` You can also login to Windows instances but will have to manually call the `load-omnibus-toolchain.bat` script which initializes the build environment. Please note the mounted code directory is also at `C:\home\vagrant\chef\omnibus` as opposed to `C:\Users\vagrant\chef\omnibus`. ```shell $ bundle exec kitchen login -windows-81-professional Last login: Sat Sep 13 10:19:04 2014 from 172.16.27.1 Microsoft Windows [Version 6.3.9600] (c) 2013 Microsoft Corporation. All rights reserved. C:\>C:\vagrant\load-omnibus-toolchain.bat C:\>cd C:\vagrant\code\chef\omnibus C:\vagrant\code\chef\omnibus>bundle install --without development C:\vagrant\code\chef\omnibus>bundle exec omnibus build chef -l internal ``` For a complete list of all commands and platforms, run `kitchen list` or `kitchen help`. License ------- ```text Copyright 2012-2016, Chef Software, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` chef-12.14.60/omnibus/config/000077500000000000000000000000001276456504500156115ustar00rootroot00000000000000chef-12.14.60/omnibus/config/projects/000077500000000000000000000000001276456504500174425ustar00rootroot00000000000000chef-12.14.60/omnibus/config/projects/angrychef.rb000066400000000000000000000027101276456504500217350ustar00rootroot00000000000000# # Copyright 2012-2016, Chef Software, Inc. # # 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. # # # This is a clone of the Chef project that we can install on the Chef build and # test machines. As such this project definition is just a thin wrapper around # `config/project/chef.rb`. # current_file = __FILE__ chef_project_contents = IO.read(File.expand_path("../chef.rb", __FILE__)) self.instance_eval chef_project_contents name "angrychef" friendly_name "Angry Chef Client" if windows? # NOTE: Ruby DevKit fundamentally CANNOT be installed into "Program Files" # Native gems will use gcc which will barf on files with spaces, # which is only fixable if everyone in the world fixes their Makefiles install_dir "#{default_root}/opscode/#{name}" package_name "angrychef" else install_dir "#{default_root}/#{name}" end resources_path "#{resources_path}/../chef" msi_upgrade_code = "D7FDDC1A-7668-404E-AD2F-61F875632A9C" project_location_dir = "angrychef" chef-12.14.60/omnibus/config/projects/chef.rb000066400000000000000000000055541276456504500207050ustar00rootroot00000000000000# # Copyright 2012-2016, Chef Software, Inc. # # 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. # name "chef" friendly_name "Chef Client" maintainer "Chef Software, Inc. " homepage "https://www.chef.io" license "Apache-2.0" license_file "../LICENSE" build_iteration 1 # Do not use __FILE__ after this point, use current_file. If you use __FILE__ # after this point, any dependent defs (ex: angrychef) that use instance_eval # will fail to work correctly. current_file ||= __FILE__ version_file = File.expand_path("../../../../VERSION", current_file) build_version IO.read(version_file).strip if windows? # NOTE: Ruby DevKit fundamentally CANNOT be installed into "Program Files" # Native gems will use gcc which will barf on files with spaces, # which is only fixable if everyone in the world fixes their Makefiles install_dir "#{default_root}/opscode/#{name}" package_name "chef-client" else install_dir "#{default_root}/#{name}" end # Global FIPS override flag. if windows? || rhel? override :fips, enabled: true end # Load dynamically updated overrides overrides_path = File.expand_path("../../../../omnibus_overrides.rb", current_file) instance_eval(IO.read(overrides_path), overrides_path) override :"ruby-windows-devkit", version: "4.5.2-20111229-1559" if windows? && windows_arch_i386? dependency "preparation" # All actual dependencies are in chef-complete, so that the addition # or removal of a dependency doesn't dirty the entire project file dependency "chef-complete" package :rpm do signing_passphrase ENV["OMNIBUS_RPM_SIGNING_PASSPHRASE"] end proj_to_work_around_cleanroom = self package :pkg do identifier "com.getchef.pkg.#{proj_to_work_around_cleanroom.name}" signing_identity "Developer ID Installer: Chef Software, Inc. (EU3VF8YLX2)" end compress :dmg msi_upgrade_code = "D607A85C-BDFA-4F08-83ED-2ECB4DCD6BC5" project_location_dir = name package :msi do fast_msi true upgrade_code msi_upgrade_code wix_candle_extension "WixUtilExtension" wix_light_extension "WixUtilExtension" signing_identity "F74E1A68005E8A9C465C3D2FF7B41F3988F0EA09", machine_store: true parameters ChefLogDllPath: windows_safe_path(gem_path("chef-[0-9]*-mingw32/ext/win32-eventlog/chef-log.dll")), ProjectLocationDir: project_location_dir end package :appx do signing_identity "F74E1A68005E8A9C465C3D2FF7B41F3988F0EA09", machine_store: true end chef-12.14.60/omnibus/config/software/000077500000000000000000000000001276456504500174435ustar00rootroot00000000000000chef-12.14.60/omnibus/config/software/chef-appbundle.rb000066400000000000000000000005721276456504500226510ustar00rootroot00000000000000name "chef-appbundle" default_version "local_source" license :project_license skip_transitive_dependency_licensing true source path: project.files_path dependency "chef" build do # This is where we get the definitions below require_relative "../../files/chef-appbundle/build-chef-appbundle" extend BuildChefAppbundle appbundle_gem "chef" appbundle_gem "ohai" end chef-12.14.60/omnibus/config/software/chef-complete.rb000066400000000000000000000007311276456504500225040ustar00rootroot00000000000000name "chef-complete" license :project_license skip_transitive_dependency_licensing true dependency "chef" dependency "chef-appbundle" dependency "chef-remove-docs" dependency "gem-permissions" dependency "shebang-cleanup" dependency "version-manifest" dependency "openssl-customization" if windows? # TODO can this be safely moved to before the chef? # It would make caching better ... dependency "ruby-windows-devkit" dependency "ruby-windows-devkit-bash" end chef-12.14.60/omnibus/config/software/chef-gem-binding_of_caller.rb000066400000000000000000000010231276456504500250550ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "MIT" license_file "https://github.com/banister/binding_of_caller/blob/master/LICENSE" skip_transitive_dependency_licensing true chef-12.14.60/omnibus/config/software/chef-gem-byebug.rb000066400000000000000000000010201276456504500227070ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "MIT" license_file "https://github.com/deivid-rodriguez/byebug/blob/master/LICENSE" skip_transitive_dependency_licensing true chef-12.14.60/omnibus/config/software/chef-gem-debug_inspector.rb000066400000000000000000000010231276456504500246110ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "MIT" license_file "https://github.com/banister/debug_inspector/blob/master/README.md" skip_transitive_dependency_licensing true chef-12.14.60/omnibus/config/software/chef-gem-ffi-yajl.rb000066400000000000000000000010461276456504500231430ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "MIT" license_file "https://github.com/chef/ffi-yajl/blob/master/LICENSE" skip_transitive_dependency_licensing true dependency "chef-gem-libyajl2" chef-12.14.60/omnibus/config/software/chef-gem-ffi.rb000066400000000000000000000012131276456504500222020ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "BSD-3-Clause" license_file "https://github.com/ffi/ffi/blob/master/LICENSE" license_file "https://github.com/ffi/ffi/blob/master/COPYING" license_file "https://github.com/ffi/ffi/blob/master/LICENSE.SPECS" skip_transitive_dependency_licensing true chef-12.14.60/omnibus/config/software/chef-gem-json.rb000066400000000000000000000011041276456504500224060ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "Ruby" license_file "https://github.com/flori/json/blob/master/README.md" license_file "https://www.ruby-lang.org/en/about/license.txt" skip_transitive_dependency_licensing true chef-12.14.60/omnibus/config/software/chef-gem-libyajl2.rb000066400000000000000000000010211276456504500231430ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "Apache-2.0" license_file "https://github.com/chef/libyajl2-gem/blob/master/LICENSE" skip_transitive_dependency_licensing true chef-12.14.60/omnibus/config/software/chef-gem-mini_portile2.rb000066400000000000000000000010251276456504500242130ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "MIT" license_file "https://github.com/flavorjones/mini_portile/blob/master/LICENSE.txt" skip_transitive_dependency_licensing true chef-12.14.60/omnibus/config/software/chef-gem-nokogiri.rb000066400000000000000000000011221276456504500232560ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "MIT" license_file "https://github.com/ruby-prof/ruby-prof/blob/master/LICENSE" skip_transitive_dependency_licensing true dependency "chef-gem-pkg-config" dependency "chef-gem-mini_portile2" chef-12.14.60/omnibus/config/software/chef-gem-pkg-config.rb000066400000000000000000000010251276456504500234630ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "LGPL-2.1" license_file "https://github.com/ruby-gnome2/pkg-config/blob/master/LGPL-2.1" skip_transitive_dependency_licensing true chef-12.14.60/omnibus/config/software/chef-gem-ruby-prof.rb000066400000000000000000000010251276456504500233640ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "BSD-2-Clause" license_file "https://github.com/ruby-prof/ruby-prof/blob/master/LICENSE" skip_transitive_dependency_licensing true chef-12.14.60/omnibus/config/software/chef-gem-ruby-shadow.rb000066400000000000000000000011311276456504500237010ustar00rootroot00000000000000# gem installs this gem from the version specified in chef's Gemfile.lock # so we can take advantage of omnibus's caching. Just duplicate this file and # add the new software def to chef software def if you want to separate # another gem's installation. require_relative "../../files/chef-gem/build-chef-gem/gem-install-software-def" BuildChefGem::GemInstallSoftwareDef.define(self, __FILE__) license "Public-Domain" license_file "https://github.com/apalmblad/ruby-shadow/blob/master/LICENSE" license_file "http://creativecommons.org/licenses/publicdomain/" skip_transitive_dependency_licensing true chef-12.14.60/omnibus/config/software/chef-remove-docs.rb000066400000000000000000000021341276456504500231160ustar00rootroot00000000000000# # Copyright 2012-2014 Chef Software, Inc. # # 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. # name "chef-remove-docs" license :project_license skip_transitive_dependency_licensing true build do # This is where we get the definitions below require_relative "../../files/chef/build-chef" extend BuildChef delete "#{install_dir}/embedded/docs" delete "#{install_dir}/embedded/share/man" delete "#{install_dir}/embedded/share/doc" delete "#{install_dir}/embedded/share/gtk-doc" delete "#{install_dir}/embedded/ssl/man" delete "#{install_dir}/embedded/man" delete "#{install_dir}/embedded/info" end chef-12.14.60/omnibus/config/software/chef.rb000066400000000000000000000053571276456504500207070ustar00rootroot00000000000000name "chef" default_version "local_source" license :project_license # For the specific super-special version "local_source", build the source from # the local git checkout. This is what you'd want to occur by default if you # just ran omnibus build locally. version("local_source") do source path: File.expand_path("../..", project.files_path), # Since we are using the local repo, we try to not copy any files # that are generated in the process of bundle installing omnibus. # If the install steps are well-behaved, this should not matter # since we only perform bundle and gem installs from the # omnibus cache source directory, but we do this regardless # to maintain consistency between what a local build sees and # what a github based build will see. options: { exclude: [ "omnibus/vendor" ] } end # For any version other than "local_source", fetch from github. if version != "local_source" source git: "git://github.com/chef/chef.git" end # For nokogiri dependency "libxml2" dependency "libxslt" dependency "libiconv" dependency "liblzma" dependency "zlib" # ruby and bundler and friends dependency "ruby" dependency "rubygems" dependency "bundler" # Install all the native gems separately # Worst offenders first to take best advantage of cache: dependency "chef-gem-ffi-yajl" dependency "chef-gem-nokogiri" dependency "chef-gem-libyajl2" dependency "chef-gem-ruby-prof" dependency "chef-gem-byebug" dependency "chef-gem-debug_inspector" dependency "chef-gem-binding_of_caller" # Now everyone else, in alphabetical order because we don't care THAT much Dir.entries(File.dirname(__FILE__)).sort.each do |gem_software| if gem_software =~ /^(chef-gem-.+)\.rb$/ dependency $1 end end build do # This is where we get the definitions below require_relative "../../files/chef/build-chef" extend BuildChef project_env = env.dup project_env["BUNDLE_GEMFILE"] = project_gemfile # Prepare to install: build config, retries, job, frozen=true # TODO Windows install seems to sometimes install already-installed gems such # as gherkin (and fail as a result) if you use jobs > 1. create_bundle_config(project_gemfile, retries: 4, jobs: windows? ? 1 : 7, frozen: true) # Install all the things. Arguments are specified in .bundle/config (see create_bundle_config) block { log.info(log_key) { "" } } bundle "install --verbose", env: project_env # Check that it worked block { log.info(log_key) { "" } } bundle "check", env: project_env # fix up git-sourced gems properly_reinstall_git_and_path_sourced_gems install_shared_gemfile # Check that the final gemfile worked block { log.info(log_key) { "" } } bundle "check", env: env, cwd: File.dirname(shared_gemfile) end chef-12.14.60/omnibus/files/000077500000000000000000000000001276456504500154465ustar00rootroot00000000000000chef-12.14.60/omnibus/files/chef-appbundle/000077500000000000000000000000001276456504500203235ustar00rootroot00000000000000chef-12.14.60/omnibus/files/chef-appbundle/build-chef-appbundle.rb000066400000000000000000000073171276456504500246320ustar00rootroot00000000000000require_relative "../chef-gem/build-chef-gem" module BuildChefAppbundle include BuildChefGem def lockdown_gem(gem_name) shared_gemfile = self.shared_gemfile # Update the Gemfile to restrict to built versions so that bundle installs # will do the right thing block "Lock down the #{gem_name} gem" do installed_path = shellout!("#{bundle_bin} show #{gem_name}", env: env, cwd: install_dir).stdout.chomp installed_gemfile = File.join(installed_path, "Gemfile") # # Include the main distribution Gemfile in the gem's Gemfile # # NOTE: if this fails and the build retries, you will see this multiple # times in the file. # distribution_gemfile = Pathname(shared_gemfile).relative_path_from(Pathname(installed_gemfile)).to_s gemfile_text = <<-EOM.gsub(/^\s+/, "") # Lock gems that are part of the distribution distribution_gemfile = File.expand_path(#{distribution_gemfile.inspect}, __FILE__) instance_eval(IO.read(distribution_gemfile), distribution_gemfile) EOM gemfile_text << IO.read(installed_gemfile) create_file(installed_gemfile) { gemfile_text } # Remove the gemfile.lock remove_file("#{installed_gemfile}.lock") if File.exist?("#{installed_gemfile}.lock") # If it's frozen, make it not be. shellout!("#{bundle_bin} config --delete frozen") # This could be changed to `bundle install` if we wanted to actually # install extra deps out of their gemfile ... shellout!("#{bundle_bin} lock", env: env, cwd: installed_path) # bundle lock doesn't always tell us when it fails, so we have to check :/ unless File.exist?("#{installed_gemfile}.lock") raise "bundle lock failed: no #{installed_gemfile}.lock created!" end # Ensure all the gems we need are actually installed (if the bundle adds # something, we need to know about it so we can include it in the main # solve). # Save bundle config and modify to use --without development before checking bundle_config = File.expand_path("../.bundle/config", installed_gemfile) orig_config = IO.read(bundle_config) if File.exist?(bundle_config) # "test", "changelog" and "guard" come from berkshelf, "maintenance" comes from chef # "tools" and "integration" come from inspec shellout!("#{bundle_bin} config --local without #{without_groups.join(":")}", env: env, cwd: installed_path) shellout!("#{bundle_bin} config --local frozen 1") shellout!("#{bundle_bin} check", env: env, cwd: installed_path) # Restore bundle config if orig_config create_file(bundle_config) { orig_config } else remove_file bundle_config end end end # appbundle the gem, making /opt/chef/bin/ do the superfast pinning # thing. # # To protect the app from loading the wrong versions of things, it uses # appbundler against the resulting file. # # Relocks the Gemfiles inside the specified gems (e.g. berkshelf, test-kitchen, # chef) to use the distribution's chosen gems. def appbundle_gem(gem_name) # First lock the gemfile down. lockdown_gem(gem_name) shared_gemfile = self.shared_gemfile # Ensure the main bin dir exists bin_dir = File.join(install_dir, "bin") mkdir(bin_dir) block "Lock down the #{gem_name} gem" do installed_path = shellout!("#{bundle_bin} show #{gem_name}", env: env, cwd: install_dir).stdout.chomp # appbundle the gem appbundler_args = [ installed_path, bin_dir, gem_name ] appbundler_args = appbundler_args.map { |a| ::Shellwords.escape(a) } shellout!("#{appbundler_bin} #{appbundler_args.join(" ")}", env: env, cwd: installed_path) end end end chef-12.14.60/omnibus/files/chef-gem/000077500000000000000000000000001276456504500171215ustar00rootroot00000000000000chef-12.14.60/omnibus/files/chef-gem/build-chef-gem.rb000066400000000000000000000067401276456504500222250ustar00rootroot00000000000000require "shellwords" require "pathname" require "bundler" require_relative "../../../version_policy" # Common definitions and helpers (like compile environment and binary # locations) for all software definitions. module BuildChefGem PLATFORM_FAMILY_FAMILIES = { "linux" => %w{wrlinux debian fedora rhel suse gentoo slackware arch exherbo alpine}, "bsd" => %w{dragonflybsd freebsd netbsd openbsd}, "solaris" => %w{smartos omnios openindiana opensolaris solaris2 nextentacore}, "aix" => %w{aix}, "windows" => %w{windows}, "mac_os_x" => %w{mac_os_x}, } def platform_family_families PLATFORM_FAMILY_FAMILIES.keys end def platform_family_family PLATFORM_FAMILY_FAMILIES. select { |key, families| families.include?(Omnibus::Ohai["platform_family"]) }. first[0] end def embedded_bin(binary) windows_safe_path("#{install_dir}/embedded/bin/#{binary}") end def appbundler_bin embedded_bin("appbundler") end def bundle_bin embedded_bin("bundle") end def gem_bin embedded_bin("gem") end def rake_bin embedded_bin("rake") end def without_groups # Add --without for every known OS except the one we're in. exclude_os_groups = platform_family_families - [ platform_family_family ] (INSTALL_WITHOUT_GROUPS + exclude_os_groups).map { |g| g.to_sym } end # # Get the path to the top level shared Gemfile included by all individual # Gemfiles # def shared_gemfile File.join(install_dir, "Gemfile") end # A common env for building everything including nokogiri and dep-selector-libgecode def env env = with_standard_compiler_flags(with_embedded_path, bfd_flags: true) # From dep-selector-libgecode # On some RHEL-based systems, the default GCC that's installed is 4.1. We # need to use 4.4, which is provided by the gcc44 and gcc44-c++ packages. # These do not use the gcc binaries so we set the flags to point to the # correct version here. if File.exist?("/usr/bin/gcc44") env["CC"] = "gcc44" env["CXX"] = "g++44" end # From dep-selector-libgecode # Ruby DevKit ships with BSD Tar env["PROG_TAR"] = "bsdtar" if windows? env["ARFLAGS"] = "rv #{env["ARFLAGS"]}" if env["ARFLAGS"] # Set up nokogiri environment and args env["NOKOGIRI_USE_SYSTEM_LIBRARIES"] = "true" env end # # Install arguments for various gems (to be passed to `gem install` or set in # `bundle config build.`). # def all_install_args @all_install_args = { "nokogiri" => %W{ --use-system-libraries --with-xml2-lib=#{Shellwords.escape("#{install_dir}/embedded/lib")} --with-xml2-include=#{Shellwords.escape("#{install_dir}/embedded/include/libxml2")} --with-xslt-lib=#{Shellwords.escape("#{install_dir}/embedded/lib")} --with-xslt-include=#{Shellwords.escape("#{install_dir}/embedded/include/libxslt")} --with-iconv-dir=#{Shellwords.escape("#{install_dir}/embedded")} --with-zlib-dir=#{Shellwords.escape("#{install_dir}/embedded")} }.join(" "), } end # gem install arguments for a particular gem. "" if no special args. def install_args_for(gem_name) all_install_args[gem_name] || "" end # Give block all the variables def block(*args, &block) super do extend BuildChefGem instance_eval(&block) end end # Give build all the variables def build(*args, &block) super do extend BuildChefGem instance_eval(&block) end end end chef-12.14.60/omnibus/files/chef-gem/build-chef-gem/000077500000000000000000000000001276456504500216715ustar00rootroot00000000000000chef-12.14.60/omnibus/files/chef-gem/build-chef-gem/gem-install-software-def.rb000066400000000000000000000072411276456504500270220ustar00rootroot00000000000000require "bundler" require "omnibus" require_relative "../build-chef-gem" require_relative "../../../../tasks/gemfile_util" module BuildChefGem class GemInstallSoftwareDef def self.define(software, software_filename) new(software, software_filename).send(:define) end include BuildChefGem include Omnibus::Logging protected def initialize(software, software_filename) @software = software @software_filename = software_filename end attr_reader :software, :software_filename def define software.name "#{File.basename(software_filename)[0..-4]}" software.default_version gem_version # If the source directory for building stuff changes, tell omnibus to # de-cache us software.source path: File.expand_path("../..", __FILE__) # ruby and bundler and friends software.dependency "ruby" software.dependency "rubygems" gem_name = self.gem_name gem_version = self.gem_version gem_metadata = self.gem_metadata lockfile_path = self.lockfile_path software.build do extend BuildChefGem if gem_version == "" if gem_metadata block do log.info(log_key) { "#{gem_name} has source #{gem_metadata} in #{lockfile_path}. We only cache rubygems.org installs in omnibus to keep things simple. The chef step will build #{gem_name} ..." } end else block do log.info(log_key) { "#{gem_name} is not in the #{lockfile_path}. This can happen if your OS doesn't build it, or if chef no longer depends on it. Skipping ..." } end end else block do log.info(log_key) { "Found version #{gem_version} of #{gem_name} in #{lockfile_path}. Building early to take advantage of omnibus caching ..." } end gem "install #{gem_name} -v #{gem_version} --no-doc --no-ri --ignore-dependencies --verbose -- #{install_args_for(gem_name)}", env: env end end end # Path above omnibus (where Gemfile is) def root_path File.expand_path("../../../../..", __FILE__) end def gemfile_path File.join(root_path, "Gemfile") end def lockfile_path "#{gemfile_path}.lock" end def gem_name @gem_name ||= begin # File must be named chef-.rb # Will look at chef/Gemfile.lock and install that version of the gem using "gem install" # (and only that version) if File.basename(software_filename) =~ /^chef-gem-(.+)\.rb$/ $1 else raise "#{software_filename} must be named chef-.rb to build a gem automatically" end end end def gem_metadata @gem_metadata ||= begin bundle = GemfileUtil::Bundle.parse(gemfile_path, lockfile_path) result = bundle.gems[gem_name] if result if bundle.select_gems(without_groups: without_groups).include?(gem_name) log.info(software.name) { "Using #{gem_name} version #{result[:version]} from #{gemfile_path}" } result else log.info(software.name) { "#{gem_name} not loaded from #{gemfile_path} because it was only in groups #{without_groups.join(", ")}. Skipping ..." } nil end else log.info(software.name) { "#{gem_name} was not found in #{lockfile_path}. Skipping ..." } nil end end end def gem_version @gem_version ||= begin if gem_metadata && URI(gem_metadata[:source]) == URI("https://rubygems.org/") gem_metadata[:version] else "" end end end end end chef-12.14.60/omnibus/files/chef/000077500000000000000000000000001276456504500163535ustar00rootroot00000000000000chef-12.14.60/omnibus/files/chef/build-chef.rb000066400000000000000000000133261276456504500207070ustar00rootroot00000000000000require "shellwords" require "pathname" require "bundler" require_relative "../chef-gem/build-chef-gem" require_relative "../../../version_policy" # We use this to break up the `build` method into readable parts module BuildChef include BuildChefGem def create_bundle_config(gemfile, without: without_groups, retries: nil, jobs: nil, frozen: nil) bundle_config = File.expand_path("../.bundle/config", gemfile) block "Put build config into #{bundle_config}: #{ { without: without, retries: retries, jobs: jobs, frozen: frozen } }" do # bundle config build.nokogiri #{nokogiri_build_config} messes up the line, # so we write it directly ourselves. new_bundle_config = "---\n" new_bundle_config << "BUNDLE_WITHOUT: #{Array(without).join(":")}\n" if without new_bundle_config << "BUNDLE_RETRY: #{retries}\n" if retries new_bundle_config << "BUNDLE_JOBS: #{jobs}\n" if jobs new_bundle_config << "BUNDLE_FROZEN: '1'\n" if frozen all_install_args.each do |gem_name, install_args| new_bundle_config << "BUNDLE_BUILD__#{gem_name.upcase}: #{install_args}\n" end create_file(bundle_config) { new_bundle_config } end end # # Get the (possibly platform-specific) path to the Gemfile. # def project_gemfile File.join(project_dir, "Gemfile") end # # Some gems we installed don't end up in the `gem list` due to the fact that # they have git sources (`gem 'chef', github: 'chef/chef'`) or paths (`gemspec` # or `gem 'chef-config', path: 'chef-config'`). To get them in there, we need # to go through these gems, run `rake install` from their top level, and # then delete the git cached versions. # # Once we finish with all this, we update the Gemfile that will end up in the # top-level install so that it doesn't have git or path references anymore. # def properly_reinstall_git_and_path_sourced_gems # Emit blank line to separate different tasks block { log.info(log_key) { "" } } project_env = env.dup.merge("BUNDLE_GEMFILE" => project_gemfile) # Reinstall git-sourced or path-sourced gems, and delete the originals block "Reinstall git-sourced gems properly" do # Grab info about the gem environment so we can make decisions gemdir = shellout!("#{gem_bin} environment gemdir", env: env).stdout.chomp gem_install_dir = File.join(gemdir, "gems") # bundle list --paths gets us the list of gem install paths. Get the ones # that are installed local (git and path sources like `gem :x, github: 'chef/x'` # or `gem :x, path: '.'` or `gemspec`). To do this, we just detect which ones # have properly-installed paths (in the `gems` directory that shows up when # you run `gem list`). locally_installed_gems = shellout!("#{bundle_bin} list --paths", env: project_env, cwd: project_dir). stdout.lines.select { |gem_path| !gem_path.start_with?(gem_install_dir) } # Install the gems for real using `rake install` in their directories locally_installed_gems.each do |gem_path| gem_path = gem_path.chomp # We use the already-installed bundle to rake install, because (hopefully) # just rake installing doesn't require anything special. # Emit blank line to separate different tasks log.info(log_key) { "" } log.info(log_key) { "Properly installing git or path sourced gem #{gem_path} using rake install" } shellout!("#{bundle_bin} exec #{rake_bin} install", env: project_env, cwd: gem_path) end end end def install_shared_gemfile # Emit blank line to separate different tasks block { log.info(log_key) { "" } } shared_gemfile = self.shared_gemfile project_env = env.dup.merge("BUNDLE_GEMFILE" => project_gemfile) # Show the config for good measure bundle "config", env: project_env # Make `Gemfile` point to these by removing path and git sources and pinning versions. block "Rewrite Gemfile using all properly-installed gems" do gem_pins = "" result = [] shellout!("#{bundle_bin} list", env: project_env).stdout.lines.map do |line| if line =~ /^\s*\*\s*(\S+)\s+\((\S+).*\)\s*$/ name, version = $1, $2 # rubocop is an exception, since different projects disagree next if GEMS_ALLOWED_TO_FLOAT.include?(name) gem_pins << "gem #{name.inspect}, #{version.inspect}, override: true\n" end end # Find the installed chef gem by looking for lib/chef.rb chef_gem = File.expand_path("../..", shellout!("#{gem_bin} which chef", env: project_env).stdout.chomp) # Figure out the path to gemfile_util from there gemfile_util = Pathname.new(File.join(chef_gem, "tasks", "gemfile_util")) gemfile_util = gemfile_util.relative_path_from(Pathname.new(shared_gemfile).dirname) create_file(shared_gemfile) { <<-EOM } # Meant to be included in component Gemfiles at the beginning with: # # instance_eval(IO.read("#{install_dir}/Gemfile"), "#{install_dir}/Gemfile") # # Override any existing gems with our own. require_relative "#{gemfile_util}" extend GemfileUtil #{gem_pins} EOM end shared_gemfile_env = env.dup.merge("BUNDLE_GEMFILE" => shared_gemfile) # Create a `Gemfile.lock` at the final location bundle "lock", env: shared_gemfile_env # Freeze the location's Gemfile.lock. create_bundle_config(shared_gemfile, frozen: true) # Clear the now-unnecessary git caches, cached gems, and git-checked-out gems block "Delete bundler git cache and git installs" do gemdir = shellout!("#{gem_bin} environment gemdir", env: env).stdout.chomp remove_file "#{gemdir}/cache" remove_file "#{gemdir}/bundler" end end end chef-12.14.60/omnibus/files/openssl-customization/000077500000000000000000000000001276456504500220375ustar00rootroot00000000000000chef-12.14.60/omnibus/files/openssl-customization/windows/000077500000000000000000000000001276456504500235315ustar00rootroot00000000000000chef-12.14.60/omnibus/files/openssl-customization/windows/ssl_env_hack.rb000066400000000000000000000023651276456504500265230ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # This script sets the SSL_CERT_FILE environment variable to the CA cert bundle # that ships with omnibus packages of Chef and Chef DK. If this environment # variable is already configured, this script is a no-op. # # This is required to make Chef tools use https URLs out of the box. unless ENV.key?("SSL_CERT_FILE") base_dirs = File.dirname(__FILE__).split(File::SEPARATOR) (base_dirs.length - 1).downto(0) do |i| candidate_ca_bundle = File.join(base_dirs[0..i] + [ "ssl/certs/cacert.pem" ]) if File.exist?(candidate_ca_bundle) ENV["SSL_CERT_FILE"] = candidate_ca_bundle break end end end chef-12.14.60/omnibus/omnibus.rb000066400000000000000000000036231276456504500163510ustar00rootroot00000000000000# # This file is used to configure the Omnibus projects in this repo. It contains # some minimal configuration examples for working with Omnibus. For a full list # of configurable options, please see the documentation for +omnibus/config.rb+. # # Build internally # ------------------------------ # By default, Omnibus uses system folders (like +/var+ and +/opt+) to build and # cache components. If you would to build everything internally, you can # uncomment the following options. This will prevent the need for root # permissions in most cases. # # Uncomment this line to change the default base directory to "local" # ------------------------------------------------------------------- # base_dir './local' # # Alternatively you can tune the individual values # ------------------------------------------------ # cache_dir './local/omnibus/cache' # git_cache_dir './local/omnibus/cache/git_cache' # source_dir './local/omnibus/src' # build_dir './local/omnibus/build' # package_dir './local/omnibus/pkg' # package_tmp './local/omnibus/pkg-tmp' # Windows architecture defaults - set to x86 unless otherwise specified. # ------------------------------ env_omnibus_windows_arch = (ENV["OMNIBUS_WINDOWS_ARCH"] || "").downcase env_omnibus_windows_arch = :x86 unless %w{x86 x64}.include?(env_omnibus_windows_arch) windows_arch env_omnibus_windows_arch # Disable git caching # ------------------------------ # use_git_caching false # Enable S3 asset caching # ------------------------------ use_s3_caching true s3_access_key ENV["AWS_ACCESS_KEY_ID"] s3_secret_key ENV["AWS_SECRET_ACCESS_KEY"] s3_bucket "opscode-omnibus-cache" build_retries 3 fetcher_retries 3 fetcher_read_timeout 120 # Load additional software # ------------------------------ # software_gems ['omnibus-software', 'my-company-software'] # local_software_dirs ['/path/to/local/software'] fatal_transitive_dependency_licensing_warnings true chef-12.14.60/omnibus/package-scripts/000077500000000000000000000000001276456504500174245ustar00rootroot00000000000000chef-12.14.60/omnibus/package-scripts/angrychef/000077500000000000000000000000001276456504500213725ustar00rootroot00000000000000chef-12.14.60/omnibus/package-scripts/angrychef/postinst000077500000000000000000000060711276456504500232070ustar00rootroot00000000000000#!/bin/sh # WARNING: REQUIRES /bin/sh # # - must run on /bin/sh on solaris 9 # - must run on /bin/sh on AIX 6.x # - if you think you are a bash wizard, you probably do not understand # this programming language. do not touch. # - if you are under 40, get peer review from your elders. # # Install a full Opscode Client # PROGNAME=`basename $0` INSTALLER_DIR=/opt/angrychef CONFIG_DIR=/etc/chef USAGE="usage: $0 [-v validation_key] ([-o organization] || [-u url])" error_exit() { echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2 exit 1 } is_darwin() { uname -v | grep "^Darwin" 2>&1 >/dev/null } is_smartos() { uname -v | grep "^joyent" 2>&1 >/dev/null } if is_smartos; then PREFIX="/opt/local" elif is_darwin; then PREFIX="/usr/local" mkdir -p "$PREFIX/bin" else PREFIX="/usr" fi validation_key= organization= chef_url= while getopts o:u:v: opt do case "$opt" in v) validation_key="${OPTARG}";; o) organization="${OPTARG}"; chef_url="https://api.opscode.com/organizations/${OPTARG}";; u) chef_url="${OPTARG}";; \?) # unknown flag echo >&2 ${USAGE} exit 1;; esac done shift `expr ${OPTIND} - 1` if [ "" != "$chef_url" ]; then mkdir -p ${CONFIG_DIR} || error_exit "Cannot create ${CONFIG_DIR}!" ( cat <<'EOP' log_level :info log_location STDOUT EOP ) > ${CONFIG_DIR}/client.rb if [ "" != "$chef_url" ]; then echo "chef_server_url '${chef_url}'" >> ${CONFIG_DIR}/client.rb fi if [ "" != "$organization" ]; then echo "validation_client_name '${organization}-validator'" >> ${CONFIG_DIR}/client.rb fi chmod 644 ${CONFIG_DIR}/client.rb fi if [ "" != "$validation_key" ]; then cp ${validation_key} ${CONFIG_DIR}/validation.pem || error_exit "Cannot copy the validation key!" chmod 600 ${CONFIG_DIR}/validation.pem fi # rm -f before ln -sf is required for solaris 9 rm -f $PREFIX/bin/chef-client rm -f $PREFIX/bin/chef-solo rm -f $PREFIX/bin/chef-apply rm -f $PREFIX/bin/chef-shell rm -f $PREFIX/bin/knife rm -f $PREFIX/bin/ohai ln -sf $INSTALLER_DIR/bin/chef-solo $PREFIX/bin || error_exit "Cannot link chef-solo to $PREFIX/bin" if [ -f "$INSTALLER_DIR/bin/chef-apply" ]; then ln -sf $INSTALLER_DIR/bin/chef-apply $PREFIX/bin || error_exit "Cannot link chef-apply to $PREFIX/bin" fi if [ -f "$INSTALLER_DIR/bin/chef-shell" ]; then ln -sf $INSTALLER_DIR/bin/chef-shell $PREFIX/bin || error_exit "Cannot link chef-shell to $PREFIX/bin" fi ln -sf $INSTALLER_DIR/bin/knife $PREFIX/bin || error_exit "Cannot link knife to $PREFIX/bin" ln -sf $INSTALLER_DIR/bin/ohai $PREFIX/bin || error_exit "Cannot link ohai to $PREFIX/bin" # We test for the presence of /usr/bin/chef-client to know if this script succeeds, so this # must appear as the last real action in the script ln -sf $INSTALLER_DIR/bin/chef-client $PREFIX/bin || error_exit "Cannot link chef-client to $PREFIX/bin" # Ensure all files/directories in $INSTALLER_DIR are owned by root. This # has been fixed on new installs but upgrades from old installs need to # be manually fixed. chown -Rh 0:0 $INSTALLER_DIR echo "Thank you for installing Chef!" exit 0 chef-12.14.60/omnibus/package-scripts/angrychef/postrm000077500000000000000000000020601276456504500226420ustar00rootroot00000000000000#!/bin/sh # WARNING: REQUIRES /bin/sh # # - must run on /bin/sh on solaris 9 # - must run on /bin/sh on AIX 6.x # - if you think you are a bash wizard, you probably do not understand # this programming language. do not touch. # - if you are under 40, get peer review from your elders. is_smartos() { uname -v | grep "^joyent" 2>&1 >/dev/null } is_darwin() { uname -v | grep "^Darwin" 2>&1 >/dev/null } if is_smartos; then PREFIX="/opt/local" elif is_darwin; then PREFIX="/usr/local" else PREFIX="/usr" fi cleanup_symlinks() { binaries="chef-client chef-solo chef-apply chef-shell knife ohai" for binary in $binaries; do rm -f $PREFIX/bin/$binary done } # Clean up binary symlinks if they exist # see: http://tickets.opscode.com/browse/CHEF-3022 if [ ! -f /etc/redhat-release -a ! -f /etc/fedora-release -a ! -f /etc/system-release -a ! -f /etc/SuSE-release ]; then # not a redhat-ish RPM-based system cleanup_symlinks elif [ "x$1" = "x0" ]; then # RPM-based system and we're deinstalling rather than upgrading cleanup_symlinks fi chef-12.14.60/omnibus/package-scripts/chef-fips/000077500000000000000000000000001276456504500212705ustar00rootroot00000000000000chef-12.14.60/omnibus/package-scripts/chef-fips/postinst000077500000000000000000000060711276456504500231050ustar00rootroot00000000000000#!/bin/sh # WARNING: REQUIRES /bin/sh # # - must run on /bin/sh on solaris 9 # - must run on /bin/sh on AIX 6.x # - if you think you are a bash wizard, you probably do not understand # this programming language. do not touch. # - if you are under 40, get peer review from your elders. # # Install a full Opscode Client # PROGNAME=`basename $0` INSTALLER_DIR=/opt/chef-fips CONFIG_DIR=/etc/chef USAGE="usage: $0 [-v validation_key] ([-o organization] || [-u url])" error_exit() { echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2 exit 1 } is_darwin() { uname -v | grep "^Darwin" 2>&1 >/dev/null } is_smartos() { uname -v | grep "^joyent" 2>&1 >/dev/null } if is_smartos; then PREFIX="/opt/local" elif is_darwin; then PREFIX="/usr/local" mkdir -p "$PREFIX/bin" else PREFIX="/usr" fi validation_key= organization= chef_url= while getopts o:u:v: opt do case "$opt" in v) validation_key="${OPTARG}";; o) organization="${OPTARG}"; chef_url="https://api.opscode.com/organizations/${OPTARG}";; u) chef_url="${OPTARG}";; \?) # unknown flag echo >&2 ${USAGE} exit 1;; esac done shift `expr ${OPTIND} - 1` if [ "" != "$chef_url" ]; then mkdir -p ${CONFIG_DIR} || error_exit "Cannot create ${CONFIG_DIR}!" ( cat <<'EOP' log_level :info log_location STDOUT EOP ) > ${CONFIG_DIR}/client.rb if [ "" != "$chef_url" ]; then echo "chef_server_url '${chef_url}'" >> ${CONFIG_DIR}/client.rb fi if [ "" != "$organization" ]; then echo "validation_client_name '${organization}-validator'" >> ${CONFIG_DIR}/client.rb fi chmod 644 ${CONFIG_DIR}/client.rb fi if [ "" != "$validation_key" ]; then cp ${validation_key} ${CONFIG_DIR}/validation.pem || error_exit "Cannot copy the validation key!" chmod 600 ${CONFIG_DIR}/validation.pem fi # rm -f before ln -sf is required for solaris 9 rm -f $PREFIX/bin/chef-client rm -f $PREFIX/bin/chef-solo rm -f $PREFIX/bin/chef-apply rm -f $PREFIX/bin/chef-shell rm -f $PREFIX/bin/knife rm -f $PREFIX/bin/ohai ln -sf $INSTALLER_DIR/bin/chef-solo $PREFIX/bin || error_exit "Cannot link chef-solo to $PREFIX/bin" if [ -f "$INSTALLER_DIR/bin/chef-apply" ]; then ln -sf $INSTALLER_DIR/bin/chef-apply $PREFIX/bin || error_exit "Cannot link chef-apply to $PREFIX/bin" fi if [ -f "$INSTALLER_DIR/bin/chef-shell" ]; then ln -sf $INSTALLER_DIR/bin/chef-shell $PREFIX/bin || error_exit "Cannot link chef-shell to $PREFIX/bin" fi ln -sf $INSTALLER_DIR/bin/knife $PREFIX/bin || error_exit "Cannot link knife to $PREFIX/bin" ln -sf $INSTALLER_DIR/bin/ohai $PREFIX/bin || error_exit "Cannot link ohai to $PREFIX/bin" # We test for the presence of /usr/bin/chef-client to know if this script succeeds, so this # must appear as the last real action in the script ln -sf $INSTALLER_DIR/bin/chef-client $PREFIX/bin || error_exit "Cannot link chef-client to $PREFIX/bin" # Ensure all files/directories in $INSTALLER_DIR are owned by root. This # has been fixed on new installs but upgrades from old installs need to # be manually fixed. chown -Rh 0:0 $INSTALLER_DIR echo "Thank you for installing Chef!" exit 0 chef-12.14.60/omnibus/package-scripts/chef-fips/postrm000077500000000000000000000020601276456504500225400ustar00rootroot00000000000000#!/bin/sh # WARNING: REQUIRES /bin/sh # # - must run on /bin/sh on solaris 9 # - must run on /bin/sh on AIX 6.x # - if you think you are a bash wizard, you probably do not understand # this programming language. do not touch. # - if you are under 40, get peer review from your elders. is_smartos() { uname -v | grep "^joyent" 2>&1 >/dev/null } is_darwin() { uname -v | grep "^Darwin" 2>&1 >/dev/null } if is_smartos; then PREFIX="/opt/local" elif is_darwin; then PREFIX="/usr/local" else PREFIX="/usr" fi cleanup_symlinks() { binaries="chef-client chef-solo chef-apply chef-shell knife ohai" for binary in $binaries; do rm -f $PREFIX/bin/$binary done } # Clean up binary symlinks if they exist # see: http://tickets.opscode.com/browse/CHEF-3022 if [ ! -f /etc/redhat-release -a ! -f /etc/fedora-release -a ! -f /etc/system-release -a ! -f /etc/SuSE-release ]; then # not a redhat-ish RPM-based system cleanup_symlinks elif [ "x$1" = "x0" ]; then # RPM-based system and we're deinstalling rather than upgrading cleanup_symlinks fi chef-12.14.60/omnibus/package-scripts/chef/000077500000000000000000000000001276456504500203315ustar00rootroot00000000000000chef-12.14.60/omnibus/package-scripts/chef/postinst000077500000000000000000000060641276456504500221500ustar00rootroot00000000000000#!/bin/sh # WARNING: REQUIRES /bin/sh # # - must run on /bin/sh on solaris 9 # - must run on /bin/sh on AIX 6.x # - if you think you are a bash wizard, you probably do not understand # this programming language. do not touch. # - if you are under 40, get peer review from your elders. # # Install a full Opscode Client # PROGNAME=`basename $0` INSTALLER_DIR=/opt/chef CONFIG_DIR=/etc/chef USAGE="usage: $0 [-v validation_key] ([-o organization] || [-u url])" error_exit() { echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2 exit 1 } is_darwin() { uname -v | grep "^Darwin" 2>&1 >/dev/null } is_smartos() { uname -v | grep "^joyent" 2>&1 >/dev/null } if is_smartos; then PREFIX="/opt/local" elif is_darwin; then PREFIX="/usr/local" mkdir -p "$PREFIX/bin" else PREFIX="/usr" fi validation_key= organization= chef_url= while getopts o:u:v: opt do case "$opt" in v) validation_key="${OPTARG}";; o) organization="${OPTARG}"; chef_url="https://api.opscode.com/organizations/${OPTARG}";; u) chef_url="${OPTARG}";; \?) # unknown flag echo >&2 ${USAGE} exit 1;; esac done shift `expr ${OPTIND} - 1` if [ "" != "$chef_url" ]; then mkdir -p ${CONFIG_DIR} || error_exit "Cannot create ${CONFIG_DIR}!" ( cat <<'EOP' log_level :info log_location STDOUT EOP ) > ${CONFIG_DIR}/client.rb if [ "" != "$chef_url" ]; then echo "chef_server_url '${chef_url}'" >> ${CONFIG_DIR}/client.rb fi if [ "" != "$organization" ]; then echo "validation_client_name '${organization}-validator'" >> ${CONFIG_DIR}/client.rb fi chmod 644 ${CONFIG_DIR}/client.rb fi if [ "" != "$validation_key" ]; then cp ${validation_key} ${CONFIG_DIR}/validation.pem || error_exit "Cannot copy the validation key!" chmod 600 ${CONFIG_DIR}/validation.pem fi # rm -f before ln -sf is required for solaris 9 rm -f $PREFIX/bin/chef-client rm -f $PREFIX/bin/chef-solo rm -f $PREFIX/bin/chef-apply rm -f $PREFIX/bin/chef-shell rm -f $PREFIX/bin/knife rm -f $PREFIX/bin/ohai ln -sf $INSTALLER_DIR/bin/chef-solo $PREFIX/bin || error_exit "Cannot link chef-solo to $PREFIX/bin" if [ -f "$INSTALLER_DIR/bin/chef-apply" ]; then ln -sf $INSTALLER_DIR/bin/chef-apply $PREFIX/bin || error_exit "Cannot link chef-apply to $PREFIX/bin" fi if [ -f "$INSTALLER_DIR/bin/chef-shell" ]; then ln -sf $INSTALLER_DIR/bin/chef-shell $PREFIX/bin || error_exit "Cannot link chef-shell to $PREFIX/bin" fi ln -sf $INSTALLER_DIR/bin/knife $PREFIX/bin || error_exit "Cannot link knife to $PREFIX/bin" ln -sf $INSTALLER_DIR/bin/ohai $PREFIX/bin || error_exit "Cannot link ohai to $PREFIX/bin" # We test for the presence of /usr/bin/chef-client to know if this script succeeds, so this # must appear as the last real action in the script ln -sf $INSTALLER_DIR/bin/chef-client $PREFIX/bin || error_exit "Cannot link chef-client to $PREFIX/bin" # Ensure all files/directories in $INSTALLER_DIR are owned by root. This # has been fixed on new installs but upgrades from old installs need to # be manually fixed. chown -Rh 0:0 $INSTALLER_DIR echo "Thank you for installing Chef!" exit 0 chef-12.14.60/omnibus/package-scripts/chef/postrm000077500000000000000000000020601276456504500216010ustar00rootroot00000000000000#!/bin/sh # WARNING: REQUIRES /bin/sh # # - must run on /bin/sh on solaris 9 # - must run on /bin/sh on AIX 6.x # - if you think you are a bash wizard, you probably do not understand # this programming language. do not touch. # - if you are under 40, get peer review from your elders. is_smartos() { uname -v | grep "^joyent" 2>&1 >/dev/null } is_darwin() { uname -v | grep "^Darwin" 2>&1 >/dev/null } if is_smartos; then PREFIX="/opt/local" elif is_darwin; then PREFIX="/usr/local" else PREFIX="/usr" fi cleanup_symlinks() { binaries="chef-client chef-solo chef-apply chef-shell knife ohai" for binary in $binaries; do rm -f $PREFIX/bin/$binary done } # Clean up binary symlinks if they exist # see: http://tickets.opscode.com/browse/CHEF-3022 if [ ! -f /etc/redhat-release -a ! -f /etc/fedora-release -a ! -f /etc/system-release -a ! -f /etc/SuSE-release ]; then # not a redhat-ish RPM-based system cleanup_symlinks elif [ "x$1" = "x0" ]; then # RPM-based system and we're deinstalling rather than upgrading cleanup_symlinks fi chef-12.14.60/omnibus/resources/000077500000000000000000000000001276456504500163565ustar00rootroot00000000000000chef-12.14.60/omnibus/resources/chef/000077500000000000000000000000001276456504500172635ustar00rootroot00000000000000chef-12.14.60/omnibus/resources/chef/dmg/000077500000000000000000000000001276456504500200325ustar00rootroot00000000000000chef-12.14.60/omnibus/resources/chef/dmg/background.png000066400000000000000000001260421276456504500226640ustar00rootroot00000000000000‰PNG  IHDRŠôº´   pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-Û cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅF¡MIDATxÚìÝwœ\UÝÇñÏ9÷N۾ɦ÷II ¡÷Ž REEñQAAbT *Š4A¤JdôÞÛfûîììÎÜ{Ïóǹ!6!eïììæ÷~e^;»ÙsÛÌ|çTeŒA!„BˆÓr„B!„E!„B!AQ!„BHPB!„…B!„E!„B!AQ!„BHPB!„…B!„E!„B!AQ!„BHPB!„…B!„ („B!$( !„B ŠB!„B‚¢B!„ („B!$( !„B ŠB!„B‚¢B!„ („B!$( !„B ŠB!„b‡äòÆ5ÿb„œ!QÈŠ€2  ô*ï½  <ü½" þ] à|ì±ZPáý,P4µ@MøuíF·ÆðfäTì˜J/Y,A±ãE! €ƒà`$0 …¡°¨ƒàµ¶08®o €ùÀÂðëZ UNŸB ŠBt^(L&»;…±¢À¶5ÕAü_& óÀÛÀìðûŒœf!„…øt°3°°/0[cXÖÍ÷+†Û‘ÀQáÏrÀ `ððz"ëä2B!AQk°p°w ;À~ÇÂ}œþl-ðð2ð<0h—KD!„E±£p°MÈGGbk Kå°¶åqá `ð"ðð*P-‡H!$( ÑÓ$°µ†Ÿ Ãáìhb±y;…·³±µ/Ï…ß !„ (D·588> ‡bÛõN oÕÀ3Àýahl’Ã#„…è„Áð 쀙D¾óõNoK€G€{°ƒb„Bô`ò¦*º«ý;°£xo¿—ë9zà °£¦_¾ô’Ã"„…èjeÀWÀò2ð  –.³p'ðp#0^‰BHP"ß?ÞþQ8†—†ççŸÀrH„B‚¢Qüx¸.ü^®ðìhé§c‘ÑæB!AQˆâ-ت‹±k-‹îåpàQà%à$y­B ŠBl¯‘a@|¸î¿”ž°ƒŒÄ®üò9B!AQˆ­Õ¸x' ˆårHzœÇÃÛþr8„B‚¢Ÿ&|ÛÄ|P)‡¤Çû ¶ã_€åp!„E!:r v=áß`G5‹ëuçLàMàj B‰BHP`4vUÇ€©r8vhåÀ•a`ü‚!„ (v\1à’0œ&‡Cld'ìü‹"ÍÑB!AQìpö^~Ž,÷&6í$ìúÑ#ëÐ !„EÑ㥰e¿†ÅZL·V +‡ˆ ì$ëÏSäp!D×’Oí"*{¿¥ç÷CôG™Z­ÌjcXáj³ÖQ¦C5ŠzchÊÝxa T ƒVÐî;"] Hbûï•c'ï~ ïWÅ=ø¸î¬û*à& §”BHPݟ®û{UxzÒŽ­qT°P+fÅT0ÏÀ,ߨåZ±¦º=YÓê»&¦¦ËXÕV„£Ð8¼ÛP…oÔGëÙ@Òñ9¶ßröëµ¥ =Ð[ºÞ]1Ѐ4~؃d¶»Â‘Øi”æÉÓK!òüÞgŒ)ØkþÅ9CÝËHà6ਠ›´2sã:xGaÞfd|w~}.ÞÐìŘÛRNÚ‹ñ^co4†%­¥4æâ¸* ÕwÝöÀ)“U®0¥Ž6Å JŒ ÏFŒjTÊ<3±¬žïŽžÎˆ¢fÒ¾»=‹#Ç!Àx``w`·0¦3£¼BHP=Ô`àt¯¦æµ1<×ÁÃ^ ^X™)®~»¡³›Ë™Ù\ɪ¶"£*Ì”¸ö‰é`Ï8Á _”¤ÈñX•)â3§ò‹ o1 ÙJ{àDQ\ ðdx‹c›¨?Û Bã]ØÅ.QâxLo®äÑÕCqtPQñRy BHPêìz½Ý¡@[LÏÅup¯gÔ“+ÛŠ«ßm¨âõº¾Ìjª Á‹§LŽëà°¤ö&“çùmXôYž)áöÅã¸zÜ{¸Êà™HãH»Œâ«À°ÍÓ§„ÁqhÃÀ]UxRûÌh®äÊÙSX•IáêÂhù– („ ( ÑYÀ­ØQ©KÁܤãÿSaî[–)™õnCoÔõeVsõ¹x¹ÆìŸÐÁçŠï`LWo¯JÜ/Öôç§ó&òý>Ä…¨ÃâÆ¡ñÅðö#àhà+Øæéxš4vmèæ.¸†(‰åxµ¶?ž³;í¾¦<–“W!„E!6á:à‡¼}~LÿéàOíóø«µ}[_©ëÏ+5ýhðâ)90¡ƒ“‹ï( ¯Ö €b×ãñµC°hðL^çÆoÀ.©÷O`ðUà‹DØüþ)¾ ¼ß!±Èñ¸gù(þ²l4Y_×2£B‚¢)î¾T ÛךÐþ?•âŽÕm©·^¨À‹ë0?]FÎè )í±ØñNÁ®+\ðJÜœ ‹ ¾7úCeðMÞzÅ ÀÖÞµÓ€‹€ðx6vú|ù;ðû®‰ÅnŽ»—æöÅãˆë€˜„D!„E!:Ô[»thn[cBûr•ùÝÜ–òy®Ϋµýhôâ G™câÚÿZœàì45ÝJ‰›ã±5Cp•áÒ1ÓÈøn¾æ„¹ Žíƒú` °¸% m_Î#ú%öfc'ÚÎHtrü= ‰Ií£•ÌÆ#„ (DGFbx’†„öÿì(së¼–òEÿZ5œçköÝþIí¹Èñ¾J~k¾" ‹¯Ì®eu×Í^,ê"wÆöK¼ø>ð@ßÂ.Aøgà¯Ø/—b'õîlmØ>’ ù<ÞŽ2¤›LàÁÕ#$$ !$(Ê!›±+ð¶v©PdÂě綔/|pÕp^°qTJûgۀد§œÄtÀ/æïŠV†cû-:,žÁÿ^)þøð8vÓ“Ø5®ïþ…íŽp)°s'nÇÅÀÛù<Öqö]n^0§ª“P…B‚¢Ø”}ÂP0+­¸*x ¦ƒŸÌo)Ÿö¯0 ¶øîÈ”öÏ/v¼³ÂPÓã8Ê`PülÞDpL¿eäÏ(üpK'Å™¶YyS™õØðö,ðKà ‡­a|øvàÉöõ`WùÉkHl󮳯Ôö¥,–“)µ…B‚¢Ø„}cWñèr óVÒñ¯jÌÅŸüçÒQ<¸j8™ÀšÒþE=9 ~,$ã¡ùùü]Éø£Š›)eé—È 1$ß(<£Éz[ƒã‘Ø®Ÿæ°ðö,vµ—§°ƒ_~Ü\†ô²-¯/s€s»"$^5gwÞ¨ï+Óß!„EÑMBbmBû×Ô­Ï®”ýý’YÝVT‘´5ˆJÍgX Œâ–…P*ãY*cíôMd]ÜLE¬]Ëê’J£Ø¦ZƯnå﯌×c›Š—‡AïïØ>ŽmÅãµa›¸óq< v"í¶À CbJ ‰B!AQ|Ht”ùgBûW,H—-¼mÑxÞkì­4æÌ"Çû!ÝdŠ›(heH(€¦\Œúlœ-e¼T3­ 1pÅØ8¤j«žÞÃ#¶q³N>ƒ]ÎñF`vµ—ÃÂÐxå^S—¯ç+$–8+ÚŠ¹qÞ$ÞoìE‰ãÉ+€B|ü}G(°¸¨ÈñNÍÎo]4~á·§íÃ; ½÷Œkÿ¿1ܵ#‡ÄÂ4qt|JÜEŽGܹd'r œ­ˆqP¼›“ÀN™ó6vJ;àå7ÀÞØþ®›s/vú¼(v<æµ”sýÜI¼ÛЛb ‰B!AQlÒnØÑÍ]eþVâæöy»¡ÏýNß›{VŽªôŒþEÊñ_Vp¸œ¦-Hk:`Yk /Öô'éø[úg1:o"õþa8|; `!p2vôtu3/ ™ù ‰®Ç´¦^|wÆ^|ØÔ‹2Wš›…B‚¢Ø”‡€Þ]¸ Õ íŸ‘ ô™¿Y¸KõfNeQºô³%Nî G™‹éúµ†»W^©íO.P[:rwìü‰éàlStiø³?ûa§ÙY¯Û7²6ǦÔÍñAC/~4k Í^Œ"ÇC&ÀB Š¢c±M‚új<]äxû.n-ýÇùîÃ=+FõÒp{BûÿAš™·IÒñy«¾ŠÇ× ¥hËšT¿Ѧıó+¾‚Ä`vŠË€4pyè—¨0;/Ô àÊÙShòb$´/‹BHP›P†]q¥«V/ e®M¹ÞgY3táùîÃ’téaÅnîu­Ì7åôlçÁ5ŠéM•lA•bàsoÎDàiàZ6,¥x#°pS>Œ €çkpíÜÉ4{1²n³BHP›ä`—`Û»‹Ê_›Ðþ)¾QW^?w²Ó‚ n6p®I8þSH-b§(v=ž]7Wjû}Z_ÅŠ¼ß x@°8Ê HiŸ7êú0­±cKÉŸø\¸GxË·*ì„Ü{`û0fóeI>!„ (6ï3À/ºäBSÁO´âG÷®É­‹ÆãªàÅŽw­éþ×àZ`vé¹À\`%v˜º0,vI[§V†Úl’ÇÖ aRy-íAüãaéKtm«Âw°ý¿Š]ÑE!„EÑEÆé‚`KjÿÛmóûëæLæÅšE)Çû­Væ«Ý°0·7€YÀ|líà¶<÷6>‘ ÁM9>Óšz±´µ”~‰ 9óÑé¯ÀÎmØÕ^¾Œ-„B‚¢È³bà@Ÿ<—Û”rü/-k-þÏ­‹ÇóZm¿A%nîïlÝú¿]mndžÞ–°ée”“@_`0v4qìD}^Ø>‰±ðkb£Ç1Øõ}lót °[[¹"üºX…­¡ÜâŒS‹ZËxhõ0¾3j&9x0 @Žñpà àÀ=òtB Š"¿~ LÍs™k‹ïä™Í¯^>k*ë²ÉÝJÜؚܽÍB7 xx xhëàwza'+ìîר0F1H¥= “K±MÜ3Ãm›†×›· P¤=Þ®ïCC.N\øFœY`Ǽ$ü03ø™†®ŽŽÆNÝ·÷¡; d"ð5líçÌpžÞZâ*à©êAL(«gÿÞkë¼@ý¦=Ð'ÔÀܧ‹°#£ÿ<̳(„B‚⎨ø#y h –»Þ1[6zÎKv&¦Í—Ú¿‹Â›±;=ËÃPµ±QÀg±=¦©nvÞ“À”ðö}lmãSqü«>›xùÊÙ»·ÚgõÒcú-¿`§’Æ«‹ïäöÀ9Û7jjíÇ—±µ¨ga§B!AQt¢Ÿb›JóeqÒñ¹{ù¨9¿[2–¤öÏÐÊZH\üø3°æc¡úhìt1‡ñDØy6ø–oÅt0Ó îbíàûž®8{licí)ÿ~rEퟫbíŸmô…žÑ…4ÐèT ø‰{÷cçCüP&— {º*øK‰›{»ÁKœý‡¥;§Î›¶ÿ]7è_J±OÒñÏÃÎáXaño@|[þØ3…B‚¢8; /a,©ý¯Ílª|úòYSiÈ%>“ÔþŸ `I¾×€C°#f…?›†ŒWS.ÙÅUÁïKÜÜÛµÙä—®3Y_øá>Ùwê«n+qs{ÆTpŸœ2(ß>ÜÁVVj o7THXB Š;°*à×ù*,¦‚K[}÷žçM¤6›Ø3©ýе#„›€K°«¾¼þ¬¶ÉùMº~]ãnc:ø[Êñ_šÕ\~Ä峦pÍœÝVÕd“ç;ÞaÊN·Ó•¾Ê6¬Wž äÔ !„Å۵إã"ç(sG{àüüºy“YÚZ2*åø»vpWy; ö/±µ^;˜çìœ|I¹<¶Ú~)Çÿ/Jýõ‰µCFžÿá>,N9Þ—ï[1²ý?k†:enî.ìâ®0ÛñgØuŽãØÌ/ËeÑ©ªb:¸%åxÿ}ªzЄó?܇'×~-®ýý]ü®‹¶IaG®ï¹%¿¨•Ùž²ú¿ž ¯÷/†N„B‚¢(xßÄNÿ)mEŽ÷¥ÙÍ«~·x,J™ëæs]´ÏÁÖî¼~?9|¿–î·’JwrX‰ë½Z—M\píÜÉÜ´`BKÖ8ߊkÿ,]°=eÀ?›|S†F/Æœæ \lKg`û¸^Àÿ‚úÛ1]BHPù0[‹¹¤ö¿¿‡Å vðÌ_ÛÁy§àÄ"Ç{eaºìð‹§ïÅ3ÕƒÞ)u³keë‚Í9¸nSÿé*ƒ«·¸61í÷ú&p§üîel¦6S!$(Š®4 ;itÔ;Þyÿ\9’çkú—–¸Þ_MþƒÙ àPàÉðû‰ÀóäoÎHѱ‘EŽ÷Xu6uÑuó&sïÊQ5 s¼«Ì¯»`[¾]æïc!1`Ik õÙÄ–Ô(‚­¾»Æô§©"O5úB!AQl­kØÆU*¶‚Wäz_¾f@Ý_—¡4æÝ†´|z[c´¾YósØQÍ“åèzâ íߤ1würÁ®ñŸÏŸè+Ì…qü  6çw|¬™ØQ†šl’V?¶¹»*à7áuµçV–ùu`¹„E!98.êB\en¬mO¾pÇâ±Ì—4ækyÞχÃý\~¶ßX•\öB¢Ì7ÊÜ죯Òï†ù“hÈÅšÔþ×M~Ws©ÂŽ„v>›¬M<;‰ø·Ù¶éãÀUr!$(ŠB:gWÒ©sw轸ö¯ûÓÒXÚZ22¡ƒ[ò¼ŸNZÂïoÀNQ"×lá:¢ÔÍ=ýtõ Ñ|¸K3%,r¼/hÏó‡¨KÿçBnèÝQL~ù;VÙ'ûÊéBHP…࢟'0[ìxç¾XÓ¿íñµƒuÊõ~ ôÎsH< ;ÊÙ~ü Ã6 ý±[ÐCöm×"Ç{fE¦hòe³ö`IkéÅŽwFžÃâ•ÀÂOS-^lãÿK„×ÒëÀñTž\./MBˆžÈ•CÐí‚ýeQÓÁÏk²É7¿d,À·t~'þ ðapr»°sÙu&0j¥µ èeÀR`µÂÔm s¥eHh ;dvê!À0`(0èw“c0¬ÈñŸ\•IzÙÌ=^ºa—·QÔLÚwÿ®ò3÷`¸]+³m6‘×RN\[ÛxvõžÎö`ìÊ-B!AQt‰Ïq—‚91Üø§¥;±$S:²ÔÉýÄäoÿ C¢ßBb60jVÎè·|£Þ Œú°,–]˜Ò~CÊñ̸Ò<£le—Òz|c+ñµ2d|‡ÇÖáý†Þ¸Ê×Á¦zÒ%°ËŽÃ¶Ø»BHŸB=(ú9þëÚRÇ_6k—oÿöƒ#ŠšUÚwÿ©ò³ŠÐÀ¥Z™kÃá(žŠö©ÃeÀ±ò2%„èI”1¦`7®ù#ä ý¯£, åxǼTÓÿ‰«æìN\›çqõ•ÿbç­ËzH4°Î ô«Y£ŸÐ˜—ËÜܼáÅ-þNÅì\ÚÀT+ý†^ñv[Xe 2¼_ h µí~õÍú¾üfÑxÖ¶¥H9>[øŒ¬ Ãâg€Ã ù ­¾{ìÀdúµvy‡¡©–¯·Îy*>Sìx{=°jøô_.ص¼ÔͽˆV*ÂKƒ}°s0æÅ3÷߉BHP`û%>G„ƒX´2÷Ãißš¶KZKOMhÿÞ<íÛ{aØ©¿ÿð¥;þ™\ ŸÍúÞ˜ž’J¯Ý«rcKع¤‘ªD)íWØÉ3zߨIÀHߨØš¿b ècçï]SKÜsš+øá¬©T·'·&,®ÇÖ ‚íwWPOkZ}÷¨!©–o›ôE®÷ƒ\ oÈGÙZ™ç~Ù¬©æúªCSŽÿ Ñ»;K‚¢B‚¢żz„h§Äi*v¼Ýþ°t§Ew-Û©¢ÈñÞ†ça¿–…!xqøý¯°ÓàKÚ|÷n÷Œ,nž5µb{WV3¶´‘²X®ØvËú`ßè]Ö­ÿV<üËÀ(v<æ·”sùì)Ôd“ÄÔ6m)ÅÖ2þvþIU ‡ri«ïzdß•‹.3 P·yF}+9þWßi¨úË÷gî«Ìßæô‹Ëbkz󲜡E!DÔ¤b÷09|óL\·¬l+ZôðêáÄuðý<…Ä4pÚF!ñ²B ‰ÞËøîíqíßwPÕê¦ý{¯eß^k)ssÅ>êÀl OJ{îatBíZ}—K¸dÌt.™±'jk×&þèóp_xÛ8[ÓïâC:¬Èñ|ríàæöû;}x¡6Œ PGE]p«ïüdÊuœÐiý}«F\YìxÇÝêBqìÐçÊË–¢'éqº‡s"õK5æ¦{Wޤ>ßÙUA¾ÂÚ·°Ó”€íx}Ä[}÷˾Qû\µú7íúVÓÕãÞãè¾+&ÄtpCÚwßoóÇ£¾N'7ñ¶x1ö¨¨á‚‘³Èßlueàä0&Ãï_ ë~À?°ƒ„ºôO±›ûÛk‡Ä[3$Wìz_æå¡ÜÁ9__vâÀ%”»Ù…¾QQ/1x²´B‚¢È“áOdRÚ¿þýÆÞÿ^5”"í_K~¦aù%¶/"Ø¿ïâã¼¢ÕwÏ÷ÚçàªÕwß4á­ö«Ç½Ç®¥u‡·ùÎiß}'0êÀ˜(7¢Íw8uÐ"Î5 /Ð[×r|)¶Ü;Àwذ‚Í;a`<xº+²‚Ï9ÞÍ¿Y¸ Ï­¸®ÔÍa6Lª™¬Ñç I¥w>}ÈB²¾ Xaq•^[!„ ØCTDøø3øÛ«†ã£PÊœ’‡}z‘ óAÀÖvuÑñ5žQ·¥}wz¯¹õ¦ o¶^3î=v-«ûl›ï<ß8O'“Ÿùÿ0@Ú‹ñ…A‹øÆˆ9´z[\‘ÜŸ ÝvnÆúáFñUàÈðšZØe/:Êœ ÎýÝ’±,É”¾“ÐÁEy(¶¨=p~t€¥Œ,n®Ëúç—÷µ|]3B!AqÇîP™¤ã_÷AcïÌu}UÊñLôƒÖ_eê+"?ý!;òa«ïQìzç}oôô5׌{—‰åõd|牶Àùѯ€³é°è»œ

ò†ã%nîo¯ÕöëýÇ%;QäxßæD\¬Óæ;?>¬Ï*Æ”4¥³óˈËûº¼„ !$(Š(µoEõàIÇûéû ½soÕõQ)ÇûaöçÏÀú¹'ËvŽWe|焸.¾dôô柌{—ÉÌ×Ò~ìMƒúz!='e˜ÞÔ‹´ï~Úè=©[ð“G±“™ öì`—‡º`G9Þ­¬Á» U)Çÿ¸ñ:ºÔÍíÂÀ¥äõW`Q„ÅAÍi)„{–'±M„·Ó©ðÍ,ƒú׫†“3êXñÒ€ØõŽ¿Þ¯î ?K¹mìÅÆ\ì€q¥ÿþé.osÒÀ%C³~ è?ƒ íä`m{êÓjÎdëº |xÛ÷’ðº: ¸»Ævþ^€”9-è³n[4ŽVÏ}ÙQææ¨‹ÌέZÅè’¦t6п°¬DB ŠÂ¯`çe›†‚g°}ü¶ýL¿z¯¡wöͺ¾$ÿ»yØ Úðþ/€‘ù<ˆ¾QÈøîÑ' \ºè†ño³kYÝçš½Ø+fCX*8>ŠM•8z³µ‰½€·áá‡`›¤SáÏ®ÃN¯SŸÏýL:þ/f5W ¿{Å(RŽw50#Êò£Ž*ssSO²}ÿ ¬Ž°¸/"óÕ !$(Š<©ÅN)s¶¹ñçlCÓ™‚âŸÿZ5‚œÑ¨èmÜ ü;¼¶F+orF_©à쳆Îk»l§i”¸¹kZ}÷á0,$Gê³ j²IœÍ7;ÇÖ­óqçϲaÚŸ‡°ƒ0çqw{¥ÿ׬¡Þm¨jI9þwÂHQq3sñ¡}W1º¤¹>kÃbT&„ÏU!„ (òêàûÀnصeÅöiüôwIüyU¦¨izS% í_H´#ëÂí;¿ÜMyÖßÌQæ‘Æ\|É 5Hèàël˜[/ 3ÛÂû»“¿õo³ß9sryÝ=¿šø»WÔîÒâÅžöê6OLehÊÅÉm~ Ë—°smvZxÂŽL_ßguUøäý<íö€bÇ»êîå£Y.[×Á‘~’0úŒáEÍ•S+kh œ;",j8¶ÖV!$(Š‚þ‰ÉØåÜÞ‚„öÿøJmÖ¶§*\œñvüÈ`›ß~ÖÉ¡fSL&pÏšJÿóÚñï2º¸y—VßyØ©»œ<dͼtùæšK€/D´ ¿®Þ(,†þÈ9Ê|£6›˜ú¯•Ééàv¢] º¯gô©Gö]IB¯™hkO?'/KB Š¢-~ì¥`¯l _~®¦?`Î`ûA|š—øß,‡åcg³¾¸ÔÉÞõÝÑ3(ssëCâÐîvÒ<£XÝV´¹ù'Úm®~Þ_žÃeyØu·ÈñòlÍ@¥K[ÚÿqÄ×Ë×v-«ÓCS-~.ÐwEXÔÑD[{/„Åv ’Ž÷Ϋuý³o×÷!åø‹±Óìx•÷ìœ|I6ÔNE®ôM¥nîækǽÇÔŠu#3¾óXw ‰1í³"SÌÊL1½Éù§ÏÊæ\…ÖlŸÅ/`ûÄFû¢¤ÌQ­¾{ìï%0ê~¥Ld5}Q{;ÞÇô[NÎèE¸;ak÷…B‚¢(L h;à÷qþwšÎœåyàéðþiÀ®Qï›G3¾ó½óFÎf^Õ}ZüØCÀ°îú¤¬Ë&hóM Í…­Q̇[€ÓÃûo§eé’Ú¿âÕº¾Î› }½T´}UÖè/ïQ¹Žr7·2@ý7Â²Ž–W!!„EQàaÑ|<|¼¾f¶Æèß@z;‹¹~ýû=¦Æ‰ÒÜ6ß=ë¬aóƒÃû¬L´äâÇNNÞ-Å´aFS%Ù@o*(~üŽ¢ýp`xÿ~ìÄÜQÛ[)>ûÀÊádý‚w£*È ôñƒS­¥ãËêÉúŸîÓò $„ (º«fà>l_´Ý°ÓìlË ¯b›´ÁÖ&Ž‹x»3™Àýêî55g ‡gôM¦›¿!gÍÊM÷OŒgäy“RÀ?ØÐ'ò*6ÔG&¡ý¼ßØÛ™ÖØËK:Þ-Q•c`°«‚Ã?Ûo9žVFTÔ$ºi-·B‚¢ÛxšcÂи…{ËFæâ<„ªö‰·½qÙNÓ¾æun7:Ο®0dÅ¢Ö²M­Èr0¾ ¶uð øÀÿk¢,PÁÞ¾QG¾XÛy ¼.#ÑîëS&”ÕÓ/™iòLdÍÏÅtÞ¼—B!AQt¹,ð¶kv~½·6óûsÿ„÷ÅN™À¨'e~uñèéôMd&fýënpLÛÂнXþ‰ä¨ÖeSÔ´or龯vá¶\Þ_\u)íçÙuY.o‹ëàΨÊñч÷Ž·õš\^GÖwŽp—’—!„EÑ-nöÀN¤½êc¿ó =¼~ÄÛSßê;çŸ4p‰9 ÷šdÆwÿˆ­±)$ë°ë(ÿørxÜ&`£ŒÎüø¸ÚðÄÚÁ4æâÅaïJß>Þ¿[ÛÝ ”2‡5åbSY3”˜î"*ª/pð¾½Ö¢•y%dPªµd|YY_?Q1 `yëBHP쌪ôЛ!¡}’ÚïI¡qýª•À)–cZ}÷Ò£û®ÈîU¹ÎiõÜ›€xî÷ÓØuµîaËýtÈθÉÕXÞÎCù/–.Üïoû†÷ÿ@„Ó×½]eNz®fmóª‚#¹°`X\“w-«Ã3êE6t£èl²î³¢Ûp yã¾;cïyÐ}£¨ˆµsâÀ¥L)¯A+LÏØ½c€~Q=x`ÔC¥nîùÓ/Da¾bl¿¿®0»–õ½úÉMmÑU°¸[›ûcàó]ôÚñsì—v:¥'£*,®ƒ/Mkìýû…éRo§’¦û³ŽdžLϨC'”6¼’tüÅ5Ca¦DPÌT„B‚âö›ÑTÙc¼ooÔõeÏÊuœ2h »÷œÀøÅ»½Õw®:±ß F7—§mßÄ®ðÛ0$vuð,àÔð˜ÿ;…M>í‹mnÿð¶v5’9,µ2{·xëûÌÚ¥¬á¡l ¯¤ƒé…¶WÖwݹ´áša©´¿8SòJ\EÇ„¦ÖÊ[B‚âvHlzÛÁ¯×õã­ú>60\Âî•5hºm`D„ƒX|£þYËÍ8qÀrF}› @çK-¶ÿávÜï^â9*Ïe_ pvðN=*ܘ6Ÿ«¾ïÕ§ ^4KÁ{öŠà99¹ÈñŒ.iZ=?]úrÜùh­ëÎÔ+ ‹…OF=w!¤˜x½®ߟ¹?œ¹ï4T×>Åáÿmasd!8(‰è±½6ß½ùЪUŒ.iê“ œ ó¼o3à 0$®·8øMžË݉ kA?<UAqívAºT/N—š˜‰¨˜r3itq#¾Qo­•3Y^…Å6ƾ|Æ\1{*Ï®ÈòÖbr&©}JÜIÇ'®ƒŽÖl.'DõÀQ•ƲÓN¸„\àüÐ'ûõ*v°Ê´¿”rØI°/Ïs¹ßÃ.óVæ‰ây2©ÅsǽÕЇ˜ž„h*ÝsFí;¢¨™”V£æE´;“ä•OѸr /0àµÚ¾¼\Û^±vJc9Æ–4Ò'‘aRY½âí I¥‰ë€¸öñŒ&0Š\`sÕ?öaÃ(ØN×8·Ðo9£‹›KÒ¾ûÍ<‡Äã±ÍÎÝÅõØÑ¿ÊSy;'Çh™ìA9±˜Žx»¾jæ-š‰P´Sgâ½×°¢z'ÚýÚöÄ®2“#Ø—±òŠ'„ (¶90&Û?3ã»´ø1–µ–`€{”¡ÄÉQogdq3“­L(«£wc:ÀÀG¡1öeÃ$ÌmšCðÜAUkðm –§}z[KZÛ /£_¥ÀOòT޹ؾ’v4öÍ‘¼`)sÔʶâ[Öf“í}ã™<£;=(úF¯Š·—Nµ4¯mK¾îF³lâ(  ;ñºBHPÛF+ƒÆs‚~– –gŠYÜZ‚o4qí“Ð>“§Ò|}ø\ú&2F}"€zFÓ89^goêaQƒ¶À¹{Ry­·kY½j÷ݯçéЯNf+'Í.0×a}+eíƒHúMìÒ~W‡A¨³ŸS벉ª¥­%5éç=£¿Á¾ PÊŒQÔüÁÛõU3°•ôÝÓ£ ‚íû*„…›CätÏðÓ)Çö[Œë€ÍâÖžªÌKÆv8bjFÊÃû÷EUˆ«Ìþ³š+Èøî ¥ÌüHšQ“Ç7Qææj£F´+cä•K!AQtAhÜôϳ Jë¹`äLö¬XG›¿ÝÙk7 Év ÿµSI#ÊZ’9£NÌÃá{øY»$cG&Gm æq|žˆúvº:8`y¦˜uíIÏQ潈> L(e‰« 0°(¢ãµ³¼Z ! ôQÜiߥ<–å’138wÚ¾´x.qlëCîѦf|ÔÓûTV“ÐÁî9Ϻ¦ÛTôÀÓ~v`Îa—s¶6±xèôpï(3°¶=Y¶"SÜ4 ÕúŠÍÚâ}£>ðviÊÎÖ[^„EÁÊø.ƒSi®û>WÌš‚oζMîÅ2gF}PË-Þ£²†v_“‡Còû0ôT§Q¯‹¹qçØsˆ¦&ÓheZÉèÿ<Åå·Ñ†[°#¹;[V^…„E‡E‡½+«ùâà…üaéÎoýhèb"êkåõˆdÚ J¥•gÔ¡ŠZìt=Y–ü.—!šÁ&ùÞ§|ì‡B$é£(hñbœ5t>' X²-ƒ[†·N— ôKãKH9Á ƒšñaø3°Z®!„B‚¢ØˆÁrùê°ùL*«#íoUEóH Áf5;ÊÌWÚ€1flÍeTZßÉ• „BHPÈM¹›åºñï0¡´~kFBG2r3€ùÅ®·jLI9£öˆx÷ÿ ,”«@!„ (: €öÀ¡w¼‹FÏ éøäÌ]#¢Øž\àÌšJ“i¼ÀÙ=âÝÿ›\B!„Eñ)Z}—KùñØ÷ˆ©`KVoѦ|×>Šˆvbâjà99óB!„E±…aq¿Þk¹jìû$ßlv™Û¨²,ؽ¢–„ã0v½â¨¼B4K´ !„EÏÔâÅ8 j5_:Ÿ´çnbQ@@ŸŠ7À’¸]¯zHXNTž—³-„BHP[sqN¸„3†,$íÅ: ‹eD0Ù šŠ]oͤò:rá.à9ÓB!„E± )Ê3ŠóFÎæ´Á‹: ‹¥@IE×jLkJ{á.Ö £…B ŠbÛFÑh¾=r§ þDÍb HFPl½Ñ4m¯·: ‹B!„è€,á'¶*,Ü¿r$)»Ô_Q׿„LL ôŽp×–Ùâ„BÑ©Q[Î9‹ƒªÖ¬Ÿ[EQV6Ðí#‹›”Jã:ÊYÖÈ™B!6MjÅV…E€£û-ç…šþMÿD€µ!…–G¸K ]ú)M™ÿIÚZÙÊMߨO›’¨ ©ö%êãç„·Îæ(ƒV† ›Ÿ !„ Ø}©ðø›N~LÖ¿ßak;­Œ¶ÀabY7¾´y-e%qDqlry:M]uòã: ã;äŒþŸ°h€bÇ£È6íwKE6ˆ¾±¢ÝwhõÝ­YnrË_U@›ïrVcÚ™b:অˆéÁYôÑT¸Y~6ám’Ú'@j…E~õÆEð¸Ó7º¿SeFõ÷Z dópœ¢|‡.Ê÷I©€´ïò³ùYœ.%áøíb›ï0²¸‰~‰ íA÷ Š íS›Mæå¢hÈʼn*ŠzFc TÅÛ)rrÒü,„ (òn]D›Ýè--ª ·¾ˆºˆ¿b}1 %ÂsP‘רíÚÙ×̲͛L Åîúæe[%æ*… CŠgºç83ǘ¼…*Ûl"}|Ï(<£%( !vX2ê¹ëDÒ -¦ƒÄÜt5Ù$Ž2Q½‹ª}íì7èdu{Šúl­L”A±_>N´ÁöIÌškçìÆË5ýIj_žB! žÔ(ö°®0%i/F6Ð(hŒhÛ˯lÿÇNm'u•)]Ó–¢6› ,–­¢«Í’Ò>K3%\3g7¦Ë(qsrõ !„ØqÊØ"QÕ”Å6šv%ªD’ ›â2@{_î(“ Gš®‹ð $âæç˜ h~»hs[Ê×OT.„BHP›UÿÁ²î7DQ@`T¯ eõ8Ê4éЍ2PâÙ0º6ÂsПk‹&/ÎwgìÅ›õ})•šD!„Åj!š¿2ñp H$MÏúôMdPIPT˜Š´ëõ~Coâ:Xá9p€ÉQ‰B!$(Š­Ö ´uú U¦¤Ñ‹•Íi® ¦‚š(6ÜÀ b×#¡= ¬Š¢ W™‘ Óeä½ZE»&óÁ@§MüS-¾ËfNeY¦˜Wj…BHPÛ[#xܤ‚^aÓóê(6<è#‹šÈàzY$¦2–gŠñŒjGxF{tÆ%´O³ã²™{0»¥‚ˆ–7B!$(î²D3aµ ôÕF G‘V( G>Gâb:˜¸ª­ˆUmŸ:ø Âó €Ó¶÷AŠ5í).›9•Ù-2O¢B Šb»D4õ‹Æ ø ±7QµØšËÎÝp£»^ï åõä½ šôfÆ´zne}.ŽÆ¼ñ¹8 ;z‹8Êà*CÊñ(r„¹À™<¤(ÍÀdšå™’÷c:èô0ê(³g“«ZÚZR3¼¨ùÉœ¯ÏÛÆ‡Z Ü üøðÓ~¹Øñ˜Ñ\Éå³§ÐKPÖsæA¼8‚èæ¥ÔÀËZ™+Ãï/>ÓÉå)lwг°5ÁG? sm)ì´O_ ·ýtà¼[«}™¼ !$(ŠMY‰í §:õÝZ™!-^¬ÏôÆ^+†µÌÎù_íe`LRû•½ãíõK3%ïFt|z£x½®ïC‡T­~ÛŒ¾55—s€;°…s1»¶&q}HLôœ){×ñ6¥Ì#-^ŒÀ¨¤£Ì·€a3 k¥;fg{30Ê·õÈE³+ä%PQè¤á¬k­%š•MŠ|£vj Ì%‚)r ôK:þØ eõäý6ÑLÃC\'ÐØ›šl¢ÑQæ©-ü³g/»7mUHlê‘!1‰mnúùžv”yðµº~´zOMHxn£ëmrx¯ØÍá*£ Œˆh?fËK B‚¢Øœ5D4òÙÑfç›záµ,ª2<£öÚ¥¬ž¸6 ˆ¨ÝÕÁákÚS•3›+I8þ=› )ÀÝØšŸÃû€Ì––Sì„!qÖÔž~ìý‹‰y¦9_öŽ]£û”‹z#üÚØ%вž>¶¤‘r7[5&¢ý˜+/B Šb³Y+ª€¥arM6IÖè:‹"Ùø@8,•¦w¼-ãõfDǨŸc_;/Ð/ªO¾¹.®ÃÖ,}xik (us…ÄxO ‰_ÎÏGA)ÇÿË«uýXÖZRÓÁñ“Þ((îTFôü™68•&¬MìA>v˜BHPù¯Uˆë`ÒÒÖµ¦-…«ƒ÷£(Ã7j>‰¶Ò!©49£Ÿ‰ê%Tp懕¬ÌµÇtð—ðÇïç…aá ¶aÐŽ£ EŽÇÓÕƒøÑì)4äâ=m²ì#€ÛòTÖBO¾RÛŸ@©ÏC#*g°4¼HDeÔ)łРP»Ne4ËB Šb Þø¢0º=pzW·§ÐʼE;:˜4µr~ ^ÂN?Óù©27yñ]]3”„öïŽÃθ ¨Ý–ÇL9>9£¹cñX®š³ûG!Ñ|òWU7½®öÄ6¿'òQXBûü°©WæÍú>¤´v„E=m/=4phD€æVÅÛª¦ZñŒÚ3¢ýXÔÈËŸB‚¢ø4³¢xP…éñ]f6Uâ*óM‰âúèÝËkI:Þ"cÔ{£xJûç<¼fï4T­.eu•ñ·í¸@i,Ëô¦J.úpoþ²| )Ç#¦ƒŽBbxø]7»¦*òT^m\zºzißÝMa°¬õšF£( @½Sêæ¨Œ·«À¨Ý#Ú9aàB Šb³c›¡:]L{Ìhîµ~=楑E£Ž̨þ‰6ã¡þÙ…ªÌ—rðÛEãy¥¦?µÙ¥±,‰pòë-:* ¦î]1ŠΚʜ–rJÝܦþ>ŽVçxàì2©np= <Tå«@W^ÕV´öº¾$µ>Ñ4Õ‚]nñÝÂp2ŠB²~c\iÅ®×;0j—ˆöeº¼ô !$(Š-±š¨F «à€e­Å¬Ë&[]¼EQ+cíã¦V®#ëëÿ`èD¡2¡ý³µ–rÉŒ=ùÁ¬©Ü¶h<4ö&¦ƒj7SŽO£ç²YSùÕÂ]ÈøîæÖeŽNÞègg£ ø¹|%vbñ’<–[—tü_=¶f«ÚŠvrUð…Ëú/F².¢2Ú4æíñ¥õ`˜ôЍœ÷B Šb àƒh‚¢Ù½6›(]ÖZ‚«ÌKmÜ7ê³ûöª&¡ƒYÞðXSAŸ×cy¦„¿,Ãe³¦rÁ‡{óØš!,Ï×>Å…F­ ¥nŽ·ê«8oÚ¾¼Q×—"ÇÃUÁ§…ÄŽ¦w9;ªú´»†Fa—&¼:ß»*¸}u[ñŠÇÖ%éx—Ew_øuÑL€¹EŽ¿x§âF|£öh?ZyòÒ'„ (¶Ô;=îà\ 'Ìl®ÀÕæ%"ê§Ø8§ìV^«Ç—×í¾s„Ç©p1ØfäR7(f6Wòóùùæûó«…xªz0+3EÄt@`¿[<–ÍžÂÚöÔæjÁüØTH\o pOø{#»øºq±Sß¼]*/ß–Ç´¹é÷Kv¦º=9ÖUæŒËš ¼Þÿ PE!¹@¿Ô?Ùê )J“ ôíËbdijB‚¢Ø ‘5C9Ê<³©’ö@ÏÕ¿»Åu0i¯ÊjrF?€­1‰Êy4…!©}’ŽÍÀ­Æus'ñöç7‹Æsù¬©Üµl'£>m~Ä8ð·O ‰;x»~rŸ.¸f>‹­Ýü5y츱”ö®šÝ\Q÷bM’Ú¿šèk³áý/FUˆègÆ—6r¼þ5%¢bÞ%ºõ¶…B‚b4—-\fnkÅÿˆYM,k-ÉÅ”y!¢íwsFŸ¾o¯uôŽ·-öúo„ǪøIGÿ¡°ó"&c­Î½(qs8j³LØU]>¿•ÛRnË{ÀUtBÿEGô¦·µ»4á‹À€}ºì…C™ç=£ïúÓÒÈúP­Ìç#,.‡X0Ø/¢rje^Û¯W5ÀÞD7jümyÉBHP[£žˆúöi˜’öÝAKZKˆéàñ¨v èÏ.n,Þ½¢–¶ÀùCÄÇë4ìdÒ› 2¤¸þÔ%¨ãÛ76øqxïÃ‚é» á‹º\‚´Cwì8Ž/«g`*Ì}XDÅ,fËËB‚¢ØZ¯DõÀqí÷acoÖµ'ež‹ªœö@Ÿ3¹¢Ž!©t6èßG|¼Æ±}£|;;$~\l?Âë€7 [+övšb›«?v® /×ö§É‹uôä|?Â[ö‚¡Ì“žQ·Ü¾x^|²«‚+".r&ðdxÿsD7=Qk`xjßÊjevFGTÎÛDÛ‡W!$(öPÓ‰¨Ÿ¢«ÌÁÕí©ÊiM½ˆëàÁ¨v 0ê€'·×1ý–“ ôÝÀÚˆÙ|JôfBâ?" ‰)Æ®¼vÔî°Kþ˜XÙVÄš¶"ÜŽ›ÍïêÂktu‘ã}ãáUÃü7ëú¤Šï¢Ÿ„üV6 b9/²kõje¼}Énµä}|„ûóB!AQlƒì4'Qègàà×ëú<a€ÓmsþQýV2´(]›3úŽˆ™ÜÁÖ:îh2í®ÒaÍ’1ŠVßÝT§¿ÿDõâSä’ÚÿÊÜ–òå]6†¤ã_]O:JKÙ0ˆå¢ÄB{ ÿµKY“éx.П¨_‚¢B‚¢Ø‘Nèà´i½Xמ¬s•y"ªr|£Nîo{ü€¥d}-O¸‘aXÜ’ÁQ77o7…¡Õw˜Ñ\I¬ãÅzìúÓy•ÐÁ÷«³©§4{*-¾{’£Ì÷òPìÍ@Sxÿ»D7`¦ÙêÑ}+×â(³0>¢ræ²aPŽBHP[íyìT ÎQÁáÕí©¾3š*‰kÿïîC2ã;ß?²ïJ†¦ZVå}{ŽÛ‰Øåë6›u°£›O)ø'¥‚U™"rf“¹è¯äq>W¿ ·Ü¾h+2E’:ø}Š] ü9¼¿'4Ów⇛§Ëc¹a³óiÒçØÐŒ.„ÅV›í«…^Jñ¹'«“ ôKÊÖnDõÆ{zïxÛ¸ã,#è[ˆn¤õÆ®bÓ1oë<‰]ÂQ†ù-eøFm*±¼…´9!Ñü¸è†ù“xfÝÀÞÅŽw¯Þy(zãÚÄË°Ý "Ñ8Û¯÷%[‹s&Òþ‰O „Åöd,ìˆØHÄUð¥›*Y–)ɺ:øG„û‘Èø®­U,JWçýË<;üÛ—mc½±KîÒ].WTg“¬ÎmjM껊LÔÛñ˜³nœ7É<µvp²ÄÍÝCtͲ›ÜÞß ;Ú9keþ{`Õ´2GC"*j-2¢B‚¢èÛ÷¯N¶2ûµä⟨LÂ6?G6M‡oÔ½ãí»?`)™À¹ [[µ"ì†7úÙ.ئéîó¤T†¦\œ†\|s«´<@„ý?]ež¾pã¼IÙ'«ë7÷G¶m„ù¶¸j£kóÚ(_§²ó÷q% ­S*jÈøÎW#ܧç±ýK…B‚¢Ø.ïÝ„¼n\û_}«®õÙÄB­ÌãîG,ã;×Ýw»–Õ·´úò<¿¾À#ØyÁŽ2=ˆú~FA¹@óaS/bz“Aq ðhD!ñ~0§Ü8RóSÕƒu©›ûNtý1Ï÷‡÷?q8ÍøFýu¿ÞkIio'ƒ:<²‘—6!„EÑrQ€˜N[ÔZZñF}’Úÿ]”;âõÙòxöس†ÎÃ7êá¨î†í66üþìt8 ÝéBXמ"0›Wñ—BâPœqãüI-O®¤JÜÜÀYyÚå,ð=lz¸>Ê£«ˆµÏ?¼ï*Ú÷¬°Ì(Ôa—!B Š¢Sxe±Ü,ߨëòx‡‡aq}“â[ÀAáwAs”¡Ñ‹QÓžÄÙt?Å,v^Èí=Gõ íŸêêçO⩵ƒËJÜÜ}y‰s±ýן·H— P¯¥´÷âɃcìhøAw¼¤ !$(ŠÎ”cC?­Îàa›³öñúWŸDån–À¨ñ@“öÀùþˆ¢–ñgOÆwNt+Ðt¤¶Ø™á÷+°}ßn*ô XÓž¤º=…«6[¹üO¶¯–ö½27wè’ÖÒ\8}ož©8¬ÄÍ=I~ìrŒë§Ãù%Pem¾sÓA}Ö˜ñ¥n{àœaQ+€gå%M!AQt¶ûÙþµÀmÀT섞g4;•4Pâz¨ ¶É/J%ß¹í¤Kœ½*×åZ¼Ø¹ Úòx,SØþ|7„×|¸8XV¨@Ìn®ØÜÈg°MéÛÔÿÍQæ7¥nîàÿ¬úÁ%3ödFSå!)Ç Ø'Ï»ú+6ô_= 8)âò>ÔÊ:ý-ùxƒn œkG7OúÆð¹FÝbP]1eȱÀ«l˜Äy ðlíâŒB:ù1eX˜.%ã»Ä;^÷y½§%[ð™¸~ÓÁ>¯Õõ{ôü÷áÁUúÊÜ×ÁÍ&ºQ¿›’ÆöƒL‡ßß ˆ²@Ó}£8yЊïèÀ¨ý#,îíð&„E$þ¬þ”ßiÅöS; »*ÉÝl¦Ïšg4CRiú&2øÿ;õÊŸ‰¸¯¢‚¢f/öÇÃú¬JÝo…iõs¶0àt¶¡ØÚÄ߽ŸýÛäz92B5¡}>lêÅgN¥.'åxÄ:ŒéðØ$G™‡‹o¿UmE?øñœÝê/›9•™â/;ÞZ™®ZÚðb6Œrþ6p|Ô¶ûÎ Ëë²ô^«2¾sYÄÅýÛƒ@!$(ŠH4l&¬Á®‡»vãç¶äsfDq3%nŽàWNù‘<%èŸ_2f:ûTV¯iñÜ3´wÑñý6vYµõA©[;; ¢ËWÒˆ©€·ë«øæûsÇ’±T·§H9~G#¡ï¦ƒ>­Z™'‹]ïˆ\ O¼kÙ˜÷Ïù`?^ªé?&áø÷Äí2ŽCºh×~ÜÞŸ ü4êx=¡ýûÎ1›˜ Ž1¨",nvõ!„ ("õÇ€ IÀwÙŠit Pìæ8¦ï ¼ ÃSÿìÊ0‘ò:Oc¾tîÈÙ I¥_Îú;]x|wîÃÖ(îþl9pIøýõÀÊ®¼’ŽO£çËGó÷÷ãéê´%nnãµ gb›Ôr1Yc»'vú–c ùŠëeMÎhzÅÚ™PVÏ>½ª™ZQê¶"^ªíϳÕ©Ë%Š“Ú?#¦ƒs±5Ð]­»vóú×?ÊÇ€Vßz\ÿe³¾7fziÆwÞFGXÞcع:#'AQ5WA·Ð)ó°ùâЪÕĵO«¿Ù^—G}sàNm¾ó÷“.ù,¨öß,¦†'•2ûuññ.Ãö_üð0¶/Ýú©ŠÞ o?Âö­;8;Qe¾60®â´ú.¯ÔöãåÚþôе“ö]ÚgDRû_,us_Á6­;"}H<3O!‘\ Ó7ž™õÃæ‘ Ôw"‰ý¼¤B‘7ÒGqaP$ŸÊXß|êiŸü,?ÛÅái/ö»S-æ3ýV´4y±“±ýí Ap:ð"ð&¶Oè¨õù;æàP`,¶Ùþ2lWw:ÀßÌãoï:Êr|Н4í»ÇieþQìxï;Ê\W@!Ñ_Çv› <^wä©ìÅê†/ YHïxÛÏèK".ïMd%!D"5Š;ˆ\`WcÙ¹´œÙ¢Ï?Naû›W·$E|5í¹kÎ9ë²Æ\líó5)qsOã èîÞ®ÁÎWùìHóÙáÿWO…7°ý婢~Øu„û½Šðæ°ýÓõÁN~p¤£Ìð½ü¾Ã†9A'cGòçeÎÆŒï~oBY}Ãq–’ñÝŸbk‹£tó§|8B Š¢ðdbTq3%®G‹·E§=ƒ­Aûo>¶/0ê14^±ó7¢Ô²ç× 8©ÄÍ= +°CY ÞÚéap|5¼¿[Ûh°S¤¬£ó&ò²7vPÍ^@ß¿ô¾Ë†¦Ø°ý_ûäéšz ¡ýÏ1Ÿ5Ñš™Žíª „E÷¢€¡E-[;ûïÓØßÊÇ6æ}Co70÷…uŽ,qsQX5‹K`û)NÅÖšµaGóÎÁÖ4.k±9ZÂßéhÞH[ËVŒ­õê|¶OÝ®Àˆ|…¬Nrv9Øù†çéz¯MûîÅ_6Éå5ei/vsŠý)]7'¨BHPÛ&0Š^±vö®\G.Øên©?Æä),ÞSúÑÎï߆1ó^¨pL‰ë=Lì‡:‰­5Û‰ K‚]£! ŠìÊ9A›ÄŽp/'ÿËéuê%|¸3ü~ víÑùÚ€ÖÀ¹dbYݲ/^H›ï^•‡²§#l !$(ŠîÈ7ŠŠX¶£eû¶Dp.¶ï]^?åŒþmLꊱüVÍaé3ëYËýSa@c— ìµ\niàÿ°ó#~ÀxØ%ŒîKèà®sG̦ÈñÎøî…y(Vj…=’ŒzÞÀUÙæy¢ŸÁ.i—79£ou1?¼lçiœ6xÑZϨÏúFÝ/g³ ­Å¬YÇç3$ËÒ¾{ᩃ1¹¢¶4㻿Å6ëGéìê>B!AQt?9£Ù¥¬ž²Xn{ÂâØé`ò¹Ý×¹ÊüìâÑrþÈYé\ ¿àý39£éà0àùðû}°}\Gçq‚Œï|sŠš5§^´~”s>ú·þ˜ÖÙB Š¢›œdÃ.e øÛ·O8 ;7`ÞøF}¯Å‹ß}òÀÅ©‹FÏ0 si6Ðÿ§l§( ÿ Câúù/OÂ.90Ÿáu}E,ûÄ%c¦“ÒÞI¾Qù„õ4vŽBHPÝO`¥®Çèâ&¼`»O÷\à¼|ïƒ3Ò^ì±ã,òë‰oÐ/Ñö§fÏ=’O.¯'ò|yWa×Á^ÿâ"l3lyž·åé6ß¹æ¬aóVÔ2¤-p~›lŠì%„E÷äÅ€d+ƒ’i¼movÞØ?_tAX<$ã»Ï/­ß÷§»¼ÅUk_kòbFÝ#g¹K,ÁŽì¾&ü> ü¸‰èû~ÜŠ¦\ì¬,Ëß™›öÜ»€þy(÷.lÿD!„ (º/ƒ]¯]Fž&âþ˜Qiß}zp*}ÎÕcßãëÃæÕ(eNo œocGg‹üx8€ M®c°£âÏÉ÷†(hËøÎ—'VÔ­üÚ°yxF]gìQ«ÁöMB Š¢{ Œêì‡ô€/óº µÎï üáìás+~1á-Æ–4þ6íÇ Œz^Îväáè[Ø¥W„?;x 8°+6(ã;-J¿pý¸w©ˆµŸ–3úûy*ú'ÀJ¹$„E·æš]ËëH9~g×*V_ ‹jò£þ¯Ås_œPZ¿ÿÍ»¾ÁYCçMW˜#Ú|ç;äyÀÍâAìHæß…ß—¿î'?ͼŸ¼¶úyÂñÿ푳¨Š·íÖ8w¨üý6p›\B Š¢Û €R7‡«Lÿð%ºnj‰móŒVæÇgŸ“üå®oùcK~•öܽ<£¥ïbçX|[s¸ üÙþØZÄ ºîºV÷F]zÅΰo¯µ}Ó¾{/véÃÈó)vÀŽL‡#„ (º?WªâíÛ;5Îæšõ}[}÷\ åŠØ"9àOa@¼¨þ9à àR Þ…×ô‡i/vò/JkÄl2¾svÇ|X\)—ˆB‚¢èQa1â¸Þõ@W¯šâF]”öÝ7ú&Ú¾rÑèê—»¾™Û¯×ÚÛ½@OÉøî ,—«¢CYànl?ÄÿcÃ@¥qØ%ùþÞïÊky^ÚsOøÂàEë¾=rY£oŒúj7á‡ýz¯%ä½òø×…Æ‚È I8þM¾Q/-l)ãùš<±v0õ¹Ä~)Ç;SÁ @ß|Y䀗±MÌÿÁ΋¸^àëÀ¹Àà‚9i°2íÅŽ9uð¢Ï9‹l ¿õ÷ð³P>xØ>/â -½d±¼Ø !$(Š­xW3ŠVßåØ~Ëùæˆ9ô‰·‘öóVqÇvgh+аh/zx*®ýÛ\mžX™)Îýyéž^7WýeŽ>¦´‡\ïbçBœö±ÿÛ8 Ûì<°6ÚÀÒ´ûÜ©ƒ}xÁ¨™dç߆Ä|¦¹š^E‚¢B‚¢ØbmC™›ã¬aó8®ß2”‚lþjØÉ—Øš¹Løóë±Kþ¢éqÜåªà¶ÇÖ iûõ¢]ŒBÛ9'‡‡ÇbxôëF—B;vŽË§G°ýƒþßÂZùP\h;``vÚ‹wêàE /°5‰]ŸŽÂÖ*JPBHPìAÑ¿6§îÂ ÈøcK¸|ì4F5‘ñÝ(–îÛ”8ðwì¤Ì`çVüFˆ^Н-P0¡$–m¿|Ö<·nÅÎ'rA/`lä¾À ¼€ö!‡ºåmléËÀœ~o\â¿L*Üë™7Ó^ì”S-ZqÁ(Û'Ñ7ê6 –ÇÍXì ,-äç¾E!DÔºÕ`…êÅ@T+t;Ù@ãÍI—pÎð9$´OÚËçûé'B"ØZ¸‡?[üX‹]þ-Q`‡ðVí^àpdß•¼\Ó¯£#uÀSá `@´&S±“=$?MÕ9ì@”ÅÀ‡ØQÉÓ°óD¶uðûc€£ã±Íé‰B¾ž <šöb_>uТ†0$~Û7ê×yþ|h€ozHB Šg]À#«‡R›M×Á}òZ}—ÉVÎ1‡ƒ«VãšöÀÉç&İ#gOîàÿ ƒÕ)ØÚ®»€ÕÀ_)œ#À}íf·òZ§Ò¬ÈÚµµ:¼=¹Ñó¨0 Á6] ^†]¹p¯>ì\† 5ܶưœÅØù—†p-м‰m+&‡GS€¢îp=ûFÝÑ8çað¢Ü·msó|£®é‚M¹;ØG!$(v§Íš=*Ö1µbߟ¹'iÏÝ!âomÃ^•ëøÞ˜ÌÐê¹ä¹Ž5¾™¸ÞnÀ3ØyøÞ ƒãaáßíZ‡òßa#0Š'DZý—óÛE㉳Uו·Qx|sÇ*Þœðëǃb[[ØŽ­LoaÙýÑØfÒý°5œƒ»Ù%äýøözÙØiÜgµ“ ô¯£Îí‚my;€E!ÝpÅ´ï²Wï5\;î]~4k iÇ ‹ߥÄÍqÎð9œ0p)H{y?ë››OÞ‚ßįbkif‡¿NìâÃùç¿i 볊ûVŒ¤É‹ã¨N»®²á­qÿÞÁÎoX…­©‹ío¸+¶É»O7¾¤kÚ}眤ãÿëŠ?à ª5-^ìNê‚m™‰"Húµ!Dw Š9/Æ”ŠZ®¿ã„E¤½cKøöˆYLíµŽ–\¼+ÞÑØ•¸ŽééÝ_sê3¾sI‰ëýé¬aó8cðBrF_Üê»7váëÑÀãòv „=,(Ú°è2±¼ŽÛ'¿J.P¨6É¢1Ó†ŠX–6ßéªÎSñN ‰ë¼Š]Sø ìHÞc°ëéþˆè§pùhË'·eøÆ°9Ìhª$×?|tÙµ /µx±óÆ7Í8ÔLöêU]Ù’‹ÿÆÀ]¸Y×ck´…BôÄ v.Á"dz]þ{¨Œße;×Ñ<‰avY¹ë±£L=à:àEàWÀîîÓŸ>ízYÜÂØ’Þi¨"m­âŽ › ôõY£¯?mð¢Ü™CæSËМ‹ÿÛ¡«ü ¸\NBôð ¶ÉPtºÍÍ“Ø4pv‰¼óÙØ&†oàߥók?1ˆ¥#Jް”÷{Ëj@ÛÁÀëß½¸O¼íõ³‡Ïåè~Ëu.Зf|÷*ºvòïa»A!„ø”7j!:²%ó$v–ðËÎ~Ÿ~Ȇ&êÎôàS«Û|‡½*kUÜL6päjØzÍÙ@_ž ôÁ‡T­~ýÖI¯sLÿåc3¾û„gôõ]ÿ‹®)+§I!$(Šm ‰Q47oNoì`—x= ‹ÕPÆ&±|œ’®Ç!U«i4žÔXo1ߨ‡Z}wß¡EéëoÜåíìǽGŸDæ¼´ç¾Fþ,mÊëØµ®›åL !D б|Ù¾<¶{ò7vrøf~¶Å7Ü‚[ï÷t0¥ÍVØä –Žd|‡“.áÐ>«(r|2¾CÚwi¼pÍqñ?ázzÆwNŽkÿ¤o˜=ã¶I¯²geÍÄvßy*è[±sEv¥7€€9[B±eºUEƒbIº”ÝËki“æÀ¨ìI׬Šññ°z+vå–o˰MÓwaGFf÷Ï[u½…V†íü5íIµ–òac/æ´T°¶-E].A6Ð ¡b*Ø!ãÕm¾{˜Ûë³*ýµaó’J—¶Îw3¾s vñ®ö&pÖV ŒB}¥Z9¸j5i/FÆwXØZFM{‚›zó~C/V·‘Òþ޳ó;ƒùõ^•Õ«N´˜)5€:¹Õw¯¡kG4<$¬“§·Bôà ¨1x(n˜7 @Âbt~ƒðñkº~Ò¡x\Onþˆ]ÕåߨéuNÅŽŽžú)s[0ˆ¥#ðŒÆó†b×cJE Ãg,c~s9?œ5•µí©aíñÆl ïÊ}ëðTË‚¯ Ï}Wj¯6ß¹ÂÀg h[Ÿ>,Í'„Û˜½º[² '?¾aÞDžªD©›“³Û€¯`û‚ÁØæè7ÂpHüîöÆPØTapogmˆAáE›ïÐê»4åâŒ)iâ¢Ñ3Œê±S5¨nó›Z'!Q!v  hÃbaÍâSÕƒ(‘°•õ#Ÿ› h›&…¡ï%lFÆû€ƒ°£jâ§>yÛl 4{1ö¬¬á¢Ñ3ðŒîQaÑÀ¢Œï^‘ ôn{õZwñå;O›ÿ›‰¯sê Å#’Úÿe«ï¾cì*;…Ôqønl-t‹<…b;2W·Ýpà:l†VÙw-Ò …G°µDÿÐvÞ^n·3žo“€¯aG¹Þ• j GI+àæ…€§û.ÿøF=ß8Žëà‘½*«›mÄZb* œs[<÷k@ynû-Ø)•„Bì¨AÀÕžQÜ0o"`8²ï*Z5í»g üº\œŽ:I!„ÅŽÃb`ÿZ5œÀ(Ú§Øõ”L3º¤‰IeuŒ.i¢_"C±ëE6Ð=v„j„²À7yØéj µ†©ÛGñkÀ{Øf󇀹ù(Ü7Š˜¸|ç0Às]ƒÀ¨¹žQ¯dýT±ã½Ú7ѶfRYûôªf·Š*cÙ’l ÍúôVß=šÂìøqÏc×m^&OM!„è|ʘÂíløçÏÞæ¿5Ø•5Ö×" ÜÍ20™aï^ÕŒ/­gLIUñ6  [ï3Øy t“ímÃ~yx Xu1à£øÉÜÉ…Å“€#*Ú ŒZœ3úߨ—7JÝÜÌAÉtv¯^ëØ»²šÁ©4±lÊÀÞísb`ÔgÝèú» ¸8<¯;¤ÒK#„Qê±#? ”!¡6´8f‡éRf·”ã`è—̰[y-ûôªfײzzÅÛ>ªi4Ò¯qK<ì†Åƒ»Áö&±Óç‰íÓö*v®½gYáç‹N•3W\±ó(ˆªÚ3°Ò ô"õ¨w´bZ±“[<²¸¹uLq#»”Õ3¶´‘¾‰ ¥®×7¨}sFÓ껇£ºÙu×Ä;å)(„;V†¸2ı+gÔf“<¾vO®LßdûõZËá}V2²¸™"Ç#g4¹@ËU²y‹€£ëÂ7ïî¢8*¼e™À‹ahüXÑy)NSÿÛ ]ìzhÌ?ÿ d€zcÔºœÑkfaÎèƨ1Ì‹©`ÍTº±2ÞΤò:úÄÛ_ZOÿd†"Ç+R06kôA~ iñܽ>Ýôzû;gã;òÔBˆèõئçmy3Ïš„öVÔ‘}W²o¯µ N¥ÃZFgGZÃw[„è2¨›ïG}ßÀ®ü!vÀDv{4¦rææ…xvÝ@rÞ#¦‚‹ÃòÚíÝè•Q˜ Ñ j]TÇtÐÕXìzuƒRiŤò:Š}mŒ)n¢*ÑF™›ÅQf0:gôž^ ö2¨)ÀÈp}ý1ü0Ò(O5Kšž…ó̹ÀÁ3Š27Ç!}VqL¿Œ*n"¡}2¾+qó†‡añ¸´OYlÍé\ìüŒ³ÃÛZ`[1¢ÚQ†˜ ˜ÞTÉý«F°´µä£e)}£SÒÄÀd+^Ø_Ö7ŠÉVÆ–4’ 4Å®ÇTšHj¿L+ÓÇ7j”oô8Ϩ QÃPXÕƒŽ5p ð7yzIPBHPìÒ ¸±À(Ú‡¸ö™PVÏ)—°We5 Ð&ƒ_>Í…ÀÕt‘³ÛªX ¬ÂÖ8.ÁN!´& uØ&ã6ìšÙYÀÆ<ìzÑ* ŠEJ{ɘ\PI ¦<0ª·gT?ýÔ /ÐáÆ"Ä{ð1~ »ÊÊ|yJIPBHP,¨ ¸žÚ}¥`\IT­áè~+¨Š·“ñ%0nÆ$àWØ5˜w4& ‡-Ø•b2…F\mTE­ ªÔØàWÞÖÊùR‡]UçVyIPBtYïnKÒ4t| 0»¥‚›zñÈê¡|nÀ2Žî»‚ªDß•ÀøIÓ°«¹|;|Ó¯ØÁ.›8Ð+¼u˜$\3y;aú9ÓÊr|9Bˆè_oälÝ;Bû”¸9ªÛSüvÑxζ/w/MÚs)qs8Jz0~ŒÜ‚]^ïa9b3Va'H?NBâ¦b±“#h^5Lˆ"úì#MÏÛ'g4í¾fH*mkû­ w¼VO½lÂké~s÷‰èøØÍ×`û|ŠbÊñiÊÅxnÝZ=œ-e¼þÐírp„‘’¦çíS17 :›â·‹ÆñïÕÃ8yàN¸W2Jú“îÁ®Šr1p¶žØq½ü(ü*: ˆÚ§É‹ñTõ`Z5Œé2b* Øõä !$(v·À¸®=ÉoçÕÚ¾œ2h {WVãjCÆwä mP\†Æ§Ê!ÙáÌÇ®þ¶bz¡* †5ˆO­ÌC«7 ˆŽD!„Åîu@Œ€{óASo&•Õò¥! Ù§W5íCVVzÙØ à Ø¥Ø®`ǽ£Y‡góV AGq} âÚÁ¶‰9]*Q!A±§Y?JúýÆ*f7WrT¿œ8`)£‹›dÆOz&¼};Úu9$=Nsøàf:qyÄž‡± ¥Œ˜–€(„ Øc) ÈñŒâáUÃxvÝ@¾ˆ-¥ÒÄ,„ (6ˆë<[=™M•œ>x!G÷[AÒñi“ÑÑ„·ý±2ŸÈ޵ÊKwð6ðlMpƒŽâÇG1¯ïƒ(MÌB ŠâãPâzÔçübÁ®¼P3€oœÍ.¥õ´Jí⦼Þ®ÎNÆÉaé2MÀcØ)nžA¦¹Ù²€(}…Ý)¯ÈÊ,…!ã»»9N¼Ï\BLû´R»ø)RØæè/‡_ËåäÅûØ.÷‹åpl> >·nàÿĸ:­œgî¿S¶"RR£X(‰Ç±ƒ]n_<Ži½¸dÌt$3²à§äk64K>œ ìÄäðtªeØÚÃû°µºR¶©€¨}š=;HEæABtwR£XˆéÇw¨J´sö°9Ýo¹@ã½vÅ€9;ÅŽ„Æm³xøð¶©YlòÞOÚsyvÝÀÿ ˆYƒøqR£(„ˆšÔ(èNC6Îuó&³ ]ΙCçSîfiõåtm¡éáí†04†Æ29<›d€9À³ÀãÀk@£–OWâæx¥¶?·/ËâV©ABHP‹é—€¿/Éœær.5“J%,n{h¼ ì Žmž#Ïj€w±5†ÏÓ€¬\6[_ªéÏ5sw§Ý×…E~( <–czS%ß™¾7Wîü>ûô®¦Õs0Ȩèm°¸7¼%€Âะ{ã=ü¬f/¯‡!±F.퉵¸fîd<£H:2ð[!AQäYʱ# /Ÿ=…/ ZÌ7G̦=pÈZââ¶kgCmãa@ÆÉÀ`tø³îú<©ÅN€=;ÏáûÀÜðç¢3BbMM,¾ˆB!AQlþD©€À(î^>šêöç ŸM¯x;m#a±sdáí¾ðg¥À@lÍãÎÀØðþ ?PRÛíë€j`¶á|l­á2`œÚÎWêæx±¦¿„D!„EQ8ìÜl­ÌòL1ׇªxié·•fl Ü\à?ý<ôúƒ±}û‡·ªðçåáïaG]a{8á­#í€ÆNû“ÚÂ[Û<¼Ût¼X‰mJ_þŸ4çIÉG!q7 ‰B Š¢ð”ÅrÌj®àÒ™{ð푳˜\^+ƒ\ò+ƒmÒ]‚mÒ툊Ã[<üªÂç\Q¿ŸZàØÅÖ°¬v9ä…=^¬éϵswÃ3…E*v<æµ”ñý{pÕ¸÷9 ÷ZZ<9Ä-áMô¶¹y×ÎlC¢’(„èùdçnœBRŽg4×ÌÙ—kúQâæäÀ’÷I”(„ (ºƒ˜Èš«çîÆK5ý)eep‹_ª•>‰B Š¢‡EÏh~2w7þ´tgbÚÇQ²B´kpÍé“(„ (ºsXTžQünÑ8~»h<1HXb;Câ‹ëçI”>‰Bˆ”Œ~èAe(e¹oåH.9‹,ßHc´Ø1mË•oØÐÜ|­Ì“(„ (zÚc±›û(,ž?j&ÐÅD+CBÛÁ^leÅz\F7KHBHP;BXÌR³(z¾˜HhŸÆ\œÇ× áùš='¶øù£ ZÊ¥O¢BHPÜqÂâ…£f¢10&º=p$tŠ‚ºÖŽ«¥Ëx½¾/O¯ȼt9µ•—ªâ*>‰B!AqÇ‹÷¯AÖh¦VÔÒlÛø% ¤}—\ 9¢ïJzÇÛȹ@#CfDWp”!©}rFóAco^­íÇ«Q›MÐ>ÅŽ'I!$(ŠO ‹ŽÇ£«‡òïÕö«>ÑÅ#«‡²g¯u|¦ïr†·SÙÀÁ“ZF1­ q 0Ôe¥2ñ¼BHP['éøöX«ÚЏwÅ_3˜qe Ûo9Ëêé›È`€vßé”&n!ÖØqµm Nû1¦7–óܺ¼R×êöCÂñ‰!MÅB!AQt¹˜ˆéßhÞk¨âÝú>ôIdص¬žƒªV³Wå:Šß(²FF\‹m¿Îâ* g4+2żQׇ§ª³,SB6ÐĵO‘4/ !„EQ˜´2$•­©lÌÅyvÝ@^¬éÏð¢¦V®cïÊuì\ÒH©›“Ð(¶:.ÏónC¯×õeNsM^ WÙ)EŽÔ !„EÑm8ÊPäx`Ik óÓe<¸jÃR…ÆÒFŠ…‘‘ÓÂ~ØÀ|TC œÂáu}™Ý\A£ÃÁöK”ÚC!„ (º9…°8N°!4¶”ñàÊ +jaxQ3ûöªfRyU‰6†6ß•‘Ó;Ðõᄵ‚ ;’~fcoæ´”ó~Cof7WÒ˜‹á„ƒVdä²BHP==4ê ¡qAºŒg× ¢_¢•aE-ì×»šãú/ÃÈ-C`zd04ĵÂÎÁY“M2§¥‚×jû²¸µ”%­¥´ùnØì\ìJ8B ŠbÇ áÈÔº\‚5õE¼Yß—7ëúðÑ3Ìöä’ìþÁÐÖ: 2¾CM6Áüt93›*˜ÛRÎât)^œÀ¨ú–¸ÒçP!$( ±þÂS7lV|¹¶?Ë3Åœ;r6V­&‹Éô:Ý"´"lF6¸ÊÐ껬˦X˜.ezS/æµ”±8]JC.ŽAÙyU@Rûr…B‚¢Ÿ®Ôͱ¢­˜«çìÆiƒË9sÈ|@ÑHStá„B;ÊÝUŽ2heH{1Ò¾Ãâ¦RÖ´¥˜ÝRÉòLK[K©Ë&0áßÄT@Ê‘`(„…ØHjß(þ¸dg´”qÞÈÙ IµÐâÅäå1 lm¯£´WdM6phòb¬j+fqº„¥­%Ìm)§.›dm{ß(Û”¬m˜LÉ !„ (Dgr”¡ÄÍñBÍ–eJ8wÄl轆´ïÊü‹|œe0@Lhe0FÑ8(eXמ¤1ge k²Iæ·”Ó8¬nK…gì„×ZÙ¾§B!$( ‘enŽ™b®œ½;g]À-$¡Ú'ò¦èõÓ²he'ìñ ÔGåÀ7ºà¦óÑmÀþ³}Uàx"@á`¨É&©ÏÅq•aFS%9;Ä轆*<£¨É&©Í& d' –ÁÇB¡4% !„E!º€Ú'0ŠÛe~K猘ÃT:’QÑvD¶OLj² ^«ëÅÚöœjeDq3^`£¢RÐ?ÑŠ†²õÛ«• k›’3šl #9^IÇ'ã;¬Î¡Ã&-žËŒ¦Jû$W†Í•¬mO×>kÚŠXמÄQ†Œïàm£h†å ¼Im¡BHP¢@ie(ss<_3€M•œ3bÇö[F«ë”U]\p2¾Ãœæ ÞjèÃSÕƒXÝV„†º¤ã“r<Œ±‘R)ÃN%ÄUðQ,ôŒf`²•ñ¥ xl—oãJ[ÚH.è¼Àè*;Gå¼–rþ´t'Þm¬"®|TXƒØìÅë>ª4\mk}IÇGj…BHPÝV±ãÑ‹ó³y™Ó\ÁYÃæQâähœ­~¬õ“?F±º­ˆ{óÄÚÁÌm)'ã»$ßNÝNßb€vÿËy·¡* Ž–¢xhõ°Ë Œ¢"–å>«8iÀF7oW £« IÇ£º=ÅKv湚´ûš„c—Á[¯ÄÍmöq”¬‡#„B‚¢è â: 0ŠûWç݆*~¸ÓìRÚ@f ÖŒÖÊÐC}.Î{ ½yzÝ Þ®ïóQ_½¸: V PêURm} \.Ðü{Õ0ž[7êVqâÀ¥Œ.iÚªF­ IíS“MrïÊ‘ügÍVµ‘Òþ†égdÌBˆN¤Œ)ÜZ…Ã?¶œ!ñ íCJ{|iÈBN¸„˜ö?Q»¨°A'´OC.·M½x½®/ï7ôfe[1Qĵ£ò{ýF‘ J݇õYʼn[P耔ã‘5š—kúsÇ’±¬Ì“pü VÄé™û „ˆ”Ô(Šn'¡}rFsë¢qÌh®ä‚‘3˜JÓêň逘 Èó[Êx»¡/×ôcnK9õQxì*ZŠ/Ð<¼zÏ®ÈaU«9qàF7}"0¦Ÿ\ x±f÷¯ÁôÆJm>µYY!„ (vXŽ2”År¼RÛy-åœ=l‡öYÅütoÕõáÍú¾,H—Òê¹Ätá­ ò?qÍPž­ÀaU«8aàRF7`×A~½¶]>šéM•(Ÿ!„ù!MÏ¢ÛË¥ £‹›XÚZJ‹çâj;h¥»tÙ Œ"ã;”ÆrÙw%U­æ¹uyjí`ÛÔ.+ˆHÓ³"jR£(º½˜¶SÕÌk)'¦ Ån÷ UZÙíöÍC«†óèš¡äMBû…BHPb{ØI³»ÿÀ­Œ·p¨"„¢‹ß—äQ˜ÁW!„ („B!$( !„B ŠB!„B‚¢B!„ („B!$( !„B ŠB!„BHPB!„…B!„E!„B!AQ!„BHPB!„…B!„E!„BѸr¶JQx+JÂûf£ÿW@ š€v ðåÐ !„B‚bÏvoÑÀ` ?Ð H±M?ÈÙ0,®V+ÙÀ`.°"ü=!„Bˆ‚¤Œ1»q‡þì|7 8(¼íè‚tX¼ ¼¼†ÈîàR`l'>ÞóÀ_ lû×ÉN|Ì[€ioóµá‡±ÁSÀ?óTÖdàB9äŸP\ dº¢ðgî¿S΀ÛaG¯Qœ |6¼Ÿ¯ãQ Œ o_Ú€÷G3 ø˜ ìÑÉYhA± øf'?æ#Å2àyIû„Ö<Å‘ÀWåB-ð“® ŠB ŠÛò†z"p&p`ƒ$°Ox» x¸xh.°ã—îäÇ+Ä7 <î¥ø˜¹;Âô8%À÷±M»7xHü¸1À/°}Ï C¤BôTßÃö\þìÈðgƒ¿‡?[ß±×F¿·ÞNئn€F9¤BHPÜœS7ŸCºñ~ n^Ã6¹!DOÔ†m.Ncû—~ß~]_sè`g–x5 …ëØ©ðµ²˜OçHB‚b2¸¸߃ök ð$ðì(W!„èéïONÿ§Âÿÿ6¶ÖpýÐæ¯`»íœƒIBZa„ ø _Âöa9¥Ÿ·ÿ ?I.—°båb礽Û·ñFàçÀãaEA©"!$(n¬¸ ; ®ßpîÆ`k/“ËX±ƒ*þ~êRìés{àû›·ÓpìĺßÚÁΟ\†ã¹œ…;è{ØE@}øÁy©!:OO˜gl_Ä;ðyüR–¿ˆ]P!v$‹€CYr(„ˆæÓXwu¶?Ê9•ìéoÀår„Bљ᫻ø vqø®¶X ¬ oFÞõÆöì‹]À>•§mzø†\ÎB|BJA—KÈ!B‚bÔŽ¾ßEeW/Ïï`'s­ù”¿) ã.À¾Øù½&Gô‚9 ;b›\΢ùÀ­t<â´;y³o»–9þw˜îfÈSJ ŠQ©Ä.Í—ïÊ—€?O`k·F+°8¼­X28) u»tÒ6®Nîoæ¢0ƒâ/ Ü»R+v°%Ý<(@»œN!$(Få'ÀÈ<–÷JXæSü¸³±Ó5üøv!û}·ãñÒÀiÀ¹ŒEDÊät¹4ÙÔ>sÿr„„‘*ôÁ,ûç䩬5ÀÙÀA„ĵaç€<8m_rêÀ‹r Ñ£9r„]© jÿüÙ›z¼1O/”ß&¿‹ÉÀ=aÙß.aË;ÞYq@!„+äÅóPÎOãó7Ö \‰­ÉÜ’Ž÷¿~.—®B!vÔ è?ÈC9ß Ë)„yo‡aGšnÊÃÀErÙ !„bGŠ'S".ãàW¶ßià|à,>¹Øý›À™ØÑ¨B!„;dPTØÁQºÛ„[¨îŽ`Csø"àóØfj!„Bˆ6(îìáãߦ¦Ð½†Å§°££—Ëå*„Bˆ|*ÄyÏ"ºÉegçu£ó38Z.S!„Bt…B«Qì…Œ: p.Ð$§]!„¢ûÅ£€¾=öä” !„BtÏ xBD»¸ZN·B!D÷ ŠØeí¢p°ZN·B!D÷ Š»"xÜFà9ÕB!„Ý7(îÑãÞÔ& !„Btë Õ܉•Ó\мܦ69-€¬$„;¼B™G±ÁãÎÁ.}' W`WÀ)í €!îò–ùT$»ÁvªðGNN™BôÌ 8" íY +§¹ œLt“¬o-n‹ÚÁÏK x[»ZèÇB?èaçÀ-ò!„ £Â7¦ÎöŒœâ‚Wh¡LÉ)ùè8ŒèFÛ;´žƒp3v‘€¯ËÃ?v!]/ÍÀuÏÜgƒ<}„ ØÙvŠà1Û€irŠ…Ø!ôÄfçpF7ÚÞ,p AQˆ¤PúaEQ°X!§X!ò¢Û\.„ ØéFð˜‹ÎíB!„Ý>(VEð˜Kåô !„Btÿ XÁc®’Ó+„BÑýƒbiÙ §W!„¢ûÅ(däô !„BHPìH£œ^!„B Šé%§W!„¢ûÅ(VÃHÈéB!„èþA1Ácö–Ó+„BÑýƒbs9HN¯B!D÷е<æ09½B!„Ý?(®‰à1GI9ÅB!„Ý;(®ˆà1‡†7!Dϧä!DÏ Š "xÌ0EN±;„˜!„è|nlÇ<ÀDP+ppœæ‚6x‹Â©2@ pì><à"`…_[çKþŸ½{s«.ð>þ9ç$™é\Ú™vz™NïôÆŠ €°Xm©(¨€rq]]]žq]Ÿg×]ÁÅuä… rUÅ "‚ ”K -ÐËLKïÓv:Ó¹'çœçßI›ÉÜ23I&™|߯W^Ó¤I~'çœ$ßü®z+¸ªÙQPÌz`?éŸÒæÃ˜~Š:Ô9ë7À—rl›& <(zÀ¯€-:EGôGËŽ ´çCk ¶UDÓnOðÅœî 88X¥C³r1Œ•¢š‘ø~‘Ó|$Oºü¸hÑaQPÌ”—3ð¼—*(ŠHò1µt}®[¿ê?Ò^‘ŒÊ¥µžŸÏÐóžLסý˜Éß øМç \§C-"""’¿AqðJ†žû*´¤ŸˆˆˆHÞE0£,3¡ø?:Ü""""ùÛ3ôÜŸ>¤C."""’ŸAqðl†ž;܉鳘OBÀ|ª"""RèAà3øÜµÀ÷óèø÷þF§«ˆˆˆzP|³¬[¦\|=ŽÍdàqàS˜>–c–$)Ø ØüW†Ëø6pE—cßg%ÜV<¢°("""…þØžá2¯ý“˜~š‹zù¿ àQ…E)ä xøÏ — ÂâÍ9òšÇw? a_ÆÔé+"""…Á4?oÎB9·?¦Œàk]†YÂðÚA„ʇ%:…EDD¤ƒâAà¦,•u!ðbð7›føwÀу|ìdÌÀŸÅ:EDD¤Ð‚"ÀÀÓY*k6¦fñIàä,Äï¯b–ª)ÁöªfQDDD .(úÀ—€æ,–y6ð¦¶îB 2MÏ[ŠÅ|ð:ðµ4=÷à×Àq:%Íšµ DD [(¶ñmàŸÈîDÙ6ð‘ಠX\^ÁŒÆnIá9*i˜¦áSÓ€#2´½Õ˜åÏVë´–4p€ë=yþ:|̤õ»tHEDFgP3øÃÀù#Pö4àòàÞ Ââ`?ÐÄ0#•K1}'á­°²´ ‹’î øåQòZžUPÝAÑÇô嫿àv„9Á%),Štç®vƒˆÈÐØy´­{€‹I­Ù·UcFQÏÔ®‘B Š/cš€UCпŸ{µDDD¤‚"˜‰¦ÿN‡®O·W­Ú"""RhAàN…Å^Ý¡ý""""…ÁÔœ)ö¯ÀµDDDDAñpXü ÐYÀÇо|C§³ˆˆˆ((vwf:˜xüš€‹€ïêTÅÞý³òÉ tìÖË0ëS‹ˆˆˆ((öcp&ðï˜æØÑì>àC˜%EDDDSÐÜ€Y£yÝ(<^»1óH^ŠY:PDDDDAq~|øàà(yM÷ïîÕi+""" ŠÃÓÜœ„,/O_Ç1Mê—[tÊŠˆˆˆ‚bú¼„¬S1KÛµçÉvÿøp:f°ŽˆˆˆHV… èµ¾\Ž> ¬¦çØ6¶¡ðnà·äo-¨ˆˆˆ((æ¥7ëÛ€³å˜ÄãGh{:€W1kXÿؘãû¯4ÍÏ7&_£ ”çÙ{ÍÎÀ± lÀÉRYá ¼×lBQPû1}ïª1ƒ_–aŒÌÏà—n'¦¯á«À³ÀsÀ;y´ßÖ§ñùþƒ¯±S«[œÆç¬ÏÂ6ÿ(ÒÇZ7>°'KeÕ?IógE³¡ˆŒ$Ë÷G~ÊÁe^™KûÄfïÃ4S…i¢ž LÂC¤ŸÇÇ€(fªž½ÀN`;fºžõÀÛÀf {ÙAIƒU¿ø‘v‚ˆdTH» 7r›Çn/ .•@E°ïÆ]Ì(k3Osð· Ó´ sî”­üüµXø´¹æT¶ñ9sÒvN™°›ç÷M摳xKQDDDA±Ðµº!Ž»Ÿæ­evÉAÚÝP·õl¢žÍÂòüßE/ñÔîéüwýötSŠåÍël‰…9oÊV¾:o-1ßÂõÏ âaÑæ†ºÆ?í›ÌCA`,²=B–ú0Šˆˆ((æX˜ÅãöñÍÚÕT†;Õ°õ¦3ÜrÞ”-QÚÌí›jy£y%Nî‡Åƒñ8w-®oáú½7ž÷ŒU»xpÛß9“†®bJsèµ&¿3yž= å×·ð¡GŒ.ÏÆÃêq{D?4D¤Àhzœ<çnˆ•5u\6c%NŒ./µ9z}`ŒãÒåÙÜúöbži˜ÊØp+G¢…Äykð|›˜ŸzKÛò)qb¼×^Æ]u ynoõˆïôÌ<ÐU‘ŽÛÚõm…’ éò<ªŠ:°ƒ÷BâûbaYå¡(Þ¡¨èco4ç½ö2Š7'ަÇ‘LSbóöXˆËgnàªYïÐáÚ)‡D0µ(®CØò¸iÁë,(kâž­óp,rnTtbHt}»ÏšÄ>µoÑ 3¹¨[¾Êo÷LãŽÍµ´ÄB”8nV£q|*ŸùeM¬˜ZÇ • A 1"¶ÇúƒãøæÛ‹iuCšî'­Ñ&êÛÌ+mfùÔzN¿çÐ{)ñ}Uî2}wýî~gG OìžÎ»¦³«sLÞ0+¨F1?yX´ÅB\>ã]®œõ®“Pû1xŽåSŠò‹msøÞ¦E„m/gÂâÁ„>‰žo ª&±¯€\в¦y<·oªåÍæñ”†¢Y ˆË«ë9mâNJ^ÏæÆ81^nœÈÍë£Í Ñ@œakwf–´°²¦ŽÓ«vRŠöºïã?,z;ûC–G±ã²»c ìœÅÛŽ ly#ÖM@5Š"¢ (=Ä|›¨gqéŒ\1ó:<Ï~½†”†¢<´}ö¢ë[Ăھ¾¶ÇÂ'dûØø„ì£mK0ºù«óúï“8%NŒÆh7®;ž5M•ƒÐãaÑé:)m“mù& N­ç´*Û8neNŒ—Tqóú%AXÌ\Íb*ÇÎü ð°-ÒŽ|Lsp4¡FÜ Fä‡Óðº[ÝGmä¶#_¡ª¨ƒ674¬÷LÈòã¸ÜÿÞÜYWK‘íŽHXTP‘LSÓsžéôÊCQ¾¾ð N¿gØ5‰É_Ö­±0+jê°,øÏM‹€þâé#铊ڙTÔÁü²&ª‹ÚˆùÝ›Á-Ë'æÙ¼Þ4Æh„mí¥ta)¹þÆÇÔ~lên˜›þf²î±¡.¾Uû ÿ´î8ÞhŸRXô±hwÆØ1Κ´¹eÍÝNo!lbQš°“² ˆ­îÀo½7ÄÒŠ½Üzä« ‹æØ9ØT†;™\ÔÎܲf¦·ö8v!ÛcCË8¶µ—²½£„Ñ¡aN;”¸ K+8nÜ^b¾mùtyÏ4T³¹µ|X±5âèqû¹­v5ãB]‡¦‹îµ–˜Í§¦o îÜ|$E¶§H"2ê¨F1t¸åá(·.|•¥• ´ÄÂé[×­fqó"ÂVϰèíA“艕 |pÂnÛGU¤Çö Y^·~w‰ìòmÚb!¶u”²¦i<cá_°žoQŠrAõlËO{HLTd»4G#ܸþ8Öôã±ØŽqò„ݬ˜ZOíØ8}½ÖCûÓÇ –RJ-VY(šÐ í ;,ÆÃ}Èò8¡²ó«·0}L+“‹Úq,SÓÛÛë±ð‰z61ßf{G ««øã¾)¼Ñ4Ë‚âAÆö .·— kêYZÑ@‘ãâmìÓØUÄÓ 5<¾c&õmeƒ ŒÑà<[R±—ÛŽ\͸pסDé}¯Äøé¶9ܱ¹–°ågµ›€jEDAQÓOï„Ê®Ÿû&ÓÇ´Òî:™=1‚/À‡vÌâ{7C'Ä“*÷°¼¦žcÆîDZ|:ƒ‘ Û2a2lùXVo”øø¾E›ëôÂÒÁÇ„œæX„×ÇšæñݦÏ9]N©ÚÅòêzjÇ6Zc:JCQ^iœ8¬šÅx@t‚€¸¢¦ž%ãöb[‡`ªçFÈö(²]ÚÝ/îŸÄC;f³¶©rÀÀ讃•O¨lÀ±¼C5Ó‰ËgŒãÒðôîß5“úÖþcÔ·étf”´pÞ”­œ3å=Jž‘3ÉÂô)}b×tÜ>‡ºÖr¶—•AH Š"¢ Xà\ߢ˳9q|7αáhÆCb°¸}ßÛ´ˆ˜oSìÄ8±²5u3v?X¦¦3ßÅÃbS,ÂMëŽcõ*JB1¢žmâ„],¯©gQy#nb¢¡Ö,öˆSëYR±;8vþ°Î?˜bÉ9×4UbÅŽ›´ ¦qɸ½¬ Òk`4}JŸÞSÃc;f²%©†ÑD›cZùhõVþvò6&D:hwC­‘Ž+qb4Ç"¬Ú3•ÇvÎds£‚¢ˆ((°.ÏôÕº|Æ.ž¶‰Ø çLWXŒØ.¯7Mà@4„H'G±s˜!#El–X˜GvÌä©ÝÓ¨{€•5u#»‡Å/7V¥“â…5u,·+ ±ïÀhóÂþÉ<Œ–Ù XR±5uƒ ˆ}Æ I:},¦iáüê­œ5yUY ˆÉÛWìÄhŠ%Ʊ„m7#QAQD T»brQßX°†%ãö¦mdóP£:M çè ˆÉ_öE¶Ëþhå¡(6þˆÄÞÃbïÍЙ¬AL=0:¼°n;‚ˆíòÉi›‡{Œ.]~µk1ßâ£Õ[G, öµ}¢V5Lå±3ÙÜVNØJo £‚¢ˆ((Ï7ýáN©ÚÍWæ®e|¤sT4íæk`ìk>½‘VŠñÊ¡šÅE¶¬ÒãàX~ëXR±/+±¯ÀØê†ƒÇÒû õpxžÊ\;‡Æ81D#ü¾¡†Gw¤·IZAQD H| æ+g½Í…5uýN»"…-qžÅ½ÁúÕKƒ&æÅãF& &³-ß'ã’rÔ06EÓÛ‡QAQD2Mó(æ‹ÖXˆ…å¸vözWì£Ó³G´©Yr_‹⸊½|«v5/5Näè±ûYZÙ€m‘µOÑ9|x?´ÆÌl˧ֳlÒŽÃMÒY%-"¢ ˜g:=›åsÉô\2}#e¡(í®‹¤¦Õ qÔØF–Tì%êÙ£¾ÿ訌nB`œ¸U{jL c[9+=+шˆ((Ž‚/ŒvÏaAY×Ì^ÏñfÀŠB¢ †…ߥ. ùcf ÒŠš †1Þ$A/"" Šy¤-¨QøÔ´M|zúFÊB±”–t‘Ñ[bf0Òò„ÀøèΙԷ™¥ -Õ‹ˆ‚âè¯ùéôN¿‡ONÛÄÒʽ´»N&û“MŽæÓ€°Ø ¼ ¬:2Pn5p°0(·Øl^^bi*k"0ðØý-ÀN 1ͯµ 8b€ò:5ÞšsäT­–çL Pœpμ¼ ´eðóè£ÁyJpÌ~æ2f“Ž×ÚA¼à( (¸Þ¬Ʊ?Ä j‹l—5uœ1i;OìšÁÏ¶ÏÆ‚`õ"ÅQÉõ-ÚÜ3KZXYSÇ9“·¶=ZbÛý§_ÎÊû¹ß;ÀÏ€;Ýi(÷$à€Ê>îã_®÷ÿ4 ³ÌÿÜAs,œ©9å&OW á±3€_¡q°7%(÷º!œO 1M‹_ÆëjM^9ð÷À‹ApÍvù¹ð>¬ž¾8„ã^Ü|;MÛ,¦f5Ù ×à¥CoÇë_ÓR|¼×Ç¿3û»ˆˆdššž3 Ãu¨ÓÆÕõœ9i;e¡cáCÕP½““n?ˆib} ÓΦbš¦WG&Ý?<ÈrKƒç?%éöfà¡ ÜõA¹S‚û]Ô&ƒwaúÁ=”†}±x´`³ÓwÒJ «Obš=פ¡ü¿`šÔS=Ô¦9<ÛŠ€z H­Á¹ô°.CÕ çLm/Ÿ!éËW÷ñy4ø,pK÷EÓâÀv}‚‰ˆ((fD|Ž´%û¸eáj&™ug³0uÉ-Áy¢çƒ/ß·’nxø˜>i7c,ü¸iåÞÖKH\<ïú^Êý¦Éù«ô¬½¼;yuÃÜ/aš–{caúQÞümÂíSƒÀ{"p`˜å¯òa¹ŒoË’n{¸x£—c÷,ðoÀç[ƒ ÷Çr¸ªƒw o%ÁõÏçÍÁ îÀÿ!}­DDòžšžÓ¤Ë³Á‚ËflàÛµ/3.¥%ÎFóÐQA0Kô,pn/!1Q+¦ÙðL ØYîLss¢§€óz ‰Éåþ3=›È+ƒ2\‘~þÏÂÐÙÀw’þo¦ïÝp…óàt=¢—€÷BžßàØ}78Æ÷0¼.‰.§{ßÈ[G’BÜdz°_ξ©O3Å´êpª"üKíË\={=ÛËæÈW'…£àÓ¤>ÝÊoÏ1øZ”¿§{ôöà ?Õ©FþS‹™h%07Kûíë˜&ÖDWajG»/p¸¶L Þe¤^c÷,ðÒ3µR)peÒ¶ü88?]Cfìêå¼X©O5Åaó¦h„%ûøá±/p|žlÕ"Æ•ajwý€Ì÷³š€i¢Kô]Ì\‰ƒq+°?áz8?‹‡ðkIz\ÊÏõ ïŠ1µÈ‰~Œ™öf$œÌN¸þPpLþ„™w=ûà¦Ã·0]1ýž}1ED%u^0}Åe36pË‘¯RîÊäÄÙ}©ÅLh× <˜…r ÂbÜAÌÜzƒµ3h"ÑiYÜõ˜Õt–ocj¾º„Fèý73étœ‹™Kp¤>®I|[a¦wŠo×ÝI÷¿.ÛЀ©MÝ›p[%ðLm§ˆˆ‚¢ !$zWÎz‡ë欣Øv陵vçÓ}@ȲS3´ éú» ½óŤë3Én?¿ç’®ÏexMœçrxõ™þ.o`ú†fÛlzvX?Bo¥—¸?Ð}ríŸÓ½iøÜ^νá*¶Ñ}n0£ãïЧˆ:zbH¼jÖÛ\:c#-±P¦æELÅØ¤ë;ÉΈÍär‡³BGò(ç ` ÍÒ>|/ézITÝ!>_OZwpΙê¤ëdfÇT$O¦}W/ÛöL0ÍæW7d`[Ç îJÐty\ O>)TªQ×·h÷>„Ķ‘ ‰Ðs®>k„ÊVöáÃ:’} ~ÏÏ¥{_Éwé}]çí ×?M÷nét3ð›¤Ûþ 3¿b6ß_""9C5Šƒ‰ŽåsÍìõ\\™ ’®/À ¨Âs˜t}3Ùðø”^‚‰;Œç[…™:&WÕ¯/^›YYfu·¡¸4é¶Kå}ÜJÒõÏc½db±ãݘÁ-«0«×„ÆÕED¤€¨é9­±çOÙÂò©u¹ÁL¨½3áz¸0 å¾F÷LJ1kò%0œ›tÛ³¸ÿ¦`&ßNô»a>§“ã§óÛtï—éŸÈò6|‚ž}%—¡½·Ë¼¤û.¢ûÊ:éö'÷‹Œ;³&tE‰ëòlæ”ä337Ðá†rm’¼&zö©ºšîSædÂ.à÷I·]Ïà'«¾!)0´cdËmÀø¤ýùëQ~J·ÓsJ¢+ÉÞDç!zbŠë2¼ßÇ,Q˜è‹tŸóQDDA±Ðµ»Ÿº… ‘Nb~Nöe¿îÍa1}ªJR|üI˜f¶c‡ðEš¨³VnqŠ¿€žËÈ=@ö&~¾ ³"M¢»é^C;ZÝ…™s3®"8ve)>~Iðå˜!”}&ptÂõ}˜uÉ_ìçòBð7qPËéÀâ ï§ké>éw<芈((ŠYšï¬IÛ9kâ6ÚÜœmQ| 324ÑiÀ¯0ó,öÅ ¾Ÿ> ür_ü¤gË™Áó- Üë0“<'Η¸¸% ûc ~k‹1K÷%¯+½³JÇpåCÓä[˜Á#‰NžàØÙA¸^œœ3G!|%úàTÏ©ØÛåäàï½Iíê ï§VLÅF}ŠH¡Ò¯ã>ø¾EØö¸ z %¡mnNïªo`jƒÞßîÇ4§nÅLE33ÝÇEÀûî?=ø"~?Ýk›ús}¼ŽN ©Á¬Ôò8fpŠ‹éÇøÌ€€ä,f€ÂÖ4ì‹ã1ýË’«'Kƒ²#Iÿ·+Ø®¦4”d.RýeaÂ[6Ýì‹“n;%áœy3» ”÷ýdÒýgç@K eœ‘p};fRíTÝÕøŒ•Á‹L.Yù&fmìŸ!"¢ (‡‚"Pl{L,ê êå|ÅëLgû_ïK¸½Sƒs-¦¦ËãðHÎdAxÌHÒýÀǀǒÊ- ¾Ð?‡ÁìöSnfª”ÇÒ´/–—T­.Ƭ–’'%…©T|m‚bK´ Âu\)pUpèØ5cšðSZçót¯E¾ÁM9óf%eÁõq˜æ·ex_ý³RË×ôÉ("…FMÏ„E×Ï›9v·`jkúªù÷ó…ÿ¦Ï×/üÔ›1M×ôóc¤¯r×aFß7Œ×=ÔÔ†YqãÔ €d»üD±:g¶aº Ü;„c÷Zp¾=˜â9S©µëÀL93X?Lº~©÷­ìíx¥zün¤ç.MÀ-" Š’Wvcš”Ïžaà¥Ù^Å4«ŠY{x¨‚p6¦ŸÛ@Ík€¯`jÞžÉbî߇iÿgLséuÀÞ?g1“LŸ<Â9³ø2¦™ú¥A”óYL `Üc˜9+ëIºÏ:“¡MÍ4”0yðÃHAQD †åû#?á˲ ¯Ì¹ãùÛãÎc^`jqQ?/3õbLŸÃEž´¸3²ø• 4uf ÜÚ .ÂL™ BÚ† Ü¿Ò}ëpL Êëo)@ ÓĽÓŸ-“&OŽbèK:ôœÛp$‹éX¼6;Ó‚/1´U\UÁ¾·1µÉC]#<~^Å'ßAêM÷ 1ýqã}“ÁtŸ‹™"Ç Þ;f'á.¿¡Nßb"2úƒ¢ˆˆˆˆä5=‹ˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆ((Šˆˆˆˆ‚¢ˆˆˆˆÈ!ÿ.kSfÂËÌIEND®B`‚chef-12.14.60/omnibus/resources/chef/dmg/icon.png000066400000000000000000007372021276456504500215030ustar00rootroot00000000000000‰PNG  IHDR+ƒtEXtSoftwareAdobe ImageReadyqÉe<ziTXtXML:com.adobe.xmp ó_hIºžIDATxÚì½ $×™ö¿Ì¬ª¬ª>ç>1f‚ ðEr—ÔÂ\Ò{)$:¶%‡$ÛaÅÚkÙ’m)l9¤Pز7Ö²¼ön¬ÃÖz%-÷²,‘Üå’X ° q$ÀÎ À˜ééžžéîºÏôû³òU¿zõ2+ëÎÌú¿‰œ¬#«:+ÿýß÷þƒ9Ž@ @ ’ ƒ@ @ $@ @ €@ @  @ @ H @ @@ @ @ @ @ @ @ @ H @ @@ @ @ @ @ @ @ @ $@ @ €@ @  @ @ ,&¬Eù¡Œ1:Ûp‡.@ ‚à…¹úætH ¦Bôýˆ?  ü+¼-•5aqÎõ¨k@˜¸‹Øï2:º÷$ñ û,ä:ê䟛h™¸í7íßâœ"ÌݵÕ5t½ÎœüKkõqwñ‚a€D Â|¿ßÚž3ŸÇq%î4à$‹ˆ²$þ­ÝÝݹ՗a³É÷b ú-ô„]·ù|>~@\î{Gy¬[ÚÊÚ},‹$Hˆ¦!§3\Òohž£!6½µ!­û>¿··×GTVVVÚ3 â³,’<(EâÆž6Ù°#Êf±†aDíwOûï°ˆŸ÷0û¿H3µäb{Ý–Ëå©ÿ¡\.ç$èÞcCøHA"@[yÜ}Mˆz¼…aBÄ_}ì' ˜ éwŸs²o!¹/ ¦p°ÑP«Žv±X4¤¿ï ¹¿#ßñBxàûÉøãžïÁ×&|\c3@Íy&†…Ø¿¹ü–Q¿k‚ûÀ"xnçùÛXÌ®uF÷>!êd—0ŸãX­V‡þŒmÛNÏm˜¿åhŠhIkU ÛPd!º†a*/’kÒßGþ½kÖõ q[ïü2…ô÷D ù_^^Fò¢—cšfOD€«x_@Žå²©’ìIô¿)N÷1‹êß°-§”ÙÕiý¤ŽÕ¼fÁÙ¬~£l7ft|™æïsΧyý³E¾×‰è¼Ìg3™Œ3£¿Å|¸žÜ~À£Ñ î/ž÷ˆÞ¶mï=\Zž¿ÕòÈ?E„ŸÅ )€@ƒø·Ûm†áÄøžì@‹×3ª¸]—ëøc|ô#ù/—Ë)þ¾o¹m»m˜¦ÙMàŸ5=Cnz†œ ýNXã¢ð°n@ÛBgÀßp¦),$°kPW‰Aûä(»íLs¿ÇÝvÌc<í¿3jäèŸg“ø-ÊÛQŒ|ç¼i£±TûÕh4Œ1Ï¡ä`©çXzŸxßA*•ŠÓØ;³ë¿^¯Oó¸†)ø×önw׉D×ûˆ¦÷ÜðÞ7<‚ßSDšû˜î&&¨Œ6¾àfÀmÏ¯ì‹ !€@@üÅÚ#ÿ=¤_1À&ëJ¥’áë4'ôYþºÝjµV,ËÊò×× …ÂÒ[o½µ¶³³³´···Æ_[æË*ß&Ï¿{…oŸåßçë _r8¦x ï1½Ç~©~N~Øßßóyù¹øý!ÅÀïõ;b$ Ìüï ú›ebû ;Çâï £÷èömØß7ës;Îõíwì†Á Z“¾ÿf}‡ý}~×9ñh!~ãÜ(cŽ÷º,® b/Âó›Þãº÷¸â=.ò¥ÆýÇ=î'VlÛÞÍår奥¥µµµ2_vøóR6›Ýã¶ .”¼ÏVùó_7øßlrûˆßÝò&ªÚ’€V† Ä*HˆŽ-¡B‰¿ÏµÊ:§“YÅb ¿Íw®Ùl®¦R©¾>P.—×777]¾|ù_¼uëÖZ£ÑXçŸ[Âmø’ãŸG‘ í}$÷¦ôÝ,Jר¸ß; ™Phtlîó0p\<.šõç’æ”F}ìgüÆýª>ïLh ¿£Pt½èÜFã7ÇEñovEoV¾Á׸T½¥È}Ë¢iš»ëëë;«««wŽ?~ûÈ‘#wr¹Üm¾ý.ß—"ÿû%¾`±„~‡'6´¤Tµp`ßšD€ñǨÞSQç×$æIüÙWŸ«0øg¬B¡á>Ë ór«ÕZç×9¹?‰þ‘K—.¹råÊá­­­C(ð÷V³Ùì7äyîĦ1:€Ãâ¯l‘¹¾D”ƒŽ Nb¦rZ¿Ço¿G &½ïº}v?åmÏzxÒçošÇdÜm÷ ‚<îï÷óƒþ¸×÷$ºAÐN ÿ3y÷N&Ð~I6 ÓB–$õ|©r³ÈŸîqÿñÎêêêícÇŽm>}úÖáÇ·øûÛüó®(À÷¥À×eïsu!€A€Þ¶‚ @ $,ùœí÷Ö&7È©jµšã¤‰â™Læ ÿŽc×®];~ñâÅã?þñqÂß³,k•¿¤?ÃÏ9¦XXì/Œo¬Pë:¤"t[ž¥ÒÕ˜Äu7jö¸Ä5.÷YÒïÍIþ¾Yßa®ÃqÇy\çÃTa‰ü(Ç?;ˆ€OûZœ¦1Ká’ÆøÅ"Ô(¨¹0cI”£³æ}þÐD®‹¸¦Ð& Ÿû•íN¹¨vƒoSk6›®À÷û6÷+o={vóôéÓ›G½É?ºÅ·Ûæß¹Ãøã2ì¦ Hb€>@¼¾°" $slÒ!É?†åg*•JžÙUnHósx¬T*¸pá©þð‡'766Žqãzˆ/ë|Yæ?cu€5_.hàÅãT*üûº‹È¯Ÿ–àwíÉ3ÓAùÿI¸7}ÿ4ŽuÐ1UÿÞ¸3ÐÃÀq~뼜¸i§Ì{|Ü¿=Jˆü¤Ón¦é DýüÐØN  ž@l‹jÿО£À‰~×/ÍáK›?F1 Ê·-–Ëåþx{}}}óØ8sæÌF>Ÿßà_µ‰"_£€õ*žЄþYXØhH 'a±È¿£.øýâ51ûkyö_Gò'RôY5=(·â6jîú´ï—(V7×…~O*GyÜýˆ’íšÅþMês³šaö«r?n¿E­0 G‹ÆùÅñáí|ÏÊÎF½Ðà´í»¼©¸ ’\c„@£ÑÀÅáÄ¿]©Tõz½Â_ßãÀ™L ¸ñ|àÝ»ï¾û:~ƒn“û£·0r€ûeL+àë&ÖÀöÔЩA€>IÛÛ¶ç«v[\“@ $Ä‚ø#Ð`"é%Üů·jºX,.q2¿Î_;ÊŸ#ñ?Éÿ™óçÏßʼnþqLàä£l,äÇ–ÉdzÂûño¨žw±, ˆ:ê :ë‚h² ¯‡1 Qí>—{sÖ@Ü"$¦]¤p\ÇvV3Ìq›uLºtþ5Gœ\ÿ3‰ùÿQú›:@¾&\ãR­V¡P(´ùkZ­Vá |»mþúæ¹sçÞýð‡?üîÉ“'¯q?ô:ÿž›ÜÝæ_‰ÛTùoÁÔ€¶Çò»"€p•;?—µ“.@ "ÿÙ³î Cª0\Ÿ#U*•òX¸У|}rssóÌsÏ=wÏË/¿|†üÙlöÿ¾Nþùì…Bâ/ò÷Ŭ¾Žà‹mðï q@0>íA³ò*Ñ×ý}9-Ž×vœrÝ£Þ‡}VÇ1é©"“ÞçQ &Et†?Çj¨¶<~FùˆR§¡8‹ ³8¶r=™ô˯‹¯@ Kþ9áw_C \.»…¹oÛ( nD›n㡇ºöÉO~òêÚÚÚUþú»˜À?w‡û¦tùw6½Ù~wvL¬¡“*À¼Ö‚Iæ$@NBÒÈ¿öFLø[ådü ~’?Æÿ{Ÿyæ™»ù6§9‘?ÊâšmÛyNøSr~.—ë~7Võ_ˆò ¿ü±Ü@w?GbÒ-ÕTGF÷x‘ï½iEaLêøªíÛâ*ÒÌk V|ñ›áõ±V$ļÇ߸ٛaS@Á¿Y$ N×lœÈÿ¬ë¸““~Ô 5@î ?Q¸æþ«ëG¢ €>lj.NüÛÕjµQ,Ë|»]þÚ&÷?o|ä#¹ò±}ì2÷I¯ð×ßåßy‹/»Ùl[bû@·3€ç/ Ò/n'©Q$@NN‚È¿z°¥7ŽK˜çÏçq~^N_¸páÞo|ã÷nmmá„þ7‡8éÏóm°ª¿$‹ú‰P<—¢ÈŸ\Í_ù—‰¿x.ñšŸ>jy¿›ÔüQïË(åÕ ã O£E™ŸNBÈbXr4Jˆwf›¢"ˆÕéå>R€¸Ž—“´ÑB”泌“ÿ$¥*ù—Ûª‚€H ~p Šœø»ÑÜ×å›´êüy‰ö6ÿæêêêÕOúÓoßwß}ïð?y#0e€®´¶¶VñÒÜ™™üC‚‹’0{Xd¢ S$ÿÌ+p‚Œ«û¯p~˜o’Ë{¿þõ¯Ÿ}饗îåÛœ^^^Æ4Œ Àpü!Ñ— ?’x÷¢õŠþáÍ%çý‹Ù}õ¹\(P¼DÔ'Ý'\Wp‘˜Tñ¾Y¸Qþþ `}æQ˜Äõ3©ãæ:‰*äV¡*ùŸ„0/H3{l…|ãe0(A¿µúBIrW)y¢Jt¡j6›&ÿ¬i¯œüÛüsKÛÛÛ+_üâWy䑵G}t%“É,ñïÍær¹›¥RÉâKéÀøyY`ûnSò‹H Ä€ü‹*¥º·¹±Cã—­Õjë|›ãü5¬êî±Ç;»±±qmÛ'óùü!þzž¿4_ ¹UŸ ÿhteb¯|9@ GÈ5äü.Ù±Ÿ”àG”¥ÎâÁ"9>Ó.87/çj^Ç>j¿M7[»h×xÔ~s„€E¯OH&ñŸåXºˆ€:9#û‚~EÅc0ýL\ Ò³ÿ1A…‹À–——­J¥’ã>hŠû½v±XÌ¿úê«Ë7nÜX~ôÑG—ï¹çžÿþÿì5þ¶±³³SäÛ³Ó§O7åÝ&€0±û‘R“$ÿÊs«P(,e2™ܰâË=O<ñÄ}O?ýô9nà0äÿ8æús#š[ZZ²Ä ½ùÇ5^Ÿ˜÷FVTþW[ý©€P^eVVfÃ^/aÉA¡ÊarÅå¼òIÌ”F- `žß1ȾÒ i¢Á<÷;NDzÕ´Ϥ¢¢rRõÿøûjê9 ê’C~ë|Èÿ¼íÖ}X¹FUŸ ˆ¿N @ŸTHòñ},ˆñýR©ä>–k`J€÷¸‰Ýøû»åryƒosùŸøÄ[Ÿþô§/òÏ^áâj£Ñ¸Í¿¯°³³Ó¸÷Þ{[ònË뤈”0{Pa,òÏ óéSŠUþ—9i?ÌŸ¾sçι/}éKï¹téÒYþÚ]|9Ê >Vøwsý‘Ü ’.ìÆYTÿ³þ"ô_ˆj qs‹Ïȳÿr@4à* ã$ãˆêfÿƒ„¿}ÅøÌ²Oº=‘R§ëØ…žA×h”ˆõ4ÒâXyzžN³îڕ߸+"üÉ$¾q$þ‹Püož“ÓýÒ2…T#„¯#|M}‘¦ŠD_Dàçñ½l6ÛàŸå.©•çÏ‘ƒ¹©Ï?ÿ|vcc#÷ÙÏ~6·ººŠ_äFÄòõÞo¼Qãþ´ó¡}£j唊 Œ~ýSaTò/‹Ü9â=4\»»»+ØÆ«3/^|Ï¿øÅ÷ …sÜ(žâFí0ÿÜ_§Òé4„_&þ:‚/È¿x¬ ýWI¾Ø^|Ÿæ/ÝA¤;ª9Ìó©ÖÍØŒÚ£=éÕgñûdg9ê$q$Óq.üD«¸´Q£1~qE¿´9º'»³LˆÚùÓ¥ùÙQU‹h("ð9 b‘"Z@,øßÖ©T* þ'ŠÜg¾U­V¯-//¿õùÏþÂÑ£G/ñ÷/óe»ܼyÓ>ò‘ð?ÛfÜŸMT$EÌ@‹üËkÏ ¦±Ø'ÿǸºç¥—^z€“ÿ8I?ËŸŸ¶mûß>Ïaᬾûky¶^%ú~!ÿâ5q3«•þeB$?†,2H†m7®‘j±6mÍdÇ}ÔÈY baÿ^\ˆô´ö3Ê)Óüû“ŠØ!¢O˜Ö¸âyU!`ÈîÝ 1)hÕë}L‘ ¶â€è ûžò÷ÑyNÕj5Îû—M¬P©TÒ¿ó;¿“þÙŸýÙ̃>ˆ5´Š‡Ú)‹5îSÛÛÛÎç>÷¹žH²3'ÿ¨Fz¡N©r¹¼Â×Ç‘ü?ùä“>öØcïÍårg9A?…µÒé4V:5‘ðË3þ‚àËmýtd?hÖ?ˆ é üù9“tZF!Уp]ÚÁ¬È$åsÜöÙóÛnZáöaEŠiG¶øÝWr^oØã§+Î9‰{všÎkï ݱ÷;GQ#4QèèA˜-I‹KáP*>Oqi±\l/ S#ä"Õè·Ê䟋®²*|V$öœø3îcÊìÍæ>´Å_O}å+_IU«Uóø€ÉßG?ÛY[[Ûå¨8p€=þøã²” @ €0ÍAÙQ ÿaø?†"5›Í47„KÜàåïßÃÓƒO=õÔƒœä»aÿü½uÛ¶±ÅŸ!*ûãZ.è§ :⯆ñëfû‡™ÕŸ¤“;¯öra ÎÃY_GuÞÇ$ˆdÏÛ ú¾89Ì“JfN¤Š%a _Æ¡[ýK.ùOÚX°ÈûÕc3l¤œº½\sJØ#! é5p;!x5ºŸÁ”Ü.ŸÏ›œøgùsƒ.æ“O>ÉpûøÃ÷Û¹\®ÍŸ;W®\©ûž¯~õ«ð·Îqò’/ëü±ÍI¿aÛ¶;ëïZ9%@·– « ÿ~áüADx’¹®³ åžÄ7Y¹}NÒÔ0¿S.Dä¿ßyçÞ‹!˜´ý™ÄùÕ‰q¸NÕý\„Æ‹N\)Â#þö>j÷é0û£Ú?ûV S\Ås¬…%Z_ãä˜%›æë«««'¸ß|î‰'žxàÂ… èWŸá¯ZYYÉÞÿýÆ /¼ €ûðøãÂW§+œ@a’†Žü»ÿËåòþøô+¯¼r?VûGƒÅÔ)¾^ç¯Û‡2и C)ˆ?Â/Ü_m×7ÊÀ5Œó=n.pTòeÕhÖdn’m “@êfIÇùq!TóÜÇ8’ÎaÒƒâXD€øC·1dЍw•ÖWe{Ý”nbJäÕ~õ¬ÐF1±´´däóù4m?>Áß?÷•¯|åÍÍÍ÷pÿùÔÊÊÊ:ßF‡"€À¯ÿú¯ù'@˜ø ͼP&&¾Â‚%+üµã7nÜ8û{¿÷{÷sƒu7b.ùç† þ¢Š0rª…þüŒ©Ldýfçž«kS4ÏM®>;ëâòß&!N¼ÚA"JEœƒ89ý“,€9+Ág”¨…yÛ…0ö-®¢Í '‡XÆ¥Õ_”Ç%"ÿóµ?r5ULvÉ-­Õ¨W\Pð"d'ÿX[kþ“(à[±X<Ç_;qäÈ‘e¾àÌšqþüy÷ßÿý EŠ @˜ùGrFÉ#( Éòå0|æ _øÂ}|»û³ÙìéT*…ùK.ù×µðSsûe…Ô¯e_ÚþÌseò=Ï€¸‘zÂl®ß8‡l'ázj×9(•Š@˜¦½‘SªâHA° »0š¨¶ý“Ÿuº’D¶ººšæŸY[^^>Ùh4Î=öØcp7÷¯Ÿ½¾¾¾zöìÙ ͸qã{óÍ7qößåu¿ök¿FüŽê@4H3Ñãá­QiÌaË¿—^zéìóÏ?ÿÚÚÚÝétú(-ŸJ¥,ÝŒ¿ª‚úåS øƒ£ ü¬$;QÃö''0«ó6 1o÷Yœ¯ï0ûO÷/a^¶Ç/ ê×d”Š{Nó3II͘öï û¦Â¯–ÅWá7‹ç(´Z-#›ÍbÎÿ!þ~å;ßùÎîÝwß}›ûÝ·Ïœ9S\^^n\¹r¿ª}ìØ1çõ×_g>ø ÞTn{@²4H!"’ù¹È/ç°ªÕêúîîî]ø‡x_>Ÿ¿‡¿v,N/qâŸR«û á ˆðû LäÌËÁ„; ³óÊ'ò@ä˜k”óL3þ„(?¢Kþ“2…b¼²ÿ+Äy‚Lž(“£°;€mÛ÷­ó™Læ¶Û~úé§ÏòíîÂn÷Þ{oúöíÛÆåË— LÀ¿…"€ø³t·H Œå´K9úY¾>öä“OÞ[*•Îq…íþVS€ýWóŸt‘:£fÖ~^3ûA¹´Q9OQqêÉ©#$EHâµë¶ûã¤Ûf¸Ñbò¬¿üòüÅãaI½>‰A-Jaqºž´BÔI]§ó·éq"D.çOÄÒDa_£ÞZvÎ× Èj‡ 9ô_ éÛc @µZõ´ÜÖÛÜ·^_^^>õòË/ßÃÉÿÕƒnð×+Ü7wÄß,—ËÎÑ£GeƒNé„PÁÏ9g:ç/)nqò774wscuŒ¥\6›5„Ã][?a”sÿâ4õ.žé ,uNÐo!jŽØ BŸäìOÅž÷9©êó¸¤mKε(®9:§Ñ>W„ð6I ó—¡¶KÖù‘òúÒØFýiŒðÞ7ùãßæh¥R9óöÛoŸY]]=|ÿý÷gŠÅ¢Y( ŒØØØ0n޼ɤ(ÂÈFŽ•J¥,7.Çžþù»3™Ì ¾,cÑ?¹Õ9Õ˜ ƒ¦PÔ€ªS +¬¢O¿…i uæåÀ¨Žý¼ZêÈCœ‡q RÍ¢ÀÞ8ç}VçHúd'ˆÈæl‡|ŸúÙÕ8ª]{*Ñqý qÙ‡Qó¨¿uQ¿¢•~þ§ðsUû¥+Z-¶é´¸=>ƈô¹—––Vòùü‰ï}ï{è‹?qâD>›Íb}.E€cÇŽuEØO ¥@a$žqóüÒK/aØÿ]Ü@æ‹¡ÿAyþƒŠÿMj°šÑíkÔjQwš)J…ìa±í¶×[.óûí·O­®®\^^NqÒ2K¥’›ð裪Åé†#@eÐD?Qëúõë¹A9É_?Ä PF.ú§S=uѯêï YÂANÂ4 uŸ©}ÈÿxÎd\œLr† „餸Ýcqï=ôVû4ˆÜ‡¹üÖòöÂçÆ(€t:á8°¹¹yʲ¬#‡ÎÕj5‹/X¬Ûäk£Ñh¸‘Dú *¨ !ŒacívÛážtéÒþô7>+©TÊ”güÿ09LUÿ0ÆuQ²° +`D×+ó&§Q>~…?éº!g|PzŠ òwßD½Žý|ÐAã·_m,]—9Ò+oÛ¶½²³³sŒûçG8rÍf³Â‰?vppmY–ør\·¥ÇÔ€!` ÿwE:Înooáäÿ¶#Å7åû«ÆOGšÃÎô‡ ùŸÁŠ2ñŸç¾ãFˆÃu:‰}§k@ml÷º&$g¬]äs¶¾•ü¾_á^õ»äÂÚ¢¶$0 à¾ù¡Û·o=räÈ2ºW¯×] Õjµ«Õ*fê ÂO¤ŸÐ¥ú¿òÜ5ž±É—J¥ÃܘàS²$„‚Œ¥ŸB:-Bf–2ªƒÝ¼:~÷Ò$®E" ‹E(Â8Û„èž#¹[Cœgü©µî8¿‘îI& 6(}V޲Uë`GÛ¶×Êåòáååå•t:j6›.+++V£Ñ00Àã{T@!äbnø?>¬T*+(X–µ‚ÏE{’0ÆÒƒÃ,µe–.š!J$‹@ˆ a#GäŸ~Cr¡kñµŽ9I[gyDñÜÎò÷ëüİ߯PvPº«Ü?ÇL€߇Cëëë«Ü/G ʼn?Ö@k˜÷Þ{¯ E @ ôÛ&´m(`d¶ÜÛÛ;Èí½Ö§L¢"©3|Aƒƒ®Oªx,D5 ÁR`ÍF¨†Ü/W+ Dfžû0îìnÂGÝÏY; q¨ª/ÿÍa÷wÖæ0HãHjâ@lH(Šÿ5¦Ö ±zúãä<íú¢‘¿¿'üc™°[GçË~)÷ÉEW›ýõL&³fÛv†¿ÕjµLŒÀ5Š˜ àCüÉÀ’@ èl˜›€kTWùr€?O«Äšù÷óT‡…—Åvf“æ$ù$RA LúšÒ gq%ÿ‹$@‘=˜ì1äÐvô³Ö}Îóő诠°ººjcŽ‹‰­ù»ȳÿ@ €è¨3ÏYG#mEÖ¸YAc£æý*¿¶à >£äN‚üý.œ·Ùíï¬c’E ²c„iÝ3ê}ã7–Ò=ý§òÔÎç…U€:@ 5ÏÀØ…Ba™¬_nÓ¤CæÇùŽaBy†_þlœœdr®q±-t]MÀX’:J«_Âülë8öuÑCÿÃÞAû7̘η•Ûk¦R© 'ùËY€ ßÜÝÝ5øZî K ›•¢‚ ?EaånDVùãtPîÿ°y“0 5;ì~¨‹ÇT‰ü±‹ªÃG×@ {m»3«Ùÿ¤œëyíOvÔèÔ [¤Hõ¸°åßÒêê*¶4qÉd2n-€\.‡b´òO…I ô†ÔX³ÙÌW*•Uþ^J7ï‚ £„1Þㄎ D€8U,Žë,Kˆ]T®¸E©©§ó@HAöK™£Ù’ÿy¶I%Ÿb´âa¾ÏoÑlƒyþù¥¥¥\6›M¥Ói—ôÛ¶íŠø˜/~]h0 €@è!]ã`šf~www)•J™î…ã¥ÈP"ý"?@î—*;Aùƒa CäÕíÂ~fÞàÝ]æ;JO÷¸8ˆÃF¸Ìš\%%„3ª×I’òcã˜Ú¤»HÀ˜ÿ¹ðs£^(7÷û,Bùǹ‡èÞîX©þ­|üýj(qî¢{O|7÷ÑÍJ¥’[^^ÎcþµZ5päEÜÔK0"$&½fp£“¯ÕjKhLF}ŽïxTcœ.W:ñV™c­V+›Ëåòná?œõ·8Êå²!Ò`?À €A6,:c`V«Õ•R©”ïØšñ üK¦ÂJå;ÕÈ¿‚¨ 8„hz"Wt\èØášR£öˆDF&Qœ™0øÓ-+¨¨¶.b;T*•ŒmÛÙ¥¥¥t½^g©TÊÍùÇZ8‘ç¥Õ “KönU|lùç)—Ëy&YQÔÌ eÓ/Ìz˜ª¨Ã ~D#CÎ49q%\t®t&u é„sg7fÄ!Ýlíi˜XÃD òqá¤?cYÖ°²Ù¬›ó/D‰üË"ù'€@è³7¢@©TJW*•n\l! ê0lÀAP—kv@ cPÕ6€äÄ"ÿ‚ÿ5ä7ƒI××lîK:ÎÑ>‡R…þPçk˜ÎYªÿ­V+ešfn}}=>—˹"ÀÒÒRʶmKiHuH  \˵8Råry™[´°d}ØYý °§0Æ0¬1õ›í—]!B9!I¸¿ ºdó£ó{âþÅóõßf†œÉ1ElHs»ÎyÎ퀄ߋ$_%þ $­V‹yNa×8H]2…Ba…¯-a„DåÑ0Îä¸3ºÊ©ƒB u€n¶"jU¥G-¬8¯}_òOK"üBﵤŠèq½¾¨[ÙîI ~°AQ³£Üƒê÷r¿[f8`ãŸÁÜŒÀét#‚ŠÿÑ &€°Àƒ:“sÖ±S ¯‘L¥RYæëÔ4Éò´˜ ÞÅr €ºÍ¨… "ƒ„¨†aº£ƒÈEÉ-¹Žâ˜Õ} ;±¶CUÐw*)HøsÙl6ÏçÝŠÿ€o¥ÓiW @!@!ÿ$@ BÐà>ÆÐ!¾äK¥Ò274Ó4¡Ñhô9•ªcdèDAÕ¸Ëi ²ñ4käÈ ÛžpÚš_M„¸9ItÌfM°F}›Ç5—‰“:‡Q¼ÎñˆžÒÂ8vpP•s"ÞÉ9ÿQ;VQµ­:r®N4 ò•YýPBnÇ£R©dWWWm˲º¶m›^þ¿ÜÐ òOÐ'HÑ_²…Ba‰?5c3Ò ªÎÄša•„…uèÕ¿¯k¹uâBŽ 9hqr"£,-BtD\ì…j»ÉÎÍ—ØèÆFÂìïÅQû¨Ÿ‹šMŒâut ²_ÃÔÄ`qÂÎÎår™l6‹)»Œ“—è[–%„]ñ?ª@È—üw „`[‘½½½%Ó4’~94Þ/?TgÍÏ`c<Ã) Úß g4¨,Uãg;H òOˆ‡@÷Áâ Qœiâµ$ÏÖ«u²F=žƒ&³ähZ|Š­———³©TªîÄß+Dü $tFµ'È3²&7,¹b±˜3$‹& ƒS?B>J¸û0mT„-&Á!£|nrÐâäëÒq’Jx¢ê¨Òl:aö†ÆžÙÛîqÒÒ}Ì›•X2l7«QÏ‘¦ Ôjµ [b /ÌýÇM°&€”à·‚jS$CÀ …‚ÅÊJ©TÊÉ*'ÖÛ³4ȸ c ƒÈ{˜V+ã8ñQW¦BLÑ<'ä@ÅÃi[Äk3.Ä"¢Gô£Ö9'®vh–¡ÿd³gg¯‚òüqõWüZOëŽù0¶0J·^¯§9ùÏcñ?¬àVòæà¾<ÖÐåþ«5HÕ#€°h†KZ3‘ Àí†U«Õòܨd™gmTÒBF˜Ö ”ïöoÆ‘XÏb†fQƒa¯’u/%ùšŽÛ}‡{qQì‰Î$xÐï™îþŽRg'çªÕjš¯3ù|>Å×.ÑGk`ZÕ t¯:i°ï3 Ün¤*•ÊJ£Ñ°eâ?jé0yüƒÄƒa ¢_¿°¿!.3¹ºÂŠDþ§CšãÇ8÷ §{eöÇŠÎßæéD#šýOö¾GµÐ^ìUÐëÃFÎò{Õm[­–ešfS8 \0úO†–ÂöÌsÐE KF#£¶üÁ4€Q sY Šæ?h›$2štE ³ t„hÚ ]ºçqO¢t½Ïj_(ï~÷Œ_Á>Õþº¯üŠÊ¯‰ò\ÞÄÆýc Œ`˜€©( Íf ÐGù_0P A¾éûŒ’ÝÝÝn³ÿîóf³™æ°‘ìóÇ̃ᥨm©à‚RȰDþ»F ^¯[P,s܈ôåÔËÏÃVÿ%E`¡”¿ £tõÂåi:aA¯ÍCXdAB„£OãÌ긎úwæ'§3Ié ~¤/.aÓA3d„á]’È_‹×‘=%øÙ,• Ë~ˆ® ÚiKíº…þ®ì‹‹I9|ŽaÿÕj5…_L/À%þø}ýl6kåý“@X\ˆÙ}a¼P!\›Ífs©V«¹~¹Fºç£ô¾'J@ÍsŒÛàHƒ9@÷ä´Ùᤚ ‹¿1‘Î}|mÌ<¢è]׿U}\ÕßÄ_~Žh4)Nø3Hø;/&Fà’Ëå Ï÷×uЉtá@H¨Ó…YÕj ‹\@5lÓt3T:E5ÎŽ ÆäÔórj‰.ù{›?²ûÉ%ÿI»ýBóG-ö§ú¿jô€ì ‹È€V«•â+ì`Š×02€/r PÈßîÐÝM!ÁNï 7FÈ 'r+ˆb@nDÒ¢åß$ ìMê³ABA\B[ º~èØÎò·Ä¥Pj*»Ïã<‡!I8®Q«ñ3+$Q´K²¶Ö(ïDæšÜŸÇ0÷ßðÖn*@.—óËÿgDüI ,Äì?ëXn!@NøÓ•Je‰?N© ã0U¤Ã+Ñ nò Õ #¬nG3[OaƒˆÿèÄPžôë÷kx‘[þÍòsd''w_ú}wP‘@X«{]øÊj*Nû7›M;›Í¢ïÖø)^-]!@ „E‚’ Äjµ I—J¥%þ<åm3И zo…sØï‰Ûì? Ìä8LÒÁHÒ±œ×½‘Ôc—ßEîøÇO ¦ãš›Oˆ¾ýTÁ?Ìl~Ðw†I¿Å¤°Ööâ¤k{ ÿ™LÆýì?HÏ $’ˆr¹,Ïúw{ÆÃ.‹Ëü±©3dê¬Â¨ÅNü¢Â€AýSãâX’Jíóf!ÂDN5a²$–0ü}ã—@÷U|íÔ8+ê~OÔï£asÐ}¦-à8¾£ç»‹ç†ˆ@ò/G 𹦠N A ¡°è,¶“%‡þ‹ D«ÕÊV*•¼W9ä:ª±›¤AS¿oPîâ¨Æš@ Ç“0À6Îîïµ[|i‚Ól@›?n·Àÿh5ùª ÍZê…mhU ЬìA«²Ë×h•ï¸ïµë%hñ¥]ãïUï€ÓªñÇ·Á6_ßá`ÄkͲ˜™CXGš¯Wù²¦½F&VzÌ,_ì%°rù²ÏÊ­‚•åÛ¦³¦Å?jÃB²î:†ÙYÆ·*ÁSÓæuM'ÙæŠ?ëßN…ÿæ·o~E­ÕðýIÿ)€5›Ít*•JãD ñç¤lÛ–É?µÿ#€°xÀÐ~æI‚^»?+•J9ï½£(òð03¤ƒÂ¤äþ§²##þ¾Ÿs£{…ů·ë¬÷oÜ..-Ç)~Õß6êošÇŒÕ(¤cÞ×Ö$þn½”6´j%h–v ä½ŒË4À·¡QâKñZÅ›üù5þþFhân0õ7ì{s¦4Ÿ3ô/kWùÂw£YtŸ6 |ÑýÞîïVCg_ŒTÌüI°²‡!µ|RKÇÀZ>©<_–º‚B:¿Vn ¬|G<0¬4E„ ÿjÏp¿{ŽÈ<÷'I5¢|-Žë‡šj£þ]|îãî»Ô€êõ!·ÄYÿz½Ž,ü'Z~óm‘÷™’àW @~L†˜Bý¥  ÀWõÄ€-ú†5xÄAEãRü/I qú Tmö"Å"F* ɯn_ƒÊ­+P»sªw®Ac÷'ó·9—ædŸ/íÚ6ßîvg†_>÷q‡Mw6€ðˇ™^˜î¹Ö{‡=æ¸Ugï"4øR¿©ñ(Q(0s`掀iÃîD¤r!sà.HóÅ>xdÖOBfí=Ð7îQÜäÙuŠöš=ùwìÒz?zÐýª›äïʧÙl¦ÒétÆèÀÕƒ±% ÖÈårn¾ @¸ „dÙ=Ævvv°(ˆû¸Ñh¸­BZ­V¾P(䨖pR¹Ð:C¬›õ—ÿ®œžÅ|z¿” š•™îþêÇ¡¯sÔIù¤ªèÜÃí.«mìmAqã"T6ÎCñú|ý ÔvßÖn€Ó®ã”9'øõ–+H; wà5öŸ3èµ"ïêö*ñW?×÷5Þ gðKòm&¿×öû¬vû28Åw ÉR×5ùïJaNBçÀeÖ {äaÈ?dÞ¹c÷AޝÍL®«šttìÅ -º.=‹Lœ£´?óø Q½¢*RMkiØÚV²«ÞÏ~²Q´XÐkÚ©T ;¸m¾QðÄyòoÔÑ@!î"€bø°h¾R©Ø£é Â~Ã’·°˜blóãÞ“9û,“NšE!ÇX¢ßªb¸þhT÷ vç”n¼åë?‚ÒµoC£p©3c­nAÌ 1kï¹V &÷êû}ÛýÞ™¡¼À”ý˜íñR¢¤¡¤­ \óçì¿& mÖûYÃ{Ï–['¡ûùæ”ß¾¥·¿ªüÙS?K§>ÙãBöÈYHç™_Ë^#•I̽áœ4#*¿kÖû‘Äq+Š×©šB3íóTôÚ¯¥µ|/ ûäï¯ÕjœI¥RFµZeRñ?Ó¶mŒ`r1piT¡(I‡WÔMõÇÇÞs‹Ž¥f³‰¹C†&Ì`¥†> 2†:@÷·’Ràˆ0}¡eÑ/B0á¯Ü|Ê›oAõÖ[PÙºÕí7¡¶õCh–7ûH»Éúgá é5?‚¯’ô®` ‘y÷¹2qí›ß¯Éëïù»S~¤ÏÕ<#±ˆ¢ÝívÛâ> »çÿˆ“ý;½žŽG¨1ÖÉ0÷ tÐ,¿øŒ¡ˆBôÜÏ(Œ½žAŽÿþ±ì#ÀGð=£Ïï©ÛiŠYwI~[ùL`àì‹=²@`*´,t?ïHÂ@cÚ|iíÔ7^€W~«ûùÌ‘ÃÊ}? «g?ùÓï3#mÏUP‹”ùÝcd“ão3“Jþý«÷è$ºÉ²êû^],,òçFó%ÌÜ"€àß.&ID©Tê’~ ‘Ót¥RÉñç© |þIT~yÿ~uüZF™ &-g3N¿!ªû÷zQr€ÛœðWo_…êö(¾û:ì½õ”®üi·…øVÂo€Dâ²oýD_Ì䛚â}}_.Ð'|CCìÅ÷à /lPq?è¦{Ýö®ý7ƒÌgG#ôExBAÏßQfþ§—äì (@8a í}F àK†ô÷š·^†Û[/Ãöóž“´öX9÷Ón”@îèYH¯ßé•Ãs±~mqÉ~DËQÕÿÅ'ÔôÕa TUøëO{ ¶÷Æ€Fðdž'¸uDJ}µ$€d[å­n1ÃÈ‹Åe¾6ƒˆ·WQt A’+ôËN„ëËýMÕªÿbÕáQ… ¨¿¾Y Hº3â1þ¶yUć'¹ý_œªÿÇ¥ÃÔ®M¾]}o vÞz oÊ7 •›ß‡ve£oVÞɼB’e²o½…üÜçrq?XR$@ø½øNÙØª¤ž ñFˆÃ5îÕâ(ûÆ%tBˆŽÏNÊ=iFïçÛÐ[3 'zÀì䔂îgÛû¢€ø®îßß;;/Ÿ‡;/ƒ"’>ð0ØG†ÜɇaåÞAþă`¤³S!A-þüÄr"‰óÝ—yGÒ9ϾéÒsTßV-òÆ7 ÷®‰müЗýJ)%ÖÈd2i1Ñçùõ¦pòo€¾©‰$’Nþ¥6 N½^wscb×j5ì§d¥I)ã~Åü„ñ”óúÄO¥ÌƒhGa?„$+Áß¹ðùÓC*·J ÐE²%Í^Dñ÷ÌšüÓ0ûó; Œ…¼§u‚€®Æ‡œ>0(’Ôï{Q¨Õj©¥¥¥4FàŸ×ù ý~wÅNJ 1€B‚¡4Â0´Z­¥jµšÅV!APUÓ°-ýr£t†zRÊ鬒 B…¡ƒÊæÛpë‡ »oª›¯A»t¥Ûç ­©™Õwß3z ¿ç—·ë†ñ+d_¾Ï|Bö⣻{öƒÞ(y{ú…‘VÐVšZé¢zR<Õ·œý4ƒn=¯~@[ol­]{¶®òŰ }à!È~ÖÞ÷3nº€™ÉŽÑQ “'ðTôo>¾¯î›õ±ÑùÔ:7¨H€ úðõz=eÛv ?Ö À°lˆ‚€”4œQë? ô¯DÝ?ù7¹‘ÈV«ÕŒ0ê¬~˜è€0UÿeƒTð/¬Ñ¥1¹ ÊØ‘>9œT5¡tý ¸ùÒáö¾ ­âU~®ZÀ…¼+üTÂß}¬‰%@Ù8«ÏˆèÇV$Ð ^ZÁG È¢@›í~öSÚ"JÀP§?e óýMhÜþÔoý ç®~9öÑÃüpà¡Ï•?бWa<‰¶,)³ÿóø©ð_|ö3Lè¿NÔ*{Ðw¡ÀŸf°íŽþ½püQð+þ7¨ô $’à?y…ö\7¦ÑhXµZm‰ ;¨Ç¨Ÿñ·Ç©*ø Aû2+òfV†fn“vãØ…óù±bñúNø‡Û¯ýKhW7»ž‡ÉúÃõeRoJDÐ ¿LøM)¬_<&Ô¹:Dö“/ „‰‘¢®€Hp‰½Ò>PmèMèIR ÚüŸÓ®AõÆÓpý+OûÌÀ>ú®°röc`8 F:—ÜóCÅáˆü/Èþé|h?ß9¨#@Pú­Ÿ`€hµZÈóìt:m‹E…€f³i`¯L&cÚ¶ årÙ¯#âO!¹Qg‚1Ç#$(ä°r¨\}†ØÏ@ '· +H½7Kå>JBÍŠÇ_ˆËoh5*pûõ?…ö.=½ó]¯Â2zsùÕYü.ág½üÄã ~•ðLÏ)É‘„]´€½‘h¾]ï·Ý¹±6ySÌò3¥Ž@{?e@­!Ð- (‹üYíæ3°ñµgà†™Ü©ÏÀò}?k< ù“&NHŽ¢Í‹S?áñ:naE€A¾ã Âºêßð¶Çn^i³ì €¯‹.̶m#ñ'òO!yö°c0PÄÊ ©R©´Ün·Sét:t˜QuR.l¢æÆ«nß/@î{ªûŒŸã±ˆéãþ]r<æ¢êǰ ¦`Fà$ PK@ éç`{T Ó »6tD? + ø ITh€¤ãœ„ßå8m(ݸ»Ÿƒ­—*›/»Ý‚}‚ÏÄŒ¾xÌzÉ>ND¨¹üL*ð§’þA3ü@ aÚ‚€OÊ€»fû‚€¨"ˆ×º›.ûõð¹!¥ ¨Ñ"ÕÀ}]Jhl®}é¯ÀU•÷ü{pà‘ÏÃÒ=Ôò!èdó’lƒi†}öWÀÈD§‹@RÆgò3蘌Bôý"Âú c¦- ºò`ÛvkàK¢ .©TÊ/쟆[ɶO‹á…¡kž-—Ë×:HD=¨bØÁÀ¯à_ÐçüŠöù¥Ð`DtÅyŸfùœVšå]Ø>ÿM¸þä?†Ú×;$ôÕùMi¦ß Aúå}~¹üÖOˆ½  ˜”*€÷ú墣€œ* Ädú-92ÀЈÍ4o¿ 7þÍËpýOþ&¬?ô×áðOþ"؇ï+»BqÎû”Tß$ªÑlQù¥Èª¤^—ã?̱Û¢K½^O---¥9Ù7D½//€¡€Qø:ø¹jùê @!æ> ó \ 0W«Õò˜"0¨u‰Ÿúè·ßwËQÓÔj"ýþƒ'Íüû£Ý¬Cáê«pëû·_ù}h×¶\ÒoIùú ø™û3þ²†ôëBû‰ô’,ˆkÞóê^€çQãknúë ?1€už‹4ÃKØ{ý_ÀÎþdOü$øÐ_…•û?ö¡34îÍáw$µcÀ(míÈ÷ê'üÃ’ý0‘·¢Ë:ò(pd:š€!"~‘ü»…A?ô²BB ‰+ ¢õŸP±Zh®Z­¦… *©÷«êFõ 2Šb_ã0Èâç¸Q]ˆ^trû¿Ûßû×Pºú-pšE—t˜>³ý2á—I?S*ú‡%ýt& #8Þ} ±6LO0úÅ€–'žPàx)F{¿f@Ë‘Z òíê7ž_{6¿u,ßÿspè#òw=<ÛÕ–¥qø{46MÆ×÷qTCüuëAŸó#ý~ùÿHöq›V«…ùÿXÀ¨T*b#£èk`'V.—ýªÿ3"ÿ$ä‡@ÔúÃ¥^¯[Ífs‰ìÚc@üˆ¾jŒdƒ«« D˜’ŸÐ0Œ× £†QMAt‰Ä~Œò·ãæŒ [4r‘CBë…-¸þ­[ßûmh—ßíäßKUûM¥UŸ ÿ†Bþ{"ýBÀ½ï”îGðÞb€ãH‘æ~1Á“?Û'þ¦ ÿ^Š@K¤.ÂÎËÿ;ìüð·À>úa8ò©ÿ ÖúìBظqögœßtU}˜âÑóØ'?ßVø«Aþb˜Y±`Á¿t: ÅbzÛ«þï†ÿ{¾7ÓÔ€È? „Äø ^Ø¿÷§ü-NþóÜh¤)w@S«ûc¬>*_¦0 Ÿ±‹Ã`Kª>! h5«P¼ò\æÿ;oüA‡Ðƒ~¶ß­âotÖ¦"€Rõß`Ò¯´M£;€@ä_Œ½…/™ä~ËůU`7]@Iè¶K³µwŸ†Ë¿ÿ4\͇£îïÃúÃ??ÑV‚‹>ë¿Î#„);µ[–úØo;Õ/x|Aóæ@ÎçÞä–e¹³ÿ|ͼNò0­{L €$¥€U.—]#)GM ÓêO¼7Ê÷¥#Œò½B¢‰½ ·^}6_ü®|Ã%)µ]Ÿ2»o*9þŒ)ÿ¥¼fšé'‚!HÐÐ¤Ö XÛ#ù Ô Rº­½uKÔ ¨Ü€?ùÏ`ó©kúOàà‡ÿäŽ?06Á‰ÚÌð<"ß’9@ésãŸ× BÚƒ"Ta@}O@L¶µÛm‹ok#é÷Ο»Æ¯ø7óYH €PÀ]7 ñ8U.——Ðn„ÍU ×WS¶8™Ö€…\09(ó"þßþaó;ÿª·~à:ÿ)™èkrû-³¶ß’"º•ûAjÙG¤¶Ö;R7–pã@¯ Gjz»dxåºb´÷#n /* /E@D´•ZõM¸óÂÿ·¿ûk°rÿçáÈOþÒÐut¹ÎQ#]4^/†åë0¨uߤ|dÿnÔëuŒHI&ÅF 8Ðl6Y F „ ¸¢ {“s.‹K$çþëŒKPd€jÐĬ?~Ÿþ?)#§™~r,H˜Ñ€fyÞ}ö·áÆ·~…{ùewf0¥´ë³ÿj%9·ß ñ'â?#BÏüeŒ ï`[{}ùï^{¼@‚.çß÷Ô…8ûn€ÔJÐ礨<]"%@¤¨Q¦ˆ¯aD@«…×öøbŸ|ŽæïÁò¹3Ì…²·ãˆã‡8Uý§ý~ŸFm§äßzýoÌýoµZv&“œy…¾± €š÷Ïh('€@”J%æþn Q°ÝnÛüý<7æ$‹¨#4¨=`‘Œ»cA¤Ÿ0Óû|ãl½ô%¸þì/wÈ>ì‡ëš þ–RÜ)Q2á'Ò?i’Ͻ†È÷äiú¯˜°àÈ€#ï¶üz{°p ;«,@H˜8 †ý«‚ÀX—R@Š…ET€wFZÞkîÚØO@1@DÔ¯?ïüöS:òQ8ú©ÿVü ˜™|¬Æ·8w%!™ä_ÍÑ46éãáÕóÂ"ߘڛF1 Ùl¢(ÀD€H €þ!ÄÉrFœ¾Ý›©·Ëåróƒä™ûaŒ®.œI|Ï –'£ðE£28Ð O˜*ñ¿q6^ø}Ø|éÿ佛߯~9Ì?¨’¿_A?òÆ ù]`hH½¡ˆ+F0ñW×Ór&eòÎÚ ¹÷ºÂAW4hk¶k÷  üòü{*ÿ;ãŸ2]Š€ˆ 0™àÕ QL*(î¹N~®¹õ]¸öÅ6¿õQ8ô±ÿÖùy°²+‰%„4FfuMúUñ×ÕP‹ª¾oD¿Ñhd0À+ùex¾9Ã"€bR‚ R' qSE@ðBðµl½^ÏyÏ}󔔺–€ªá "ÿaóúýö µ;påëÿ+ì\xÌmågÚ¯õ³”53ü+ùSnX’ï7‹oø|¦}Oz$¸ïÉŸg‘`‚èvGzÜî%íø:ë%õ¬G4P?×!ÄWP‡9 `”€¡.Ë€¨Q+Àí ¤´¥zr\£pý«ß…­çÞ‡?ù·àÀÿ>q%ÿqû›ôûâyì‡Éý÷tÛûµôZ¦–––Rž¯ïi^€°áÿB A€Bœ Âý%Ò/RpÖ?W,³¦‰x_Ž ›È÷WIºèsªæÊéÚ™ÈõÔ®ƒD‡(Šò蓮†é:a®á8Ÿ‡“¤úÎ&\~üWàÖÿ™æ/ZùÉÄ_´ñK)yþÌè­üß1”ÛLöÙ$ßè%ù‚Ä3…ì Bßý>¦Eu{¦lZµººEvï­¶†ô:Ñ@ \± ½Y  Þg]`(qÀ‰„( Îþ3Ö+Ìê²ÕEˆZ‚ô÷¥ Mh{u¼†è €ïßy®õ?†'ÿÿì/ÃúûÌEÄqæŸrë“ë‹èÙTýµ€aTLPøÎ臣Cï¥Ø˜àu) »~~ÆŸ4IC¡PÀ¼Ùˆ°f³™«Õjö £6_ ÿ×Õ˜ä—L§/²„­¦;¯jÁãþ½ÊæÛpãù/Àæ‹¿ÎG礠Æ_ö³Xïš±ÞÊÿæ‚ð÷͸½aú}³ø††à«ä^&öò6¬wEè›ígš3%Þdy˜Èm.¿ÓËhÝmœ>†ÍúH¿Ôð¾+´½¯’¶MÀŸ³aÄ¿ÈUpfaƒ4:L.ì!@!æ]jz’ÿn×€öþ>£ àÖð„§ò.\ÿ£¿ [O¿úïÂúÿfv9¶Dm^¶œÈÿhûÅJÿ£ãú::?G¼.OÎ5úF@äÆÙ/ £˜×€ùˆbM³þ$bìâvgÿ½ÇøºÉ ÄRµZÍ0UšDž}˜¨£ :ƒ¾—÷bŠÃœû¸8ÕÛ×Üÿ›/ü*÷ÆËÝâ~a‰O?Ÿ¢~Æ¢\$g÷{‰=cFÿL¾nŸY=~Ÿà›>ä^™Áï›Ñ×Å‹ËÑlˆßèkD0Yy¶ßég¹àhG!ôN—ì3U$§GPÄnš*´÷S £eÐy0CAÀoÖ&Æ·ƒì§´Ez€÷Øð© „Ç‹hí¾7¾òÂíï|~â—`ýáŸ#e/YM:ùOÊø>ïýœÖ¹Ô@þ(𕌣|‘ì‹^@•üëjù'€×× B-,ˆ™L&0ï?ˆdëBýÕÇºÏ 2Šq(“ÐyŽû¬ŽÍ(§Ýjµ'n}ïw¡¹w±CûBÃ'ÇßP†÷ÄÿÀÙ}¿±¾ß%ü–7…*­’|S"ú¦†Ü3éoK=¯ÿ GKÖòÚÎßi Ž6’ ÷ûtwóö™îïvv—à÷ NŸ@àxóè(ëpiA`ä@»ÕI)Ô( švsg9ëö6PÓ\Û u€Vð:¸Ï½ïjÜ|®ùyØ~îãpìsÿ–ïû$¿Ì­ÈÚT"àñ>6I˜ùWÃÿu):ß;ì„œÜ  Õjan/4¥`ÜŽ¶m3Ît£%Ý$$’ÏÐtEoâ7ÿr»ÝNÉaúAÅútyGaÄ‚AÆjל:áˆn¾ð/áݧþhU·Üs‚Ä?Ñaþ, Ù…ð[°?»ïxwÝY:Ù{®’|Aì{&‘{uôGÞѱGé]§gKy†Ý¾r߆;Za¡#b¸N¦ø=Ÿ—wÅÛ¦÷´Ý!è.!÷aºÒ^öbeÂÄŸí‹*ñwº¿Íq‚q ÐÇßÑòÞkí×pZû¢€*t¿òQAÅþ¢ „éà+8^ë@ï’w¼‚ÿ¶Ææópù Ÿûä£pâ~²ÇÃJÓ¸A>ÀD½:ÃåýÆïö;ý„ôwtøvv*•²” kû§FH $À-–S€“~!äùÚTý?Ì@æWÌoÒÆ5ƒ¨:PÍkŸÍáã Ì㘄ٯV½ wÎ ®|íïC}÷¼Kê-M;?‹ˆHÒbvã¨;Ëß!øŒé…ýL¾Dô½çŽ&÷^æ–ŽB<9¯Þ%°x2s|—r©¬ûØàk÷¹ÉI•Á—T†ÿ„ÎûÌâ n—Æ5ÿKñí:D¿SõÑöH¸Áß·Õa¡C¦› O}âë6'þ­¹Éw§ÎW¸Ôøvepš5Χùv ß–¿ÆŸ¾_/ñÇ•Î÷¸‡ÅQX°àÖR¤ëX—™êÄ–' ´öEn´€Óó|¸(AÀ§¸ŸúZÑ’)öëІ}!@ ÄÚ–×  RË@UàÏëן‚·þ¯ÂÊÃÿÄ/Bîäûh\‹Á>‡­ŸCÇp<Ÿu˜É±Aß§‹,E/ÔÓ|± …>?n†ÖÀ4¥€¯‰ @H€€K½^ý@3Åbq ÅÀ°†GGlüŒ(:2mík=¥8ju¢.ðçþï¾õ¼ûä¯Báí¯w ü©³ýÞs—ü›û’øëÂú{fù-…ð{3ü}¤Þ›1ï!ùæ¾p  ’~ïZ’È>Sx£LôÛR.¼K®²GòÇÁÈÈ–;,»Îw ½Ý!òi‰à§íý”‚ŽüÌ®od€(4kq…êeh׋µ´«·Ê·Á©lSº NõŽGÒ÷Å}Òߧ˺ý8t/¿ß 1÷D.á÷H=ßéŠ^”@÷µfo7—©7A@->Ø/8 »Ÿä¨ƒíGˆ‚à‘~ËÀŽmo[·@ "à犯þS(ýø÷`åCÿû·þ6X¹ÕX“Ç$ÏüÇaf=î-ÿÔß {O¶ÛÃ5–¿O-ºDßKñMaÈ¿7ñ‡\‚óÿ $âŒR©ä†ýÀ~€k¸!@#âÆ!ÇŸ›a¿ÏψGÎkÒ ,“LãÔRD€ÙãaÍY;=º¿Wݾ WžøU¸ýêïr»Þ ÷Ww-‹æÿAUúåYþn8¿7£?Ìì~—ì‹PcŸà÷LñJaîÎ>Iì%ùÜ"Á_½`ýn¾> Æòq`N„Ìß½4ÿú”û˜¹¡Òl¬ëh¾ç‡£t.pÀo§1ÄÅ‘wZePçk¾oA»°­âu€ËÐÚ¹Nõ¶Gνƒ¿×Ø'ämW,ˆ¿[@ŠèF tˆ'RÀ %ÐêCD0™=C便ao3]€¶g“Äá…ÜVD¨B@»Q‚Ý ?úWpè'ÿ.þä_‹åXºaÿq(¬—”¨U6òB T?XøáRûlÖjµ2)¯çŸ˜ýÇÿPÀLð:€" @!)IÔ@òïåÿäööö–<ƒ00´ÈÏ€ÉkÌ?³þ~íJ†5šêß £¤Î[ ˆŠ@1ÎÀ§A7.¹­j®=õÃÆóÿ÷”Ë9jâÏzɾ;ëï=F;e,"ñgJ[>«?‡ŸIds÷ 3äì¾Ù¾/lZ7V_[mAø½0t ÃÇ|$ñ¹c`8ÇI>'úkgÀX?Ì^¡û ÷âçÇ+ÓsZÝõÊI~ôq[ZÊ÷I»´ í;×ørZwÞäë·E~ã`š¦T½s¸ŸFáÖ `ûu´”š~QⱈhöÔÀZLDt‹!¶»å;ÇÜ_ˆë-'öß„ýÒŽª`z©nz€,x‘n[ÁÒ›°õoþÜyîÉç7aéîº)*³´»ó oúØåý‹Kj cëWcKŽXíÚ¾v»ç5¿c¢{_þ¼8¹OÛ¶í¶D_n‚ðS'16BÚX¤Z­fÁ« 8J~R+øOÊðÓq!¨À<ÿ[¯>ןü¨ï¼ÞÛÒÏ#ö)ð˳þÌØoçgªíü’DüýHÈY~&ˆ¾aI‚@Àì¾LöC§Í%ŽD’¿tXö_[» 'ùæú=`äÍMHBG¯ÑhB³Õ‚F“¯ÝÇ|i¶ºN`›ÞVK8”õF–ó9¸ëÔ‰H_Fþˆ»À©í¿ØnB«pÚw®BëÎÛàì]§² N¯«Ûü|5A.8è8¦)°ŸFÀ M”Öð"œn1ÁV·¨ +$`!D9:ÀKMp0¤Öh÷ÔsЉq¾ ET€Ÿ  B€ãµ dr3ˆÒE¸þ;–økpð§þkÈ}ÏÌä<:Ä¥h]ä¯Å?ÝDVPÇ,¿¨WU;àcœŒkµZé\.‡«ÈÿÇÙÀΩTJWô.XI²7 ¥H@ÞK`ƒZý…Øä4¿h‚E3ô„h/Q@éúáò×þì½õU—¸‹.±7öI¿Lþ â/8rÜG줟1)—ßw–_~Ïð1»ß™ä„_%ûÇbç@œÝ?ô^0ðeý^`ËÇ€q¢odtĆ)Ù+¼v¹=†½b J¥2ìì È×¾Þ-a¯P„R¹ZêÍÔêu¾}¸æÄ¾Â?[o4ù󦉅»×䌬ÕjºÇ[þUù¶öƒïƒÿù¿ÿÛñ»Nø95WO»KêîOtŽ™›F° m‘Jpç-hßzÚ»—: {:tˆ7}À}lº¯í§s´ºQ½"A':€¡ÈÐv\1Àý.ÓûŒX{`ÁXÝŽÐß9Àðj! +HB€¨àìúnJDãâoÃÆå?‚Õ?û÷`í'©s¯N™ˆyç>F±^Ï("îXùÜAHècûÍþ«Ûáº^¯§8Ù·q¶Ÿ?v-©ˆ@€¿'§‰Ý,! ˆ± D.?( XôƒyĆe‹Åb&ì,¾z¤«ð6×?(½`Ð —0qªQ°¨‚̬~_³´ï~óŸÂÆóÿÄÍ󷤪þ–RÙ_ù3Xo-‘—k þ¾Eüt3ýb–?å3Ëo)9ýû¤ßñÈzáï>§C3Á8ô~0Ž=æ‘÷Ø«^e}[j8!ˆø­­mØØº›[°qs ®Ý¸Éß‚*'ð­F‹“{Nâ›-h´Ðjvf÷Ek§ö>gfB@a8ýã.QÔÈFž¡5ÓÀVOÁXcÀíFÀeá&´6_‡ÖïCkën7<ŽTXp_ðÒ˜(.¸Ÿ&Щ!îøœµ¼µˆh¹5þÙýˆ‘r/1€ xÏ‘./7˜Bˆ‘Rû@÷ƒÎ{¢i%óÈ÷§;;P}ñ¿ƒ­ «?ó«>õÈC4þEƒ4'åøëó†ù~¿I6é1«V«X0Íi§/ i‚×À/€Š’@H˜R@—K4›Íåz½n£d …üû}N &Q„Aƒ {Ž“‘­ï®>þ Uºâ^Ƥ6~ò¬¿ÔÒO¤XF’‰Ò=âß;Óoyaÿ¦`ôÎð3æw¼üpÇˣȃ“ʃqð!0Nÿ0O|¸Sm\‘‡“twö½ÑpÉúí]¸zí\¾z Þº| .^º ÛÛ=DÞ™ô!í{Óè^0®Í4<Û9…ö¬‘!"üºpÏ'_¬Ü:XGxÿç;ÚÀî»Ð¼ñ*´®¿í;Á©ïÔ œ¦·¼kÆ›ÅwÏŒé¤^z·;ëN­NÝf6;ºEÅùnï·5Tãëcx «Å)*Éi÷Ú*ñŸ›Ji¾-_øñm³XÎròÿH´D¥(úòœ4!`е1(âÁ/äßïXáû|lJq?<›ÉdÌZ­†Eqþ8ùWSÀ‡ø‹çä`“@ˆ§ êZ ÷!wT­J¥’õ*„4NagàÇ©p:Œ¡ôÛfÞ†ZÔ%®ÎAÛîDé÷•7.ÂåÇþGØ{ó{ÃýÍþ<±-ýLÃë.ïµd‰!þRõþná>?ÒïÍôwgù-)×_þcî,áw ·Ê{j `õ,°•»À<ø0Ž?ì†ów:Œv41¿ÃñoßÙm¾ÜØØ‚·¯\…+W¯ÃÅw®Ân¡02·cA¯F#°ž‡†wœqÆ»Cü;á ¿ R–¹vÆX= i¾ÀÿvG´¹u Ú7_ƒÖæÐÞ{Ú»obézÜ:žÇ+Š ZžM^ÀØ~Ê@'E é¥ÈR^´‰×nPŽ Å'"·è¨Zžêaº‘¿em¾NuÈ?'ümÀû:ånd®žƒÕ?ÿk¾=Žaÿò¿Ø~È0þm_ZçG*®äÇ(Ñ €?¶øöilýçE»›a$*¶m³r¹LUÿI $‘ü o~Éð¤ŠÅâ 7i¹Riì×@,]ÅÓAB\BüD-"ò?"°ºÿÆ Ïþ*´«}áþ*é— ü™Iªì4Ûï’ÑaI¿˜å7µ³üÝv|H¼2À8õ`yØúÝ~3=òOÁÐü o¾ ï\}×Õ¿±¹ ›·n»¡ûÅreŒÃÃ|H=S¬w·÷AÏûŽæ‚0$‘¥ j²üZ´ “IñkÍ$#‡Wß¡³¸¼ïσSÙƒÖÎho_‚æï³õhc×·é :§Ë±Ü®®2rÁ@~Lù{Ýk°O 0ö Ê- c^/@èyû÷¹Ù©¡áFídøc›¯Óî‚Þ÷8ûïðçK?óOøK¹¹“âPô/ªû¨V·O‚/¨ÖË ª‹%ûÐAפn;-ùô&üÆV€ÞD ›ÿÿa70¬þ)q†GüE€X¬F£‘CuPgDü pPø‘0tòã0ƒë"Ty%$Åk?‚·¾ôw ²ñBO?KRfož7ß? ÄŸižH³ý¬;sïþ!H7úgù]‹´|Ì{?æñ¸3ü̲CÓáÖí;ðÚëàÕ7ÎÃ+¯Ÿ‡½B§(_©ZVÛòèH~gÊÔˆ“Jæ é`2uеÇFûœtþD”VƒæÇ"á‹ei’ Ðw–²«`eßìÄû!õÀÏ4ø¹Þ¹ Íw¾ ­+ß‚vuÓpÜcjy5 ¯¦€¹ Ð-Ø/¸Y'b “" E›[Šûì{Ÿ3Ö!úŽ™åF߃f†_þ6ÿ½øÎü›nëÎÜgþ0×ïš8ŸùSÅÿ8µËM¤µQÈzÁC%õ8ïð=L3SêPé‹Uÿ]5 3ótÀð^Ó~Š €g”J%Æ E²W¯×™×"*Ãß_f©8ÌêÀähñX¼ŸG#¢-YÙ”šNÑT£ ‚úšÊ;hˆÊÀÇ‘Òu.¨Ÿ3@0ŠÕ@,ÿ¾0¿uÒ¿¯Q܆ëßüM¸ñܯtH¿ÔÖÏ4ôáþj?#IÄßw¶?Õ%þ£~·Ì82"üŽÌ °µû8éÿi0N|lø~œÙ¯Öê°¹u ¾ÿêëðÃW ßþÞ Ùv†>d„"ù†t’Y÷x)3©¾Ç˜ùô—W|8‘÷n®§öŸâ6k>1#Z÷n”ì·[*ÂÊðË5)¬!püýÀ>ñ7¡]Ü„úÅo@ãò·À)¾NmÏ« àqàÕ¤píOo‹AW h·ökxõÜáŠY Øo°7Û6‰ËäßȸäŸqâ®u#˜{ß[n4…ý‰ÿÖ=®ÓýŸ'é$ò?9[’ï7ó/ûÕ²&û‹2W_W A@þ.…‹µi8É7jµšÉdÒÈú‘ 0 €ûñ(`$ëA €Êu,Hà7|k`KP•à5yב~çÐÔ(¹ñAF4Šˆ0ÆQHkX„(„akGLÌaáNýÎùgáê×ÿT7_‚ÛoÛgJýSùïiëgļÀ__n¿©´îóHO1¿Ô>ñ7¬žœþ@Ò„iå,˜Çâ£|ùÐФgF¶¶ïÀÆÆMxó+îìþ‹/¿ÕFC=³þä¤oF_&ú É×|¦^¼Bo„fk†XàÍÿs;o‚É É?¿M¬™@îÖY×Ù® ¾t2ø÷Áþà_v[6Þy¦SLpóehW6Ýã bÝȯF“Ó¤hv®yÑ‚R›"0!`2äßè›ùïÿ_ÛüyÆM¿Áz £Pþ uúÏL¼»F\Û’/лoI,䯷ÕÙ¢Q„!Ⱦ:úü˜êk¡¼ïç^'P:èÌ$âÏPôÝÈ^=€l¡PXöú†2Vº™|Á j8΀f&= axêÌÿ¬·$åÐEÒm¸öäoÀÖ‹ÿ‡[\.òçÎúËÕý¥VAyþF~ø8³ý^np‡ø›Ý~Ž(V§#ýËgÀ8õ 0޼Œ÷³W‡ÚÝJµ ç/¼?xíu¸ôÎU·"ÿ­mhµ[>Œjÿ„„!û‚€‹Pû¾û®à¾n•Jæ™ï“àºî/0‘ìó…¯ÓòiuZº©,zWÚVý7ܼƒ_wF÷¹ûÏ {1Œ£­ÚqUÜ1VO@š/©{>Ní¿„æÆ¡þÚ¿‚Öæ·9—oyÛÝ6•Z–×:БºˆÈ$þ^T€¯c dcÛýûßÞñH¿Kþ /÷ ozBŸuê' ý¾¿ÿ?{ß Gqfýº{Ò&å,$@("!!D&˜Œ}Ƨs8Û¿Ó9œÏ¾süÏöýöÙwöáŒÉ„ˆ !@BHBY(G”V«Õ†ÙØýwUWuW÷tOØÕάªìafgggFÝ]_Õ{ßûÞ‡H]YAto·ì«†}H¥{ôå„E>Ãìr”†æ3 äI8rK&“‘~ýú‘v€J&“¡èŸ¤€t("DÈ6€’£ŠÁ¿Â[€{3Ô@¡=`Ní‘7H ’¢.;¯½îÃJÙ~Ç®¯.pÇÓÈÄ›ñá¢{pà­ßÛÆ}®Z¯ÉŸ»?ÏüWÜ?O ? ø³M>ûQ–íwH1ÛO€½ãÞÏ]Ñ ë÷&xP†œmâ5ÐFL/ÚÀ¨mÚÚ;°ïÀA¼µt9½ýö¯YŠú‰"@aê±<À« (@”µÝÁŠþiÝ?QÿЋ0ÿjí0„§›àÈ„²¯U½ÙîO‚ÿò×=KPÛíBq©˜÷ð’ét:LÊ~™á]À¸ €¨PœéŸý’£úâŒ"‚ B©TªÖ¼gPk(JŠ­Ý/6°û½g P ‹šŸœ´+-ùä[ÙRÎ캵[gÿmÛæY™ü¾œõÏ ü#N¶ß¼·³ý¶©_Ø6ô³H!ÛO‰N”‘çA›t=ÔÁ§Ð i1é$Ûÿúâ¥xyÁ"ìÚ»GZÛÉfA¿+˯X ŸÔð‹’~1ï¨J àg-”]Ÿ!Á~ߨxûÅ ¿çýFüÃuØ9û+&NoClÐtô›t ý÷ˆ—ÿs£uŸxuÁùHïYŽôÚ‡‘=ºÁ2´$yknÖE€Ç5C·”¼<€Ü“yV„a`ÙÛýQÓ?"ù·Zÿ@ÿJ¤B?‰Ð ³* ÜvwìË2uITôü¿Ó/{_ª `1d€X®ËÖQÒí+"Öû3Ú 0‹¡££#Ÿ ÿ’£ªª®âOs‡ZZZÌ å=ëÍÀàê+ʓґ™Ýý‹mÏäü¡wCæ(y9C¥Ý öõX7ñ³º$_­¢EÙ{mûÝóv»Ð³hZ7;fÕ\9vÖßëðÈú«}ø»êûY¶?G毱sÖÎÌ|¿†±ÐNºÚøËLPP_àïìL`ÛŽÝxþÕ×ñê›KÄ3ã÷ÅÐÏ¿Zð³³’ð `_nøû4Ègü—s}¦:qtó[H%Zèe¡^…dÓ*4-ù„ûMÀ 3¾Š~Ó¯GxÀ(szÔø_;ªfµœr-¢“¯F¦i’+ïCfÏ"o#mhûº%k0“üsU4äøøJw;xLÿlð¯2“?Õ"©š!ChüuO¾¦¢Îûñbø'Gå ?Ãë|Yo)þ³÷½ í¥E€=&I¿è‡XP™úWA’9$ G5î¯X½‹Ù3ƒJ8“ÉÔš¿+êZ(^KÉžä#Ä@–/ØU* /Íþzç˜w…躆²‰6ìzùÏh\v‹ÉçøÅù²þ-÷/øÛ-üÂð÷•ù3àŠB~ÔqDæFQG¡­=Ž;÷àÝ•«ñü+o %GQÒþœ,?s¼WºøåÜ=nb†ßzT(ŽtÜŠƒKn³¯LrÍë,7–nÝ‚C ަE?Gì„Ë0`æçQsÒ¹ˆ 9™ÕóûÍC¡!§ tåï`$ãH®{™m/#Û¼Ö"`Ýr¯€¬Û' ¨Î] \1B³?¼½ŸbfˆÐR ÚîO‹ 4ö2Df|¶ÏFÕ6%ñWÙ¤L)‰ ¯Qbйúv#’ÿˆ øCÂïTæ aÖûµ”CrTá‚`KÿS©¯ˆttt4ðk¡˜ì½è*ÚÕ^¬iI1ß©”`+Gß_TËÑr±³q¶>ù=ÛèOSzÿ°ñ{þý²þ þs\ýµØn᧸êûÍ[¨êøA{ÔA'õuöí?€7ßYŽwÞ[‰u›·#«g-„¢;¸ÝÎÞ€~+lÖÅ]Ó_ðKÐÜÅŽ®#›EÛžÕГÑ6ïÙ=Ÿó$uî} ój‹š±—¢aʵ¨Ÿzµ9¢Áß-Z‡Ø¬/Á˜~#Ò»ßEzÓóÈì~Õœol®‰H÷„ ÍœæcC³È7@‰ƒÆnúcíþ¢‚é_˜©T¨ƒ§ zÎ7{øÉºÿ¾ñ]%‰“›oS̱#{t¯’€ ËæûÅÚ’ÒÕðKð/ 9ªuÄãq’é·™<†üéd…Bó÷¤ dÖ'fâÅ,«˜= ì…Ì{¢{@%-.â1’5ƒ=Flþ\êy²%ÿO-Çè/̲üüWeÖ_ñÛÔü5vo×÷kÈëûcƒ¡Mû{hcζdþEÔö¯\³sž·ì@sk‹ üâ×á *òþ¢@¿$à—£˜øáÍ®ùÅoq$îÃÁ·ï°ç;'þÄ8àþ 3œ´íFëúѶéq„ŒEÔ0ðüo ÒTàuHü1"§\‚ðI {ä›H­ôæÇm"€*ÈeMÅ}YK!À ó^áD÷ÈGùâ…àøO þj,Ç•™ÿ¹Lÿ4¨uûð'%ø¯¬ïx¼ì‰¼@>ß^;ŸJ _i¬wÔ¡‹HýÓét$‹…YŒ£²?î@Ä u€ÿ’£ÏÄ!W´%e±x<^oÍDòû ð´•òÚj^xý6’¨ŽO:Þ„=¯ÞŠÆ÷nµ³ù¢ÑÍü›“*¸ì?Àá¿"kýs€¿Â ûW-âþtƒÊþ–ì˜Õ"pÿ“ Mý´Q§ü$#q°± óßXŒ§ç-ÀÑö6øJüUOM¿ÚuЯ­üä8~cƒ¸y.DÅjšý߹ɯåôšä Ї ¥ºp…g’ÈÝ‚#Kþ€fóV3ær ¾äǨs†‰¥|¯QE #4t"B—ýúyÿˆ$!6>#ÑH?€–yI uö8DIC·Ì¯Y ý#þÅÔ.ðö8þGhË?¢Á¿ëèeÿNïû €—Žÿ= ˆ§ÔnÛoÏì% ºÚ.ÑC Öß1BÇpÀãÀ#€_;@9$ G5ÅäJyÈܯioo¯%¿XGÒB$@O/B•ܰ6WŽ Ûø˜ç¨ãÀfì|îè R]O{¿H€äŸoöÕªÊúfèG7õy2þ6ð'™Eóuêˆs¡N¼ê°)ÏA¼£6oÁÜàíåkÕ3޼߯ên‰?©áwŒüó|”ú%à—Côâ}!ÓРë9ÝÑŒ} ÿ‡^Z!ü?Ms<üýÎëÖe¯0°Ý¹gö>¼¡ºáxîOP7ùrÄFLüw¨5ýQsö×ñY¤7½‚ôO"Û¼‘*öܱÌUFdaì=i#h‚R`%@¾²O¼°À?süל¶üóº3NÄ.þ7¨ #Ê«{Û´O÷=3çýŠ_†þXG¢òO&“¡†††Û£’Ì?UD"MèO ;H@Ž*#\™±~5f0¨±·ßE8ôtJmqÒ•š èE;_¶_n z~CQº`ÍÞÀÎg¾#qÀ•É iBæ_0ú uþbÖ¿"Á¿¯Á_ÈÊðS×þ¨üUGö/ÖøûÒýcÜ5POþ(Ô! ~…æ£-xó÷ðêÂÅØ¸u‡%ñwÿ`‰?•,R¢¢Ð/ÍûäèY0H<.;8¸ÔŽ„0äf œà~“#ÈJ›5¬Çô÷L@.X½0?ˆÆ×~Œ¦7£h˜öè7ýÓ¨AðF>Zèôžx%Ò»ÞAzõƒÈ6­fåD1£Ð9n¹ÿ3r¯Ÿ€ óöÁò´û«a¦Vý?í`ƒ ‘sþ)¯çGW}oðjpý¯¶9(“#]ÿ…Ž[>SS~K§Ó¤ëW”¤ü ø7¦Ùr#Öî ‡$ä¨Fp$v`-ü”L&S—L&c~£ØE÷X/>•¾ØÕzÉÑó‰RÊ-H¯}‹ïÃþו+ùgÿDòÚûùeý9ü¬ àO6Ïðs©”ÉýÃàoý=¼ºÔS> mâuPútÕ÷ûç‡㩹/aÑ;ËÑt´™Œî|­œl¿»m15ã~–Ôß úe=¿]‹…âs>2ÑH%°ÑÖüŒ?5þCB PNèŒPùcݺי£¿Êˆ[!N¢åý¿¢mýCˆŽ¸Ïþô›ñÉ`" ր褫w12{–#±ìèG7˜ï§9„˜ÁZø²9Å}hY#ÐÜþü±šj¬Ì?3ÿ³”;ó>íK=ÇnÚWÖ|¹~—wþOµÿå¼¾üÊ– Õûû©šˆÔ7›Í†8@poÈJ¨€‰ ÄD¾Ö€¤"@rTnŒ!“¼­­˜{Øà_±vÞµ­­­1bB‚ƒÀä@ JUÕS’|`ב,d X¨´ è³D™g%,0r‘;6dK©¥©–ƒØ1÷ßÐúÁ“ö&ž›ú…y›¿õXå¿«£¿‚QÜ?êqõwÚùÙ­ü´0Ô1—#4ã‹@ºÞL&‹-Ûwâ¡Ççà•k˜j€%àïÎö+v†_|ìÍöKyÿñ»ù-W¬(¦CHàw2ÿ¦³i7Z>xÔŽT1ÄBM¸‰%–šÅÉúìe¤›ôyŽW}.GçîWѹçUzi$^ð/pÆç Õö÷÷ Çw!"§\„ô®eH,» ÙÃï; øÜ¡AK¥~DeÒBÐkhQrlŽŠà_¨ùÀhò§™tµU6Tæs}¬ÿ®×ïjÔ•¶7;ÖÃëÈï·_#¾Ž3ï{j‘Í?W|‰ÐÞÞ&ûÉdŒ |€*xrÿ— _rTÏ^KqMèT*2'½ ú hƒþ Í¿tùePŠ©©,f(¥6S.ÌrºFÈùˆïY‹íO}é#k\&¥Jþ+>ë/ÖùÛÀ?fÉýÉfž<Ïêü9ðWDஇ:ò\ø¨œ÷£“©V®^ÙϾ„ë7šn8 _s¾œ_m¿¢ªôËqÌbB—Ë…Ìÿ}øÚͰܲà"ø½ÝTuñ¶ßù=ýSPѽP" rR@04hyÀ~4Îÿ'yó·pæ÷ÑÖg,µŸx¶y{éío!±ê!臗Á &€°HA¾.Q‚0KŠÎ;B Ù™~ üט?G­ŸéßšàìňL¾Þ ”±ŠÿÝ]{«©^¥ËégɾRÐ|¥¬~{m¿®%Aׄ¨xòv `«rÌûtA&I?‚"‘í‹ÅÐÑÑd(Á¿$䨦½'˜ã'¡ööö:]×£ܳ@¦ð füµÅ–ôDA5I뻺ᔣÌçAÏ iýìžó-éVÛ½;¬ù¸ü‡‚%ÿ•›õ÷«óødü#NÆ_ »?$&POº ê„k¡—÷_Û™HàÍ·—aÞ« ±æƒ­ X8_#Gæ¯æÖöûKü9ð—uýr” ì(LÇàèº2~Õ1 åàß&¿àdôUy0€¯ !±<€©f Oy@6ÑŒ¦·ƒ–·¡aú×ÐÖç9%p}!Š€ÐØ³Þñ’+ïB¶i›Ÿ¬¥7%Pˆ?ñ÷Ã)Piœ q„€ÚöÅB ˜7mèt„§}J¬_Yt9ãñÒò¯¾¯Üï_ó©^Å’‰î^\AÀy‹Ó?ðf»LP@ O $ä¨Ò £ˆ÷äüwttÔ“`à·ðúeöóÉîóó|¿+à¿*¨TôÚñ5ÒIXúö¾ü#Gò¯:ÒñV5’ÿ¹¿•ñëü#ŽÁwö§?kLæ«X­ÂLô¡Œ<Ú”,s¿<×j"™²Úø½ð*v}¸zÆpñp×ÜÙ~¡}ŸøË¶}Çï¼íɸî×­¦+±øÀâ‡,X¬ð³#÷çqBñ‚pȃUÊjŠ£ X›—ˆª€¬H°òò³Á|²‰#h^ö_h]}ê&Þ€A|5£§ù‡‰P‘ —2"`1Kþzç>‹øS¬®À) v¬Ô3 Yª!ü+ýNFdÆ ?2®ÓÝqE?Àtµì)d­åžÔ#X?“ÉÄÂá0oÿÍñ?mH<àeÅ”È! 9*iÄãqžñçXM¥R<“íèè¨3'~Xíb©‹dP_Óî0˜¥ r¿ ÂLÌÝž—ÿŒ¦•Í­÷׬6¼ÅŸ&ôõ®hÉ ÜŸü¨'ëvö'­ÁŒ‡6ó›&ðÏ”þƒgüï|x6šš:õý"ð÷Èü}ü™‰Ÿü%è—£üÀ£ÉP 8!†¡‡–üK ¤)îÚE ” ŽŽ6ܪ»D€—ˆ„{®°}’-h[{Ú×ߺ 7aÈGj+¼ƒv ˜| ¢¯DbÕcH®ºÇüûƒÌ‘ƒöwU­Žü[­þ@ÕBøWëF <ã‹f¼˜Pqçþx»Ö+}M–£ð9ë‰óè#ûÕZ&“‰š#ÌÃSÿRòñ0_ã'ý—CrTSÌn$0Ð nÎóp<¯'@üêòeö½Æ%ANÊ¥ž¶xKÒ¢÷Gâð.ì|æçhßõ’o½„^ó/ÖñV¢Ë¿âyÀêpm¹?ü.wWHþ ~4Ôi_„6úl«î!`#ÑÖÖŽe+ÖàÞÇæ`ßÁC6ðGðwÜü °×ŠÍö˹qÜn‚ËÝÖë4ôKùÜß#ÝìÊüð®ŠÙÿ;ä 2@狳ê¨ÈLÒ +ŽA ý{ …ûˆo~œÞê'}ƒ.ù'ĆO4Cƒ)ŸùÅcg|Ñi‡Îew#³éi©ÃôÍ A`Å•°MRðOfo¤ÂSnDhôÌ>†«e}–ûˆ¾ó%º¼±Ëo_Íÿ.hÏí-åeûuÒ,‡#ì=Þ €dÿ™ Ÿ€’£† à÷Ö>C×kâñxÈ~FU}S!`´±òc»³xUãâ,%p½;âû7bÛcߦfÔ­[u›üÓâ¯2%ÿ~Yÿ¨#÷'R]žõëüI/MfØ ¨ã®ƒ:éZómj?’¸úÏ_´s_~6o/üV]øËÚþcÜ?ª}dõ¶õÞ{eÿ]‰É‡ß½‡f*' y€*” yjÿ‹™Ê#øcQ –P¾Î|*ˉÅñ °­;`ý]|Ó#èÜñê&Ïÿ6b£Nõÿ‘:Ô^øCdÌ\ý7¤·> …´d$!õ€¥¢!ü×!4ñz„Æ_VÖõúxïÇ ø?žMÿÊþýާxÔþÏïo|öôj:¦ …Iðˆù‚ˆ¹pK@Žj‰7~“رD"QK àÚDµÜó«¥ þ…ZúSfP-‹H>—W9ŽýhÝþ¶<òÉFºYæÀ?¤9Y¿zÿŠ”ü2ù£Yÿ˜GîµÔüÛuþíó­Ž3AÿÄO@mnKñÅÁçÜ[KWàѧçbã– ‘ð÷û mü â2ÇÔOÊü»}”ÞUg¹÷‚Ò÷bR>WìBejÅŽÆU/ Ûyˆ?'TfþGf€¦v½8V$¼^!Ÿò»•`Èñ È2r@ˆ€lª mkîB|ólÔOû ºèÛˆ ã¿ :¡þ_¤'} ‰Å¿ƒÞ²Åü7jÎ¥å•þGF‡È´* ¤Oíþ$aÑ·ŽMDßoo]l\ Rêz_KÌ¿Í!kv6›¥áƒ{„ÃaÕ'¤É“) 9ªm¾¡¼Í©é!j6™kãñx ™ôÜÝŸÜ“×{owÑXD̺xç ZÞàÆß|oï{óv…ù~µOÇj#ê%P µL” p¹ODÖܰ¿ˆO}%Çì/âÉüWE½¿OÖßmòs»û‹&®:ÿ ”ÁS žþu¨ƒÆù>¶l߉[îx¶í6ÿ,Óu௰^â2ÿî{Õbœï ´T“v«ö9Ș^îåŠS~ïW®Ø{à­;¡§šìv \ šþñìw§¹H0<Ÿ[À²ÿ”,ÐX÷Á'L=@ïÍh]þg´¯»Îù)žÿuh5ýü.B„Çœ‰ðçžBrà H¼ûŸP² ­‚b®Ã¤h8þGÏÿ^Ÿ[oª©Ý_5|ÇB{¾ã™[óù‘–âÞ—ï…Écróþ=þ;þwÞøg‚û²Áøèìì 744DTgCOM™"@¿ 7lI™‡$ä¨ðDë{À2ýfP‰€y_›N§kXíO €.% Ô×ôx] ä18† "“ÄþÅ÷aßk¿´Zù õþa!óÊSï_1Ú¶|Y%ê1ù‹ uþ¤m—fÉw¹Ü?:êŒo@s¶e0vîù?ñ ^{ë=OÆŸAø¼ÀŸýìþRæ_è« >7c#^ybŠâŽ=ªp¾Bæd %ªÒ·ÈïFÚÛ»;£iÝ$›6Úµÿ\ö¯­ÿ¼Îÿe™öð/à¼Q¨LÀ»ˆÞ:' l" GÞü޾w+ä×è7óSD€†èÔët5â‹ÿŒì®ù0²i('¡öÒ_öHtìÍv}rMîRO×â÷„A¥¶b"ÍK®ðçDðï7xRž¿S̽8 E9ÀÚÐ!ˆ@†%ËöÓ½…êâñx”¼†“€^Ãrº 3¼R; ãIPI‹a_46¬ÔÅ4ÛÙŠ=óoFã»7;õþ‚ÓÔcö®äzÿ|YÿwžõPp¯ˆY- uìÕP§Þ¥f@àÇl<Œ,ƒOÌÍüy ¿à/ëû‹û9åT¨WD4éUäþmî¡ÞWUlÀOˆª ïÆ`ô‰Säø_ rhÙßï±CÜõ_4ÿÓÔž ~ª)ÈþŸ‹s¸a`Ö0XŽýh|å›hYýäǨ›xÔpÌggAÝ¥?CæÃ«\÷4j.ø!'•¢åÚZyœ<'¥»bÌ¢‹-q_Ô€ïñS©TØ|#nÿüó.¤€<çü²€$䍯Í ®ÖÄét:”H$êÍ[ŒKêý CòúBÁÞ¯\ \‹w5€k)ƒ;6#ÝÞ„ÏÿG×=œãôoþ©–É=7û³»*ü{þc ü3þkÏå’û9ÍþŸƒ:t²å*0æ¾üžš;»÷í³9| Çs¯1à¯þÇs}¿ðû}ÛóãçdéÝ5ú¹@^ü Õ%ðóW øvUV3NOx/švÇÖ[ûï5÷+Gìm߻ɯ N¬€cªy²ÿªÒsñÃËíh°Uþ”Ð4àçÆ€¾D€á4fxŸZ‚Ø)ŸÆ°«ƒÈqþÄÑgÐ[_ñÕ´.W›ñŸÜó”ÃÄýlzÉÔôû{¿=¨÷5¼ŒV×u²±ˆ2<(¶DþN"¦CrTèâA'*oóA&,iH Í 1@^ÀŸÏ¨äXðT“ (pËQ¾‘<º;žù9âÛžsÿ°O½E›ýtøjý5–ù§a·»?ì§}êøËÍ·ˆ~ÜÆÍÛð¿÷<ŒÛvBÏèpôÞÎOµ,þ%ð/ØWmu…¥gï‘“ÝÞÃ~ø˜ú±{Õoøë…¨Ä@¤Qu[·BífË©¼:´ìq$›Vå” i^ñ„¹ŠzYÝ¿"tÈ«0ÿâ Ø¹åIìÞö4œó üÈw¡Õ8f$ÎñÞð/÷8å=·ùˆoË?ñgo9/ÈÓq€„X4 q¥0˜@8Vb±˜ÒÑÑ‘³CÌþK@Žê‰3Öœw¢ ›ì¡ÎÎNÒ ìgªçW Ðð­eZ¥ý:9äè8¸;Ÿù)¾‘×éŸÊþÕjÿ,ëOŠ}•˜Çá?œõ'í»F]uÆ—-wÿ€q¤ù(î|ðqÌ_¼Ôü9 ?içÇ$ýŒ8¿ø Éø½`ߪ¿·Ž­ª9@_¡1—\¬E¢¢2S9瘪~B(>›DñR ˆ“â)2¯Ý 7bÛ{3 +Ù >Ø^S­îf"ãû7£mç[¶Ôßný§º³ÿÇÊNÁo÷íí biõ0ŸÏu€HèÖßzÍK~‹£ïߎaWÝŒ†i×™!¦ö¸µü÷ý¤ô¿ëǬ”ýo9¯'!6j¤`(Ò>ààŸ–›Ê. 9ªap“?VÿÏÊs•H{{{½ù³}  1@ÕVmÂòõ$-×Â&]†{~“] #¾ÿì|êHZn;ù‡5wæ?¢9F€bæ®b$ÿ~F´ŽŸgý£&Pt¤ÿ4óï—õ¯ õ´¯B{n »"‘Äë‹—à–»E2•¤è!§Îßv‰WìN"ðçæ~}øüA`_ÌìÛ`ŸOv,ñÛ÷ª+†Š™WüJé§À°â®Z ¶Œ«‚6«ùê_½ŽãåR¦^5‰KìØ"‚»þ_˜ÆÇDþð;/ +Õüôµš »11#qŸû"ÚÖÝ€A—ü5cfXŒB×µJ0í«äõ°šdôü—RZ[è|¸×’†Ký³Ù,iHÍ=ˆ8NS/@MÓüJŠ KrH@ŽŠIñ¸BÜ>y1þP˜Ûg˜)B¼V¿˜ÀS(ÃRl ;^ ¹8ö øßþø·‘>²Æqú÷ióWÑNÿ…ŒþˆäŸƒ ü²þ£/‚6ó+Pj‡~ÔÚ›ñèÏaÙÚuÐ3¢É_n¿"pÇø¾ßÎ/ôû~ÞRgì©›eÒ¹Ë>?†ü¸jü9prÅÉþ‹ý|‡ÖŽ­ ]7rcµaƒD¡-«neÿé-›­* T[ΑjkDÇÞ6i¨ ÷ª(ÿW{v*(%¾ÎU·Y ï@¿jý. Ë€ü;²Â¡ìÜö4>üð ˜õ} ¼è;Ðjú÷ ð_mrz¹¯éÛûÂb[¡z¼_g€ ë'¨„€&> €Á+åÏÿÄP‹D"Ô ´GÙ$É! 9J4Ü€™|(¬-H-QÍ7óσ>à{¾_¿Sn8â””¼m¯”¼¯÷ Ú›Û^X@ƒLYx ¹@—o´ïY‹mÙÖ-V]?ËÊqð 9mþBð¯\ð¯å´÷£r­ÆeôG•æÍÎúGúCöeh'$°µ¹öîyè Ì]ð&ÚZÛ]Àßzì­óçL–ö¸ß:~!Ž©J.à·Øs ïû<«¯z^»¯äÑŽ+,%+zš¹ öE@o£?áçp8„ººÔDc¨©‰‚$y Ý{ÑHºùæ™lãÆžP՛瞈sMk_EÛÖ9¶ô_åÀ_ ŽEö¿«a% ²nYæ`—ÏÓ#›8‚æ·ö OcØÇoCí¸sluQo¬-Ç[Ý¥«¤Ù_yÑ”Ï[¿ïUâŠ5ý¢ò‰ú¹®ò³Hð׈ûf‚R©T4‹E ÀÛ’A<„Nbx‘^’£ŠúŠ÷G‰D-›øyY?‡ÒžXÀKQ TÒâò«…ͯ–qtÓbìxê[0rÀDÈúWl›¿bþ(øÓšüWˆ‹¿-ùÏ@9ábh§)oÖÿý5ëðÇÛîÇÆFZj^GîïHÒsåþ}ø›å§`Þ®ßwäüvv_µŽ™Ê@?ݼ±Ì¾/Øç.Wp°nÔ󌾅U,§ÿÆäG0h` 6oýêkѯ_L€ß¯¡Áü¹ýÍŸkkj%‚'kÔW@‘7þ—ˆ¤Ú mËBzrlÓ?Åý·6Ï•‘ý/•°«I„®YÅMdTóZìyðÔOû†]ùKDŽ>æ¾/gþý€^5ü÷îuR(¾{I¿óF€~&“‰˜#Ì»°v€¶üëþ¥@rTKì ÌŸªª¼Ÿüuñx¼–›æÿ~ SPw€b|ž äȪmq’θåG6¼]Ï|Çÿ‡lðÏÛüñì?ÿ9ä‘鿀¯ ÿ–œßÏè¹ü«a¬Sɸêô¯AwIðqj>Ї/¾ñRÉǸEËý]}àºÍý~µüÜð‚zëwsyÓT+»¯±ã¥ „‰Êˆ€|`ŸÎ}š­±~&€ß~®©‰ ¾¦5µQÔÆj0xP 2ú7`àÀ~&àˆúÚš.žD@•”;¶¶l~Íç£ódýûšÜ?ô‹.ûô›û[ÚÏkùUVcoeú5ë÷¢ä_Ìô;™™¥q“%Yó\ 4ãOƒ ãNÄ)&𯯫¥&ªš jw†!( Ø3ô¿DÙÕÑ™@¼£Ã¼uÒûDg’>G2;‰ÒÙ,’‰$ýΆùýæó™t†¶ª‰Eé{“ÇÑh„~@8b>G(aAˆ‹úú:JbÔÕÖQƒÁp8ì‹1KÉæÅÇBØrÇS=›BëÖ¥Ð;÷ÓøÂÛŠr@ˆÅžý·³êFyB…Ñ»i¥PœwûЛa)xYõ P˜i Ê”¤, ¾ûžü$Ú¦~ #?öÿª\Qkˆ¬¡/?H•£üÇØ».v\ ê§Üð–˜ë Q"ùgC?Éþ“{3ö+…€¿ÌúK@ŽJŒ1‚ó¿Â³ýæÐâñxC:&=@óÖî—Rƒ_ª@WÈJwÆ•£<£iÝ«Ø3÷Ç9àŸ˜ýñšMóÿ•höçÔûÇü]þEÉ6eìePOûÔ:£¿„ èîlæ¾²&Èãz¨|¾š€–ƒ^º³WÉzõËý9ð÷šù‰ÙþñÄÌ8Ÿ´Ÿ?/ºø»=&ã'’~"Ó¯'æ{&À?éÄј2a&žr¡ò,¥D=J§LZ7ò¸3‘‘£Í8|¸MGšq¨©‡àð‘#8d>×ï -ýD’¢`¬8qË€Ð7ÐùNyövšylG lÞHiÃ@ 31ï‰wÁ пbшy‹Ñ{RWª(u* èÉ’ªÎ}›Ð¼ò^«(1]ä1‡šcèŠ.ŠásPK\7zü{‰^`(NY€ª:jªЙW€y,2Ì+ ¾þ^lÝ6£nx “.¶Ì^ÆÕ®«a/#ÿ±!X‚öÍÞò\ïã õ@ &¤ xÔŒË3·1#xƒ\ÿ þ% G…,ì‰K¤=™L&œL&ëÌßE¼ýE‹U”º8È…DŽbqûÿð¥_QÙuƒO½?Éú“L¿]óÏ]þÃŽä?d¾fÚç¡¿ÊúGúŒM[wàϷ߇ÍÛwù:üûšüÙr 6WcÖ¿P¶ŸKü©[»æ€{òXýš¦:’U \²~KÊOÀo,ŘÑÃ0fÔpŒ= ãNƒ~ uÝŽifFSóQÔ¥æävðp<Œ}áÃhmo'-[»‹1ó^¯ Uð á:6À¯öÀ|˜5°ßü¾ä„n‰¢€”AŒ> Ç Áð¡C0xàjx8dÈ` <˜* Š"e_KÌÏèØ»ÊD³[)ðkÂKÿ©0'DQ±uht›±žávÝë¬Þƒ€$¸,Œ È œù}†{$âÃG¯ÆÀóÿ Ã/ÿÔHmw÷+}ü—ãßx,Aª•q¬‹Éúû~ï/þŽ(`‚ýSPù?ñ # €H$â•ÿË‹BrTúˆÇã\òO'-™ÔétZa}@#íííõüüûeY‚dþ~¥…6gÅ0Þ÷öc©æ¬FàŸ¹¶ ªP>ÝõR7o`Ø„Q¼Žv× BN›€»¶¦ÑHýêë1þ¤1˜yÚTœ6u"Æœ0Ú|>Röë,¹#ôÃKÝ™3𨡠þ-o HÐ0É…gˆn‚žâ§`=—uÐ2ÿ·¨”žŒAe"xm™c «ȯÓû^ÃοÌÄðO<ˆ%´ ”­+gȽLïÿrwï^YÜ#³=¿jbRâ C2˜Å Æ’£zc ŸødÂæ0N×ttt4p Ü¬±IWý*Ñø/há” hi£m÷ì1ÁúÈêÿêÿÞz&õWø'5ÿÞzš5ÍB™òEhS?îÖmÜŒ?Ü|7ö8è|¤æqøWûFÖ¿x™¿PÛϲý¤>Ÿ¼&ÄH·îóÉô“7 ™ŸÈÐgN›Œ3gL3Aÿ’ç{2eÕêÀ¿nã&¬X½ï,{éT/#-Ø«Šû9Ãö•€?S<¨®ä ßܾáô•w“†õmx YÁ:ÿCò¼æ¢¡!™íñNz;r´ ;÷îÇ‚7ßÿcGÀYgLÃÙ³f`ÂÉ'ÓR bR¨u±…b&~í¼`gþ©ñ_¸ÖüO-3錰9ÅŸ•‰Sà7ÒæM#6æ/3æóšE¼!ªŒÛªβ]€ÂjÅpÌu½ç|Û¿‡¡Wþá†!=ºnW`íé= Jåèù㤬»÷çbºxßÏç9%•J‘6€R÷/‚|Ò€žÐï±’£’b oÈî¹Ëg,×ö¯˜ÚÊ U@>ö²;~Õ´8y±dÏKñ[°{îÏ‘j\îj¹e»ýW*ø/hö'HþM a¨$›¨9à?ÚêÌï@=áLß·'dÝ3/ÎǃOÎÍ•ü+V™âuø¯Ò¬©2žé'÷aªpˆnäljntG²üšya;Š÷͘6 Ã*é{’sr¸©cÛŽ]Xµî,~w%R™t.Hγ?Rr²ùŠòÜ»€½’{>¥ˆ=˜oì.ˆû}þFúvƒž;ño €€Š(-pL]è xÔ²`}*XUN/ÅÊ·cÏCØóâë˜3ï5ú^'™çòœ3¦ã´S'cô¨á1l(ú5ÔƒCuƒ0òú?¢cêuHm~ú¡•@ÇóÔĨŠÇRëó¡Úß^á#d•ÐùÞæ½’¥àß@¶b@,`ÿ^Å{™”-•ç-¼D€Ÿ€· ÔYÇrNÉóºjýܶê6$ö½‹Ÿü_ÔŒžAçåñ V¥ü_Ž|Ǽ«êÛ ½¹(ý÷{ Sþ’ÒàH,#~`$ãOUø³2²  $䨮Á&?—ü»~6Gm<¯ç¸ÔÀÓ• ŒËðzó8ËÑõ‘½·$þÞl¿%kdsS·@?#G ÅŒS'bâø“pÂÈá%IûSé4vìÚ‹›¶`Ó–XkÞï݈¶ÿËü¹hÌ öó}À÷÷vûF×V®ì 0ðÒ)@À*jqÊásHÃ)‘§ê79 ‘‹nÕׇR †-'p¬4KO°kï~z{ò¹—QS¥-Ox ¦O‚iS&bÈàA× 5Z‡úS¯¦\l¼é=+Ùý62{év|5v®4 åªõo#h×™_1kþ»ÒNy€b»<€¦ý3‚=€Q>ÿl¥çÞBA~5€"ü.ËëÎßf—aïýÅknÇÀ3np•tw—mu%ø¯VÀ_È¿‹¿ß{%Ýü~ÏïÉz™Éd" D@eÿÜt–€ÿˆUr¥x„l( 9ª…àlá¤æ§&‘HD…¶€%/ŪŠ]\ ™ VSp—£¸‘M'°ë¹_ ±ïÍða _ U+ø¯µÍþ@Á¿Pï?âl¨³¾¥Ö?û¼eûNüïÝ`Í[—?£?h.uçP²òÁ>àO½ÌŸ€}nêljëµÖßr\E€Ƽ…Íß]tîLœ~Ú 5`mM¬èï—H&±~ãf¼¹ä=|°e;ö8Œ£ñvwË<øTì󌾢}×sp¯xa¿’÷Ò €»E=?"ôå»e âJ!{û Ød/°­b•8ÊyÅFžCÐÎüYg)éÎdë6m§·§_x#‡ ÂÈ‘Cpά8÷¬3pòØ1Y­~´)×À˜x9ô¶Èì[…ÔúÇ¡YGÕ;„\£jð©Ç ª•–XDó €Ÿ•X<‡ÉzNyDonŸ•"Ÿ£Ь‡*iÈ€VuHýœÍ¶áðÜ¿GbïrŒ¸î·Ýê Aµ}‰(ÖH;èu^³lñµÞr€÷ %&ÖG @À?Qhæ C”¤€`¨6$! 9z;¾[[[›Ê³]Ì€LòºŽŽŽ˜àúxëÊBWL)A©ï]Ìë{»w­\ìK!¦tlâŸßú¬ þ5Á›Ü*ü‹mþj<²ÿ˜möG%ÿÀ˜€`ÒЦÞdýã|Æü…oá·ž¢ýÞ-àÏ`%Íî›ïCûØk¾FÕ–õ÷Jý½ÀŸKüýdþšm\Ê®'Ý’;’¿9|>zÑ9˜:y% ÍKKÄ;:°jí¼0!VoØ„ÎDÊ—ºèw®@/àWú±Á¾ÕÏÉ軞ó3ñóJýƒ@¼R +n×ÔJð´Ðìc¤ûn Fä\9`öy0Ø1u‘Š©É'uÆÞCô¶rí&:§ú÷«ÇU—\ˆ+.9ãN:‘•άUZÚ€1ÐúŸ€è”ëmÙ‹äº9Èl ¤š­M5› D¥@3þD>«Z>ŽO@†©ÒÔŸÂà%ð0º‹züûíêFjìXîƒ2¨‰ÃQpb }å-صNøÜCˆ Ýå5VªîÊ¿o‘Ç´²È¢B{^?c¿BçÖ»?æF€Éd2lÞÇHöŸ“D-Lº0€ V€Þ­˜þ’£Â‚ ­ÿç“>•J…2™L9é£Amõвż®§M É©äÂV™ƒ€ÿöMOÒMbˆÿˆhøW5àŸµù£¦a¹Nÿ6ø7…:ý»PO¹Ì÷­‰ÌüÇæ`ÎK¯#Ù™ð€Gò¯Ú²+Õ¦1wòjÊúûÕø[Žþðç ŸËüyÍ?þ®Ú~&ó6t&žr".…8Ö›ÿÉê>rΙGoýêkñw×^ Î…Q#†a`ÿþA‹}SmÀXÔ^øCç| ©o"ýÁ\dW›oÞjµ$׃Êkþ éd8>T `=Í^*/p À¿ßß(¬#€®8—lHTb`¯K\„™QŸu'Ÿk&ÝJ• ¦«©æ_&/ªûb‚}’õ§¢FØb> ¡_[@¹Ù–€†ÿôÖÖC‹Çãuæ}Œ;'{¥÷b½¾·=`¾Å-_ÍQ¾@ç}Îë$_ò;?C¾Rl¿Cwƒ°ÚÆG¥|ŸJôXØóâÑúÁS6ø©ð§.ÜZ€Úæ€ü§Í•ý Nÿ`-ÂbC žùPGÌð}ëÃGšñ?·ß%ï¯11›;EIþ«Ãè¯XàOÛ÷i!W}¿möç+ó×1Éý§O›Œ3N›Œh4Z𻲠±© «×nÄ»+W›·5hwÀ¿Žß'˯X ßP'ï¨à÷‚}ŸŒ~ÐW*àwsÅÉ2 ÞC†§<ÀpŠG-`)Aa¹„}Oš­WluyÐOàáÙÏã‘§_À‰'ŒÄÅç‰3OŸ†‰ãÇå-QÂ1D'^Io™C µåUd¶>=ÑÈÊ|U í@»¨4MnBè)»\Ç ~] zóòPrIƒ©¦ÈÄ7 ̦âÃû/ÆëîÆ ³¿`u[¨ràßÝ5WŽãð{÷˜A’þ ë,Èä<&f~~ïïÝ›²Ï#80‰DðgË4(±XŒÞ:::ü¤ÿòB—€•cà´T„‰6's½ ÂÝzó"[òùùRÊbKº Úý~–‹yyÇÁwAÓŠ»Ì`Ân½e×ý‡¬ÇôVñà?FÍý\†~à¿á$h³¾ eÈDß·Þ¶s7þÛÿ¶lw×û .ÿj€Ë5ËÜOuIýÃ4ËŸø“MÖœ£gÍ8gΜ†“NeÿÂ"™LbùªuxóeXµ~öô»<|´ŸÓŒÛf{ίhF_ \e†€’©ì^T‚¡Õj@³Yö:ó¹»÷cçžçñè³/búÄñ”¸øÂs0vôÈü›§a“éMŸñY¤w¾Ôêû ·ï²© 2G 6_íF; ˜óZ'ÆžÌ# ;D@÷1|·ÿÐ6d‡–”¨Ì y:Zì9BŸ›^üÒ‡6aØÕ¿¢FŒÕþ«m¿ ÷6•u.ü|° ùl…ˆ^`þždþ£Ä€+øg¦€Ôþ%’€•8¸ë?ëíI6À¼ l>& €kRô‘Ï Ÿ@È.°k¹Z1Þ•\bPi ü‘ ¯áÀÂßé£.ðO³ÿøW"øW,é®Õœ×øÇ̧jµÛüÔ,k‚þPÏø?Púû×».]¾ ·ßû7ìÙU|]þµª‘ü{ þV«Okø™«(¤åÔø‹À?$Ô÷‹Àÿ² ÏÄùgþ õ‡ /_5âÙ^Åk‹—¡¹¥ ÉTÒîanãrUqƒþ‚Y~>· ~ìËͶ?ˆåX\Uüw”F.1`‘Y×›¸Ë¸ @·Ì÷ùzCA6'“ÄÒ ë÷ÙTï¯Û„U6ã‰ç_Æø±cð‰k.ÃùgŸ‘W]¢ÖCtê'™t 2¾äŠ¿"Û¸ŠLVëŸÇ¾¿s-h–‘ Aˆ€¬›ÛB—‰@SÅÞÿ^" ¨$@e>”ÞÐbëò?!st'F~æNh5ý«ÄÉ!G¹€±ÆÙœð*o‹iÓÍöÿj*•Š‘šÑ€©Ô<À_^ð’£Rc‹ÏLú) ²Ÿ ¶±C¿R¾î,Œåð èêg–ãß.7#Öhß½{žù.Œd£-û1ó?.ýרs ë7íã‹Ö«àßjãÌþjl³¿ð?æR(3¾¥f ï[Ï™÷*xâY´µÅ}À¿•í'@Ø"ªÇåß›õW™‚Öï³v~´¾?Tð'[šX,Š‹Î>—^xŽ úƒ®o"ñoimÅÚõ›ðäs/aͦ-´\†“© ýÁY~ç±âýð—Pòü¬øB?’< BIݽÂ=tRÉïRX-ú±Ë}Þ¼^Z[âX¹ö¬\÷b5|ì²àcW_†1£G"ì×B’\áÂ'Go™ƒ‘\q²ûß3ÃA+³(dóÙ6H ™×[ÖŠ\@‰@NÇ€nVÚ*=ü‡ätiì+R°ÏJȽø½6ÉÔìÜövݹ£>û7ĆOpŸséã#ÉŠãìœxI€ þbZú>Äé 1U°.dMV˜€‰«™LFIÈQéÃø ¯"™3žåg¥QR@Ô=º®½@” † µ2É—]/¶ ŠkÙÅM‚Ÿ€\Ë;ÒíMØúÈçüsÏeÿωà_eÓŠ ²* üÇø8mþÔ˜‰3"–Ü—ƒÿq×A9ís& È•µêæµvÏÃOzÌþ¼õþÕ'ùÏçìO@½f›ûÙ¨¨Œÿ°¡ƒ1kÆ©¸èÜ3 ç¯X"Y‹­;vaé{ïãóØ65·€Èñöí95ýb¦Ÿy*äÖò;¦}*øöóðwk~> ¿˜Ã¨(þ„€MÐ Ý~k>§9È“w mû § 1P‰Žžš·O½øΞ~*>~Íe˜>uR°q ÙX Ÿ‚ðu7[DÀú9ÈlŸ#ÓÂ|;D"@õQ¤Y<Œ©zAw#£k!íØ€K ®JÈâÏÀ<ÈÙ#«°÷ž 1⦧QwÊù.sÀj2Ó“ßSŽž8gA$@W÷äâcøÓé4ñˆŒ?Ã*ó ëà !² @rTêÜ;>XÀšx<^K‹|Jl÷WHVh‚Œÿ*a1êŽéŸ\ä‹[þš þP°ÚýÙàŸš@T.øÛüEø9mþÆßõ´›,?ÏȘ õ–;ÀË — “N;࿊ëý ü‘¬¿²À~ˆß3àO7>ÀÄð!8÷Ì8ûô©0÷$„s0¦F~¯¿ù¿»„T1„C%Ôö[Ù~úUKÞ¯¨è÷ÖòKYƒþrmEB€¢Šf‹54ëÂrÛPPPp2€ç¶ä^q{ê–­Ze«×cì¨á¸üâópÅ%`ôÈñ—¡á¿@æ´Ï ¹þd¶< #Û!ÂE‰€,M—»ÓÎ2tÿãÙSà_éÞiÑXIÉAÐCÎJ¬K€u\­P«§`ßÃÅðf£ß´ki+F¹Þ–@æKžHb ²®¥|å·ÅîUÅóšD P “ÉDb±Q(üÆKÂá°_+@@ª$ G寒ÖÖV"Ýá€dF«æ]Œ”Ð톦S@î]h*aÑ ò' byÆö'ÿ‰}‹í ¿&dÿéÏü^À¿Ri²?ðO2ÿa7øŸüy¨§Þ`þ.·µ¦ÿqóxgÅ*è `Àif­*ëýóËýU»¥ŸüCÖ󬥟×ܯ¾¾W^z>N›<õuµyç yý«o,ƼWÞÀ–{Ñ™€¿æý4áÈøíŒ¿'Óï ú%à/_°Í\ËEäÖO›“¢€K ˆ¾¬L€Ü«ÂåÊóùÝÄ}=‹§^˜‹Îž‰ÏýÝÇqâØàÞö¡¡úÈ?#{Ú§‘Xõ2[ç˜o“rÜEò!kþ”¡­©€¾6k}ënªzüé®à]tV¡1?Mwx5òšŒnÿCOß½ó~ 8ë&3G+/îUYÒ X©¸½s-uµMv1ª?@ñFÖgrŸL&# ˆÙ?‘úÛÞ<á°ÆHx¿V€¾S^IÈQ¿_¿~èìì¤lž¹a¦mÍǵÄð#›Í*¥fÿK]4ʹ°ôÔ"åD% Pž±ï¿¢eíV&ˆgþYË?ü+þà¿WŽ|!ðª¥õþ¾àÆw N¸Ò÷m>‚ßüñVlغÖ;[àßcö§VG½¿_Ö_cª…°ÆÚùñü¹À üÉ\ Gøö² 1kÆTÔÄ¢yçj2™Â¼ùoà±§_@SK‹‰‘ô`àOMü¬l¿bKü@?ý¿'Ë/çùæ•‘\=4á½þ¶  ð°É‹àd€!ºò m Z[ãxaÁ[ôvîÓðåÏ~ §Nšà\ãâ Š˜ÁãP÷Ñÿ ý̯¡óíÿAfÏ‹°¿¯J?žÆ 5MÛ ÒøBü buHJ˜Q ‘)jû­ôöi·•þ¾äwD @6£YV*ÐôÒW¡'š1ðü¯C+Ð! 7öWÇÚ¸R÷Orô !D RÜímódÿ©ZØÄáP(ÔÂT@ÊõH éÉd ‘â´—„€$äè­8B&0Sˆ‹©ï©mkk#ŽŸÀ^ìàgè]üT–¹Ÿ+†MI¼Ý¼Æ%A°»&A~ß·+r«JÝ ôÖF:þ¿þK[öÌþ‚ÛÅ´ûë*ø'såÌŸBs®ïÛ’6¿üý­8ÐØþ `VT+h×¢WAÖŸ~w?¹?½9uþøkšêþÑXŸ; w&bZùí?؈7/Á͆ãòîþˆ²Ž+—øƒ’ÖUe}÷™~¹Qî€ä3©K­û/#!àO&(}DÏ¢ °ö³  ¦N ~éÊuô6eüÉøâ×Óv‚µµ5¾_Eí?u×þ‰z$–ÝŽì¾EæÇ¤Ù5hÍ+ s" «ˆ! 늶â„Û$Ð_ Ð+²ÿ|Üà `x|Hi@Fµ¦²ÂH£ „lÛ! ¹â'ÐjT˜®&P-eþÕ ú½çÐ/£/{j¢” jÉM£‡ù{‚:;;IÀÉø“–¹Œ ÁFÓ4¿Y 9*9®ˆ÷$ûoNl-•JÕ›·¨;Ê?o‹é_Z‰‹§_i‚tîÒNÛ±ó±ÏØàßüB»?^  ŽÿÕþM°yÖ œpŽïÛ®ÙðþýÏw¢éH36…Ë韸â«ZUÔûr÷çrO\Ò}ëüÙt ™ŽéS'àšK/Dÿ~õy?wϾýX¸x)š=¶ðs5WæoØeªÛÉŸ“•çþJ6Vì3«·îèý-£Ÿo€]&à´=SX ¿î@®!g©0°qëüò?nÅÔI§àÓ¿ç53 õ¿ ©­ ‘Z÷2ûÓÏW9*¦ ,Ó@ÃH[sƒµ¤pYl¨û îcþ ½¥XÀ}HY@Èp|2 D9ÚWü™ÎÃñ±?@«Xû„jÓ²‹B域B¤€_rÊÛú¯+Ÿ#¾Q˜c¬î_a-ÄÉúM €X,¦ttt ð/C9$ G9ƒ‹8Y‰¦'”L&kYÏOSXŒ#)õûÅô5-¶Œ +Ú=¼jT41Qm#“Œcû#_ueþCLö/¶ûÓ„vJ%€nWM7ÛwÍ¿ ü«V€øY? ÿï®X?Þz޶¶1ð_½NÿAY"ïׄ:[îÏjþ½rÿ¬ ª¦L<—ä\Œ=2Gõ#ަæ£xfÞ«˜¿p ösõ„/ðwLýDàŸ“í$þŽéšœëÇŽôAƒ•rø=dµdd7ô¶䪺çÿñ ï²nÓV¬ß´ Ó¦ŒÇ×_…óΜI[ZúÈøK{6R[_CâßAO5Cpñµ>—}WƒÒ䪶À?UØ¢[]èTÑ…íx7΋RÄïsHXÜ ĽTë0&±áìëlÆÈþ‚PÃЊm==ˆšìhK ¦Ÿ:Ån‡ZÌþFîi*›œ êŽå5ð©àßK&x?Ë|>dÞ¢æÐÚÚÚÀ˘€ðË’£’c <îÿŒ ÐH @óçp!wÐr/”ù—ƒèöþÔ'ÀÊQ:øß3ï7H]oƒüæVð\öïÕ“õø'`R%Ù±Õ_Œµú‹ àßú šùsžïÛ¾¹ä=Üz÷£àßëô_©fŘüY5‚ü‡òÊý‡ˆë.ÿ&O8¢ù¨wWÿGg?‡_{ û þ#¿ ™¿ËKA‚þÞþ†0+õ4(Š«½ 'èâIZô)Ž*€\jY¸UÒr—·Ô Û'À|ë6nŦ­;1îÄQøú?C½.ȼÉùèH-¢§~‘“.DâýGZ};ìT¸a)‡èûR’2k)èç«¶7€R‚7@ɾ æ%èñ4˜:JgŠXÏéB—€Ôާ±ï±Œ¼én„û<®€{{ϼð šý àF‰Dyfæ® P 0ÏÄ‹Qt÷5=MHðßµ¡§“8üî“hYsŸíøo×þ{ÚýqÃ?¥›Îžÿ來ÿ Uÿ$óÞ/ Œ<Ý÷m,z·˜à?ïpÀ¿ÐæOSµªpú/6ë¡rÿS ÈýiÛQóµ×\z.8ëtD"Ám½R©^~íMüåÇÑÉZùþ¼k‚Ÿ©ŸU¾(%þ½w•`ö:Àý<ªë±ã@À¸jt]e­Éš«[×a*Á¦­»ñ“_ÿ S'Ÿ‚o}ù&œ:y-—ÉùØÚ¨¹à{ˆžv#:þú¾Åæ{¥ ÀúéAõ¨X7"¼Ê þ»ziˆö _Â}¼þ쟓ùð%ì{ô yã݈ =¥Oƒ; ðsÉ2Ür׃8ÒÜj.!*íóàÏaذ¡˜yÚ©+÷0ÕGte¯ZÌ~Ýûzïë˜ç—šÉdb&à3@ê@n„@pö_’’£RF<§Òqš[e“:ÒÞÞNŠnCù²pÞ@Q (÷¾¶ÜzJ Ùò²ìVж}Ìÿ1•ôkŠ“ýp§5¸Ýß1_=|Á„fú à§àŸ>ŽXàßÜø$¥…¡^ðoP†Mõ}Û—_·Ýû7tv$rÀ¿èô¯U”ù\ÃPÖŸÞÚO”û“<}Ò\sùE2(ØÀ‹´G|ízÜrÇÃ8pø°ø3Ï€¿ÂŒýÜÀŸµõó“ùËMp/Ƹý Ê’ý—HØaÃS" °45Éô«Ì+À&  q"À9ë?؆ïýì÷¸ô‚³ðÙ¿»N9ÉW öúëoGjÓ«H®ø+ô£ë™A!\5€î‰môãtä]>ð¯tãï ~hÙ\¼C¸‘dd™9 9ÔÙƒ‹°ÿ‰¯`Ä÷!:|BŸœ:XúÞ*Üÿ·ÙX¿y»eËLbI¼Û±g?|üYœxÂ( 8@ÆšªÞ:ù›OkDÏ'ÀïoýÊ  ZSSR­…\a>ôq$!†âä5 _rTGPQi;a¢ñxœôÔѺ¨Kñ(ìò½w¹>¯+$€dλ°a!¦O}ÍÕî×ý@ U0øW8ø¯¡À_a’’ý7Ì{šù×-í²zöOÁÿ‹óâ¶ûG²Ó þUšù÷ÿÌy¾Ò%ÿæwæÿ¡p¨¨¬ÿˆáCpõ¥”û¯^»›3ï®ZÝÚXøoÆ?Hæ/ü+ 4û ľpZrTl#­[×c!"ðÆÛïáíå«ð‰+/Åçoü8€¹È¤+>ñ\$V<€äª[,QjH(¨TË:P±Sé\ Ç P)S(-Ãß+ @þY¤ Y32ìkëœ ¿k|fÿF~îDŸØÃ§ûØ%Èç,yo%æ<ÿŠym¼ËÖ(îÓ¤9­L̓´lõzÜõÐøé¾)cMïÑó]_…’nA*€ –~Dð;•˜ƒ›@?°o¨D£QR  Âßø¯/EwIÈÑw¶`¼öŸOpó¹)pöøÅù•;ðu5›ß“ß·§=úêHµÆþ×o‘lrdÿBÝHsJ*¢ÝŸ þ üƒ?%ʤÿQGú¯²#c‚ÿ…2Ê_öÿÒ‚E¸íÞÇ@Úç þ«AòOÚ†iÖßúÄxJ¬õ÷fýÓæ®ýâóÎÀŸ‡º×s2n£OÍÅ #•LuøËúþ üâ¾³’ÿ{ˆ`‹¬›0TöÏ/ŽH%Ó˜ýü«X¼lnúä5¸îòK|•X?Ô\ð}„O¾oþ?èM+í¶€ EÆ!F@Ÿ3VŸ=…u  +é|ˆÓÕH°u‚’Ëüs!ƒ@ììóyÓC=Vp,·íØþ6‹–¾‡xqÕe8åä1y?ïñ9/àéyóqèHŒ,›cv{Djµfû )ó¯¸$ùïû»w7 ðR ¹K8Ø„[îz/Ì_ˆþÖW©?€_üšŽ†@bÍSH,ý tžÐÎ$!JÐï`¨ùKºyŽ”î2_þÈKð¯ÚÊK,ÅÙ_ňï?æžå-­m¸ëþGðÊ¢%8ÚÒæ6ÕZ74^î$Èäü’™L¦1çÅ×1yü8Ìœ~jÉû*9zôóó”±§:æy'Ƹü5üõ\ºO~ç÷>º®ÜO‹É{™{˜È€¢d}O§Ó ïÒÃ<€\]—0倕g¬.f@ LiXC÷ýyZÿu' u¥¬ ”–‚eß· ©d«€÷öhÞøö/ø©ü·éþÜÀnÃ^1à?䀞ù§µÿ«  þuKöOÜþ}ZÖ-Xø6þëöùºøç¹ªÿªjýÑ:ÿUçÚûyký¯ºø||äÜY&d3²îƒÍø¯[ïÅ®}ûs þƨöÆ×úÞ ü«Œ8žNQŽO»¶iû¾"‰ónëö½øö¿þ;®¿êb|áÓŸÀÈá>m﵈Íú".GÇ+ÿýÐ i[Bcx+BÚJ/ $zÉ[v/¿Ó!ÚKð6ôÛ¯eß9sðmœóm ÿämˆ ŸTÖ½LOÿ9Ï¿„ûŸÃ2þl/¤©î¬?ËüC’.|!5ÁþC‡qË=à/ø%êjk垥Å´¿"Dß‹¯õ+­%8Á\§Í¥>Ñì>P™ôŸÄ€ø‰"$! 9zmfØþº gSÀÚÎÎNª/ä@¾,}1nÿż¾‡ÿ­e ¼^ÕCoIë*}!oß½æÿÎÎì»ZþY [öo×ý£—Ûýqð¯pð_c9þ«1[ö/‚eÆ·Lð¶/ø_øÖ»øÃ_î5Á¿^õàßOòOþHæ_µTö¯i®¬ÿ¨QÃð™_Ñ#G~Îsƒú·§æâ¥×ßvTgšÕRLUL¸€¿õM%ð¯Ô«@ÈaW¶"€ÿ\$`XDÀÜWaÁ›Ëðí/×\~±/¹¦õ†AbŃH®¼Fê(;*ŒÈKàØéÛ hij€r€ÿBÓ× Þ‚y*ÃŒÉÓ™ý¯¡qÞO0ô· 2ää²ì+zjým>Ú‚7͵ã®GžÀ¾ƒM¬mcÅ?XÝb(ÙÉTPšÂÈP»%‚u¤ ÅÊþnß½¸õüö_¿' €*þ~ûj/ ÷sö÷{Ÿ|]¼ü>Ë\ŸMœŽv55x1²P=~$€"¿$䨨x¢ØD@&“ ™÷µ1î Ð[ 4(@õVû?9J™Î4¾÷7¤®wÕý‡T§Ý_ØÇôOíUð¯ à?æþi,óOÿC¬W¹ þ§|ê)5ŸÏÝx¿»b5~ûßwØF`Õþ]þÃàçFazoýŽ£ò¯¾ðÜ™¸ü#çÒ¬SÐxã­¥¸ý¾Çq°ñ°óÙ¶Üßê,¤ Àß–ûÛÇMÿ®:Çbºáå¶Ï…tsˆÝ*уˆ’ÚÖ­ƒÙÑÙ‰?ßñÞ~ï}|ë+ŸÅÉcÇ05•âúœØ¬/#4j&:ÿú¡wÌ8fyX@š¼kœîS” #8áö(bÛ®Ã-’ôx $¯ÄÊ0B@3ïS{^@ã¼(†]ÿg„-+×H$’X¼ä]<òäsXóÁVJzÒ°Ïè“N1 +ãOÄ(‘:[cXÊlÝÈâÝ÷×aÞ« ññ«.•s¯…®E²n…_òίÍwq ¹¥Óé0ñü ‘ {{â@D€âLÿä* 9z3n°Œ¿w’kííí ¤×g$ ÌnÊxû¥ F1߂ڛD¡s!†õXþJ‡W<‹–Õw»ÿyÝ¿hüç­ûï5ðogÅTþc–ùm÷¦² ü+t3nœ|”É×[Ä€glܲ ¿þó_ÙÎ,[Sà?ŸË(Ì2ÿ&è„}$ÿ,ë?dð@\{ÅE˜6i|àuJ²þw?ô.YŽt*íà![îbnߪ?ËvIs¿âÀ½Z„còÆ;ïï¬Ô¥•O6Ø—ï’@¶šHeõù~D@VåÖ÷Ö^z銵fÜÙ‰Ï~òj|úãWåššŸ5õ×ÿ‰÷ArÅY`_µHË À" W€â[*½º9 `°îœ0„rjF윃¦—ë0äºÿ@¨ÿÈŠY{—¿¿÷>üÞ_¿™€,üUðg1O³,GaÇÆ:Y¤ê=‰Y3¦bÔˆarÞUÈòá z}Оµ˜ëÚ sI×CæãX4%]¹y8mHžˆÅbèèèÈg(Á¿$䨌¸bgÿYg,%ÔÙÙYkþ)%Ó^J9@1¨˜×KG~±FJv(<š7¼Æwn¦‘^äÿ¼î_êþÕÞ4ýó¶ûSIößÊö+ØSé„¶ü#à_± Œ‘çCþ£ÆrÞrû®=øÑ¯þÉTÒeÕ þ©¹”ù5÷ »]þý$ÿ$Ë0ëô©¸öò‹Ð¯¾ÎÞHxçË‹ ÞÀ}>KþíϲþŽÜ_êü½Ço;?? ¯JÉ*^úE(«¼O(H0røMñ¾Î°ß[ -ŽKb€¯-:ï`1%ªn‚t’¥7,àGޱÆX”¬jp–-­í¸ó¡ÙxãíeøÕ¾ÇŒÊ]«k¢æ¼ï"4öt¼ô-‰,]®Ò;ƒŸf#ÍžP©ÙiÐ6^)Ï?¿l$€"–pC@F$6?Œ#æq|å¯ Õ êUð¿iË6ÜqߣX¸t{ÖüæåM–ê |`‹¦Ækþ]À_ñÌ{‹´¡¾ôàh´ µ½¿úÃm¸÷æ—•*%üöËÅ\—^#n?€|{{öX3×yÒ ¨…í6€üCqóy¿2€Þàå€Ë؃Gpggg?çv½m<_v¿ÐßR”kîjÝž÷喇nðHùKïƒÞ¾ÛûvæŸ~M¨û/Æ%æØ‚ÿ˜UóÏ[ýQðÏÛý™ÓdШç~ßÿ¹-ìv¸ßþÉo‘Τ¬M[•‚ßz&ùç.ÿÔPü‡Âa|âê‹qöÌÓ?£Ñüúßû°dåê\“?Å:ü˜ÙßãXî_è{@¾¢¸Á½¢8Œ¿F…ß²4&îÝ­t›N‘$ ?jâkD‡êã™àÞ´C3w#ÿc¬˜emÞ5«É"tëo7oß…¿ÿÇŸâ_ÿ®½âbÔÖÔx/,„O8ý¿²m/þ3²{^´4!à ë@;×Eض-Ð]¾J™NGw¦³ `x<ø=W£t¬¾•’¼ƒ¯ú•âëéé%Déö»ñøÓÏãéX±VhgJ?#¼Àß"‰¼Å&ìÏ`çYQ-À:W*½~6mÛ…;xßüÊMrÃRÅd@)à¿XRÁ»6ðØÌqiÿ—N§kL°Zˆ«ÌÀôûùÈ! 9z3†ˆ“1•J©L I$µâ9/$?*¥FÿXÇž~o¯@ö×uÆÁ7ïDÇÎÝuÿìVQuÿ"ø§uÿV¦Ÿšý©Qv‹¸Ál0Ô‹~å þmÁ¿þú¿úøª÷Û5ÿnÉÿðaƒñ™O\‰1£FÎÁ·–,Ç-w?XëÏÝý½rkc,ÿ*üj ïù"ÀWØ1S’Ë&Ô\°à=¦…œ¥}Ñ?àë†ðkç±ƒó «–]$X/7ƒ>_<1ЗœãÎËa|ˆR 0®›ÏŒ2ÈjºkŸ}Ë=båêõøú—>ƒ“Çžûþá4\:ß;‰e¿7¿Î*34¶®!—0T·/@½å˜Îükpðï5TIJvßñþŸ(ø|ÅÏ(ÜÓ{ r=ïÝ·s_Z€‡g?O¥þøC\/žñW=ÀŸýùÏwS Ã!idÐs-Su<üô<\zá9˜8þ$¹i©‚‘¯.¿Àj!è·§ R¹ Ï‘Na‘h4â†(‰€¨ˆ€¬€ÿ’£’I2qÉÎXmÊ¢­­­ „å[z³ßAõAb ³äÅxxÛÃrŠ¿g5H”]÷S-ˆA°TÐÎ\—ü*‘Üè)’¤iå3hÝ4Ç–ü“ÍXX0ÿŸïýºÿ<íþhö?Bÿmð¯g¡^øk(‘\C»d*…ßüñ6ljª^ðÏëýÍï¢Ù}Årø×û“¹6cê$\õ¥è×Pç;'ZÛÚñÈìç0÷Õ…èìH8{UŸ¬¿ãî|Èýó~Äû}òéߨÀwû<ïaÇTx”Þã+þì—ÉçøBkTäÓç kùÏŽðÍ&KÇÒÇj1`ÿËêzH‘è³ê"€Xˆeº îTÃ* й §yþ¿· ·îÄÿùÒ§qÅÅÂñîrX¸Ú³¿†ðˆÓåû0ûÙE¥ ª‘Ðmÿ¥ÄÞKËõÞå ëðï@Zž“5(£;åä%íK ­aœÿ]/S&Øä‰gð‚…ع{Ÿó¾ª³¿fÝwøû_?à’ˆÒ«_üñÚ‚ßßrV¯ß„~óˈE£žÏ67öcÏAÃgæ >ÿgÈì_HI( `ãÇ´`ß!À{=õø·yŠ$Y‹è¡$Þ ¯ÿ¡ÚÁ¨?ýS=²/yá•×ðГÏbëŽ=ÎüËÛÒOSþ¤åÿb¾yoQ@#]¡&¨ À¼>ölƒ?ƒ/ßôw¾-$娎x!&ëºóžù:$üD&’ ˜ !‚ ý|䀽Hì^Ìѳ¦­­­ŽLæbú„ú1½’½jž"üÞ_úXcïK¿Cç·\Ù~žýç¦Z%ÕýÓ  mÖî•@ ›lµûË@™ñ](£fæ¼ÿ<>‡:ÙC„V<«mƒÛêÿÜì/D?‘ý3³?¢€ 1 IŽI¿~õ¸áºË1yBp?í§æ¾lnz碥µMÿÞ¬¿eò'Êý-b¨oÕùú¹÷ ø‰ƒ³áç¯U¹:‚¿Ž(Lø&Šg3' ž çGýjü 7 ÀŸ²¿ °½á"põ#²ì±¦–j³ô^x?V6à"<ǽϨ‚ü Cè`>Z rDü‚ÅX¹f#~÷³`ü¸ÝJ9rí 8 Ÿºoÿ/«þ‡Ö÷ók޶ ´Ï±nRìà–L”kj+ÏyIîcèR€w HàÈ‚Ÿ˜ V-ê§^]–½ ¹†W¬\ƒÿ¾ã>ZïŸÉðœýàϤþào«~”.”?yI»€–)àƒ³çaæôSqÆi§ú=ÉQ„@¾ÎTùÚû«,àϧR©h}}}”á5›ÍZ{„Pˆª ÀJ‚¦©ì 9zc<- à0¢ €9‘kãñx"D€BD€!P,{ÙÀR‚b¹Z÷‰ ß ø%øß}íÛ^²€¿âÿÞ–¼î¿÷ŽšcúG³üÄñßÕî/ìj÷‡S> uâÕ9ï’Ngðâ‚Exâ¹—í÷µ”³¡ÿÏÞwHQžï?ß̶«ô^A) QEÅ®Ø{Kª‰)¿_ŠÉßDÓcMb‰½w¬ ‚`D¤÷G9¸»½²·»óŸ¯Í|3;{·WwNçÃñö¶Ìí´oÞçyŸ÷yy0§é6ø'þÿ^mþ˜»cfQ ê‡óÏ8}zõð\?ü·ßó >œ÷¹­6ýšeý=¥ýÊ\iÕý2< ±c A¿.ê~e9†Ìö»¾œ;Õu©ÙÿÆæ&â1fÃÈrs ËgÀ ¤é]§ÁeØ€_ÈE ©€=d¸È TÒi[5 ÖJ6)`¾N f0åói»l ƒ ëbßѧê€fß?T5€˜P5Cv  çßgôX©j€e{pÝÏnÁ/~p-¦ž<‰©{ßÇœ 'ý¡ž#ÿàç0êË…Û€ÆS {ÍR Р½.k7 Åòïë² … ÒÕq`Ö­÷@lÐQÍ>.´®ͺ¸ÿá'ðéÂ%ƒ°zþŒ–~‚üd³ŸÒÒÏáëAˆcþhÖ©"¶“µ4¸€Hƒ9màÏwý÷ýõôêÑ-;Ø'žÝ^HÖØ×f{=[RbóÓ*÷O$|62oÞæsZaa¡» iÂ匀F{Ì#®½Òúž¦Jм&£öƹ¢´Æ¤ë¥øº›&k*Q6çÿ€º½–ÿ®€ÍW-ÿ”ºÕôIÿîvæÍ±ÇèG^— ”L 2ïó/p÷CO:Á? À¦YFN~ÿºð'PÁ4ö4û£Û<îÈÃX‹¿âÂÏõ¯\³üÇ¿°yËvPHUDˆ]ÍYëÿUÉúç*íç? _dù5ÍøuÙ…A€}MÓ2€¾¦Îw$8¢;'0—ßpÍcŽú}ñ9Bì„CЦP]š;  ¹É[ö/%å6X7,€&Á»ZJ I ø)!@Õiñ;ý>Œ,Ï©¹¨òI´ø~åVHR @jFP¸Að×{Á*ózýî·.GQa¡ûdFä©Ð:@ü­›ª\)þ”$xG~,›N´ê”Þ@ÎJÐåëR‚*µw>Êß¿ =οᮃšü}Ö¬Û€^}o½?qæw’›³¿'ð稪åó¡¸àìÍ•àŸŸ't»ËöÀ[3?Ä%çŸÂ‚Xw ðï›Ê$U®qicD€E\k\±'€˜ÎÛÉ6€LPãêИü?þŒF<g-ÿ(à—à?‘H°‹Ò|>F´”G͸7Õ𯹓X®ú†Ê òŽ¿n]’µUØòê/¬ºþ ×Ý–TúBúMD¢¼ÕŸü‡y-+ÿ…½ ÿ‘çZ–­Xƒ<ð˜ þ5ÔŠ£=oŽæºöÆÀ„þ¹À?ÌÈwÄ!CpþS˜iœ×ðæÌYxà¿Ï±v"¶…]﯉R½ÃJþ³5Û¯ÊüeÖŸí_ï¶?E¦Ÿ~¹@¿! ê VvqðàþuÈP ìÛ›®lrþæ„ɪ½æ²ÉЍ¯,CÒ\Rµª9#Qt}œ}J*ã6Á?Íë$-qƒOD§³þžwb²“02iî+xRù5«7oÃÒ•«™¡¸Ý:o5 ìp? CUÈó¹[ ÏòUÐ\" ¯¤­$ß ¾-Ðb•p#8»$€àãÏ—à~÷7üù×?e™n÷ú¢#΄Vø2*_:íWMÒ»IX×~ºEæ€F+ßd)€T.¤é=̰•PH€tù|ì{ýÐëÏg¬ë¨1£ñ£ë®Äßïû¯M*HÅ… y™D+;û·?é¡ÌMæÃs`ô¡コ§AsþîÄY¶xÔK Û˜q·ûw±Ðºÿh$ ÓŒ¿(`­éïJ'€¼ @ƒÁðÓŽzÑš¯Eãñx¡¦ ú°!9i.¥M мÚùåZ6ðu:†-ÝÍý¼‘¬ÇþU³P»ýCø{¹þÇÒø3€þC¬îŸÆ¼ép ´C/)铱ªm;vâá§^@ “ÀiÂôO·þ}Öî¯ÙàßAc;çNŒ"¥ÍŸšxì™—ðÔKo*f¹Õûw´¬¿þŽÖ|D³²ý™¿¹ÿšnÕôKy¿ô3c:Ñš/`̨á8â°èÓ³b±(¢‘p‹®ët¢‰½›PW¶µ»× Q¶u{W›à¾FªÖ<¾&À7~:YÍ€¾NêûZ R/]qïÀ-Ý×=»wc +'O:Uq¬\µöÖlÜ,¾Ÿ¦ ?Âäþ!Voì>'; ÐòØåÒs 5ˆ€¼ðŸ*I–ùç%„gÀ!K8 °vÓvÜð³ßâŽÛ~ŽƒÌ}*”4¥—½‡ÊçÎ6÷UµP>ÙJP߆$ó`Eõn_€fnFìÙÝÏ(ð'R¹Æ=#Q¿íuìyýWè~Ο3Öuò¤ ؼež›þž¥r☟Xñð·6ÜàõŒð’iøî¬Opø¨æ±Î>ÿ ÅÆ ¹ú«}÷ã\þ6úõõõJˆd¢œ“™À|^Õ}º=2 ‚‘ç°Àº8Å@¥;…µµµE’pOMa"bz_sƒË¶&¼¼ܵS_‡Q·v¼ù3Ëñ_2ûÒý*ý—­þDý¿þ #l“A†LÎXU¼º/M‹–­‚ÓôO³úÙû©Ý_sÀ?„äyâ1c1uòq¬îÜ}NÓ_ÿ~÷ðÖ¬¹&€LÛà_´±"Žz­Ã¹ü{9úË6‰2ãO÷!Q$þj¶_­÷ç^(PÚÚñà¿°°Ã† ÄÄñc1tЀ¦ÏW´f=•0Á{Òõ5&Ð_ƒêÍ‹P½e>â›ÞCª®Üáï•˵ç¶hf:—²G#Þ¯Wßé|Ä”´¢KŸžÝq’ ʨgÇÂÅ_2Ϫ H§S¢E 8ôëJ"€BV#m?ºi¡ `å©4—t§f$ŸÖ÷ Ï* Ð( #5¼ö'·à/¿þ1Ž=zl†1\¨Ïh”^3•Ï_€t|»ÞÙ¼Ïö¨Ä˜Ô!<ݬ4>iãÝ#¿’,0$f· %Fé>I nÅèì=%ã®t¬§¤¸ž{:V­ßÂ:*ˆ†~Žcï;ð¯œ?¡Ö $À²5Ù½ïç?¼>ˆ˜}NxµlÉ:²½G®—¶‹­¯¯Æb±H8Ö‰„&KhöŸvð¸Å# ‚á‡QQQÁ¤:ÄÒ¬Êx âñxª ÈU”•l,ʸ7D2´G€Õþr=¹ŽdÍìž÷8P·¢•±ƒÐ„ñŸ?¥ÿQ‘ý(Ù*£Çxèc¯õ\ËÛ3?ÄËo½ïØwš¼–Ì8¤ŸùÞ­þ„Û¿ø7#ß)'ŽÇ”ã'09ºû©ŠWã÷¿Ÿ.ZêtúW ‰4ÕôUïŸ øÓçy}¿-óg5þ”Lõýºð9à$±3¤/¥ ë*--1Á~?Ö_{øA,pjÊ 6˜t¿r7j¶/C|ÃǨÚðêã;­¿•5¼2¼¯A+"SãŸè ð§Ùö;åϨwFBTý4OÄ0騣ÙRYÇœOâó¥+°}×nTšç4ûîr¾¢>ôš6ÿ1OUæJkiK%±ô1ý†Ò,¶šÀ'vàXm$³—ÂÛž>÷Ë?þ?ÿþ7qú”`FüŽÕé] ôŠ7Pñâ•Hï[dî?· „fñ°L C”"¤3xûb^O@>–—±TDÈü¦‘0Ï›Ù7#6h<Â=bhŽ^¾ý¿·Y£ÖßçÃ*ƒh—9gÁbþþœ~ò¤ höYü–«ƒSHlñ¸ûoÒû^]]]¨¤¤$ªÌûŒ „UP@@¯Ë/Œv ,ó?qÓ{@‘$²õ¦wM& í\Þï6&iëÀÓO™þ|ÚÕÛWbßü;ìv*ø—Ò±ø]úϲ4¨• ‚>êROúéÂÅxòÅ×­óþÎ ™þåÌZÒu™½ÎâöÏjÓøŸ|ÜQ8é¸c,ð¯Zçÿ‡ÛïÅ‚%ˬHØ þ5]³úZK%ÿþ }½€?'®4áè¯YÀ?¤ÓÖˆô+fj¶ßþÔaÞ|Ï!Ãcô!Ã0bØAèª)æ2RÕå¨Ùö¥ øùRµy6ê+68@}6 GÔkÏðÝ%Б²‡ü_!*6„³”^ä¬ßSš¥=ó”X‹;Úbp¡y>/Zº[w–Y;m?mÏH³ÿÌ;Ÿv À®+ÚjŸ(f®®’h÷}럳äú^¾:“ïSM€!€ ½öÿvß™ÙÝùgžšaJ©÷DéEO£â¹‹`ìÿ’}ÖI¤Ùºèï¹’Äãto ð/TØ]äÌż̯šs…Q»û^üz}onƺ‡ €ÿýþ7ñ÷ûs’x€¿I±SXg«+¿?Â3fãÔ'm}N dk˜‹Š·©°(n¨­­¥Ùÿ(í@§‹è  Ɇ=¸ÔB ‚Ñà_^ˆôâO&“TPPSS…Õ @Üܵö¹}¹%¥-%Úú;z•|UG¢b7ö.zž2:l @Á¿ê €¼ô}ÎIú² û˜ìuÀd û°ŒUmß± ¯LŸý•Uü³KÀÎøûÑôO‚YY›¦ÙjÅy^óoàø Gà”ŽeõçîQ¾¿¾ã~,øb™u@3œþu)ùïõþàÑ«[¸usÞ¿Þªíg`_J ÝÊöËXÍþÑX'N8 #Š^=º!æü½ê+v¢råûˆošêK‘Øû«é‡NÔs\ôêõ¦)fâ~ìÄ~¶:€8¯[«³ÝÆP ­ÈÜî°'ÙPK*_2æ1zЬõgdÀšuññ‚Åøì‹BµÄwkéH¥ñ'HšoSÊD‡„ª;ÌŸ)’æòyQ ‰€´u=r@åsäàòàå#8t5sÀ»y5u \6íÌŒó[+êŽN=ƒŠ§ÏE:¾žIê `…õt§2A}Zû,2#²š X›Àòy(ãft9ëOŽuÐùõóú_ðùÌš·ÞŒ]!Øïüû¯Ú°O½8W_r^=ûð7÷s¹6¦Ö51CÈ”Ðd pIЀ.ÀŸUˆ€þ€F;ÙÀ¾®Y&K¯¬¬,­¯¯Q™*uõŒD"L¢[rdÕ¸yt p‡^“–G{ÈÉÄk²M“úžö8"AÚü·¦éŸWæ®±Q¹v.*–>be-é¿À²$@•þçßj@úϦ2ÝÜ)þ“AF\±/(˜{áõw0__"€Ìnkšæ;Ó?‡iÿÿ¢îß üŸxÜ‘8õÄã<ÁÿÞ}å¸õo÷`ÉŠÕà?»Ó‡ÿŠÁŸþ¶Ì_cRý°ðWeþ4Ë›4—];cêÉÇbÄðƒQ‹æL˜ÖWìBùç/âÀŠé¨+_TÍne$mÀïÊÚ«™}MïÏ’ ú5Ћ¸ˆKÅÃ$=üSÖ¸ITK²þíMп׹´ãŽ8 cGÀî½û0û“Ï0cö'¨O¦DÍ6ßIš!2/ I3" ­¨¼ýع&¶Ë×D€lÇ8Nî @ìæã”èÏK|ê%Ö&ò² Îa~!ÐPÚ¥—¿†O£z' ÊìåüŒLC@ ÖÛ4èÏå4s“R1ÀÚÒooÞ;ª¿ø;bCN@ÁˆÓŸ--)Æw¾q©yïXêÚ:1¼?‚"=h¨mg¾I;è!Œè¢†¯o}ðÎ>í¤ -`þbö¬ó±”hÞ®ýr^¦qMcs"}|Ÿü›ôþgâÇßjj ‡ÃºÄ/‹c*€X,Fª«« 2­f€ÿ€†¦|õ'‰óâÈ \Nj@+xCA_ksÍië÷uïÐØ ¤¹û§zÇJì™ÿ(Ïþ«õÁo÷–µÿÈ£ã‹;ûO8à·¥ÿ!.ý'ÜáÛè2úˆ‹<#—ߜé3fÙ«sÔýKé?¬~Ï~0ý³­&Áˆ–ù×C–qÿ§Lž€“Ž—QÏKÇ®²=¸ùw`ݦ-.ðâíï´ŽåôßÁ«ç‰6~ÂÁÚ_ügÚº/Æ }pÊ 0ì A¹\€0RõHT”¡bå{Ø·àÔ–-àëSÁ‰h'š³´Fóû–„_¹D2ÛÐÌ«\@%$øg5>1s)`ÄYš’>Ð<ÕO­5ÿ6„š²îx<Žþò÷¬»Â9gNÁa‡G4eÇ×kDÌcÙ¯O/\qÁY˜f¾ÿãyŸ›€çcìܽW|/ÙíC*D¶ËD‰” )ÞÙÁRƒÜõÞ´ Úœ -ø éBþ͵û¶ èñ%‚xèÙרO⪋ÎEQQ¡“,/íƒN×ÌàJ€_²ý¡ O¾7=JÔkÀhÍû^€¿ëO;ÚŠ)ª! PþÚeˆô_½¤—c=½{vÇÿûÉ øÕŸîam$‰Ø¿ÜSÀàŠ)×yí'"ÀY Àý v”íŃO<ÿ½ñº ˜Ê 7“™kjÜíVlyù4T¶›f]lè„J[²!¦Ø_£F€¢€×ìêŒ<‚~«È_ñWUU•¦R)VÜçΰ7gÂj¨>¿)uû-Íô7÷æÚÒ¬ùWe”/} u;æ°Œ¿4þc]t[ @×ÿ¼HðO¿PXdý#Šñ@ ͲŸtÎnK–¯d5ÉTšþšwÝ¿Nd&ËŽÿð¯s k.šÅÖÝ­þ L>þ(œxìQžàëöøå­·cë®]–%6Wu¸ÛüÙýíýlöçîŠ ëüC¬žßüþ!Ý2J¤* &÷J nòçþ…1V×?ù¸ñèçîî5÷$)èß…êM ±wÁÈoš %A ·9¸æªÇ׈ ø5âüšÂ¶iJ&ð–þ»/ç A|–Û# Ù)ƒè·½Ô¿¥ó,äËV®ÅÊuÙ2ëÓÏPXP€sO›ŒAýû¡SiIÖÏÄb˜râDLš8 ¿X†w?ø6oG"UoíhЉiÏxCª"Ì}Fç ¦H „ÈÊ8iB”Nt´:@Zéàž"}dGî @ŸL1ó¿ô&ê“I\{ÅE޶¡ì<.ì‚’K_Bå3ç!]±Ô|;%ÝŒI€¶š=šr*© Â0Ã@’võØûâ·Ñó¯8þ_ÆiSOÀËï~k‘×Û„À_H™­D óüNaÆÜù¸àìS1tpÐ0ßD@®ÞW^_-ßRŸË¶^wòH&)L&cT@³þ&vx'dÊÿóš# F@Úx¾I¹ð#555ô.®û èæþÛâ»6f<ØÞ#_Dåúù¨Zùš#Û/{ƒK#@wrÞ\ÿ)Paà?²þDØÙs £¹Ü!ç >!cÿÒå…WßÁÆ-Û¡ÖýÛ-œ8ØÕ €^?[bƒªÆcíþB!‡Û?Û8îÈQ&øçYŸ¾sWnþÃYÁ¿Úæ•øØé¿1¹?ù"ÃfûˆgýCÒàÏü5óD?öÈ1Äh è×§q Zg ¿båLXù*ê÷¯…è è”軲úšV–ßE¸Á>qðdô:'Y0£$ 4~íA˜Qào ‚ül[Ï-U“™)~êy&É—ç•b?÷Ú;xᙌ¼™2iŽ1åÀ}³®Ÿ’c´íÝ£G`éŠÕ˜ùá§ørå:[9èçûLgRqÂæÄ“LÓv:Ü€z¢,í­h!Z¶ú»H*wÐÍíI ]ÿ3¯Í`×Õ7.›†b—@/îâóGå3gÃHl͉p ðvþÙH€l~òÛ¦¶¾ŽøçÏ èÈKm×Ms˜ 7\s f~ü*«ª­R#¯w¦í<ë ßÚà¥ÿø×c¸ûÏ¿ "hŸîçriYݘ±«G‰¡­Ís=¬–‹N:UP/Ñ  Õ(Ë`@0š1âñ8Q.TÇEh^¨aóõ3 ¹§W]Q6†±±É©¡ömì¿&}m5ö/yÉò% èKð/ )ý—]ò2³gHÿCŠáŸ4ý [Á“þžìùMŸxþUÌ£­îÜuÿ–ô_÷EÝ¿;«­ËŒvˆ×®³úõP˜ƒZiÎcÑ#Æé'—¨Ó±oÿÜvû½Ø¼}d};¦Zˆoø—õÁþÿ²¢lëÇÀ˜#L-Áþu ü»?ýÜ ¸1‡¡oïÿš(_ô ö/}Õ;>fF~j¶_'2@¿®efÿu ø5'à×\eÄò ·ˆ'P²k³‰h¤i–¸- Q·Úª¹J¯mÛwâ‹eÜ·‚H¶…9ÙëH§ ,[µË×lÀS/½‰ G†SÍëá¨1£³®3‹2Ÿ€ÃF ׫Öáµ·ÞÇš [lÆE#\ @Dy üSô±(  ÙbÒVjÒZ“g#’©´B¤Ù63%€ùßS¯¾ÃÊ®¤å.%@¨ûPŸÿ*_¾Fýî¬$€æå ðïµ/?Ù6n÷®DlÈñ¬¢:è¾øÝϾƒŸýîNG)€!?·GeÛ'Ï…‘Y¼|-¾X¾cFG>ü©mþÙÞã¥pyui©T*jŽ0 XóJÛ‹Ó¥@0 ùš3 ”ÔÕÕ!ß)ˆÇã…Ôѳ)½E½ØÂæHö³™ 6‡ñláWwëöT T¬þµ›Þc`?¢ól$¤šÝ2 ²^¢'d>lÛSZ€RÂÁ¿ýW¤ÿ´uøùžÒÿe+Vã½ç1I«e¬“¥î>½*¸•Ym™ñX²Ú)ø§®çgŸz3©rêšZüö/w1`$÷§7ø×•6Äwå0¹fýyyDX€þµŸÔúcúã°‘ÃpÆ”IèÞµ {oC£¾² »çüåKŸA²r‹¹Ï“6XÐ,¬h~Ý•Ù÷Êòë þ °Ÿ䓦í@4ֵ܋¼m9É|6GñDÁôs¯¼k&;ž]ÇÄñFìÝ·oÌœƒ™sçáÐ!ƒqþ™Spò m(ƒˆáè1£ðÅ—+ñÂôw±e[;8†eÈ8½< @Um©h‘Á|nnx–/½F$ à/W?ì<ü…7PRT„igÂö•:Âý@ñY£ê«]$€ýM<;-9ŸZFxú¸®9IÐQþÚÐíÒ'AbÎò’£ÇŒÆ©'ŒÃŒ9 ø©—N[_Hó+ Å9‘7‚0pßCÏàî?ßÌL¢ƒá" = Q"L‰„yD¤¢…8«^€ÁÈ€¬¨¨Ð”ž®’ (¨®®.2_ׄ“§õ~ùØ‹aln}~KŒL"r .›”¶W+D?ý *W1ð Ÿ¶Š—Ùÿ¥UÓƒ•ÁI+EÎmH¨`œüIã?QÿOxÓB)ý׆_ô="c5‰úzüûñç°uÇNlÁ?'À?/+P¥ÿù¹À5—´Â™ÕY@Wfþûôî‹Ï=ͳ}mm-þ÷w5ÁÿúœÁ‡‘ü+µþYQÁÊ#TÞÊüÜÀ~½q û÷íÝðüL ®lv~p,{Ü’øË˜Yfûåe"³üîŸÄ#˯“ܾ#©¶ >›BìÝ»¯¾ý¿†aC=< ;qœêjøbùj|±b5î~ø)\}á98mÊñ Ìzýͨ zÆy8Ž;³?Y€—¦¿‡=åû­ƒ$5ó &E Á6Qv9¸$;Ï ‡¿š!Ë€û}Žù¤œÆ)ˆFà02ä8NùjÞÿŒÄNF„HP )Ôhe ¥§e†láGÎî…õ[§#¾ðq¿Ö¼9: û|¾töî¯çTÇh èP(¥Ë×nħŸ}ãŽ9JŒ<Ï»¹&ë¼Ô½jÇ€lŸ—&€ÐÄ!-0GDâ~aÈLÃá°MáÚ‚ÁhËyÞ†uuu…æEN”6éOC »¹À½%äA{¢_uRȽï*–¼†ôžyˆ†a)"BúÏýbfR¤àiFp—h0™('Úˆ pKÿ¥ÔßÊú‡¬ì?ÅýA¨ô?T±ªMð¿zýf.Òtµü#²õü#ýWÛý1·zQÃN³ÚL œëé~/,*Ä•nà?‘¨Ç/n½Z3ÝÁCÿ¬3DTÈ]1ù“Y}!÷ïÛ§N›|,Ëò6üã›a÷ÇáÀò'3’erÑ•l¿®šæaîo?ÍBåÃA‰4Œçƒ`xæ¥×ù9¬‚yý‹öÒÍK±…Ë›`nö•À]>Çžu¦ž2 =»wóüûôº8é¸cX†÷æâ­÷?B¼¦†]#,±ËГÁHR¦и ‰«!—BËtMP´–é_3I’J³=™v‘tz¿çágX瑳¦žÄæ"uÄFž‹tej?½éä>NrV»y±ïÒ->·[óôlÈ€)Ò6œ¯œu#¢CND¸Ï(Ç:hÉÕÿ|÷jÜzçP[—Èè àg€ªr`'µÁ[!Üÿè³mΓ^¤r0ÚoNljI­ËÅ?ƒPcBw×I¨ŸI¥RáX,¡’Ú:œµÌÕ4‹  *á+OŒ¶Žç„û¿¼ÐéãÂÐ ™¾NÍ”¨Ä+—Œ{c2ІJädC'¨»'l54™åÒ± 5%ª•hhˆÌqøgÿF(±ƒeüÃVö_7AÔ ° Dm½.Î&šâJ±hˆôg’“$G2 Ù„€¬[¦çGÄ N£¢íŸpýÙ&ýý  S¦{ñÚ ›1÷ÓÏ™ SúO|#ýW®ÝÖxöŸšþ…íZvMo)sùæ¥ç g·.žëüÓ÷céêµNðO:.øo(ëÏ<d €"÷§×D4Áä‰ã0eÒ1 f´ÒÉ:Ä×ÏÞù›Àÿ)O௹¿n?'»d„4o#¿lYþ|fø ¨m Ûï[4õoÕÔÖâÅ7ß·Kó%øÀ_ÞC4_¦Ž1ø¹ïP<(?PÉzÝ¿üö{8êÉ8õäãÑ7KLJ’â"\tÎTL<ú¼øÆ Ì[¼õÉ/¥Ð8J ¥©/?Š4J1Iͤñ’· Q Ù dÒ’â ({¡Þ$Àíÿz ¥%%8ɼžÜDZð˜ë‘®Ú…ú%w!•®äç¿¡(Ü›ç8H»üè¨|ïVt:ïè%Îóeâø#qä¨á˜÷Å—¬ e+éc?* % àÑpëνø|ÉrœÁþÿ­3'ˆB*çQÚ6*«W'‰†z‹¶dó+þ*øò7Aª–#fÎ÷T G G:ƒDzšKp‰¹›X±„)b&ª)4Ãs1“ÖCœsçÆ|¶DßrëWïMåˆé?±¤ÿ!ÛõŸhVCúMé9ÂsU÷<ø8víÝKúO¸ôŸ(Òî¡¯ç „ešÚqËÿY«?ÙÊŽg¶é»“f€~Õ…gb@_oÇú{zs,FšRù [uó^5ÿ~ÿÒ‘9ú›K$¢³Þ™çnÔ\"²3‚nƒÿ”ˆqøüàÚËqÚä‰YÁ¿‘N¡fÛ2lzæF¬ü<ì_fƒM³»aP’ŒzdÐ%J†íß%‘UÊgB‚?-ƒ@¢”äü“.I¿ÍoÓÖÉz¤4+Ë*åªô|¡Š"Îk"®q{åmbÓ­Û»ïzúeüðæ?àÉ^Ã>*õÏ2úöé‰ï}óRÜtý•èß«7¯õføIBÏ,Fü蜸ӥŒVÞûˆí ¢ KÖ¹/o‘â`/jVØl#æZª“2„Ìïÿñ|±l¥çjЧü¡g±k‚aC!€ Ã&M;oÛúÚ°n?ÊïòZMlzuëf{þø;W£[çÎ|›ÓüJÙ”_Ñ¥8¶V°h“‡ŸyÛvì ¢ê<ÿ†žËÖÆ¯)óxC #úÓÄ fè¡Ç4>äßb$}‹ðÉf´ €`´3pµZÊçéE\]]]R[[óêÚœ ©½}S [ºïüô¶õ÷ˆ >]Ï…c -ÖËÄíÝMÀß™ ôCÅ à:]btërq‘ZŽdÑSÊÓqû%$¬¸þ+ÙƒË|µQ—˜È«Sƪޜ1 k×o†ÃõD¸ÜÛÒ‡ï|`·é_(Ì/Ír‡™¡Î¥Ïÿç>™IÙ½Ìëžyi:^~ë=˜Ä¶þ5ÅôPs(ˆÏÀ¿&€’ÕA€æ…@÷‰‰¾)ð‹ÇTŽÌbzN˜x¬ Šï\s!.=ÿtôëÝ3ëõ]_¹[^ü9Ö>tþédÂþ:Ïò« ?ªÅ•;:×›P¯86húÓ¦$±HX$[nû¿°jÝzoàœ»¡u?Ž+ÅŒ´­4!Z³7¡-ÕôÎã"Î MÎËöEK_«|ÿ,[•±Ž^=ºãô“&"‰ˆòþ|J~y?'è5•ü ز£ ‹¾\tXò ÐP'€¦Äãn€l ^'ÐÚBœ¶ý¯3"€Jÿ£Ñ¨ïrcÒ6“S0 ]Ü’P/ê­ÿO&“Õð/àíE4æÐؤÔÔ µ?é¨ <ŸD‰^Ø‘ãQxÒ¯QríbDOúÐ XÍ”˜^†¬³'Ümß0,ûî"ØBÉ€B… (äd€XØ:H(S5Iâ€ÿ}îú/ÚþIé?ÿcoJ2³àe»÷âßO<šDÂÚ?~•þ«¦²îŸ×¶›€Wã²–Q4¸ÙØqãÇbÂQc q÷gÂäÍ6ø'ü[íu±í„ûäQùàµ/ûƒ7Ѿ/áàŸöo§v,fÝY¡wÌXÜòÓïâƒʨS–#߇íï܎匽Ÿ?€dÍÞLà¯+Ù~×BÁ¿jž)}B €~ÌMëà‡1sÖ\ì)? T!Îì¿,g!¢KÏþë¶¿‡&¯{ž‘×E×~Àìt/ÊlÞº ·ßû®ûñ¯ðég‹³~ŸâÂ\yÁYøý/nĈƒ‡0#N í‰ëŠ]Y!Y®¢ 2B¨”óA#­UÒv$ÉBìÞ[Ž¿Ýý0Ö­ß”¹ŠH1JÎ}¤Ûñ6ö5à0Ò4rÿ:6ßӖ׆뱃°ù& v ª>¹éª=ë¸öò Ìsbp&ÛÖ1‚;–j%jº¹aÓ– °öУìl-Ý„¸Zf«– ¤Ói üc”èOM Ñ Û%Œ€F{ÎP<`wS€yáFÔz.wÝ{®€¿9ÁcsMó~- iŠ 4`<"'ÞŒÈ/@?æfhý¦€ „*d=œÓT઒P ˆ€'ÀòR© à‘2[ç¥ P¥ÿRò/À¿ƒD ‘ø@`ð BàwÜ÷0*«ªaվÿÒÕôÛ…3MÿX¦y4 èƒ3§h$s›—.[…ßßõ€ ü+“nƒ×¶ûà\w×û»%ÿt?DYÖ?Ìö ÿ& ×…*‚fb{õì†ïóRL;ó¦ ð"òRuÕ(ÿâ ¬üçqØ5ë·H»\ýþ.‰¿Zÿo·ËôèWƒ¿´ ª:éùÄ ¯!•LZ$™Šù™l·[°+aL±Ú‡ Ÿ qý3"@\ŽÔ§¹¬Û° ?¿õÿÌåvlؼ5+†:hnùÙwqõ…g£ºâ†u=q²°NüsåÄwPJ´æž)m¨%ɉ X½~ |ò%ì,ËÃz×Á(:ñwЊeû0Ý @>wÛ+€rÿ]«U§âáQ·ü^ à¤Í}uÕEg¡9ÊÆ¦:à*M¡÷Ž5·`þâ/ƒÈ:ñgC™ùº—‡U¶^}ì¥øRô2N¥RQè‡Äs² ÑuÝ­ðR£ŒÀ°ƒxƒQ¶Æns©Ù¤œD´t’àQšÿÑ%e™R³@ƒ¥¬ëÅóIn‘Å 5DÔE=RúÏÁ?kûÇÊã?3¤Ò¾ÑÓõþçKðÙ’å’ãÈêúŸOé¿xæv<óÏdí!ÝaúGMšèóW_t6 \}¸éØY¶?¿ív‘TÁ¿È~êšüû ìÁ üË2N†ðˆ!Yß/ŒþÂb¿H‘2·yÒ1Gà\*Ëö¨ógÙŽt µ[—bלû3œý5È3`/ŒýBºÓô/¤ÙØÒíà杻G¶ù–?Õօ̉¶G™´*3|:.^º[”:dIk«*‹C”¯¶ M>§ *¼žc{ÃÜ'Ü·--¦"¾Gh+´•k7àÂ3OÁ¹gNA—N¥™ ×<9èy7ò¡xø©—±vãföY –)@Ö-NSXÉ¥)¡Ê¿ÛçM1ÌÉdR ÍÛØ- 3üè³ÅèÙ³+n¸òb:Vz"Rã~Šš¹¿Q¿‹ígV©ÓP{À<ÿ 0Oø)c#Cú{JLTô÷êÏ@¨÷H„{p\;´ä1cFá•w?4?Ë[ëY]è>ô³©žðH+Æ ³?Z€£…aC1R;Ǥ •ìæR®å6ÓözìEüÊuR___O!Xü<ï@UT2oÒ¢@#t^0 m>oÈŸ‰D‚ˆ¾žÑÊÊÊbógÈ‹Ìe4d4ÒÐÄÕRÀí×–Tm=š²¿Ûd?Ä: t"ŒªÝ0ö®±sŒ]ó`$ö ¾h( 9' øÙ«é$ÏØ[ä@=ï&`†Q„½7iÕˆ:Æ”LúOA¿.ÿBì/±à¾ÇQ@÷Qb2™Âñì˨­­$ž(¨×®•ýwÕÖ«èQ’†Dtô3 )Ù½DLeûTà¡§_ÂìOàûߺGíù=>h n¾éz¼øÆ»x{Ö<¤’õ @§@ŠYé  ÞPU›`:'" Î:7 ä… tñí_~ó}æµqÉygd¬¢àèo!¹kËþa“N’€r“ 1ÏÊp‘²û!¾oªì=Ô­z‡îqÞ™'cźXÅŒÕÂìPúyÍGàß³ÅáõþÔ‘eúCJDHÎêÄjï7lè \xö©èÞ5K¯js¿ÕìX‰-/Ý„êíŸ8€?k×Gœÿª”_Öô«­üüüÝñ»JX KH¸U@¿nÇ–m;°býFfþGtÛTAü9+NIà 6G"@vu)yÓÒÚ [ð«?܉󦞄«.>;—fÜ?JJŠñK§aøÐƒðø Ó±{O¹0Ž#æú ƲﭥÁÁ2ÃlØJI†ùJ àj¨’Œ¼»þ_¿ˆþ}z±–xîQ<åÿ¡âÀz¤¶¾Fõaæ6íIc´ˆœ¸ ½v郈 ‡èã]„Ð œ0áH¬Z·Ù¡ w<Ýïiëfe/Ì_´ 'w úõíÄDy^ ßý\¶8[m÷—k,lµUø!‰„‰‚ ²@#ìdX@0Ús¢ VzÓº˜ âñx±èåé`iÆÑKJ$'¡ pƒxMDª‰ˆ›‰tK™²õ•õZº®Öï¾r>Ï¡¥Kþg‰H‰”ôé?¤>cçR`ûHïYÂÉÂ3BD”P:ƒ‚QC¢” P2€¡¹¤K§Ì³ýÜø/jÿ19¦y.|©QöÎøz{LàÿχžTš?¥ÿ™þY`Üôï°ãøñGd8þSðÿòïâ­æŠ@Mc5Ç ºÕþLú0Øïgð¯Ù*VûOË hÆŸ©B¬ûb['»O2ìÏ5å”РĞÆUõŒÈÈÓó Ø61R6Í@n}²ÿzüy”––`ô¡ÃœuBáä[kÒ{?²·À£Ào·?IÔHSOz¼¤hͨXŽš%Ï"2à(°³mêäãð劵ødÑ—&Ž)n:‚ À"=–®^‹~Œo^6-²óZíN›á•¥’^ŠaöçXü{ä×ÕÕE ¢ô19ÌÏ3)%Âá°—@G¾U~-G`ø™?***„²˜¨aAmmm‹½0Ÿ `7ǰ­'Ķ BÕÇ~jèû¨€lEK¡ :Úä[:ç1hGÿ¤÷D °/Ò:í¢•¢¼³.¢“€£‹uø—‚ì13 Ô£6ø·ŒÿÌcîmìÕߨººO>G[…¥…áWv×’g鿽ôwÕô/,¤îºf×ý—–᪠Ïf¤€àš7ìÅ_®À½?mMá<³íêîcð/[üQ )%aô ;À?5úëTZŒ^9N™4Á»Þ?B|ãgXuÏfòg(]ÚtÙ²Oü…ùOÚ( ä2÷ó»±_±›øwÜAɼO,A²¾ÞaþG¬ ˜€47l‘¤´uNòîj{P·Y Ílܲ¿üÃÌh´¢²ÊóÕ«G7üêG7à²ó¦²Nl^285J˜1 ¬6lKšÔ&äíD²ö‚0ß$U°aËv<ñü랦€á¾cÿó¢µ3€Ÿ.0wk@õy]ñýH¬|Õ‹žÍ<zvÇ)'N`ÇV54äÅèçXƒÈ«‹oe*e`ÞÂ¥8PQ„?yŠŠMs‰[Ýñ¼J ¸ã5)H[RüX(&€@¦NàÁhÏ!<õ¢€V3_+ŒÇã N^Ò¢\AxSj‰üL¸@4cDK@†L†vÂ-ÐOú+´Ãô= (ì‡4m+È x7ƒppoè1ñ³@!DG þÕì¿ùœ6â2N¸ÆúM›ñ欹 ÈÔÛfEŸç©N­û‰l?“½‹vwšÌÜÓL· ¾uÙù–£½:vïÙ‡ßüé.H!-ª!®*PÄ¿àŸ~·pú§¥ø3§áòÏ:?jis_Än7s{Žó÷v<ûòØ»¿B /›´Ö¥€ÒI´Ùç59tózI¨]˜™Ÿì§Á"^ygþ÷ÖÛñåŠ5Yï[—œw:~ò«Ð¥s‰HÖ¼Ôt€øê#ºliH¬L1ýïã…_àé§3…’{Ä›†èáß±ð¯!Ä’ 8Ù$€Õ v×nëP‹ºÕ¯#ß›ñùã9çž:Iÿ´µq©Žr!Š@Ïå Ûv`ú;±NžH/SÀæÄÐy¸ðU˜·ãpL˜ÿAÊÿéb>O"‘Éhìµ`@0Za’ ²_§ 4óFL €¨(áq°{-»¹thhâj ³ÀÖì­ÕÉ æ(î møÐ'ýÚ¤ßA?ò'@¿S6|šÙmÙ­ Ö‚ý¥Å hÈ\ÿC‚< ŽoÝA†gšLíÝ[Ž'ŸÝF{൶D‘Íå; îÈþ[mîx 'BBrÇ‹·Særæ”ã0°_f©C¼º·ßó jêêxø­v:Û¬ùü‹ïÈŒþ„ËØDãðÛ-þ¸ÙŸlñG‰ ãÆà†«.BIq‘çúë«ö`Ëó?Áö·~Ô`Ö_¶ö éΖ~_•Œ¿êþ/W§Gµ  _5µüä³E¨¯KXm,%ú2Ôìkl„¨õ°#XŽìØ?AÈŸ¶Àbò°bÍzÜüÇ;ñò3PO[zŒc‹_ýèz ÔÇÎþ6™ðÏÓˆ¢½°1èàe(¾ööûžŸ/B]ÎdõiŸ~4‚ZÔV€ÖïæÎHn{ñùf|¶° €;¦°/ÈtGh ¨ìêš:̘3/ˆoòLx©xú\6ð¯¾ÞPÜ^WWgÞªõÍøS@õ ½ý_PÁhÇû[ f`ß ¦Bµµµ%‰D¢À]{ßV-ûr­SjÊïí¡2ðCÉÃWî¤ì2dØTènBèÔû¡ýs^ǘ@?„4%LφX‰'Â|àŸ…M¡"hŸãù7V¯ßˆK—s`À²Þ!.³®ÿ<#ž ,³ÿô;Qð¯‡x½;k÷'@± ºõïÓ&OÌXG"Q7gÌ‚%r{a—;èj§’×NÙÀ?8ü‡#ºÿ! ü³š]·ÌþêMðöÔqÑÙ§fx °óBHþ×ýë”/}Ì þEÖ?¢dýÝrÀl…÷WAêïõ˜Xü“ÿoõoÎø{ö•+Ù~@Z3ûŸ1×»‰Ñ"BƒwYÑlX^Y…<ø8î¼ÿ”íÙçù÷¨ƒú/ømŒ9ÌÉÔH@vï&ž¹—ä9hT̹ù«]ðÂôXèÑC^+ꎂ£¾­dÛ *`W~°ˆr]ÉçÒi$Ö¿TÅÎŒÏu.=÷¡n° C’H°sÏ^Ìùä³ °iGàßP+ÀlÏ7ÔÀë=n0Õó+NSÀ‚p8¬› þ% @È4ýkÌ0ŒÖò›!.b½¶¶¶Ð|-Òysß×È÷k’Ô>@Þ †ô·Ã€tr° äŽÿ5Âg=}ì€Î#™Ÿ€!”Ô0‘²î_š –˜Ÿ=äìŒÕîØµ¯¾ùž-yWêß5FèÓ?¢9—öÀªñ•þkBòÏzÚ‹ßÙ6˜¤n¢Ö«/Î$;h+À•«×â¾Gžv®[:þ[à_d*¡åßÃÃþU§æò/¿pú—àŸÖûÓ+ûº+ÎÇä‰G{n“üÏ{kœŒÚ½K­ \Æ~%ã/%ÿ^r æ:"ðWÁˆ3Óï¬ýïHUMoÎœŠŠj«;5;ÑÔjÙÏHˆ(Æ‚Yʘô]*#ym¯Ïø·üõŸX¾r­ç}¦W®¸ù¦pÚ‰ ÁË–¥vy› y'4wè`˜4å<´Ë¬¶ìØ…ç_åûd¬/2tbã~šqΦ}J¸÷¼¦9ÌáÏ\Ä?úgÆg‹ Í9lÔ|­3÷  ²‘B¤Ȱs=×µxér,\¾šùèLg‚áàOÔþÓL;Í Kùª¦Ô¶$ÜÆ¬µŸf þ¥éuÙÖL|Á§ ««Í';Êðû;°ëþ…ã¿!H·×¿Á¿Ãé_dþ¥Ù_II~úÝk0úЃ=ç‡úÊÝØöò¯°mú÷­XZ¶ôS3þjÖ?¤fý=äþsÎwþtƒK²ÜAB°÷fŒ-ÛwZ-,Å ±<í³ »¿d) Â@¶%pª–­Z‡_üᎬµÒ±h?øÖåøæ¥ç2¢JÔÀP@–laJÄõœo%€–-Pt”h®Ì¸Î¶ã“…Kð賯x®·`Ì%ˆù_(åÿ¶) ŸÁ˜Çþá*€$ë^AýŽLÕÃÁqõEg)*›äè*…•Y¾n–,_2mûcóÆæ°¦”4x+ aW·¯P*•¢ €xMv¤ž¤°°È^Œ€F;€Y"â1“ê˜K¸®®®Àüòº¸ÝD.r¢–îÆjëPÐÚà_]Ð OáTQwh£.„~ÚüWh#®‚Ñí0‘.<#Ôu´AÇg|rÝÆÍxc懢¦^ö½çÙ:.©ç’ò¨t“R{–mלd@[ªœÆ«{×…€‘¢åÝÀñGŽÎXu_~ ¤wSi´4ýƒ–iúçLJáñÿ–äßåôOݳoºá*ôéÕÝ#BJ£vçjlxìJì]üo+–à?rIþ]Y þ;ºÜ?û¼ÖqÀ¾×xkÆØW^)²ÿâê´ø5+ûOš½šðI‡€Ÿd”`T½˜âHU˜ÏﯬÂßîü÷iÔ%žsÚ™§àÆo\Æs@bóÃùxcÆìÌFŠs´.Dz}VÀ¿!û9O.UPTéʨšý×L²#Å ÇíìæÒQT@°Ï»§Ï@2™ —ö‹ïMÀ55VvǹîßÊóÔG,bÞ§Ã[0µ1%"‘ˆFð»ëÿ ‚Ñ^$€ü‘H$tqG+**J(‹çы٫¿¨œTµ€œ Ô¶!n¹PC;[0– p{u*h­!>5Jt¯¿= ê6æÒ;×ɼ£ÜÐßþÞi ´Ñ— 4ùVè‰Ðèë¡÷®ý_¼t%ÖmÞÎ4ÍúÓšzj*§GÆXÐU‰ ÂêÌuf,ÇA·]sßÐBÀËøOfÿC¬ÝŸpý}¦‹K qù´33ÖC¥ÿŸ/Y†·g}dí& @x›C™þyµúóÿLòïÿýúöÂ÷¾y k÷—‰ýS¨Z?ë»ÕÛ>¶ú‡KÉ¿ZïO‡<€¿Öåþ^`?sðg2Ñ+€u®TͳyÛNËÃÂ*ÙAëdÿ›=¯Ë¹@ƵD¨Èu«dYÀS¯¼…Ûï}ûöí÷\픎Å×^ŠN%E \½#ïJ÷ƒ–- µODÍÁ@9I€Êê8^}ûóØîÈ8ÂýÆ à˜Ÿ° º˜o±ËÀ0™¸6×:5©oɶ÷‘Ü•™!ïݳ®¿üYˆ½åûƒ » æIw<îîÜ%ߣÆNÙ’Wr=îu¸cO·98‹•ï¡Õ××ÇÌ–«¥æ„l€è€`´×ˆÇãD¹ð‰Èü§©««+¤^Ù2üÙ&–Ö5úó Zƒúÿül­gì) Òc´ç‚ô•ñòòÕëðÞGóy=®¨£†MÀ+@qa Eæ½*j.Ôd޶—cmæÂÂu^×,2@“½èÝd€Ô¶„ `A´–iüÖló?úžH4ŒS&ƒâ¢ÂŒulÚ² ÿø×£V­°]÷ïOÓ?7ø—(à—²üƒqú÷﮺E…™çK*‰ŠïaÃ“ÓØ¿^â2èÝ’]qø×³dý¿×PfÆß¯uÿj€ê&åõÿÖÌYز£LV¼‹l¿Ìx·,ûOZ©c±Y® 5€$Ô”ð;³?ÅŸÿùolݶÓs•“&ÍJºwë$æD›Ž„dÏöP¤›¶ƒ­,1QIñòªuñß§_ò$ô£ÃOAtÌ/¬ì?S¤íóØÉ€Œc?;PùÁï3ÞC=¦œ0ѹgݵ;¾EšÝò@œ™s>ýŒ&š‚`¦bÕÆ~o( çã7¦ÂUÈ’J¥¢T@ÿ‰d€Ò À=U!  m`9:h‚ò3/̪ªªbaÞ‘ kÊdÔP‹‘\w®]ÚbBÍ7 ðu.9hî¶3…K]õõI–í/ŒÆPRXˆ¢‚ ¢&ð˜ ? æD?y 8)! ’Ad(D]}62 Ñï¨ÿѪñ_H· û4ðR×Ú3Ú=ö¨ÄK¯¿ƒ•qXY5Q÷odŽºÿ<*.ÃC]”<0"&¤ÔüÓ}-œëè0x`|÷š‹=Á:™@ù¢—°ñé ª­´âQ]³¾šõèvÖ_’ _ÍÔ„ÐïhuÿtlÞ² +×n°Ì:5‘ñ'Dkqö¿Uçw·7!–€—h¢G³H€O}‰?ÜùV®Yç¹Ê GÁ·¯¼Ý»v‚ w …È< mMhÍþŒBâFÂE+Xk@÷ñÐ :£`Ìe EÃ,ßÃ}H¸OG»C€úMÏ#Y¶:ã3ÔÛåJªòruHùPÏÑàµwf£|E|ç‘h¬4 ¹ÞRe T$‘HD‹ŠŠ¢²Å¸ù%Çâñx¡x-+ã—mbi+àÜšÝZ3À눊€¯“ŠAnë‡ÄƒwÜŠþr3®8*÷ï‹ÒâB–e‰2‰¹ÝK^|î2ϳÏ, /È€HD*x™ª¶IŸ‹ @Ó<3þ£Án÷nqÞÔ“,†¦*h¢‘>çu94p©# ‚Ñúó…53P%uèD*•*®©©) ®YË5lŒ h(SßPA¶rƒÆ<r¸íÙÆ0òþ}ûôą眆?ÿæ&üòG×aê ÇbèÀþ()*býºØ–$€Æåÿ¬ýž¥Ò„Ž“:W„ 7Ï3@aÙŠ~/ã?ÑÒ¿yÔ˜‘èÓ«Gƶ-_½ÿyâ[úO¼¤ÿðUÝ¿þå¾sâ%"³þ¢“ý›Ëðƒâ;W_ìiLJÁÙì{±sæ/l¹;øD1÷YÿKò¯)½âµ¯ÜDïüÝÊŽ* ]Cu&c*‘Xd‘ºöJÙî=X²l%Œ”‘‘ýÏ(²öÓÙ@I„oßþ–MÛËpû}3é´×8jì(n~Ù£ —Àö6#{4í7@ód¨ø—Þ°uî{äÏF‡ž€è¡ß¶ÎiÙЭð3 g=g.õkÿ‹ú]+2@}§Ò\1íŒ €ß;°’.Yš"æØæÎGÙž}A ⣘PUVªuý ½¿±ø¾¾¾>‰Db[0ðO?Íþ›?ÝmsùÒA°Áh!eÿРŸ¦ý;“ÉdAmmm´¡Ìsjþs™°r©3jè¹ÆØÎ¦ÊǽˆwÛ•Žþ;r Akîóýúà‚³NÁO¾w ®¿rN=q† îϾ4ÈÓ„!ø¯Å‡¬žôò1“¬SB ¢[Ê€=Ç"kò9X'–üßÎÜóã6x@œvâ±ÛR¯Æs>µ\ÿ9Ø·[þ†uGÔ™_ð¯9À¿,»Ð™C%Rah¾ÿàƒà†+/ò\g:Q]3ïÀ®Ù·Zfü«5ÿj½¿%ùWÀÿW.Ê 6Ø7´Á÷©f}Ú»Þ aæ¥ó'!pXýNÏ%Gó<Žwߟƒk6‰§Y’wMÞ;š™ýo—l™Z P‘ÛžùOHÀ\¶—íÃ?þó8>üx¾ç÷=bôÜxíèÙµÔ~9`8Jˆ'ug)€¥’àßxñ²Õxë½Ì®ZqŒÿ>R‚ü *™'O§]d—¿.IÛ›A™‡è¦WÏùŒúšŒã|ñ9S9ÑeÝÄmE€ß”ŽD‘rŒéQž»p 6nÞáíy Í"B²?}H´§£ V*\ž^d€\$¶Ûþ)Ÿ#<û_ZZ„‰Gñ܆Ï/Å ¯¿+¶œ¤í–κµûcû2Ì=8ø—‹f©…úöîŽë®˜æy¤ëª°ã¿ ìã¿ØNÿš¨õ×¹ôß«Þÿ«æòŸF92ú2«¯›ÿQ ¡a˜ù3f¾d.zÌ<'#ü±V`>¥ÅÖ|¡„€æ"ò´·h›<êþŸ¬¯·@¿•^Tom~Ëþ»Hå‹,=?Y­.b{tEÍP¶g?îzèI  ^G>7\u Š¢²À¿0Œ6óhm]ˆM@) ¨ˆÇñøóÓQS[—ñ™pÏa(žtüi»@%à3@îz˪B!kEªrWÆgºwëŠIãDzãf(müÙÎ*@çòk6xžc¦lõÿÙ~oLÙ«v K*•¢@üôuZPPP !³ö?ýŒvœìfÉâöa>N&“1s‰4f ÒTƒ>¯×¼Öá–"y¹|˜#ÿ7²öhyX\T„C>gž2 ßûÖ¥¸éúËqê¤cЩ¸ÐŒ²5'è =,j×eÛ:àÃ’Ð4ä®÷\9  ð¯ àϳ¿:t0ÆŒ:4ã;oݾo͘Ý!¤ÿøWÚýÉm–µÿtŸiü›Ñ|§NÅøÎ5—°zX÷ &;Þþ öÌ»Óþe›?Z瑲á.‡Ë‡¿™e)UÀ¯‹Œ~„~‚üPÌü.æïìÓ¥ˆ¦TÍ¥ÐÜ9â'}Þü [!b ±H€ö³æ~Šåk7ØÙQë®gë?ßß èŪ+Žý3@œtØ%”¸ó?OàCÚÉÄcßOšp¾}ùæMܰº¹#Ô{g+‘­~͈’M%—ˆE `×¾ý¸ÿ‘§3Oÿp ›fž®ƒ-#@5ëoþôp[Á¨—píÏÂHÄ3>såEg£¸ ÀÞ@Eñ‘öõe“rôX.\²lÿñ7õëÕÇŽã¹=ïø1æ/þÒjù'¥ÿšÒŠI·²Á>1ýŽÿŽvšn.êü—”áÆk/÷lw˜6ƒä²Ù÷aÏ‚»¼Á¿Zó¯fý¿ ’âúE˜ßYõú–›zHeb×ôKÞ—y¥åõœª¿ŒÒóYgmÀzÍ{Í[ˆýû+…áŸøÎšÍeËMßçó b¶HÄÂ÷·y/K›sa]y¡QòÏ|žû=åøç#O±ý?ùøc2VyÆ”I´öwÿ÷Y>o@-` ?WÀ‡ XN„‚HËì6½ ÓiÖÁå½àìÓ&cøÐÁN0]ÚÅSîDÅëÓ,¾Ã€mrIˆ½ü†‹åw¤^éšP/Ô-ü-b‡]ˆPaŽ÷6Ô\cÞ¢åì| †ˆ¸ƒ‡sƒý¶¡,îâçäòµ±øËU<°ˆ· Ê5王ågÓé´ç›-æôŠ?åz…’˜Åô1Íö›sQA4 ™¿Só?.3æF€^`ßóÎ Ž@ÐÁ Bps¶˜Ã #•JE‰í«¥“´Njlj §þl}JûlK¾¯k >ÿÄùqµ¥Ý†˜Ë%çMÅoþ!¾û‹1þˆQèÚ©…Ѱí°/Û÷¹ ¥2ÀÝZvàF‚¢Œ@WÌïtÑóÞ šÆŽ>ƒú÷Í8ÿ|¾oΜã¸uªÒâ“–¦.ÇÖî/äl÷Wl‚ÿk¯8];wòÿÕ(›ó ¹üÁüËš+ó¯}EÀ¿šé— ]dù‰&*ã/âY}½ÐÜÅ0tºÐß‹@Bæ.6Ÿ§¯™ÏÑÇáþ3$ßSb.Å&þ/`ëƒå%ZÔ¡6'$á'z.Ïùd–¬Xmë¥áå™ýÏqŽÈË|N2’­Òwùˆúx]lØ®=ûqߣÏbî8jØA8úˆQÛO³b³>š‡]ûö ¡Jÿ‰"ýG^¥ÿjÝ¿Ãñ?¤e8þë'="Ñ0¦>™5fÿD öÎ{eüƳæ_¶ùÓ•ÚìÏSâ/äý è·¤ü¡b¶°û&Ч€Ÿ~òKøë ä‹×õBö˜“0B&Ð× ôˆ0 äK{ç/®²}BÍ óŒb ²ÿíqïÈ õ)À×›ÐÙk’Ø^¶ÿ~ì9,þr…çöÐ,ù…gL±kž¹’môe5(µ3—KV­Ã{~’ùÑh JN»Ín¨lµjèËK›¸ºošKÝ¢"µ'³eÞñŽÆÀ¾½˜â R9"ï >ñhøþÌ·tݦ-X¸øË ˜iãxµ¹É¶ÆT´Ùbo© ˜Â|OÌaÙ€JÿiÂQã5Dæ8C# ‚ÑÒÇ6Qæ…šH$äý1& uUŸ-»ßRìÖ’s7E]àë±ýÈŸ¯aÐV¤ îcGÀ圉ïë2\uáY˜4n zvïÌ<¤2@×CÁˆ4¼DïÀ³áRúORÊ ô5KK2þöŒYsñþÜù,Õ%û¡ÛMÞ9§§ù¤±Úæ0¤IÇÿPFëD¼ê&z?ù¸ñ{؈Ìc™J |ÉëØñÎÏ,™¯»æ_•ýwhðïø²ª$_Éô“Bô+€žùìÛÏëä‡ ùb>[Ìõ™ÀŸPŸ-æªÿà U´ó9EÛþ-3ÁŸtB×Å\¯MÉþ“&eÿór<À„xi @_߸uî}èI¬Û°9c5…1\pÖsË¥ôÜ&Þ€I ®ãVý$ 9IˆKÌ¥Æ÷<ü4’ÉTÆ{C݇!2ôá¼0±áãË\SÚUkP¿}±çû5k,…aåü}®°ê0콿kï~|4Q·QL×X€ÆbùlŠÝ\§€ßÄsP+3æñRX£^€æóÍiŒ€Fk€!yAÒ§¨4‡æ…‹Çã%êñõš$z.WS¿\&´\š¿¾79¿}·Ò’"5f$.:÷4|ï—âº+ÎÇÄ#÷6$<ÛÍM·¥ÿâ=tKið>îð‘=üàŒ¿GÛþ}òÙbÔÖ',G)MH¡%y'[æ;ûïU÷oùb„™þ‰ýxø¡ÃpŠG«CF`n\ˆm¯^gÕôfÿ:ÉÿÆé?Cæ²³ýzLdû« ¿PEla¿ëüù´^ ²ûæçÃè˜ëŠ po‚ &í -ôC|¡Ù~Ù"vÐÄãvŸ~¶ë6m¥Fæ1ñîºÝá‚JðO4{éó‰' AFêÑ»÷ªõ[pσOb_ùŒUôìÞ W^x6† èËHD#íì Ð&}W+C‚½*ñâôw3Ëõ0JO¹ÅºÈ «€áÏÍtr}´#ÀšéHîÛ”ñÞ'ŽÃ! ¸?¥Nÿm™áRê"•Æ¢e«‚ ¦•c\Jqs%²‘9*I}}=UDxhžÓR©‘jdJþ/€2À>Wðë”ûrP 4­ Ç4îØÑäZú†žk-Óº €}#m‰+~GÛ_ɸ‘ðî];³eÔ!Ãpzüxlß¾ ó‰«6 ‘¬g› †ˆû[ ]¯fà>zäPgàQ×ÿy‹¾´·Oÿ-3“˜géCuÿÌôµA£júôdj ¯Q»c6?w“ùf€Ý–ýgÿbVV€4õc—™*Ûà›Iñu3e@H<¯ñ:}¦¨ 5Ñ>Œ/„dR!ôýZç¡ÐJûA+ìRÔZ¬ -e„tÕ@>l?`ËÖíøråZó¸rÙ¿l©‹r^F¨Ç9­œƒFÚÈëœÐäpÖ2$5e·inÄÈ GÍß~¹÷<ô~óÓïAºmË1tð\{ùù¸åöû‘4—¦€`â2‹“Æ{Yç´¶@¼¼Î ÝŸßñþÇ_ÀéSŽG§’Çë¡nCyê–ßeÏ£àó-»È¦€Öe/úÉMÏ"¹ý2„ºr¼­oïžuèÁøbÅZÌ6Ò”ô¢ç ßwùTÂdÄßÈ}*°hé á¡ô FëÄJM-hèxºÿ†»3—ºPR?‘H„ #´æŸ:„€( H¯Ÿø€`´å¨¨¨`"ÿ¢^‡švÖÖÖ ÇΜ'›¦´ lîgò `ýX÷ß|Kol¹‡öò‡hlPÀKåûtim´ùêµñÉÂ%ظy;êê둺2Soˆï?áÈÑ8xРŒuî,ÛÙŸÎG=#d}½zö‡ñŸœØàƈ¡'¢%"/wà.e=ºvÁ5Ÿ“dè¨ÛµÖÿßA}¼ŒGè»Á¿—ìß÷àŸ¸Hw~ü#üvkÕû ÐÏ~„·æ“à_dÆ ðÏÌ!Ÿƒvæ¯G™úaÔw;z׃¡u£ ¿/H¤Ð®'þŠÁ>Z°Ð «ÌÍÔE9 aûµ#ÄRH“ĬΚЀK"ÀMøÂÓ@¤tŸÊ4ýÝ43†¦¿Ïœ³E……øÙ÷¿•1_{ôXü솫ð×uœpT@Ÿ+HÔò<¤R”™Òí6åS/¾ë¯¼ÊŠŸ)>ùW¨]õªä–Ò A< ~#¤Àp~¯ú":ü4~}*cü‘‡cî¼Ï±y{™¹}æ‰aèâPúÚ°¾WdwóÿÕÕ5˜9û“€h!ÈÏåywŒíÕV»)ñUcñ;ý)J¢l&³Ýÿ!JÜ&€€·@`¸Áh‹“²pTþO;ˆŸEÕÕÕw{æ€àæzøH¶–Š!­CÂtdâ#‰à°‘ÃÙR]S‹•kÖcñ²Uؾk7*+ã,SÇsÍŸG6Ò3ûÿê[ïaùš ✄bü§9Œÿà7é?ÿ¼öŸê<“KoñEævž=õtïÖ%c]õû·cÇ;·¡f÷"k›CÄ–û7Tóß¡À¿Òª)»7M•à‡ÄûÂü1õ„/<Ëo·òÓ {€té2 zÏÐ{ ‰ujà»øëºÚY¶‹–¬àþ4ÃÏÎu‘az>™ç’N4G‹+)yO‹èQSH ©ÛÕ}¼¥»“Õ4b‘Œ $²é(ÿý?EÏ]qõÅçeÜ«N?]¨…Ø Ð&×O®s’ÈÛ[*˜?Qäÿô+ïâÜ©'¡_Ÿ^NÒµ¤' Žüj>û­¹©in(¼B$œ ÄN}1(›-'7¿Ä¦³v²ã=‡Ž£ÆŒb€º%ìp­ M¥ßõ9Ÿâ¦o_NǼ-UQz©²½NñD2™ ›÷û(m XWWÇÞL¥ÿtPÒŽ’æ{ÜWAd@0ÚávCd­0•ç˜l¨¶¶¶¸ªªª€JvØÍßeØ·þ\Œ½ŒÝ€Û«‡©W†Ðýý¼¾ºŽ¦Lx^€3ß ´%ýí¿ 7³¯Â f]4ˆ£Kùþ ¬\»k7lÁ’e«1á˜#0 _ïŒÏì*ÛÏ—,禴Œ"JÖ6ÿÙÿlÒþuæy ÿ©,™´¥" jÝ#ˆcï‚gP±úe>gôCJÍ¿®;Ûüuðï)÷—îúá¸oúS‹(`?l{° ¸Î>+A?S›çMçau½×ÈÿÏÞ›€Kr\e¢'"3k¹u—Þ7õ.µZ­¥µZ›­Å²¼b ؈1˜±Ùüë¼ùæñf˜yóæ}oæ{3 ðñàfµ ˜1XÆÆÆ¶ddí’µµ¤nIÝê}½[íU™/NdDVäVÛ­ºuo)»–[Y•‘™qâügùЙícå4Sõô?ûIÞ÷³E€ú€?ÃŒGžI‚|ØuCÜk.o5ÅÖ—@ƒ¸@Ð ÀÛáù)ÑDDW%ëýÐd@;«NÌ~húˆ–—v Àe+Wªð·ÿðuؽã2¸ëö[Bß”Ïåà]÷ÜÉË(žxîÈ4B§iË0RðuˆëëÉ)ãáø=øÌß~ >ù#|>ÚmòŽÿÊÏüðê§ük-þ‘º“A(ÑñÞ÷Ñr@дpê¯}-æ@ÀŒúòoüZp¯x";‚@‹2€èzíEʘí O~çE¸í¦ƒ‰v‘þÁ~‘_/]”ÒJäwà5À¡hóç;à„“¿\.ãb–ôAÈ» ¾RÑ%eDù<0Úˆ™©c¯#¯Ãm9° À®ÕjlgºaMöËIÎ7Ì߉*I©à†É(odõÊÚ5Ó<]÷‡¾ÿýð3?öƒðnf°£ƒ *ŸýŸ_‚׎äk¡ý'‰mÿFý— D4õßosØ";äå ÀìÞ± ÞÿÎ{¿gþÐ7àÜ7ÿwÕÏÜh·dÔŸ¶x,gû×ø‡À˜Õßgô/ømù°eŸUHý|ö~± ?Od Œ„<[~2oýÈßù3`ï{'ÿãæ4SõëM7^Ÿxà{`ÿ¾=Ëg¡0‘c`/ÃYÐs edMÙr9%lÿy@º)2P$«¼<t˜ç„ xg•_fŠÎTÜðgÎ_‚?øó¿Óg/Ävß¶u|ü¿‡Ÿ¾Ž5°‡oà‘%è#Ò"9µ"N„¿ùÒ7xvCìø ëhþNhÉѰÛ"ôäs;P¡Ü˜Bkž~š—âÝö_±nºö*Q6àÑÍ>I=~OSøÖ£OC¥Z3FÀ29’ìôNö{Òߣ¶p+`P‘=Ï2±”V€KÀvÀÉšÔˆqIqˆBi[ÛÅbq’MÚl'Ðß ÉH»–!i@¾ŸÞ¥QÅ4,'ƒ.µåFVî"½kû¶ÄÔÿ —.ÁϾM·ÙRÃ$ý÷¹Ð-=¢ÿDv>ðIüV–’úïÁ†õëàƒï¾7ñ»ÊÇŸ…“_ü9?Zì3‘éþèHÿZÎÎhK?ꈺ}ŒòçE»¾¼`óGÀŸg"`ìÇ}<<—3»À¹á½ÿ?Cæ-?ÎÞ»ÀšÙ¶"tÿž;àc|ü§_ûEøÍóóðÞ·¿ÖNNcX 2¶Òô[jRø-¥Í$:ly¿*œd4¦©¸Oz†à9è pøè øo¿÷éÄݯb€ñ~ò‡Äáy~⨞7ƒÒ §'Àßå¼+JT¦îú9 ùÝÿó!ª4º»ñ¥k^zj‡¿û;–>¼õÖC7'I$¢†ÁŠV«Nßf{ø‰g`aaÑ,þCüílò^ìînìoùyÅFÎò… §€ä ¹\.õß”€‘!›¡Á£ç£Z§\.8zèrÂ/—ÂêE ú¸dê¬ÒßÔ€##‘¿ú—ᬈvÑŽþÑïGÿå1!a1ÿ<õŸú©ÿh˜"·ÞÛ·ÅKêŽÁé¯ü'h,žò³ DŠ¿#mþm‘öoÑqÿJÔŸL€ÏÀ¿•müd”ßoÛÇ7íç©ÿLmº ²wÿ[ÈÝ÷ëà\ùné§vvŲ3S“pÝ+áÇèÃðŸÿí/ÀG¿ï»`ýúß¡$ZlÎ&™ybù¼¶øÙœ‹j˜Ùü U'@0é°øï>ñCð{üÙØ®8ßzËõð¾·ß®.pËsõ;4ÜéïÏþýWáô™sññ®Û ™=ï÷;bxu€ï ˆdhçëPï¥úiÞ0éz^Ç˦¼X€æ:èzƒc],•áØÉÓf°ÎL²­»íÞÕ«­žT€ ¿Ñh°e>ã`­¿ çäãÈ€ÎH.0bF†©° È™d0ù9ºÅ¢ìí"ÿýý.{U¡à?Þ‹à8ËÜü|õ›ß†J½AíJôdç[ÿù@«‰õSÿýºYç‰5Í\÷ÞyK\¿ÔÊpþ‰¿€…#_Œ`+…ôO{ðTBÔŸä9ÐçéþjÔ?wäyf€üÙÕÍ®ºãnȽû¿@öž_kóv2òͯêåq.Uj§kñZ»fÞýö·ÁoþÚ/ÁO~ìðgûeàcz‘ê/œ¸qð¯dÙƒ»Köuç jTœÞ *µ*|ñkÁ#=Ûu ;w?ø½ï‡u3“0úÈX’%­î ëTñ};1  pË™<À#ÿ®.ÐFW$Ý/žr¸³/Aãä³±mÙ¸î½ã–DMçè¿ÿÜÍþ{êÙ¯Ÿ‘¥Ù;IºiY­Þëd“ËG¹Æ³Gì,†A¿xO:0€Br½¿q€‘aš¤ü×ëu$æ@É•J¥{ÛJó"ÒžöKýîAô´ïSÉNŒè äWš³æ3Ÿÿ"ÌŠ4I£ÿjê?7ìDôŸ·h“©ÿ"B‹àÇŽmðÞûß–ììxéáüÃÿ>«$ý³•v–ÀTwð/Ÿ†Â7Ý/D€^DÿñyާúsàŸßt÷;!{߯CööŸ:½µã=¾–x_ÝvóAøß~é“ðSÿì¿|瘠þ €>wðL”x6!dpN€%™®¤¿=¨bàCkfçá¿ÿÁ_ÀìÜBl¿í[·À/þäÇDÔØ rã]hÆ=HZF$ñå_?ø5xó䩨G3Û®gËí­è¿ÛÂÜøÜ]’Ãf˜kðü9>–CåÅÏ'8t¦áþ{îPáÁxñ¤ù÷dz/¼gÏ_0ÆÎí¥42ívlþÝÚOIõÿòQfÉ"Øgø92‚?‹­I €ã02h)‹DL\uÂy8 ‘¬ƒýK¨ÊÆÚ+ `/à«×²‚¥þ½—ÏI—FxbÄÈrI£Ù„/|ùŸ ^«)êW¯èpT ñ_Àü/ Ù0uÍ!Û±9yÕÚ™x;ºÊÉçàÜCÿ%èN–Dú§2þk[,˜’òï×ú˨¿ˆö-r?Îúvç½¹ó!û–:µeÅ;ºúuö½åƃðóŸü|üï†[7ñšxjùQ~Iˆ½mx+JÛç À(Í @u?¯êñ)NI¬†râÌyø¯¿ûGñyÊ>ðšýðþw¼U]ñ"š‚ÿ”‰V­5à™çA£ÑŒ}bâà‡ÙÜ9Ð"à !3Aç7ç¡þêg®%…ë®Ú\:Oe;Ж TÊÂäð±pôÍSf±¢3 >®NÜ^íp¥þº/kµ/ ù '$/åÆ!`F†°bÊ.2ý'm¾R©`š6ñ;e ÂÕ½½TpFô+MþñëC±\¦oó¿.ÑIüÇAV„øÏí·ìÛ ÷$¤þ»µ2\|ꯡræñüóè¿x´Æ·ûk5Z­ú$ÑŸOèGr?ø{Aº¿#Xý`Ýp5dÞú«½í“`­¿<õ§º‰æŒ£±Ú`»»·Ý~3üʧ~ >òÁwÁº©ie®Pžþo ìgX©NéèÝá³$oQÿ Š’ÖÔçóƒòˆ?û|ó‘Çc»NOMÂzŸH‡èã`tq1ƒï€¤ª½þ»¯>¯¾þFü¾Ø?8›oñKD€á¹ÀK?…Ah´øÔ>û\¡0÷ßu[ä«ÿ§Ùlb­NaqqqÓrºí­FÇ;e´«Ýïô~Rß“©ø'¥$ âœEkk»UÀîü/õºóøþ쯾nÓSŒ|}£ÿA꿈Hâ?¿7ÀúukàÎ[¯OüŽù¿ý¿‚×–’úo·©û×јõÁ¿-ˆþrA]?Où·ü?êŸeÀ£þ™ êo_ÿ£¹óçÁÞvCª$S9“Œ¼qÏTRu|?¥ kf¦àƒïy;üëŸÿqxËÁ«} ÏJ~ ‰%g–(™C'L¼Y–øý!§…ª|'ÀB±¿ó‡ŸRà@lÉæà~â_¥/.Ù  ž³eÌ8~ê¼pèHâ'2{ï’Yã;!ÞP‹ãµq a€[‚ê‹›“{ï¼$`« ãÕÕ@~ˆ#ÇNÀ™s¦ `)6`R7,5`%ÓóÕ5"Í~oç\N²¹)¥±ßj4“¬D@²S´ gXl‹¦ÿ›6€Æ`dYVËøÄÊW«Õl;ð¿\)ú½*Á´ZýNf¯¿eˆG V¢³$í]†˜Ÿxú9¿¶U~žF¯qô_¶]³yê¿oðÏð2cn¹ájØùžØ÷Tϼ žø£øWÚ×ý‡ÈþZõþ À?a€ßOù÷£ý~Ô_a÷Ǩÿæ!÷Þÿξw²Ý¦ÛÞ/Q²¿avBåXŠlݼ~î'?ŸúøÂæµk gçL8ª¸ÛLÉ‘ªÆN§cëûнA¼)`KÇuêÜ%ø­ßÿÓ8pÌ8pïÛnƒ}{v„ŽÉk±J. üÿ¦‰e|ûÉï$rL\‰Y÷ù©ÿžˆü{rxíOiíåOƒ×¬…ïOvÝo»ùz°,[|…Ûrnˆ'£v$®ßÊõÃ[÷Í“§áµ£ocg@ú´_;¾—ëÙî»0Â_«Õ¸A¿HÿÙ;'ÀÀ4ªã0ÒÁL•"'1zéØV(•J9ñ§ž{‚¶+‰[§º¤A“¶û]ã02l©7êðW_x0Ú„G•©–Ñéà5ú–Oøg‰è?ÂÔ5׸Þê[Ý’‹OJG¿ƒ–’þO•š-ëþ“ÈþBõþ9¿½ŸÂðÏ£þÄú“ìZ°nú$dîúe … mAT‰ÓRd«An¿åzøw¿òÓpï7ûº\€zéðKM,ÿþEîËÈRn2øÛ’ #€¿Phº øÊCÃãO?Ûgfj ~î'~H!ô!dS‹¼ø.õàÁÇ'ž?ßyá•ØgìµÛ!³ëm~ô_¦ÿ+­µj è…Á?בëÿÚi¨{¼Åf($ŸËÂ}o½Y‰Àz­ëéi1ä¹ ³ðüK¯…5@û5 Ô§ûµs¤eèÊ ö2ËÀ¾%³ŽEª2T•bRwã0#KZ[ü J$ ˜À["aG'°;¨v~Ý®Ãú=œ3ú5XІ½Ïr©Ý1ž?¾ýÔó ”­Î´dþ‡VôA8úï1ÀKàà+aj²ûž¹ç„KOýnð:ùG@ý—-ëþð/ÈþH¶ôy½NDÿñýLÐÚn<™û~œ½÷×°Ûì¥^Œ»qšÃËÌôüäÇ>?õ#µì9È–w‚l‚;DKâXþ©hH' pÄÖ€ÿõ÷þ$!­`ßÞ]ð~ìÈrôQ °ÌçÇ/9¢-d ž<èU(Wª±û)¿çm`­{K%/C ?´¢Uœ¿j‡¿^£ûØÝ·ÝÙ¬~wGW´w”iù6”{Е â­—¿nŒëÕ~Ö‰~t²úý²°Ñh ðÏI°ÏÑ¿hˆïår¹NÀ߀~ã02XÝКÍ"}Ô©V«×u³AŸî%Dº»éÐMû¿~Sü{Qi[ôûÇ¥¦Ü€ÿ•1¦Jµ _{è‘ÐæLòáè?‡è¿0°o½þ¸ñº«bû7Ës0÷Âß±Ç ø—Kaü'ºÖý“(ø·ød<â©þ9ñèƒÎNù s÷/ÜÔ·3î©ÿË=×ïºýøåO}®Ø½#DÌ)¦õƒè9wiu"\Rô8NûÖ;'NŸ‡Ï}áb»e3øá0ÕÆi©“£Ä–O¿ø2<óüKÁý$×ìÌÎ wùû‚h¿äp=… P§Á$Í ¶Õ_ý,¸•¹Øßo½ù ¬™šŒ¹2ä+¸;J-ã̸GŸ4FÏôj·ÎØvQõú¢À0ò‹e˜`} •H*ÒYÿMÄß8Œ ai‘"G|n—J¥{mwÛþ£bèÆ(íìË T}ÒÖn#Ëo,ôK8®2¿°û_¢Z–%jôŸhýG2µhô_.ÝÙŒ×\y9Öûžgö™¿…¹ÿX€+œr“©ÿã?hX÷ÿù æ? ûö~¼ÞYþþyëæŸ‚Ìåz1âT=itQrù®ð«ŸúQ¸ó–ë¹!ÃÙ©¨ÿ%j;Ë6NÝÀôÔR€ +@~ÿÏ?‹ÅRlŸë×Â÷×}ýgŒ úš\D8{þ<õ—â¶›Ùí7‡¢þ ”´"Ѻ7"Ú;r¸\<î¥x­|abvnÛ”$Я À‹¤^à.,–áÅC‡’”ƒ¥ ½[gA4ŸÔ‹n“f³™Ç±Ï>æÁ-€v$€FŒÀÈ`×"™þ%@vaaaQo^»Zþ~Ó‰Ò@ÿ ²úu^¤}NUtÆà6²\rôÍpáâ%ËW¹4ý­Nbþ—Ñ*Œr—Yw·ßr®9°/¶}îÌúR`Jv•ñ?šú¯'ø·•È®þèÈþ8! $3 ™{þ=8{îZ w]4ê², øÔ}>ôÞû€Ú2ÜoˆG„#À?Fˆ;H¿åd9N¬ê©¢Ô !x,W+ð§ŸûBl74È1 €ZªNé" @aqñ œbç+¯½'Ïœ‹"gÓUàlOPïz­.ZN)BC·,Ǿ^­ûø­7^“ÿ7[ ð"w Rz-†j¥ ϾôŠ1† g»áéꧤW®G*­ò½[f³ÙŒÌÿ`"Sþš™iøÄ|0–ÐlG$§jöÉò¼~ü<öä³±yeoØ W~ ÜPW2ÀØ$*Á‚Ú›ƒ»p.ö™®;›7®S¶ú €äðuvå€àyƒÝg=ý¼ ¶ ȉ>)w§ _´Ÿñú‰6ݤÙl"€#>OÑ8w`Ûq0Æ`doK©rgNPœ¬lræ1WLËIòêE=|IdTÑú¨²iG¦—Æ@ýþ4…íKš%ëµ³Aôû’z¨Ž WÇE¿Ç¼mûøäì¹ ðà׿¥|Ÿ—,Kæt‹þSÙGÛÿQA æù‘[oºöîÜÛ¿râY˜?ô¿-QÒÿi«åŸÊú¯e 2Ž­þ$áŸLûÏFÀ¿tÓyÇZØ80Cl%¬ËYnuÿ=wÀ§>ñƒ°aýq?†ë“£pZ¾Ô°U ÐrXð¹Dþß?ù+h4š±Ý>üwÃÔD. ×’ëÇu$C£Tª2 ùBücX°ó6ŸÀS²t"ôÒÔN ¾»§þšsÇbŸÛÅØyÙVå·uݤGp­8ä ñ*.ËpìÄ)ãèÝ®Êç£û(à>ÐÛ˜A¤ÚèG`tÛ‹çr¹Œ¨ùçàC”P¥@Ò* Æ!`Fk¾½8 °cH¶R©äI‚u™æ9Lcm‚ `Ó~+‰ÄÏ,$«gq[ Çvøõ7Àkº¾ñŽ=Ê%ñ_(yÄ©·"¢hIæt‘À+UEÚj.“áiZô¿ræ1ñJê¿Bü§¦þk£=}¡€ÿ\‹íŸ;²¼æßÿ6x‘Üvdîùeö2×÷}5 "ÔQüv£r8¼åÆëàg>ñlݲ>T;í¨$–€lA×i£]~né[(ö[ˆ†mé¯?òT"ÙZ.—…>ôþ]~ ¹`FWRç+àŸ$dË-Ç–ˆ”’ |/!ÑŒhKT?TɨCóÜˉŸ»œ\z­Lh=¦ÍÇå¾nít,fq¼ôòh6›ÆZ¢n[J€¢›î_i@õsHˆ%âo’ 3‚ndй  ã0²T™ŸŸÕÛàl4“‹‹‹HH;ÿ4ÀÝÎXUSòAïµS€q'€ÎdžÑÿÇžúN€íe’ÒV /o 5*Â…$Íÿ–%kÿe&ŽÕ¹ûŽa÷ö­±ï¨žyŠ'÷ë­!LþG¨õWêþGn ¤‚ÿœÿøçüÛ@/»²w|ª§¤e?ÉÇ¥p¥˜¹žî„¸áš«à§þåpÙ¶M-T(´'Á‰, a«u 8¶Ï‚/7*K݈%0æüì6þÿø«D¢Ý˜c!8£ÂÈ*A£È:òu :8\˜ƒG{&î˜\¹}ï .o*ñßè”lk ²½ÄF 8Ð<󸥋±Ý÷]¾6¬[£Lªðí,ï_:Šë—pÝd™ŸM†ë¿åJÍ?K´‡“l÷åp¨Ï«Õª“e‚Ñ~øM-J(ÄÿM@ÍÅ6§`<õƒlá\˜+‹9µf¾“‘Ù)# lP†l’ÃA-]X)­²ÒJ –kÿq[ÇQÞú}ð[ô8yú\ —ƒûïº ¾ð•‡ á6˜Ñ€÷;:õ€( bÞ·¸÷F7K©BÈKÄ‹jµß~ê;ðÃù`_;ÛqsŠI+V¥þ%£Šw‰–ÌìÑ#¾€?î^| š^:±.´×ö­›aÏŽmpþâ\ì¤xïýY(–«­ŽÄO³çÝ.ļ¤IM®–ãÝlþÍ&>o‚פð·_þ:|à]÷®ÛBû]¶u \Í~øÆ·Ÿ¶Ïò3~ؘ‘Ž-Ê F¿¶ª¿ì GGÇÛ¨ÔjðÈãOÇ$S€ÂÁÜñ/—‹…_Ó¼?Ÿ‰#²wFɵé²#e×Ô­Cóìó±]rÙ,ç^‰xŒ9è·Eÿu™j¯ËuãÇé Wª»ã§ÏÀµ íc,9 ô§9Ú}¶Ý±D‚¶ëº¼@¼FðÏ=mZ‚þÆ`dàzÃ÷q+¥].—'±`4¨oGôý;O?Œ´äë¤P:¦5ßêÈ:ë £ÿ‡¿.,Q+Òc[IÿׄüO¦‘ÊTRüS‹÷_±¶lŠGÿ›¥YX|åKà5ª¡è?U£ÿЊþkž¤qÊ#ÿT0þËè¿<¶þÙ9É­ç®ÿ•=N-yŽ®§Ý¸ûý÷Ü ¥r~ûO>À-ìè`ù ˜20E…3,î4ˆ¾÷q:\p›˜ `A³Á€½Kàk? |è½±,€ïý®wÂs/†ó—æùyyŽÒSx-E&ú\cîì`ãÃTw—"™ ßzüøè÷ |†í ä÷¿ æ¾å™dƒÌ‹ÌËŸóZQ^ùm (÷æÔÁ? ^…]ŸÜtèS;.Ûʰ‹Åª}Ø ñše2l“×NdmRTw6’L"»¼çR~Ÿ¾yüTk5È&”‘éNïöZš›æhNúž´´ÿ„õÊÆV€LlÉ3&HÈÞ'¥R)‰0ê0àß8Œô«" [N0§R©`¨*üI ¤èOz/©^/À¾S¶8Lê`<èzl¯~ Î]¸È° —LÛdô¸?Bþç·þ¡É "8Üyóu03~Ï=ó7P_8ŠþÓHÛ?¢[Û¿hÝ¿dø§~­?¾‡©Â<=—fÀ¹ígNnˆá6®éþ+I÷|÷{ßÁæä%ø«¿†¹ÜÙå0àÏ#©–• ¤ÈRîµ!cÑšÓeÀ¿!Z“&/~õaxÏÛß ù-aÀÁköÃÖàÂ¥>nlåÍ8qˆ´ˆ´­ ôGðÏêXîÐD Ú€c§ÏÂ…‹³°^%Æì°2noö)?{ÉšÏF@.˜ßÈ™0Úñ)¸‡Û0.vVg›ë+ËÒ9hœ{œo íµffìÛ ?ûwbdví²ìºaÍ5»v»vT£k'¯:lü7šM8sþ\šOt&éÝNéÔ¡«S÷™vDÛi¤€âoXûŸµD¯@"_ʲcHï`Ä8Œ N @L°l±XœÄùÈ/¬m‡§Û¨I BFúeÔ?©·sÔÐÎ)ÐMÖAô÷åoGãnH÷rü+…4¬ÇÍr_ßA:•jµ:}ódxí)ö-¡#_%/¥—ZóÜa aÏαŒ”…ç?nåRÀü/Ùÿe6€$Ô3úŸQRþ3ü;¢æŸòôûÀ`m¾¦§û§›zÌ4BÓ•¨³Ú›QÉ÷¼÷>8}î<<öì‹ ôSÇ(ª8¬ ®™,yr QWaý¿Û„&:]Yì±i1ÐÕ€—¿›ØG§ž*wßq ;qj —›IDÚ|üþØuÈãã ’è°\¨#·X£O?÷"Üw÷¡V‰œ pßwCåÉg@lðhŽgø%?–åœÏÁãå @Ü@À·¾³‡b€©ÂØ·ž|þev¯2ÐÏÀÎÉ@&ãðû7béáìͦØmx.^šãqãè]ç¶³M¢„I³v%hÒ¾n‘}CN6‹;ШÈÈ "9ÔNQõiœÆ`d|Òh4(NZ¶cëÚíZ~,ðê'3 Í)ÐŽµtœÁðRøVºŒs´ôµ£ÇàØÉÓ ™þ éÿ#Fÿ’ügH"0ð™›‘}ûžÛn€B!^û_>ö4ÔæÂÑŒø<~hý'‚ ?‚ÿ¹Ã€‚ã_+öhm¼œk¾»o}§>—ÆUôž6ÜË㈈~~Ãúuð¡÷¾Μ½g ÉðÈ•Åî_›¸–heÖ·µº ×Õ @2…:ƶëÅ€q£åìøzôI¸æÀ>ظ~mh¿{ßúøæ#OÀ±Sgyú8F’mÇöÔâîÊÖøvîÝZŠoü3^-ð¯ÿ-ÿ€Ñ‚ˆ°þ;"ýßñýçS:7}2FÂÕëœTÓü“HSW¢AÚ©c̨ÀÚ~wÝq lÙ¼ .]œ‹)*À¡ìŠ1jÜé[d‹5ÿôŠ”y*“­­];“˜qò‘¾nºîjh6¼îŸò±v²Æ:)`RŽ A%’bkUlWë"d9P¸ï?‚Wžå=Ê3zˆ ÿÓq ™²¤óñ¦c±³*ëÖ®ŸþøœôÑ'll•^!ڌΠ=ãÂkÇŽ{fzŒ fž¤9:u褷“ìpÙÈç ²H­VËLLL8âó2 É»X–4z9g€qY B™ŸŸ§¢&#ÿü‘-Š“år¹@iË‚iGþèÝt èµÖXŽ#Ft”ÅÅ"œÈ£MQYxþ‹Ð(_Vr5òoA+ú¯Q§xˆÿÀÑ[©ý÷£ƒøšî¸ì·Åa5Î%-Ž{9œ³½ž¿N0²ä¼úÊ®]ãpw,oºÜSƒ&ôÍ_õž¹®`‰Ö»î}«Y`¤êߨóYÕ›Ýèhé°NšŸò=tÔj5gÍš59ÇqhµZåØCdX˜ òQ¯±Ps ÆÏ&SÑÀ­\6Ys¥R)KÚ¬¦i™íÞï—ü©êÝï¬Ôxý×kœƒø­W^{fg|ÅMÿWWÂ]¿ ýŸ·üÞ}‹ò~èê¢ÓÁ11”Ùg> ^³ Af€ˆþ·ZpisqðOˆÔû Ÿ§ ·En=dnþø@ ²n_ë>ÿF}¼ýf¤-·ŽÔU'¯¦î+FŒ¬ЦÅw7­¿{±ÙÇϰ,+#ñ‡È>æ›ã8Dq¤YfÒ€‘%€‘­HÔH?m4˜¤Ëf £Îs5ß-dð¯ÇbüÒˇa±\ ¡`N®§K!")¡Å@e;@ð37·mÞë×®‰ÿFñ"”ÅÿÒê/¨ýãÓb¨²ö¿SôÿîL€}õ‡ÙËì@AsZ¶Ô¸Ü׃ ,E7A— €=x=iœFŒô>g¢ëI7Ý´Ú• túµË€,¨Õj6úy¶Y‚ƒL$Pb#‘JïéÿF€‘~ø¢^¯ÛÕjµ€ê¤ï¦h•ÕÔpµ.zãò{ê¾Ï‚ª°v ¯}£¸òj]sÀþ/ëE:ÕÅ×íOný÷â—Àk6$¬‘ÿQÝÈÿZoýÇÞàÓ{À¹âC¥«yþé®ëVjé™.cꗤ̈#êDNs0w3Ç:e ÈïPÛK›Àu]ôœc•ä"€b@.—kGhÈÀÈ J"È7TÏœØäÌÊÉŠª!ß®v?MÁ$ET’Ú„´38Ò”‹úžGÕš1<ªS·-ÑúOn„÷ʶƒ¬ ÌðòëÀ¾æû‡¢däd5è·a|×r»•DÄjŽÇˆ‘Õeƒöä£-»Ù7å³[ßrè™ÈÈ x¸`Û6i4iíh$&`L¤X,¦²j2ɰ¿Ožç:¸({R:Q/Êf¥K5Q´2ûR¿úÚQÞ§:@Õ¤q—ªväõÿAô_uPø}¶±dèÚ«®ˆA£‹‡¿ÞÂÕ‚ù_n²ó¯~äøÜµÿN«ö°þŸÙÖ†}KþÉ4_§2—•þu?Ó…eyÎí8Î#FtšKie·i6z¯z«[2AnÅPÊ;d³ÙŒ4lD¹1‰dD¿QšÆ`dÆ&‘<l"R1)sÅbq2ÉÐNi$yÛµìUÙôâ„Ð /%še Æ•-f~‡Åƒú…úß14¦¡¹.êÿU#mÇ+öìŒíë6*°øÊç•}ÃìÿnkKþ'Òÿ1í_<ò#Îoëª M/˜9¯¿^6 tÙì#FŒ PßuÓM£ÛNth7F÷± @FéDF±:"LÍ¿qÜüQ jÿ¥ç-'2h·`ºÛIßîï½FúÇÙ(6ÆŒ‘4©V«ðò‘£¾B ù¼ItŒ^{„5‰_ÿ϶lÎ53Ó±]š¥KÐX8}Y÷¯’þ©•#[@þ‡) –¯sˆ%")þÁ’ÂV°·ÔŒŽ3Õ1,ÕÉÒϾƱ3°bĈ‘á̯nçZ43'Í‘}dXƒ0{ÇÎårÄâ}"º`@ø×¥²ã_™ŸŸVÛ×u'ÊårŸ·cþO{ìÇèé¥ @7N„q0ŒÀH’œ;Qzæ@ÖÿSÁ¶:€cP ÃŽÿž&°kûÖøýî6 rê¥ðê-Sÿe6€V«z$ý_¶T2À™ºí¶¡êˆ4ææ•þÇa¼+ê6“öoÄÈètW/v}’# ]ÛAl7Ž%¢ ˆ¤20 &ýß8Œ ÅÂ>§Ífs¢R©äˆÏÄ,Âíjø‡Õ2i¥{üacD•F£ÇNœ Õþ y +ÿG¬f <ÀèÍe3pù®xú¿W/CùÄÓÁ¸Ôˆ?%š®êiéÿÒM1±ìËï¸Q&ÉWUýª:cWxVç#ãµÖš¨¿#zèïnZ.GÖíJ„ñ%öã8Y™àã¿{Ÿ´é`Æ`¤O㪕äÚšõýG¯[aaa!‡¨Q°ÁI/$UÝŒt£pºQXêïã±vS³´šHªÒÎç¸FWz=æåçRÕ:›co?%@¿YÛ¨Vëì Ü<ý¿ÉNq!Ÿ…Ý;¶Å>ïV¡rüñ Z€e8*û¿#LLÿ÷]0Øúï&ºîJ ùµÿiÉü/Ò$— ˆÇM—­dÐ§ÛØ À6bdÕØý¡h½:ÿ¥ƒ9­5wÒç¢þM ÊïP×SàßlÛ†r¹Œéÿ9¶ÎY—ˆ.Dáè>ýß(3ã0ÒÝÚnÈ@¾U©T&ÙcV5 p²¦E Ú‘þµnƒ6:Ò~ߤ¦éãfü­ô:Ðr¹ÇOèƒH»th…Àé2Ÿ{JÀPÒr LN`ûÖÍqP[ž…ʉ¯ñApX­´TµÐȯlû¿pøäâZä7ƒµ}°éÿQ]et×ðý|¾_dÚë­Œc3bdœõcšÍžô~¯\*íìJé$ˆ` ‹½—uÇRÁ?þƒоõŸ) 0#}›¡‰%¼ƒv©Tš¨×ë9Õ«‡^;uâ.EI¤}¾›.Ý*ž4G€‘ñ7°ú5ÀÇa¼¥RNœ:ã+S ˆi¨ €F×AtP206>9™‡LƉûÆÂÙÐJ-ð\·œGûeQ>ùg:˜Ü ÖÖë‡>7ÇÅ 0¨c\N2¾Õ2u¾¯¢e‡FŒŽnídË·³÷åúÔI—$qˆýÐXÈbª?‚‘Á‹©ÿÜ ËåÚw0d€Æ`¤W] LFõ¹³¸¸8Å¿#'h¯ ?©m`7 ¦%eÄH· Î8Þ;ÅR Î]˜ ÍRQ«:ª=·ëÖÄÙÿ=· õÙãcCvÐÓ¶þŸ(Þî ‘3}6¹ˆ“¨q–vOë~ÿꕳ‘ÿQ85†y®t½¯LY‚#£›gÑ÷äº}ŒÚøÑ²‚¨]ŸòwŠGÇqlñÝAg2Û¶ÛeaFúQÐj¼f[¦R©(¥vÔHè¦~¼ÛÖ}QE°ÀÛ¸isÌúÈb©—æf[ª4vïë£b£‡æ‚YÛ-Öǯ[­Õ ¯…VnYÉ)ÕmIWÀ?¯ùž Îþ? týUË bukc¼íó:Ÿ']£ëf­7bD¯ù×ü÷ãd6œÂqƒÄãØ ÀQ~Á?ÒPA®ý'Æ `F–0×Aá¨×ëTLÖL±XD€•¤–Yêßé;’ŽÓÝêqÖ+1ú2{iš®—¨Z©™niz@òäDÖ¯“â5+‹Ð˜=ÚZ½I«Ô^ýª‘0VÿïGþ‰ìYÈï=ÈÄV ¯\–{Z÷ûwz£ß±ö[Ç¿Üë×J½n+Ý1aÄÈjþ½Öû÷Z®µËEY1­ÕjXà<t… Ðdh,¶9ú ø’Ý?4¡̱¿O‚OA0P§)‹^ëöUÏ_·Ê¦W£ -}i5(ð^Œ¾qæX©rqn. È×-õ?-“Vþ>Þ[SSذnMìz¹¥ иôZä#žŽê5:‘lÄwtà1OlkÍÎeBºê±AÍÇåš×Ë­?LæÆêÕçFŒŒ èO#ðK³ñ“Ö#ÉÖΙ­r‹‰çØ Ø Ÿãßxp’á’þMÍ¿¦b2Æhþ+“’ˆÉ‹^·lµZ`˜Fëÿ{!ýëåïѾ¡Ýº½zWrêx¿^ÛqëJ4/ÍÎ% ºýëeWé¿?99…B!ö~cñÔÄüÚæò¥#¨ÿ—º 7XÎP!©äÊ€ q®uâk0ä¼FŒŒÖ†êÄÕÕmûîNäÛI¿ƒéþ®ëò…Œ Ik¨>7 €qéC ž±è˜˜ŸŸ/`ê zèdû?Ù—:i±N"Ö,î¨<Ô÷dQU E_·S:IŸÅãŠf2è‚WR]©®çN÷y ÌÍ/ø÷±XÖbÜsÚ(ú¤… ÏÁD>;ÿnyÜÊ¥PÄ_%Ôý.nÝ9LÚy Ó»† ÎTÝ•ÔKy¥¤ºê¿ål¨ûš£Ëµ7bÄÈàÖœ4^.õïíˆj“øÀ¤=¯òD×7Û¶¡T*er¹\–ëv°Ç¡'ÐoF–fô1ÊO䆓Š|­VËEAtD6I¹$9VŠQa s®¥”Êe˜_,†Ôh¨ `„‡Ohw?>91ÙL<2Þ,_ #ÖÌ—hu£ÈA‡N<ãø*»Èô–¡³4g•n¬Aͧ~ǵ\5üý죳³Q7Ç·YÑSŸ÷:7Ûé=éÀŽ’âÏ4›MǶí,¦ü‹¿Éà8D€í"þF‰h †`LÀ?„IùÛ8GÙDœ,‹91ƒ «FŸº-hç,è¤@ºíÐÏoŒZÉš”Gö“¤X,Ci±$Zÿ%¯s£…¨ÃOɲswzºxK+ƒGþ•Á¤€Z^²¨O›/Ù5@óë–õÖmDz |¹ÈÕ} ¡#FÆÍnjgÏ·³]Óþž¦UQ­V‘ò–À'²$YåèœþO `êchØZ2¢ù|WŸ7 ›MÄB¹\Î%¥îÚ éäDT¥Ž=˜MŸãá\Çq<¯rѬÖëP©Õô@û½œceÑŸL¨ÿç‘Ôþ+º88º]Êhn-‰µË¢tì×>jð¿”9¶œ¿¥Û53ë#Fº]oºÍîmˆKªÿêHÄ {8ì1'À>_fÑ€uȘËåÚÑÅfFzŸ÷$äECÒMt°É˜MRýQý´Q ©vdAíœFV§Œ3Ùãb±¥j%Q­RÙšN'BIà§Ç»0UÈǯI½ ÍÒÅÐJŠú“±¸±ëÿ=lј] 43=–€{ÜÀô(Ïárí³ZîŸ~Ú†1bdxs±}ÑM·€vN„vÇẮ…D€ ì[˜|ŒC2, Èf³j-^Z'ChFºÿ)“Å)•JÂsú5’÷´¶í”LØï¤Pz1*Œñ±òãv¼åRjµF›uLÿ{Ö¶ãõÿ^³ P/µVnI8.Ø?’IH²Ì:±‡~ïª|:9¶FÍü¿éÿý›Žiÿ:Ý;+¹#ãî†}œõOè>`‹V€¶È Ê0@á0d€Æ`¤?)‹DLÆXÿ………)œŒÝ‚´Sƒ`wî…,h\úf1Æf’”«U¨7š JŸóì2…L Û¨°­ +©ÞŸŒÍ8]€ìŒ1GþužÓ¦ÜlüΑ#«]Ÿwµï¦$-­íŸü[´ë—ü´^¯çÐw”ýy@‘€\*^Ѷƒ°qÑÞX““O¢Z­FD½M¦T*M°çv'€ßkªO·†@»J§ÞÁK‰Ú12j©WkP¯7“ï}Ýõ û/c[‰h6•„ùª›5ÔNG¸­öIJ¶n{/¯&ðßëþËý7À6ý~6ë¬#zÎËaDþÓxP¢å‘*‚ƒÜ'Œd˜úã0Ò÷¢ëmöö\­ÃÉ‹Å)öK¸IÚMø¤¾ÕIÌÈQeôýIý°£}I»Q`8N ·AC«Íà\‰¤ öJ%¨»õ1»&ýókãX68¶¿GUð¢»AB䟌Ñ`¹Î±œ¡Þ㸡¾’úMíÆ2îà_÷c^I:UÇvÆIbĈ~6•Júæ´SÛ÷%­GIÎë(ÙnøûXã|~­VË¢Hl"ü+’2Œ€‘^&¼l(kmlƒm¹J¥2áë:Ã3)ÅhPF̸µ4b¤Ôªup›ãëä˜Èec·^¯Q À¬ŸÖÓ6ì8ä|vvYÁ›¬òƒr>,×–û\é˜ú¯Óq˜µÙˆýp@§¹Û©Ä·[§^ROu Ðo6›™\.ç€BPŽ™²- $g€Fb›S ÿÄÇY5;;‹^5""éÒã–/‹éèŒwËüÙKŸënÍRËÆðZ&þí;/‚ßúý?õ3kè’p¶Flʇ2Ä:rÏuafÍ <ð¡÷ÁÎíÛÂp¬2'¿ô›P¾ø:û°¥t{%Áu¸ì’垊c±Ø£Eý×SÞÓ7}Ýo¡}êõ:4šn°ö÷ŸËu*)òüóoY4éFE&À1žh @\ LUj 4éàÌqŒç±é4Ì92bDoÝ•@Ì×3¯V¯>Ú?ÙÉÉÉ,#EÆ.%1Àr‡êa´1€4éü³É扜Âââbž$ÌÚv þ½ˆtû·^[ƒ¬%'Nž#¯‡#p\o½¤…#°B„L¦°m󘟇qT‹P9ùTÏ<Ø‘ 7õrëþqHþ]\¾Øæ1€ì]vC üãÂZk6â¥*ct/ãBn%±ã3ðÏ;Œ?ša×c´iø£(³½îµõ+aÝЉ$Q=»#1²%M?¤9¢eŠe·T®$ÞbÔÎá j]ÿ¹xôH˜pÈ8éÔå®û7ëM{ÐoΑ#zÏÓ4ðßn¿4ðßÉ!ØÌ0BõoµZÍÁ2dö­r¹´Äút¤˜v©éÿh<Ã6›M«R©FŽI ¦Eˆ—-YŠ#ÀݼÕz§Ô·þ†àZÑ™Àáé¤Ð •£ƒ‚(YC÷ùl6â¸<`;Y¬ƒïR7(Ž€‘9Dv‚­”XÜÀÆḛ̀÷â,òXû_k4"k˜pÖhœÅ;~+‰;„gr4‚Ò}'S’GÂURÿý,tB­5ê¶:ÿ–®©ö+åž1bÄÈèæo»yœÆþ¯fâ¦éFù1p€ìD†¢ Vâkt .)•JI€ymÀ¿qé`,„˜5Ù{N¹\žd“Ó‰zÓÿ1ëd¸·ÀÎØ‰Ç†lòÐ,s`j^¦ÙSA²G>ªQIÐ.‰ÿð‘P¦­ß$yÅÝà\¤&ºi.Hh[Éyž[Ãñ¸B9º¾#ÀÏ3¯^þo«¥ Š·zxÝuòJHÿ×)ênjýo|Ð. Ó~ä«íÅÕ}“Rÿ•Ï¡ñEÀ/-$*G'>Bûˆ¿ã0ÒÅÍ'”|¯^¯ãs'-ËrOÿ¼Èe¤õ•ŸGo^;å!¿?¤­a¡WÚwwc|$}Ç(Œµ¥¾g~± a‹?Š”{9v2êR”øÇh!Ñ dl‹Gþs¹,dœL|ìn“}‹¯Dþ2Êî_ßÑ­¿K@î”@ðO'À£Y¬·„û>©…Þ8I¥V õn­ 9†¡ë¡s¤¹’ô?Ï>tƒÍã,ŽìyuÞok˜ÀQ±RÀ.@.)Ú4J0®Sä_Gg„º>G€#Zb€€}?‰?Kê·¤6àQPÕ‡ª£:JØŽPìc1,‚È™f³É; VÉd2Ü€$€^`”qér‘ŽNœ‰YäÀk˜Ö¦OUiÊ ißAvH3,Ɖp9޳\©p#¶ÿDqp%?J½I¨¨Ý§œ/ƒy6 ŽÉKܳ셶MEª=m1ï²1¬ äSù§9öÄؼì‚$ÈñµÐóÆz«×üªEç¨51î[} -%à­ÿÄÅ,cÀ%Î+]¯QŠ Mç-7È]%ëàØ:F¢ ˈ#ã¯áôl4T0u]7‹mÿ$— ,GZŠ$B_o®žqé Åb1Dˆ£ÿrⱿO¢.M¤þ4%2HFænú‰®vò'UͦϪ/ô#f*àß :Œ&Br»`Ú8n”³ûûÇç6S˜Ö½fЉ-ˆ´ÓV€MÓ³è—øÇÅÏ·ÍHd‚N‡$† `˜s$fŒ;v˜qœ ¡™{ݳ™%"ÿœbysÁ-ž¯^ÈM¯8@5ÈßÑù˜uoIhÀ¶#FF¡K:÷c›'9(ÕßYƤV«eQƒ¿DÿDæŒ6ÆcÄ8Æ~¢ÎÑ-#·¸¸XHB„r‚¶üƒàÀ¿ßß ‡ÁrÈR"zÝîë×b{ Ô¦­Útþ­–ö‘‰:A?gÿ·oïç—&xPo4 ÊÈÇl9éÓp"CPoO.!  p6'b”)<…Üm2LÙL¸^68F|Iú¼Ÿ“䡎(K03=Ü«ò>µ²[¡Ù8,Àè¿úU‘ÇZ|F¤Ps ô×u%è8лƞæ±'[?Qün”CÚ÷öÃøo Žti4|€éz^gµ8‚óˆ5þ–dþ™ò¯2÷]°l+v½©áˆ(ªÖàÃ6â·ï“¢þ•;-{aÔÀÆšÉ8c{Ïá­ÆÖh(VâÀØs`MlT@u¸öHGØ/\ŸSxº8øÇôÇqT€¨À(Dã0ÒÍüŸ››£ªÑ‡™F£P*•ò˜n“íT ú¼¯cZÚPšR£vØå õzÍ'þë| Ú²òU­Þ[!k 2°î:!‚®Ù5‘·ƒÎÉD–õ‰×¿.Þ•Ù5l³Ø¸ÊÅJ|þ!‘haãH³2zÅþè øþØäü Ä-W™ªnHâ_¶.[-Ìÿ+aLid¼£:7ãè<2bÄHÜ&í¥ ŸìÜ$Ò@ùµZÍqü¾Ü²xi¨_ ÀÞŽ&YÆ`FR&‰òÉâº.†Z'ŠÅb®ï]/J¤—V"Ý(šv†ð S˜ÆU0}ÞJlÇF´W‘òVщa%TÙY†ÃêaŒ¦ßDÑâ´A&«I¸ôÿáazâ‰Mô/ÎDZÇÞ·§wÆ®±þâ* M>V÷âkà5ªC5Ä¢ç}ô×rååˆþƒ3c”ǸÔò<#FŒèç¤-ßC6©T*ô1ÉÓ&%fA': 9埴ù~£˜ŒÀHd2éÿ’  ^¯O²É—MšLÝDÝÛEõûIñ´¢Ñ D Ó˜G6vjY0®ÎP„ÁÕzl'î XЬŽ÷$t‰Y ȃ0÷/Ȉ´ý]ϯ”o6]8{þbâ>Κ±¶#ª#AÇQ ï…ÿ”“¶ÊÜó‡†R¤ºI«givFº.k™#FV–®KKÓ†U³V«a²  €ä@rär9 ø“Ú>ã0Òyúøù„Áé”J¥{S'£R‡ÓS{ª´6€Ñ6‚I®üÍ´ßOëAÚZŽÚxæq4y«¼ñž¦µZ2Ž»Ÿ¨“e¬$ÞÄZ—¼z9<:–ù¨f£¯Vݦǻ6Ô›M¸xi6~¯³19k¶k öSÑ Èè¿ß Ðã߯ZŠ~ˆ¦Ehé–úŸÖJv©fŒ3bå#Fº]Þ¼TÛ:ºæ0l¸ÚºÔn½Bl¯v%ÇâÑn6›ùl6KUð©ÿ"+ ð7àß8Œ¤­×J@¢L`»R©F¦âv)AIõªQp?h0d ®vÅÎÛêÅT!+ Ùh6⋉·®‚ÌqQ÷.œ#r&Í2ï#Û±`Â÷p ŒíÅý#BÐ<Õ_ü¾'ÀÓsƒÃÁG$n<q6ažÚ`ÍlMmÏèfd€^lð"ôà.¾^ùÒÐ@U?í˜VøÖo­„µA—1,‡Ûˆ#£Ÿßýp,E7Ÿ ‰áýŒ-°Š,™8Ž‚ k”’qiIk®•™r¹<Éþf÷›òÓ)í¿FÑnyú1ŒVƒÑBÙÇI™ z"æèeAFüJ9žêOl›µ1™l‘¸¹ÿv…Ç„úñB~&'óã C|ç„xP.W/®]PºxÉ8ts€§dÈã–„€.4N>Í®ceèçÚÈòëo®.÷‚¹'Y=º6 Û½[bnåû,Ì@€þAÇ2ö¯kí“2Œ€‘$)‹ÁdÁI¥Î=ö:³°°€-íNmA:MúN,ÖKMãì—p5‰í`*¹èE2³2zY3ìøKL†î%þi~ë\7<8I ˆQóÊl"ÀÔT yqÂ(Ãȉgˆ- ¬Â®Ø¸ôç‡e5npÀ¼ àðßC³²04p5.¬ÿØIeÆ 'Ú]##FŒ¬< ßÎÞo·»ïé&›#ýõz‰ÒôpBÀ„.à 0bFRn™úOjµϳ‹‹‹“V"ízgžDZÕ¢”±Ü‹’Z ’Í8ñ‰¿§ñýé§¾cK¼RBOy$´&¶pUO¢RG'€§2È‹HrùLb—€é©Iplg,ܲ Ü+•œ>{.¾0X8›o -ÑÒ/2 [tú`ÉéR]ÊïéÒâM'`Úoêÿ¸ë~ÝÆ`JŒYyà?IÏF9iúµéÛ9Ô÷18Y¯×sL°èŸ—,#W™mÛDIÿoWÿoÄ8Œ$eÙRCmÁ@?fä Ø{³ÓâÞ) ‹õ0HcŒ´™RÍæØÜ—*|—ìòXÆP©Vâ ¨éÍoj]w=GÕþ¯Å£‰ïÏLOcQýïåhßà(–ËpúÜ…Øu#™ p6\|vtså ¿”"ïJ$^RžUCصª{$‘ÏaPkX«A_.Åa “ãÅDÛ1²úr©»¢úª›–âpZ­Vl6+3$i géÿQÓϰÿ€‘nd~~ž*u5bþ‘|¥R™Ž»HG¥Ðް~Òø»í%ÚÍï¬øIÈÎwŒ‰*”iä®§ö”ÇzòZ"¸¦ùõc£å=P äZõä¼`D0ý?ñ>×ä6ˆ!Ì W]|on¾§NŸß“™IÈlÜk¨ `(‰PŸòû;µLROjž„,AÁúÿœŒüƒŸ™Œ%Ñíœêw‡€qsOœl8)™¡i5B¥RÉ%M”aÎ~Û÷™ùÜY …<¯ŠR-Ñ„4ò½ Kd@è£v–a´B*R;Õ]<•8€­›7‚MÈ€<á¸ÑÙåFNt‹µÝûâ¥98y:pb#8[ïäuô^$ÿ¨™¤[ЛýýÎûHm|“Žp¤S`‚;j‡> nå¢ö zPºS÷(ðzP›…£Íõ6çLj‘•ïèµÕß t«ü=Ä$ èg-Å€ÅLeI YÕ£NÃ`FÚ8xtIN$öÚ*‹“Õj5§zﺙÌݺ#@7ßk tLÌå-Âù6 Ø—ï+©þó¨iäòïM‘"ª/c€f 1Í®Fµöß+î-gOã÷éÖM86õ<b1ñ‘çËÇ«äÀëçŠã:qö?u&6­éÙzC¬õŸ;¬á$¦÷·@¿LñÇh?Oñ·|Pôè/ˆ×“ÂàgøÏ'ÁsØûl«½ð?Õ¾¯aGY©—+lô¸#FŒ,¿Ží–  ~nZ ªv}£ÑȰǬLõGà’”g³Y+—Ë‘´UÞ€ÿÑŠmN¾s=ðˆ ñÚÆúœtX_ÓM[~ Í4þú/ÓS¦H]…Ô,Ñ’´$ ÇX«5x&€­f2à‚€uØIß3FZß½ô  QÙyÙ¶VtÜhæREçŒê¸mýû„“7ž>s.Á÷ွnWˆ0ô=ƒºIä ~)ÎâO}G.U‚äÏw 3 üš_ñ;¤z¤u€x°M—)Ò&©­àñ¯+]Ö‰ˆihsl@¿¥«®] ‘³Ž1bDW]¤–ýFKÕµ¦Ýëv6¼ÈJ¦lË:Žc«YÉ舀þ¤,ùÜÔ,€‘Ó8`ÿõñN©TšÄÇ(˜nçÝ$AÈRÚÃ).õF& …–Ñ?FªPÕZÉ*wf„®u¶Ä™h,´ †ëìð^cÿ4}€©È–Í ®´pDpMÆÁ­á©ØØƒó—æ X*Aab"ô1‹e2sÀüs1‡§\°¾®]Bû>€Ÿp@ôc{!K}»ú©ø,n‰Ï1ÃD8<ÏåöÚ+n>Ö®;€6®Z£P—ßѽÔŽãŒ0bÄH?s¾Ÿ ÝhÖ@;Ðß®f%7 ì ñ$Åè?’ær9$$]8Œ€‘ˆqdÈL2‹‹‹Ìhõï7# —ϵû~¾{ÆÕË3=91v†¯ þ1Ê\*U Zôê°K¬üeÐ\8ÔB‹m £Q{5D¶ÀÔÊ€{éM ëvǮ޶Mëáø©³Z{28p÷| GOõ·‹ã !iid¦¡=l'×Lèâ@'ÖÉN,´P£§mŸ¼VŒ»…“]þÜ}#æÀó±ï.ß +|B¢yâ‘ «Ã«¯‡³gѰ=¼@LogÕP=¢\/Ïw"ôté’Òüð'²ÖŸHÐïøéýØÒ/ú©’îoÌÜð3ðû- èewµí& ë÷™Ø`€Ö ÿ:{Ýîs?1²ºth§Úü¤¨~/΀´ý£ïa¤¿V«eÐw„näõÿø9|”X&ü€‘.Lf¢dàC¶\.ü.[êh t›]°ZÉS ù±S‹Dp}péÁ…‹sà6“¦ G–9G¾œøPè+lï76tý”þåϽKo$î¹w÷øê·oó¹6D@k!'ütÜœ9>y‘XÃÚ9ö¡JRJ à½HMów‚ˆ¿íwø·Buýþs*^ónCì7ýt¼‘èÔ. »îk˵ô'7– kÜ¢ÿ½þV¿àÙnü1àŸÄZþu ü¥úhí7zFý¡Ë1ºŸqÛbê?,£þø\´ŒZ†ýß8Œtžë$è3¤L& ³náôŸ$…Ä ÆÐN!¨D!íêïH°+û¦·|ìô½ã`\uû4Â0?6µäb|{½87Ÿ<¶éM>ÈÍé <ÙºïOΓïGÉ=ö¼¶Þìk‰×mï®ímœz2õû ¡ãËGÞ€s.ÂÆõëBŸÍl½ì-ï€æ‰¿7XdHeOý!õ“5ûN˜Ðƒ~'^×Ͼý÷ÉÊÙqdvßÍÉýH&/>7ìóç \gtªÃÔü÷J:;îÆ·^óØd£1²I¶{R]ÈO#”]ÇßX@ô×l6ƒïR¿íyܰ-ùÌÌL³0âOXÁGÇq(¤såeF’æøüü<ÖÎeòb6@®Z­NtÈR÷;y £[JFj¤¨нKÇY0M»ËB©Z‡qz¹o­†lòIÔÓ‡ŽÃþ{^C’˜P…æì±]qñÛ²iCkPrN*†*P²ˆœ‹ìñÙ—^sç/Åö†ËÁÙp4ÿ}ð§hÅ¢Öö+¤~Iiþ4£Dû幟-à©)þNèô°ö¾¬ËnbÏŽì RO+˜[Ž. ƒþÞFy.¾ü-¨\:Îæ°»…ÛÜÝzþIás„´¬i*žO_÷>p¦7Çö}þ¥—áÄÉÓËÖr€7Aëú5]رm \íØÇ.^œ…GžxŠ:^k©r°õzn½é lÚ¸!vïÏy *çßXGç ïW*—Ï…Ìä:˜:p¿±òìèö³J¢ê÷'½×h4Îw²Âa@êõ:',Ã@%v+Ø逦€q‘@_L"ŒB>Y°Ý{](•J9AصáÔÀG½†½´ì¼w!;ƒd€’q2°aÃZ8zülËД_f'Ho@ŸÀyf|íÙ¹-ö‘žò3;¡qÚaŸ¯ÇHä<݆êI€F "ùpEhÎ`c¹,¬Dmöîܯ¿y2a ø]–6×.Æâ/¯éÂBN;Wï¿<¶Ÿ³ùZ(g֩϶8HćÂQˆ• ú£íûào+õÿ-¿Û‚Åï4nè"èŸØ dÓ øß tj›–µàýFñWŠÃ³3@'â¿ÿ1ý»Ÿmv”D•nôЈ/Uö‰ªp³Ø±-ÿ7œãknšMÜÿ?þß¿ /~C8óôí½Bä@qµa:€ÿ‘;2ÙãßuW¢àÐá#ðk¿ñÿøz ´^ÇñýÖÿù¯aó¦x·’3ý.\xöx&–ÆfBøƒû“rzþ˜ßr§q ò>JxÞMýRi@§ïHx¿Ã@>:¸y! FÿJ:vÜgÆ!`«Ö 8d¤'›hÅb1›î][Ÿ–)fØuô;•%¬VÉç³°nzŽÂÙ±Çé³ç‹úíuW@…>Ò¬ûVŒˆ8FgÌx)^¬_µYð.¼ qä²8på^áðüöóì?ׯPo¡m@žçµÎ=i]…熛¯;kf¦CÏMw@ãäƒñ.r>ó~ГVÄ?ÄäÏÁ? ²øK¢?5ÅŸ½&ù@ÖîkÇ`m=(>£`–é’j6ÓRœƒ6Gõ;º;3.zÎ>úÿµ ˆDhMQJÚÿ#.ü[ÄO:²åfùïþ+6Íò±}ß8v.\šã>I"â ­Ò ®[«‹¸¸þEâó‹ú%DøÖš©)¸fÿ•±ÝKå ¼zä q‚|¢S8[«ñùc”Žr"Ê"ñóœ(CÊù\œÃÄ­¸5~͉º\=ŠªÕ©½GqÚŽ…-×#€º¸— žº^%•G׳nµZ Ó¨²üðNe@.—#¥R‰¤8¢·®Æ`æµp„¯†Í&Ñ4öÜL«ý*„v¤K1úºÉ,h§4’«Ù0Y˜€\.«õ1ªÑãÐûÜ¢1sò̹Äëk¯Ýíƒ;Á ïïçÇÆ=$×#Ë€ÒØw¹±Õ,¾ îÅ#{ï } ÷íÙ²€ˆ§´ÛÓÉ2€Ðu$’…Ñùð£ÏÀûßqwÌ`¯ßͶýÐ8ñ`ðYO%žß‰)ýìÙ=L³-Фü«Lþáh?‘Ñ~%ÅßZ{Ȧë€^vЩ­f5Ðü÷óÛºDÿk‹çáä7ªçŸ € A6ÄõÑ„¬T‚=K‰¬Êè*,߇Faêú‹ œ°|㟅2oÕ*ÒãE9Uu€ŠX¶ò¤üƒŒ¬[;—ïÞÛ¥Z­Âˇ_çû`"¥# T=œTòøüˆ?ŽÑž¨+öì€L&^:W›?nu¾å "­’Õ @5žœW–¸O‘`Ñ&dÖìÄ”ÖVÉœ‘éÇ$¼_Û:-; êðv]×f˜$‡©þ‚›LfPñ<©`’z3àß8̼àŸ€ì`—Ëå<›`ȸٕ‘4ê…n)u­«‰àhfzÇãÖ/¥O­!÷DÎ~ÓC`LÁmúv¤Ë¶ã'Ï$^3gÃ^öZü{µ`dIBâ?a${’Ð+ÌŸŠ+QÛöy¬.}l¡Å­¶Œ•j Nž9 —34*ÙË޵ûÊGÃcãiý9ðhžáùœïRû€Êêö£A1±è¶Ûn9tÝ N~, °4Õqî:;†ñ½o~õÀì‹îƒ™2/ QÀIEß#ÃÂÁ!ÈÌ[ÿ<òogØœ’Ý™mÁÕCß~Jå³ÏÑ!ï_1u1¨ÈMŽe?BŽŒâ”¯“ë×®…+/ßÛ³ÑlÂãßy8äWP²L·×c„ ãÞƒñ{ÒkEÅø±3œ<7¿<¾™ËÀkVƒHš¾ÒÊ«$" €p *þŠeµ"L!´×$3Ö6¬Y çg/¬©W×5Ýz:„¹„‹C\ÇGž|®½ê X»f&´OnÿýPyþ/ ~üh02>G-ÿ €0ðoåE@´}ŸLèggnºèvü×_´°t®IV¨$µÒSÿ;Yg=~êÑÏÀ™þïø—iój}2hERI2•Phñà §;xbÍ€gxyMæà³Áģǯ}.^šiç–Œ1ºTøˆ6¥òž¡0¦¼ÔÊaàØa¯×®™DP·ŸJe˜Ÿ[P"ý¢ã%šO¬,Üó èØÈ2°„Óë×ÂÌÔdl¿fis/‡€¿tbÉl²Täq6îóŵ€™¯vì©-ÆØÀéÔ `­Ñ®Xš¬lV½^Ïg³Y[”[â32RÀ¿é`FRô¨$”“.#:XÝùvuA½’õ%þ^R?»­OZmà_J!ŸBQE;ê"=YçÎþáY ó;lH¸aÝšð>Ø2fê*h. álI&§[‰|"cñîüQp/¾Ö–kBŸ^ÃÀòÕûvÃ7¿”N¨Á ÕL>ByLè¶Â×y ~àƒï‰9Hfì×BýÄĵö €L°ÕdB°ùgE‹¿¡Ÿýøa+Ð]÷µõz “›€ØË_tÔ Ý-Û˜–r¼ƒ28}^.¾ôM8þåߨ]¢’‘Ä“ç)Y$D‘Q×ý‹'¶àÅ´y“ ­iî„#V–«˜ÜUïMÌžùÒW¿Éu³üF*¼DÔñX’U~¤¥$-w㌳‡?Ï1à¿ïž88n6áÈkG•Láà >2¶ˆ‚?F>F*’.ü솬í@&ë°ûÑâǼvf&&â×ЭÁ­^ô@Ð"”Qv™0ÊÌúA¥pð{“ݧ„N°Ç [26°÷cå/““ Í1ÝúSG19aÙwa’ÿ!W™'ZârðŽfFÃ8ŒÀHW:TTÔj5œ\Ȩ™™ŸŸŸ”׊”1|”à\¦brù¾ÚJ0©¾GÝ' ÀËv„шXÔY쩦õ_£2ʱ®1—Ë@µZ¹|ø¨›¼„Å~@«!­ë|êÌÙ˜€xM°7_®tŒƒˆ2uôîÅ'Á›eFfİiÃ:Ø»s;|óñ§[î­R7Ú¢6  ý»ÔÏVÀkú¡aëæ ±(‚‹úÑæžñ£ùÌ #4ž=ÁË€z`e|€HñGœ»1‘rë[ÀÞsŸŸâÏ?gõp˜ÞHçiT‡I'{&K=ØmÀ¸:9;µ‘Ru?côyY8þ"ýòoBíâ³-²¼¨€¶ê꣢ wQŽ£©ÄšòÁ¿ãN7œköæÛÁJàÌh4šðäs/òþ} ø·DfŽÈä}—hEìÿ øgll‡m˜ ËÂý{ãk»ïž}ñå@‘à_::¬‘CéåÎ v p\|Œìf´„>œ™žLXŽ\h,ž ¥ØKð/3Z‚:Úû„s‚›xl}à÷)/ck¶€dÐéOÿFí|Õ®YrRßÇÏË÷Ó€~RÛ¿¨®Æ(?Ã)È‘¯Ñ!‡>¿!¥LügÀ¿qé S¹° •+—Ë”Êù©à½Û¾ KIåï‡ø©—~¥«MÖ¯[ÛŠÂùõêHYžÈ ~d™f™˜'NëDš)8¯†Ú°rªƒsß"ädù^Üùc‰÷õ˜IÌaÂÀµ$£ÒƯÉhYÐÁg¾øµ‡á¶›¯‡õ€³ýF°7„æü3l•Ÿð:Lý'¿ÀÊ)ÿAg€éÝ`íº获0ÃorÉ O^¼ÌÿikѨÇR>w Ž|þ× øúƒ¡zy ü壌 „©<´ ý¥®šcuT“<•°äç3åðï™k? $7ûžo|ëQ8}æ|k uñ-ðo‰Òt£+ËYdà³âÛ Õb=<‚‹dG§{¼æª}±½1þ[?Õ¢Gbþ‘p”gŒþŠRÖ2l\¸qç†eó{tÝÌ l`¶@ÌÁQ™ƒú…#z’[f´ÈT{2ÂûVš+~'C¼¯òþ:ÁËÄþ¡°½'ǯ‘Þtm§ˆ½jó§ñD1Dš@ù}¬ûÏ2üï èo4ü g€ úuhVaÄ84Æ!žLYs#ºkä&¥ S=j;ãµÛ¿ujç×­’'B¿Qëö­›yJ}™)àt„iäa"@qްôŸ´ÚÝ‹Þ &N2›®†RBz­~”òd·.‚ßÔú—àâapÎÚÚë²-›aß®ðêÑã¼ z‰+PÏ®9z޽yÖÎLÅ2uò×=¥óOƒW=Í ;t`Ò£Yv°%Å™d dÍ7€³÷@¦· ÔàÑE—$µKZnÝÀ¿Nº·:{^ýë_…ÅÃ㳋H?üÛqð/ë¨ÃãÖÀd% ºË 🎀\«­æÄ6°7HW_ÿÖ£pöüÅÖ—Åâ~ ºŸŠ.HéF‹h88öÉÃl¾!ɪcù\¨‹öíÝÅ®_ÜtEÇó™sA²ë“Pº¿té…¾ ÜÁáGÿ-Ìn`ãµY:Fÿ9©lTOWKP=ûb0$^ÿá:{‹´ZZŽhxþosOU6X|.‡_:½ËÏþ22}ÍЂývŽ€vk¬ün5s êP~—T«ÕL>ŸÏ"~­‰,p'JÍ0Îã0"e~~ž×Ìà$FN|©TÊ·š,­æ´Û”ÚA8’”‡‘°á»eóÆHú¿îÇg&ÀK;7_L´ ¬é-Aí?Z`*å¡§ê• –ÈšMiˆ{¼Å³ÀÎ[yÀ«Gßäƒác!­Ñx¾GO‹œbÕ™£:< 8ØñþKÿû.ßÍ[T†|9{ïúÔ•ì<Ì3ÐïGÿ=¶‘‰-@×íºýV Ûn ‹¿šú8j=²Zô˜‘üžÁÿüYxù/ æ^úË0ÙŸÕJýwðo«õÿ:ÿÀDFž ¢©Sþ­,¯£öüfª½î£@ ëbßóüK¯À‘7Ž)MFÿ% )ã|ò>ãYìB!oXlhË3ƒÓÿ†k÷'îÿä3ÏµŽŸø*Û’¥ :Q¤þ£“ÃÂG±ÉÒ€ŒãÀŽË¶$èì&”O|=Œ¢dÚôq”7-÷@8~VýÞ»Ô `ÉÄ& YS0HÝœÊ{^WÛýNR†€ê„¨T*™5kÖ8 ·H"K°2;`f@’Š3bF ý—Þ5œcÍf³€%øZôÚìjâ¦dË Æ{%\ŠYÕ^(úqÇwgËe8wþlܰ>ô'j;`¯»ÜKOs^=íkÙPéÇ蓺~^ÀÂKà•/ÆvÍçrœ½¹¨U=8:9Zë²>û\š›9P2û¿*EM°6ÝÖ¶[ÁÚy;ÐéˆÆ@µ”öEÃ2ºúuD BÿéªCGí0ÀÈ¿þÿ<þðg¬V&€¥’”)àŸ(º`¤§8ðDPßT£> âY6XK-Ûkrà˜kË ‰‘ÕzøÛpøã ¶„x–Ÿ×DèH#ÿüc”A;å ˜øàXíh÷\{àÊÄy÷ÈÏB“ ‚N×Pž¿ךŒQ¶$$ ºÈͯÂÊçs2m:$HèÕZŽn9òPjÈcEƒ®/~¾…xÿÍNɯ1Fß`ðÂÀ÷WßS³Ü’Êä#}ú&y úÙk*è€ø­šlx´æ b Ópˆbšpj £‚9@ÄœPèeƒ ËŠve8EQoa'GA7ïÅá`À²Ô™ñ²gç¶p›< ÕŸ âÖ"áoþë¹¹"œ¿0wú°…ßÞpmllJé¹F-½Öh¹·"œç]<^£Ûk»~Ö¬‰œ/OÛ·žzòiËÝ$øk=Š$¤ €wCîú‡Ü=ÿdßñÀ9øa kv ü§e,J‡,¥¾},¢Ö—¥ž—AØzå oÂKþ³ø·Ä–QÀ?Ï ­”iJýËÿöÞNŽëÊ¿¯ i@#iD#I#f°%Ë2ÉSÀqâÄÙlv¿ì·Ùìow»ûm6°ÙÈ:qÀ1%ŽbÙ–I2‰,É’e‹™™i ê_ª^U½êîáj©žÝ𙿂÷êžsÏ=7W'€î8ƒ!YU*÷0Ÿ …PÄx¿E&þ§$ ÓgÎÂö{œ‘…Ê ±ÕKÎ:x‰~,p¬P—|œ+{õ€Šežó ·/Ýµï ¤3YY(Ýí"zg×Aìy0¸Ê{L šŽo³^¯d•"ò9ô+Ù$HlHÇŠ° £×趬Ÿ~­:Ú ‡`·*ǰ Ç-˜›_þ„ç5cÀÈf®544”¥Óéx®^ËùL} <¤u¤ô¿ØF÷ 8 hV?ú™¦? †àž8§Ï×zÎG¥¬¨½jÒÿ¬\¢Ã´öÕ*s`)d“§<Ï7ºÆ àú{7ÈîFbÀº }~q7oÁ"hjñ–¥à–€Ñ)Ÿ½zމK¢]2ƒR÷èðQi]d!ëFG ]±/ºZÖÞÏ«?²¶?õu¨ÛþW[òÏÁ¿–üs‹• €`úS¥Y~VCM³ÿìoÖ?“ê KÈœt7-ƒµ›·ƒXoŸƒ*ƒÆ( Ë‚s÷~K Ì™i“ÆJÒ¦­Û1ñb䃾»ýÛà_”˜ñ2Hî™Òtt³ãmdÍÕ»ÿ*öOrÓ¬sÔ"²v¶e••ä‹…Åkèdþ­™'p¶{–áS(Ñh·Ìe‚þ‡þÙ¤Blbª---‰t:ñúmuiösÿlíBÒÞ Ò-=ºØ†ªjPÕ¿5°èØd©NR™ ÔÕ×{Î%¼ª¥•R@#HÁ·!ü‚%p– À~,sä 3"«ó¼»7÷%eòº‚´¡þ*¿7·´Àâ÷VAš1úAxº“XýÑzøÏïÿî¾÷€ës);0o/aÓ•ãÌŽ•°õñ¯@Ýî—r‚ü·ø—à±nÿ4‹ŠˆÒI«?sK¨™š¹–ˆGÆý ¨X}ãuõ °nã6§ÔˆÁ¾½á¼.º¹.Þº òê”ïlmvÉäñÄÐ3/×n€ú†kÂÙ*wV¤Ëz0Xß9ö¿5Õƒ%¸Z…Æï{“»]e Vä“ýÇ#V)mSŽÎ!d·\äûµîuÝ] àú×­è&Ø×8AŽñK$‘eýeÜUháv¹h2p¨êµµµåæ}„à]Än|Rºëã ‘í¢Ý/Öªq6Ðï5"[èþÞîï”l_Wã‰x úöé O‡;ö‘}ÜÄÅäÄé3ÐlÊè6©‰>€¢}­çeÁi4ÂÃ9²V¯¿³¾ùÝÃٳ窉J€ŽÚ†®Ù¹²B²u»;ɉckæÃö'¾ MG—Y5ÿÒÌ¿üÈMÿ‚Á± –AÅ-6£$#ªnÉÿé‚ZVáDË<¯~gÉrXùázŠ@@ Tö_¬ç _áF€lzbwü~}=]IðX¿q446{À1²2Ò(0õÿ¼´“ˆ·^d#‰@?I#›†æã«-–Ú»¹›@qƒ’~‹HO€Òc`åwòµRàeש|ד\×_û 8DknnN`€û–1Ÿ2‹Å° âÿ²#!pqކ†éäÀlšy‹´´´Ä1Ë&2â­mÝ×rËbîáÝ$AköMyi©¤O^ðtò¢€Xe÷í?xÎÕz3äZ¯Á öœ(ß—EB}ð +½)oǃé“'@ŸÞ=¤ÛWLä_†j`Á»ïyd﹈ë›;xéêÑØØ?óüð—€dc£u|´i;|ùß…u›¶@¦‹ÝA†tæ~o϶dÓ)Ø=ÿØþäg S»Ýaø'ÿšü+Mx2Á?wR§†Qšý7ïÇnêø¨DÆßjïÏÛœ9wV|°šš[ÀJ¬)È¡ðAâb€E•dü™ä“š¢8²ÿ³¦O‚XD÷¼w98u†·8çO”»†Ðí[Ž9̺†:Ö CA¤Øòˆµ‚qÞº -%€õ½U©Z%í¿ÛwƵC±ç©bƸ €ÎjþÉø .À$îlþíÃ÷&Q˜ ŸhD±€y—Vˆ<ǽ@êК€-sx¡ƒÿÎÔcÑ(ô,/óÁûÙÀÁG¿²Í;öAC}ÒKô jù'â7œ›…¼ä ¹gfïs©&ÏKûVö‚Aúc-mt(ld1”¸UÏÍ »Pà·¶t5 pÖR÷=ð(<ð—çÀ`ÖÛ 1&SÍà]5V-|í;?…y¯,„Úú† ®t©5¾;T ÇwÆ>‡ßù¨FÚÊøë ø“› û÷ÿ"  ÖÀõùÜù?JK°q²³ÿ¨dhCf™Å<¯^ºb5,[½Ö6Ý­ÿðPº}©qfƉ€ÂrÇÇcòøÑHx[‹®Y·;a£²]JðêÿÁ6ë³¥ÿ´óÿj£G “­Éƒ™s‘^ÿÿÀž¿²µß2€½Â@¿æQ¾¿;Ë{Æòx2ö(‹D"üÂÀar 4ý €p2ÏÀÕ õõõ¸ šÏ´¯P§þÖ<–ÏÐ|Èõa7ï(//…ÁúBNèßÍ Cæ Þ²é\]4RÊú:0¶asC¼¬cvi©ùöÈG™´çU—N›½ÊËÙ;ˆ G¶—"€úÆ&˜¿àÝ•m!ÛãvÜ–qÄ?½÷~xÙÜnƒ'…eQ¦¿åùÅßýéøÙo„=ûGò®òº*˜Ì÷œ®$=2éf"ùßøàg¡vÇ‹øëB†ÿ(#4M^ó/>ÁÌþ3ã?þ#vÛ?D q&Ø0בȸ;Aí3Úó6ÇOœ‚å«>„L* ®E Ü­ÿ‚`€:þsdËÿñÙ…}Uzõì!}íŽÝû ¥9ÅvŸM ¢ƒo$Y1cC¼ˆ¡züuk†!­=ÀUm>åÛö/0õÿ9·ßÜ0ÈëlBÍ´‡¨õ{_ñ> úÓé4öˆðû± ø1Ö ÐOޏ¸ ¤!nÏ2ü»9yâ ¥ŠP—oBæùœ¼_{[vgÛÝ‹p[6‘«¨(·±AzÉý¼uGÓíÙwHš5Žô(1ÌAȈ€àçRHm}ÞüÎ^ಓ!‘ˆyˆð(¢RdÍm|áµwàëîÐë]Á÷»‘Î\‡wíÙß¿û^’E5X N¤jè+ÌŠ[¡òêe«ÖÁ¿}ûǰ।¥çŲîµ7 ,t$O€Ï}¶?ýHŸ^ç÷ëª-û'àßÇíß þƒçú/1þS¨ôß òZw«ô™ꀩҷZ²b,Yù¡}l0ÈT}²ÿA0ÿCÈ2ƳÌÿTÅ‘ý¿têxZçXþèÈ1Ñ":dõÿ–âA¡7üw<…eeÒyÑrî ©ÿ—¯ŠËË¢ûOe¯ôŸ^ï² öeꆣCÖÜ\m÷ùZyçÀ(•JÅâñxÄ„)*»OáCb(®~!áÀ“ ¶¶VáŠëgÌ?ãÉd2.tðìíQäZÚôù©.F—ÿBFyY)”&>È$ø%ÑTzÛî½¶µ0ôÅ*­`& v^Ü0ÖÀ‹‹W¬ì‘×Áhñ–:Äc1¨:˜´ˆ†â2tu’Í)øËsó;& .òçu•®¿Ý°y+|ûG¿€-;öÚY:bÚ¥ÒìbˆRlÏj¬ë“ÍðÓß>÷üæAضc7‘ó†à¿}ƒdý?œîÿœXõkP2I‡ÙŸ.fþ2@Í#ûøç3ÔýǘñW0ç@$û¯¼ Ô^C=oµïÀ!xïýéÆ!+'Øì¿Œ&ÿgàŸ‚cjTˆ7¥fØ©üÓ–í°m×>pºý£@Êÿù+Š“àshò¸Ñæ9ë=.‡7™×˜:kñeGaœd !…Õ ‡Þ¯ôlâ»È×ÞBÛˮùJˆ ù|<_›››± Ê=Øý¸ö@9H€p„@8§el®I§Ó% Æ T™k2·À·÷±‹Eòß^ RVVÂúάDÞi”Ëg6×qGÔ@vÈqàŠÝ€eo¨ÐÀõeË\ öÅiL­Avï{Ò æ†k.ƒ’XŒl£³@ÑD€åB:›†e﯅#Gw @t“…½~à¶~Ëßÿ¾e‚ÿÃGO2Y2Ξb9²Fêþ1¢Dš]§lÕ+« hq¼½d|÷žûàñç^‚¦ææ8¨ wÇ´~íÈ×ÕîßÛÿì|æï uz§Ö?ªSào:7ýcÀ(ø.¤_F¥Žÿ 7þ£õÿÄ+ØÊ£»ôA—Hßâý×™· Âñ föß±ÆîL!äÿ–ù»ò :(ã<ןlö:ì!T¡¾>Pò^–€i Væ Î…1#†âØKl€Tíë|EÈÅ=B@¼­Ž ŠebIïS­V‡jÏáØ>Œô;qíΗ¼“][ ]ýLÇÙûaϲˆ¦iQÕf²pþ'1 -Iç3ËôóB‡WÌDccc”,¯ŠÒjßÙ€¼#…pÐ1¸ªô²Œ Í8ïôâ‚àȱ“RWc­r¬˜F­MÁØ:Ë ]š2`B ©-OJ£¯©ÆA,qlVM€x pý¦y<šáá§æµ‹ ósõÏEdvöºÑÜܯ¿µþçžß@íù$ýL¼Î0¢ÒlD®KÍÿèc*»Q/"Ô rOž> <õ"üËýÞZ¼ ZR© fj+ÉYèql:sv½ô#Øøà§àôú‡RçõþÜä/*Jþ…Vœ@Ák/w¹c:©óR€KúO2ù‘rІÏ”ðâÍ[wÂko.¦œªÅz3û/ÊâI½¿Ìü휓ÆÂÀý<ï±~Ó6X·y‡p^)ù? ˆüß>Ôt­2Ź®Uõï :f±ÜsáÄH'{À~ ëÿ­óXeçbÿÔJéÑ0ÀëÄu7×µRv½ïÏîó~&¾¥R) LµŒkÿ¹é‹ÅPŽUP „£øg“ A²Z___†ëkÄIëî‡+«rÿ-fÓøï"¡ Jn[è¹Kü‚{ÌÚóþ¡²EHüìnPºií©4¢1Ý…€öpX'¨£ûoã¶Ò—E‡Ì!—û ì[`6—³Yú¤Á*\0ÿΞٙ󇽋«9¯æÎ¹Ô'Šã8r* X–#یĀŒ¹½·î‚µ6wJp#ËHtözÐLÂÓ/¼ ?½ïAhiÉйkÈþ©á#“$+vZY±¢pJâýËiŠŽ8a˜÷mß}~ö»Gáß¿w7¬úp¤Ó™¢X‡ ]ó;²D#ÝX{Þë~w+Yü¿mØG³þ.Àoý®:ÿyæß ’¼û5Sñ•Hÿþ–ô“¤øw–×GßÚ`oö¿¥¥¯x¶ïÚG@¾•ýJX¸~<0Ùðšÿáì? tñ( X%ÛwíÝû‚WþœUݼ­b™ÍÞ òöýªúUBI‰·ô/ÛT©ó‡œû ìR%Pç±_ @…¨WÔ>Se­>Á®ÿ§$²€šÜ­‡ô楯ºiîž}T4Ãs ¨¹ãù†xð‰yÐÜÒÒ©Ä›Ì礣Çé³gáw==þ<¹’À\UìzžùWU«õŸB@%°ÜX%ÎÞ´3b䀯(6H! ƒu›vÂÿûÿ~ÿò­" ¹¹¥€C`tÉz×ÚýÜѰ-õg`ƒÕ÷Ì‚¯RgÖ›»Ï°êùu Jþy½¿êÊüE)uýšwG˜é °\zÍõÒ·X¹f-<ñü«,ûÏþuâHµ:Wt; €/wû×TVÀ@ˆÂŠ&Cð¼ÇÑã'Ìù´UÅB›C«9 L€k·bTò>'ÆÔTC¯ o—ƒÆcÛ¡ùäVë\VÅÃ*¼·‹[€Øy‡Èùm[¬ú‹$Â(¿“ÖoYÝ¡~`…Öþà㥚÷Gu&eá‰J,ÿDZXLÉüCðï`A¯ƒ0'^___Ž''ÑðÏö‡…‚ñ¶òB; „wçÀLjÿ^½„…6k£ã`ñÖ9Kü)cŒ• Xö³Å'NŸ%RhÏñõ­ï¥Ô$Èpm^Êå / Àoˆým¤ë ½ëyé—®ìÝ fNﺤÙe,¬ë1r\4>vþòÌKr.ÉÖ‚ÎÂûö„{µ7dà_QœY\ÿ¯ª ȳ„à· rU^ßk>'ke^¦‚UHòË0lÞ¶ ¾ùýŸÃ?ÿç÷IiÀé3ç “ÉÍ:X¨)T¾‘ii‚äñ=°ûåŸÀêM„¯} 2ç·°ï¨õWi­¿ü“Ç™ü_”üóÀ&ÐW‡ô_cµÿQZ…¥ÿ&ø§Ò& V€>ú6Pʽ`øàá#ðÒkoCÊ<‡ìì¿ÐfŽ—Ó1S<ÖúOÈþóµ`äˆjÒ×=p—ŽEË?G"Q(u°ƒ±Žzº0E'Z‡Tõ‡²ÒÏK›Oí¦c+¢Ã6DJ€<,,é¿b‘Vþªù“>¦V - ƒüN$Ä¿ùù• äç[Ï[cÔËæ¬–ÍfcŒ õÿì'î b?3¬ýÐ0$.ÞÑÐÐ fý‘+(ŽàæOµÐ€¸=™ûŽì& .2Y’Ÿt´«\¿ƒp9Ì ò ÒnYá;â 7ÍþÀÿÃ=Ñ7lÞîÝ‚l¢#oêééÛÈZ¨wûÖz$˜¤c?ù«é¤¶½. ro»áj¶¶¡£ã­‹ÊΦT i5¶uÇîN `Äu®3”¶lƒüâw°âƒõVm®ü«¼ó$ÕÈJ1#ûø³¨\e Kµ|U05°÷ÁÏÛ¶çüïÏÿÿðïÀϾ;ví1¯É¢ìRȵ€ËLškOÀ™­K`û“ÿý|*Yô=0šŽYÙ|±®„âÿ˜àÀßáòï’üç3û Œd𤑬?ý1ó>öÿéÿ˜Ï‚6t–wý5ÌÅï­"í*ímCôüE¶‰íY‚pž0PŒ¿Êæoý‡¿·a>mÔˆ¡0«á\—;à¹bÍËì9··›¶»ýïr€„Ú¼=+Ê¡²²·ä™†–ÓûlðlÐïvS & €è’·¢ŒU„~ͯBÝû;âº*–‚aó¿T*…àR«œ™àR„­ÿB ®ÐÀ ̉ÄO´±±±Ìœ`göd­™ìù‡Ö¶ëkQH±±]ªMpü™'NŸ‡Ÿ|þþkß»}?¼³d9ì?hžOétç”v*³ZCVÔí_ ‡Þùl½ÿ6Øþð\8¿ñÏ fëmc?ðçîþüÚüiB‹¿B$ÿV ø·ZþÅ©ë¿Ê]ÿ¹ƒºÚ ¹ ºQúv®Û?ñWÌ!‹`²çˆC€äðŒµo,ûO”È"KGV‚Áùÿ¾‡aáâåöôs¨ xÛ+ø8äÿìñ¡ƒúCUÿ>^¢ãÌAHú<‡@x)A€O>õÿTŒ¡ZGAé5Vj\ŽÎ‰]eõû²ç²~çSˆŸ3ýétÚÄù1){¿áV€Ü¼Ùÿ 膡…» X Š3fB ŒÍ£¸ "Ñ©Mi¯c¶L6ä÷Ú êÅïÙQÒÑ ø‹ ±zP•$Z5Ø™%Á£Øë¹[ÎWäãÀÅà¹m–£'NyÏW|±Hô¥t õ۬Ͱ,ò àwó&ú“²ïT¿²g÷ƒÒÓÙŸ;¢kðùOÜ÷>ü¤–2SÁ s°I½ø‘Ú{ð<=ï5øüí·tÈ\“­kÕ—¥,|g üî‘ÇáܹzGM®Ó韛¦±Ì?ø€×~B|ýæÁo–c2!HIÅPX·‹,y"Þ6•°J*ÙÍ‹V¬!·þ}+á²iaÂø10rX5 4¢‘H‡_oZ»OÛ£Æ8òêw aÿ›äw]"?ÖªMÚ÷‰ŸÿΛHñûmX,µþ®?–ùÇÌCo:Éü㺗àK=>OJ6ú<õò¼Ý±'`Þ« ¡¡©™tŠØöÏ6þ vö×ÿ«´ÔF¡~xÃûõéMˆSÙÀ­ÿösâÄëëëK)6¤ÙÑI¿@;—4¨-r¡®è}!‚û‚IóøVõ¯„£ÇOûÎ  |édSlÙ±Æá:8:DG š>¼ÛΰQ ϱ1Ÿ¬Ùó[!µñyˆ^ùÎEVÓHK@£DƒfëŠla2˜ À@´ƒb؆æFxcÑr˜6iœ°kW#û½µJ$¿[ðÍ{e<øØ³ÐÜœz¤+V[?üÛ.ÿ‘,F~‹Ø •Ù™eqlN"€›|áM=vò¼¸p¹õ¯¬„q£‡ÃèÃHMô(s?÷ª¨èòµ±Ðç»Iþû€¹ÿ‡_Üéú=Ö\ç»V4ð3ûºÂ²žŠ3ûiaÉV,¨; EG¾Ke5ÿ þ‰ù_œ” 3þÃç„yÎDÆÜÚ éÞõÖÒ÷H;BIvažðy€P€Úÿ‰Ò«Çþ›\7¢åR+ï¶Ç¤5i 3é^ƒ €h"‘À€ÂýÌXöPò­ŽáQ €‹vÔÖÖ*¸çµ3qÜ@Tàçt”vìÔ)r{wùj(‹ÇH¿ðýúAµ ’j† …æ­ª?iñî&RÅ×õwœY2š’{øó3óàËwÝѪ¹éWïßQänó÷ð_ž…ù ¹Ó?ë®jð'Ù~—Ó?c_ÛGšÐEÜA Âö ðΪATØ “—ø¨ Ò÷Âd@]clß{¶ïÙÆJ€D4 ½zô€Ò²èaÞèÆ 2E_èݳ‚t¡À&cX…Òžu2WÉYkÖàSï„ì¹m:bw À¾ªØdW´ø†-´@’F¿Ajþ¹é_”fÿ‘N,>;Ô~—@dÒ稀k`×ÿ}’Lú¯²rÒ¾2€Òq7øeÿU…‚|ŽR3§Oñ¼¾¶®½÷¾ýnb!|€Ìñðí´åÿ<ÎÁÛZ^VJ=ϵ´¥Îo~̎ׄ¹`!§îîàølgý?Ö¸A=c”DP{ׄA~'k2õœ;Vç÷ãswœq?toÊf¥ç1o9îç+Àc|…vñ@MMMózÅ¿cÃN¢ÿ7O|ü7n^ù¿¬  ݸ(ç´#™“«ËjødäVâE D˺Bês;,£ûug Ý Øûõ©°zÇ£Àâ Ÿ{LÉ%òÇNœ†»öÁ¨šjçÉ]Ò ¢£> -ëem¦|®Ã–0ƒ]½@Ÿ–ã>°"?knœsyÅuž7_s9¼ÁHN›¼ëuUŽ+3²¶[!A;‚¦L ¼»ü;²f_2¥CÖƒöŽýÃïz –°rµù£A«Óé¿ÃŽƒ@²•.ú¼,É„fy›È,W` ì·F3À:|ò$À‰ôôY¿tU…HDÍÜNœaÕ4:­ë:ô(/…²’Rò8~>.+ø?_¸£Ó3î„úµAæø[þ/šàr.¶õ+ÚôüëDöO2þ¸öƒ{-FT¸å>÷ÈiR>ô‰Ÿ¥Ç@ÏÛa5Ëëo-†Õë·pØÅÞ^Ò/ Òwö_A‚ì_U „D,ãF3ÏYo‰ã™3çà™—ÞÈ ±Õa°Ìÿ,ù?';²ºðm-MÄÍùW-¹žf¡éänóZrÞR¿p^CuµÿC:Él‡„ûôÐúOƒº.&ŸüÖ`w/×ýîD–Ÿ¢@|]&“1/;jL¥™J…¸€µlUû?VB‚ƒ¸°ç.k›áð0'“ÚØØXÚÜÜZЮ:¿EÃO6$‚óö~^.7#Ù‘Û”Ÿµ#‘ˆÁðÁU°×‘=7,f¸ú³aHmíÌ/¹çÀص@ñ ˆ¹ š×³ž%CE" F€à-à§pöÄjHí| ô179žß£¼ ®™s)¼Ž¬­ì¿l¢Ã f­yŠAt†ôµçøõÄésðøóó¡zШЯà9" 2Ú»ælܲî½ÿOÔéUãïÿÜ‘?§ÓGœ ÆžD!TU€ÁÚMU€¨¸`doDIY6JdI÷€I¼m*LNIæ–t§¬c~¯QLÀ[:öãPwn Ô«æ?]<ÝÔqÿn[;\Žÿ¤æŸÈý©Ù-ˆ‘ºDdÓ¸bôñŸmàT)1¶låðГó€ÁÚòrnü,鿸+8V5 ú5VÀ³ÿøô4 /\9k†çõÉÆ&xgÙ pÐAA7ÿ³JñÈU™l½/+-iÇysº j·8[Ê" œë… EvÂ>¸ýŠ•‡~'Æ™²[¾µ˜gîs­Ýùº ˆ `~ÀùP”›þ±Òe…“±X ’ɤÌPF„À¿Sgn8=·1Ic‚ÿ’T*ñç] L }ÏÖ´¼åö,ß5¼ÚÂýYÇZ˜µÑq†‘5¤«„nË–<çCëü@Z3–,_?üùos·ùSÕ®ÿRp w mˆƒhÅjáÆÍ UZâ…Á’J= ·¤²oZïÍ2ªH%ÏQ…}\eQ¨h*ɲ’žëyÖ¶^gü^W>íNÐ{M´>wõgæmJfýþY»?êúe$ÏüSðUSú„/BdÔ Ò·ühÃføñ½Xà_Ul#K[ú%ý·Îs–©—ÕþsõX‰ ¦M ñxÌó>µuµðØóóÇ•fÿEó?ó?ûpÊÿ‘«$³²Wè×·Ò;o2i¨]ÿˆµªàƒázê¢`¢! µïä0’ï&R ÐxÔÏ/ W AÙkñüÎf³š¹öÇt]Wyí?¹Ì1€èÏàBöŸ]7‘žL&Ë0£ÆŸ'“êäkÒš¬º›õó[Z{QÌe8’żGÏåпoo3 `±Q4'´€ÒŸÛví‡}Žx €Þ# 2ô:Î2%€àMvgíÅÀ,sd ¤v½ëyMEyËpIÈœ:…!*•ɵ‘%ïÅÿoغîÿóÓ9×·²(W-ckÆ‹¯.€Ÿÿî!8|줣Í ÿŠj·ùëjðïC(` #ÖMB …)?B€©øMeÏW4 TrSIy~~!Æa(­¼?D_ngâ„B|ªvËt’€¤&X»?þã¤ÝBÿFôÑŸÈDy9ƾ‡à7|››ÈÙ¢r<5¸®ÿ¢ôŸÔ¾óº\–¢Ú 2ßͧõ3¯us¯˜éyŸææxsÑrhldíï9ï.yAЬz°Ýÿ…ç$bQ˜9u¢ä¢’…ä Ó|Æ2ücܘeyànØíÀßah_yG ­jZàw>vhÓz]1ˆ1<ëR¦f2™h<×™ª™ûV€®Na€‡ú]õûzCCCœ?j>ã¾|YöBÁBÀw{‚ÂBƒûÖ€™áI·>0úVö‚ÒDÂò‹§pÑ4N̺Nj.éßsð(9~Ò»¿Ôh•#mÙ¿á Ü_æÎZÖï€ôþeÒ§\·]w…­ê`—¦ “H$€“ôÜϘÛðÞªµðòïø’‹2™a[ÛŒâ‘N§ááÇž…?üùi8{®ÎÙæO‘µù뤚ÿ¶"UÅ ä_F(`u.`m S1û4žQe?5šåG˜ÐD’@eûC…X4º®·ùš’«¾4ß(›zh½gÓsžO Ùg´qwv›ë¿ø7HÝ?ÿ”°Á¿J@Ÿ6ôzˆLù¼ô-OŸ9 ?û"lÙµ‡mÈ"†Á@×7¦Ù…•»þ³ì¿bÐÚÿ“ÇKÛc߃GžzÁy|UgöŸVõh»8 H>[½ ˜:yœPŸýèvþ81v Úÿ!ñÑ’V|†÷F÷]w¶¦Åw¡J»\ ;~í¯ßç›×câÀëÿéÝÔ0‹)3À¼^á €‹a“‘J¥¸l&Z[[[ЉYNY¦ßðçËÄçZPÚÛ Ð¯æß°‡žtTƒ«úúQ$‚‹²2çbƒ`ßÁCf@×èy j¿«­ì?þ n’»öé½/Cj÷bÏë° `Ö´IP´*Y èD?p¾! /¾ñ¬Ý°¥ 5¡=eMMð›?þ žœ7’ÉfG6\þ‰  ÆþÝk£âOˆÊ…´“c`,y?öHˆøU¤[%8ë‹EHgUU»Ei0ô¾S,…¿e‹çôoø§™ÿñ@0°Àÿ´% JÿK 2ýïͧ—zÞ2i®•/¿ñ6¼üæ"›p³¤ÿ(°®ÿŽì?ÿÍ9©õ «ÿdž—€Õn¥pýU—yÞûY,\ôËþƒýç;<`­ÿ,‚”  ]8.ÇTbß{ Í@ýîÄPÌò#áú4a$â|>[Dñ3¯éÓdF8:÷úÑm®Çó)ˆyÜÎ2þîç(Ø´<†/2ü²f‚ÖPÉf³~6!ø €‹o444Øë»0ë˜|&’L&KÌß•\u=­•Ìû‘¹…Bƒø|àßh+ PH{±bƆ^åúE†ƒƒå 9B-?=FËV¯ƒc'Oy €þãA5¸À1àa)€¿wÃnHú@úšQ#†Ã'o¸JPˆG´8TöUl½0ÿ?|ü<ôÄópàÐÏ<õóh-=söÜý«?ÀË CKsÆvúW5`h6 ª€æY*ÄhÚqu–H‘ª¸B€·WIE 4]%YÿˆyÓ5ZGž«-kgŽø¸[Aí1ŽÎï¬Wý“5 Xfƒ¼æßü :Á?àŸø½ÇCdÆÿ¥Ìkš‰Ûu-Y± þðè³Ö(‚¢¦k0fä0é;ûáÓN³Á¿Ð"SìЭõÿHB`²1@ËÐ<Öý'š§|$ î»àúáVóØšÿt«†eå¸îußx§1vçÎÿø1^¶#t@¬ qæƒæà–ü‡„@H\ìsÙéïÊ&O¼¥¥%´Ÿf›r—«üÚBÑ®ø¬îÚæ±£†C¯òr»åœË9žæ`EÌYûÑ‹ˆa0€Œ`í¦mp¾®Þóšèˆ« ÒoްmrðÄ2äó˜Ñ°Ò‡Vgg÷˜0v4ÜzÝ•B‡Ïâ!¬w8Ðg×oC V­Û÷ÿùiË!¿9œolÞº¾û£_ÁÚÛɹ¡"ÿ6(Hõþµ>H àÂc«ÿNLÿ4;°³®äÅÄm)éjë¶ÄF\æ¤÷L궪ºôÐz€NÁ?¹%¨ÑŸÂÌÿ4þM D–ÂÕ&øÿ'Pû•¾õ®½ûá{wÿR¸›—‚ãsXH,þƒV÷oEóÈnû§2×ÞöÌW\ûÂm7\ 2£––<úÌKæºÁLdsÄ»>HÙ›uºÿsUÎì“âé†3Ð|r9ù‘£t ¨EÒxK5‹Ô¢ê$Í"%•D?@Ѳ0²ï‚ÑVÓn¿uܯŒÀ¯- ûùØPPä&®ûÇÙÜ À|,¬û €pÈX°ÍQ:.ihhˆË&h®À¹5²ý\ÜÖvùþ[䃠·çÖQcâØQj4%Î!¿a¸¡v0.DYÃK°}Ázsé*RÛêÙߥ}@í3ÞrÿØ/€ˆµšøû·lø´ ÑáÒ) ÊÑ *[\ÛŒ\&¾BW2ß\ö¾§3@[çü²•«á»wÿví;Ⱦ‚ØæÏþ•`‚ÿŽ¿’#ÇdöU¶X&ƒ.bÈ;6pàØ@ÞmâXè( D^EI¾,ëþ‘m»â§Ë.#ŽÓ^À‚ÿ¨‰‹( @2ÿJ„‚sQùPüÔ*y{´ýÃwü+8qê4ûÕü ¯û¨éŸ_öŸ·þSlé?>Tƒôƒ™Ó'Ißë™_…ÓçÎ ë*² ùßAÊþ;Jx)ouˆëJaÀ•—ÍšoÖn^-õGS¤`àƒ[þÏ7ï×Î6OûÒ0¨ïà/‹[e±~kã]÷gÉÀ?¿OP󢿿fÍùØ«šö˜Ÿ`N?t±· € û×ÖÖ*®‰­²€¨lÈڴǤ5ÀÞ5ôûŽùœH;Šè¨u"HëMyY) ìß—Õ뺠b1 EÃp,4™Œ›·ï"OîyhSæmDb Ÿ ZÎ@ËŽ×!›ô–>L?n¸b–  Xj<üº «toÊü·–À>ã˜c­ޏ&zÞü7àû?û-?uÞrØF `ã)zUÛé.ðï{qgmÈ”ÂIÉBÖßÖ8Mçj¢'Ć]Ò-?Źž5 > »æ‹’Äêý‰ì?Nn$Û¯RÀààË£1øÇ™ÿKþÔS¥oÛý}ï'÷Zä–³îƒ_ÞÂÊôÏãì?ª5Oé¼×#Ü~ëuÒ÷8~â<÷² ˆ[Ò6>p `Õ’ÔfûÒƒgþ5Öí@a¦…£†>½zJçËùmoB¦áu.«¶Ý‡mÄö\þOæ²êÿ•~™ó;]H¼åõ¹ÖïÖ’ùpHSSSÄÿÄ w(A€¸@,ó —BcÀ¸èX<)–J¥ÔÆÆÆ²æææ˜X+ï7± ë­iØ9ŸŸ@GÿÎ^L»sÌ™9Õ ¢4f¬çí“W,ݸÔ‚E+‰ÄÓ=´AÓ@é1Ìá .ëÜ+±KÀîKm¾Ò‡×I_2Û<¶SÆŽb¶ø1‡€jýnàL+H›çÅËo.†Gž|¾Õs +FþË3pçM†f³I–_³ þøW8@¢åAÿAMhtÇ÷оôA·ÐÚ—  Ð5®Ë€¿Ìì€ÿ’ùW `‚-NÀ¿¢ê„Œ"à¿bDgøƒÿ‡ÃÏî{¶ìÜcÍ£¢2ýêÜseÿqÛ?¼.LŸ4†,}¯Gž|ÎÕÖ ÄÏþÛ­ÿ¨`p¶[4ÿS»Õ!Ùnvâ\9k”HÜÿëw¯€¦› ŸÿyžàµÿäÿHeD€¹Í±JPJû‡Á}Wœ{íPñæJ̹ãuë\ÎÑ€ûd2Íü×°ñ XÝÍl¨Øõ>~ €°4 $.ÖùŒìÃŒf“Éd"NGe5r¹ê}Zæó~¤C{¥î ø/T•ÞOS'Ž…t&ãpÖ·Æ™¢ˆ «nAÄî=q’z¸FdÄÇEXîàÁWø]¹Ä«[ó¦§ sîç5C€«gOgПÔ¬”è)€û˜›À•æÿÍ©4¼´p1<þÜËŸû§NŸ…_þþxbÞk4«ƒÍþ&ø'nâÈÙæX›?¸˜Á¬” ý^m¶ª÷®†èÐË=óšLÿlÀ&¶üsÉŒÜ ¬Àµÿ¸@ÑíÌï±ùo&øŸ"}ûÃGÃ}<«×m²Á?¢ŸEºX ~nÏôÏ}p <¯ý׸ùŸb‘Ü~óµÒ÷Úºc¼·j-¤AìÎþSó?Å2ÐÚ'šÿ©Šý§qîŠ6|È``˜È1êv,…æ“k8¬vWôXF€A‚V+KAþoTÝÛÿ)=ªÂˆ> ×˜ö”öæ+óe¦ßšy‹`À/¤ öˆÅb x­-ÏŠޏ°SˆÁþEoll,ÅÉ=ÙdÒÙ|L ˆïÌà4—Œ(,ë)|–&J`Tõ`òõqÎ êv€Ãa€¶pÑ ÒïØ=¢co1#ľv³CI'€ m*òƃÀâwKÙÿ<¤¬—¾þªÙ—Àµ³gØ¥\’£0@0d^a|²±^xýmRã›h8t¾w÷½ðβÕ&˜ÐÍ Zc-þTŒH H¥ø* J/FÙ±±š¹ ÷¿2† ˺“ôó«÷'mþ˜Ã¿Ðâ,ó5ƒ]ü+}&CtÖ×| ÿŽ8 ¿yàQX¼ò›<#HP%² üCpMÿ¬õü©èWy<Ëþã.à¿á*(+-‘¾Ï#?§ÏµÿŽì¿êÌþ¨õŸÊÈIMugÿéy{¹Æ÷ä-}…‘ª; G79®*“ý«Arÿ 0—üù?!ùËRÚ7\$»ã|lE‚®µ>bùpƒ€OÔL&‹ó*;±ÉZ*¸#€ß Å¸èæ­ìf­©©©³i2CŽ"ºeQòë Þ‘Á¨aÎ b^ù¯¼l:5øËû?°–qŽ2ë˜s—rú÷Š5 •öªP´´ª9`˜A6o Ɔ·C@1°ãJ&RÛÍü 2göz^ÅH™G¿ÞbÍd¶(ˆœË©Ãç¿3Àü¿®¡ž~y!£àeÿÁnýÇA¿ÆÚ*Œ ÆóeìÈáHÄ=¯­Û±êö¾j|Eÿ¢ù_åÿ[{‰bÅœJY˜ýïÎu¸­fÔù„îÏ“µdCe4nlnPyXàc’!pñŒ††$ôÕD|ò±‰=þ|™9Q4±¯§ ³±~/¦n‡N·*€×öä›ì²I_¨«¨ßóø¶ÉÜDÃ`;G€‰ÀÄ1#¥n A°‚¼’ØŽÍü/kdaÕG¤¯OÌüG½‡ú³Ùà{ø©@ø™=»R{–€‘nrÌ-|®Î¾d*̹d2Y” ·  XKxc:aG &‹¨«OÂã}žõQà5m¼yÎÿö'߆‘ÇPGm,ˆ :Î2*` ò“öºWUÚ‚ ¹nÍ@m9d­ót¯EµÎF‡Ì¥t8óYð+ %`‡/õ²¬?—üãzó†]þ±@Ñl§,û7(âµúZˆÎþ:(år0„]þïþõý®Ì?­ñ÷´û 8ø§=ëÒ+uDù?àb5¾ø™[Éß²ñø³/ÁñSg˜¥m~¨°ýÄì?¸²ÿª$û?eÂhèÛ§·äâ…ä¡ `´œ·r*ršÿ)(h^>òPɹzmÀ¤0¸ïJò üÕ¶n| à ¹üÄDÌà*kgÉñ ÿ,óO¥RX óïÀÞŸx0@húá`“ ñÑÔÔij¨ù{ËiÚ he‹B[YÁ\Œc[ƒË )KßÙ£´´èë àÐ"Ø"ô.¹(p+²²$8¨K§ X¸x…ô}ÔžC@ës)©wº½Š=D¿«ñ½oBöÌéܸåú«`Ò˜š€Ó;­$áCïP,2ÿ]Ÿl‚'æ½O>?_~˜ÆØQ5ðoüÌš:‘œO´¦X'm#‘!ÇÈïšfõçíÿá"wÑ]{Z=ÈgæÚÖ‘yÄF~Ú²ÀäËZ¾¹Þ¡ÀÈ“õþ¸ä?ÎÚûÅ ·ÛüÕ‚yŽŽù Dgý+ D/éÇ`—ûüì>Xúþ‡^ð¯QàKÛýiÿŽ]æ’þs"ÿ$€¬%êU3§Áऀäñç^‚{÷ 0€µ²´4ðÁÌþ+ÂuÍV?ØÙ¼áG€¾•Þs¡aßP»ãUà‚Q€\×ÔÝÙš,û/Èÿ“þH¢”ÃÅ5 dA{qD×%“É`Ü €Ôþcé?뀳ÿùÚ†#$.1ÀšH,ËkhhÀJk[qøïÖöÍõ¼¶´Œ*ô³Ã!!Jpù Ö;Ú”<@÷¡b Ü71Bvë'ê즃$•¼'OŸ…{öK‘Iâ’Ü@Ð`èßÏü/ˆ-­V¢#k‚l£·-àÀýàŠYÓ 4žT†ãØMf[È9ü\$@C²Ÿ÷*üñÏÏ@VÒAØCUÁ¿ÿóßÁí7^ÝüQ¢½aàoøœ"ç•¢Ú-Ç…•8‰€î ÀºôlQÕíC‹BdðÌV·ì°%OjôZü•Ø’-aÕû¤4€fÂP´¢Ó¾Ñ_IH?»ýÿûÿüÞÿhcQƒë¿(ý'$€Æ@13þ‹Eá–ë¯$„{=~ÞZ¼j…ì¿].fgÿQà²ÿDá$Ëþ³Ôýª~0d°'l¦“k-$šþ©<û¯éÐã­Õèj*Èÿù" ö™kA™Ÿ¶üËw½r«¾D S`0ÓÒÒ‹Ç㤠ÂüSá-L$ô„e!p‘‹šõ'*Ü/Óñd2ÇH((( ôËØ‹çrsnI`[Jdß)¤@‰ LŒ©&a£[[lj ”ƒ~Äz>Ù§ü5Ý uógD]3×i†½±h¹™hƒ§V1ÕF'ý‹­Ÿnª;µíAÈ`ã§lÆ3o½þj¸tò8–ýÌ:ˆŽ¢»*âsÂÈA0c@Üïûù×ß‚_üþahlj’¾U¯žð÷w} þé‹w ÏmÒkœ•¹o«L>­2"â¹{1ƒÿöÆY¡ˆ Ñš/H[:æ?­ÑPì·\F¤Ö_¨÷W™ású§$¥—WCôòÿ‚ÈøOúZ¶oÙ¾ þõ[?€m»ö=øçó[”þcÐ¥ÿø'_û9qó…Ûo‚ò²2éûýåéy°eç^aÝTœÙË?$Xûo»ÂªPûØ™8n$Œ¨ö¶;l>¹Îo}ÃÎô»ÌÿÅ«ëÞƒÎhTKãtÿ'òÿh/І\öÿ…dþÛã ë€o---zÄðg2ü‘‰`lúøþü‡ÀÅ5_ù¤ÂŒl6KXsÒ”Ô××—à Ä+dBæšØùzxÊL?òäcs}‡ ÿb¸Žpìð¡à7<ÿv)èw¶_! 6Šà[ļE5ˆè:èX¦Í~êºFîÇY“ƒ‡‘–€²€#>ýŸ›•-@VÔÆ<±§sâÿ†LýIé{Üññ`dõpBÿc쫌Ð@q‘ ²Hlš¶`ñJøñ/ÿ§Ïž“¾6K¼ášËáÿùUèÕ³Œ¾%Ю¤]Lª¥2¡-ÔT‚¢øo_`ÙC«¨‚øˆë=†ŸîöŸöÜYnôÇ€?ÿÍþƒ–°À?(4óOx{# jÕe»ú@4Cú1Ø÷潕ÀWÿóûpøØI'øWŠOöo¯k´þKýuÕ)ûÇk;—N›ÆŒ”¶À{{ñrX¾z- H‘`kgÿù”ì¿Enâ–wª­8;àÑ¿²7Œ)7€l<¶ jwÊbñB]ûó à>d²ïƒkjjŠâ?¾1ŸÒ|ŒÏ»çªáèæ |þüyž¦’©˜&ÞØØÍUƒ_HF>— Wïb÷Åð=Uõ‡ÉF;ÐpVPtF€èG‚¼Ÿ˜>1)¶n‚~=bþÎj´ñ-5oXº­ë¤tŒÝoµ °låG’Ï5ßcÄ\3l€‰à_œsÊþ‰Äû0n¨ÝMk£¥Á3·‡ 7Í =JJ-b~2t ,ClÉIHóW+>Úÿõƒ_Á¶]{ä Ñ<ׯ©{¾ý ˜8¶x¡-?/UFP32ÅV oY€x~õ0Úpb¶q“;Û·%:ôˆ ½Ý©pwÿ0Ú9çeµþ<ëO€?“ükXPJHZ÷OÍþˆôÕ\£&ý-Ä®ù¶/ðI66Á_ç/€¯ïnRîB?Su¸ýUÍ¿Túo_ˆôŸ·è4ÇYÿßxµÔÿÌ™³0ïÕ…pôäir@È\dÆ*â2zÞ].8ûC4=$kÕòÐÎþg±óÿ¨a0nÔÏëS癵ÏÛïeþ£)ó¿€ÉÿG¢û?›LH+­úª0 ïî¹)1lÏëeë¿“HÈ\€;DyÛ?æ@H]'±¡’‡nr܇Â,aH\¸óqðÏ•JsssicccL<ñ[ ÜsMæB=Úó¿ˆ/~Ádh(8ÀRÕŸ™ît^@.ÐoKüéψîÊö›ÀŸþ(úqøãÌ”Q·Å€§‡AK*%A%¸ô[R¬SL*7î²Ú®û9¤¯#¾îqÓµWÂÌ©B)€!è<‚NH…}r%B^`ÏCð£_þ–®Xíû½zö€oýë—á¶®€’’8Ý3LnA@ .#Áfd­Iæe\ v&Hù;&OgoÞ§b£oö´ ¿6?¹?â&% ð³l?!âÌ ÆŒþ¨•VAôòï€>å æs#Ò;sö<ôØ3ð³ß=ìøLU¡àŸ(§õ­(ðϾ¸‡ƒÕ¾.0|¬|øâ·@ïžÒ÷{ôéy°zýŒZ!—þSÅÑN48ÛŸ/ûß§gO?ªFú>ͧ÷ùM¶Îe·ùŸ¢ÑM!6ŒV)Øå¾Ï<‡õas -`D@.‡ÿBãlÑ@Œßeø‚ßM±Nú þŠJýA׊ì.Á~H\sÕ}Â3 ­©©©$•JEe56…ÖèûÕüÔµWò’v?FÕTÃÔq£À)í˜2·‹¿ú½ ¢QHú£Q–é'À>J~rwv|£S…¹O×!‹BUÿJblxóÜË¥k¾‚Áj®©¤˜—›^B ¸cWPg·´&?ëßþ&d“g¥¯ýâg?c†W{è%ˆ«˜”¥3r“b9~Þ‘§àÞŸ€§_xšš[| ±Ï}òfø×/ßðÑwÓf5þ,—¨p? ¼„²€Î$ºlýk‹ ÀkytàTÐ*gYY— h¶µÊðçrw­?qö/5¦f†•õW©äöÁWCüšïƒ^}™ïGîÙw~vßCð¸ÕáD þIKTUaÝQ”@·úsžýÆ×"ýרë?©ÿ'fœŠ.®»r ¯,uý_´l,Yñ-­GAŽö‡A‘þ‹§RÞÚó6bØ ˜:i¬çõéä8³ökW‘p£Ö¬³B€NÑý_¬ÿ'þT¼ªô™ (ZhA$ìr]:$-Á=s^ÒMLf(>Ÿu/‹âz¡ W(±X¬àˆNZ¸ ºðšØ9dæ$Ñ“Éd)ž@"Ó&öäT|Œ†üúæëóéú}\>}K øû¹ëýýØÃB®ö´%,v¢¢ª?4 /|´y› Ó"h\ 4r 8‹÷*ô;Aë8².î ïsÌ(Õz81@[= Ï!`A­b-ç±hšÀ¿fØP?j8ô¬è‘sn@¢Äg}’K¿f5?È Ñ¿ÂÞ7h&yÈP8 ÍÚbJ|øŒºmиê(¹êÄ ]•½zÂí·Ì…Ÿ|NŸ­3+=Æxûe]@±Zú®_Ä Ø B 2¿£ÁM„g s[ †²dãðßøùçëêáÑç_}‡ŽÂ—ïú´´—6>' Ç ‚§^|/_cÏd­1Ï_¼^j2xß›ï²t ÊàÏÆÿ‘Öí×]¤™æE·ì϶/­¾ösÍŒœñ‰wAÝ¢÷­røŸŒyÓZ3Ñ=á$-ÈD°×ÉODZù™óOÅ¿›?Üx‹3rÍŠö}ü ºÑrùwï‡t:«>\÷ÿùIÛììõ“JÜéZIœÓ‹ üó–V­?#ƒéßv/pÜöîêË/D<æy?,ýŸ¿`>qʾñýPé¿XúàhyÈTbö¿¢¬ &Ž)}ŸtÝi8µê‡wöJ´¤ÿbÀÀ¬/ìŠeµý£YDÊhiš>ê–"uª½°¾;{ïŽÁÅ+vÄÇñß"æÈW>,Æü˜Èd2¸€vöìY…ÅsäDÒuݯ¸P þüå'=›8Ø0CÇ s2i­qólëBÑYN¡,º „‹m :Ô‡ŸoHÃa«ŽðW„L þ@³UÌQƒ ô«ŠUÊk°9è§±=½+{WÀèáC¡fø4 ?Dô–„UCgCgã2IzUà—dãÏ Ÿ 9Á7Ä_“Íë~Ñ‘sA<ÃÓçúŠYÓaëŽÝðÚ»+ e‚ &¦  »dÂ}âAÂ`ȰIdŠ‹  Ëü=•JÃ[K߇CGŽÁ?ÿŸÏÃøÑòÀº¬´þï> £Ìsì•…‹áø©³˜ £çëF@‚MŒùyxaÒ ·ÄD!V0!PŒD@kOFï¯Á"HM8êØœ'¤SÐì<‡Cñ#þ<-ýø#³(ÿHåÆQFèægaà¯Ùµþæy¡ô‘éÿjŸ‘¾× d2 /¿þ6üñ±g…z…­©0aÃ?,û§ WµHÖ þ‰àŸ¬ñš ~yÝ?Ĉ îüÄ„À”§_|–‰ÆlXÒ(°k˃&ýWxiœ¢­Ùÿª•0kúdÏûd[’pfí‹Ö5Wl÷ÇG«ýçG‰Ï¦PXB˜ç@¿q¾Ý/ÂÑ…$•O6¿Ð×»“b¹®_W1Ñý_P*«æ:ÕðâÔ@åÝ¡4 E"‘|&€á €‹ü ˆM´H]]ÖSi~-=d·ö€ý|Ì^kÁB: ´7¨¼˜}.6 ÞYò>œ'&¡«‚)Ó åþ¹²ýªJï'àž|«Ö‘™ª©ªmª¦)ŠÄ"$™ÿa?€‘&à=b( T=ÊJÛvaªñÿïË ŒD#@^>ZL*peyêþôüÛ7I/q÷øÒŸ„{Âf+£ho)¹²A  ŸGr JH`fhÙŒ@˜÷g鹌Il‘€Û„}ÿ§¿‡/ßu;ÜtÝ•ò0Õܹ×\~)Ô LHƒw–®&_‚ú ÑØ"€¤Ü0ð7ËØDÿ®A– ì€u³+¶-Ò ”LýwH®ù!óó‰p~½5À_ѨÀü›Ôð³Œ?~ÉlRYÉúk£?ú¨›Å{ønÿ‰S§á~ æ/\DIY–¶Úü©ÈÊžøw›þq‡Kþ/øÁU“¹Ï>ñ±k`„£‹‰=ÞY²^{k©uÀ,É¿Âÿ‚©ˆ»ÐíµÉo…©á,X+‰Ãô‰ã¤ï‘iªƒKþÛ*ãU±[ÿ)AsÿwÉÿþôwƒ©´a7CÌp‹hKìî·Î -Ë¥±½» –ù§Ó阎ÿê_è H€H„ÀÅ5˜,8øGtD°ÆPíŒ:üÖÔuÄbÔ™Ÿq1ŽŠòr8 l€jþ½°« @üÅl?¯ÝÄþ,ˆKüY`£sÐ/ܧŠÙ!ÛoúU„Ñ#¡zpôï[ ªÚ¾Ì€¢ÇAz4­1§Î2 `ˆ™Á€^5¬ï†œ&fœ¸àß9[»êßþ!”ÞôÒ_\Øcá¿tÜóÛ?Á±“g(ÉCJ?QXu²Ðå™kä"dJ_( €¥ú1AKd}ÀXÅ$‚Óçjá¾Gž„­;wÃßÜñqiIƒ«ÀßÞqL3 žš÷œ8M=$¤‘yÆe!ƒ?B›‰¶4þã/j‡’«+‡¯€XÍ\¨ÿà‡$3šeË™!,mŽÓ åþ\êïþô௓?.÷7Œ hU—>é.ÐúŒ"j¿}±vÃføÅï†í»÷3ð¯Z’`®ö¶Ó¿êhutðÏç©húGêý± nëJ~§pÒ÷~ìH˜9}Y³Ü«xæ/xÛšªTú'ý·Ö*VÒfûᨖ ÿx}èo®IW;Äó>ÙL œýhžù3k9ÿsðïÈþ£vLïN[hxû?Ývÿ7ÿ6HI }Äܰþ?€ó¶­*\Y)¯ß{ºUü~Üú/•JEb±˜ÎîSXKsB`6L§Ó‚þ¸è† ðQ&“qøe755!Æ¢ÅÌÇKBj{‚µ¶°~~ù¹žßÇÑp6æÌšk7n‡çÏ“ ƒ&¿zx$)ñì¼,ÛÏë÷Ýuý– +ëú©m"ÁÌÔ‰ca☑Dî_šHt(xPûÔ@tò@ËG? ÓŒÌ0\\GËÀTð,¦Â¶?Ô²ýh©¾ ¢ãnö”Œ6>{ëõð—ç_óõ¤Ž¢I–aXï'ûN%D‰¿û>÷‘‘  Ü ¨!Kê½-ÇIsÚšSðÚ»Ë`çÞð…Ûo1ç¼÷:)3¦Œ'å2o/[ ¯¿½œß Øä‚ ´ >…³X¥ßEÉ2E@;‰€Àÿ"zåˆ×ü ´ìy‚,« ¨‹L³Á¿Ú6àÏÛš1¹?vøLøèî2¿DLè<â$¬kkëá…WÀcϾDÚ™Ò½¬zêýÖ‚f·6µ‹üsE—eúdzÿºÆJ(9Œw^yY)Ü4wô’øºà²€¿Î–¯ÙÀˆhd••þ‹3GÌþ[^7Œà¦%ñ(̾d²<®ji‚ãK~hƒÜúKÿÝíÿ‚dþ‡”þ1–Ói7  õÿJùpPÊ„Y€ÉwLîÎâË|ÆdïƒÏw³x<½|Ö¢0G¬¼a9>½‚Šú‚áhåäÄYG@²ì*J<™LÆ™ ïä.¨·¦µ_!ÊŽP„£mcÚ¤qPÞ£Nœ;'D †§ @e€‡J‡©Ÿ˜í·Å65[òJüñ{ ®êÓ'…áCC<#Aag %R‘áWB˶‘`4îtxpY°‚­Iw)y̼¿~ñ€6p ¨¸¯¸k~Üh×;÷€wÞ[©T†¤EU±?ƒ=_açlWlœásŸL–‘£?"âCnhîœ “Rc_!S k>€ Ö~~ÿ_`ÓÖ]ð_¼X©¡gàdŸ¾ùz˜5m2<7lÞ¶‡˜f"ÞzP5(èWéœÈ’L†éL6/@ö±àÁ€É€.YÓZÍr¡¢\ŸµŠP2åóдë 2_¬V€ÈþYË8©¹ÉìG™Ñ_.à{ô±Ÿ5o¥´õA2ÓÙ»ö¯xV­Ýèü{ëýV‚bKÛÉ-ð/_ËôO×,Ó? þ#ñ¼¹óã7°¡ƒ¤ï9ÿ·á™ß`«’ʤÿJà¥ÿÖú$dÿ¹É-QÃ!šýϘç@ßÞ=á !ižW§V>éÆãìØ³®0‚!9"ê¶ ÿE¨û'¤]¥ÆTæù0ü@%•a@ÖÍóÕ/ÁVHy°Ÿ)`¾kÛÐm*žJ¥ô²²²(ü8Ù‰ñ?S x<®BØ0$.ö‰+žð ð£t:]ÂJ{Jò½¶Ðv‚!¸æ¸ræt8|ä4¦[¼…±8pS]Ã/©íçòEží×XVCtñWX¿Â|pœÝŸ2~4L2o½*ÊÛÝ.²Ð¡œ Ñ _‚–¾k\UÈù”“ù¸Ki: µ¯z~æIsƒ£ž÷ùÊ? ÇNœ†µ›wRvÜ‘¹Q×tAÉÓUžî2€ÖÔf°A_ÂÚfÅTc‹RPŽ ¶®ž}e!¬Ý´¾ú¥;aÊ$yí-&†ª‚o~åï`ÃÖíðì‹ àèÉ3ì“ÅÊ«+s` Oj¨d(€Ÿ‹÷£ÁéÆ"-TiÃʺ$@oKÓ¶õ»«×u½ßXˆTÝ™coZ›aˆdÿ:mççpõ·É€B€¿6pè“ﵲƋAnC2 /½úÜÿ—§¡±¹™Ž\’ÿ"3ûs9þãë7ýÓy«?V 0SÀŒ9'n¿ùZ˜àã|¿fÝxjޫΦ ø/é¿?7ûÏëœyöŸ—‰x”¨d#ÝxN®¸—0¾ìã¨æÎüÑü€6wÌcg(Ô#qÃ|Lí3Î|8cA›¿®¤ ¬ãW«® >ªÙßÜ °©©)Ò»woL(L9@H]×UוÌû ;„À…9¸ãÿ¹sçvAA¬í¾ Æ£­1ýk/A˸¯³©p´o`óç^yÛŒ0À.`²iEûŸÓ ŽXÛOLŒ„l¿Æk%uýø¹‰xèÓ'‡C ÿ]¨©¹Òû®‚ìÉ%x!âçæ0 kFêÒ‹´MXˆ¥Xön†U>¶êýÊ®ÿ_O)–Þ~ýÿþ÷ç÷þ#ÇAeòO)x¶Â·.RȰf[j1ø$5äÒý,SO°0Ÿ»cïøï» 7^=¾tç§|[Kâs}ꄱä¶àÝ÷àÝe«à8«GÞ‹œýî »s@Ñ®ä;f ÚFÐK@%¾ö†w8€Úòäs ~0«õ%Óþοö¦mü)D† Š2ðOümÁ<Σ :õoA«šæA_î wÃæ­ð‡Gž„5¶ØYI‹?Qò_,õþž3†{~­ àŸ»þ³ëWÌœ 3§O&k“{àºÿ§þú ì9pˆî+–ó°¤ÿL½Dé?_“xö_wöŸ¨Ì' Цú'–<`eÿEó?…­ûŠ ÷3™ùýªm t.éCæšóg`ˆu#†Õù·Ö¬»xEŠG0nq?ŽñLKK‹¹\hQ†iÞQ™B4‘H€t:¿/VL‡„@H\(d™@ˆÕÄð;•T*UÚØØó+µäz€š““׫¹{y’ cëÄÁÏœOtúÌõ™Vð&ÈrÝóž£JZǸ%™¹ê‘.äѳg˜ðœ>w‡ÕäÝ((¡;¡[TîzÿöÝ~æ€$ÍÐeðç˜?±7ÀË —À’•Á7¾ò70cÊ$ir>>6w\}ù¥°ðÝe°Êœ?GNœ¢ë‘q«Ì3y¯MT`(¤K'ñdP2À’£°}¯HæI¶ËbTß’síîx ƒøèЙ€J‡ƒ‘Üã"1èOPà¯ÆòJý­k~Nù ˆŒ»´êËI«Ñ\ïñ“§à…ù à±çæÓ2Ñè|I&_g’’ݶ$ÿœP >ø—9þãdö:3ÿã¥$ž0çÀÈCáÚ+gAyY‰çý›šà™^¥«>t’l¢ôÔõß•ýçDˆàrnM¯¨¹_>}ëõÒ÷Iן†Ó>ÙTƒ£õ7ýã®ÿÁËþcR†ËÿuÁPcíþÌãe^Ÿ”Ò¾a$ßç¨{­v×óûeÿ9pw“²×úÅ{²NâühnnÖÍŸQ]ו¦¦&…á›ÿ1ós™ü_ÆZ‡J€¸ <±ý9áqÝL2™,I§ÓQÒ[×\ç`ùœ@Ãú}&µ˜¾÷M×^ Ë?ÚH€62ÃàD1A?®ÓŒF©\:ËösÙ¿,ÛOˆ3à«èQÕÀÄq£HÐöMdÄ\Hï¿ 2‡^q:Z…õ¢ ήÜ€›CYW¹LÔ/þ:è}G6`’gîM7>÷‰áÑgçC²¹™wÓf Ô¾VŠ*€.%Ú[Ðê64) À€œø­³”4ÉòðÀ€sµuðãß<sfLƒ;?ù1  ó ¬cÑ|â¦káÊٗ»ËÞ‡5ë·ÀQFÂö(!€Öø+ä³ «<@Í*D€_“±:ØÊă+ñ˜¸ÉЀ%5‚4ßñ¾ÓÊûAé¥ÿêý“Ý÷’˜ú%À0oH‹ƒ¡ÆÌõÌòjÄîUî–ú›Fé1ôš>âs!)ɹ’ÉFXºbµ ü_„{XÀŸ®•6˜…’¦ a[þ-O˜ŽÿxöîUŸ¼i.ôóéÆñòkoÂ3óZ€Råuÿ*Õ*Û?H鿵±ì?57ä]DÌÛ¸15¾ÞGÞüd’G-ªÐ@±ëÿƒ™ý×íÖŠjÍ+Ôk(•£Â >Èó¹@_0wý¿Œøåà^–|ô{óy¸‹Y4‰pÉ?é€ÿ‰F£j,C&ÖvF á €â§@¯†0—jNŠÒL&õ3¶jí…¬-²~Ùk Êù|òŽ^È„D¡/È5Æ@ßžpêl-Íý#…fkt•dk¢¸i¸fÓí ýJKìã÷SS ‰D<°Û­öª½úZH|Å’üƒ½-‚~­Ì¿°gSµpþõ…žwÍ%ã,öñ“§á•·—RS@FXiIg€.q¯ï¨/š2_„3ð|{/ àÙ º½--ix×nï}°>ÿ©›`î3aÈÀ*ßéÙ£ î¸í¸üÒ©°rÍzøpÃV8rì8dSC{ÐN öD=…?b¾Æ|²ÊÊ0!@òÄB€-`!`@+Jºø„îÎì¿#xÕãsÔ/¢»6‰™û>n®iæ:¥&LLü! ü#bØŒDR{mØ\ü9?;•JÁÖ»á‰ç^†Eæydïþ|Y¥8%ÿb柵ûã&˜H¦ ÍrüÇÏŤòç>ù1¨,—€¿·r <òÌKÀMÿT–æ¶J$Q¢Túï®ý×UÅî”î¡ÀH×Oßr46j>½êv¼Ùt#Ù>žùç5ÿªP´­'Ù«‹†f©i ÖfS©ZßÑa_d„€ì<õS ø©ò®×`) 4ÇÄtsáe˜ÀÒqšAî̘ý € ü³IÇýÿðlhÞ§Ë&V!À·=ý? Ö9X¿.3… ‡Ëê:|Æ/xüyLE˜Ks$ªC,1ÿŽP€ªx²ýø Ø¿/Œ¬ #†ýúìO>²'Ç62öVH\™ýÏ[Wˆ, ²:‡vÆÄç*ÛNq—>¿ j~*>ù{é¾ÿÒŸ€“§ÎÀ{n mî¸7ƒ‘»3@«Ô…^‚EÓ¿$\¾¼$€ÚR2Ï zÄ-U#&L ÷ØóóaÑ{«áö[¯ƒk¯¸Œ]~ç2>ÿo7ƒù93§ÁºÍÛ`Õ`ïÁÃ&rÅjyI;ôX¬¥ jXJüýù ·BÀ·d ‹Nä ®ÛJI/HLý4¯¿‡ þ‘#™¤D™@·3þÌŸAx¨C.½z!òýæñ~ñÕ7aÁ¢epúìyþétŽ"g­? $Ïú-ø'Nÿ¬ÖŸ øÇd׿<;j„ô=·ïÚzê¯pw¨NØfHhù@é?_w¸wþ×yë?¶ðivÝ—B¯ž=¤óæØÛ¿„æ³›èZÌ ÿXæßýWP.JŒTµœÿ­ì¿Î²ÿ 13Nè‚ÿ }?`žØÍÕ¾;_ë?÷gãL*• ÀjsFÔαXLaDòþá € s˜ õ:H˜\XC\*ã'ã—MòŽ2lkÉAw’;ÑF`âØQ´=™¢‘ V\éšNn˜ °3ô8–Ä0zäP¨2† @ þÜ»1èûU)©„Èðë!yä0Òg¸“ºÅ ÀÂË,µƒÁ  ›‚æ}/BÝÒ(»òÒ×õïζ½ûÙ,sm_Ky­|›JŒ7ÀHvøD—— ¯@ôÀ%úƒûzÂôëaîU³àÚ+fçÄ}+{Á W͆K&O€-;vÃûk6ÀÖ{ •É0Ÿ "wl?E†jxºø„¬`„€!#Ø÷Bj1OÉwîªò®Xdíö¬ù+‡ÄÔÏCËÆ{Ìc#àŸÔü£ˆ¹ßuê AöeÖÄ+ Ð_ ê KÍÛ ·Dò~ö‰“§aá»K`Þ«oÁ¡c'¤Àßvø/þ¬¿ ü þUZ÷ÑœàŸ:þgáÆkfÃÌiå ÷ÄIxì™aöìU¨ûçeZp[þ±iFLtñ>Ѹ‘.½¶b‚„|oƒ²~lîåd¹GòÐF¨?°’QHÌú+ÎìðN•<Ù¬ˆè9 ôasÂ@>às»-î[îûüHwù€à¦¦R©&ˆý¿¢š¹Èðògównèçޏpç©ÂÒJÜÀœ‘úúúž<~l^!“¼­µgQñ[Ü LЂÒb'ÊËpÛµsàÝåkÌ ‚ÿ(Ëà†7 \ÆÔ@Uÿ¾äت8ÉÉŸ…–}K ³÷I®þ¶€ 8ú÷ó`¦þ–§Ùžt=4®ý ¨=ª 1ùNÏùZ’HÀ7¿ú%øö=÷ÁÉÓg™) }R»Îœ·meÑy¾F'^¾9`ÕØ“Ú% m²ó²‚7¢íñï+?Úï¯Ýï,^ 7]wÌ™5¸+±lô¬('j€©ÇÀÁÃÇHiÀÊ7€¹>3ó2û¹ºŠèù§‚ÕÝ"X6ZTŒÈf ‹ FØ}ïÅu3‡Ÿ€ã C­k‰ÑÝë`>õ˜Ös0h?™KI¶·!Ã! Ù¿æo=†™ÀäZP«¦‚Úg$-È3Μ=o.~^]¸ví=@.ÿ¹¿Š‡ÿâÍúû’íÇ —«ÈHÝ¿jÕ¼ãã3çÒ©pÝ•³À ê½ 7™„¿¾ü:,\²Üÿκ%°uÿâ>AVÛ\zÝ$r8‰ŽšÏJ›çÛçn»ž¬½²q|éï¡éÄ+»¯*ÎÏþsb Û/FŒDõfÿ5û~|5ÁJ›ÊÑæñŒ@8‚7§Ý&€mY×e-[SûÏý°¹9VÄãq­ïïàŸØÀ¼) —ÿ‡à?$.üùÊA?æD!g2™8+P ©¹ÉõX¾çªš þÎÑ£¼.›> –®Zgf:qn&nÍæ"\Z‡™S'Âè‘ÕPQ^‰xÜCÖóˆMú$Oo£~ƒ…?‘ ý(ç*P(~…l˨_öÐ*ª!Z=Ëóýûö†ïüÛ?Àwïù-445S@Ï“ã-$m&dåîŒ:‡9‰…©ÜíYà¢Ö†ãûß_»6nß ¯¾¹>}Û p‰9?r¥%%Dî<¼z\Õ,غs/¼»t8|”J˜YÙ Œ¤$¸ÚŸª²ÜÀ»ÈgùOc$ÚbXP8IN Äç/±ÊÛòÏÿ¶¯ƒeœš³ô(V%³ÿ j_zβCÓ ûè57€Ú{8(¥ý •õ IxãíÅøïÜ{R锸ÓýÂ:äˆ&1(ʬ!à?Š?sþÁÿ” £IÆÛ]FÃÇo/…GŸ{™­"øg²y^÷¯¯îßÏøÔÿ³ý@HV^5À\o§M+ÍþŸß´ö-µ:»ðÚ‹zí¿å£¡1³MÍ"kPé`ÐGÝFïžÛ!Ä纻È|ò॥¥wˆpàïÄÒB®áU¡ü?$.¶!°i²ú—xsss\$ò±mmûaþ…5úõ­„©ãGÂî‡ Áø1à ð2pÄ¢Q¼¸ÏÁ¶óA;oôa³AÛ}-¤6o õÿ¼ÌšeÒƒÞÀÂÏ –à(FrWã^¨}ë¿ âŽ'@ï9Øó~#ªÃw¾þð£_?M-´Æ–E¡È%h3 `HˆY @1'H¢ ~*umGYzàn}D @¿`C² >X¿¶îÞ£L`ÿÉ›çÂe—L%nç²yÇþýúT’6Œ—LÇO†×o÷׬‡Óçk)°AB[U‹Ý1_o9(S©z†© fÈÕ H-³†õÄ',ù¿áž×ö5A±|?F¸ˆ§€°òÇTˆ œ¨l(‰~ŸøYЇ^j¢3öË¿AuuõðÚ›‹àõw–ÂÎ= I Àß&N»ÕÊþ“ºóÔÑ8øWìöJPv9ÇíãDäÿÀ\ÿi/rŸZ9&lýWLsݧßÿÏÞ{ÊqUÙ¢«ª:Ý +é*gY9Y–-ËQÎr6ÛÀ€f€! üÏðæÁ„ÿ˜!ÍgÀõGNC06Æ猳e[Ž’%Y’•s¼©oçªWû„êSÕU®î•º[uìRÇÛ¡ºÎ©½Ö^{í  ï·Ëç!¦¶¬Æ Àþ»X<ÉRg*°_ƒuðidŽh¾ùI'1„‘”A“£¥»»»Ø1fºS%h—T^—-¯ù‡ÐVbóÔºUž©.À²C?J üh³PåLMmŒ;—]p6¶î܃óÎ:eû«Ý¿µ|ÿcÝ2±š÷j9ó30÷­‚yðY'¦tdõh /¥% Åbº4¶×Š­ G^C÷}‡Îüô–Ò`œJ<¾ôÙ¿Âw~ú;¤R9'Øg`x°HTüÇr‡û¨x 7 ÷í·[‹Ãéb?X|Ÿôõ%ñÚšuXóÎ&Lž0–u¸hù™hiiqæ zäºFÝYÚÚ Ì´À´)“ðžË/ÄÖ»ñüK¯Ù¯õRéT1{ìÀKñ;XÜÌPüàRöo £A³˜ù×-GQ`* §óƒ)» h®˜^~fžÕ©ÄA,Ûe:ª”# ‡b ¨*CoGçGï·ϨGâUÉüijÿUÀ #2!мs¶««K‚|ÇÀžTmdœQ«<{ åA†Alžßb* êgœ²hÛN´¡Ø‚#ýÒ&XÙ=N-½ôój+…>.1˜:ÛY[@«HÈÏ_ç÷=Ž#wïÿ%4Ÿ–f§Ÿ²Ÿûèð³[îB_2Í¢6Ø$@¹Óö`v¨…$€†¢I xùtRHÿ».—Ùl6‡ÍÛvá¦ü~vóøàµW°®äð­Ê|½uê²FxþìlËd²Ø´e^~} ÖmØ‚î¾^{š,ã­ùëÂØOC^ÂU¢|oY II°ÛÞõp€¿4/´œ ÎÿÜpP0ëÓÓï; &¶lÛŽ;ïÿùy$“)u–¹3þàÝQTwi6É)¼:l]3ƒ\Ôã†k.ÃÔÉþm4_ëmÜôýŸ£«»OÜãgúgÔ­éŸËõ_´@4„ä?)ÖÿGØÇbXqÞ™æS‘Þ· GVß|¶›~Nö_WZÖcö_¬¬(CSJHñBªÅ?¸>r"cæ„Vtkhùg5ãyO2ø³—”#Îa Ñh”µô˜V Âþ‹óEÓ”l¼aO†¶þþþ„x¬"x¯Åµ¿–2ÉÖJ‹ƒf¨]ÇàŽø©7"·õI˜ÛïàÆzœu4„!  3û˜zg!aÉÜî‡pøžÏ£óºñL¨gœwöéÈäòøÍ÷ÚÀ'S-÷pj$޶@ó¹~¼Ø?“@V_, àŒ…ˆep’EfÙÅw ½ÑÕÕ‹Ÿþæv¶]zþ™xϳŒI~ ” º¨+ÇÂy³Ù–/°kÏ~¼öÖ¬Yÿ.ºzz‘ÍfÑŸÎ %€æ[.5@ódd—fù=­"`j¥Ù›¢@éú¯ëÍGÞæìc¾/™´éZÜvÏxkíö;HÐÏ¿w)ð/Öùó8TcuÏhh¹¿ë¼¬€\§Õ_ð?ft'>tÝ•˜>u’ïk¯}g¾÷³ßbÇî½âÍ”ºÿˆjú§Õ¥éŸ‹$¤OQú¯»¤ÿr^Ýpõ%?vŒïë|åôm¾Ÿ½~{Û¯U¼®+ÙÿºÚ šø3U ¹þN— ê¸]øþ0Шc"ÀTÂ~Š“hžÛ´Žär9êõþŒX‹Fu2ôxT%ÿx)!Ð$€¦NüdHÆåL©Ž¦^{ à¼–¿©Õx$Ç?@¬õdS¿kË9_Dÿá·a%×ùâÓã…K€cME‡?·Û~/Ž<܉Ϋoòu”"·î<‘wÞ‡L&ï´ä¾Ëø˜0]$# Àö› føëqÇ 5ûüRŽ,Ê4VÇ@·t7`‰?ó³·ù³¦ãêË.Àâ…s1aÜX´$•O°ö>mò¶]wÕ¥èíKâM[°áÝ­¬T‡êÑ»m°šIçxé‚¢Tñfšäw”¥­Õý_¶¦Ó’¥¦ š´†\ƒ¼£«»{ö`rô[ï~Gº»•¼‘î"<ÊÿæûûYÛ^ ø5j$>rÃU˜1m²ïkoݾ?ýõ-X³~c)ø7tü×§é_‘CQú¯¥ÿ¤ú¡R&ýÇÍü93°`îL¶ÿ¼£ûíÇqäß+ó×mü'Ýþ몸YS¯(> r֚ͤ´^ÒoØ1‘‰‹Ã€©AãºZã~¿.•žç%!$`ß`ÒÛŒ¼H€òi?]a8B ©&*Éþ5Á˜E3™L[¡P  ãËâU;‰«1ö(Ыõ Ì /áP‡1n>¢óoDöõ› ú|U%¦€utJqái•Ðy;]|~/;ËL#³ét?5Ã/þGß×¼â’åÈ›yüöŽ‘ÏØk‚ @_×x<ï ¼=ªUÔ³²B造–ÐÕ¨˜‚‘Fy"@fz¥õ›¶b½ut´ã¼3–à¬Ó—`öŒé;fk—VÍ ÁéK±ê'îÂæm;±s÷^ìÙ=}}èîîÅ¡î>… (:ÒÁJ©ÇàÏo–&ƒ&kIˆcÐFét†í»µ6âÉg^ÄËo¼ÍëÐÐïÔ÷óƒYd£¥ÑŸæþR¾.{pjšãÝÐTà_´ˆU[ýÉxƒŽÑ6øÿË\ƒ™Ó¦ø¾öž}ûñ“ÿºÏ¿ò†?ø~õlúW•ô_/Jÿiê\yñrfê…Tެ¹™î Nößo“%õw8Øpã?=Ê”ó0œ(¾äcc¦sý p^Ëß—k'X ! *ìçEìs]"‹>’ ò0•$ jê§€çvþC ¹æ®h¡N¸H___;•Ǩ¦C¹HÔòU„£~Ç@³ùõªHœóyä¶>óÀÓN-½Þ@1|  I?Ñ!ÀA©–íAÿ›?„žèÀ°sþÖ÷5¯¹ôBôsÇý6@*’E×A×Ãÿ²%Þú~o'€zÛÏÞ²Õä›@ ‘F­Yi€ˆº}Y{O{¨·§>þ°·ÉÇaù²%Xrò|Ì5ƒ§jç­ï$±¦íÌ¥<³–ìOaïþƒlÛw຺{ÑÓÛ‹®Þ$wõ •Î;ˆ¡ëîù¨û¸ŒYr>XBß ÕÃÏSÛ‡( ÌcáÝÍÛðÆš·ñij/£§/é3Ž|²ý(šû9¤‰Ìøó×ÐÀß þ¹ÿƒ&À?oí•™q[ÿt<~äýW‚:. ü?þì‹Eð/ÿ5f”çvü¯Çº²(#ýEéÿ5—žéSü}ެ~‡V}ßùæò¶Úö¯®²ÿªü_‚~û:ïxAeöoÚ:Æ„“ë±oa8< =hm­¼W»VWS‚,Tz>ŸÇb1C=ò„`P€º™&!Ž¡]šØØué¨iOŠXooo Uªdöü&e9ö¯–çvk¸Á~.Õá‡-çÉûÿ’ª€ZÖÖK)}#PÏŒ”Ií,qÉÌ̳‡Ñ÷ÒM,€vÖßø¾æU+Îg­_ýñ^r¦šJæYaÒ'E3€°|€cDH  "uH"ŒúÐ…G€.Ûñ–oyáÀ®Ýûð‡?=‚Ûï} sgOÃ) ça°`ÎLŒ7¶æû¶ÖÌ:i*ÛäH¥RèêéÑ®næ!p¤»½½Iôõ÷£¯·=É~Û×û“Ìô®H øà–l?h'z~#¹Ò¹ñ›±~Ã&¬^¿‘Õ÷ïÜ»ßç7ÖK³ýàŽþÔkEW¨æ~‹ÒÐÀßüS›õ³·~Ldý™ùËþsÙ¿ÿãÆŽÂ‡¯»2ü:|¿øíðÀãO;û›òÚ]X⢾ÁµÒM”CL³ÿ’Eó¨yÉkõï\ƒƒ+áÙêýˆ¨ýg†"ÿ¯»CË(Éþ;*V`ï“%·Ÿ ‹úÄ1{%õ°7Ó_©3ØL@¦æö܉²Ô?_XöŸºžÙ÷ëž0Ç«GH4+iÇ uQ$gX:&@¤p]Îo ϯÅ@d0}ÂŽAYØ&/EtÖ‘[÷SÌ¥…œÐ›ß—5T:8—Ùƒè{ñßYæ¦ýŒù¾æå/g}îúû;  é–| ‹×ˤS¾}é"4…ÐÊ|è"Ø…¬ '€s¤ðš¾ß}Ê$"TÔQ`݆ÍXkoÑHS&Že}Óç͞Šæ3¡v¨‰Ìjia¹±«ƒÊ9Ré4SØç ô§ÒHg²Èær¬£A:“A*•fÏÉdó0 ä &Ó¹|Éþ$¦Nšx\×ioà©jϸúíõxkÝz¼»e;óMر{?¤ˆÃie¨qS49tgûG ©¿ßù;"€?e²£ø/fþUð?qÂX|ðÚËË‚ÿŸÿö6ÜýÐ ø~ †§ÝŸVßà_®]ÿErýŸû¯Ç Ìc#™p k»ŽúÿÕÄ÷G“»:ÒT1·¼„‚hm‹ÇãQ%á)Ë@þÔ>¡ $š}$“IµæŸ]Ïd2Œ³¯·@öÌôä¾unçTJ ö ­ÔJÄE”¯í÷|Qûã<^î3èä]9Žê÷g3}¾–óÿù­Âìߊd^æ8è$€S >¼¡ùÜÌ~ô>ÿo̸iØåûšÛÁl,ÅsòÙ‚‚Dwf•GÙp^s@¾ºÉ¯Ë°ÑÕ7ŠG€C¤hŠ*¦Ø'¥å¬XB*,8Æ t>ŸÃæí»ØöØS+1fôHL?Ó¦L¹³°pþœ0? “·.ÈS cX{Ù癬¡é´"”¿ôõbHÄŦw·`õºw°îw±mç.–å?|¸»x2dv[¶SÔ„ü\˜–9Ù~ÖäO‘ùÕPŽáFméxžV­®; Vfû#JöŸËÛùLÒö®¹4ÐíŸÀÿ¯~;î¸ÿQ8êðÏMÿ" þé¿^”þGD)“þ‹˜‰æÊŠóÏÄâs}_¯wã Ø÷ô¿8)KúÕºYó:2þs oöŸÀ„m–ÆK¶c‹ÿZˈ0Xj€øHé)ö¦r)¿ø½@¯bÓ4]¯!ã|?¼Àæ' ß$¢ÑhŒðNwdaíÏÉÁ]ÂÍ=¥ü_2c䉑J¥Úä©ÔjïhS¹L5‹Ä@zÍh¹j8ê$ŽCë%ÿ©o€UHs0$[êIC@zÃ|'þ¹™¯ð0´¢ ÀãrFô=ÿuv_ °üÌÓÐÒÇþìwÈf jSRᑯ³–rº)ºèÜ(P75€Ve«ÀF!øSUyÛšå£ ÐÓ@zeÚwï;€Ý{0ƒºû{š™ˆ=³Nš†ófaæôi¬Çú@–µjÖB!¹¬;òT 7o³ÁþF¼½~¶l߉#]½8ÔÕÃZ%ò6}`uååA?JkûQ&Û4¬«%P«‚ÇìOýtéÔ¶ ËŒéSlð¿S'Mð}ýÃGºñ›[ïÂmzØþ5 þõÆÿ*©*K#TÉT´F¤ý£‰ùBÄȲS±ÖžÞ‘9¸ûŸû ùœ“ù7é?3ýSI­Žv»4ÜÙþFÔÉþÓïk%ÆÂ˜pªý”xT4I0Z¯@2À'Ù§åóùXkkkTfþ‹ÓÐåÔÐ0!ÐÐóRRþaØÚßßß*&‡/à/'ï ó)#P't¹…¯i(ƒKoëpßJ=èô³?ù‹È½ùm{²à”p+]Ì:'üL]$€¸tH­HXù4:Îù´ïë.]¼ÿò…Oã¦ý’ÉLñ}$ `ƒY“‰¨ w'Bo«@Ç@kŠƒ; <@ûGšÂGÀ[ëA˜)j¦æÔá[â’äúÔ3}›½½ºf=Z{ ­ñZÛ˜0v Ì…¹3§cÆŒi˜8nlÓÌa*Cؾs7¶lÝŽµ6à_·á]ì;xiè÷õ¥\€ŸIû Ý-ïG1kïý¥NþÞl?ŸEÍ”í÷ÿn³¿b›?)ûçmþxÆ[㜲h.®½â"ŒÕéûú¬æÿwÄí÷=âÿºüeÿ¨WÃ?ߺ¡ÙÖÑÐÔqÞYK1}J©*ÂÌepdÍÃè~ç¶ã?ÙúOÖ•ôßÙÌ­@ÉþÀ:„€%Üÿc ÿúðIaÐÓ€ëB‚X}¼°ïý;õÜ¡*¼¯©>7“ÉÄ:;;²ç¹À6ÌÀ^›t„€!p¢ 1I´žž]H`äD£Z™–þþþ„`ÊÊkÕ´ø" Tà~´üáh< ¬×(>\Ï×#ˆŸõYäw<óÐóN)€ÒR½aØÂ @— †$tnnO$@rå7aåR~Á|_—jÔ¿þ¥Ïá{?»;÷`xU%èß‚n:ï'+ä!}¬ m˜p¢V7¢3Éâ»›Š*À`»ÄS" 2‡ €TXÜÃÁ~rý§Í< lÙ±/¿þ¶ÈÖ¬.yâ¸18iÚdB¦Nœ€)“'`ܘш'âîhiÍZ:ŽtuaÏÞýØ·ÿ ¶îÜ…MïnÅ;ïnÁ¡®n,æóä?P€ÉÌ åõ~O–ò¥‰„¾ôÛ6¶¿øWÍþ¤Ó¿cøgD)Ào±®ž}ú)¸úÒó1¼c˜ïë8x?ûÍ­®šÿ"ø×=à_wÀ¿^§à_ÎkµîŸgþ#né¿Ø?T*%»sxGß–—±ëá¿sÖEÕøm"û¯×ðW®0°³§Mœ+XöŸËÿ™#ư©0Æ/ kÿ›(Îh,¥‚{o²±‘ ~6›µ§X$Fµþ™L†aÚ(ûFµX,F*rÿ° `H4Ïœ 84 Ú%P@¨8¡«)ðf÷Ëu¨vQ Á8Ž7 à: ÅÚÐzéw‘¼ãXù>gÊNNWõ-¬ú\4j%LêðÊMöeF®ø¾@ˆ¤¿_ùâgñÝŸüëßÝîzÅ4ûes@û…É,P'C9 %BFPüø3¹ÌAªT0yãD^4!KDÉ# ,i-Ç}_ƒ|-ÑKÀâåôÚD"d²Y{ãO9Ò݇·7l)ùHÃÛ[0yÒ8Œ3ãÇŽ¶·QÕ9’ù´·µÚ[»$ð@iù¨pîãw(ÞÁI^æËå˜É`²/‰®înj]ï£ó,¬n¨ýÐxw(CµH/ÉòËš~‚~þgÍWÛ_ø;€V+šý˜UþÕ6#ª,\vá9¸ô‚³Ñ’ð—vïÞ»?ýõ­.·ÿòàîý^§û«èP¬ûWË"äsçÏ™óÎ\Êžë™CÛ°çÉÿ„©Hÿ ôG ü¿žÿdöŸÉýeí¿ÿõ«ˆÍ¾ƨ™aÒÀ¿Ú¸<Ô«8Á«,Êú«$) Ãh!Ó?‰m(ùI "ì |NRK‡„@H4,¨qp1iÈ#’L&Û¨g¦Ÿ¹F%³¾ IÔ§³Ú…¡yú›X7Êo\ò½(xµ™øòï óÔgœ,·Ëa_÷¨Øë”o®• ûÌ|/úßø¬\?F^öUh‘ÒzÖáíø×ÿþ·øÑ¯o˯½eÿMÑéŸÕVTµƒÂ‚%ñEíl—gjŠ9¨Ù¤çe]^&‡òÐ꬀Ãfq »×@Xl¡p·æü¨–Ò^ ¨ƒ íÞßÙŠ·í­ÒhkM ½µ…D,Š·¨SAkK‚9œËÏ‘¡Îéû¬tZööõ!Ÿ+øƒiÍ[hš{?¹žÀåùº|ž¦ÄzRž-R¦ÜmûŠà^ÄŠ.Ð_|fpò¯ü‹zÿˆa8€ßÏé_ûðW]ŠóÎ:ÍׂƖm;ðƒ_üO¿øj à_ |½zY7åçªû—Ÿäð\pÎé¬%bÉÜË$qèõ»Ñ³éî¢ñŸýU1KÉ¿¡TAÔg3ý–1ô³ÚrþgŠƒ“#çØp f' ³>øáb¿Äž7£ïgæGª-Õ(\%¼¯çW2 o¶±7RÀ‹V€Ì ‘Hhýýý>g–’Û¡ $šc®ªq“˜P‘T*ÕN€=ü`‚ðjˆ€ZkúC2 u7ÙlÀ[ð^¶>…üÖ[š«^h& BjÍOa¦{0òŠoÀhYz2°â¿ûäG0úÎxð©ç‘Ë䕯ÕEs@Ö!€H[v¯Ÿ/@3‘Év2ù>%µdr•O7ñ$ %ónYNGW»AT&”ô=ÿùÅÍd2Å6½DábŸ åŒÑ³ÃÀ¡Á‰€ üP‰0ð‹ÿËIþýb~éa&þößQ'£È_j¬þŸT~˜Èø‡à?$šüû]¤Óé6ê™9T€¿Vp_m A9§Ñp„ãXßc2ÚŠøE_CáÖç€ô§#€†b{ÀFñ²«DX‚`5ûâ{ ¤Þù5Ìþè¼æ»ˆŒ˜èûÚ7Þp5FŽ;zÝÝI;HÒwj¨ @ç$Ëe30+ÚY–»U`e×1*@-àùkÇvÒ) €P@çï-KQ ‚bÀ¥{YªÝy¸ø^º÷Ìâ êY‡Ç]ºò™Å/ãþþ"¨»öKèÖ‹í* _ðüq‹—LÀ]Ÿ­)Aÿ r~©$ùgmQï/åÿ†¸Ÿwê01aüÜxýU¾†vr¼òÚ[øÏŸü 6o¯ü׹韔ý;uÿ²å_„×ýKo˜³–ž‚‹ÎYæûzéƒ[±ýÞ/²é§‹Œ‰ô_«cã?AÀi2þ#ðoÄ\ }ìi0&M²v)€«ÿòR´)wÅXAí½åóå%x…B›‘"?À‰K„Ô}ïµÚš»À!¬R¤Ý@$€¡*t0Ù- ³ý¸ãF_ó}DÇÏ÷}íË/^ŽñcFã÷w?ˆí»öŠøÞr‚_$75! D½Ï §$D¿¦d¹­b[AuÝ:zSÉÌkÓ—ÔæKè/}Ë’p¦ƒå7WTx Ÿ¤$e4÷]ZÉQ8¸^½â÷Ö5¸ã;q»Ø nÀ¯²'P–¿ø—Y"¤l.%ðW%ÿŽÙŸ=!çÏ™‰÷¿çRŒíïôO}¹Ÿ~î%|ïç¿Á®½ðoðŒ6.ä¿S£(„‰ÜgønÓ?¯öDŽ\¼|l0Ròzùþ.ìögHxÃWúoxÚýÕ•ôß•ý×ø/Jÿ5ÇõŸÛ¾F'-ƒÑ9= Ö› ¶ rð÷óþò⨉>¹Ñ:“ÏçKž§’ôºMY~û2á€Eð·Ì@—méÒc¨…$@H4ñ<Õ¤€L<ÆR©TÍ›jô@dùåC ¾ÂQ?ãx€ùúivà7åtÄ—}ÙWþŪz  5% ÕÜ…+qðî¿AçßEü¤³|_û”“ç¡sÔÜz׃xsíûd¯˜ÿXBþJÆu @òƒfR xŽxëà¥:@+ªŠô“ÄæR! »~`M}†ÅÉ)M‘ª¸ çðkåÿ£ÊDúŠ"¸we­„Yp$ýRà}šà‡BAà&he›n]þu—ä?"Dâ9Ì—ÃÞ.8{).¿è\fé72™,xôÏø?¿øz“¢×ƒwjä™­±jþ]¤‰æöH(&œ DI{{.Ç£üËèD( €rõÿá €æ_êõl6«‹Œ~Kooo{0¶€µ´ùó²…Ç%pG8†úmEtÉÈï}Ö®?±í–j.kTxÝ,$-.öãûJ¡k5ýé¯1ü’ÿ…¶“ßãûÚS&ŽÇg?þ!ÜûÐx䙕È:¾<¨¤zw ì;YïæP êæ£PË$)ÀËrk‹Ÿðºü±‹Ì‚ã# ¾´—$ð=•9 U`ì‘¡ù<òu÷]z)Ùp"þ@«dý £èò¯fþ‹’^ïOÇO[[+®¼ø\ÖÆ.î“ѦÑÝÓƒ[_¾õnäÍBü »ü‹.²Ë‚^ßæpjkD¾ß¤AbÄiýç¨$ìí¬ÓNƧù·üËÚŽíw}Ò94é¤Î]ÿKH=µí_ÄÕöeÿ¢ã—@6. šô6àÅ4^ÌAóÿmœ“ˆÇã1¡z– Ô>BÍ;!¢»aO„xooo«è‘yTê±è ]mËÀp„£Þ†ÞÚ‰ÄyÿúïyZzƒQ²g³e™E9\“2«J$€¦Ÿ Ë,š©Ýèzìs(tíDÇyŸõ=±w´·áÆ®Á´Éð«?Þ‡þdšƒ~z-–hn§ñ~÷Ô) ÑÕCº†é>àZ‘ñ[î´¿((ÑrDû–û¥+šY˜",=‹kå ×ß©žâ(®û>9 ÒÝ«Ôú«Y ¦cÑ¢ä_z¨’ÿéS&âêËÎÇœÓúÞýX›¿û}JùüK·]ãŽÿÌ@‚­~ ÿœXDî?ÕôOdü¥A¢4ý£#sÁܸúÒó}_¤ÿÛïù'äú÷;²C’² ^§®ÿã?Íqý/mûDzÿ£OAtÞÕáDl¶õ$ÀÀÏõ(Ηâ1Z¶´´°NÖÿS74ºô±>gšð$?<ÙM©»¡‰ÐšN§[/XϯNêJ“|°‚àZ”µ”„£ Nƒ êqœ¸ø»H?ð~'äõÒzS  ‹ÚmÝ„R§ ¤óûèy…Lz^ø*òÝ;0òНÙ1a¼¤NØç½ ÆÃ}ví;À{³kÅÌnAHƒ)óÜlj€! Î}@pÉÔ”u)®\¿'R2-÷#eŽS~·QÅáóQË|nõ n€¯‡@¿ð_.ë5x‹?GÒ®dýižžyÚ"\qñy=jdà{mܼ ßùÁϱꭵ£ëòœ þëµÞßü{Mÿ¤ZÂ1ýõ^S'OÀ W¯ð­û·²)ìîWèÞp'_:5·ìßÒ]oé‰ñ_\1þ³·ÄHD¦Ÿ-ÖNÆ&XKüÚôyqˆ_½¿7†/‡T<tŸ$ éz6›wvvÆI @µþüîaÝÊá‰#$škŽøWtš¹\®5•JµH øl _®l ÖR\†£!†nƒÓÎBìœï ÷â?pÇ|º›ÿæ!Ø÷ÒÀO$€}=ÏÍØy¹€™Bßš!×½WÝ„èÈ)%Æ¡4f4ÿúÅ¿Åmz/¼ü&rù<ó/ê&U°ô5u0EÐÑŒ†8¢ƒÇS¿” ÐKã$«JB¯ÄÔúX¬«A€'ëO€_weýeû:'ë/€,ýÖ×_³ç.[â fåX¹êu|ë? ¼ðOàYÓyV˜w`h ðu*Y—I¢Øo´ÏFŽìÀ•‹ ãÆ–$³€žÍ/aÏÿàjù' yÝÏü¯îΚÿ±@ã?}älÄæ^NÈf_g*$ç¼d€×°ª9èÿ¾„o"¤r&É¿P>3üOæÑh”u0ô oÂ;zzz¤û%;°MÓÔ …B[ÂKT;Ù*u¿ ’t¿ViþÃ1˜c¨Í µH ¢ Þ ³{ ë~ÄU,£Ö<$€ô°„ìŸý‚ÁI€œÉƒW†È­2ÛïÃ?îÆÈ‹¿Ž–ÙøÎé‘Ã;ðÉoÀüÙÓñû»Boo¿ó®"üõùK5@ P“5l0ûM ކLòÂQ0„Ã÷¼ ‡C˜Ö•ÍúÛ“iÒ„q¸ÁÿDλÙlw?ð(þÏ/nF*“ák™Ó濹ýúu­!Á¿&Á¿0ý‹ÒñßpÊ%$aOıüŒÓpÊ¢y~'dïÀÖ?~fÁ,mù§/ Ù±R«Sé?ùŠ0 .²ÿªñŸÁç¶ñˆÍ¿.œ”' 0Ðóœ·S@%@õ>{ ŠJ@€rþ‡PeúÃGH4ß\ô8F&“i#£Œ öG·¢D –÷ lj¬i@|é§îzæžG¸ @cþöpô$i ÀÁ™ŠqœáÒ0B†€â~Nùƒ¯âÀ}ðSÿ#/ü‚#–ž&(¨¾àì30{ÆIøõîÁšõïrÀ/À‚A™þP €AGæ·Ð„à?(‰_ÖßÕ«> ëOsè’ågà’óÎBGG{àûQ½ÿ/÷GÜýÐneÚü5øgDI”Kÿe§]JÿímÙ’…¸âb·ûBªÛþðd{¶¥ÿA-ÿà²ô¨¿…Fÿ±Úÿ‘ Àä€E]qÆœŒÈä¥áÄé#&#~Þ¿ms ØYΚeµÅ1­ùxÓh °Ø ˜¡’F‡ÿ‘”µ®BѽکcMDïʯcß>Ž\÷îÀן8n þÇ>…\³Ã;ÚIb9®Xg4ÉÄŒ9rkºÓš‹IeæS~N½è4Žãw™ƒ3yCðï|Ëë:—Ûs°j ‹ ζ˜½E™”?Æ.£ Ä2€ÎçvlÐaϵÏüÕpí•—ÿ¯¾±ÿüÿp]7Øû²6w›?4XÍ¿ø—nÿüG™ã¿aðu†Îy‹æÍÄÞs¹ÿqŸKc×#ßFïöGõ2¢¶ûSêÿëRú`üÇêþ5^óo‰–̯¥uâ§ýu89›0žósý¯°FŒ&}È7 …u0V`%ö5è¾D"Q ø‡cG¨8>Õu`‹‰Éf³möõX9 S­ôÿh€V%sÁ\7G0÷0ÆÎEâ’ï"õÀ‡€BŸ›¦€B ™Ìõ¾Q”êÇ“?¹jhðîrö÷å—Vß_`$H™Íwa÷/WaÌû~…–i§ÛqcÔ÷=®»z̉Ûï ë6la`CgÞÛ_DìÈY+PÖ_«Ê$=~Œá¼³CT)¢9õE“?Gîïdü=ÿ"ëoØ÷/=eÞwEyàO’ÿ;î}?ùÍL¥ÄûU˜ýñg6 ø׉Àpµû‹rð/[$ʺÿÓ&áƒ×^ÉöuI\fphÕí8ðâMlÒ=uÿ²ö?R÷-ÿäžÑEýTÔþ‹MdÿmCtÎ{¡µ 'é ²U£®51äU“úJ•³ýXÜž—Q欋õMób"—„>á¹¾yæ$¸! ]õ÷÷³¯G*MÔZÁy9Ó¾ÁnßÊÿÏ¢^ëÿ™|:ýÌ["ûoI%_2}• °Ð%€hkE‰!]‘¹2ܑ܎ýxºWþ …¾ƒï1wÖIøçÿ÷¸áªKÐÑÞÊÀ;“ÿ듌˜°ÖT*Õ*”5·ôÞç§"¨V"t´ArH øW¯‡û|ð‡Ñ1ñsþ™ôaXûÈE[g(˜hÖÞNâÆ$$^“$€(•ej€H_æ-~¦Î+%Ïšè}ëHï]…áËþÃ_¨˜>u2>~ãõXºx=îzðÏØ¹wŸI:7 ´¯S¶_³ƒK·\DÛDÐ6YÁÁ'¹Ž£9njþÒì]ŠÇT¹?̓% çbùY§aöŒi´Œ®îÜ÷ÈøÃ]`ßÁÃŽä_fý!€ä_úm4$ø—Žÿþ1‘õˆšé»pÊ¢¹¬SBÐØýèwqè_Â*䨾ˆxêþ#F±@fýëvw±ú~µí_Q`‘À>6H[ò)hm!ž:‘Ö¨Uº^5@Ò4ˆ *Èd2±áÇǤô_ÊÿIFËy„kH4 /Ïç[Óétë±:ÐËåÌâ†Ê@$ÇçÄ–T :OBüÜBæI;Àîzj¼\‘@ýºÆåý2 (q4)„éëDí¯U û¼üZfñkå÷¯ÄÁ_FjÛ'1ò‚¿Gläß¹%‘À™§/Á¬™Óñçç_¶·WXY€ô ÎLöÌ$¶&. òt J"à˜0.C÷?0HÙÿdí¯äêÏþxý¹ üU"@—m0¹ÿ¸±£°â¼³±xᦞ)7Þ\³ÿuËXùê[ÈÙàÕ1úÓ|\þY¶¿1%ÿAàŸeûYûDÙîgþ¥i"Y3¦âC×^“zåvì}æßa撎쟶¨^Ìø3ðoé¿VÿÒiü§ÙÀ_–u µÎˆÎ¸Ð¾ Oî'X¬WkŒîM&ªuþ^5€_9ª·9B¡`ãühœ²ýT: üöÆŒm,†]B 1‡˜ NÝ‹l®Ø¥-•J%äÄÑóï—Ñ/—ÙW'«Ó>ÈÜW³xó2‚ºOvÂ{_Ь€%¡Ð dÅ@¿Ã@€ÿ±Ü_ƒÙ‹ö¨I€ '#vΗ‘~ò¿éíN9“Ì»H7F‹@¦P€¿ p-±™ËïV°ÿ7$‡chÃs$×ü Ù=¯¡cÙg0lÉõvÜé`FÁZ˜-=y>|â9¼hÇœïPîK@`†tºÅö1© “êÍÆ#´!{ò ŒAÿ!ðwÌèjþ4È îT{NŒÝYv-ëéíÃ÷=Œ[îzGººKþ¤¼?ÂkýYòþeæŸÀœjþ Éšû'ŒÅ_𽬔Âw?n|;ü'äS‡Ü²å2*¤ÿuÛòOÿ$ý§p^ò&ýWÊDýâôOAK 'ó 2DÉq`Ì/køƒ€¼¼]ΗÌKÐkˆ÷¶´A¿ÖßßOõÿ ªùÊgM–÷ÿ0«ÍGôôôèRú'&ˆ‘N§ÛÈ C ’©63_”G½¯Vö°ž€[8N,rm(¥ÈIç"±üßúóç ’6hÒ…|VÀ'&g7™¹÷¨?@ÞÛ–Ìì+åÛ–èƒÍ®k%2…©å1TÕWáðï ã£uÙW5=°SÀIÓ¦à“ù~œ{橸÷á§°qËÇêšî|6°ò€âoKåÌ­¼P^ÀŸ*;ÔßúslÿôÁ<¸šôWüÕš~iêGjaGuþç¹ãÇ.+÷§±rÕëøímwã5òÒYÑšDò_üØW3ÿ^ð?zÔüÍ×cx‡?ÐMïÛˆmwÿ7d{¶8-Q™ã¿"ÿ/1ý«k¢‰ÿüšÒÿ˜½fÆðÿLÑâ©‹´ôq¿ó^-†ÒA‰H?Ÿ8-b¯…qÊø c@ˆ2æH$ÐßßDh>Ÿ…H„P1åA«Ð$‹I¥RíÙl–zdÖ”¥¯UæÕ÷kËÃÑh$ÀP‚ºÈÜËÈ}™g>Ç'Pø‹[–h¥W$€÷4)U .R@|L‰Ed—¦0,èâÒPÊL®°r½Hmº»w=ƒŽÓ¿€ágF@Ɖzž/Y8sfLÇ«o­Áý<ƒ= _ùç3™„YÊ -ÊZØ÷éyáøëÎCxŠh¤ó„þ²Æ_mççvóç5þAÀÁœ¸jÅy˜0v4b±òrìã×·ÞÁŒ4ÉáŸ&5ë/dþM"ù¯üÇìõCÿ´PÑOô7f”ïëæzöaó>Ô¾UìõuøGŠY™ù—à_×êt™P¥ÿú™ôŸÀœIÿIýAà_똎èìË ÅÚÂ@¡Ébž 8Þ{ûÑbç •°~†‚{KÔŸ@>Ÿ×e½xâG9к‰¨B [º5ÅìF¤¿¿¿Íž ±x<î’ÝÔÂÂUÛ¢¯Z2`0ƒ¨p„ãxŸcD¾×~ý²Ï~Á‘Â33;ÖÐä€õK¸ËÜβܱ¥%IiFˆ®ºVT䥂H š©}èzþ_зæ6t®øÚf_hHõÎçy:#^xå Ü÷سèíK:þ”·8G”+ØûÙ~#+ÆKØ›šÒ9À¬®sÀ1YŸêx ”Ú¯¤¤ A¿låÇÔ'Ô»¤þ¢Ÿðç=ÙÁ€?í©©“Æã½W\ÈúÑÓß•DxÝóÀcøý÷bÛÎ=E‡èåþи’ÿN®ŒÞO}ôL?Ö÷u é^lþý§‘Üù”ÓUJýUÓ¿ˆ¨ý×ëÕôÏ+ýw¹þÇÀêþ©þ_JÿOûèÃ'…ÁÁ‰8p‚zé¨ìÊÔôWZ7Ë•(›aãæÄ£Jy‚, 2ÔkˆR ÿ!ÐàDó‚ûà%“Év"‚jèý&r¹É^ "üÞ»šç„À¿9O"áàÑ`lÑûì3P™ç¾Tlg ©¼Ìª×)  ‚Ëò'\÷£Ô¢[€¦˜äò^B@™ûÜá7±ïöëÐ:çFt^ø%ÄÆÎâÙIŸ1¬½—_´g-]‚'Ÿ]‰'ž}É´³S9`1°ã¢4@v`*AП•+ W3‡R=Xçmÿ€A*hRோM-瘣?û Œ¿a8 øÓùݾ¸Ѹì‚s°ääy×Rú›×W¯Åy3^ûüùgrvC€|CS²þnÉ£ÿrà?ZüÑ}èZL4ÁŸLÉ¥±ùæO¡gËýl]`ÐY‘üG… "²þNö_ké¿Ìþë1ë?KÿÇÍÏÝ'P\Þ£Àï}ü’~¾_døŸÏçIélÈŒ?ê@*J#2 }B ùæ«d¼ÔÛ±ÞÞÞvì¯&éÑÔÖùr÷ ô=C ަ'+(¨Zx= <3/ü“P°\SÀÊ×= àÅm*øwݯ|T©jÛ5@A|Eùu J‹Áä;·°mÄ9_EÇÒ!6r²È`•Žáí¸îê¸ä‚³ðØS/à™•¯¡'ÙÏ MµÏ‚Ïè3†Î;Ø'Ã@ò (XÅò® {\“Ý”/;¨d€¢œ¨Wð?X„X³€~v<èüèò¶òÓøg ß(•þÂpWSjjÈëb¢ ü©ý©‹+°\.½û÷ã—¿»?û"Ò™¬bò—Ã?š,ë_üWÊüê_w%fL›þ3ØrËçеñN{Q2yÍ¿ÿŠãD8þõ þ½Òªû ŸÌþø—$)EFÎå®ÿ±ö00GUñQ92 š¿ ”œdÿ±D"SHM¶´ï/§ÁEH4æH&“šì ØÿsßpÓL€Ø±r,Ý@&cµ‹W40! >a>§=U£‹®g/ËHuà$€^÷J€Zðœ·$À« €‘1©Pȯ*—™€ïzáëè[ýkŒ8÷ŸÑ¾à DÚGíí¸ášËpÙ…çâ‘??‡W­FWo/ï I 4°NŒ”°A¿Î2þ`d@^¨H@뚪 ¹[óć³› 8¶^~µ¾CÉ&5 èŸÛ›í/‘ù;&ÒðÏpˆ/ð'©?ÿ©“Çãü³OÇÒÅ ªØ…:Œûy¿¿ã^t÷&•Ï\líW”û ‡(µþ jôçŠwþ;;‡ãƒï½ sgä¿o 9l¿ëŸqdÝ-°òœ˜ÕèEÉ?3þk$ð/¥ÿäð¯Jÿ©½Õý³óøâ@95 bNÀ˜Méý”Áµ´ôK(úý½Ÿá z›€~.—#€(õ¿.U^-ÿ!ÐTóJý¿r_"“É´Òd¨µî¦Vp.Œ8JÀ¿_Ýt-µÔ!ðù ·/ôˆP}ñ_„€êÐõº*ðýUÂA˜.H  IÀ/Ô:©Äã–)J¬" Pè݆ƒ½ožg­³/ 4 ¤1¬½ ïÏå¸ò’óñø3/â•×߯Þ‡/8`T'I?Ù12À~ÈtT¤à%àªf\Xô €R"0(d@OÍAkýWçkP9Ð_ZÛÏ%—Ì_׋­üR@÷þñD3¦N™KWüiì³ã—V½Žþê÷8t¤ÛEÑT”û7IÖß þÁ¢´ú ¬ùÑ®^sg,d&vÜÿM|ó—L IÃ?¥ÝŸ¬ý—ŽÿZ½ƒqƒ×ý'ÜÒ"´¢ô?¶ðFã…ÁAr1ºø÷«×÷«ý÷úU£öv0ÔLÓ$€˜<ª• ¸î?!ÐØU*äe6›e„—}»µ¯¯¯•Ï–©^~‰ 4Ä„ lËQÎ忚úþ2“¸äþjî Áó±[üû}ê}Ÿy{׳ãÈ àv=»òë6XÌ8’øz!ªÿªô¿ÜÞ̰Ov `YÜ4‘]Úeö<ƒ}w=ƒ–×aø²¡eúY0ÁRÕö¶V\{ÅŸü¢sñÌÊUxõuزcë9ÌdÑï†mX\꯱¶`\@*"X‰€ipS6L`‰%K\²0`B Î†~ô héU‡ëzèç’zúš#ñ—Ù~C/¶ôSýxÛ*‘yW€GG;æÍœŽ¥§,£žAÎþo¬~¿¹ílܼyX”þž~rVþÝÀY/ø§ÎºØ¿*øG£žV|‘>¼ï¿f/˜øú»ý.¼ò˜™^ø*uÿ†RPÿíþ z±ÆxÖŸZþéÜP3bÜõ_ç¦ÆØÓ™¾Z´% ¾›xT£&¬ ¼Æ|ñBñ=ŸþVþ½Ä2îTLüÊzÈבkl&“‰uvvƨÖß¾NxHØGF£Þ¯ $B ñç­˜ TüB†­©TªÂ Zi9æ¯Ö£–'¸G8ªžÌ‘'ì^våW˜ÄœeêJHº/¯ãrµý_5 ?h«ÖÀ䩤@¾?Å©Íw£ÿÝ»Ñ6ïF ?í#HL== µ¥%‘Àå.ÇÅ瞉—__ƒ×W¯Ç;ïnC_²Oœ,ûä¦1Po²V¼;@‘ Р3G S`dÜhZœ 0 •†¥sÐoA?S¶"°j-8¶ÙspŸºüº 9à7ô¢5Ë䓤_Ü–æöïÉöóþžB boíímöñuÌ›…93¦1_ŠjF:Á£~O=ÿžYùªû×P?ô²uþ™1PÉN­Ï.³?ÖiÁY~C€ÿ(ÿtŸ þ©è¯þâ=˜oÏó ±ïÙ_`×ã_³Áÿ^þuîô¯‚§î_Ù­êþ¥éŸÚò¯…]:Ù"¸&žƒÈŒ‹&MÄ‚Úu«àÞ¯½ŸÔ¢4¥Îì2ŸÏGíyMxGòæ ÀJ­8P® @ @¤‚eY!!õ=Uy'M†¾¾¾a¹\.ÞÒÒR3À®%ã^« h5| Î¢áh üMk$Nþ V“™]ù Tö %/ Ì¿kw#_¯äßÓÍÏìUoðL›)‰ð.šððªè~F‹¤÷£wͯÜx/Z¦]†a'¿ís/¶ãßÖ²Çéä cÙ¶lÉ"lßµkÖo ¯¼U,‰4w"TÇhyÈÖB°î-–©ÕXÆVU¨],oÉÜ œÞŸóX«$ø×Õ5ëL-€_‚vC/fÇV~ÒàϧÖ_×5—JÀþ‚ì™2i<–ž<³gNÃÄqcHžZÕwééíÃÃO$>ŽáÐíàvÁ5ö GîÅÿfj;þ™'€.H®p•X ¶/€·­Ÿ·`(‹Jþ^"€}kÕ(P!躙éFÿ–ûÞù$º_¿m³¯DÇ’ë1¹ü ÎFÆÁÄñc±xá>Òƒõ›6cå«oaÇž}ÝÀ<>)ž‰DD‚ñ¸‰D´X,V® !ÐÐàß;ᢩTŠ rêãáÈ_nkÐ Ä”á ÇQ.°³WØ1[r/| f÷Û¤0€kÁS¸ …ÛÀŒãfèÀþ`šÇ(0™ì¢ ³×¬4 —Dz×SÈìy=oü-Ó–cøÒ¢ejeÓ«¶ÖV¶M?gŸ¾‡ŽtaÝúwñìËoØ×ƒnåÃ;_ y$údÉ€&繺J Xn•#ŠDçŠ×5ﺫ\¯D”# ôÁXsªþAž+@”ÏUkÚ½†}ššøK6e ÐO»göŒ©X¶d}9©FZñª×ØL&‹Uo¬ÆŸz¯¯^‡#Ý=*X=ðuþø7j[¿r€ÀÏì¯þ)ëÏo남¡gÆôÉLö?rxGà{ì|ðÛØûÜÿD!s¤ùÀ?kjª÷·Á$ÁTü™ôß~Ž>f1b ßÇJÂŽZâ?¯ãÿÑb† ÷UñÕýçóy¦õû‚Щ €‘H$È P³Ÿ¶ €¦ÿ²@^§û===í†aDdæ_íÙ)Í3¼€»R ¾:ñ#ø;õW2©4ÁY=­}"W>Ê õ@Ër€Á$K< iÕ¯ë÷}› ü×sljȴs ÅÿÙþ'ÌýÏÛ WgÁªÅ mÄQ@^_€A0ôËöËnn%Ò˜_ƺ>Fy¡àN?üo"ÀÌ#×õ6rÝk‘Üp7¢£NÁˆeǰ…W2†rƒÖC2£mò„ñ¸`ù¬/ûÛë7bÕ[k±gÿAY¬Ìçˆ%¼,F9È/®Ëì«H•€ ¦TR@q[Î=&&PJ¤Z@›®2u?z8ŸÒPë®Õ§VÜ«e”|査)B)È—÷ÍúŠÙ}yþa¥€_žËTÀÏzm'7Ý£(pÊ1,¾é±…sgbѼY˜?g#„ñXÕûÂ>‰GÿüvîÞ‹T&ãþºòµfü›ø;±„ø.AfLú¯˜ýéŠJcѼ™øË÷¿‰Dð\Þv÷—±ÿ¥ï1R°©À¿œ!,óŸàuÿFÔ1ÿcÒþísGûDç\}ÄÔ0ÂGÅøÏ/Î÷Æ”ÕÄ…^³@õµ%`$¹ðPñŒ÷¤ˆ‰¿q<Xäcš|átÿp„@3œ5EmÊ ,ãétº•þ£‘ÐE)@­D@íû„ Œú‡_»‘D;ÚfžmÇi¥áÆø“¿è›ÈÚkaÛŸX˜™Ñ9™Éˆˆû…/€•sÀJÎÿÇKÅÀÏ(Ð48@Á¡(X~D€ ºSûQØõöí}‡ö…FÇâë?——V”ù’$7n±kª/&é÷¥‹ƒ`í†MxkíFlÙ±‹õj×ì7¶L·ÑŸÌê«LJD–yú|¦( ˆxÒ_@ú ÅàK’* àdô­Ò Î‚U–üÑö}w›å;pW÷êš$Ÿãù€¬¿Ö€¨fûUɾî(4ß ?È,–ÌÿF–Ü_ü D.š?'ÛÛܙӷ¿!²ÑÕjWµzí;¸ãÞ‡ñüª×‘IgÝõýìs¨ä{eàï"Müá’ÙŸüÓïF¥gœ¶xÏe¿j[ïü'\õC˜ùTs€×N4¸Û¿®8þë\à2ý‹&™y"ÓÏ OÀá(OÆ”—à AªU9àM(ù© |’O¤ˆÙƒÚ…B!'»üß§`¹ëá €úÉdR²Zœà½0¥  ­¿¿¿MFª  V€WKG€£›uc¸‚àpç±oå­8ðäW€ô~Œ¿ì?0rÙG|I}äTÄ/ø2²/A~Ý/l`(A8Àt0€)–f³¸ gò*F­—éâˆÊ2•„¢Ð`*­%À¾[!+¹ }¯|ý¯~ññç¡ã”"1wôöѼov… ‰^oÂø1˜8a,V\pú’ýX¿q3V¯ÛˆM[v ?B®P@.—ReÝŰÈÑÛÈYò>¸•–CPï!TÇKx‰• (ÉüW‘Ùñ]O= ^{ùêk«kž Sù²Æ_}â>)ï×Ü-û4™õWjøKHrË ø#./ïѳg`þÜ™Ì9žî¯ePæªP0±gß><ûÂ*Üóðز}§wFeþ|§ðïª*DII³gü½àßköGÀ?ª:ü;„¯÷§ß°µµž» W\tnà{Ò=Øzû—phõo`²lÿôаà?Àô)Xý?9þMÿèX2¦\„ØÉ7„'àp”“jm­Á ö½€¾\©÷oU@6›1"!ÎKšTЈF£ÞV€AmÃ5]áƒ}Ð …–ÞÞÞA H‚_ó º†‰_-[x,Áø‰úCÿ…úf>‡]Oü_ü_Ðò},=ôÄ?ÀJw¡óÜOÁhYzì¶t"~ÎßA6ÙW¾%Lÿt¨Ð4™ý/S`Uš#ðG7Ψ2 Y +D@ÁCèPv¸ÍbÏ"ùçgÑÿL#n|Ññ Ü5þU *8}É"¶åmà¿wßFÃûÞ}‘ÍåÊd‘Íç‹Ä†ðpuåp+\Ä€ÅՊ䀃q“Á"I ÷€ùÕý›5FºVz0ùf<©ß©éGQ´©ÙUί•œ‡Ü~ú—@$™õÑï4}ÊD,˜;³Oš†áô®¦ÒôôôàõÕkqÏãµëÏ^õËö3G¥‚T(°/ÑÜÀ?ü™ýI÷ÕìoTç\½â|,;uQà{äzöcÓÍŸDÏ»÷Aj„%øú´úkLðï6ýÓ”K‹)¢Œ 0Æ/C|ɇC<ŽªI¿øy(ÜÿýL½÷§Óé()(ÛŸçN¡ RÈûƒ Ã.!Ð8€ E÷µ €Îamödhñ˰«õô•Ÿ_¹ûÊMþ.2Ò á8Ú‘ïïÂÖû¿#oý†•…Pú²YÞ³ò›@¦ç}†OOf-Ö†Øi…Ö>Ùç¿ 3{¸Ø&Ði oPI@_/ð÷“þ׋" A˜D€Ü¢’€?.^‡ÕG†A±‘Q3jÿ%'KmLž8žm¤ `¸uÇ.lݾ›¶ìÄáîn$û3èO¥‘Îd=­-¦òP4KƒÛ@©«t]G‘ͱ”l·O½¦UrÎQ²ªü-ÊÔö+ WöMлÿŽ5Þì¿$¼R~×9Êc„ÈÛ,ê̤¯¥%ŽÖ–Œ;3§MÆœ™Óìëcü»ö÷§ÐÓׇ ›¶àñ§žÇÓ/½jÿŽýÊþ” _ÙC2ÛO· ¹Ot‡Èàw¢é¿ó[‹ï¥ÖûGXö¿ŒÙŸø©ü溫.ÁìÓß#µo#Þ½ùãHî~ÞI6øgf1ÅôO”q¥îß~NÇTD\­uTx"Gµø£"vðöµøDÉ,¡P(žó|p %>³Ù,ÿ‘ö‡(fŸ€Š]„™`H„@ýž'»»»uªsQ&–žÉdZÒétESÀŠ4HŽ´Êr÷ÕÒz0$ÂÑÄgS¤ï–{¾ŒÞM÷° “¦4tÚ2Déþ7¤»0â‚/":zFéܱ¾èÜ+ ·Aæ©…Ù»‘“N;oÇ P–ä`R^  iE…t P±°…bZذ},¿‹.. «è! )†‚¬.Ýh³ƒæ{·µ¡õ쿯h 8cM2³NšÊ¶pÙø®=û°s÷>lß¹‡™Åõ¥úÑ—L£§'‰<")`\öÅ@ËRÍê%  ¹ž • ܬ*÷½wx]ýÎEÜ;+eªj–X‘¨x÷·)úãñ¨ ôhkmaý“ÇÅô©“0}Ê$ŒÑqTÇZOoî–;ñÊ«oâá??žd_ è—ÿâw÷Ëö bàþαã©÷'²Ì•õ7¤ â´O¨D㺫W”mó×·íulúÝÈYï€ÿˆúèoðO›cú—ð€ûÐ2Ê>o\ cÂâð\Ž‘už­Z|P 'ùø7òù<ÕãÅìµÁH¥RìAiÂ$Ð[à%¼~!ð €úžw*SÅ–|ûliO‚Hooïð\.—P]ôƒ€~ÐD®uû½N9æ°Zb"ÿþûp0ö‰ PóuÃQËoi"¹k¶Üõ%¤÷¼À‚LˆFð—']Oo¸‡S0òÂ/#6ùßß͘´‰«~ŒôS_‡¹ïY‘Å6X¶S¶ä+†, P¼*¨TàßHܸæ!1 ñ]LMqv'äsø>²÷ÑJu,ƒÆL´Z'"6óBûzu5àµÃùýí”IØvö²%ÐÜ·ÿöî?€=öÖÕÓÇ|zz“豯S ú¥¥\Ÿÿú–ó€šÁ·ô{,ëïÞ÷eÌž<À¾¸ïKY¥ŠŽM¢Ü_7ÐïèhC{K+†µ·btçHŒ; ãÆŒÂ¤ ã™SÿÑœ_è;P›¾={öáÝmÛñêoã‰çV"MþA¿8G ЇØPdþ.à/ÍSïÍc þëýà—uÿŽñ¢böwÝ•“Ü7ð}ºÞ~ [nÿ ²½[…¡Á¾J4Gæ?Q4ý³¯—˜þt¢s.OÆá¨i®úu«&f÷ûÛj±†Ÿ_€Tþ˜¦±k‘Ù~‘ýç&öH$Z?©°üA8B á€ ÓP‘ªD²Ùl¢P(Ä$3^ÕIwŠZƒ£}p„£á毙G÷¦—±õî/"ß½–¡2믒N)€8måw>‚ÃÂð ¿‚–Yç¹dèŽ9ÎÈiH\úmä^ú!rná_§v˜g€5À0yûÀº4Ò´T:º¯JЃÒ'€ÔAÁþ–g—&]2ÇìvDÞ-š8n߇2×´Í™5ݹ€ƒ‡àà¡.²/{z{ÑÓ×Þ¾$º{“öe?ÒéŒÓÆ@ü’å©|×€Â'_ýÎAEQƒå$íí6¬m-öÖfƒý¶6Œ1 c;;Yx̘NJ •´¦Èù…¤¨Ô¦oמ½Ø²u'^[ý6^Xõ&r’t©ôÓ¥Á磔øûƒ~ñ"Mší÷þÒÿ!"€}5õþtPPW†Ë.8+ì­ÜØÿâÍØñà?"ß¿Çÿr½)YIȵW×üËD\ê2óŸ`D¿¦¬“‰½ÇŸ‰ø’Óq8>o=‰ÃjýÀ¼&€êëX³[õïLÑ)Å«:°ŸgØu "@“‰T´n ØÐK „Ùÿhˆ Xr ÛwÅz{{;ÈøRNDY"à4jfj Qµò¿à¯R0æ}®ìÿ9˜$Á‰Þj0Èt¥™ö]=~/3ŸÇ¡5`Û½ÿ9ýë¢îT^:(%ød'2Õ…C«Ðõðça]ð5´-|ýÄÒl¦Þ>±å_‚6|:roþZ¾['CT@mIçA£åî i–Ë ¯œü¿n‰–2ñ²êPÔû'xöÌþeÐ" {OøÚu"¾àÚªŽŸ£™[µ2¬ã¦u“ŠÇ˜½^&ûSèîéeªÞd?# z{“H¦RÌW€jÕûÓiá3B>_¨j­&,_ÆÀ*÷ýŸé¡özÃÚZÐÚš`}ªÓoµÁ}×aí6ÐÞá"´É¬¾ßgHGÚ‡Gºº±eûlÙ¶ßÝŠ·ßÙ„w6oõXÁ ?¨®Ÿ=Ç#ñpÂ?ð/%ÿ³ÈzYëÏ¿»ÞŸÌþˆì¹þª8õäùeÖÛ,ö>ùì~úßQH‚¨¸à@?"À¿$^ 7ñZ÷àßµCy»?MÖüqnþI0×8¦ö>9ñ¥—FáÇ€ç° ÌåÜ,§0­¦´^ª—å&ÿV½_%HúŸËåZâñxD˜¡ëB Ë•„#$kÞ‰IáŠIM§Ó­öõˆ·µ'ÎcÁzƒ² â Ìþ7&©Žò£OcÿÊÛ±óá„fö»jýø§Û‘b €¦Ô®ëÒÉŽëû·áð£ŸC¾{7†-û+è-ÃK×ø0Ä–ü%ôS‘}å°zÖO®VlÒ€jö%ë6¯”XαÒ8à¿ÒÙ]Sÿ¡à™eÈì·°ì"­" æÎÙÆ¤s¡%:ª"ÕüÖºcIRQ D ™6¿Ï“Íæ²Á*•a—ìv&ÃJ2™,S d²¼AÎ~ŒL©U!u.ÈÙ÷òö9+Ó1”/ØÏË›0 &¶¤Ä”GX"gä“aߥi1û¾x,Š„½q—æ(ðô8ÿü‰»lIp€®CXh¿tu÷2Æm;vaó–íØ¸e6Ø€Ÿ”þ€¿x”~ºÏÙ]Ì7eþ9 Í-óÿ~’Úb øGù?ŽA ý[Íõu;¸4‹’ÝŒ² TÔžºþÞâ*ƒ1€$IN¡çù/#ß½ÃÏÿoˆ Ÿà³"G™y1´ŽIȽòcv> a7Æ}¨m ÆÕì’©b”6³÷ƒÉ| ÛÍyDr¦žEOu’Ç2—lî–M„€EA´n?Ö:±“ß?$Û±öß ÷"Mۈᕟ›g@¿ÀÀ?ïg_@Á> 6è7Eá=µÓË›vÉdÜt‹ã‚@ílÝ Vn†U¢Ù¿=2äß=èõé»ì;pïÚ@óÖí ô“éâÖ{pðÈ‘ÊY~¨ûÊ‚~hÅl¿âup¢€~/ðW]þý$ÿ²Þ?"RòE±lÉB\qѹL4úw­Å޾†žMwôUüG áŸ.ÌW5iüÙÈÀ·î_‚þ(Ëü³ºÕô/1 Ñ“oDdü¢ðdŽÁ›Ïey*ˆ¯wª²?u±¿i,ÛÜ'âöIM1IväèNíÇ{?Ðq»ßûÛwíÁö»±mçnì¶Áþ¾ƒ‡°Ó¾ìM&+þâ÷(¶,ùÉš~( Ÿ=á„ý~ÀŸ•Cˆ¬?Ëü —’ù—“üwt´ãÊ‹—ãŒÓNfÞAƒÌþv>ôU$÷¾ÈÈR‘EY•\sc‘ùWÀ¿®x®4øWÛýñKËþøµ#:ûDg^;á’9^M¼_.†÷S+«XF%h] WçeCz6›¥.1žôçàŸ®Sæ¥õÿ!ø €Æ'êòÏåœYM¸^¶¦R©6­H¹L5‚©½dAÐe¥‰îeêj%Ô²€póc*C4Ò‡v`ëƒÿŽžµ·¸À¿üEÏi]£ÅqZò{Âáë‘ €¼ú˜X_û<ÏnïÜ…‘—ß„ÄIþfXZk'âg~ƨ™È¾ø-Xù.žUcj“檀<ÿ–ôÐxË@žñ·êÞ2Ç*w†÷dÿéûò`ÙÀ?"î·÷Iëx;p¾²¦¹Tí:6ÔG»–v,ÉE2?ÜwÀö»÷`ێزm:Ì\û»{ûpèHò…\ñ á¹aç˜ðüÐÜY~¯{?fú«ÿ%YQï/³ý~’ÿãÆŽÆG®¿ 'M›\öýö>ý3ì}ê&dz6;f´VF4÷zÔæ¯qÁ¿Úî/ævüÄ`L<±“oOÈá8ª¹\m†Þû7~@¾Ú¸4ÈCL%òù||ذa1!ùK®æø Ø0ì 7!d.b²h¦iJ€^Iº?Td-¯[Ž-ô–0„cððp hRGßη±ío ¹õaÇuÚÕr*âqžÖŠ«jVG }R[Ó›§`–²ÿš€Ü ù#oàÀÝƈó¾‰a§~À1thÑyWC9—ìyŠ—ÈV”ígß?‚K]+pO"LÊÄåQMËÀzŠ“Ë?Cdÿ!€¿Ìü;­í»} ŒÉ§×|üÔƒ¢¦žT=ƒùYÈ·`÷ÞýL¿Ǿ¤l>eò»lOæ†ÉT}ö% LÕÜPw]꺟bCüÌò{Ýûù½nÐ"˜ùUš ÕdýÙ%µ÷³¯ËÇ¥äŸþö´Åó˜ÙßðŽaïe²Ø~÷—q`ÕY½P›¿Hð_÷¿R•íþŠ·ùš¦w.@üŒO¹ºÆ„#GùÝå<Çü?2¡’ØÏ NGGÅÎj/|ÀÚB„p„@½ ûÒèBÖ¿ÐíB¡ÐžL&[ÅïL)¡«é¤=À?P¹¿÷yÞ˰6ð ÷päç°ý¯"s`oó§И"ý7¤ó´V X¡f¢D*›î¦YÏÀ¿Á?•þ7&·²‡qäñÏ"»=†Ÿ÷ÿ Ò1¾ôƒévà=a1ŒK¾Üš;‘}ã{ì…y €ÁÉ* w_‹DäX9Ÿ‹Â4Pu¯35€VÝ-̳tV'ëÎüóK­e4Œ“.¨‹cm Çf#uI!9'u'Hö÷£§§‡»ºqè0omxð½éb™|õdDÈÌ ³Ü´Ì fÐÈ'”ë+5üA~àü¥ýÀ iäWÕ¹\’ "ëoIT˜ýfýUÉÿEçâìeK\<Ò‡¶aËmŸGßÖ¹,Xÿ*à*NÿMþèouÚýA€fú7l çþ=´Xkä„cȉ€ZË{ˑռ§Ä@T‰DÂøO“J P>ã¶ €†š€R`QÌB¡ÐÚßß¹ZhèUº'WôAìúx%3ïd “ÁpTM•%}B²epƾU÷`ç£ß@¡÷]—Ù_T‚%ó/ÿUð¯ypª4T3þ&À/ÊõÙ%]0b€Œíçõ¿õ=ä®ÃÈ_A|Òbÿß¼m ¢K?}Ü"džù ¬Ô^æÈm1°dÙï¥1·A~\pÃ@ž”3EbN¶ TH4À)Tñ=`@Ÿ9Ã%ÿçÙ{N Ÿ‰èŒ eíl42k ûÃ]]x⩱¹nïCê@èe¨‹@6cuû8êK¦Ð×—dµ÷è“© n²½SÁÞLaQ/+á ¯ ¬û÷{¿\.GÝÏâ6à×)!ª±uÇ`$@4eF€>]åÿ"±Ä!P_ ßúZOO1[2`bšÝîîîaö$HP[%9É$kN“†®WbÚ¼-7¼==åkøõVê·¬‚~šÀ•ȈJ>ƒà†Éþk1åºfTõ÷6àÙõô¯°ûÏ_‡–ï+šLéJÖßþ …ÀD}wKÁº¸m @׋؛„Άlhß‘Ýõ(ܱ#.ø†-y?{‘ò-Cdúrè¿Gvå÷‘ßzû&# íE¦Üþ ¼B />` >ðuîyÀ@¿”þG„*À²c0&ÉUU;jŸâÁ\cŽuíÿ@ÿ~ßþCøÎO~móv &×yÙ)@iA¶BÍëMi¿ä{f” {Á¾\÷ÔÌ?<~‡+(ü'º´¿ìyûÿ²÷&P’\g™èwcËÌÚ««÷U½/ju«»µX»%y“m¼€Íc1ËÃ6I'¥°÷ÔG‰)DµVU0Å÷ò!š‘±( ;@Gh «ZâBš=77Çb¼œ¬E\–8_ü3y­“¼ùiçÑ »·ö•¾mÕÜ™ <ÿùßÅ…û‡{˜T¯“+ó'KOÉ…høÏF2€Ïu¼`±ë ¬åz‚PðEzÀôK¸ô™Çܹã¹ý§a¦x‚Œ¡ (Üõë0Ÿ¾•dî"'‚J&úl>å‹t–.`ÀŸ— –ãè†h€¦3·’¿xÿ…øû9 DpØ·j?¬­7µµ]͇`šO{\(1ïÏ3Bšþ‹s¬ì㇈îg×Ô ±¸Ÿ 摱JkøJ¸Lˆã iÌÉG#د{÷Øy2¿ýMÿ|rýÙg=:xmÙ´oyýí¸zï®ô&F½¹‹Ïâ¹ý,¦N–¶7?ôúŠ– #Z9ð·‚×¼¼ª¢¯²,Á?ÿ#A¹¿Â‘÷Á\·_7Pm™Ãš­ûã˜#î\l6žH" íûâ›aýLÁqSäþ#Èt”•TP}­ÚèîN(z³¦§§‡è>'o9Ž4eÍ,5ÿ¼ç^È"V§,)¹¤³ó°¹WÏà¹ú/RQúÕ›æ‘VTé¿ø¯}…€XÌ2Àe ##Då>¾ÍŸSÿ.ªN`ôÎÿ€â¶dA; `ï ÌMGPùúoÁ½ð-ønYµ "À“ä?±diUtC4@¦òôŸ‡t¼ìŸ]0LžÁߺà }£ój;íìG‹Ý/Ûó]Фº,‹Ç"È"À½YkGܧü š°ƒýȹ5à_h;‘áþ„Ÿ¼,Wf®¿Œ$A½QÚíLÜ~ÓQ¼ñÎ[1Пž«îÎMaâä½8õ±Amæ™È†üËH+SÉ÷çd¬x$Ëü‚çV‘ƒþÀóoàß‚}õ{`m{n¤Ú‹4%âÀ?Ž/â¡ÿIdA.¥™ºÍÙs(ø—šiÅb‘”ËeÒ8Éh1@MôP#¢Ñ« ס»Ä´Ò}¨oëb GiV ŸÞ+ ÿ•gÓgžÄsŸü”_üRC™?KÔ›Ž—ù3be§òj3“ ?Ï£ôq@V)€ŸS” dÆR.üý¾éç1tô@œbR#€9²Å7ÿ*Ç?Žê‰¿„?ý"/HD™@_¤0Ï€”+ô=Ô)ˆ.ÔPKÿ!ˆð Sñþ á?¶­¹Ö–çÝo’@û|ǯ^ë—FxJ¾‘Œ§chŒ$­ÇÔPüà'‘ @? ì‡Nþ…´ÉäpÿÀëϼûÍrýÙ¶aý¼ùu·á𽋓¬ç¿þ'xùëÿ9$ú²Bþ{6ß?üø/qðDðÏÆ0«öžwÂÙ{n¤Ú:Ö÷Ó"ãëÿfs^§aÖybXÁ¨V«,Àœ°m›kˆTiÙ»’ÊjÓ@×w>ú–…r¹Ü³÷ÒÌÇm=% (èò/´Ø~ú¾/Êr"À_4" ¥Ò<Üß9þ¢rÃ\w ŒáM¹¾SûyúÐbŒyK;®ú¥˜Ã²zFÄ3JÀOôàgÜEå½:ÀG"ÈOúQ°Ý­ç ùÿx¸?óþ3m"þ­t…6& ®?rwßzÆVf`ÿ&Ÿý^øÔ/búì}¯¿ù–þ=›ïŸ þþ‹¡çŸ=gÕKˆÌù§ï[;ßBÇíw醪mÑ瘬ôâ¸FXÞõ¦0w]7r|Ò÷°0ÿZ­V¤fóe’"Í¢è~éY–Ú4Ð;}O&DÇ*NOO—éÕî2~ÍH€Ž-2ôÂLþn5zí.>ú¼ðé_†7õ\ƒÒ¿ºI à?N„øEÑ€ìsE¸kM¤0]€šì›ùî_â«'1rÛ/ oïÝ<ü¿¡2€<º ¥{~sÇ?÷äßÁ¿r’~•«DB€„kÐÅ*þ„ûêi‹G¤¦¨Þü@%[QþgûŸÈÈ^˜·¹¹,^ÿÒ›2Â"hðŒð'"þZ úŒõXƒº,V@úÊ50¢G] lS©áþfþ±?+ŒPsý¥Â?ËõóÝ·ãàþ]™ß[z—ü8^øÇöm‚zH¿ªòoÑÿžË÷OÿÁØÄÀ©þY €Q ã—ŒÁlÙm`n ÇÞ««¶E²JfE ä9o3½€x)@f2ôŸ ÿt,2øg»Y…¥€ž 4Ð;F~Èj ï¿ÿ ô—èûI€šCÓJHNžŽÚªÂz3p\hÕ»¦-ÿ½Èz½’¯K³6Í”þÏßÿQœùÒÿöRD;Jÿ Y+&éð§"%€Eä³G¡ÏhY.½zéA\úÌû1ûÒO`ø5ÿ'¬á Éã]Œ½î¦ëP}ô¯P;õIz’YþTÝyJ_ä×DZ€Á¿t±ôòzÿa:ö_Wÿ—¹ÿLùß\³·­€{!éP‹þÛE„Â{DIAxxà1nÌÁ|.ë^¦xò5Ð_<àŸ'Ü_0Ež¿TøìÇõ‡¯Æí7]‡U£ÃclÓ/<‚³ÿü¸|â#õÞK%[­úø–T5z0ß?üøcà?ôüÛ@«iÃÜ|Š7¼O7XmKþÓ¢Ž2ÿ¨ØEF$a¡'Bæææ CCCŽ¢Æða±JZ @= Pˆ\¤ÏÎ΢ôT©\.I¬eš77Þ„Âk~R7Zm‹¶>ZÈÜ•V î|”%Õ’€Í*Ôj5»ÀêÒA‰•KãP¼jZÒ£žP4Ð/lÌ¢a³}&Ýú'''ûå>•5 'â„üI1‹ H«öù4•¥ ª¿Ð:íš\H¿íŽøè¶ÿo¡ÆËü}ö·qé¡?¦¸µÉ7•¡¨\é߬ëƒÿD@괔ɡóR~ð¹ÚÅoãâ?þ¿ƒG¿öÈÆÔkgï}ÌMGQ;ùiTÿß@åJ B˜Böœ}+©%èˆÛI}€P,Î¥ÿï¿þï Ï´µóÍ0àýWÇ©¤¼Ç4²Ä@ºÿ‘rûÚcßÀ? õW€¿ ûz¸?ýaõØn¹þZÜpôúÓ×6ž‹òÙ8÷Ï¿KüiØF¤×ßT…þŒzjUO{ýçþmðztÜ27¼†‚ÿCßstãÕ¶hcDRX® >þÙ¤2~ìXáÌ ?§–äãx?űÈSlÛ.°Pú\FGt¼",ÍÅÿt@Mt5óFÆÇÇ•²–¼ñ3õË~Úà ÍðEqÀ…«4†°sÚº]×Q“ÚZ±¹ñó8ý©_ÇäÉ¿¡€Ö CPã¥þLsáJÿ ]G¦¦0À/ªÔDJ Ï`òþ_ÅìóßÀÈm?ƒ¾]w¤—ÝX 纃±ñ(j'>÷…/ÒóM!¬EÈ=Aþ_¬†D@Uz B¨G´HæJèýGÃfˆ¯#<÷Ÿ8} P+TǤqaEt/¸†üÝ üÃ5B:ð—^þ:øoùc'¸áÈAÜyË Ø¸amf_¨•Çñʃ—ïûCÌ\z4UèOÕT‘dkÜëŸk<èað°ŒuÇ(øÿ)B¿nÀÚºs<‰þfÑñ’YéÌI†â ›é¡1L$=þÁe²J`$@­Vkè}úŽi Gú‘eC Ïó,–ÿ?;;Ëj`æÎÃiõ˜v…µ6« Á¹¶n²òËÏà¹Oü{̼ðeVãJÿvJ™¿vˆýÍwM™˜À–˜¾¨Æ&SDÕ® >_=ûE¼òÉG1pè}ºþanIð7†9¶µçoEå;âšJDÞA]o](ì³÷¤P`H¸S@Ú+˜àý÷¥÷Ÿ×ζAAÁÌÖ×ÁÞÒò"§•ãójmÌg ì†q³$ëN¢—X= üyô IBàÏþ9è7ˆÙ¡ŠüíÙµ ·ÞpWïÝÉÝ™¿aú¥ãxéóÄ•'þ2ÐA]èÏÊáõG/†ü'€ÿºÚ6øgâÆêƒ(ÞôÓ ¥aݹ´-ù’%¼Ð(ÝøÖ̱(4Ø$ÏJš¤n<€E(=/)€$._ di Ë¦ 5ÌŸ=±fffúiãw$Ðê‚PeéZ%²Êd¥´J´³ê(€ù/W’Ÿz§ÿáçQyå;¨'Éà?,AE–ü'’DË”&Èj™à‰ã8 R¼Ù ˜|ð˜}ñ¾þ}8ô¶ôï* ÀÞóF˜ë¯Aí©Ï¡rò¯€Ù‹t¡Jû²oˆ…¹Ìgå|lø‰Ð¨§¸¨Sm¨QþWrÿeþ¿ôþ‹÷­í¯éëØ=éä:b¡}sQû¶w{ø["_ú*ðç"€b ãã þ##C¸óæëpÝáƒÈü Õ©K¸xÿ_áÂýŒÙËO†^3¡¼ŸZÖ/¢òß‹^ÿTð_Œ–úKÿdÕ~nú¿ÄX¥û’¶Åµ,/üb q2 –F`RTd21 ( `°R€¤\.g¥Ä—PÚ4Ð}S‡R€5R›6êAºËIËÓŸogmw€–Úzb’sk¸tâËxáŸ~îä鈚¿-Âü¹uø_je™Ô”ö”Ô«cs¢@D°œÜÊÙ¯âÒgBùô70rÓûà¬I/Ûe m„sô½0¯ºÕÇÿµÓÿÔ¦ÅŪQö•ôùÂ×å©„‡"¸AÕ’”LäWþåþ ï¿ÿ3V„1²¹mcÖBF=j[*àÏTýM¡”üMú¥®ÌówéöºÛoÄõ×ÄÆõk¡¦(&ÙÄwïÃKŸûϘzþ üói^SÕYY^ÿLðÏ!üîAñ查cí þµ-é8×ãj5­8KX<)Ï?„Ç¢à9'¨Ùâ8"Þ7D@’ê|ŸÿšèÎ~'4WúcÂ"DÅ™ššêc¿Y'L{ÝL̯ÕEkÞpZô@§ëÔëEö<@ñ ‰šp«³¸ôÀßá…Ïÿ"Hm*¢æ¯æû3Ñ?£KÁ|½)IŽÍ¢d•Cw‹ãüê$¦û#TÎ| ×üFny_º— ­ÙãÖŸƒµçT¿ó§ðÎ#€þ¢üžOOΫÂÓ‹Çû¸D2* ŸN€ï§\çˆ÷ߌ)ÿ;¡÷ßyéÖþïé[Ó±þÒÉ~ÓSÞm= ü¥Ç?"(,€ÿ±ÃpûMǰ}Ë&~|–Í]z/íqñ;FmæBø›’¼þ’pµ„Ê¿ÿª®Êòÿ%úÓçFA”ú HÊÀó¿Å[~Ž‚ÿuükë:ð¿R·y>Û¬™ ˜ZÁ¶™{†¿”˜ Ré)Ú4Ð6==M¤ d¶¥é}]ÿ¼ÞþùÔïLSùoö¼•E§^Œj[Rð?3óßø0Îõ׸'Š(ßR<ÿìÑ^*3»üÇIõµL ÅcM¤:0ðÏ£Œ`±_{å!\¹÷1L?ó%ŒÞòÓèÛ}{ú÷ÙEX›ŽÂZûßQ;ý TùSøWNÒïcÞ~S„!_À‰ßâQDêøYD€üš¥Íb£îý·#åÿøw÷Qà²zGèèZ‘ÚibN·øÇ¿Í JýÉÀߣێm›ðÆ×Þ‚íô±¯TÊ.íW™Á¥ï|g¿ò_1ûêñ0 ÇP€½¬¤¢zýÓÊûõÜŠ=âcLÿ"ìßž"Æ*F’Òe5;@Áÿ¿ƒ1¸VcmÝ7¾¤`‰f•»âï'UPç¹´s)¬ü«h ç(w’²ýL€, V«5‹Ц €îœFH½õñ¼0;;ÛçóDÚæ ?+G¿Ù±Ù¢6‹¥kõ\IƒD;-mÚT«N\Àù/ÿ\þ΂erŠhž¨B-pl·ƒÿø,§FðŒ{‘ÀþVÀšìr¬J ªøns/}?õŠ;ߊU¯ý9ØÃR#˜¢¾½ûu0·\êÓ_@õÄ_ÀŸ>èºpO0>i{U‘`ƈ7ø~Fz@ÌûÏ?+½i| H^únö¾wÒt -ãGZ‰¤NE3ér¨Ú²î« ü ¾@fuà_÷KþaŽ?kâ×ê±Q¼õ w`ÏŽmèïkÚŽ¦ž{g>ÿŒ?ûI~¢–PòçÚ*V¶×èñ þ¸—àŸKqðÏÇ*›‹©²cUûQ¼ýAúF5FÑÖUcLÒ\×®¹%‹HPß‹‘„Nô/˦3@¾%~ þ5Ðý¦°_ÿ‡ —6ì¾ñññ‘Ð4LFÖÙŒçéeåÜ4ëœò󭪪»ÁÓ ü®N§¬äv5±ÈåfsŸÃË_üML=ý1¾5E˜©¥†¦ €ï?[L[@ °4ß쇵õf«ÐÖÅ‘K»qÜÒc©þèËò}*ð—ªþqàÏDýÞtç-8vèJ¥bÓy`îÕ—pî+¿‡‹ý1ÜÊdø›jTÕrõú7€Qæ/!쟓¦£€+H§Z{$üÓà_[⑸Ç> P`Ùï‰qEñê¼”t~!ô—Z.©ýÏÍÍ9ýýýŽ((u8<ŠU€ÿšèµé$¬@:kØìu‘>/ ÅË–À{QÔ鳞wÒ¥¬Ú:en¥Œñ>‚Ê©¡`‹¢u"´_z©ˆ"(Õ§ÿA_Š:Ì á„Kp™Îöüº@ +Ë2ðïÉÏø¨M>Ë_û%L>òŒÝõÑ·ç.N)­“Ãè_Âц½÷M¨žøTNþ5Hå ‚2Bn“U0•ˆ?H :'XÚ@-E'ÀŒxÿÕ°þ9qŒµïûèè9БqJ Å3ó=·6m™s¶|mÔ+o°åôRÕ? øÛâ½°œŸ(Ø9‡‡‡p×m7à¦ë®EÁ±›¶ÓZyïÿ œ»÷¿¡BÇ©î/EþÂ|ÿX®¼¼1äb§GWè‘|öO8üD©¿‚þT'áàŸU¦ sãkPxÍ¿¥ë×\[oŒAM°ÅB×ñidƒzþZ­ÆFxžgH]öÚ¶í, €^ZÆi`%ÚÄįi‰ÀóÏr\ ÚÈûËår?ê)¹òñó¶ Êç#ü§ÁK?p¯d3>ŒÞð^òÌ=û·¡‡J–ý3”ç<-@®é„=s¯ë€ºFå„€’ =òDVÏY4€ëÕÏjP^½ò8ÎÿÝ{PÜzÆîüy6„YèKM 0ú× pÃOÀ>ð^1 úô?ÕÉðs@‘ˆ?Ð à?F–4ˆˆü~áñ—Êÿ! út!nïzCÐ1²Åïªê)öþk¢vñÇí¤ü~¿ÁÁ| èD×åþ,j`dt·Þp·ÝtŒÖnÊ*zµ .?þ9œýü¯£|ñáð·‚d´HiT–î¿l¼þ à_xþyÈ¿ý! àð×ÁØ$À¿U„¹éV þŠîvtc×Ö•ãO³±¾•ÔÞ<ç˪: –EŸµ-Ë*²ÿJ¥V`¯mÛ&,; V«:ü_=6¥ÈÒD Áa¹ojjª€ˆ<Àü‰óçy ‡fç],0ºRÔìµå7{õv¬zݯbº8ˆ¹§þwHXFtAZo«¼%EgŽ.Ç>Ió+‰ þà5K¯—©¼R€ û÷ëüàì³/~gþü³¸úÇ0zóûá¬ÝM×µÅÔßd ¬á^.ûà{P}죨>÷ 2AO8ài!ˆÒ±éø’@°ˆö½ÈÂÛEÿbÊÿôµµ‚ÿþU¹çÿVÈͤÆn >Û­ÕnRBÛÌœ£ø~V„íKH!«¸á ˆ+(%ˆ:ÀÁ?]l‡ÊÿaZC ý°ö½‹ 3tî½¶¶ÿo¿ÔÏ1EÈ¿ö3 `)D€©ÔÍr|~̆ukpÃу¸ùºÃ¬\Vsà_)£|î)œÿòïâòÉ¿¦]²VKIÔëo+aþñpÿeãõOÿõ|ÿB]ì/Ìýwmƒ¥$±‹4{ïÛázw4ÄL›¶.—â¹øyÀz3¡ÜùèˆÉ÷Ù8æy«†V´,ËxI`B§F±XÅL:ü_=ÙédÞ0͆933Ã*8Fcܬc6ë¨óy/‹ÈÓ‘Óô4`ÕÖq oJ·ü Ѩ>ú{A <•Ü{ü3àïËœóHiºfeéºü'­_ãªú\(Р߫ÞÂ,5À'uÝ©)0þíbòÑ?Áð?«ß gì*ºøµÓWq…ëÿ ×þæÿ{¸§¿ü9zârðC€‡ „@ $xz€ÀÒÕÿÐûOŸ«¯†1¼¥í×5mAÄS@oÓ¦ÕÿW.è—¹ýŒ 4Láég@,Ì_–ö“ï%Û±±uÓ\wí\wøêæ¡þøÏ^< ßü0.=òa¸³—ëDž¢ÜŸ–ç ÷Ç2ñúg‚ ø‹±œYæ^ŒÂ(ì«¿ξ{4ø×Ö³cVR…±x\ ¾(9EsǬÕj… @XÆ”åÿ›Fp@\0SP”Ô̽&–|z!h,hOMM ÑÇBq¿fDÀ|;`»úBR ´ik‡1ð_¼ñýôqµ‡ÿ'E·“¢tžB¤·ËrωBÈÞÚ¥SFèeK!ÔŸ×ðÅ¿,A>{ßóê©®8ÈõCŒoî2.ßûK˜|øÏ0týOb`ßa¯Ú’»„‘®y*'?ƒÚs_„åé@'€ÝCPˆ^Õ¿? ‚²Žxùÿ¥Õ°÷¿sÑAs7ŒgzLí!ЯClqo¿!ÀÄÃ/ˆ€¤R~²S2ÿÛ6á†#×ààþ=βlà_Ãì…gpé¡ãåoýü^ØÛoÓà_[ÏYIkÿ¼åÂã„À|pv¹L×u‹…BÁ–éÒB/‘,º).ˆ R@Mt' »=33S¢ÏÍ¥Òy:mö.k€éÄoÖ‹am©í‘.Ö ×ýŒÒ*Tümø³/‹êx$¨ï‹T¾×5eºèÞ” ÜåcÒ{qCjÈ´O~àù7„H L€BÈL‰êäwqéË?‡ÉGÿCGý{ß{tS¶ŸU€sðp®~ª§î…{ú«ð_y ~ùl]0PxEŨȹ/"‚²[–Pþ¥û·À\°ã .îùh–©A¼ý*èo ñ¼ýœ P¼ý’I€”ü~VÎodd{¶oÅõG¯ÁÞWåjK¬gùì \~ôÓ8ßosBOýÍifÌë/ ,—pÿØPæÏpêàŸ¾Á?è½Ú çÈÁÚ| :YÛrË’È€N9ÐUB~/óò»®[ `Ÿ¥¢ ÿcÂéü³T€Z­–G@›&–Ö¦§§¥ð‘aþ"€iéûƒô¹%bèi|o¾z û|Òþ¤Î¯ a©µ;e9Ž´ß –ÍŠ/¢å÷wbáµÒž­ Ô+ëºØx’Ò0ªÿòxS§€×¡@@Ô”á/!% ‹9dU ‰ˆd(/ØBÞWõ2ˆƒ(tWåÒÃxå câ;Ç0xí``ß›àŒmÍö~Ñŵ½óN8»î‚{þqTŸû*¼sÁ‚©EtˆP3$2çŸyÚ¤Cq ¬¯_Ô±B­{¼üû^Cµô ¹â_÷úGsûå¾h˜?àï×Ãt6¬_ƒý{vàúÃWcÓ†uùÆÚ^§N?ˆ+O|/SàïVÆÃß,¿ôø³ÍÎþË*Ï?ü•2ÌË_ "DÎþ™8©ct?œc?sÍÝo´õŒ©ëz!®ÎmÍʇËÏ$¥ÊÉçò|ñµ¨ú½i:lc Ÿ‚{à(ã­ÄOF, Þ›uGÔ@wv:ÙˆeN í$¬±:år™i˜Ya8ysö絈i±| —]ÝÆ´ÅµwðÜôÊ7~ÞåGE$êe\kbKH è¢`2µ‹ÅS²º_Ð (€8 +ÈÔ€x—¾ôÆ¿óÿbèðc`ÿQX»«éøÀ¼÷ló&^BíùûQ;óMøçä:Á1AZÏ­åšBø>7¶ðû¹”DÛBÆ<û¿2@¿,ßG„š¿*àg(€_zû#s¼ðö³0ÿm[6âþÇÀÐà@¾yÀ­bòÙoáʉÏãƒoö’” F<Å›¯æ÷["¯Ù ü% ˆD` ➦ô/=ÿ2ü?Ìùb¦MǯëáýQC4æÐ¶,Ç»¤4c âó8š’Α¥' KjµšÃRXî¿ëº< €l³,+IPƒMôÆ”#J]pw™mÛ¬à€T¹L[x$)ù§)û·"ð—W3 6^ .=°œîA;ÿsãµpîúMTïû Üó_ã`—-Ö}"ÔïB`,RºH@újø wŠy½¾ùç^…P#\YBPÖ^=ŽKÿü ˜xøÏ0xè‡0¸ÿ (l8Ðô·C›á\ó.Ø»^Ú¹Gà¾ômxg¾NËËüüá%çÿ$øBÜØp¤cy¶î;z|ìݱ‡( [K _ñþË“Ôud™L"Æonë֬ž۰÷ú¸ÅB¾Zò^uOÿKýÜ™ !ð72€¿™ ð'ˈÊ%IÏçù§‚™ï樂ù« þñ($úÚÚvœk¤8¤;жeþã>%œæ“0FRÕøçD%4R©TŠ¥RÉ‘½UVHc„€ˆˆ~=¹j g¦ž` þÂôôt‰–Ê[UëO]Ää\èV/t—ìëëÞ„X½xí¯÷ÿ¼ÓŸX>P³âžm #–V úãûâ‘ ˆ¤P @ x’ ®!ÂÿÍ À®ˆðÄëÚ•'ñ꽿Œ‰‡ÿö¿û^¾«nÄû²~KiöŽ;`o¿î«ï„{öax/ý \¦àUEÕ œÆ®ædÁRî…ô·v¥!,ÂÈ¢?’½ü\½_ùñç9@¿*èGdþ¿A" Ÿ]sFrØ»{)ðg ÃºÕ )wiæÎNRÐÿE\~üÓ¸òÔ'xŽø O~3௠üa9{ýÀ†Ò¿)À?»’Vì½ï„}Í÷©Ú´-³ñ¯Ó½fDÜæææì‘‘[æúËR€L жmN¤hèr€šè:°ÆåÄÄÃü\Õ’…ÿ €–€ = ‘k¶Ís\'œŒ&E0hë, °'ÿsxÈí¿ˆêÀ¸ˆ/Ãyú„Ç?øú&ºÆJqOÒ{i¯ç³.ŽT  _h%òþ%@êº|¿¼JŒ˜|W¾ýAL<öaôïy'÷ÞCoqúšü“‚ü]|ów½îÅ“¨ýܾ ¿|ÆÈv¾Ñ®hs­¿èMì-دϩb¼^~BêB~†xžú#yýŠ ªäÏóhéóukWãšý»°sÛ\µuúûJ¹ïwuò\~䓸üÄg1qê3ðk3 Àß@~EÿðŸWìO„ý³ãí8‡~öž×ë¤meŒ‹ žþøœ–• ßçþgá™*=77çPð_bx‰b%†þù&R 4†ýëIR]?I/¿\s³z—¥™™™BžÐýf€/O鎅,*RãS[ûé´A·“j­Ýþ[ù« àÜø~Ô7 öðïï\b€$ÈðIs]9wP±‰J ¨‘íèzñÔµt`"@_ºPtˆöL>ö'˜>ùQ¯zvÜ…¯‡=¼±ùï) ÃÚz#ÌMGáí~ü)FlîYà¬ÇÇî¸öÑÈ5<µîá—€_õò›†ôþ×Uü³@? I‚à[Õ¦muôà>ìÝ}ô[¿fuKícæÜI\úÎßcü©Ï¡|öüœˆK†ûéÀŸ,Weÿ´Á-žïŠýøó±?!øGJkP¸þ§èXtDw&mËè'ñ´ñ¨ÕõešÐ_³ï¦¸Èbz ð‹* ÀŸ¥L«Y$@—Ë7k`%!ÿÆôôôP¥R)ɪY,+= 8OëÜ­jh {s-ØÂu³J°~HÿZTüx'!ãWÂ2¼½ XÕ_– uñ: ” ¾×ɵr"Àb€’z D@u3ßýfžýÆß‚þ­·aðà÷ ¸é`Ó„)q›c;AVïZÀÝ®T]¶ti·iÞý$°ÏCû…/o Oxž¿ôöËÞOÒAÀÔñ¾xÝGÿÁý»±mÓŒ­A©XÌýy•L>{?.=ò˜xö‹¨\~2r (`A?÷úkà¯<åûsð/òý% Ààõ·xYµÎuê*ݹ´­˜1´Yr¦HÒ&S#Ò˜ê~I¦zžÇJM‘%ñK`aÔE:v–Ëå4€$üf.¼®ï®&–Äd€XòJ¿]­VKLñ’-’:Ë|¹YÑíª* ´Ý±ØUK³è{Ò‚Ñ…žµóNµ¨>ô!xg¾Ì½Ù„H]@oI£Òÿ’Jþ-4  D' ‘&`àßú}œðj¨œýæÎÞ‹É“Ÿ@aÝ x ¾9(¹Õ% 1 ¼/$ÂF÷ËÎß³$°4z÷h¯ƒ}áÙ'u?QŽQû %¤Ÿ¥HÐOÙI<ߣ ßÄŽm›pøÀ>l§«F†18ÐßÒÿY?WýG\yⳘ>s?jåsM¿­¨ù¯¸PÿDð ùgÀ>’ïD@xýyÈ?Ýr Lì¯o•îpÚV ðÏZc&}&mž÷ÓˆFЇóø3ÐOqRH0 4zûIÆH ¿&–|:"ÂË/sý™…5111H;'dMN¢ä†XEä×ÇU8ÕÕì9Ɉ†Ü•˳Žwfu‘”ô¹v-†WúB:>p.´t£6ºÖ[w5Èí¿ŒêC›á>õçõYƒ{@ТjÜ;” Ð ¹d¿^ " ¢ Åø7”Rƒ,d¹z媗O |úsxå¾ßÃÀ®7`øÚf‡ \ºÏä8¶XDÛrHéé1C~³P~bÔÅ÷L¸Fý}Ô«ó° ù7%A ÌÇô³ûäzÁü}Õæ 8vøvnߊÑá!ž×ߪ>ÎÔéqébòÔW1{ù$¼êt}-€º°_ôI´ŒŸþ½þuð/½ý è—DȿӘïï ÃÞ÷½°w¿¡¹n‰6m=jjI¿x9¾8)ÀðE ß'•˜ÅuÝð¹Ð>CšÖ™üÂéoRÐ_¤fÕj5®þÏ{4}d)LP "=@ƒM,­MOO“$Ì„-èSgvv¶¾¶ò‚¸Vóÿ"V•”°\…çziÐVÛSü¹¾/ó3c`œ[~µ‘-¨~ë7‚ÜOþCA@# D“h€ vÒëß "ÀC]ÀåYÄ€+Jzs¯À»ð ®\|ý Žahÿ÷`ðÀ›`–¼4ßBÇÓ…üþ•þ[ùq ×<”_õîK öA¢¢~2ܢ̟zLÄË:ègåýØ?vhvlÛŠá¡”Š…Ü*þÒ*WÎáÊñOãòcÿ€™ ¢Z> _,¦ êzRØÞŒ~•XQÀ¿aùocv(ôW%þlAÔKüV¥„«úÓãKká\÷~X¯e’czbÔ¶¢,YÞÏ7+ ß§: és“b$À´ &šÎLIPuÒHmšè¦¾E˜¢%‘µ.i±'&&†Ò®ûB›Y99i5>³Î5ŸÅr»ÄÚê×0΢v;øÏR“íšßÈt½dpª÷ÿ¼ò‹tITð…H ×P£Ê¶?% ­ÀRÜîVˆðýÆŠ’¨G€)îÔ)L?} 3Ï—¾º};ïÁðÑ÷ ´é`K)ó©`’÷­ö³•¨þŸ»\lJƒNùâCáû1Ͼ ö‘擼ûDì7å¼Hˆ Ù¤Bð³v;8Ї½»®Â¡ý»±uóF ö£à8-{ú}·Š‰§îÅ¥‡þS/|ÕÉSðªsáÐ!R~¿ý²Ôߊþê? ý%©ü¢!ÿ²Ä#{Ùx>v5 7ükÛô"CÛŠ\GÆqB3ŒÐÌ!˜]ÿž¤ˆ,-"l™»/¢§‰  4†þë2€šèÚé‰ÐÏj\¢Á§¦¦X€)ø‚…þòú¥^øikÿµîÖpáv¨-î6aí¸dh#ª~ÞK_à~ÿ \ ¾… @H^÷¢åç/Ïó_J°¿P"€eIðpiFü¨P /¢` RÀ“稌£J·ñGžÁøñÃ^}ƒ{߆áC߃Âêíi›yªvWNɳ8[ôþÒð}Êk#åÚµpÎÆñ òùWÈÆ„$~èKp®æí'yî›y÷Ilaªv<æw±·eÓzسWïÝ…ukV£X°™*uë÷ˆvŒ©ÓáÕÇþO|•©3pç®åQï3„4óWKýÉ´"ú Á þ!ø!ÿ<׿ÿ2ä_Í÷ç‘f ƶ;Q8òC:ä_ÛŠ_W¦â­®/UL’öÙðÏ¢§x)@f¬€ëº¬@(¨T€ýšèj‹1`2%€5äâôôôc´ò.ö²Ô7ó,:ó0{I¸•EŽ&§-ÅfÐ>3Wï¹ã—àž8€ê#ÿƒ/, ×ó®£Ý¤‡ÑR ¦€þ…§$…ú/føÿ|‰u@¶ßTO¤HàOÄ{ѨW¨œ½¯œû.~ý?¡°î&Œz7÷ß…Â覆 ÑIõÿÅÿKÚ—ÓJŒB‚ïz8}þ ìc€-îÉ'DÝOêJüâĪ‚?ž{ö®aDCHIDÔ/Û»ùŸ¥6Ó  uhpvoÇžWaçU[1Ð_âaýq}ž<€ßsk˜=ÿ4.ÿ ®ÿ*W¾ ·:Aߊ^oSzïQ÷ð7xù•}+ø7€%ן8uO?¡›%H€Påß ÂûÙgXÈÿþwÁÞóz=éi[ÑÀ?þ˜¶öOòÜ7óò·²¦ZbLø¯X*•! ‘ÀÞ3ž÷¯MÝ׿X# –º¸+ÍÌ̉¢z•7—¿•×Y9ýYÏ›‘øwǠݭ׿×ÛƒQ…qä‡AÆv¡úÍ߀7s.ð 2 Êæ _¦¸<¼OD8 ¯‘H å—û‹ã0çv øoF@Ð!,lÙà>úÏù\%=ÀW¢\I°óÐëêWk˜yéK(¿ø%à ƒèÛ|'F®}wÞ {h X 1ç7…%y(–’P[Ú´mœU†åï„~æX$ÎÑìëç7HlÁ)¾/æ ‰yëÀH>~£_S°ü|Áü_ƒýýع} ölߊWmæ^þ¼‹ØxgõÙ˜P«`îÒó¸üè?áòã™—ïçÿ{ôË©ê/Á½ óoÿJ˜¿)€Xß[À?Iè¯ÿøÇCþW‚säG`ŽíЋ m+z ™%Øêœ•ô<‰4H"Ô}sssöÐЋ¡ÿ<8ŠyÿYu¤kèˆMt5À< F¥Rég¢:À¼@T»ˆiy?yÃ~k¡ª½ÜÉ„N·…ÿ/›{D¥µý6Ìʿü7xçî Àýl¸TRxÛ D+¤“ñPÿ¤ÐÿnNH[›Cx2}×ìIÅtº‰† LROðET€«DX¸"7@ ò+Z›Dù¹O¡|êSð­J›îÆè¡wbp÷­p×€Ø ¶àoSû\Lñ¿¥îCD sƒÅÛnð²v¬Ò ºB/}H–‘äù$<Ÿ*¢§Š€_¸«žvC„ù«Ä@Ø\»˜wŸ&ÇâÿÏð`?¶oÙÄüvnÛ‚ukÇ01^~¯RÆÜ+§qåÄpåñSÐÿ@D„=‘?- ô›1u)ög îí_qùý©à_¶l …þœð›"ä?ôø+!ÿVÌ«î†síÐq¢¤W¤Ú´©žþøš3ÖÓÈó¬ÈÔ,RUb!Š ô Œ`•Äç ¶9ŽV @ì×€šèî¾511aˆ¼ÙðÐÇò\ÔF?_ ˜[ÍkïKSšo–Š I€ÎþÿñûÚ-×e9ÞcÕU(¾î¨ÿ(ÜÎCÓÔ @ؤGø0JD¥žÐB¥€¼eÿº1 iî+¿ÝDp#‰ùÿ˜JT€Ô`ûÙå•Ïe䀯lž7‡¹?ƒs/ÐͰPÜt†öÞƒ· 0º™bºàg¹½1¬“QL½*þG EQŸoËþ œ`Ä"¢ï>§Ç>‰ÍÑ2´¤äG>/É„ø¼›£°O·äéÛôw¯ÁUðïØ¶ Û·nÆà@ÿÂî±çÒq`îÜ4fÎWžüÆOü=æ^=ýuÏø.˜c;õbA›¶„µ4â*] PŽé¤'–¥Ž;'åqì¼Ò›Ÿt<ÃÕj•¿fôý" ÷¯Ã'^€¬`¹\&)$@* MK5……ù)´X´ñº®[pRÊåtÍrü[9‡¶ÞúÚ:}ñM˜›¡ø–?Bõ›¿ ÷¥¯P\?%€à⪰\_‘«Àµ<…3”Ÿ\ —n·tp ˆ¤  ÷DñúÂóoÖI_D ÈHIHrmîäS¸ò Û~‡^þú¶¾ƒ»_‹þ­ÇPÙÀ £Ð)ÃÒòªç#”BÏ?¡ š^³b¶c£`Ù0-3 ǯßר<¢äüK`Ÿx=ÕŒvÃnª'ú„Eˆ ô <ÔF‰þ¶±±l\·֮Ɔuk±zÕȼ"ê’ îì¸3“<Ÿêôƒ˜8ù9”ÏÞ ·6WoÐòßQÕûS@Ü«¯‚~¢úuù…¡°2¿,ïçļþvþú³8QÀ gÖž·ÃÞÿ=t—mÚ´5‚zù<€_Z@S¨dAüsi¿Cˆ²ç&}äŠx:OŸŽ &~mšè† "÷_ièöÌÌL¿çy…xgiö²TüÖ:_²¤Ýþ›Õkíu¢b±A)­‚sǯ vòª'>úùr2oÿ=$X³òÐu£® @wð´ðëX'|lößÍb€¹Iˆ ä¿|"*ÀGÜs A (2…€‘+q2 ¨F0‡òsŸ@ùÔ'økпýn l¿ Åu»a®†Ý7Ê#Ò¦zy<”BzÌÃÏ@v©möœyÐM.h6S„T`Ÿ¯ÿÊîàóý‚øÎìÛèë+bld«ÇF±võ*¬[Å>#ÏÛ;x¨Í\Amê2ª“1söqLú&®œü[¸ÕéàA¹Ôã >ÚObŠÿJ÷ö§‚³ü3½þvTèod/ ×¼æ¦#zñ M[“y+-$?^m* ä«Ï“È 4GÆe‘LôßuÝ¢ÃD[ ê¨eùÁ4,'´÷_KÝ·B“Äžššê7 ÃLö ñÎÏç³y¾N¡ÑÖÍDÛ¢’vöÁwÂØp ª|îÙ¯pÿàå7ë%‰!´Ôh€x¥€¨6@’⊵§ÀÚgdT€EbQfk‡hd€8§h„‚ £TÝF$L<…ñGØö‡ü}gÕ5Øy7ú·CqíNØcpèfõ /;Â,ÿòøöÙº‹…Ó;Bø/ðü ‚‰h>eÕ€{ŽmùùŽ}º ö÷axh€nƒ¥ÛÈÈÆVb ¯Ô±kÃòøY8mú*ãç1sîIL¿ð¦Nß‹Êä3JMËèGc>?^–i}Òæ~6ôCøÇ¼þLä/µ¼Ÿâõ·ûan» ö¾·Â\§'GmÚšÌ=y+äIéMsD¶2Ž3lT­VY„´%R¨ YEpÀjYL0K@›&–|J“Þ"Éúº0999@Û²„·ÚÒÒZ)ج#gÕ Õ¶r'“åü½Qo§sõoø¯¨>ü¨}÷ðËgéU^+„) ‰F£¤6€ÇK žoŸ¯{}/ ò»]ô*_èº_ H dd€‰¨€/u$ðdO@..$~žWŽãʃÇqù–^@éèAôm½}¢¸v졵pVÃXEW"vׂø™(ÛçЋíˆ|z‡åÖÓ×]sYÀ³ÿ‘í§«0Àô˜x…ö>½.ì9÷аG¦ÏÒ(ˆ/‹èï/q@ÏøúJô9ýýøq󪳨N\Duê"*—Ï`öüILŸyå3ßBeâ$1q€žÚ/Õù¸·_!ˆýùØÃ&¹þÌãÏ=ÿļÿDæúÓÏ lsõ»a_uKoåOiÓÖ#ë¸Vt½’Jü%é˜Éðùú, P(ØRK>B€i¨B€šÐ@wÙôô´öÏäÜÜœl˜…©©)F˜i :/¨Nûìb& þµ­Dð¯N^„çØÂÜp-ªýÜs_£@¡Bß3éâ_TˆEFך€à*xDN˜²¼—ß¡ÿèЬ«’›ùH2€}FxJª€K° D¨$¢Ñžˆ`QµË`üUº="ˆÒf”6݈Һý(®Þgdì¡upXê݈aö\¿‰Ûú5«ñs?ù#ü&ÿ¶‰‚SË:Ø3 o²ò˜¼, )<ú6ûE¶¯ŠÒ.•yµ Ü©K¨L^  ÿežÃ?ûòS(Ÿ{”>>„ÚÜåÄ0…ˆ+¹üˆyùU¿ˆJ¿$H$¼JxNè¯hПÈþ¥)üÛͽþVŒÍ·ÀÙÿ6#[ôd¬M[›Ööq‡ßbD[2±?P*•lŸòçÔ¢Á¿&ºuz ãÿe$#°X €`´2sÿÓÞ›Ox§Àº&ºä.ç{³p °é(̵ûPyô¯Q;ùÿ¢ 9|äyÕA€É=þžWà‹YŸ¥xô8ËŽ(ÅËø“<÷:š6à vÀ—Ì‹h¨‚R@TðÕèÔ÷{Šƒ$ø¹g^ÂÌ3/aú™:Ñ`íEqíA”ÖîEal;ì‘õpÖÒý«QZKok)w»ê†>:2<„·½é®yõ­¥üýîÌÏÙ¯1ÏþÝ®¼„Ê¥Ó˜¹ø]Ìœû6ªÓgê&¥M%yø‰"Èý±Ð~ý‹ƒ~#–Æ£gJ¤„û àOê ?âõ7ðOòúï};¬m7Ñöék«MÛÚš>¾îIJ˜Ï|¦~§ ¹¹¹3üE%NÐ]$¦ÐÖ¥ˆ&´µG¨\–ªÕjŸ$ÔЗN,ºòˆ ¦í×À^›¶›ÝçºcíÕ¨=ñq¸ç¿Ãåâ„y«¸èÿ&«`òa—@”Æ!ž8®µ’K=¸Í7ô!Óv" ¤L¼‡©¤ž* Ó ")t9a B@ò/žš> Ί2Baò)”é6ýl]«ÀìÛ„Âêý(Œî‚3¶ Îð؃ka°H1ú¸ VqPp­ö9¦ÈÏú.q°ÏÂø«ã/£2~Õ+/böÕg1÷Êãpç.Õ+&„ó‡mGUÛ‡Rv±¼þàO íW¢B€è•iÓ¾÷çaýø+ ÿlü …þ ~¼±éf8ߥ½þÚ´u€ÈÿñÏäY7%¯¾fà_–w]ס`ßÎSþ a¬|:#„@<@WÐÀÒƒ‘»"Á¿|nT*•þñññ~ÚÐÃF/]¤ÖçL[tÅkpJ2!鸊Tøˆ€ R€ O,-€Ept i"]uÏ—h¦WP_{HÐOðê:¢¢€®øGÔtÄôˆåýð¹BBø³g0÷Ò̾ø¥ºº=Ë•ïß{dœ¡Í°7Àâ©k`õ­bƒ#œ°ûG)&Zü©8Kª•…ÝBŒåè»e òËL˜ïU ò_A}¾½L·ó¨L¼ˆêåS¨ÍœOì‘•_,÷^ô Gìgþf^~5¿_ƒþœÀŸ]Q5Ü_*û’(Dþ¥×Ÿ‰QŽì‚µûÍtœ½Äé××W›¶Œûñr}i¥ÙzŸóȺKU÷OÊùo›ù¼zv~†WÊå²³fÍšÃItŸ$x5F$*úŽj «ú×ÄÄ„¡}±é«T*…40ïüÑ'þ‚¸åPìPðïA]ܲ´ à³>V´¦t Ð-w™Ä U©Þ¯}S!uwÏW"Œ¨–€/ø—¸ˆ ()Àù›) Áp,]ðL?‡¹©ç0«òßÎÈ«`ö­§à=}…Õ?³8DésúÚî[Å ³ØÏ£L¶J´ÉÚÚo[% r‘ôx¯6onšo¬Ìž[ž€;7xô%à/¿ —þé‹ôýW(ø‰>ž¡ C?èÇ?1êûâ`?îÝ—>ò~ àoðò+çthK4’ço5†ûË€0Ïßsý¹×Ÿ¥Zm{=ìo‡1¼I_[mÚ:@ ÄS[þ›ï÷©ÄCùôÛ;-Ë2)^âC6#D”€Ñ××ÇH‚,$vRDèRfšXüiOYh±òý´áQ×Lì|id@RÍøyÀoÑ Ÿ6M|¤Ï+æ†C0F·¢vêTýP>ÇÁ.Kà}ǤÏ\žÀ‘ªgÑý5Ž@}^QÀèZ"`©"ò’ñPlO$A|Xm@!B€/ŽSñ @¿Ql~ô=ùÌ0r€Þã©S¨NœâwÚGcõ9Ó†QZ£0‰Ã¤Ïû)N¢ö}]¤8ªËéƒéôÓ×}\À¤¡à‰½Ï ΰÈÃ.pÑJy¥XEbÙ‘ò“!¨e ²Úl¸Ï­ÎÐ?´ÕzUxÕJ [Á¼ö•x•2Ý7Ã7—}wvR¼fÀŸ½7E)蟥@ö ¼™óôT3aÅ ÄšsÃ}Tn°šþ!¾ ã‡Be|(¹þˆ•åSÚLV.¿ý­ÿXž?÷ÞFëáþ¶ „ü•T «ÀÚs¬Íר%}}µikOóø«[»°sÆ‘QÈÌjµ# ¶m(1↓àyžÿ×@×OD4jÞ(]×µ¦§§„ÀEÛ€uVÄ@+Z­ˆ.¦øŸ&z ëûk¿Å‘ÀkÅ£>÷ùÏÕI±¸õ‚XŠ8}îíªq"€EQ°Nt‡>@[rÿ牠HKãF#! ‚M™Ï¹œòXD€ øeLˆS ;ꟑ•  œ7Äî8üÉqÔ&‚“êoßY:Ѱú)vä€Ë°ŠH1+±¡ªnÀ‚i:<9%\=…ÛƒçamЭ0y}ºoŽþ&–®R¡À¾ ¿:N÷U£¿!ç½# ÷+îQOË­yÒ¨àû„4’H Òû5à_ ðWËú‰öDØõpb bÀ¬+üÓãÌ=o‡½ënƒëô]Ц­ëë´èb¹¦Kë ý®¤´3™†\«Õ,ú>ï‡UX €R °ÙÔÒÅ*JšXö€Â^±Íší÷<¯ æÏçQÜìèJ õÏY ž6m- ²kvüùgPÛv;jý!¼ñ“ b~âºVeyÈ«_£¯\@¸>âú1í*b\üu~o.zÓ ~œ!Ä£¥’ñ’ƒÊq~øÇȾߋý5*! d±[Ï¿x˜›æ/]¿E¼–ãþ’ŒöFbO²À³;6±¡‚ré­'±œûÈó°ŸéÝWHhÀß¾¡!Ï_.èg‹Ðþ…¸_ø•\ÿ7QàÿFëöÓ·}µië00ŸÏÄuæs‰(«V«1G©%4Ô¸±˲ÒJÆ¿ÿšX|c Vt U蕳°§¦¦Ð,Œ>Yû²Ê{Ì—Ph國9(-·AVÿË纅8;nƒµv/jOþ*Oÿ-0ó2_üºL€OY&‡.}Né^ƒ•dÀŸ0ÏkPJ0$@–$5`)Ôÿ;yþ$Ð'½ì*ð&hôrû1m€Hj@1àûÉÑžÈ9ð•”(©q’±ˆø·ßKø­­^õ’F¸’~Ý#õîŽ3b¡ô*À?Ÿê#Ç(I@2À~äœ Þ} øÛÔ×âyþFQ|¥¬Ÿ$„¸_=Ï?þ(Á:ðØ[n) ëë«MÛ"­Q²ÒƒÓÖwi€y_+I%˜ÊÑq‹a'Q5-¬ÀªX–ÅŽt9@Mt‹MOO¡j)½ÿa*µÂäää€iÖ¥ž“ˆ€4r Ï1­‚´<~¾ZÚôuZ)÷¶YÕŽ:ùvýk`ý!˜Û^ƒÊÃ÷Å/ÒcM. h°´? qÐO‰_'àÛt¿Ì_|€®nÍmH-ðIÔµ¹œŠ¯Aë¾ßH F „ÀÑè€8ñöÇÏeF+¨@?Þ ½4Ç_ØåMÿqП ²À|4Að†’@¾'%j@âQ’@pè•b›ûXVž?Ó‚$÷“àߌˆüY»ßsû0Yi?ÃÔ×Y›¶%\§&…üg¥$i ¨IïñyÊó"˜†…úW*•b¡P°e€ª€äÜ¢‡vM,©‰X¦‚W333ý,•Eíli52Õ}YDÁB:{£·ÞýØ.–5 ÛJk×Ã`a­k x篢öâ›P;þx—cÅO˜$¸“ ¬à©B#€x<] ›«t3é@ò~—Ve‰A_žñýDPîGo‡*ˆxYA•Hð£ïõîÇSÓóïÏóšõzÄÀ5IÙŸv,‰‘-$æÕo ¾#îÙ×`1¿Éào%äù‚~úÕ<3ÐA¡¯ÍuGaí;ÌÕ»è)Šú:kÓÖë—, ÐL; Ï9Ôõqìs¬€ÃQ~¿)ôÔˆBèr€šè®N£€=077Ç¥iš¥ññq–`ÊœV÷Ҷ톎G„/&”ðµŒ_­$/¥üÒÈ –‹#¿³àÕ€réþ§vÕéÎû]‹ùÿuKûHkëiÚj©MÞ¯œ>Ø;þTŸþjO|þìE®@ˆ ôåB›õM7Ðà©NË·] "`%Ï®I  3‰ œ 1:ÀLê~™—Ê÷€Èk¯³0äSq"RÈ£ÉûMŽ×@©¿,égÕËøI ÏŸÂ€@Ùߪòß·Ρ¦cßAî¯ï 6mK±v‘k|3äù¼ˆxnÀ q,Ç5IÑ WH O±ÓpDè?ß'„FÏ¿®  €®š.Ã):k¸ÅJ¥R‚7n±³e ,¢§Ãùµi[\ÂGSû½ê_Âá÷ÀÞvªÇÿµg>.òн@ì}"Tη8@¤€WYT"`1Ò]?'ôþ§}^ ±/óýäœøxOˆ0~tªW_% Œìcò^#u ìl‚ ‹ ˆ|†¤_[ ô—¸¿Ê0ÿPÜÏRBýí:ðg`ß”9ÿNTàO”õƒU‚µï]œø$}céB›6m_ŸÌ·‚X<Ì? G¨D@oÈðù¾ÄDÕjµ088ÈCœCÕHÔd€&ºgêT, mÐ}ª8`¼Ó¤…ÕäìIL[žÐ›ø¹4йÁVÛò¾–ÍJä$¥‚D&`ıj;œ[ÿÌ]oDõÑÀ;ÿ 9šp2ÀçZ¾H0"À0;N,ÇÖÛŠ˜!!ùÁ3irŽÄßÒ„ ˆ~J¹ßlád™B£pNŒæ×(þëѲ[¿ õOþ v]ÙŸÃêƒ¹íµ°÷}Hÿ˜V÷צ­K×SqLµ®IÒ HÂ3*Þ¡ý Ç €”Ëe{õêÕ!Hd9u¦ â*=eh {”u µ bau-§§§Ki#ÀÉó™VH„ù,­þß;ÿ&½ÿ§¾Ž¿gÐÃ}¦ kÓa˜kþ ܳ¡úèŸÁ»ô‡u,%À'²X½×HpqÀZwEEüž.÷|»Éó>I¿L!è7²‰‘/+ÉAšh`¿L€¿QÏå'B½?êñîùçz&6ŒuGa_ó.£ÛÒ@›6m]±6•aü󉈋û¥=ϳnR1Ììì¬mYV~‘ÀI¶Ù¶MX%€Z­–åñ¬:óÛ‘›¬ mͦÐññqV¦"$X ¦ znEBiƸµŒç% ²¾++¬G‹jÒA[ö„˜5)Ɖ€Æ{èXWÝcýa¸§¿Žê£ ¯|žGÌ Q¼^0A ÐF€·¤yN»¤c·þ?-¤;‘vë+ $›,h×mѲÊ+ ôçþ ôÛu±?µ¤n„¹þƪ½p|¬G‚ñJ›6m]·VIŠn%  ŽHŠHz-×Cð3À"+÷Ç"nâdÛ‡dõÿ4a@ ü5°˜ýŠDb­V³'&&úécÁqœL ¦¬™×«¯æ7 ÛI«0§ƒw `jભ—­Ùä¦Nry'T£8cß[`2}€'?ÚɯL@Ÿ»¥ý"Àm;°”¸¤­h¾§'—öýÛz¤ÕÀ¿ø3ÐÅ»/Düxñ¢,à/Ëú!ÿdxì}íÆà}mÚ´u5„3Òœ­¬aTŒ_%• ”¸”î+P°oŠ€ðt,ÙáÿzjÓÀ’óìY333¬A'4ô¶ï´\›¤>ßï_l¥wmšDéµÿ-OÜùö%£´ …£? {ï=¨ø;TŸùüÊ8ljMˆwiSæqËZÉÏoíà…nmÚ–ð¥ü @ÆïÔ=þü¹­„÷' nµûÍÀŸUÐ×[›¶\c%UùRÁz ”ˆGÒÊËõ"ŒnÖjµ"5Y6=Œ`è~”Ëå41@õµöþk`ñ§X5Ο6n{vv¶Ÿ>µó­æñgÕÞÌ öÓIV8O+^LmÚV‚¥…ÿç! rý«áÜð>X¾µÇÿÕÓ_€?ûj“ˆ³±j}Ï÷“ˆç@È€.Ä@ÍïéÒ“š„ÐÖ¶FŸ¨è/Jù…â~IÀ¿®èþìuÿzXÛ_{÷ÝôcúzkÓÖC€?MįÞȃQ¤Æ@üÜ*è—~¥¤¹I?Ç"¦-‘¿¦S[–•UPÏ”šX²Å?A=€¿æLNN²YÑ´m;ÞP{bGRÃldÌxèL£–HÔ©²|i„CZ~r¢‚yìûVªF@RiƼb(íüþ¥øŸ—ãÿ¶ïÍji%xZYÑkà¼æ§`x'ªÇÿî™û¸F€A½Ÿ «øµ NöD„uä:ÒI§ï¿žÛ´-GàUô‹û¥6^°s¾5ø¿öλ@JÃúzkÓ¶ Ö¯YkŸ¤’~IŽMùœaö>Ã{ÈÏHr 0ªÕ*Ó°é1\üO„p2€‰*#\’€6M,ýÔ+:†Ã"§ÚÑ4÷¾Uï}§Í|… W8 ¤/‚ž@SsäâŠÿífŒ¡(Üòûò.Ôžü$jgîƒ?õR4" ©jýuù뀨Ñç6}¯*P;=~!Q‹1wXüo‘ž6míotJ~?…ýL¥”ŸUþD(ù‡@àñ÷9PZMÿݰwÝMŸèk®MÛ2$’Âþ“ªÅ?Ÿô~šÎ@\¯L8' þN¡P°d™@÷™Œ`b€µZ ük`Émzzš5ØHÀÜÜœlŒ¥‰‰‰Bꑺøo5 §]Á|Dµ-.¡ïÍÒ^ÿ¤çIk»ÍÙç¦ kâ{Q{òSœð&OƒxU„UT"3®P« r’€²À¨€yææw¢õ’žoW²Ýè>¦­uàß ìWô'øËTYƯüa•èJf-ìm·ÃÞý:¢öøkÓ¶œ€Üù˜±˜UÕ¨Ù¹³Èõ»Чø©HÁ~12¢F–꿞5Ð ý(ZÏË4ÍâôôtŸ¡(kä­±™·T_ÈÈSU@}mÚ–±ˆçÆŸ„5ñ6ÔNþ#Ü—þE³ôÄÁ't—§ 0ú¥N@HpÀ‹DäÖ è4øoáàùê.Vù¿N|¿¶ úòû“„ý¬º¢¿êéáþ>0àÛý0ú7ÀÜöZØ»î¤í××]›¶eþbqÇÇ|ª‘±TjµÊSØ6;;k³C¤,üß¶mUÈWP›&:gRý_>L•Üú&&&úTb U`ßÎÎÚ áF*hb }×\§ tßžoÚŒš°ØÑœ¸áýð¼Õï~ ÞK_‡åiŠïgox8 ÷ð 2€ðyŠ×ð¢Q~Þ¨=—À…a¡m°¶Þ {ûí vQ_wmÚ–ñZ´•JaY‚Çó-®] °nݺ’Ìÿç`•F0:@]ˆJ˜¦ÉòTúfffŠÅš4åý,oþ|Ë›yúã¿UƒmšÈ7)²ILlj×À]4"`` G~Þþ·¢vêkpOþ«OR\?Á=Ì>Ü«oéÌ»üµÏÝ0M€ä ˜‡p`'…ÿZ½Ú ½=z„Ô¶h _zû¹"¿"ìæ÷‹°~ÓIÈïW„ýèØû¤8cdÌm·ÂÚvcðmÚ´-kðŸE 4óâ'a‘V*%¬‹HµZeÞ›åúW*H@Fض̈́ ÅVYÿH@QM@{Û4бé™LLLBõR6H–ËÒ?33SBÃ’ ¾[ ÍO# Òž/$¢ •èmÑëÔ¬@'Æ#­°xdÚqñ0¸¤þ¾Øf‡áxüwÃ}ñ~Ôžûgx—æ^¡˜ÝcvADdêà~JTÿÿdº€hHB@-zÚg_Öiô§~£žÛ¯ ö1Ͻúñü~üÕü~¡èÏ"ÈÀ&cû`o»æ†CºáiÓ¶×9y‰IzqÀ,ü'Ô l£ ß¡XŠEO @.ˆFá?þ¯ €¥\¬…eÿÔhÎÎÎÔj5Ö˜¡½ ¦u@û£¶e3¤‚ýÅ,™Ù& ý\ÄËÚ~Üó£öÜWá]x8Ð   Þgêÿ<·_” ÿ€Hˆ à`DD@Tð€ð½F ³ÈdéÉqC÷% úUР?’@ȳ!·?îíç¹ýv˜çŸæï›Ã;`¬=këÍ0Ƕëë¯M›¶T'bVT@žò×iøA¤N«çgØÔ±mÛ°*¨hDÑHSÿ§h„&ì±×®ëÚSSS¬@˜@Go"sÖîN*;S‡ßĢ%忤ýæf‚+ .×6?ßÿ³›®KÖÿ0ß”µ?t›¨&ÿ]¦kó1X›ŽÀ»üª§þÞ¹á½ú8õ'\ÀæN‘Ð<*ÀâÀßGM!üt2@ ÿ_Lð­…ÿ´µ­á$æõǼýø³ãlEÔÏJóçQôœöŒÕ`¬? ›Žd`­¾Ú´­PÌ÷¾«[ó‹r{™ë¿¬µ†L‡”ߥâyæ e¡þÊ÷Zô{K¬ìŸü,÷Ÿ,µºX,¢\.§‰j丽E_Tó("dƒ·gffúés[zÿ“@A+ù3 ZißÓJØ6mË­]$‘wó!³š ãtç?oÀXu «~ þÌ;P{î^¸g‚wé8}}1˜U¥N÷BJ!@¿`Ðç¬Ü ,%èÛây À/–×x»`ª^HØ};ôuØ¿ý‘¼~Y¾OQê'Ò£O¬¨·?ôô×Ãüèçél+­£ ÿ¬MGa®»F ûiÓ¦-nH[¥•>N« w4Æ¿S! Ïój–p8ò‰€,@¢Á¿&ͦ§§‰"þGêm™‹N8,€]gÆpµêño¦È™×c¹}i¿W“Œ/çvÐ* ïÛ½BJ£°¼ξ· vöÔÎ<ïÂcð.?”„)Ò ¦3Lðe¥?… à$€Ñ@äJXæUô°ªAz^¿ZÒÏ ¢ åX~|LÍß*ÁÙIÿ!˜›®×aþÚ´iK\Ã¥=¶kÍwv¦‰Œ‹ ¨—lj _‰c¹Æ#ÇáZ1!@={j`É;“Zî¯0==Ý ¡6±SåÍ‘é–BÿÖL‹òõæ=Kõy>³HŸ‚ sóu|óÆ_„{ö;ðÎ? ÷Ü}ð«eFJŠ,%=À €zÀˆŸÿjkd@ —v1ï‚ÿ×Ö>ÐoÅòúë@Ÿõ×~ |ÞçåÞþ¾u0Vä‚~ֆà ¥}/´iÓ6/R ì§E2§•L# R˜™ú¿S(ì˜dÒÃþõLª €Å ¢Q³RmàÅr¹Ì"LÙYâB€q€‘–KŸ•"·vg3`ºþ…äƒ/çATë"¬Œ{»”¥þ:iÆð¾a×à¾üfÔ^>ïìðÆ¿Kqÿ,Y'ÒûÏÁŠšh„‘|MzõŠìQdêtìž.õ\¢I€e ø[ýF âŸúùç + ë¼ý$$Œ±ý0׆¹ñ0ŒÑ«è.GßmÚ´5]ÏäÅiÒ>—†or]7‘t`^þZ­V,•JÃUL;@–¤àßDÔë¯A¿&–|Ú—ÈÜÜœ|]œžžî“«Ú¼¥þò–ûË:G;ŽQÁL’VÁ|ϹÒVm½{ßòôW9©å©›Û³f—êQ{îwá ¸g¦Û}ðg/Áà ^Vð‚©E¦ ø^X"ø–¸^2*@$@Hx"‚I·Âo×ýn?æë5B[@¬d_(äÿЊùYJx¿š×_?—ŠÒ1gh;Ì5WSà€nûAúFõzX›6mó&Zµ´üÿù¤G)aýýýŽôª2ð/K&èÄGb=j`ñM€"¶¾ÉÉÉ>Ö€YîJ¼s¨®1¾¼³PÏÔ@V“ +½g]OÕÛŸg²[NÑ1Æàz˜C`o»îå·Â}ù8ô_y¨NÓ™×à_L1!—Q¬Ê€’d< ökÙ×DºÏHLª*Ы³¿ŽèuÀè'°BÏ|$OŸ{õ°‡™ú‰¡áé—Þ~{æú£0è_½æÈ– ÿ_›6mÚ°~LóÞËת³Cªú§à L‚!Éi¢~ßìì¬366æ"´Z”¢ërÀmV @›&:¾$P+°',W¥6â{O%ò²lieþZÿy« duTmÚVÐO éÏÛO“Êþ-{r†E¬ÝÇ7ì~#Ü‹OÓí ÔÎ~¸t$Dµ$€(%ˆ UÀWRˆ¬&À_ÛaÚ@ôÝ ]€ëÔ5Z!ºñ¥ûbOþôçñò›B½ßˆåô›1ЯnF]Å_äõÃ,Â\w-Œ5aŒí€ÉBü’^ïjÓ¦­'ÖYI@faªV«ÙŽãþûI€ÿ ƒi°M„5° ·`ñ?>>ÎT)!+Ó0æææú§¦¦Š´¡ò–4ÑŒ,Эj$•æ˜OÔ@ü»’H y~YÓ=²ÿ/«ƒkk„ ~ÝçóÿuÛÿ”ÖæûõÐ_HûL½F¥aX[¯‡µùì½o„wñÔ.œ€wîøL/@ r§ ˜.ó†6~ (_óÆ^@ÿB€,"! k éò+ì£ð‡`ßhðòŸÄÈ™Óo¦ƒþ0§Ÿ>Z}0WíáåûŒ±]0‡7kA?mÚ´uÄØ:_Îëq½²¤õ~ †}’4“Ô©&?ðF¹\fáÿEúܤÇ1'*“>¶mÇGë$@—Ô@ÇAQ”ÿˆÒ1Ìééé×u‹Y_›¶^ÿšàñ„ÿVÚ½K$è$n ¬ç›¹íFø÷À}õ4¼‹OÂ=÷üñ§¹7“~"ôxdÛãòê>êÞ>÷ÃÊ<5@‚ý¦„ÐTPpKÝSWà`Fº—Ê~ÃTÊø‚ô2@¿þFÆèn˜ô›«wÃÙÒ·Jß+mÚ´-êz3¯nY<œ?Ú̪ L<½@Á¾É«Õj•Ð笠Ô4É Ôs3|¦ M´½óѸäKÐÆj—Ëå~×u ’ÙŠw¦…ªî/pÔ`TƒÝ.ÐÐo“Tÿ{éštú·V¿|d+0[«n7qÝÎdÀ™oÓçOSœ^ ÈN´ pÏS¢B‚ÄRüB€„„€$d‘B|°à7΋b½IvyÿõðÚe`?Üóð§~#ü<œ_äø‡ÇI€_Wí'‰ ß¦ ÌÇ`Ží„1´¤´ŠjÚ´iÓ¶Xk€<‚ßqðßì˜<ÇÅ#‘ékËó¼’(ù'«ðGF‹EB1VZÐÀâx¤˜ƒô}[í ]€' t´R¾#m_žŽžãh[!×c¾à±—Kä¥MbI,¶¶˜Q`Ĉst°ùzx»ßoòÜ‹OÂ;û ¼+'á×Êb €?‹0ø¼m ÝÕà 2@D@V`‚žLPDCBQR€4’©`Ñ®&4øï! ßöDï~$¬ßTã€_%Dþ¿x^÷òaÉ>ß7ꀟ=EžÏo®¿ƪí\X“G´˜Ÿ6mÚ–d}Ó̱ØêÚ6Oå°Œõ ýw …‚%Eÿ˜¶ÛD:@ø þ5ÐÑNCDã$J'aOxÝg±Ö’¦ÞjyÀ¼ ,ísí!Ô¦m%O–jîµ€vüÎù– "¦cx#߬×r2À/_„{é¸çwña`öYa~R?ôðS–„û#!€ Ï%D:€dç%@ê©<í@’Á­“1bÀG‚÷_/1º è·öï~ö­ Ä^ð+ž}Ëá÷Ã}ìü$êågÝ[ZKAÿ! ú¯¡íŒ¾ÕôkO¿6mÚº’HÃyÈ€¸8rÒç’RD*€Q­VY€­T "€ˆÈ€Ü)Ú4Ðv“eÿPgäóÓ@3ï’8`¨h÷~!ÚÚwÍzEDn¥öõ¸€^𢭾w9¿ŸE ¬èf®Ù oûíð˯»òü—Gíüð'Ÿç`܇É5$Ú&¾_÷âR;À«Wàd@ àK@V„ T-%:€ŸW'R2ÅOÒµÐâ øM>r€ýºw?Pñ'â¹xß0\¬ÏPrù‰Ð¶0뀟{ù ŸíA«÷Ò¶¾¤oÄî×7^›6m]½NH‹NÒ>J2&ò'Гœ%iŸNS¦òÏ"l9¸Û¶ÍÖ ÀÁ?KH© WMtÖ(À'BáRÕVœ˜˜èVÙ­@¿Úüw~ ÕiÝ ö“ö'Ý·¸0`·ö™vý¶Ž[@ÌJ7sÕv`ë°g¾^ùx—ž…{îaàW&‚ÿ…a,Ï~À±@—?÷¥€¤ Èc!}åyH(¯‰ÔˆE Hb@%T¤ï{©`V‚ÿˆÿb% ­4ŸˆHƒò#ï¥}á$RÁ> E¡Ÿ âÝ€ý:¸~#ôò‡€_ýÅÕ07\GAÿÃ[h›¢û†è)=°jÓ¦­«×£yÒ‚³DüÒΓQœ$ (7ôçææŠ…BÁÕÕ˜( ¯°&JæIЦ €Î-¦Eƒ$B ‚T*žÂÔÿgffúë+¤‚‰¤ÅqR^3¡Ý‹÷…”EÓÖúÀ«I€ÞËI¤M¯hô”H¡]â›1´ `ÊÛq0;wêexž„{ñD.àÕ8îb>ûàß3R@fàé÷…v'x:@= ¬4Y‚ÐWÀ”(0"Äš“ /–)Þü@qfÃíྠ€ŸòÃ}@$ ãzöI ì“Fï>kâØ\€ß9FÁþº«a¬Ú£o ¤0âôéAT›6m=þÓÖÿyÄþâÕ‘dô3ÅC‰k¥øçUÁt¶‹Eôõõq€>ç%פ`¬@'g(MhKm° ìµ°š•´qöMLLô±÷³Äúòú$V-ïÂ^ ½É÷Y¿K wŽ—úH:F[kʧ½ŒW1”Õ§z)ç¿¿Q¨ðvü7$¦ ”Fº£[áo8 ¿2 ÌMÛ>ïâSp_>ÿÕÇ)–Ÿ€AT‚D%Àw" Ôð¢BBÀk$ˆB øÑÔ€0j ý*I ‚øûÊþºyóGøyÓ:Ü[a Œ„sâ“1€ßöI}_Ä£¯}©æ_÷Þ7€ý0xî¾/ÎABàÏÆnFúù%èw)Ð?xøY™>–ÇOÁ>qúµ€Ÿ6mÚzÒ’¢ÙsÓ49€;<ÒÖ@ñ5m|½'fâ›z ûÞÙÙÙÂÈÈHA„úÒáÊÞc%Q÷ü'Eh0¢ €ÅBÕÅ~&^Ñ777W’ûÒ”úó~OÏ DÓ@½6mÚòѤþ× d@7ôõV~Cóãèxfø†¾U0F·Œ¨¾~eÞäx¯œ¢Û º=N÷]VÔ+ ˆçr_€7›BtPÝïÑTÅÛŸH€“Añ/¹ A¢yM€¿×‰0KîÑ»cä ½û‰_zõ# _ÜH¾tðC€ùˆç?öA…þà|ò=žaˆïë_clÝöþÒ¨ü}  6mÚ´-“uNÖë<óxÜñ'Z˜ï™×Ÿb« ­>‹®fƒ2 pL~´i`±ú c¤ÆÇÇ &N!:o ´Qö—Ëåb]¼²9ðo–sÓjGn—x & LiëáÉ0-âa%¦mtíøa:<Çš•Qã)믅_}P›ƒ_¾ ïò³p/žä¤€?ù©!! =ê²Ò€J H /‰%êB€>â©É¢$¡Tˆ´¥ ^MAðcà?ƒH˜×ÝHïFÂûßHŒ¨h_„Hø’05}3ò:ú¨“¼v„B„`*È—¿=Pèçç2 £ è豈/]iþöÞ¤Gš%«uÆÃ#2ó;çPuªh q/z‚< Ð â0€1þS&0@è"„`@#@¢‘€G=UÝG=šs22ú&óÙö´ßNK3só.›µ¤ÈˆŒÆÃÃ}›Û^Ëvs¯<¥ôUpá çs|Þ¢†-÷?ÄòE0Û+‡ÃT cu?>Y€"þc#Àµâ6€š/¯­™ì«Çcê°ÛíÒétškôE }H‘a©rA€ IÀq[ˆÛm¼‚åU0ž(2÷áõÜÝ>ÿ?¢ Õ8¢è¸‹^V߉Î_ü?ÑóSÝþïèyI¢Àù5Ç;zù¸°ýbt R™µ|¯$÷/²eáË‹‘ûÿö½ñQàå-ÿ÷ F7‚7u Þ o·W@Fv‹æÊüÇAâØÖÈxÙ$÷Ñ[‚.aûæë‡ç3Ñÿ˜“ëÜ}^ÕÿøÞQviÖC‚Ñý÷g+ûcZÙÿìD£»¯f¶Ã‚’_Øèù·q“!À\…åö\Ïóç(@ý;£pÿgþŸ¥¦i¼Ùl‚Ãÿ5Wƒ  ^@þ>Ÿ§Šüß){å9Æ>’^„ð‡(v>rŸ'€ü—»Ý?w¶ÉÊw~Ûš^Su¿ªˆ77D䦋ìÏÕ5ïÃwG£ïùÑ(:³"‚Ñi=/ÿwtþòߢçÇ^¾ø—èù‹Pó­¦çÏ:?<ÊV _˜h¿‰Ä>2ýøE¶04{üfÕÞç£ÈH3MÁÁÿùý¹‡×Ò€¹2ï%ÿ±!˜ŸaÒnü×¾ QÉý½o‹ò½‰‘^Õ×65ý>ùŸÑèÓÿñJô?ýEö¿Ký©zßô•ì#'?]å·‰y>”L1­Ó‰£¦´âO"À~¿u]µ¬@ä/'@³c†oÜ€ Y¨Ç“õzý Œ4 ùÏ+Âçsà}=;ërÊÍ ¸˜,ú W^hÑ˶›:[6ÕqäZÇíãw¨sGÄoÆ­Ù>‰F_‹&ßýF/$¨Ý¿l¿ˆž¿ü–ºýkôòôoêþ_¢—ÕÿŽ¢óî#éçYà «ÿ ! «ú¯ï­m_|úí¶á7£bGøÿ¥õ`”ÃÙ%a÷fAÔõóîœÿØ*¼ˆÏòkoþe»zÎa±áÍûÅkñÇmÅ“;Eî0Š?|4úð}êœ~O–BUù3²Oíÿ2Ò²àóùmÑÇ®Uû*¾ˆM4Ð"Àøt:Í&Ç㑟QEWS €È^Ð6bå@=P?æ6€²ÐŸ®R9[­VÔpâÊm)BnÓÂ_DÝ:_\™–ŸwMPC²Å¦¯3MOó¼ù¿/Ö©“”uþi4þ좗çÿëuÕž…Ý2‹x^þ¿ÑËÓ·¢ç'õxõ­èeýÿ©÷Þºoj Dº–@ùÂüß’þwgôMêÀÇ€ÈKü_玃¾™Ûmú%ôÞ3ð~»üù#:À$ùÙ[„ðÀÂÀ8¢»¯Gã‡ïbEðã{õøþ»Õã¯Gqúðú^"øê¿©9ñóCS}Ѧ˜@ ¥.¿DÎ×”û¯ˆ?EL(€Ú¬kÒ² »î`Sù‡ÐˆsÍäŸzœl·[Æ6¥«HÕp×独öÛÞSV@ú D€¡œ7W%[œ×úí¿©cj 9”U‚¾“rÐ)'œ¯Û, |ú¬Šÿk{@ºÕÝ9ж_FçõD/«ÿŒ^Ö߉žW߉^6ßÉê¼ìþS}ø¨7<~Ç¥ÿòâ§äoÒ "O_eþ„ù3ÃH{ôŽ´¿]•Ï=pïI¿üîØŒÐÛ¤c½øîh¤H~¼ø<Šï¾šµÚ-¾’ÕsˆçŸ\ZøE#QD ùi.FB¸€¹hb+˜3Îç3ELÇ㋎ýÿWHÓÔV0vOd€šK½úŸ‘ÝÃ2¦6êùtµZQ À˜ „Ù…Fì«6šÃoäÏxl[ªV¾F7Ìý“ÿC¨G0©¼íùòG‘lû9¼åþ´)üßEÛZÇ1Îñèý²Cúáµ5áEÐ-é{ϧèå°Ž^6ÿ=oÿ;жtÿEí—YTÁËþQݾ|½¶êcgƒ¬[ê ¼‹&9ô6A!ö‹>"ÿb=f®t·Ÿ§÷Q4û$§ŸFqú™"òߥïéö!ëà«c'»puþK¨?H>@s#¯ÈÛø†É_˜chÎóæ³.a€ÞÃïã×W¢*ÿo¢xÛ\T™È>E($ AiTÐò@h~üè°ÿX2Ît³Ù,"^â txmù6u9ÖeÛ–rŒ—žLŒR¨«’ûÖ'q¤Ëç²Hô•ËÅÁk»ºØ˜b5ãÆÔVîáëÑø²‚ÿü¶V?>FÏû§(Ú¯¢—ã6²Gýÿq£þ_G/§zïéõÿ—³ò¶t§ƒ¬¸áæuûDœÏ{õòÎ_† ýhš­Ä¿z ³lÇc* 8JtšD½LÔ{¦wꥹº¥êõùk»‡Ú¬PÆA ˆ!ù=ejõŠ}?Æ]Ë7'G)”IÃʱn[ñ?³UcH„Nó'‡Àí.‹"à#"Îå9s妬ôß7+ÒÎÏõþ¼íäÍÃ2@ñªäááaFÿ˜cqŠ ˆì‘ÄÓÕ3p2£ÔFŸÏçÅjµšë—JUé}Ñ‚de ˜ÁYlîØížÍ|qI¢éÑ‘"4oQ¾¿k×>[š–Ínq|d¾ Wññ–"ƒ¦P H¿âùÓ„ç0ò) IÙ àݦqF!46Vèöøø8~Šs¼Ûn·i¤Ð׎#Ä¡.›§’:}'¤y“aß U“mÿ®íа oCµkÈ›7]~kNÉ›_MNãª}¦ÿO§S2™LRj¨Cª9 æ"ì‘?“€æDçO ÀˆZ‡T¦ØBanáÌAhX îÏ94óÿåxJ-€2¿«mi,UÇdέÉ€ë87çåñ•HÈÐ9صØr:(U}Ƒֺ`vo©KúcL€j,±0r ÿŸ®×ë{e´3®žï-ºäÈ‹©s0Wéïéû oýd¡ÏÇ´ ×Ý ­K¿cÈûèkÁ:òUøC•ù7$}À·Púüü<¡ÎjÀ«þÜ €4MMâo« @¨| ÖU+'Ûíöî|>'®v|EBQ·nG¶Žˆ…!^0q¼º _8Zž0Ðõ »ŽkF !†ŒÑ>u8Áµ(ê•W\s¬™àJ5E—«-¨Ò=wȸ+EDï#\i˜!ÔçWéœÿ˜V·©H–ËåÃDÚZØ*OqÖ¹'&¿·'ûfÚeˆ®Ë=?Ízf…é¡;št,¸7*„ƒn“"…;ÛD›Ú—.Ú¦KÌ -àØõ±˜—ÇÙ6Ûn7WHâmúA¶TH“‡„zYùß6›œb6›÷ûý|±XL‰ðϢש€¢Yq’$®üt€Ð¼sÉáÿ/¯H”±."Ñb±hµÿ­HÊ@'«Gå/¢8vÝ+ÊÐlþßbm ¯‡ ù‚”ž7Ëø²!)”y‹¡â9âV‰XT9€:Ðë$²ƒüCh ëõúbd\˜‚îtXJºZ­Ôsã’^fE¦Žþ"û"[ì¢7Ä*ã}<]³ÿ[îgBÿ‡v-s…`@ÞÜ‘W¼L‘p×j¿k(×ŸŠª/ J«iA R¤¬[Ž"Tÿ‡p›q’ Ô«r¤5}zzZPdP”QÝòÈ|‘">Ùµ/p&ý¹2=Ç_EŠM×…‹¶yŠ×@p]ÊÎ!!ü ï¹E1W{eâXÇãqJ`Î%kX: ß@ãŽö›\ÏB˜o6›‹`H¾U&] Îz½Ä&TüqõCn®á`ëß%ûßü…k àóIåÿ.®1ÒF·#kžq€ý~ŸtÑ?ý¶×TªÙ B €Ð¨#ÅiQàx<.Öëõ‚ Ó¥h¹O•Ü›ï*C@*Š9ÖmØPAêJ=Tþo×±l˵¨L}`˜ä?‹Yز¥©“Å…‡•˜¯Š>*Tp¬n‘Hˆù 43Nˆü/—Ë+Tú~ôüü¼Øl6iH…ý¢-ªªÙò‰!Ÿé³(P—3ŒüÿnNzæ$Ú!h¹úy3ç\‹€*óH^‘' ä}ñ||8(ÿN«ý4Ÿi! ã¯:€I¿kÅ΀ºÇ‡NFyÍ&Q`t>Ÿïöûý¬¨Á• ÕÏë[n{O‘ &ùNÈîÀ,à8¤ÂU‹Šöü÷yÌâ:4%”)è¦m­ÅO§Ę蟎¼¾äÿs'€ÈÞ0v‘7œAµêK9^¨PÅÓÓÓƒú?e&qÀ¶"ì8®Þ˜¶÷Êžšò³"%ÁKú}Õ8Íþž2/'TxÒEÐF BVÞláæ}$ÚNþÉÆùVvC!‰u·º–ý¹®[C]%‡¸È·Í·Í[é÷½OT÷—û§§Ó)¥n¨£¨`œ¦©ø#5À A9¿)zÛ0Ò=)'»ÝnA=+%9·åÂuÎ\Uø‹æî›Ÿ©ê¢×ö ŽIà„ÒfÔÕÖ·ŠV(ó½·ØO.êhžk3Í£¯c©,@¾}ш?×¢$ƒxÍÑôºœ«-¢­ü'ÓétÂäŸî)%@Ý|MÞ†|8å±^¯c.ø§‘®6ôd¹\>¨ÇsÅÜ@eªÄû`™ºyÝÌAg²?¤x¾\Q0]š ÛH¯ú"ƹ®ym=×UE\ƒ€²ó‡ƒ˜ÂzÈ|\¤¦€ŒÐïŸÏçùl6›èùú"P ¤t€ÓéEö PØN$¥Ü‘°RZ¤P`Á/S¸£Ž&ŽGÉnû>–Žéêù¾FŠÆ5νë;\•‡‹„4Àɾ‚/å9oû:-st<î ÓbýØÖÐŒ,@@9góMø?ç®P‘ŠÝnw¯þŸ¾È€¦‹tåEÀ9ñïûoìÚoºe§‚6†þ‡@•Qm¥Ê7DiuÍy< ®¹Ôܾ¬  W÷Óù|>eÆÂGDî°ÀDT¸€Åñx$#ŒÉ@7›Í5(Cœ«¶ÓÈs]Ÿ1‹úŠõ:ì8Æí™m÷m@ôªå¶ U¯hûÔ9嵯â ùjñèT€˜º«)¢?‹>¦_ÿ§nh[ý Ô?6"a…Ü @áâééie¢U½E˾¿¬3Ø÷âXPÇØ»öø¨S0*+X5ýuo?4  ËvÚ§ßÜŽôçåæ%ÿ¶…•ELùúáp˜¥išñ×5Fš™¡þ¶ÐLŠêº Ùâ|½^/ôã ‡­n'¸LdÀ-ˆÄ€•¸îØHW¯ºV®û4fóŽGÞëm:eE\k€.ùlybƒäX§ÓiBmÍ‹kP7‚ Vg3^.—£Ht Uj·ÛÝm6›” —{Šç­ÌÛ¼­7¦ë±o`ɾæf•ss{fq~ŽIGHžíÐmÃu^‡<)t…ü×1queb.;fÛ˜ZH•Çg»t”µ'têœ?eû>æÌl~®Ã8ªù_>{>Ÿßñ s¿˜¿éWü*!€ÿóóó =G‹­“É„ÚÆFÔ€ÐÌx b<>Nw‡Ã!ÕÉ)ïN—ÃR™:$¢ DSQûl´j®^WÆAH—\oÀÍ=êà6QÁ÷ÝRˆ8ŸÏuŸÒj?þÓâA jD¡¤?Æä  ¼¿(çÓÓÓƒ.JYN:©¾žœºÈ—«ug|¨¢Æ5ÇAò»ÛfßEöǶº€ë øý›ReÁ&äu#;NÓççg*ÈmÖ³âë$P@š¦æê?Ò 476ø¦E€Én·[(#M\+ùUûNuxCœÞ¼ê›(*U$¶H¶ùX`ßšßǦWþÛ|ì̱¦ PpÎ÷E-—Ù¶kQS‹ãóùœR¨?‘~ýúˆ£,dßEüAh ”¢êdf äHªûéz½þ@÷²G·Ë ›È¥õm3$‚À7°`Èö±_èzEØpùÅÆŠ,0šŸ )Ì+£èñX€Ùl–¨ûjÍ)\ÀGò  ®ñË4ºO¨ ²Ñ‰iÄUze†ªp!Þ¦Ô!œ¿NzßÏË0ÎÝ5„âß᪯›€zý¶~—ÏëŠ( ¼ÿãñ˜¥(Î?fH ·‹Zf$ Öë5¯þËÐÿX+U)ÕPÇ2¬>¯Ú¿é æ9©!¢‚­ÊŽó0I~ê—p—Zí§kóù|ÊÝØt—€ F€H¬üChfŒìv»øÕ†ã”j¨Çã¼PspU!à6!Á' ‘0À1ñÄñëǹ ­Dl‹p’]Y0N Ÿü—á®EÊÐO™J­çïÑét¢€„Výõâëˆn`Yý‡ã  þ±‰.]‰r¾Z­î"­J½9Ⱥ—¥¯FHoMs0I'׿ðêÚoöÃ$«ü:¿—î½™ÛY¡´ÕE€>öþ.³7m™sÜ4‰¬Ó.ªVìm˹ÍÝçóc^ÃÌkq›®!6ÕdÛWÄßõ¼9wš$ÝçÿújØDÉ_˜Ç$MÓ‘~½Í˜k$I‚‚®'Ç”©ùv»óó·"ueê ”­ä t‹ônBÙ6¦ ûÒ·”vVÚ¸újƒ]/^Àÿµq97ÛR˜ÇãTý­öKòO‹°Ô ÐH0»`â„Pɑ̌h¹\Ž´ñ^ŠžN§ûõz½OG!©eœÀk R8šåœx CÀ’u2p®os­(ú=æ5·m«ÿe †Î'PvŽu-–™›Ít<ß¼&;ˆ×ãív›(ò?#¯çó˜SD}¶(B@MÈH Š”‡Ãa±ÙlfòùêÿErZC`hêÈ?0d´™@ÝzßêîT2¢Ìy.r®]Ee Î9ÓWcÌ'äñ—Ð.drHP<‹º¬¥“ÉdLäŸÞ£g…) MÓ< ¶ð9@aÇíÒ€€ívûp<SsEÑEðCŠõ…~Ö×04¤4tÿ o"@ÃÂ1æê9&]!Ì¡ aÀµæZW®¾o^òu °E±™ï—ó ÑUZ¦ŠìõsY@j¨‹2©G$€úÇ…f’%¦Øn· õZÂ!¦¡JX©/Z€«Î‚]p6«;õ}šÚœC]öÜøÂÒ0ù·G¨¨Rİ‹çÕt~p=ná T{l"@Þ¥MPüjr>Ÿg c޶¦{J ¤i:ŠÞçþG“)€p¬×ëø£-„6ÔéÓÓÓõ0)CòC¼:«?‡¦Àá¼ i®G²Ê¬&…´ýkfŸº$äu_€¶ˆ¾×]s—\X2¹‡­ÃýóùœÎf³)Œ^£°) ~~~Îî#{€M€–ÿ“ÝnGÇ> ﵪb•U2dˆç§mb×­÷£o$S:m£>[ Í™€kÌUUý WQ[W4-öÇÙt:p àåÉ?«N—ƒ8ÍV«Õƒ2‰4äÐ~˜f¸§+,Ù1 «Zç©l®×lá¦ÔsÓ¶=8 ù¤Ív<»*ØŠ·])o t=O7ê9º¯CŠéúx§ë#×eébû?9—@dnáÿI¾Á>S¨?ëëŒF7ž§MîÃs·è @×Òù|ž?;KͦŠˆÜ¡ÿ˜@!Ô1&>Z±.˜R @u?r‘¤¢®ã#øeZ„ø7G’ú´Z7Á§ cáÖûP¥%Î+~Я9ÆÅgBCý}yÿ¡>‡¨­6:NÉt:M(€‹r+@þEˆ€P3âœÿLØï÷™ Œn¾^¯ï´!:óWòoWµL× ñ Êz¡û ›(÷ý·‰Ø€r="ZõÔ…¢•!û‡k1pmŸ ¯º¿k2C„ÔI39G(Þ5[,3£;Ýâ$IFÂÉ@}ã‚ J|:†óÇÇÇ9`Ùâ~!¯W©]W—8¢îãáKå€Ð/Ü—ï.s~»RY¿KÄ9äx"ý ¸å\ÚD}³¹E€Ýn7¥"€´âO!ÿšwQ@–  E[ „PÉA‹—ËåHþOx8î( º”ÐWäçÛ|È`±õÂt­l•%í¡á=ò_/+)Ô–cpý­«íPŸl×d ‹oN M ²Om×)êšëZe,Œ¦Ó©-ì'€ê6.œß‹‘)#ï÷ûÅáp˜IÀ¬>]w?ͧ0´Ï¦ësp8‹‘As忈ÀÓ7’<¤óÞõcÛÆðÿ¡ØkÈu¶íi @¿½‹Üߊð÷*®5UÿR €±.þÇ\L >Î,€²6øÖòÉ7›ÍêMêÐùrýó™Ù/3ÏÙõ9Ž uí mwîûl'UÒ5ªž·.¼Ö>W9¶EDÒ®Œ9\«€[ï"äßùlû¬Ï¸¢žùŵ¨à\‘ý‘àa$P=¶Qš¦Q€A€Px,†E)¦ê6WF™p[Œ*•´]ÿËvP®Ö€¾–Àõœõ>•뻹ÆO¿i¡¯¶Pu…¡O«åˆÄÚ8'åu)s-R†ÔJs ŠgÕuë׳(€‘Fô~µßµú?ø‰@»—$\ÿO'&ŸA*\^wÊÊ«¬i{¬% ûpšdÓ¬9`þïs(eNÊ« qúá˜Ú G‘Uå¶;ø} ?–µ4ò& .³²ûiSû¯au¤HÉ)$Êòõ¸K×+ßuû®>ß!¯µ¹Iö¹j¿oŽ'®ÁóÍÕô˜Ÿ;ŸÏïö•^SŸçI’P+ÀˆS¯9€""w&UD/|¿ßïcm˜)µ$ÀP·*Àg®ª¢Cß0Ûa¢Oµ€n“ç¼k®QÀ-æÒ2 ¶Eø…)8pJ3åûS'€4Mý=˜µœL&&ñù‡PùlpóÕjuOÙX9üßWýÒL×j¯áÚ¾ É鄃 \{2íÊ>\³øßPÑkD*àÚ¾‚¼w…ò‡ø¾ÿ}í´M~DÄëx<¦$ðÂ+‰ @÷º€Iø1B¨ìˆÅ–Jáó§§§”µ<–ê—¡ý¡« f ß(N´úncmmûWõ{®ÝÁáZÇÂ$Ж9ÉWÙßõ8¯` 3™ÏÑâên·›RÕu½Øšd§4mjH£·)f4œ}Åìžn#MôY \”»ÍfsGFÈFÊ•§]2Š8¾¾þ›®*e£ ®…ò@hèÎDßÕ1ÜÅ6‹¦C„kp+ŸÄV€ œûê‘é†åLq€ˆÖápH&“IÊEÿd Àt:µE`"…PɹŒ-Æ8:‹ív›hÕéM¾Šl§Wd•)èk#XæûQÀDˆëÞµ¾gèÇßUðÚ0÷› ’yï³ùW¶pÛöÌ–¾òýŠw)þ?™Qµæ^´«[†4w|“-€@r WcÞ´¤üê@)¥±Úœ9ßà°_.¾K$àj˜†HáÍû·­8qENߪZ_œÓ[9ÚpðÛsß–Üÿ¢×<[$SÛÄ›SÒÕñ! ¸µO y ×4Ëã-¦ÿàúé[˜‘ü==}P†:Ë#Îeh_AbÂW¥A€:m¥«Ñ2×$ÿuWþ7#|‘Tmï¸mŠ´òsÕ pñô5GNN§S:›Í¦"ýz¤S"]ÀU 0ŠP @Öëµlûg¶œª×ê~ÚêÏ弆vu„ËÖúEy—¡mEÏWˆZ[íúÚûÕ•ñëÐÖ¹)¤@ÈÜâ{ÙjãÑ?3Eô'ڇ˸,Õ]ã€T B €:ì^±0Èt½^ß+ƒ›pÞ|ù.RÉ¿ á¨+ `H4äãÃ&š ø]Ëû¿æ¹¾Ö÷ÈÐB›C@µ9¼J›q[:€Ìû7ßK$ÿ|>_"ôgFŒ4Meˆê%Z ˜­×ë»Èàsx‹†Ô” Ù÷åöÛöWE v`’Á¼üñ¡ŒùÛœ ­þA‘Uÿ´»ÐÈf¹XGÿŽÇc2›Í’ø-²ÏÏϱà·1È¿‚ âÿ&`¿ßóÿóÕjuO†Vg%ñ¢Åøê\Åš:Ô^Û Í¿6ߦ;tå™c¼m¿Äh;é/2W]°´t¤‡Cªˆ€Ü€ÃÿMÂIÖ" Œãq&l·Û9©M.^—ãY–äç½ßìÏi¶âŠ0¤ß;t²!Ï5·˜±…›¹ì¡ ǯÍyÿu¤)…8¦€âzPͧ(²8™7G˹ٌÎd¿ÌünzíxŸï–Ëå‚ÂQ¸e“ I¤¥¡sx½­G¦é šïs "neæ#-¾ÁiöÛôåÈömµªë}Ã!Ô# MË“¾Î5þ[ꨫ÷š¼šc¼­×hÛüä$wp½ž'¸ºô˜­é{è1?Oœk³Ù$ 3ŠÐÛ¼¤¨ç¹À›ÝÀY…PxПÇÇÇ‘606ÐÑétZì÷û™$öR(*Ò À&„¶Ø1öµEúaWUŠð4ý®!*Øj<`<ÀuæòÐNd.!\Š ²˜¯¦“Éd&Vû9 æÖ€‘ÅmaÆA¦¬þÏF3^­Vûý~éÛJ¾/ÆEømƒFVÇ Í?.S— ¡CçìaLÊý:OE;£ä‘ë6F@›çà:y‚o[&’éšçóyªîæI’ŒuèL‘t/Z ÿÜ  €J!ãÛn· uŸäåÛ ¼lŒ*ƒ¬JÎŽ!™vþÞ<ññ–¿¥‰í‰˜Ù€rü!o‘ÐÕr×÷Ø· _ ‚ÚÆøùù™"ÆTøWý© išú ¢8 €bÄŸ»èÛd³Ù|PF7ãp|[Þ½­:fY'ÖŒðTÙ C"@™žkìs]…ÿB^3 ×wßZÌÄuèšcŠyQΡ¾†Ék\…y^W÷’$™œÏçHG²H“¿EöÕÿAO‚mÿÕ Eõðd½^/ÔñY8¯Žÿ*¯W"†à”ò9²Emõ‘"8wר§®®ìc|]lÿ»Òš]B‘ïdNC+þ´Ò¿ßïÓÙl6‘yÿ: ¦"€9íQ¦ì†"ø¶ÆlµZÝ«û‰4lWULÛ€ðýo{Þ|Ý%:”|«‡¡á;]r¾‡à„ßr¥Ç÷6rÛVÿ«ØA^DCWm «ÿ@—üœ¼V€!ÿ›ÑÌE‹Ÿ žE…ØgÔ È¿~>kHuÒ4•äßÇå ~#ZMdáz²ÛíîÔñ©hmÅç"ÿæ~ÈýPß:ûvöñ¼bèƒÈÑDá¿:W•kJH“«À_[¯e ÿ@WÈ‘ÿóæf[×—`F3K@ó°”R(äŸ#ˆ«ÑÝèU%@Ø?€jöéôel±"ý™]M&“ùÓÓÓ½zL(³6T~lu3: ÏÉ–¯Ë¶‚®v.F˜×‚Ðõ½¼=Û y™F ê1Úï,KrËvÙ¸Ö糖¼wÉF͈¨"-Xo1&ÿtݧ³u>³½?oASr$¹Mù¿â_o¶5NãÃá0KÓt&¾+KÉN’Ä\õAü!”u°/"€þ¤nóív;ãKy€Úê¢ïçAU•Ø@_m§NÖ–’²­>ûhWtNÈQhc„¼># èÿ©ì?¹¶áó£ä"©­Ó½v8J HlÍg³•ÕàÍ÷AÄÿñññbDÚÁŒÇãýÓÓÓÂ4¢"«æ¡-«Êlè‰Ãq¼î~6]™þšbF[‰{×/åæwW+tßçå{LÍ ("{ªH~ÊUÿ)úZwQ„€ƒü#@¸­G¢ @vÐ(÷dµZÍêl…Q´˜Ÿ¹M[nïÞöÝC õ¯“u2èq“«ÄeHhWíäZû|Ícc®¤·9Ì^¦gáZôÁ¿ Y´,ãK¹ŠòŸÏSõ8N§Ô f®Æ@š¦¶ûvhPhÏuÚÞåŽÇÉf³YP'³à_‘—ºaÎóW¤òr‘€ðãØ…cXÄÖ])=@;Ä [q!ùØÌlÛ56tÉg2£Øló›Ï ]……ð09ŸÏ3jûG\ŒÒ³¹ ÝGî¤DˆÈµûèmø?ßO7›Íƒ2âÄ6Êú:œÁª$~ˆ"ðzŽŸ<Žm\Šr—Ä2Eÿú\øÏt,PdšŸŸCÚ˜‡Îñ®Ï{¶7>0Ñ+ÿ¯¥:T@G„ÿAD8°^¯cíDÆBIbcœî÷û…zmRÄy Q¹ª†Ë”@V'ĵ•¶“~Ûþ–%Ó]™ô_QTZNh¡–øEg£B€0™LâÓéd’|Säj¿qfFBF¬ Ld£ŒköÅ_<¨û =ïë)SLãæA$‹Ð±ÁËÐU_ølÁƒÍüNs0™ý5åÿ®°Y³šgß/n.ÂØ5B4’s Ò×–óP4uA^Sšþ­m9Ftýòµªc\wAŒ€TFIÆ%719Ї˜ï±Y¶¥H›‘•ü9žÃù¹/ÄoˆÐkîE¹ÿé|>Ÿrø¿æhY@š¦¶B€€`ZÿcCœív»…î=éUÂlN_Ñêþ¾çh€˜Ñ|E²\y<¶Áo:±}phmD hh¸>´H•ºIc]Dˆ/Ôl'¦íôEêªÖ¥Êÿu‰e>›¾u¤ DàšókαñלjËû— .Âo{>¯sÍß²$¢:¨À”»0mS³ ñ‡ ˜oIÆw8FZ ˜/—KŠ…8¯6’Zô¢Lþ®ÌåÏÒ®r´‚ŒDNFðã<°Ä¢ïaÝu˺~wÞSµ[(akëy¬³EbÙâ¡×°“¶w ¹EÄ¢¦†î`} àšþ‡¹'ý3[•}É÷ñÙÏKM6Å…¼´RÉahſߓ0ã…Úè5 žÍf®V€€`âkcŒµñeÀz½Niü¸ÈOѶ}e‰‚«¯´L%pÝèõétš½_  ì&ƒ6­®;PU÷?_‹ú.ð¦£{MÇ×6iÕµÊÛ¦âyM aòx•ls ÝÁ†ýÐ…k•oeßæ¸"ee¤ñù|nüÚjãI»Ý.yxx 6£Ãá@;:"¾F7ÅiÌ€Qd¯°?å’qS¸ )K§Óén½^ßÔò Dˆ£P…Ì™!ý¼ÚσSƸÜO&“7â?/Ÿƒs ã#\Êï5SDÊŠo]:ö׊‚iú{®y¬nêÖ‘×;ÆM_Doà˜Á¾¾3ó·ØæK[Ô²- ÏOô½æû^Ûó¶tdÅÇ’4Mg"];ãlTÀÒÆm!„û“£ÙlvaúÚ¨æ¤:™ƒ"/  Y)ià»PqÎç¦sŽ„™"àËÅÁä@|i~[e„Ákඉ-·nGi+ê \GléâöqNØ7ÎIÓó‘-^’èÐ…L3²X[]1³–/Ê T0^‹Ç#cŸQ¸ÿápÈi)=›ªÿS+À$I¨N€- ÿ£ ðÎ("n-AɰÔã±2²ûÝn7÷-ÿ‡„ôב_k:s€p$½*gÚŠoÈB f!7Û÷wIÈ#®ßzŽÊ†è×u «´µ«c2»µ0T÷d\÷¾×‘rq­ þEÔø¶ó¼4†ªã¤Œ Ö‘BRw PÛ¾¿èö›¾>—Ùþ-k´åü45¾š·]$—yöÚÿ¦ïç¥-¿;äüHrÎÑ¿L7Z¤¥›À’ÈncÜiŒ8¿æªüï« RÍUH÷õx<Òbì|üºS#ÞG]£ÍVÐ& R€ë›fÀÅ(”ÁO¶Ûí= ˜:.¡©>'ÃêO·¼¼ `†öH1  ¡µEˆËPp­|{Wa³<±¸îêÿ5¿£ÍçÚe»ÆµQЀ:A|€VÉMßžÉ4qèþîîî]j0ó ¸-ŸBçU®àËm…ù³ôX}žv†øÝ¿P”6¥Ð‚-EDÒ  _ˆ¤Á(£š.—Ë;eè3—³ç«xéêÀ$\Vëw9©2ü†”â?Hò gÜÊ{aÀÓý~GÇÎWßlÃ甜sÏ$ž“|™sïÑÿ4ðh0Ê}„ß7™ßïSÞ䪹­yûáz¹oʼnnž²k›ÌІÚÈS•‰;o_|ä1Ä!«jGU>ogЍïU«:¾«j í¹:A‘‚F¾k¤+Ò·²ÂY‘mT .r<ÊüÞÐë@¨HSEhjJhJ ¼Æ¼×DyûX%DÞåçø|Š¢‚TÝ翌ÀYõZSæúQæú"À½šŒM 6.`.Ð55þò×6™WÐÊ>¢žžž.Ã8B€D](ý" ˜©Æe¯û!0º‘’µX,"]”ðͪ<=–ƒÝF lOŠ R¸p}öVíÞ|×kßÞ¸#GËEôÌzUVým„ɬiQVà):–]q¸–V´–EˆUvßCzõV=6EÏG]5 Ê`) •!õe¿· ÇÕ5W”9O¶H­ªQ3Ug¥nÜ5Ç^#ú£ªòûŠŽ_×<[õøWÓ|ãÀE4‹v)r]¨ºš_† V4ЬW!Œò=E#ê=ë¼FuÕÒ‘7[¡?ŠØl6ÙsÜ:\¾Ov3·*¬ùŠžË›þÞEL©âŸ‚öQ³4-VÈZ¨þÀõzë0”‹±ì÷û‘6À¥heÉÚ–"´È.ÊaÜDö)‡nôR iÒgézìSθ€ëâÀ Ÿk¶ ºo— ¥ª“Ô…^L›t2›pPš<&!çÅ,#üê"z¶4”‚QÕrýÞ¼•Û¦‹ æ9£e VuêüÍeÂQmUß_f|ØÂ*óÈLh¨|Y²V5•¢Îkj•"qM®V·%,¯ejQ+ï½×*CÇ—ïPÕ&ë o*‚£‰ßW'-2~ËF…ˆB×ôë>~URPËØO]5}8ªXÎm¼`ɾ™âEÖ®¶9϶áÛß¡¿›:ÌæóùTç¥h»ÚߌüS*€îpù(Ä^ÛÒ–‰¾QHIºZ­î©âdžãé«ÀàB}ZäœVú?ýôÓK˜?+o’q‘ÙÖ#o²Ê[åÏ»×2¹We¾?o¿Ën¿lîØ5¯2Ÿ/21ÕÂæ"Ԧ훠ÊLš6YÇq,3fÊ’é:VòË’Šk S!¶_„¸~h‘"‚P¯Ý×qýoÃowÕ’€!^ú|ýlÛØÎ+°'Ÿ#_K.Ê…B~ÞV0Ü\ 5k„F$ÚWõ-‹P\)áð}OQ#D@(+\È¿0ÂùjµJɾä@±‘û¼L`˜ø“šF+ý_ùÊW¢ù|þŽèËB’ðÛÞc(›:'¸­P‡-Ǹhñ“PÇ¢Ž}™®ÐÔ…2äëÖð:I¿´ß¶e±—:¾ßµªë#`¦@Pæ˜å xR`m‡Y$„ºiÑȕƓ—ûÚ{Ï»¦r$Jã·J«Ö*ç¢.§¹Ìùºe9_‹'9¾åõºlŽuÞ~»VÈn}~¯Q£ ´@b¢R•ú·"Ç×ôêJ*CŒ‹ÚbuãÇeÏ!é“MA5k|q*1ûÿ22ÙÕ Ìç[˜Ã÷9×±“íÊ]‚Àáp˜~úé§ ó²H— @t0ùí1à­a\Z¼*MwOOO÷Zaª|‘¡mÐÊ>þïú®ïŠîïï/ÄCá0mHî ÜûS¶ tu!ú¡+ŽuæG‡’¯Ð‰êZ“û5#n]¡Ì¤äËÝ.Kð›<Ž>!©jvÇªÈ¹Ï á¯K»•Ö½Ÿ¾spkBÚvÜzU¯©ë}•"¤u’’¾ØÏ­ÛÈvé˜Þb_«^ç®ýýmlK|óVô8ñëÄ3èF‹Ô€îéÿõz}Yù—ÅÉÍÏËÕ}_ÊC^»¿¼¨DWÚçDñ—åý+>ÅwSÏŸËë`èÐF/—ËL=ÐÈžæÛív&ɉmµÜgèæ€ùì³Ï²p*¨a®â›ï5‘,ÊÆ¤Ÿ J¢oF*È}ãÞŸ¶:­Whغ€®8\×"YEd¨àRŒ‹üμó\'I¬Ã9ª3o¯‰Ïw…ü×ù]eV º]ï<<”ãéJ‚=Cãm$ð}¼¾ñqæÊþ”z,A‹“$(@buPü'{ž"%¿\È\µ—EFT©Ó¡9X|:¦Š¿d€þ]‘|JÐ5ÌÂî‘ømWc0òäc]‘Lùÿj0Ì¥0 ‹cHRÎUüe¤€Ù7ý{¾ç{²<€4ÐÌ¢}y N ïá}°~lù>¶¶%m¼—© ;§´ )µ{×ï•(Ìï»f_ù¼ЖMU¦²ûîB÷ÉU¤3ô÷©Ú|ÍeŽEÄŠœËº :Ã=^M§Ÿ@—Æøl¿ ¿1o82˜:Ñ"%ñŠP<(»‘ ÀÑævY`~!k–I!À5ŸÊúhü~ɹxû´p¹ÙlˆÍÕ¾ŽEÄ6mc¤9’«ýŸóÐ A€àYK ¶'R½&ÊÐî•Q&ܪOªY,ÈÕw2`Ùê —Bý¿þõ¯_ +jÒQ¶‘,Ù£óáÑ@'£"ù¸]ž ‹†í6õ[Ëâ*ûr«ÏÖy,«Vý¯·‹€kÍs4ÇQ¤ÀÃÃC¶pI¢E@­¥À  ²S”½Ì ˜&ÿá×óöIø§õ˜ÒÆ‚ÀgÔ@<‡4a¾fô±2­øOw»Ý2Ì=op&æÒxÉÀ¹˜¯Dík_Ë ý‘8Àa4f(¿+@’ÿ!«§] m%ùe~clªJA©&E€:÷¥ÎkBd»éãg¦a•Xšü}éºè7I®Âsqâ6´ IQ$È›Ù5@ÎÇ\ÿŒ…WW'›¨ýJÕ¾Œ™Cg£ŽÔ @í[¼Ùl|)’÷ &€‡ü3„ó6¥º7WFÙ€ex?‡¨bF†NäßøFvOŠ‹Ò14o+/¤¿HïÌ:>t¶K[ ˜[“â¢ÄïûÝòvÍÚm%¬E òµ‰ü@×|FÙPΩ2¢™žgòN‘Äy¨°9<‘Y7>Ï‚­{@èÜ+[*L(ÿŸnúõ‘ŽÈKˆ£Ö€``½^ÇG2Ö«ñÉr¹¼WF<á Cñ9_Ŭ²OE3¨Âÿ矞; z ä÷ÉÞê¶Š›y¼ër`ûìÔÍaÏŒ†Tì¬-ûÝöãW×êyÓmôn}LAþ€¾ûŽf%Nq69,îG"%§EPâ>²Îw:“ÛfÎcF¸üUý*ò7K’dòJF—"€ºÀá(r§ v‰î   )Y­Vwꩱ«]ž¹’OÆOÄŸ4˜Ü³‘³&süm-Íbvu…æ† º‹~Ú¾÷vÌ·-„ýÚ+ùem¦Iû«òûq­€ëûÌCd2™Às4çÿ¢î$ÐsÜEÀFäm‘&é7ïuíµ‘âV3…„·G"Õp£×èí  ;DùG à¡_Âÿ•aÅ:œA)ꩱ¬üÏ¡0œÀÿ“òÄ-þ´âOƒÛjP¨ ='ÓävòBþë*ê5ôjËm%aMý~)VÙZïõé–Mèây¯Ú°)Ѥ‰¥®^CPè˜ÐËÕ}ž¿¸†ωüÙâóúéF¯‘@éT8„³3€|­ M~DRHÐݲQ ê;]…ÿø…a(éáp˜“qÉ<i¬ŠQèˇ²•q¡=f‚¿\./ƒË–#Cid €¦œt[!- ?‡AÒçs]®ûøtaõ¿Îï«ÃÆ|Û«×:N¸F}‡LA–«ü’¸›~ƒ$ðœ"@œ‡£ ÄH Zh\#@v'?1É¿ñ´ÚŸ¨íRÀHû” 0Òõ\yÿÃ>¿0q;ég£á'±2à»/¿üòn:ŽX cÅK†ÂaÓªÿ'Ÿ|’6+b }¡Þ™¤€ÉA$óþ¥“é#ý¶6æû}ÝlÂ…©¸ÕUg  „P’à2!Î×**ÖÄï¶]P»zNeÔBSçÊUoãÖB†k?ë\ýïQ¾Å9z0,ÈÜYüÏÕ’\>o.˜òç¨{µ¤ÂèÄAH àˆâNÄ—Ì"ƒÌ}¸~ÝÓêjµJ(€«ÿëïÌs@än8ȉ,—Ë‘V£Ø0HUš+M¥£IFJÆËäþîî.úêW¿z©€)‹bÆÑrÕ?|ä9“U[¡@ÿa M!ä¬í+×Ü·¶¶Â«»}ë%´Ix«³5"tY¨:ïsâD›Í&h[ô<}âQô˜S¦9€ÅþzõÞ©zߌVguäA¶úO‚€ƒä~‡`±i6 YPÝS‘‰{e ©xý’³OÆHFJ!ÿÜ“WÖ™øË®Ò‘´…´%üeò¢¯M¢nM€oEÛr.Z@rÈ•þ¥`r+RÝ÷šuü†k (B ð›9ú&)2's3q(^H%"âÏ)Ü9ùñ)3-Zq0 ÍžQ±?æn:ZkH~;¸Ú ²Q+ƒ›®×ë;ª4IÅýô˜Œ“ þQø?‡®ÐstOÆÊ¹0ô°-d¦ŒCZ4Z d>útÎ]ä¼®Ú[¶Á»E¡¹6­þÛªþöYð€.ˆu.Špx?ÝÓ*?×H£TiâN´°Ê‘Ó$0w29”ÚÆD½'Ul,„‰‘ú|Ö €Zê.ÿ‡ëø¿1el“ý~¯ i&sY8¿Ÿòý)Ÿ…”+10¯úKÒÏ]ʈ¡+Q¾îuf }h[ÄCŸB—m¿¥OÕâñ½í·7ªŸi»w½Ç¶ Â)Ò²… q%J¡¦×¨P:q*æ\œVM ­²n™Þ~&(ÂO@Fúuþltˆ¢°t€ÞG@pض6Ú˜SaJ-ÕSSiÔô‘R¦xµŸ@†J†K+þ¾0ß*c^+À:tý ø&Y±ÙYWE€[åwHWËU ÛÕy~Šn¯hj Õ|… Ï×…¹ ýÏ…i!•Zd?’‘ØZ «ÏRÀ©²bîtSïÍÈ^ppÄ€Ÿü_RXPw‰2D&’`‘‘.‹gex#¸8“™ïoVÌä•Pç5dÅ¿( ¢ÃÜõßBŽÌ¾ª×n•Öôïmû廓ˆÉ…ïØ2W B UâZÍÎ…>±ÝÒš/wò}R`¾DüŠÛ R@æK¼Ð*ªô¯¸×\}fB«þDø)}€¢(€ îv;[Ø¿) ¦Ú ¬×k™÷i¿"QƳPÏ%‘'£RF7â"¤VÑê?¯üsX \Ð@6% ¡¼@Ò,E'y¡•÷]lmh³Óº+Ï_»ÕÆ\ôÎ&ÛÒʰËcšô½|\Æ÷nç'Sdº4®¯F\‹V©Æ½—¹-"ùêó‰Â”Vý5FºÀHGDÙ´ã…‹móŒŒ#2HjøÅ_<è “Ù©%*z2L) ˆ ”GRöÑ”ÏÉAáZõr ¸¼Ág®Ë÷ÊZ®ïî11‰pŸ#%òò±l¶ÑÖßaVm:äûÚǃ¿‹¯E~[…ƒ¢¿»)‚^wÊ D Ës³™{ÏÏËhg97Óó\+Íõ¯úóöå¼Ëÿ?|ø½Søûh•ŸÞO«þÔ¦]ê´q€¬TÏ"w;ÀANΈ°8ÿ‘¨@÷:”$=s>fwwwÙ{9ïŸ#,Ûò’wÛ}Ûœm ½äÈ9â[ý/+„t(–ÙfÆáPÉÛæhvn³ùª @ÄŸ“@iÌ·H ¦Û’¨×ÑÂ=èFQ´€aõ€Ï ãÈ GÛb¹\.tA‰¬] ­ü“’²`#EÂd\«Öp=74ç´Ë…]«²ÈŸïü÷¥Ÿù­ ‚äµãÚ­6×µ©À|j—êòK¹cÓ¸è:§¬×ëL @=GEgÔ @§ŒDü© `ô6ß?ŠNe¬ˆþH‘ÿúG‘üÅf³™“QQ‹ 2F6Jén×ùíkÈ´¹šo˜ìãq¨û7TÝ^•ãZ4£/Eÿªl¯ Õÿ]û±èá·¥i†lC¦G›¾‹Äµh±•È?Õ €ê­‘@iôõX=œÌô¶²Å[@"€‹ü6"†M G>þh§1 wëõ:¥v¤B‘Ñ‘1Ê|Ÿ’e:ƒæ ð‘qÛvër }$èñ·÷s×®“&D«:¶YfU¾ã³{"ÎÐW! ŽEPɇhU_„ò_:ûÊW¾rH  ûÝn7U×'zÎÍ*ÒgE@!€ÛþâK@mÀãív{¯ÿŒWÿ)äÄ\ý7 Y§³LK¿>>ÀO(|5%€n müÎkí6dŒ!xÉfKUí`fðµ-`1¸+?O+ý\4ê°}úé§Ùk_~ù%GLÔë©ÎõéBY-Þj 2È¿­% €¡úô|ã(Eö§ëõúŽ”%2(R›Ê8ŽU‰Z‘C GµuCè9Â9l®èßµ?[׸Åù­ÿ˜4}m@ЧyÛÇi|‹™æóæü(kÐcÙ€ÞK Dþ)@½ž óù|¢·?2Rl·Á[6 PÆéjµºŸÍfSÎC±µÙ®Ìç ÞbÕph„¢/‚…ë¼™ê)ÈE;ÎÍ&ü6n«ÍÄß´ŒW /~@ž“÷:…úS´5WÿIÏS=Hxzz¢â³$IƼ€KwËDªà!þ¶é5A‚ ±^¯™ü_Røe7Éf³¹W†4!¥‰óRBZ²É׸oeèrõk7ß'·ÒÛÉ#çÔ¸ÚöÑA­#MãV¤Ä%6]›ÀÜ‚d×]`³®¼ÿkÞk·=tÕ,é£sk‚: ñèÙwu<³µò“s«Ë_e~dúé\Ý_¤{è1ud£ÎÄÍÎçóXݧºà%€îE @“ÜŽøCpع²Úè’Ýn·8c[žŠÏ!½EUï‚„@7ȯig®P«>¯&ÂvÛqÜû¸òïSD^úžÿÍûÁ®.ôÿýý}´X,H©[V¸é÷‘@5l…]vÒ]æ›ÍæÔ¥ì é÷ªŽcvÇ·[\y!4ÏÛÐÎa…6«|þZÇ»-«Åu“ÿ6µ¢ä}AX>÷O\œÈÇs|јkÙ"¤ÿ £ŽÇc¼ÛíÒûûû“7º§4j@OŒ€04›æûý>ÖªSºZ­¬(ùÈsHÎKÑB~!Žrè{šj'4G¶†xŽš"¾u¤¶”ÛÚvCIoíðÚp,|•ŒëǸÎ}óO}>[È"£éƒ˜ÿ»¢dT¥P*ô·ÛíuŸL§Ó·t'@t°­ö£ qû¿X(N£óù¼X¯×$Ä. w©âTçµÞ(ºMÿn\hMõÓF`û¶jÙ$qªJ>û^k¡Iòß¶c‡Õ~¨6—×åS9а¿‰àl~/ýOÅÙ©H E(®–Ìçó4ú˜Ò¥ŒÇc³@4dò @  ÏÏÏóÕj•êbï Vøç¢5®Y$ @{È®-,Ê¥Š"Bà6ôÚmêš>¯yµJš_Úú B+ÐgÿÕç_åE ˜¼§ˆßÆÏS¡@>ù䓸éé)QÿOõjÿE$ òFQ’ßEï£5YC¶D+ü#‘ÿONÜèx<Þ¯V«y¤SJòA+òZU¢#U5[1¹!:¦m[õó•bÅðkWºï:‰oë1oóñ@~¨×·Ê›[C[ÚÄl+ÿü:?Ö­'ãñxN+þ\ϸ¥ÐæB€Q„à£í½!ÿdLãý~·Ýngyd²É€&-àv^L]þóŠ§ ]úÝmkùWä{šhÚòßU{€6û¬uÕ4³¥ØŠaóÿ@µÁŸª×¨umË¢¸éF‚ÕˆòÃÿÑ`¨Æl Ê€¦Ëåò^ÖŒT&.8aS½Ì\mŸÁsM¹3«`ú|ÝU|ÃVuÛìd0D m¿OFäh «êÃùaÔe""ê8×<žm ‘ï’…ù<Ѷ‰ý‚ÈôÙö-`ùZøÙx‹+}N>O!ÿ’1?›L&ããñ˜’ ?CÕ qš¦Lòþཱིf3‚Én·[(ƒKØ@¹è„™PTÙ*SpÎüµ.ZmrÌÍ ŸI|û~NòªÄ–9_·$ÿ]Nh* £IÛ)²}rhfno¢{Ó;ò*ã=uKf³Ù”WÿÅ-/üPÞ’î ÃJžžž>¨§'2ÿŸVðy½ê )Úž¯Ž¶€!Û軣ܖßç*Y–øöõ\É[[‰pYÁÆU·OŽÁ­rÿàv>Ró¶¯ö‘üÓé´˜N§ÆëîÔ`$ŠÆÑ€Wÿ!XlM>ÖF3[­V0~sàê“íÞ¬vé#}EÄWÍ"‘@»„[îuùØ]@Bíl»jM޼î"MïoÇÍ ã»Æª@Ûìú.¸8‰+5Ùü\¨Ÿ`¦2Ë…X½Ñáp˜RÀë[2®– ÓéÔFüÍh€ÁˆÖëõå„3Ä˳Ífó@€ÌËæ¶EœÍª„=dÛ¾œ™"Nq{Ëߺ}ž-âÃwÑl¢CD—Éÿ5·{ÍÕÿ¶Œ7dBý×J|žÿZ$*ÚÖBØÕ9‹òü‡Ã\aJ]"½˜K© EP$@d/ˆ6€C¶m6¬ý~Ï‚@º^¯çf€k¥ÖFBÚb\#¦¢Ò'¢yíc`†ôûÚ4õœTù\Wíº¯¡é×(°×Ä{Ÿãˬà—ñL¿ÙŒ %²¿Ûí¨ÀŒ9çÿ+ò?6Èÿ ÛB0È¿Ñ/ï80À¬ЄÓëê †"´%Ê¡Žš ˜lü H^ýÇ´­äÿ–¿ €ês­Ïo³Eø:§ñíp8ÌÒ4M(ìŸx.§Ð[“$±…ø£ÀP!VfcfÂEãóù|·ÙlæB ¸ô›¤{›áW)~U¥€XÝÇ£OçW*…×.¸æªêo»õ™ÈV™$BR`êÎ_ݯ*…ÿúJNÛ`ÃŽ ÁTôçŠ+/RxÜôl×ÂrâÓé4Q˜éÕþŒÓé#.øÙk ª €†/—ËL!b㣑óù¼X­Vs"ûü<“ºÏ#p6a€ WÈ÷Iï3$ÜŒ àíÊßÓóóúîø»ˆwS‹íÜÊvu~_ÛÏiSDìV¿»ì÷¶…6%B4ýûPÄnã7H?ÚFÆÍ×m|ÇÇULŸÙ-yÝs{öív›(²Ÿê¶\ÜrÿãétëNƒÿ‡ l/z[[Ž7›Í½2ª™!yׯÝfÌUWC^©+àlpžoGz]] ò.Ž@;É`"vê˜ÄûFÌÑòÚáÙ¼¯X`^án[”€¨àŒˆ>q7nH,8Ç€] Èp:¦›ÍæN o”,³"eòbìyùþ¡E}âD[Šã á‚è"ú®|¦kÒ¾O<}ýþ¡þ†>‚€.ÎÃ.r:G»¶å ý÷¥ hLžŸŸS*ú§£³.º@œ¦i^+ÀAÀ[â†6Y¯×wÊxŸsÚ¢/Ô¡-’+2H‡î_û¸Ú•¸z 9ŸC$š.Q+T„kÓïiÓêxWÅ„®@_}kŸoÛÄ<°`6¦…Ûét:á¾§V€Η·úßËI€0ÿçNê.Y¯×$¸T-׸¶ãXô½C©J«ßa+úç#3}% MçëÓ1»Öoi"ì¿Éíâ¼@»ç¸*äßôílþ±/Àü<-ûÇTa½¦sÓÊ?§p “Ü›"@ï ^P?6 ˆ;¨‡$P À‰lùWVÝr‰®AT•¬Wu>á¼V'D¶ T'¯f‹·ŽjérÅÿ¦Èÿµm9dlµ H€¾‹uùU®9Óµè*ݨƒÛLaÊõÛè~:f½UÔ@ ÀÁ-ÿD@FºZ­îÕý¸H˜¾‹ÌûMÞ¶C¶YD0JjÀµn[ÅÓ:dSÄ´íûa½[ÿ.‡Î7õ}uv-©ºm¿¦A ¸kŸ…rWⶇÃ!‹à¼ˆKQ–.ÑEOø%ô¿ßgÆ¡ì$Ýn· e<#_µökö“· Ÿc‡óú¾/”iHÿºÉXW«¾ mMþ¾¢…V¸ÿë#辊ÿ®Ç®R3=€>‘}îüw>Ÿ Ðåÿ³;ºQ Ëê?"€Ì¨¸ÝÂb¹\ÞQØ›0®·QTª4T(ç@p¸/´Å¶½<'X¨^—çèw …Œ^3·Ú÷øáÉmY…mâ¸>??g·º¿'4}¦ëEÿšM‡ÿ‡DÒt¥Ž ²}ò­m$_r ^+)(·GŸcÿÏœCÑϾOúI’DÛí–øBd_}öå•21ÀAö79^àðÿHGc‹O§Ób½^/”ÅÒ}› $æsu„Ü­ߎlBN*Ö÷…à4AdóÆÑ5HÛ5„›¾Ecte,™ÎO[Ò%  ó¶+j6¯ YkKÞSw…Ùèuù?ãºZÑ¢n’$®€Þÿƒ`·ÇÇÇÿÓ÷¤Ý­V«™6+©‹Ð”qtË8’X…jæbæªHz‹i½¼PiEùÚDZ,YÂ8ƒM㸘Ó\QÎ!ó^ž0R€_ã€ívK@ªÃý#½òŸTÏ>ü€°m@±0&j#±Øï÷éx<¾íÝž%PgnkÙªäC@S!é>òo;¾}^õ )|Ø5ÂT5¬¼è±èÚ±k‹=·¹ ©Í)ùúäûÙþ·El†¤7—ù>N;àçè±âoÔpF9ÿ\@‘ÿlqWÔˆ òo{@ßɤ{E²c{>Ÿ§«Õêáùù95ó¸¥P¦M_‘¼c›£]Å™D€z¶i†ååú÷ùx7•ƒÝå¢mÊýïúï³}_Û5}`(B@ÞœÒ-´`ž¸ Å€‰òÒ$I&ÜP=?¦ðŠHÓԌȋèݤ>‚áÆ\øïMˆÂdµZݩצ&ɳgH{¾ÐTäq™þè! }½0Õ½-Û±l{?ò.›}_3W¾ë­ùš"ñm9.]¹†!ç’êGU‰…mó,åúŸN§t6›Mô¿×ÓÝ#É÷,5†2ŠèµíŸLH–Ë僺¿l3u¯x–%ÿ×tüw¡óÂe«òß~{­ƒü·elÕ}mºÕï )œ×¶1ÖDqXh£îo.’Mq6ÖlÏËZm´Ú8H˜òS”ÿ¯Sâ$IFÂoн.à5ôŸ )Öa"ôp¶ÝnïÔã ·Ï“íÈòœå²Î`Õ\8¤Í^ìÌ•iyrµrÊÙê5ÓVn!u%uâ–µòö§­BÆ?04ß-dþ³Uð¯²mÃ_¤Bî3JàÅ]Ž  MSY `ÅÑ@„ ÿü|º^¯ÔÃ1=G·Óéô¦¹‹,¸"l*™Ù¿Ò\=–äÒÖ½H;BY³`mé꼨Ùzš™`4E”M»d»§^¯tßôq÷«¦ÎE›£Dn}¨"Ý긢å0ߨV Í&ÎÛêø¸VïÍÚüºù“SI…ÂüÇãLý„ >¤Èú£Û»›+üh8(‚Q…¸Àn·½ÚSLÀ‚ûGq.Ë(ayïËÊ8ÂpN«_Ûj?Žks„¯ÍÕÞÛ ¤ô½£klU¹æp}ÈUÕß̹_ æM/ÔÆ§Ói¦pÞ¦zŽ š«ÿh8@cŸŸŸ/  /žžžîÈnBÉGÑÐïâEÂ÷‹D u‘qS­ìijš¤7=i\ƒø#e£;v,W×û( @ßE_»tóµèLSØl6³Åb1£í/PØ?…|’$±‘þAMÚƒ´AqÕÿˉ×Õ#ïžžžRýš5|?Äiö…Ƹœì¢m‹l×La€~ìmmÿ†VíÿÚ}åÛFݦŽCÏ6‡Â Ün¾óEÇšïóù“¡ ¡!óì~¿Ÿ*Â?£œÍñ²HJ V€B0k ""5”ùøø˜Å†è:‘û¿Ûívó6|EÛïU ‘)2Ê~nèöÐUØeRã«[ѤP5|¿ì±AèÿuÆ'öšóŸÊ.rº:kÉÇy]lÛÐûGÅ󧩺eÜu€Œü½ }âu…ÈËãÓé4]¯×wÔB"ʉ¨DäPçõ»ö…Ê”%pT‹<[±*Í’Í¡E¬T™|ûLþ»4ÖPº/²ÒºíÐèg¾).7y~~N(ìŸø.GzS iššM ×N'"^ ŽtÞ?ŸìÉv»½'Àftu:v!!3!ßG'úNzÈ12Ióë’ÿ&ö£1­Íö;d!Dn烸 òçó¢j]‹p¶›zÏDÝæ³ÙlÌ|oôZ pÄ­#{!@W @¯ÈÓ&üñ„kƒ"L÷ûý…øƒïùk‘…"ß]4*°ç-) œ€ýþ²ÇÿÖmí°Â^l\!~y¶a®#”õsŠF˜ßÇÏÏϳ$I&DøéF"¥ÌçsŠ ‘øC'{ôŠXGa%Ëåò’G|+…¶œ[ª€ü<á3 yQ¡H+:³w&õS¢ƒ\47IV7ïÛB–ªô­ï t­òÖ±ftGÈŠrÕš·ÚÞJ±èñ3»´µ DV ¯>uH¡>—Ÿê+èj(·åšoE'€ññx¤€„x)ô_GŒ¨ôü^œÀ€_ÛrHÝp¶Ýnï|ÇÇG‹¨Y²²|™Áç¾0BV±»NÌc#Ÿ·ý~ßñÇòZ!Ô.|Ųĸ©sË”—ˆS5¡ípÙQhØ]I4È5´s®6Âñƒ>ç[pÍK‘6…"úçó9¡Êû×ï‰u1À؈\1ÀÁ ëõ:yÿ—V€ú¹ùÓÓÓ½úw|«U³ºÂž}j[Ÿh“ÀrÔƒí=!9ƦpPlûUæü”µ—k“«6Ô«pM·Låi3ÑnÓ8.zÎÚ°ÿ-€!¾hò ®HS\0¿[‘~*êžR€~}DÏÑM§˜)Q4 6€Øküædk£H—ËåB‹(ä Vi æj©’·oˉµ9ÈE vÙau­þÛŽSÞù•ï!Ò^W€LϨ“À×uþª~¾-«Ê!¿ÃÖb&Oü ù}e¯ .YEìÒøîYùúJüóæ9WÄ­¯Ð¶+ÍÚ÷ý2`¿ßOIÐ!ÿYÀX!Iº¹"Á LüÑk(R†B÷Dúï¨ O^ŽKQêËy±µ 6çßeø>Â[„ÀV%pmÊ“—烛ÜÌ÷—5š@^ÀµIGÁ+…IuÑQóøÔAàJ0Ç]Ua¡è~»Ž™”¡¦ê'49¾C·Û¶´…<ç§ÉŽ2€kÎwy¾Šmž M=Ì›Mÿ’üuöù;¯K?ûì³ñÏW«ÕœO¾dÛˆx¨#æk5g†¯»Ú\è0—7ùý2ÇßÜwÚÖétzó¿ÏQÌ[¡®êˆæ=o[•­“˜ò±p]Ôn]ð¯ª@“÷ºY”²É–‡U+µ—]ýùœ/G¾Êq0kT9ÿe¶‘÷»êß·"®m'¸CX€~}×Ñ+ê—{Û{Ìzd!­}©×¶…<£ûý~Oùÿ‰"û#åïËHoê0J’$æç£ünúbÓ‘HІD†0QÆ|¿ÙlÒRo3Ö²¤Ñ“Ç’9ê“ÉäÍ=?/~,É»)4œÏçÜýã×ø½!"†o›."ïR«äΆ„Ň ¦ë?T%ðeÏG$¤M„ЄŒv)ƒ²)¦ðPöçMvUέo}nÍß9”z hU´Ý¿ru3»ŠIîb+8^Ä—É‹µu ¯Éßív‰âLs"ûŠ×EÜ€ÂÿÍ:pâOÿ÷r¢F ý˜þL‡Ã½2š”Œ§è Ê+€B¦åç)D…I?=&‚Ï7\’ôÓcI^tTƒÕ©æç}ûWdÒ–7íêP' T½˜¹Xè90IÔ­B¯E„º’ ^´ØaÓεŠ(ÖMÛD@mÑO¾I¿¤ÄhÓ|T$ßô!̶¼Ì<¤¬Ïዸ¶E¬Êý ïV|n¢îŲNúyÆišÆ$ DöB€Ö]ê‹ €etOö@•!•ñLÖëõ½2؇Gçuð‘"î!‚"ðDô•afÿÓ=?Ç¤ß ûwm_®pÛH”/üߤEC…}ľ é%áeÅósUC¯z'§*“GŸÄ¾‹= Ìp_Àæ7‡“x÷‘«òÄ7(¢X¾7¤ –­”YÇKF*Kgz<ç³Ùl¬_st€âU¶ö®½‹|…(#ãT€d»Ý’T)DрݞÂ7^Ý'²ÏD_îo~³H]Q6Õ¾: 9>6’^÷Jxèv‹ ƒÝØ¿*¿«ª-@0Špì Å(“‚*yƒ-ÅQFËHª·%ùŠ\¾€)ä‰fô¯þ>Z½¤4€,@íÇ TOÅ ô2 `È]ˆì´qŒø9…u H€\~6nsPø(“~ºѧûÙl–‘}~ž‰¿y+ë@‡¤#T g.Bàëtòë Áâ*ãP…¦Hh_蟗¢âß›"€\ dÎC"/f¹?’ŸÇQò¢<]вÂîŠkM¹ €ú?+¨n¶V€’ø÷ƒÁÏVü—ËåH†€è‚éÓÓµœ¸ˆshH»i˜¼’OdŸ ?ÝóŠ¿Ìñ·­îÛÀ9þfXŒ¬ `ˉ/*ØcáÕ¡ÝB·S¶Ê<ÉíÉÜ-Â÷ÛrÞëÉÚn×op=„ÁÎó¡Cæn³P9݈øÓ=‰dGYDЕàã âùÑ~¿OÒ4Ò²?m‹ RÚ7½"t'[4€ž’ú“©?ÊÙ ÒÕju¯îÇ!¡ñ2Ÿ_Vþ鯡þfn¿Itóúaògeê œbP¶zH»¾¢D¿N¢²m[‡y±2» ­YŸˆÜ-Â÷ëþÍC#à ÿÐ~t!Ѳ՟ùº«€Éc\5Ðl‘ôûý>‹àâ~6áÁµiéD0:ÉÝÝÕ Çôÿ¬œz~åÿƒÐ'Ÿ“l[ÿËÉW *‘wºÀÈ•c#ê’ÄËÕw"þ´ÚÏ€ ó7‹ú•qŒÍ}¡mÊZܰ®UÕ¢+ãEÚŠÝÏ–p!â„­kW¿ |ãçñ¿»»Ën\äÏ$þ|Ï!06rïª`¶ØàÇ’ø7AþÛâ¸×õý.¢br}”]@þ»·Cì¶]…YÏÕñÌ|ΜïÍIâYP þ"»Ÿ1"1€„¹ðIÿËÕÉ—ô¶âõz¨÷Î)Ê›~)›Ò(@q´1µÜl6y"€z!LlϗܾW·éétZ‡T?ÿ®ÅŸé\s>yZåxxÈîYà×Ì4Wê€é´‡tàœ^ýç° 8ñD[¼îÕ[³®ÿ¶Ù4ÆtÏOó…ôçýïÚ¦[ñ¢¦)&PjÝˈ³É©(€Z¼+n6!¢¯y·ŒIˆÞvDÀ`#–Ëe–ÿ‰Ê(’õzý@ù"\“磈õ¹,€nê¹qñçÿ{¾8ä"€Y €HWÿ_1S䡌cÊÄ+ìfX>y"ûLüiåŸ[ýÉ•Iø¥`Vÿ·­üçÕ`¡ÁÖæ(~ëãqC=ƒ÷ŸC:@1ŸËô—ÍÇuúWR0ý¾Ýn÷&ü_v-éêÿ±º%T@§vg ´ú¿X,Æ$ P±Áh )¬×ëX´¤\,ÿ?z­0[.—÷t\\-øØÈˆèá'òO7*öGdÜEþe¸¿$ÿyƒÌEèeˆŒ­F]…AþÛ½o·ˆè‚pÐ! ×(è¦OiÎßœ»_4: ï{dAî`ú:\€} ›¯AmÿŽÇc:›Í¨êßH× k~6ÒÑà¾4}²a-È6€tŸ>==ÝE=¯¬³ªÄáöDþiÕÿÇYçÞ˳؟,ja$Spðµ4£ Ì×\Ÿ"gOR躠ÒFrÝw †èI£,ù·µ72̨göáYt {® `nWŽzU›MiÑ—DÝ&RÁÇI’¸H?"únûG)Çã‘댩àjµº'µÈÞÂíýhµŸVý©àåþs>IòY<°Ýlm²YÑRÖ 0Ãþþ‘9ûzÏÙÎwÛ~#R- ß~‡Kì/*˜ïåè ^ˆeÈV„²«€HÙ&®GµÝ"ÿÔæ]?Õ çf³ÙHq@×Ê/Ç! òO­ 4ùÏ"Ôsóív;×µÞo?aòÏaÿLÆåêv`E+>[;‹<‡Ýµ’'·a«öo«¦ ¼?®®P!AcüÆz÷ "Є?´×§È›ÿ]¯¹xŒÙ8-Êé—E¥` ?KµÞ’>Ìtî¬ÞCœÈÿx>ŸôÂð…#ZCè‘|® SSÈùf³™Óë²ðzùçJÿ¼òÏäž ¿ÙÃR¦°A²ËU~žnÖ0k°‘ÛÚúÚt¸¸í}]Éy®2AÈ×1™\húúÄ6A&«<¾1Ò&¢Þû5ÃËœgNõ®…°% î9 ã¨küÉ{f»½¼sãZl“<‡WïÍU~Û>0gâí×"ÒO"wØn·—mKn¦¸]òùçŸS!ÀÑn·#òSá?õÙ,€t©E ÁMÞøâù@—Ȥ‹¾ÚRL)÷«ÕjFÏÉü"ûDúéFFÂ9ÿf±?Û½Èç÷óT9Pd]S3Ó šºPæ2  ®í@\kâ("Æ4AÄšø-uw¹Å1)û]uÛÊ£@@@à`ã88^]õ©º|¬ª,œäk~^ò’¼E¹@ÂÌER³ò?§PÞÕŸ?SÅÛf:Ú›‹Áéß4M³€(¬`oÚN ãx||‘êCíÿ8 €"”/žžžæºCÄ…ÈSÁ?Zù§œzL€m…ŸS̶¶is¬ÌœÛ –-.XãH›ÈàrÈ¯åØ¹ê”êÚ礪×EóV¿©Žm݆k<܊Ķq{2ß­­ÎH$ˆ?ÐO›µßsÕ¾QàlEÒ%÷¡Ï7ãHÅï¤ÿï÷ûD½w6™P#€q¬Þ“=*H¢qÂÈÝ îù¤}ìlecŠP÷‰2{ ‘9ÿ\íß¶òÏ)f•ÿ¼•—³l´‘GŽJ`ÈÿCmß>…Ô&(Jª«FT%U2dȼ ]ƒ˜øj< Í1){¼ëß¿¥3Vw IþÛdO>G€î“~—燹üpßb™¹òïòá¹8 ¥ #ÔÿbÿM¡ÿôPq;J v€T+À•ÿß+ò?X€N8§p( Aûý~N…"È€ˆ8ù§U¨Ú¿Ìíg€NFØZýùBåCŠd˜Q>‚c¦Ø‘Ï1w ¡«ñEþ¼¼‹JÞwqQ.“ïkݦ·]6%á–«ÇuF›4NÝÖ¢}gØO±q×có9¹_-5h±ÖŒx~~¦ˆï­ø‹(ffnhŒ,{…A ëõšÉ>“ÿw õúî5I¤Ÿ«ýsU¹Ê/‹r$€Ìÿq”«†ˆç <3b  ©0óoäs!]|ߟGŒòZæÕ ¸5‰º6 jkøÿµ¿»Mä¸o礋ûÐ%R…ë)P‡5‘6[¤›™¹ Û¦oQÒU|ÐüNN‡–Qt£ÇT€ü© z}ªkdùÿÀ«ÿÑû3@Ç‘­úG¯)Lþé©Ùããã Bd4´êO«ÿ$ÉYüO>–¡ÿ®Ïœf9hmª˜y/ˆ¬P—ÃnKK¨+Œ¾irdË9’¯ç @³ùÿU‹HU軽Ðk"Ì5·êú¼«¥¹«ð¸íó.?ÜVÜÏuíí@@\"è¶Ûíè¹ÑápPÿÎ&šû1̺Á‘@…O§S¯IÿÐ:©c>ñŠ0ggÿ|>϶Ûí‚T!"ü´êO7˜üóê>‡ú“Éa#Ÿ&-Bþåsf@ù?ßø½úªüÉ®ÉÛümR§«Ö-ÀDØÎýºõÊ?VØÞ ®Ö綪ý.na{^ ®…NÉ_L®#Óh¿ˆÃéý$¾G5&úsÙʯŽ’/ÚCè(ùÏ’?¨Ÿ|2u?{zzZÐÿ´òOUÿeÑ?³å?&±À\ù÷…Á»ŒÕVS>‘zºÉž™rµ?´¥ÆÐ/`ø]ý>^mŒH©ó·µ¹5Ä è6\‘¾&ÇadG—ɬEfrÓ¿aRÏþmœÉ·¿ôâJ\Èë¹­×ëÑv»%IB=G\@w .ãù|Sªx”ßÀ¼‡Ðþ¤Eè~³ãñ˜R…ýù'åÈlóg÷Ës|CŒW>o®ôSK¹Ê/‰>>Y2ÅäÖ…¹[ÙÖ­*÷w%\µMûh —WÐ!À•{/‹ìÙZ£Ëôi_m4Û¢¨ü?¯M «:ï×P*QÜnÊ­9\×°‘þ^;4ƒ‹x||ÌòD¼=&X™ ŽÔ;žè¿ß‘·ˆ&‹KŸ›ÛpS¤€Œ`!€ˆ¹K\0X])Ò¾}çý¡ïL’$& !%@s¿øµ`vG­u@ßêoÈÿàMÚ³œAþ9 =N EÈÕÉ7sþCu[¾>“~ õçâ}®¶Ç»ÇC&¾}'*}j/‡Õÿnì_^[úéCKnáòøy™ÏOüFÖP£çùÛ"\€¹/¶¨þ ,6 þOdt±Zý×]FÚö™3½æ|p·‚ ödTùŸ+[²šaÿ¦Ê"Á¡ü¼ÒO!2²Š¿‹ÈÛœ-Ͻ+¤æ¤Évlú €ˆǶ7C¦8`kégã22R€?I rÎQ¾íÛ¢œm «ò9æmô=šóN/¡à£QÌ‘á´8L©ü•‘=ô@Ç^ùçŽY?HEÄçêäO¹í›¹Ò_6\æòËÂ~²Zï »‘Šq;ù—ÇÇd¸k6 p ŸÐå‹È×ç}m͹Z?ݳàãX®ÅQW›tÙeMG#LÔ{Xà6ðÙcæ†  8š¹ˆè‹M·¬3€"â2æ¶²íŸiEªïóʾ ƒ!ÒÏ>·­\»^—ß5t"g+rÑ"oBi› ·í\Ãî`ØÄŸÁ+ö®¶!~ /„2?âôë¼tjWº4ó GT5-øNud@Fþõs1ŒüzE®`žTe$tÒãív›Îçó14G°±ú*þ»VߥarŽ?ßB ZøÚ]¸ò^€~‡¦~[sÿûnï¨ø@›ýŽÐϸSeDwnèJ5´E˜]Ôdº5c6›‡Ã„8¥_‹íÅ–€¶¶€üøÅr ­X¯×±&êÙ‰¤,`¿ßO•QŒ9¥ˆa»ªûØͶ~EV§mû …8ä‰)¬àØô“ü·_¸æü@\>BH‘tZ,%W’QØy‚܆8åšwAñ°‰nÿ†äëÿM²E=î0¸6€ºà Ù‰<ñn·K(üƒ .Ôɵ…´Ø-så¿HË>ß¾À.@þ1Yá·á¸Pž´WõÇ}i.ÞÄ܉S²mE]7 #¯õg©àûDçEàZpŽ(€(êi7€ÉŒ›¡ŒBVzÌŒBÈXT€|cd&¹´©N¦òd üœËø‹& Ÿ¨ôi¥çä¿oí˜Ó«¤ûR¡}Ï»Vô9t_¶c·ñ/Ûv墫ä`âzf0&þÓéÔ¶òo+Ø‹bHƒ+(Wþù]ðo¬[B¼3ü¼þ“lp,˜ù(r›¶ÂpÂ[w„ÿ·‹ü·y|ãœ@¿|K×ü^´èoÞ¶|‘϶ÏpÔ´Œ° æb¬Y @¦(ª‘̹`ïWþ‡*Äò„³íЉO’äÍê? iûg‹¢€«G&ðfÈSߢ%Pøo¶Ûæcƒsýò=šŠr—p Bø­„ßÖP œ @mÿ¨  ùç{õÚ‹à‡®Hè¸@ôþÿ¢C?â¢]Ų¥ØE t µ3€­«BŸ~XmYÚn#l€²þMYÞ“WÛËU@Y€R ^ÄŽˆáx¿ß»Vÿ{0¶]fÆòB­^Ì3çÄfl ²×mùþü¾6¡Ä¾éÏu(öéw7ñ{nuŒnynº@dÛb·EŠ”0<_ÛGÎ]B@UÉUüOò4™ S´úf'8:@þ4ßφÐaÒÏ'QŸpnñžÃÿmÆ& Æl‘åJÈ‹è3™½Qék*Ð=Ñ¡ÃÊwã úî'Àw,î'øþçTêÔ×1·åüËÏ0ãÅVy<³ûÃá@ï#Röb~zýEü¼Õ.Øé‚€CŒx‘÷ʲ\…³y’e•J©§Õ)T%²&dÑ@\Œò/@ò8Ù*ŒvUàhû6›üÞ:cŒnØÜÊ—”~£«h7æÇâÇV.ÌùÈ¿Í_3É¿¯ «% Äù¢éÞ1ŸgnŸCú]'¾“BÀdˆœT ®ø¯nYì¿"ò§£’Äœ#|äß4ô¢¹ê¸Ðø!#/däcÛÜ×—´ïD쿹ÊwöÝþQônÇ \o®E$¤È…û!Ï6q ´øº/€#Œ€“þü‹ ÿ!Ð{Ò?X@Ÿ¬7úžZžÌ³‡üÓª?ÝKÀeày øœÈ ‚ȘÿWmËØ4k"÷¿K„²êþ¶™È¶95×( ÏݾB}yégB(/îW¸¢rÍôŸâ‹E-_Ô9æŠÿ²};§Ù;ô“àK’$Ïd úÿ¬ýƒ8§ÃZøO®úËœ3E TÈ@±‹ŠyQæœ yLMÂoF ´M2ùïóÛva×# Ïp¥ØÚ*×Ëtàª)¿CòÍ]Ï•ñ-lÄßú/W÷9 @á¨ß3á×A¬y¡«õºt÷÷÷/ËåòEkf$õ†<šJÎëp”óZAqÌ'-Lø]ÇÒj\¨6‰@?IvÛìÎ ¿óåé@×a[ò›ëŠÑVÿÆVl…‹lǶò/Eú/j·¥ð£_{Qäÿåp8¼Xˆä tÐ0%ùÏN(üétzˆ>Vu|chl,t±0‹Ê4q郙—÷gÜè¶‹‡­Nƒ©ä¸bõÂDÈ~u©>T™ól‹Aì7ʂ߶ÅAÌ“Õ|Ÿ2­ÑmõÙ\aÿ¦ »½ñbï‹÷ÏÒtKx_¾¿ùç ÙÛÊ(€ì9ÊéWÿlF&[üI¶²I*C Ú*šÏ—¹Ø -b€k2L&“7ëý~ÿ®(ÈE•®ÙA„ƒ!Oîpl€!ùèžnI’¼k1gŠ@i2VKät^Ãï'’ÖÝß.¤ðt:=ïv;jh.÷Ú¢ð¬ï/U IýQƒý È#Õqk?^U¶…ŽËÕy1ðLßÅ¢è…$àwÅçUz™«o‹¬àßËb ¿‡I?ýÿøøý×ýWôŸÿùŸÑÿ÷GËå2úâ‹/² Ôùn\ûá–]TûD‹aèïønã~÷õšƒ4F möǾ'û™äKN§Óh6›e?ûì³è“O>‰¾úÕ¯f·Ï?ÿ<ûŸ>§W3žÀ¾¨|>-q Í;{>B®yœÉŒÌ°åÿËÿÕq'ââ@]àÅ þ½Íý¤À¤ŸE~^GuxÈ5‰Å€<#,ë  Ñ/ ò9º8ÒؼPÒŠ>]œ‹Eöø;ßùNô/ÿò/Ñ?üÃ?Dÿñÿ===Eëõ:Üt!—d_vnÁë¶5íL†va€ý´‡ìuñüTÝÿ¡Úg]ããÿ–öû»-$a—çƒï¿ùÍo^üÓ»»»ìöµ¯}-úáþáèð£ïýÞïæóyD«Çt£ˆû¯ü˜;‡1ø¹!Fä ´E8•­€K ÇêœPØÿI.ÖÒù%òO]Ô9ô¥ `÷mïRà… F·†8¨I !S³Ú›& ¾M|¬å…˜nDÞ9ÜŠ+=GX"ùñýÍßüMôío;#ütA¥×è=tAfa‡UX[§†k\|»&6Ô>õ¼ý<…!€·E×ÏOÕýª}ÊT¼*¿ñÖÇ/¤.P—犮ÿ¾¦í?ºéø2žlïá¨Qò%)¢ôßÿýߣ¿û»¿Ë¡¾ñoD?öc?ýèþh@SY@>© Gçï2;T ‰ø×õ^W¸¿­€>g˜ø‹÷ù—Á >|8/—Ëg!PHH¬#žeµH—`ªM®Õ²íó ¾O I´äocÕ•ž#bOj*­ðÿå_þev£0 (" MÓ7QtÏ*¬¼(Èï 9Žuk_÷ˆ¦‰p+ MÚ[‘ý³½7ô÷9yßYäû›ñ3ÛZ^ûûo½\ħ©ó“·ÿ×´ÿ¦Æw“ǧÎÏÛÞëÛ¾™¦WÆ~ò"ë´Óï¸Æñ­‹ ‡´×-óûš&ðU¯/yöqëó×ûhÃüb³?~žýEÓ7”©¦d t¾²ç)¥ôŸÿùŸ³(?ú£?Š~â'~"úñÿñ,U€ÞK¯s4«, >ä°ÿºæLWî¿Ù @ž_º§4o4ïË@¹ÿ$Ü0¤c9¢ý<¿^ ^„ƒD"ÀÚC0ñôû+âˆ5¡p÷© )k3tŠ(äêË/¿ÌVüÿäOþ$S`YꪭH#½Ç¬ãz>›<!Ä´.Q¹å*MÕó“×^3÷9™Tr›v°ª:H·FÕýëúù©ºÿU?ßôùiúó®×¥ówËãWä÷…îs®/¶çËü¾®__n}þ†>ÿ„Œ/›HÀ>%Ÿùö)É%ÒH¢ù¥¿ó;¿ýÙŸýYô3?ó3ÑOþäOfõ(:•X0¢ôUŠTmúúÒ&ÒÞÄvlB€$üfKcu>ÏG:i¯ïæó©ÎͳQ@_m1z› @ÿt|çÏV£2 Î 'wå²4)ô!¼“.„|!¥{º@Ò…’ˆ=Ó¿þ뿎þøÿ8úû¿ÿû,äŠ/šô:_T9”KVj•"€ï|@õ‹3·Æµˆ¢QÕB YÇ¢íç§êþ÷Ý>›ÿßìc¸à0pöåù4I$yþû¡º€\…J„ÿ·~ë·2õgög£ú¡ºt£â(Uöyé~èöSv!Ì%ØÈ¿~(Í[«3µü‹_W|é<>[ŠBè)ùÖψžu ÀÁ¶šì"ú¾UÅkå—w\¨ÃýI ’¿Ùl¢?øƒ?ˆþüÏÿ<»Ðù§‹%GhðEW*´z€g)ÒÅé2ßJdAwûmëšûb~j´¨Ðûlrüc|°á‚}E[]() p-Nàç8-€müQ"úÿøÿýÓ?ýSôÓ?ýÓÑÏÿüÏgþëv»Í^çír+`çR!â¯\Ô“yÿ¶Zè9¸ö›æþÿ¬ ôÜÐ2Pú‡Vè_ºÊŸÔãS^q¸*ÅãB[º>ã‹2èªHÀ„žÉýýý}ôoÿöo™Šú¯ÿú¯ÙZ’xIøùy¾˜²0ÀQæë¶ã†"<ý˜Šþ^›È‡cÔnÇ¡Ëç]`?ì¸ÿÉçÑÖ%JÅKù£fT€ìbÅÑ,,üáþaæÏþÂ/üBô?ðÑjµº§©Q2$â/‡ˆ¶Šÿ®çÅù:ŸõI×Û¼´¤:Ä !ô_¸ÔFA'E÷²è‡ÐL@Í!¹º,³-u Ì“7ÑtÁÁebÎLJWîù¸ñÀ§• ù'òOJ “’ù^Þ–¬×`†iñë®cÕ·‚Š pL(F@m>¹Y7Ê$©²U¸ôuùuŠpýÖ·¾ýê¯þjôK¿ôKY· ýg0§‘ôXÖ¯êâ1µù?EŠ8›ï•Ìsa’¹êÏâ qÍ7ŠèŸõ6³Å`Š8—”p=Å'Ÿ|òòå—_¾i¨ þœµ:äUÚt¡êX<á…Ù°:ʯÓ1'²ÿWõWÑoþæo^jÈ6~òbavgp©†æs6UÑÅ1ÌѶ÷ȈQIˆn[€“ÐMÈÕ|ÓÇ·Õ“ ["ÿœžJ«Ð¿þë¿-—Ëèç~îç²(Wòs)"€>G¾1½‡#\¹Ã@§ÉV þcò(_”€íf« Z„‰ÿé€gî8”n 4Û§vœ+Nb)@ÀE>lã]‘.%0oP”]"]2Ÿ ÷q…úŸ.zú§ýÞïý^öEpÁÙÐLÈ#ÿE‹¹p<`Ø`?ÛLùeQÀW N¾F>,G´Òsôøw÷w3?÷§~ê§²Å0.(ÂÔߥÙ— áV6òo^Ô1==ëØ-\òþ©@ôq•ß|üî+!ttl‹B/œBä_Çîµ@äKr0ÃÌÁo§íSõÕ¥‹©dÙÅ.v”óÿ·û·YØ?](ùy^õç(“üÛH¿™2Ò‹c@™9ÜÌgçœ)˜¯›œ€ýZúÿîî.Èç¥È×ù‘¹tàÕþ^N‚ÐâZÐ3ë-øø¹°jÔq>põÙÀÓ°×u 9²ÈÊž =r(ˆoå?O‰*KÐûX™^z®ÌÏu¾ýíogBî¥J¿Ÿž§‹!åCÙTP“ðË×} ‹ø 8°ÏWÐ|¯¼—ÅÂM!€#YéžZÒsîOï¥¯ßøßˆ~åW~%ú¾ïû¾Ìߥçdá?úìP"W]ÜÇEôC;˜œLq#WþçZ ú˜¿ä~[d€#ûW‘ÍgVÔãb›’gS£Í3ü P' LW3å‚'ôÛèBG¼ßþíßΊ¡À"½—^#¥Ô<¾r•ßU°K„ ä혯]•ëmm‘¿R àüîjEÿój?Eü¯ÿõ¿¢_þå_ÎN?¦mqí¬.¬ƒð»R,BÒÌUñ û?ÿ§s²ßï//høâ ý¡ÿCèñ]T€HG(£8ŠçÞT ·åÿçU¢KQ?Zõ—Eü¨?ê7¿ùÍ, €;p¨Ôýýý»â+&ù-ú¿áZÇm% _ózȼ/ɦ|ž|ÛO>ù$Ë÷—­È/þÿÙ;óGŽóŒ7‡3äœ;3»³»:WYI+YGb!RœAœ ×áO‘o‘ù2òŒbØ0àXŽüO,ËŠ¤¬ìÕj¯¹x_÷é©ÕÖVõA6Éîæó8¼9d³ºYÏSïñé§ŸÿñÿüýßÿýSQen˜Vü§1’æTI‘¶­oh»|”SÔg]ñ/¥ð[_ÑýÚlxyYhÝì°usg·«ÚƒÑ6 òÞY²–}±«¢"ÄÿW¿úUðÓŸþ4r=õí8àés»õŸ™>`’Âþ‹z¥ø¯,4I!„TÛpuпÿfëkƪ»ØóÌQûÎ;Á·¾õ­(úµL©¬iÅ|Ömìë´õÿè×RÑÑxƒð›à‰>·jT¶è agggò͸¹ê¨9„)d†òØ}ì}ÂÞ׿r;\–ƒÂ²#ôê¾^µGÈ?xºÈ‰}@´~®û\EB|Ì"@«"œip[B!UÄ%Bãþ\ó_;’Ø4ttìþç/¿ür´@†ù²^„¬zD±ËLɪmìÕ~»øŸ~œlkäþUt1þD‘ßV„€y9nã‡U0VqÉê²ò#ÎͲÃuÑ ÀWEÒt®˜EO»sûŠ`¤1*v@UŽà£> þçþçÒÀ}fX¿ýfž?εY`>ÆŒ°_Ãeé GÁKñO!„âþ¶ÛóLßm¾û0w5ç¹®ù-æÄ¿øÅ/.£ÊÒ oý‘VK¹j±Ùf€i(b`h“Pß§Ówž% FÛdwêýýýðôôÔþ²'ˆqâË÷÷ ¾¤\•¼[–IlšÛèøø8Zý×yÿæ6é`kß_VñE±H!„RNS é~—†ÐÑψ0ýHøñ¼ûî»Qì"ÌÝ—¥O’4•¯â\GÐh4DçGöÿT‹ÁI)•dU“VCk@éz½z½>Ö­ãkÚÓ÷¸U)ˆƒžn…òË_þ2xøðaT UøÒˆŸ»J1M!„B–a¤™§Ú÷ëçêèX̉aŽŒhÌ‘?ùä“ËZ«(ú³Ì¯Ò. Bωî Qqñ›çbñ¡Ü슈3h”}lZ_6úAŽdÇúòHÒüyŠö2šûhmòñÇ_ºf‹Å¤œ×9!Eø¡_•4B!„d¯qQf$æÉúqh“9³¾mU·YœJSÀÖq²­G‚«ˆVxú¯RB]ÞcT¢êÿÊŽ’Pwñß“Ó@‡Þ$­öûBRÌû|m鲈ZWþPw\{;hÁÿèÑ£àË/¿¼¬†Ší‹ƒ›nâ«â¯_ƒùþ¤ˆÇŽKB!„øæëqµ«~ŽpóùñÝ»w£H» ¶9Ç.ËDšš]>ý”uñÕ¥Éð?¡3 9¶¶¶Ý=ÒïCë3À¼^ôÐxsåß4?~…ÿÛMôêÕWþ)z !„Bh˜ó^€ÐsN¬Ó0wÖ³ËÚ ,é=»Ä|sÃ%ôm“@-èNdûC]øME`õ• VMàÓˆ;pt+5(`ô'wÄæ·»`‚*n(ÓJ²6°ú/;×eD…ÝÞÄU$„f¡B!„*™¾(=7ÆÜsfÔ€¾½Œó¸"YŒŸq`·ûsEàaHï6*ý_Fôz=´œdÿ•0ØPíŸê€56¼ùÿq0içMåq‹ÚÁ]§ãããËjÿ—ƒÎXý/ÃçãE/!„BHÞó+;"sfDø¢€K+º2ô…ú»ž“T°]¶oTš_mËhñÂßÓþÏP¹Ž+› {?ª“c(9Eý!L7)í€Ì[ÀƵ",⎭`ÚyC wSWÿ×íÓ´¡x&„B!U˜Ús^³ N—=??÷ /S4€¯Mx¦OüÛÚL4N¸¼MG€«N‰o#åm4J0Ÿ ý—/ÿrOaÚ±z9 ÒDØ5«1P¥qv8bF㩺¾¦U3(þ+yì 9RÖÜFRޱEÊyLåqTíwÅ5¿uÆÆ¼sfÌuzßœ¹ìß…«“šO¹Bûõ"­yÙ.⎓h:$û£ÃÛ$4@½·N§ã[í¯„ÈcåÚ†­ žíiVlýz½>Zä"kþKY&9:—I_6€ú`WEáo~þ².Š^ÂñE8¶È<Ðõ¸É„TißwôÖsEs>ßï÷ŸéFVÅ9ò¬sç¸Víúö ÏDè6€«(þWÒ0¾Ø§ò9#çR´›dÔ4«ûin\ €¤E<¸ùz•¢€ ¶³.xRåzŠB™ÿ’Ç*þÆRÖ±½1Ìõ1­×ëŶ/ó¾c~_4pÖ¶€®€ú5677G²m‡æöV]&V×·4¡þ•1VÖ@å‡z½®óÿC5àpy°±±1´CH\¢Ýe ¸vʸܔ¸|V“a3;`8>urkJÚfe9À%…6Íú’ÆÂ<·QV#«ŒßaÚ÷ŸdÊ­êd6ËÊDÜöI"XåíW•Ϻ¬Ï4ÿ7¾«V”«Ÿ§Šé‘eýý/óöYÔvIóý$ÕñÒâU·ÔÏÃÜÙþ,e‹*%º9îq¾Nlf*€y a¨´ÞeÄ7Ä¿lçH.öGÀ¢ÙlN.ê?Œ/+BÝzFc¨‹×Ùâß•‡¢CÙÍÇÍc'µsaŠðçàÍj"”¸÷šÇç(ú¶p½¿E¾gsßs¡¼Y¶oÒÿ „,÷øÃ|ñr¾ï‚HUp…óÛ—íÈãU^TpÕpi1ß¶34º»ƒÁȸ=Òü0Œ€ÀþaÕM•P«ý0÷ªׇª_¤·­„oÕÞ·úŸ÷ŽQÄÕµóꊦe2wh„ÿëöãí:InVq6ëëûžŸ·hL³½ !iÅfÙ?`y|iV®ÊÊíSÄí3ëñaÚççõ¹²þÿ¬¿‘Ó~þ´ÆCY" –õ>m±“õ}Ì{ûí÷µlã«*ÇçYæv¸»)Tõ˜×ó='Æ sgýœ2]qÞ<Þ¿Küû4[³ÙDT÷(4Þˆª0I!î“Rh”m¿žnÿÝ&ç5,}¸Bh`Š~_ž{Öq–Šžq-JÈž+DIÔ¶··£j¦ºú¿-ÊôífT»2j‘â¢V‡ËÖò¥ ?ðUnOIH&àÜ?ËÿýÎòr|²Ã|é¿8Gø¿MŒÈdÌõ"˜Oi|ljý¬š'mw-þ• ÷šˆêt@¨‹ó(À÷L¨::çš_µ†ˆn–q(§+_?íN–Ÿæ~û`a®”Åt½ÜÖl6/…½írš;»)þË$¼¢X¶Š¶yþygà”¹õ!U`Úc_^+O³FôÇßi¶mÕ>S ÊÿýØó=æÍH-ôñX˜¸¾¹¹émX¤y¿=“Òzæi®xt[¸¾¾Ž€±i²@üë6‹»+\倕¸råÊøììl\Ä E*@ #08\æÀsÕˆ›0øŠ & ðeí\YÚÉ„{‰ÓÖÖÚ.wïÞúšbgÓÕÿñ8Sì» ß¶LUûäêÿr&HEØîœ{ûòû!„’V Z"ô©ù¯6Ð=ëàà š7·ÛíË93æÕEšŸdù}Ìò[hÎõõ6²5¶…«X»ýXU`p~~>Q·…J÷EùÿƒÁÀÌãõµ¬äùú*ï›ØE”އÃaMEŒ766zêòš¯ÇdÜ€¶bšYw°¢9€¾Ï~åÊ•Kw0ä5Áp…þ» €Uá«JADn{BŠ=A#Ë9ÆÐ$ZÝý‹ûg5ÇþÞlª§M3Ѳ"F#a‹ÛaXB¶p /ÜLgˆ‹ºŽ+Ê˜èó¶PWüFà‡ ¬ª °Ê5&ÑP]F€áææf_…‹¬Çµï3 tøŠáÅíIƒÞ6ŠøÃàËÇÑÛäèèè2¤ÉõˆÀcq®M—øŸ5Ç~Ù+xY¾§¼º),kœL“Pä"JenCY†í»ˆñÏï/þÿ] p|Ñ*³@¯úö§‘<'ri};Ä>ÅT7²ËôX¬ü_¿~}¦ÅÃ"|ÿY;“R®\‘×:•¬`=w‚üCÁw¡¬SŒ¶€+!úi\Ž¥È2û?NT¸HW9D±â~Öc–¨(Õµ]Ñú:ú˜â †\&ÝãTG˜áN.@ߟÔ0¯àyX—"œ&‚"¯ÿ¿¬q8¯ XQê,Û ZÖ„cQÇ·¢§0EaùøE|¿eúŠ”†X´”H²ØyC#aí”]S¬j‘kŠV]×õ‚Îoܸ9ê¥6‚ÒŠÿ¸Â†®<{Ûº´Ò»766†0]B#­".ìŸ)U4t@Œ,ÿ«€¾ì€#3ä&®‚g–Ãn Rö.×çÑ`ÇBÒÐ ®&¶©nobÖpúú<…À´íõªòãZQ´U‚¸Y#qŠ4A¦€ä›ßWq÷/W­Ÿy_YÞÿùä)·ÓþúÜûvd@QZÙã´ï?©KEžy_Óü'ý×ý û'„²hñhþ&ñw¨\ß›k¥ßœÃº ±†92æÃZW¼úê«‘ €ºY.Ó È‘Ó.z&û6§Í»ú¿ý?t‡Dy«ý˜Hø+ño ú¸v€•cÕ mL”À«¢(8P…"j¦¨M[üoÖ"î±eª€0&ðÅ_\ŠyÝóÔ6 \âß>å) órÀËZY@Q~À’BÔÈü·RŸUß>yìßUÝ.Uÿü<­îþ5ka5oèó¤°ts~ŒâØè˜¥Ãÿ1WVmêJQdqž­Íן&…ÏߨØ @çûk€.êv[è³ `ÕÙÞÞCôGqþçQ@a(;bcæ@Ó+ÿ‹:0ú|Ú‰A„„>Bš®^½´Z­è6íz&åû»Úš[Õ‰IYÿWQ €¤B4d±‡Âgu¾ÿYª¶¯ŠPÕϺˆ6‹EÞn49Ë5>ã¨+Çßµúo>VþÓóÛýýýàöíÛA§Ó)ĸȣ½ß¬ÏwÕ ð™.ŽçODç †pXÔê¾N@L€ ¾êÿ3/I „ª'¤n¨ËH èˠ颙˜…Ó:]vO˼wä"‡Â»Þ+r™ðöÛo?ýéO#—ÛU‡=ÙÂÞ6ÒDL»-–a ¤›iÿo^ï¿H†Ã2ÄxÙEÖö„¤Û± /Yý¡Šc­èûWÚ÷7¯ã?ÉÛÄNÖ‹„fš°mö˜Ïѱrüî»ïFOž<¹œÉ)â÷Wd‹¸[[[ƒN§3VZNvO¢ÕÔy³Ä}˜p¹R¬tÇÆÆFdÔëõ‘*Ä/z ƒ¦;Ɖ\®Ù>@¸ŠUdÙéÊ>±×ï+ÿo½õVð«_ý*º¼½½EØ+û¦ø3ÌÔ<Œ€¢™&UýYë¼-浯QVg[®úœ‘ 4ÿEÚÆ¾c’«¸yݵ0ñ¿¹¹-’ï¼óN4?®Êw˜6‚Ée’d‰öò™2úFcpzz:R ¾Øæº@d$ü´‘4Jh„"üõÛ˜\ÄíÀÉNÙEK@_^Kø'8ÒäUíà­ ÀݼråJðÁ?üá/ßì*ÿ>À6\ϱKñ_<ñ_Ô6œ4B€B|ÂÒŽèõEùº 9ÚmÁMc‹aßùÎw¢Õÿ“““hÎ\µc^R½Ÿ´ó±¸Zö ©Ý¨ç†"ï '*ü-'±_ÙÕ~GGGáG} |á*D£0ª gdâð™æÁ í OsÀqÝ_&±bäÎÎ΢'Ÿ}öYpïÞ½è`WÜ/M¾ÜóÒ»"EX,#…a†[ÿB¸o’¢ ¼E½,ó¹etZõ$_4¯þìZÄmslߦCÿ_{íµàÎ;‘ø×©«°¥Ùn®×s‰}×cEcŒd÷Um·èv]ü½ßï‡ÊðUþ¯¼°Òòå‡"ôuÀ¥€‘õz½«ÒTÀš×Èr w¦Ý©ÊrðE˜MÄå÷ß?xøðáe€Kð§ý|IÆAË>ÐÎb9Ëûs®óš$MZ|ÏÏÚvÖ÷ç3&–]:Ëçsm³YÞÿ¼ TeMuÊú^ò8>N3¾²ìÓìrm…¿ E³ì³Aœ÷˜*óøNó=$ý~fÝÓ|Vßoá4s¼iB§g_ä=¾çe0ÌkÆNóÙ1ÞØØˆ"c€ZuvkÀª‰þ´µ£+|¢?&ª „ ÷Î í¨ï¨Ê»h?œ5\&@à¹N  ¼òÊ+“ŒeÄø˜¨tä…ŒäÀ×E€Ê©¹&¨ºzgïª:íÁ³ Ñ:Û ï½MŽŽ‚oûÛÁÏþóè>œÌÂ)8ðáh†AùjؽVmñoçbÍD±‹ÄØßûåñü¤¹Ü2×®¹c’àLúœ8a«¶0çÅŠ¿hŽ ÓéûûûÁŸýÙŸE··ÛíhnŒçà1e®öø™fŸp ü4ß•:MÐÎ]4Ü@x."¼‘ûu°¾kå?¨²1°êÑ—(;éP ¬üㄞ‘=ôÔÅíÁŒñ§ÎtI-B²þ $åõ–Á ÀûÅÁ 5¸œ8(Ï=÷\ðñÁ~ðƒ@;qÿ¸Œí‹çÄ SdûЦMÜD+ëAÊþŽg™$¹R¦ù^ã"SÒŒ)×{(êXÊûõ²T§öûMz¾vü]EF—1Ù[Ö{ˆ{Y·ñ"ê̒Ƶè|î¸÷–%$3é{H2íì ØE8y¼·YkYeûøBxÓægp˜Ç¶_T4@žÇÎY#æñû•WÍŠEìÿÓŒ/»¨·oQÉu»ëû× †˜Ïâ\·Áþó?ÿó`oo/Š‚Åó07Ö…±WÁ\›¥ø¥Ëpì/0¢€@5yS«ÿãÁ`€€I‚à¯|!À•6Uý_„©Ž˜¨œ‹nmmuP@NÓÞÁ“ZQäú•f‚Vt(¿Þ.Ú—éà ?£.h¢‹› ' ;é£G"àïþîï‚ýèGÁãǃÃÃÃËj¨¾ ”/7×UÕ}ÖÜú¬ª¬¯©·O–ÖYtµ³È"wy˜yG¿)Ç6.‚¨ Ì+c^"{UVbÈòX™SüÒ¯fÉù-ÛöYF›¾²ìEj·œÆ J“wžöwQ­˜ï"ÌFÀßüÍßDâÿÁƒ—yÿzn¬k•% ëñ-N#¹¢'ÒLfжZåTÎÿH4\ÿääd¨L]ùE'ÊpE¬LKÀ•6ŽŽŽÂ>ú(Å!Ç¡ÊÁõáÞÞ^ç:„`péÜôiv–,!¾®ÕÓ´|‘ºß)ÎqÀÃà ˆ\(|¾—^z)øîw¿üøÇ?ŽŠš²„\Û+nåÞ­ßÒ„Fúªþç1JÓ†ož!ÊY"â&“Ó`›µÂý¼SÒtú˜§€˜¦²}žmËŠòùæ5þç½}fÙ§òx^–÷?KË¿y·ñšçöŸvá·}Ö.!yïIUƵM–½ùø›fŸ_Ô÷6K @Z±Ÿ„N{ż÷¯þꯢ¹.¢_  ‹þ™)ºh` $×<Ö·M]†bܹUðï©ëª–ÒP¶g j¹E+þˆ 1† ¸óÿ]&@à¹N Ì m•-õ×jˆÈàéÊÎÙÃírª'9.q?MžK–ó2´AÒ…M‚(z‡S‹|þ¯¿þ:¸uëVð·û·Á¿ÿû¿_FL39‰3I²æï§mç{f›?ßóMƒÉg^Ìë‡rÞ!ÊYBÚ⌈¼sˆÓF@,b0nõlÙ1i…dÞy™{¢bÞÿcÚ×Oû<3*K»¯´5Šº}ò|ý¢u ‰›ÀûÞ»Oàe5˜§9ÎÌ’?k„¬AÑÇwÒþdÐL“Bç6™õûM³@—fþƒÓ÷¾÷½àÚµkQØ?æ·f‹llgÌq».\…è3W=…¬ßW\ýÛÐãiݨí&çý± ¯Uÿ!´ŸGì'µþ«Œ °òÀñññdkk+ ÿWÝ.#dçlïììô 㦮W±1uoO—ˆ˜fÒ_Ô>ØytHöE-ìu¨“v;q`¼yófðýï?øÉO~]×ÝôN®¿]$ÏÕÌV®þóq«êIÐ|ßqÒwT#@G9LûúÓL|õÃ’Æ™öG<×L3ù˜µPMÒ¾œ4>æ]DjÚ ê,+Ë‹žØ¤)ä¹ ƒ¡ÏÏK@¦ŠiҘͳpß"W`§ Ï;Še>óúýš—€^Tíe§ÍZ1i|NSÉ|ÍYk(åõ}¹RG]óFûx§<Ð)«æØÇbÂýQðõ­0§ÕÝÌvI5¯ÊŠ:MX\½5{­¿“óóóÉááa_®÷d[GÞT@¤\v} ž]ù¯|ñ?ÆCQ4—Qc+„*E@·ßï7777*N.­gp™ Ñ Qƒ"†÷îÝ nܸÕøùÏ|üñÇÑ}º5 ¢ðx„J!}`kk+2L#ÀµÍl'[·Z3ú€.e²è ÿŸ§YäøI›Æ±¬18K8g{í˜YtŠCÞïoÞ{òÜ~z¾h›zlš÷ëù¢^Ä”žâ±fÔª^Ôúö·¿üÑýQtÄ?nÇeÌ_M³ êLj<‹êš¯e¶f×@EQ ¶:˜‘ðGè¿®Aæ0\!ÿI4ÊÊ{ï½7999+—(JÀ-Åÿ:;;;ÝV«…Ê€M05{E3ÉM/jôecÔÂ'\¿ÿ~pýúõàÿøƒ×^{-øðÃ/¾ø":àšõ677/wz»%£ýaO:ôÚ|^Ú•,«Â.‘˜Ô wžã%ï"ˆËžà¤5ÍÒ¼î,ሳæÊ¦yÿ³l‹Yÿ¿ïùózÝyM´ó6QË6›¦½hÒsg)ЙçþŸt¬Ç~·ˆŒy„]ôÜdš¹ËzO³´ Íkþ7ë矦FGÚm0MJ@–cô¼>¿ùúæÙ¿Þ.ò­0ÿÄœQÏ!õÜÓŒf…)ðòË/|ðATàÅÿPðO¯N›‚5)Í•^­¥ þétN}YmíííõPüctuê¼EçÈÿÇ¢nðlῸ(¬ê¸š\0–x,;8 #8D5}üÝï~7R æ:H™ _ €iÝî*›:¤_VDD!ôqÀÜÝÝ ®\¹E|þùçÁ/~ñ‹àôô4:hÂå3S0t»}ZäÛF€¯É´ßOÖÐW_ÕÓy4—~=ïŽópqï?KNó¼«ÝÏûû›‡¹™õóä)0ó\‘(ò1zYù´³äÞæY“çóg-Bºì–”Ë8–ørÂeüM3îòN‘(‹I˜”"–×j¿ï5çÕ%&-æãjY˜«úf˜¾¾Œ¹(.ãüêÕ«Q¸ÿíÛ·£¹)ŠýŸŸ_.véÕB¼jDyíOqÅÔUê €®6à`å_ÿët:ºà{’ЯôDàbGŽ ÙI##忉\îˆmÃ4’Çì¡;`’èÏû¤Jaÿ.Ì5„ùkgŸN„ÿooo¯¾újT$ð׿þuðÉ'ŸgggÑ}8˜š®­~M3„Ë,bš¾Ž´©ÓNfìIq¯ŸWˆÛ¼ÆPÒ{,Ú¸ÍÒ• ËûŸ¶®Ç¼÷ÿyE˜Åf7³¬Pe­°>t˜E¥}Mûÿóz~±;í6És[Î#Òcšh­<#˜òÜ>‹Š€IŠv+‚àðEÅå÷ØE¦ ÄýÿiÇñ4ïûù´ïÛ—êªS «õëÛÌÒƒƒƒàÍ7ß îܹÍc±PÕjµ¢E-Ìgµ‘€Å­UŠ ÎCÿøZ1êíhÖýÒÑþ"ò;;;½v»=P¹ÿµ°;év»Õ Ü)¾Ž4ªŠBÈN‹œ´+„‘4ñÙ’AÖSm$Ãÿ³­I:d©¢^†ƒŠ-À°Ãêb~¸—õŽŒëh•‚*äûïïïï¼óNt ýío|öÙgQÍܯ‹ê¨SüúÜwéŠÜH;1HjƒcÓ¼þ¢À³LŽ“r=fíýi–ˆÓl»¬&ý>gY…™fâ‘drN{\Êësͺ*=ï ú²÷ß$ìý3©iÖmTÄ6y¦êÌûø6mW¡¢Pyåy;I½É“¾ÿ´âxÚãožãÇ5Kóy—Ul1ÏIí }ó/-àuˆ?ž æ!ôŸþùHô¿òÊ+Ñü‹V?Žæ­ºV€NÀkà¹8Çc `׬ZÑ?ë~`wÐZÁH @Q÷¾lã^¿ß¨âîº@”îb€;ä%Bÿi(nܸþìg?‹ZÊN:R#&hˆ•ÙÉ;2˜:0d ¡`Í€Y'•.7+ËUä¾ÁYxq „Kª…¿¶¯ëè¼*0±òÔD Ì ùU¨ðÿ÷Á£G¢¯îà:ÀÛyߨ51NÙ‘f2ìûá]Duæ¬ck–÷‘¥…Ó¼Âc“V“úÏk.k•ìiÛñåñ}ÆuŘŠ²·CÖ•·,iU¾ÕßX˜ÅìËSÈ,:&©nIš¶^ó>ŽMõ‘ö9³¤¥ar]öÿ7­˜ö=k·‚¬BÞãkV!5ëøÏã;ç|6íñÛµc®ðëëˆFÅÜ‚ÕýQÙœœœD'-HqBtªnñ§ç“z óYYŠP—Õ ˜æxœt,7kX…¾±Ú?Г¹=nGHÿÇ™Nèt:“À_ý?ð\§PUÐRvFˆþ‘ š‘ÊÁi´¹¹Ù’¶ #ðbì=mø&±ó  aC抢n§¨”æöÓyþÚAÕEWp‚a§P€‘ðÆoDU¿Â æÂ°tåU}`v €EWt/zñ¥EýÏE§¸,c»—9ÏÕÎç+f–}LŸ×û¨Â¸)¾[öãǪÃïo6`ÇÇ8SóMœ0'Å9›0ÇDˆ?N¨I…âÓ˜Ob¾‰,:éªòf*<`qKÏ;õ¹®° ©®¨Ë<"£´ `F«vàr5.õœnÿ‡cÏ'ü“jTb§¦   „ìäÈÿ¡€ \Æ AþHw¿u||Ü“ëÛ¨`’ó9[q“ 4;Cš^åe=€˜&€+ŒNïÌ® ªvPq‚À×áX80ã€}xxèló7‹Û?¯ 2̓Åÿß"î3ìB!„ç7XçìëE$¤¢>yòä2·ß^Á×¥¶0µÿG\êHÕ·©«.H\d™YÃKGS蓎¸ÐT®ÿpoo¯ÓG©ÿ‹oQ@˜ívQvÀ¸ÜÿJGÐD܇·nÝÁ‚ ‚r¬ÚF`üt®^½zvÿþ}¸Ic9 ¬ã@€P í"ÙbÞ>ÀqBßÎ[¯Â#ïÏ££[…o¥#` ¸þï4ÿ»(Û‘ï½J?REý,ÿ„BÈrðÕ„‚Ð×+ûf8¿/õƒ<½MóJq³ gëçêa-Œðþn·Û;<ÍŠœ`öõ©-³ ÷kºÚ½Ä„Êš×^Õby«f"pB)Þ5N„®Âü$ÏùÛ¬F€ÙñÇÕ &€ê†ÕýÞ­[·:½^¯ƒ]ÔÐÅÿúý>òÿ'A|û¿8# r¬q˜^€B€‡‡‡þ} „ÿÃë#i¿üòË- *9!Ä$D¾‰k¥ÞnUá;Àĵt½NšSOÞUq]æH\ÝŸ£[¦ê*ýxTõ3qò@!„c^êšÆ- ñ7<ÞDɪKÒ˜,®B€* Êóo·ÛÝ^x¡-‚¿×ív¡Õ¢ð¹ŒöQ Àðâ¤ý•7 899 EäcÕ}#‡‘›Q ¦@çèèè-e ¶¶¶6F= O: $Õ˜¦ç±y_QŒ»Ot^‘zåß< û<.ÃEWm¥¨%‹Ü!„²<\úã~³íÕh’}þ“õ±æ¶Ö¹ÿºðŸÑÞ{"⾿»»ÛÙßßGþOUþVÿå¾ÑÙÙYT È–û_ih(þ÷ÿ7|ýõ×#Á¿¾¾ŽÜÔÀ`A$@gccãüàààT@É– Àµf³Yóåë' \WÑ‘´-3\¬;Ü¢pÞ)¶bô#)ê6*Ò÷´L£ƒß !„Bæ9׉C·¢v¥˜’ù×'óåÿk@ÕÑâOÄ=Âÿ[r›èýη¡°[¯×éeø„Sœžz[Uùþ˜ øÇüÇ="e¡lä@Ú°^¯ä0DIG篽öÚÙ“'O 0@€ÙËÓ,<‡Ë8p˜¸Áö ’$f«,*³üŸ²Kœ—¨æû,Ö&!„BŠ/pö?ß¶·NÐAfôĬiÐTºõ·.-†p¡%­%tpùÿÿ8Ém£Wÿ+@ÀàÞ½{ýZð`!‡D1FWûðððTNg]ä C¹=Ô½A]ƒ:nEß>Ϻ£QÈòsó3–óÇ’Û‰B!U&©šïñf$µ‘çÿTø¿h0ÜbÑVîG»ööööv«Óéôä¶nGø?Ä»ÝF À‰Jí6…þDì–•oHÀ{HPe “ôÞ袴$ œ£µÄÙ;wN=z„Z}ô˜Äó\WÒ*߬ý0Ë&|âº&ŠÿUÛ>Ü!„RUñŸÖ H3´óÿa@ÔËåbþ_ýõs,Ô"@ôLÿCøÔþOž'ümc ò⟀Å?ýÓ?…>„í=¤ @®ÜÜ;ñÅOež¡Ú$\&£¢äS‘fJ@šŠý®ªÿ®Ç”±‰ÝÎ⧸âzÚÿÅï4yûdMù!„B)ò¼ÆwÛ,óB»Nšù‡ðÇ ‚‘þr¹»½½}~óæÍó³³³6:µaå: º§§§H@@w–ðÿ•(È"€Ï2ÞÚÚêË꯯¯Cà÷aÈ`ëˤ½%·¼ñÆ'Ÿ~úé®ÐØÜܬËÀÜÀ„^Îkf΋o°wŸ0Nêa_Ñ¥¶ ‹ï•À4Xö÷² ˜cŸB!«<§IJ0‹þ™â…ÿÐÞOècõÿÿðÏQ§ «ÿX˜…1€Uÿv»=ìõzˆ0Ãÿ'1â¬HÀ ¤À-ª¿ˆùºÈÍ0úè ƒéìöíÛ'rùìøø¸%ƒ ·ŒôLaÀyìleŠpu< çg$„B!«cdM° ¬þËi(Z-Zý¿uëÖ¹\n#5!ÿXýGë?9 Ÿ:ôö÷÷Û20·‘ wÃØ’Ó¹\ßxûí·ëOž<©}þùç(h1‘S¸³³ â ×õö–½fþŸ4;P£ h?ª«Zù_O³|æi„õ2:äñ½æõ:E„B!UžçØó¸´s;y\¨Âþ'8Á@æõP€øG*ý#çÿÎ;§ªèßeî?êµÉýr>FåG뿱uîþ+“ÿO Ùþíßþm¼½½=”×___ïnnn¶DÏ7e°ÂhÊ`mÔëõ–\®ðÁµþð‡<+þ0ÆF±)·5 —7ä¾z "äzÍÞIÌI_¦X½ƒ)!„B!…K‘'þ•¾‰€ÕzTLGû4ˆw¬æ«ªþý³³³Þ“'Oêßúîw¿{ñßëõ¢öë­V+ZýG±v„þ#]@ž‹—IZí«“«úÿJäÿÓHɇ~89<<¨üÿöh4ÚÚØØ€èߑߒq».®." &ÀÏ~ö3Ñ;::B €1®Ã@tLl{¹-J € ` BªnTyå¿¬ß !„BÈ<Ì=ï ¿!ÊÕ‡jq?B1?uꉠïÀxÿý÷¡¹ÐrÿzÝn'¬úÃ$@À…ÛQü/ð·ý{®û„¥¡‚>úhrýúuôë¡ÀööörÿåzSÎHËõÑhT;<< Þzë­ÉÇŒA ÑLD ô<¶‡„ýCüËù­ÿ,ño ü$ñ—TÝ mPG'€ã_þò—ýwß}·.³¾¿¿_ßÚ‚~¯¯É`ŒÜ~•ë2><<ß¹sgøë_ÿ­*š­VkðüóÏ÷åyýííí^³ÙÜ‘ÇEQrŽvMõÝ @`¨".ú~cL”FSè‘•;XÐà „BÈêˆ~S+MT¨?Bþ±’Uˆè~„õwEEâÿÞ½{ÿýN§3ñ?¸rå ñTé(Méív»‹Î0=z„ð<ÄÖo‹þ‘ã>_ À•€À&Àï~÷»¨ÀÆÆFÿöíÛk2Pëk;;;5Dà1Èí—A 2äºìîîŽÞ|óÍÁÇ<üâ‹/ʲyíÚ5܆ §þúúzON»H ×Ù–lC^ Quå¨i3@¿§B© @›~»­Òûd !„BHå„¿î‚6Ô¡þþrŠŠù!aü>÷?Z\ˆpòXä|~œ7^{í58^»wïv[­ÖæÁÁA÷ùçŸß¼zõj&€ìíÝÝÝ+ªÎÀ®ü¯Mè›r^G±@ˆ~9­©ôü©©Û cPüÌw[-kûÎú«\ÀÒýÿ„B©ªèW :£ýŠü ÕŠW4Dâ_h#â+þXý???¿ôÒK£›7oÂ8¡}zT9ýt 5 J€YE +ÿvÈ¿¹ê? ž+^ùÿ©ùü*T”Ïa2^3ΣÂ|ê„ùúÎÎÎÆõë×·nÝ‚˜ß¹"4›Ím9íA¼c%_Æ4Vñ7e瀈ßDÁ@9­ñÅuúûûûë7nÜØ:::j\»vmoccc¯Óh4väþ= ”Ëè°!ß ¢šÈ0Þ×e·€e˜fø?óÿiñý—m;B!„,csÙÃïbnoŠe´:Ò!®‘®ÅNyBý;rj£ÆŸÜÖ:>>î"|_<Äÿáûx¾h&´V"¦'!Q—D ô€ÿCyN+ÿ¨øÚÁ³aÿZðÕi`œŒÛ‡–1àê‹0.º¾¦0`šˆ¢¨Ë ^ßÜÜl¼úê«[;;;›‡‡‡çßA‚¿ Ö¦ ðmÙ!6dgi¢{€œo € ¬ú‹€‰_¿~}"Z&J˜(PÉÂÂ…ÿÚíöH^g$¯1„øoµZQ«?ÕRÐ\ùŸ‚~è1ôe»À\WÿiTÛp™Që>„éCÄ4www7aÈI¯Ú7äý4äq*`5Ï×899©?~üfº<fºlÉN³{xx¸'¯»¿½½½/÷#M'›êuñÿ±ò¿¦G·€šµMjyngŠÿê‰ÿ¼ÿoUÇÇ=!„BÊ*ú-ªZcªìåêkáü~æ-%úÏ…Hüýõ×GE9ûrŠÄöÕ«W#á¯^/T&Bôš8ï÷ûQ @§Ó÷z=ˆþ¡ºõT÷¬ü-ñï3†)Ä? NÌ3™k>'ì è ¤ò‹h"D¨ãõð˜ õØè¹ò]D¯#;×Úéééz»Ý^k4ë*" )§íëׯïí_»vmww÷@µµòš[ˆ@Mà¢SÀšQ  æø,5Ïö©M³)þ«kPüÓ „BHyÅ~‚à7M-þue¬Â÷å„ÿ¶ˆÿhµ_DÿéÉÉÉ™ýótDüwåò^3ý3ªBåZüG/(¯Cü#«þx"äüÒ ¾Yõw‰Wø¿ àê0×ÕÕ7Mˆqœ°Bôì ´Ä}¸ ç0´ø×FrúQè¯Óé¬!Ï«û"ø×ppp€¨€Ýçž{nÿêÕ« €€¨€}ù_»(:ˆ”¼.êè÷é( X‹ù|IÛ®æ2(„æ;6)þ‹ý]Ò#„BÈ¢…}Œõ ÜÐýZûã‚neŽ–~=UÕ¿%bý\ÄýÙ© BÿôÑ£G­‡¶?~Ü“ÓáúÖ¨W.šu¢è¼°h’¢Åu ®c•à¤Å¼2BCÜçþõ¦üîçVû`‘c²¬@UqÌñ2´>g˜ò`%ûÏB!4æjÔ<Âÿ2 _æ,XåG%þ¡ÊëÇJ?D[8á."ÿ\?Nm½Úªür_ª\¬øG¢ë–jÁ?Pÿã2¬+ÿ¡Ê'¨‹e>Œc„¿+ôße¸Ì»â¿¹úР0‹ àKX žŽpž«Â}‘Y Œ€ËçC cÿÄíØ¡Œëu%ÎõŠþr ´põêÕ­£££-9GtÀ®Ü›››»ò˜f³¹Ly˜M°×Q†@ôš*hM…6×TÐZ¨¯ÈØQ-/WíÕÏšoÈ#TZ??æQ!Žó1¶wÞç8Øe}^Y‹ÿMém£‚œ;ÇŒ}Žc¿1ÇÜåþ7f !„Rq ¥îr,¬=õ2ŽyÃSyþFë>½Ê á‹Âû#däó÷ºÝn§×ëµâ~"î["ò["üqŽ6~]´ò;??èü|ýšºK€è,Nšï)4û¯Q—íP|[ â߬ü?J0âÄÿ\WÿiTß‚gSìH€5CôÛéÏœ”à€ª^WºÁ>¯çõkZÄ#µ]ööö¢‚GGG¨°&rÛ®œ¶vvvvÑ@N»òø¦j+ˆt˜ˆˆÒ @Õk×Í¢‚z»ê ¦ø·ëø¾#Ê Í¶ÎúÝYÏöA RJ6 ÿ^«&píã¦2Ážù!VÆ+B!˜Ìá9¦8­…ß$èÛ@hîËÐv³xŸ*à…õc•¿ßïwÚß¹ -¾¶}"ø;Ož<Ñaý}9E«üÈÍWyüa†÷8vèñ[ñw…ýÇEŒçvÅÿ¹‡þÓ¨¶` Hó|Íä¦ `Šÿ5ãÜ6Öb.¯Y—íÿW žJÀçjˆÀ_ƒ)€H+W®D§½½½&Šâ|gg—ѲpCNM,\__oêš*u…u¤€n;XÓ˜Û¤æj/í»¨q<.ý½R°®¨ÉA!„Ŷ&³VñÍ×yj%]ŸTá>-øQ‰¿“ˆx¬ô÷Z­VOt.¢pzzÚG8¿n»‡Šüx ekáâ2­×Œr4ÎãúÄ>ž^uÏ*ü“ [ð¬çØÅŽL¿#ÛEëô [3.»vܺunú5càÖóºqŸËƒg£´sˆá9UØ„B!„üÌ #Á4!ìv˜¿½òžEôg1ÆŽç„–ð_ˆø/4rÜ1 &žûLÑZ†Kôëó‰ç6;* f™fT€]„f!„B!$­æq­ª»VûCà÷]'˜º€ï±®œŠ¹íµ fíCü×-#`Í!øÍtû6Ó ðEاÀñþhB!„B|bù™”Ï)©°_–ó4æ@œðgJ+ €¹™öNaŠúÀq» PsˆùqðlWŸà«`×&°O¡Ã°w„B!„Pô‡1âß.º—&·?MD@šûâ þÅ}6d&ÀÕws<°f]ÖâÜ·’?â‹Ú—}¯ã3j1"¿³³Ð „B!¤B?Iðû„šÂ~YVðÇ ¯çþqÕþ)þi,Ô0Û˜î•ú$#Àî à»î3ÖbL€šeZøZè-kG¢ñ@!„Bª(¾—ý^|!þGôÛ+ÿiÂö“ ׊~Üu[øSüÓXŠ `ß¾f™f¡>}yâè>CÀ'ü}Qq&ÀZŒà:_vŠwbB!„Bò™G‡1禠žÉ-ýl Iü' ÿ4ç!Å? €"˜ö€›ñ9ø5è¥0òŠpE¬¥0Òˆ®ÔB!„R±ïº/m˜ÿ¬áþqâ?ÀŸÄ¼Š~…2jÖ€L ½ÏbØÅÿ’¢ÌǺÒ’Þ—KÜ×hB!„R  ©¨_d+ðWä/®À_–Uý¤“ïsK3|÷Ç…Þ§1Æ3 ©P`ÚNkAº´€$±_V# V²ñ¶Jð»!„B™]ø¿]y’”ïŸÔªo’AðAúPνhÂ0¯‡# ôˆlóòšÇ ¨§0\çiêÄ>AVdÆÈB£€B!«8¿˜¥Èß,€¯xŸ+Œßõž‚ ]Ž?çS4 ³ÓÕרÇùŒ»–Àšu»«XàZ ñŸ GJ…9!„B!Ó‰÷< €4ùþIaÿY[øÅåò)…~˜ÒÜ 4 k¤}|’!°f™v±À8c@Gøj $¬@!„BHq ƒ4•þÓˆÿ0¥ØŸ¥bÖU~ ¥2ÒÚÐcA¶î.Á—& È`4!„BÉEØgynÖÕÿ$#`c$ôKúiD>…? €Òi²]GÀ%ÈÓ˜£ÀòŸµ+À4-§…F!„BYua?ÍëOÓî/Î pò³/A|^ÿ4ŸÂŸ@evüÚ< â‹ô¹Ì€´—}§ ` !„B!e1’Zÿ¥é&ˆý¸¢‚."«æ!4Vb‡­eܱӚµ ¢?’WýËÔ€æ)ú4!„BÈ<æI÷ÒF¤m×÷8O¢@2 |_ WŠ@BÔgýóhH!N!„BHþ"8Œ¹'ÜÓŠý Fô‡ üœ„ÀJ â: $E)Ä~ž«þû„B!„,Î.'y~ }dŽ‚Z>: ˹OÀ§½B!„²xƒ )%ÀgL#ø)öi‚>‘Ÿg¸? B!„B#øÓ>>Li øþÅ> RBCÀ|LÍóøh…Ce~M_k[WQcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCV^fV^fcSCcSCcSCcSCcSCZZZZZZZZZZZZ_WOcSCcSCcSCZZZcSCcSCcSCcSCZZZZZZZZZZZZZZZcSCcSCcSCcSCZZZcSCcSCcSCcSC_WOcSCcSCcSCcSC_WO_WOcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC\WO;i‹!x»†ç‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‚Ü'u¯Fcx`THcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCV^f)„Ü ‹ó ‹ó$‡çQbrcSCcSCQbr ‹ó ‹ó ‹ó ‹ó2|ÄcSCcSCQbr ‹óQbrcSCcSCQbr ‹ó ‹ó ‹ó ‹ó ‹ócSCcSCcSCQbr ‹óZZZcSCcSC2|Ä)„ÜcSCcSCcSCcSC;u­)„ÜcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCS\`%v´ˆí‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ …ä2oœ[WQcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC_WO$‡ç2|ÄV^fV^f6x¸Dm•cSCcSCQbr ‹óMf~QbrQbrV^fcSCcSCQbr ‹óQbrcSCcSCQbr ‹óMf~QbrQbrQbrcSCcSCcSCQbr ‹óZZZcSCDm• ‹ó)„ÜcSCcSCcSCcSC;u­)„ÜcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC[WQ(t®‰ð‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ †ç7l’aTFcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCHi‰ ‹óZZZcSCcSCcSCcSCcSCcSCQbr ‹óZZZcSCcSCcSCcSCcSCQbr ‹óQbrcSCcSCQbr ‹óMf~QbrQbrZZZcSCcSCcSCQbr ‹óZZZQbr ‹ó)„Ü)„ÜcSCcSCcSCcSC;u­)„ÜcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCGcv …ä‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ€Õ*sª:jŽEdzWlzŠš¤y’£V„¦'y¶ „à‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ~ÍU[]cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCDm• ‹ócSCcSCcSCcSCcSCcSCcSCQbr ‹óZZZcSCcSCcSCcSCcSCQbr ‹óQbrcSCcSCQbr ‹ó ‹ó ‹ó ‹ó;u­cSCcSCcSCQbr ‹óQbr$‡ç6x¸Dm•)„ÜcSCcSCcSCcSC;u­)„ÜcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC3nš‰ñ‰ñ‰ñ‰ñ‰ñ „á1oWZZcSCcSCcSCcSC€sfÿÿÿÿÿÿÿÿÿøø÷ÒÎÉ“£2{³ˆí‰ñ‰ñ‰ñ‰ñ †çIbscSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCHi‰ ‹óZZZcSCcSCcSCcSCcSCcSCQbr ‹óZZZcSCcSCcSCcSCcSCQbr ‹óQbrcSCcSCQbr ‹óQbrZZZZZZ_WOcSCcSCcSCQbr ‹ó)„Ü)„Ü_WODm•)„ÜcSCcSCcSCcSC;u­)„ÜcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC,r§‰ñ‰ñ‰ñ‰ñ‡ê/p¢"x¹‚ÙbSDcSCcSCcSCcSC€sfÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿööõ¯¯®3~¶‰ñ‰ñ‰ñ‰ñˆîBfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC)„Ü)„ÜMf~Mf~2|ÄDm•cSCcSCQbr ‹óZZZcSCcSCcSCcSCcSCQbr ‹óQbrcSCcSCQbr ‹óDm•Hi‰Hi‰Mf~cSCcSCcSCQbr ‹ó$‡çV^fcSCDm•)„ÜcSCcSCQbrHi‰2|Ä$‡çHi‰Mf~cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCFdxˆî‰ñ‰ñ}Ë-q¥ˆî‰ñ‰ñIaqcSCcSCcSCcSC€sfÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñï „á‰ñ‰ñ‰ñ‰ðFcwcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCZZZ2|Ä ‹ó ‹ó-€ÐQbrcSCcSCQbr ‹óZZZcSCcSCcSCcSCcSCQbr ‹óQbrcSCcSCQbr ‹ó ‹ó ‹ó ‹ó ‹ócSCcSCcSCQbr ‹óHi‰cSCcSCDm•)„ÜcSCcSC2|Ä ‹ó ‹ó ‹ó ‹ó ‹ócSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCFdxˆî%v´|ȉñ‰ñ‰ñ‰ñ#w·cSCcSCwi[Žƒw’ˆ¶®§¶¯§¿¸²áÞÛÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ²²°Ñ‰ñ‰ñ‰ñ†éR]bcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC_WO_WOcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCIaq׉ñ‰ñ‰ñ‰ñ „áG|£¾¸±ïíìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿãáÞ¹±ªÂ¼µûúúÿÿÿÿÿÿÿÿÿÿÿÿÆÄÀ€Î‰ñ‰ñ‰ñ€Ô`UHcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC^VLÒ‰ñ‰ñ‰ñ‰ñ1y°ÂÂÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøø÷º³¬ØÔÐÿÿÿÿÿÿÿÿÿÿÿÿ¼»¸‚Û‰ñ‰ñ‰ñ/p cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC"w¸‰ñ‰ñ‰ñ‡ëbƒ›óòñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÜÙÁº´ÿÿÿÿÿÿÿÿÿÿÿÿ“œ¢ˆî‰ñ‰ñˆíVZ[cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCiYJxj\€sfzl^rdUcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC@f‚‰ñ‰ñ‰ñˆînˆ›üüûÿÿÿÿÿÿÿÿÿÿÿÿóòñÅÀº·¯¨±©¡™…¬¤›ÔÏËýüüÿÿÿÿÿÿÿÿÿÿÿÿíëê¼µ¯ÿÿÿÿÿÿÿÿÿøø÷H©‰ñ‰ñ‰ñ'u¯cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCdTDŽ‚w¼µéçäÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø÷öÚ×Ó°¨ xj\cSCcSCcSCcSCcSCcSCcSCcSCcSC‹€tÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅ­¥œcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC~qcÏÊÅÏÊÅÏÊÅÏÊÅÏÊź³¬cSCcSCcSCcSCcSCcSCcSCÁ»µÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅjZKcSCcSCcSCcSCcSCÁ»µÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅwi[cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC‘†{¨Ÿ–k\McSC^VK „á‰ñ‰ñ‰ñO~¡úúùÿÿÿÿÿÿÿÿÿõôòµ­¦ÌÇÂûúúÿÿÿìêècSCcSC^VL8x©±¶¸ÿÿÿÿÿÿÿÿÿÿÿÿçåâÍÈÃÿÿÿÿÿÿÿÿÿÈÄÀ „á‰ñ‰ñˆîYYUcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC—Œ‚æãáÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÌÇÂxj\cSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm^OcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÔÐËÿÿÿööõ¸±©8l“‰ñ‰ñ‰ñ"{¾ëéçÿÿÿÿÿÿÿÿÿâßܽ¶¯þþþÿÿÿÿÿÿÿÿÿìêècSCcSC>h‡‰ñ …åe…÷öõÿÿÿÿÿÿÿÿÿÉýðîíÿÿÿÿÿÿþþþYƒ£‰ñ‰ñ‰ñ7l”cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCn_PÐËÆÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùøø –cSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm^OcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCrcUþþþÿÿÿÿÿÿÛØÔ‚Û‰ñ‰ñˆî›§®ÿÿÿÿÿÿÿÿÿåâß”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìêècSCcSC}ɉñ‰ñ‰ðYœûûûÿÿÿÿÿÿÿÿÿ´­¥ÿÿÿÿÿÿÿÿÿ¼º¶ˆì‰ñ‰ñ~ÏcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCvhZêçåÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿº³¬dTDcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm^OcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”Šÿÿÿÿÿÿÿÿÿ}–§‰ñ‰ñ‰ñ(y·öõôÿÿÿÿÿÿûúú{m`cSC£šÿÿÿÿÿÿþþþÙÕÑ´¬¤cSCU[\‰ñ‰ñ‰ñ‰ñˆí‡˜¥ÿÿÿÿÿÿÿÿÿÒÎÉîíëÿÿÿÿÿÿõôò#z¼‰ñ‰ñ‰ð[WQcSCcSCcSCcSCcSCcSCcSCcSCpaRêèæÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿº³¬cSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm^OcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÁ»µÿÿÿÿÿÿÿÿÿ9x§‰ñ‰ñ‰ñv’¥ÿÿÿÿÿÿÿÿÿ·¯¨cSCcSCcSC¬£›áÞÛ~qccSCcSCcSCcSCEdz …å‰ñ‰ñ‰ñ}Ëäáßÿÿÿÿÿÿþþþ¾·°ÿÿÿÿÿÿÿÿÿi‹¤‰ñ‰ñ‰ñFcxcSCcSCcSCcSCcSCcSCcSCdTDÖÒÎÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìêèÇÁ»·¯¨»´­ÍÈÃóòðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùùø‹€tcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèåãâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜk\McSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÝÙÖÿÿÿÿÿÿîìê{ĉñ‰ñ‡ëÉÆÃÿÿÿÿÿÿþþþ@vž1ožYYUcSCcSCeUEcSCcSCcSCcSCcSCcSCQ]cˆì‰ñ‰ñ‰ñ~–¦ÿÿÿÿÿÿÿÿÿ·¯¨ÿÿÿÿÿÿÿÿÿŸ¥©‰ñ‰ñ‰ñ5m—cSCcSCcSCcSCcSCcSCcSC¢˜ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåâߎƒwdTDcSCcSCcSCcSCdTD”‰~åâßÿÿÿÿÿÿÿÿÿûúú‹€tcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCïíìÿÿÿÿÿÿÕÑÍ „à‰ñ‰ñ|ÇîìêÿÿÿÿÿÿàÝÙ€Õ‰ñˆî&u²`UHcSCcSCcSCcSCcSCcSCcSCcSCVZ[Q]dQ]dQ]d`]W€sf€sf€sfugY€sf€sf€sfrfYQ]dQ]dQ]dYYUcSCcSCcSCcSCcSCcSCiYJóòðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÇÂhXIcSCcSCcSCcSCcSCcSCcSCcSCfVG¹²«ÿÿÿüüû“ˆ}cSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCøø÷ÿÿÿÿÿÿÊÄ¿‰ñ‰ñ‰ñ(w´ÿÿÿÿÿÿÿÿÿ»¹¶‰ñ‰ñ‰ñ‚ÛcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC¡˜ŽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÚ×ÓgWGcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC –Œ“ˆ}cSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCâßÜâßÜâßܦ¥¢ÑÑÑ=ršÿÿÿÿÿÿÿÿÿ§­‰ñ‰ñ‰ñzÀcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÚ×Óÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýýýyk^cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCm^Oÿÿÿÿÿÿÿÿÿ©¬¬‰ñ‰ñ‰ñ{ÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCj[LýýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿȽcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCdTDûûûÿÿÿÿÿÿËÆÀ‡ê‰ñ‰ñ‡é`THcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ}qÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ’‡|cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”Šÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿêèæ»´­»´­»´­»´­»´­»´­»´­»´­»´­»´­ÌÇÂÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¸±©¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–gXHcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø÷ööõôöõôöõôöõôöõôöõôöõôöõôöõôöõôöõôöõôˆ{ocSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCãàÝÿÿÿÿÿÿïíì y¼‰ñ‰ñ‰ñ8k‘cSCcSCcSCcSCcSCcSCcSCdTD®¦ž»´­»´­»´­–Œ»´­»´­»´­ƒƒ-q¥-q¥-q¥Nj~»´­»´­»´­’‡|cSCcSCcSCcSCcSC –Œÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿk\McSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm^OcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠ~rcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC½¶¯ÿÿÿÿÿÿÿÿÿZ‚ ‰ñ‰ñ‰ñˆìJaqcSCcSCcSCcSCcSCcSC±©¡ÿÿÿÿÿÿÿÿÿüüûÁº´ÿÿÿÿÿÿÿÿÿ]†¥‰ñ‰ñ‰ñ]„¡ÿÿÿÿÿÿÿÿÿ¢˜cSCcSCcSCcSCcSC© —ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøø÷cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm^OcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠ~rcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCgXH˜‚ÿÿÿÿÿÿÿÿÿÔÒÏ€Õ‰ñ‰ñ‰ñˆî/p¢U[\cSCcSCrdUÒÎÉÿÿÿÿÿÿÿÿÿÿÿÿ¼µøø÷ÿÿÿÿÿÿõôó#yº‰ñ‰ñ‰ñ¦«ÿÿÿÿÿÿÿÿÿ…ylcSCcSCcSCcSCcSC¬¤›ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðïícSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm^OcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠ~rcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC}ob»´­óòñãàÝâßÜÿÿÿÿÿÿÿÿÿ”¡‡ë‰ñ‰ñ‰ñ‰ñ‰ñ×cSCk\MùøøÿÿÿÿÿÿÿÿÿåãàÑÌÈÿÿÿÿÿÿÿÿÿ¤«¯ˆî‰ñ‰ñ~ÍâßÜÿÿÿÿÿÿööõfVGcSCcSCcSCcSCcSC¥œ“ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýüücSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm^OcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠ~rcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC_UJ?g…"w¸ÓÐËÿÿÿÿÿÿÿÿÿ´¬¤ÿÿÿÿÿÿÿÿÿüüûd„›‡ë‰ñ‰ñ‰ñ‰ñ‚ÜcSCcSCľ¸ÿÿÿÿÿÿèåãÁº´ÿÿÿÿÿÿÿÿÿõôó-y³‰ñ‰ñ‰ñK}¢ÿÿÿÿÿÿÿÿÿÌÇÂcSCcSCcSCcSCcSCcSC’‡|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}obcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm^OcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿØÔÐÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅÏÊÅ~qccSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC@f‚ †ç‰ñ‰ñu¡ÿÿÿÿÿÿÿÿÿïíìÌÇÂÿÿÿÿÿÿÿÿÿüüûŠš¥~Ήñ‰ñ‰ñ‚ÜcSCcSC‰}qþþþÉÃ½ÌÆÁÿÿÿÿÿÿÿÿÿÿÿÿx ‰ð‰ñ‰ñ „â½¾½ÿÿÿÿÿÿÿÿÿ”‰~cSCcSCcSCcSCcSCcSCwi[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ©¡˜cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿØÔЀsf€sf€sf€sf€sf€sf€sf€sf€sf€sf –ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿˆ|pm^Om^Om^Om^Om^Om^Om^Om^Om^Om^Om^Om^OdTDcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCP^f‰ñ‰ñ‰ñÖÕÒÎÿÿÿÿÿÿÿÿÿÏÊÅâßÜÿÿÿÿÿÿÿÿÿÿÿÿÞÛØn‹ )w²€ÔÑcSCcSCseV¹²«òðïÿÿÿÿÿÿÿÿÿÿÿÿ«±µƒÞ‰ñ‰ñ‰ñH|¡þþþÿÿÿÿÿÿîìêeVFcSCcSCcSCcSCcSCcSCcSCíëéÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿêèæeVFcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCzÁ‰ñ‰ñ‰ñUƒ¥ùøøÿÿÿÿÿÿÿÿÿÅ¿¹âßÜÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûúúàÝÙÐËÆÕÑÍéçäÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ²·º€Ô‰ñ‰ñ‰ñ}ËxmaÜÙÕÿÿÿÿÿÿ¦”cSCcSCcSCcSCcSCcSCcSCcSC¹±ªÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¨Ÿ–cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCl\McSCcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCP^f‰ð‰ñ‰ñˆî“›¡ÿÿÿÿÿÿÿÿÿÿÿÿÑÌÇÉľÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúúùŠš¥‚Û‰ñ‰ñ‰ñ†çU[\cSCdTD¤š‘âßÜgWGcSCcSCcSCcSCcSCcSCcSCcSCzm_þþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüüû‹€tcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC‚uhîìꤛ’cSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC,r§‰ñ‰ñ‰ñ ƒß¯¯®ÿÿÿÿÿÿÿÿÿÿÿÿïíì´­¥ÝÚÖÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýýýÀÂÁ=z¨ˆí‰ñ‰ñ‰ñˆîHbscSCcSCcSCcSCdTDcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÆÀºÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûúú¡˜ŽdTDcSCcSCcSCcSCcSCcSCgWGª¡™úúùÿÿÿþþþ„ycSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC_UKÖ‰ñ‰ñ‰ñ‚Ù²²±ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæãá·°©½¶¯×ÓÏîíëööõöõôéçäÏÊŨŸ–alq …ä‰ñ‰ñ‰ñ‰ñ†éHbucSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCseVôóòÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿãàÝ©¡˜‚uhqcTrcUˆ|p³«£îìêÿÿÿÿÿÿÿÿÿÿÿÿ÷öõrecSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¨Ÿ–”‰~”‰~”‰~”‰~”‰~”‰~”‰~”‰~”‰~”‰~”‰~”‰~”‰~‹scSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¸±©¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCT\_†ç‰ñ‰ñ‰ñƒÞ”œ¡ûúúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïíìÛ×ÔÁ»µcSCcSCcSCcSCaTG …ä‰ñ‰ñ‰ñ‚ÚP^fcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC’‡|þþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿíëêrdUcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCKao‡ë‰ñ‰ñ‰ñˆìVƒ¤ØÕÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìêècSCcSCcSCcSCcSC-q¥‰ñˆï*s«#w· y½cSDcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC£™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¢˜cSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCJap†ç‰ñ‰ñ‰ñ‰ñ€Ôy ÖÓÏþþþÿÿÿÿÿÿÿÿÿÿÿÿìêècSCcSCcSCcSCcSCR]b(t®/p¡ †æ‰ñ‰ñ9kcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC˜‚øø÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüüû£šcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCR]b‚Ú‰ñ‰ñ‰ñ‰ñ‰ñ‚ÚJ€¨˜¡¦Ç½ãáÞôóòëéçcSCcSCcSC^VLGcu)t­†è‰ñ‰ñ‰ñ‰ñˆíT[^cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC{n`ØÔÐÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâß܃vicSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC]VM'u°‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‡ìÐ y½&u²$vµzÁ؉ð‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‡ê:jcSDcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC‚vÏÊÅýüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÖÒÎ˜ŽƒdTDcSCcSCcSCcSCcSCcSCcSCž”ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCL`m~̉ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñˆï%v³XYVcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCl\M‘†z¬¤›¿¹²Å¿¹Å¿¹¾·°«¢™“ˆ}n_PcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCn_P€sf€sf€sf€sf€sfwi[cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCj[L€sf€sf€sf€sf€sf{m`cSCcSCcSCcSCcSCcSCcSC}ob€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf{m`cSCcSCcSCcSCcSCcSC}ob€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sf€sfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCJaq"w¸ˆí‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ …ã-q¥U[\cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC^VMDd{.q¤|ǃވï‰ñ‰ñ‡ëØy¾3nšL`mbTEcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCchef-12.14.60/omnibus/resources/chef/msi/assets/dialog_background.bmp000066400000000000000000022616301276456504500255250ustar00rootroot00000000000000BM˜c 6(í8 bc   cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCV^fZZZcSCcSCcSCcSCcSCZZZZZZZZZZZZcSCcSCcSCcSCZZZcSCcSCcSCcSCZZZZZZZZZZZZ_WOcSCcSCcSCZZZcSCcSCcSC_WO_WOcSCcSCcSCcSC_WO_WOcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCMf}$‡æ ‹ò ‹ò2|Ã_WOcSCcSCQbr ‹ò ‹ò ‹ò ‹òHi‰cSCcSCQbr ‹òZZZcSCcSCQbr ‹ò ‹ò ‹ò ‹ò;u¬cSCcSCQbr ‹òcSCcSCZZZ$‡æDm•cSCcSCcSCcSC2|Ã;u¬cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCZZZ ‹ò;u¬_WOV^f;u¬ZZZcSCcSCQbr ‹òZZZZZZZZZcSCcSCcSCQbr ‹òZZZcSCcSCQbr ‹òZZZZZZZZZ_WOcSCcSCQbr ‹òcSC_WO-€Ï ‹òDm•cSCcSCcSCcSC2|Ã;u¬cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCDm•$‡æcSCcSCcSCcSCcSCcSCcSCQbr ‹òcSCcSCcSCcSCcSCcSCQbr ‹òZZZcSCcSCQbr ‹òDm•Dm•Dm•ZZZcSCcSCQbr ‹òcSC6x¸)„Û$‡æDm•cSCcSCcSCcSC2|Ã;u¬cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC?q )„ÛcSCcSCcSCcSCcSCcSCcSCQbr ‹òcSCcSCcSCcSCcSCcSCQbr ‹òZZZcSCcSCQbr ‹ò2|Ã2|Ã2|ÃV^fcSCcSCQbr ‹òDm• ‹òV^f)„ÛDm•cSCcSCcSCcSC2|Ã;u¬cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCQbr ‹òQbrcSCcSCQbrcSCcSCcSCQbr ‹òcSCcSCcSCcSCcSCcSCQbr ‹òZZZcSCcSCQbr ‹òcSCcSCcSCcSCcSCcSCQbr ‹ò ‹òHi‰cSC)„ÛDm•cSCcSCcSCcSC2|Ã;u¬cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC;u¬ ‹ò-€Ï)„Û ‹òV^fcSCcSCQbr ‹òcSCcSCcSCcSCcSCcSCQbr ‹òZZZcSCcSCQbr ‹ò2|Ã2|Ã2|ÃDm•cSCcSCQbr ‹ò;u¬cSCcSC)„ÛDm•cSCcSC6x¸)„Û ‹ò$‡æ)„Û?q cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCQbrDm•Dm•ZZZcSCcSCcSCZZZDm•cSCcSCcSCcSCcSCcSCZZZDm•_WOcSCcSCZZZDm•Dm•Dm•Dm•QbrcSCcSCZZZDm•cSCcSCcSCHi‰QbrcSCcSCMf}Dm•Dm•Dm•Dm•QbrcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCl]Nƒwj“ˆ}•Š’‡|…xlm^PcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCrdU®¥âßÜþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿçä⸱©zm_cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŽ‚wìêèìêèìêèìêèìê訟–cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèìêèìêèìêèìêèÒÎÉcSCcSCcSCcSCcSCcSC…ylìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèÁ»µcSCcSCcSCcSCcSCcSCcSCÛ×ÔìêèìêèìêèìêèãáÞcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCj[L·°©úúùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÅ¿¹paRcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC~qcéçäÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñïîˆ|pcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC…xlööõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüüû“ˆ}cSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCxk]óòñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæãáeVFcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCdTDßÛØÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅ–‹€seVeUEk\Mƒwj°¨ îíëÿÿÿÿÿÿÿÿÿÿÿÿêçåtfXcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÅ¿¹Š~rŠ~rŠ~rŠ~rŠ~rŠ~rŠ~rŠ~rŠ~rŠ~rŠ~rŠ~rŠ~r~qccSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC¡—ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðïíŠ~rcSCcSCcSCcSCcSCcSCcSCiYJµ­¦þþþÿÿÿêçåtfXcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCeUEìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöõôzl^cSCcSCcSCcSCcSCcSCcSCcSCcSCcSC’‡|âßÜtfXcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŽ‚wÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿš…cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCdTDcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC¾·°ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿãáÞdTDcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCàÝÙÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ©¡˜cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÔÏ˨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅž”Šž”Šž”Šž”Šž”Šž”Šž”Šž”Šž”Šž”Šž”Š“ˆ}cSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿþþþâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜ“ˆ}cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCööõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚uicSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿž”ŠcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿqcTcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿž”ŠcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm^PcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿž”ŠcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCúúùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{n`cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿž”ŠcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCäáßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ“‰cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöõôìêèìêèìêèìêèìêèìêèìêèìêèìêèìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñïîâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜâßÜÊÅÀcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿúúù¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–¨Ÿ–}obcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCľ¸ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÑÌÇcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC˜Žƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚uicSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCiYJõôòÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåãàjZKcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC|oa›†cSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC±©¡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿØÕÑo`QcSCcSCcSCcSCcSCcSCcSCcSC˜‚÷öõþþþ‘†{cSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿöõôcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCjZKíëêÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóòñ¨Ÿ–pbScSCcSCcSCgWG‘†z×ÒÎÿÿÿÿÿÿÿÿÿùøø‚uicSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿ÷öõwi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCˆ|püüûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþèåãÚ×ÓáÞÛùøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðïívhZcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC’ˆþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÑÌÈcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC–Œøø÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÛØseVcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC{m`×ÓÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüüû´­¥gWGcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC‚vÏÊÅûûûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñï¬ugYcSCcSCcSCcSCcSCcSCcSCcSCcSC”‰~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±©¡cSCcSCcSCcSCcSCcSCcSCìêèÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCj[LŽƒw¨Ÿ–¹²«½¶¯»´­°¨ “‰~qcdTDcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCiZJwi[wi[wi[wi[wi[m^OcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCwi[wi[wi[wi[wi[seVcSCcSCcSCcSCcSCcSChXIwi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[m^OcSCcSCcSCcSCcSCcSCcSCtfXwi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[wi[cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCbTEVZ[Fcw8k‘*sª"w¸{Å|Æ}Ë{Ã!x»&u²1ož>h…L`m\WOcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCQ]c9k x¼†ç‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ð€Ô*s«Ce~^VKcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCWZX3nš×‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‡ì"x¹Ce|aTFcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCL`l!x»‰ð‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñƒÝ7l“]WOcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCO^g{Éñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ†ç6l•`UIcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC[WQ'u¯‰ð‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñƒÞBfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCBe~ …ä‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ð‚Úy¾+s©/p 4m˜7l’3nš.q¤&v²}É …ã‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ"x¹[WQcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC_UJ'u¯‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ð|Ç3nšL`l`THcSCcSCcSCcSCcSCÇÁ»âßÜØÕÑÌǶ¯§ –vwvCg€)t¬ „á‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ …ãHbscSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCWZYÒ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ€Õ7l”YYUcSCcSCcSCcSCcSCcSCcSCcSCcSCØÕÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿööõÐËÆ›’ˆWiu'u°ˆí‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ð7l“cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCO^f „á‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ~ÌBe~;i‹Be~cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCØÕÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüüûÇÁ»~{w2n›†é‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ&u²aTGcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCM_k†ç‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñƒÝBf7l’ …ä‰ñ€ÓcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCØÕÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ×ÓÏ}zv+r¨ˆï‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ y¼aTGcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCU[\ ƒß‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ð/p¢Ce}‚܉ñ‰ñ‰ñ‰ñM_kcSCcSCcSCcSCcSCcSCcSCcSCcSCØÕÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉľ^ks€Ô‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ y¼aTGcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCU[\ ƒß‰ñ‰ñ‰ñ‰ñ „áFdx$w¶‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ'u°cSCcSCcSCcSCcSCcSCcSCcSCcSCØÕÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóò𕎅,r§‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ%v´cSDcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCU[\ ƒß‰ñ‰ñ~ÍIaq‚Ú‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‡ë]VNcSCcSCcSCcSCcSCcSCcSCcSC„x¤š‘­¥œÂ¼µÙÕÑùøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¼µ®Cj†‡ì‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ2oœcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCWZY‚ÙzÁCe|‡ê‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ:jcSCdTD€sf§ž•ľ·Ú×ÓêèæòñðöõôîíëæãáÑÌȹ±ª˜ŽƒŽƒw¶¯§êçåÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿØÔÐUjx†é‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñGcvcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCZXS?g„‰ð‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ †ç1ožtvu»´­òðïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÚ×Ó¢˜”‰~×ÒÎÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÝÙ]lu†ç‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ …ãZXTcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCBf‰ð‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñˆï-q¥}|yÔÏËÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöõô±©¡’‡|äáßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâßÜUjxˆî‰ñ‰ñ‰ñ‰ñ‰ñ‰ñzÁcSDcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCKaoˆî‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ€Õ[jsȽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóò𞔊¯§Ÿýýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ×ÒÎFi„‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñAfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCWZX †æ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ%v´‰…~òñðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÎÉÄ‘†{ñïîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¾·°+r¨‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ ƒß^VLcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCaTF}ˉñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ2oœ­¥ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿêèæ‹€téçäÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ—‡Ð‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ1ožcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC4m˜‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ6m–¼µ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùùøÕÑ͵­¦ž”‹”‰~…z–Œ¨Ÿ–¼µæãáÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóòð‹€têçåÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõôó`lsˆï‰ñ‰ñ‰ñ‰ñ‰ñ‡é\WOcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCdTDcSCcSCcSCcSCcSCcSCcSCU[]ˆí‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ+r¨³«¤ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÙÕÑš…•мµ®ÞÛ×ôóòÿÿÿ”‰~cSCcSCcSCcSCyk^³«¤óòñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóòð‹€tñïîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉý-q¥‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ3nšcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC¤š‘ÝÚÖ˜ŽƒeVFcSCcSCcSCcSC x¼‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ{Åž–ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿãàÝ‘†{±©¡óòðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”‰~cSCcSCcSCcSCJap{ÄOgy³«£ûúúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿêç呆{ýüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ…€z †ç‰ñ‰ñ‰ñ‰ñ‰ñ …å`UHcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCãáÞÿÿÿÿÿÿîìꬣ›n_PcSCN_j‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ …årtrúúùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¶¯§ž”‹òñðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”‰~cSCcSCcSCcSC%v´‰ñ‰ñ y¼psråâßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÐËÆ±©¡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿØÕÑ3nš‰ñ‰ñ‰ñ‰ñ‰ñ‰ñBfcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC‚uiÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùøø—Œ‚ y¼‰ñ‰ñ‰ñ‰ñ‰ñ‰ñAi†âßÜÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüüûš…ÇÁ»ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”‰~cSCcSCcSC\WOˆì‰ñ‰ñ‰ñ …äOgyÒÎÉÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ –ŒãàÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ„x‡ê‰ñ‰ñ‰ñ‰ñ‰ñz¿cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC¶¯§ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿipr‰ð‰ñ‰ñ‰ñ‰ñ‰ñ~Φ•ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþ“ˆ}ý·ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”‰~cSCcSCcSC:jމñ‰ñ‰ñ‰ñ‰ñˆîFgÔÏËÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòñð’‡|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉý(t®‰ñ‰ñ‰ñ‰ñ‰ñ‰ð[XRcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCçäâÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÚ×Ó1o‰ñ‰ñ‰ñ‰ñ‰ñ‰ñOhxóòñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¦“cSCtfXêçåÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”‰~cSCcSCcSCÒ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñˆîQhwåãàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ´¬¤ÕÑÍÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûû]lu‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ@f‚cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCugYÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¦“‚Û‰ñ‰ñ‰ñ‰ñ‰ñѨŸ–ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÊÅcSCcSCcSCtfXêçåÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùùøØÕѼµqdcSCcSCO^h‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ …åqtsûûûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõôó•Šÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¡˜ŽƒÞ‰ñ‰ñ‰ñ‰ñ‰ñ&u±cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC›†ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿbms‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ;i‹èæäÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøø÷teWcSCcSCcSCcSCtfXêçåÿÿÿÿÿÿîìê¡—j[LcSCcSCcSCcSCcSCWZZ$w¶‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ x¼´­¥ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¢˜êèæÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÑÌÇ*sª‰ñ‰ñ‰ñ‰ñ‰ñ ƒßcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC½¶¯ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿçåâ8k‘‰ñ‰ñ‰ñ‰ñ‰ñˆï}{vÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¶¯§cSCcSCcSCcSCcSCcSCtfXêçåȽj[LcSCcSCcSCcSCcSCcSCcSCcSCcSCIbr …ã‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñQgvôóòÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÚ×Ó¶¯§ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúúùIg|‰ñ‰ñ‰ñ‰ñ‰ñ‰ñZXScSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÚÖÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿľ¸#w¸‰ñ‰ñ‰ñ‰ñ‰ñ|Ç´­¥ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýüü^ip7l“]VNcSCcSCcSCcSCcSCo`QdTDcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCR]b …ã‰ñ‰ñ‰ñ‰ñ‰ñ‰ñz·¯¨ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþ„yÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyyw‰ñ‰ñ‰ñ‰ñ‰ñ‰ñIaqcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCóòðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¦”‚Ú‰ñ‰ñ‰ñ‰ñ‰ñ2n›ÝÚÖÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÑÌÈ,r§‰ñ‡é+r¨U[]cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCHbu‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ðpttÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ˜Žƒøø÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¡˜Ž „á‰ñ‰ñ‰ñ‰ñ‰ñ9kcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCiYJÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠ‚z‰ñ‰ñ‰ñ‰ñ‰ñ‰ñGezýüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ£šƒÝ‰ñ‰ñ‰ñ‰ðy¾JaqcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCXYWWZYWZYWZYWZYWZYWZY_UJvhZwi[wi[wi[wi[wi[l\MteWwi[wi[wi[wi[wi[m^OYYUWZYWZYWZYWZYWZY]WOcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCwi[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿkrv‰ñ‰ñ‰ñ‰ñ‰ñ‰ñiptÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿpuv‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ|ÆcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC„wkÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUiw‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‹ƒ{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþIfy‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ8k‘cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCŠ~rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿHcu‰ñ‰ñ‰ñ‰ñ‰ñ‡ê–Œÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòñð>h‡‰ñ‰ñ‰ñ‰ñ‰ñ‰ñKaocSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC{n`Å¿¹Å¿¹Å¿¹Å¿¹Å¿¹Å¿¹P^e'u°'u°'u°'u°'u°+s©ž”Šÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìêè9k‰ñ‰ñ‰ñ‰ñ‰ñ‰ñQ]dcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC™Ž„ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðîíh‡cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC€sfÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcou‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ#w¶cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCiYJþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ—Ž… †ç‰ñ‰ñ‰ñ‰ñ‰ñˆìZXTcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCçåâÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÅ¿¹#w¶‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ&u±cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC{m`êçåìêèìêèìêèìêèìêèìêèˆ{oìêèìêèìêèìêèìêèìêè–‹€Ð‚܂܂܂܂Ü>h‡Ú×Óìêèìêèìêèìêèìê褛’cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC¿¸²ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿööõJg{‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ>h…cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCl]NâßÜÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåâߨŸ–ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿhpt‰ñ‰ñ‰ñ‰ñ‰ñ‰ñRjzÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ–ŒcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC“ˆ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¡™€Õ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ðh‡ˆï‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‡ì0p ~{wËÆÀýýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”‰~cSCcSCcSCcSCcSCcSCcSCcSCcSCIbs‚Ü7l”Edz}ɉñ‰ñ‰ñ‰ñ‰ñ7l’cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCHbt†è‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‡ì)s«`mt¨Ÿ–ÝÙÖþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”‰~cSCcSCcSCcSCcSCcSCcSCcSCcSC]VM=h‡~Ήñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñˆîN_jcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCT[^€Ó‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñÖ2oœXkw•Œ‚µ­¦ÒÎÉèåãûúúÿÿÿÿÿÿ”‰~cSCcSCcSCcSCcSCcSC[XR@gƒ#w¶‡é‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‚ÜaTGcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC`UI+s©‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‡ë}Ë*sª7l”BfQgvWjvVdmKaoFcx=i‰1ož#w¸‚Û‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñƒÝKaocSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCL`mÖ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñˆï+s©^VMcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCaTG:jŽ …ã‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ{ÄR]bcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSC^VM5m– ƒß‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñzÀL`lcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCaTG@f‚{Éð‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ „â.q£S\`cSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCYYU:j y¼†é‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñ‰ñØ-q¥IaqaTFcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCaTFP^e>h‡,r§y¾Ò …ä‡ë‰ñ‰ñ‰ð †ç ƒß}É%v³3nšGcv[XRcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCcSCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿchef-12.14.60/omnibus/resources/chef/msi/assets/oc.ico000066400000000000000000001216301276456504500224550ustar00rootroot00000000000000 ðV P F šZ–!! 40j11 4'd|(" „  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò0 ‹ò@ ‹ò€ ‹ò@ ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò` ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÐ ‹òPÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÀ ‹òÿ ‹òô ‹òž ‹òP`\_¬bXSÔZcxÖ3‚×ò ‹òÿ ‹ò  ‹òÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òð ‹òÖ ‹òÿ)‡æˆdTC@dTC¬dTCèdTCÿdTCÿVh†Ø ‹òÿ ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò¬ ‹òÿ4Õà`[ZòdTCÿdTCÿdTCÿdTCôdTCßdTCÿXd|Ý ‹òÿ ‹òpÿÿÿdTCdTCp.…ßj ‹òÿ:~ÊÝdTCÿdTCâdTC¸dTCˆPm–_^dódTCÿdTCâdTCÿ?z¿ô ‹òàÿÿÿdTC`dTCÿ-†ââ!‹ñòcWNÿdTCÐdTCñdTCÿdTC€ ‹òà ‹òÿ]aoôdTCòdTCÿ_]bØ ‹òÿ ‹ò@dTC cWNÿ ‹òÿ<}ÅÐdTCÿ3‚׸Gu­ddTCPÿÿÿ ‹ò` ‹òÀ9~̳dTCÀdTC¸bXQ¿ ‹òÀ ‹ò`dTC^_jÌ ‹òÀSkÎ`ZXÿ ‹òÿ ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC cWNÿ ‹òÿ ‹òÀÿÿÿÿÿÿdTCdTC€dTCtdTC€By¹ˆ1ƒÛdTC€dTC@ÿÿÿÿÿÿdTC dTCœdTCÿ4Õà ‹òÿ ‹ò° ‹ò@dTCÐdTCÿdTCÝdTCÿ"‹ðó.…àådTCÿdTC` ‹ò ‹òÀ\aqÜdTCÿdTCêcWNÿ3‚ØÜ ‹òÿ ‹ò€dTC¦dTCÝdTCÿLp¡à ‹òÿMožèdTCÿdTC ÿÿÿ ‹òÐ3‚ØÿdTEódTCódTCèdTCÿ_^dó_\^ÈdTCÿdTCÿUiˆâ ‹òÿ ‹òàbXQÄdTC ÿÿÿÿÿÿ ‹ò0 ‹òÿ9~ÌódTEódTCÿdTCÜdTCØdTCÈdTCÀ8ÎÄ ‹òÿ ‹òð ‹ò0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò` ‹òÿ3‚×ò_]bØdTCÿdTCÿdTC€ÿÿÿ ‹ò ‹òÖ ‹òâ ‹òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò@ ‹òà ‹òÿ*‡åÿByºÜ6ÒÈ ‹òÐ ‹òÿ ‹òÿ ‹òÐ ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò` ‹ò° ‹òÀ ‹òÀ ‹òÀ ‹ò  ‹òPÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü€ð€À€À€à€€€ÿ€á€À€€€€ÀC€à€ø€(2 Ä   ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò@ ‹òP ‹ò€ ‹ò@ ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò` ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò° ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò@ ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÐ ‹ò0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òp ‹òÿ ‹òÿ ‹òÿ ‹òÔ ‹ò` ‹ò ÿÿÿdTCÀdTCÿdTDò^^eÊ@z½è ‹òÿ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òð ‹òÿ ‹òó ‹òÜ ‹òÿ ‹ò`ÿÿÿÿÿÿdTCÀdTCÿdTCÿdTCÿdTCÿaYVØ3‚×ò ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò0 ‹òÎ ‹òñ ‹òÿ ‹òÿ3‚׸dTCdTCÀdTCÌdTCÐdTCÈdTCædTCÿdTCÿdTDò;}ÇÙ ‹òÿ ‹òÿ ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò0 ‹òÿ ‹òÿ!‹ñòTi‹ØdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÓdTCñdTCÿdTDñ9~Ìó ‹òÿ ‹òàÿÿÿÿÿÿÿÿÿdTC ÿÿÿ ‹ò ‹òà ‹òÿ"‹ðó_^dódTCÿdTCÿdTCædTCÈdTC dTCcWOâdTCÿdTCÿdTCÙdTCñdTCÿcWMÜ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿdTCðdTCÐ9~Ë  ‹òÿ ‹òÿ[bsÜdTCÿdTCÿdTCÓdTCÿdTCÿdTC€ ‹ò ‹òÿAy¼ÈdTCÿdTCÿdTCØdTCÿdTCÿMpŸØ ‹òÿ ‹òàÿÿÿdTC@dTCÿdTCÿ$Šíæ ‹òÿ8ÎÄdTCÿdTCÿdTCtdTCðdTCÿdTCÿdTC€ ‹òp ‹òÿ ‹òÿ:~ÉÊdTCÿdTCÿdTCØdTCÿcWOß ‹òÿ ‹òÿ ‹ò0dTC€dTCÿ^^eæ ‹òÿ ‹òÿXe}ßdTCÿ[buÐ ‹ò dTC0dTC°dTCPÿÿÿ ‹ò0 ‹òð ‹òÿ ‹òÿ\aqèdTCÿdTCÎdTCÿdTCÿ*‡åÿ ‹òÿ ‹ò`dTCdTCÿ[buÐ ‹òÿ ‹òÿdTCÿdTCÿ;}ƺ ‹òÿ ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò@ ‹ò@Nnš‡=Œ l™wfv˜ûºæš­sÚûÜO^‹ RåãK6÷«€û r߃îkôzº?k r¿n¢À§î{˜¬Êܯm âþmÄýÞvÿÖiè‹Ç•&ûgàÂJö d°YƼBH¨{!nìá¾dSb°Üïñ}ÝÜgí aö`»?ó~¾ê€Ï…À"`."Š("ïoíØd!yÈ@°ù^YÀÀ`°û¾Òæžà·$AD1xÀz`60˜å~½»aSr2„Hd Ø‚†ÏBBœì ì ì…„½û7ß{k{^~2ð^ë!Lw_³Z6C20‹Ö¶ Z z |ø!Àh`ä»gSoî·Fao,ü®CÅ>¦!B˜…܉:d)lD† ât]dIÁf„>Û}u¢^àG!ß=D½Ðo«ˆ%„…ˆÞ&ÕdÈÀ2ľ_è;#¡? }õŸ¹÷ Ã#„0°˜¼…È Êýyÿ2dÐ|da3Ñ€¶÷Lû.üRè=MŸAÓà‘A‘ÁdêÉ`ŠlŒdˆ éÈ@Ñ€¶Ï Qïx` ÐŒÐÇ~2X ¼ üø ¹ µþ?ÎAã!€F"FðC@Ð88(AV@Ƽ7 jPFa""„ 2VA“Y¨[AŒàg!m¿úcxùø ÅVO/óP¼À«PÌÁfY´›AŒàçEÀ¾ÀÅ(’ïô2H 8Hàk¨w¾BVÁÆ B†6E†bЀà·N.ú¢è~²+ï2Ø2¢È˜< LED°1{!!C.ü¶À Hã{‚Ÿ¹_­ 6"‚OÈAƒØætŒàg£Ü_#ßùýÛü}jåðˆÀo”‘ n» ;FðƒHðG×;“Ñøé>nFíÌåîÏ·IØ&¸Oø(¸7¸ ùr¶Õû² ÁFµÿîDŠÞ/·%"ئzŒÖÏG¹ûK€3P:/ÜÛ¶V¡ŒÁƒî×ÛT|`›!Ÿðg¡Fœ“‘¹ß åñ3Ø6á¥nÞÖ²¸iO1Z¿ *Ù½åò3~~x ¸ øõéKi½øc´~gàrÝÏO÷kÏ ÙðÜ‚»€¡ ä' ¤¥Ähý¶HÛß ì‚È QG½YkÇ|í¡¡ŸAýìÁÍýÌû:@ú»Mu([pð5Péý"ˆ í Fëw¡^ëç¥Ñõz]quÔÒ »¯/Ý÷ÐL¾¨÷µU?¥×XιÁèz ‡ ‘,Ý›@w6(êý,è¾÷@)ÓÔO1òF›e“^u6õÖÀsî×iH—Õ¯¿p;Êé·f­ï A¯õ½£ÔÕgÀ" ,çûü¬ºõ„êl‚QÈÛàPÑ–+¾Ü?{}8›° Îjí`@u4èDœ€÷ù•/ŸìÐðz°øÚDÚú)ŽÑ,ÃAˆ r¨Ÿ‘“ì›ØBÔ¢ E7ߣ~ƒ´ ´0ãbòú]€óÖïHëKí9hÕºï«QÁÊ f-gN^Nõzr«mÂYœ7cLöÚºœPE$+u¬6Ô›çÞä¡¡Ô·(£âþ&¦àöâq¥54<Ç¿ÁÙþî=_à¾>vìCODƒáh|y>*±n„á^Ç•ÀëÈ%pZò¡©€Voø„?ظ8™£­QÔ¾Z¬DC2gX0£0«vù•Ñ~ØÕziiIÎúpvVÔ± ùï‰ÖîI%€MNdS2hçÞ—ß»º÷¦0I§F‚§•é"üЊÀ'üyhaÜìDêùûÞn7K€ÿ,療¢u Îüðˆà’šü"´°G¡@å‘Ôk·d=‹”!øÅs®E÷ep:p,š¸\Lâb6Ú¾ìxàÇâq¥iaú{HEÍù øEd">Š|ÿT"°Òö³€Ç²ö䜼ʲCß;¶ âXÝPúí7ÈÏmG+óãŒ4iªyøõoÜ…îá¨î  f×°ƒŠ®#ü~?Rž|Âß8ulå%û¼|ð6­˜=£Öà{Út~Hað Oô€Ï'5"ý6üo€Çвê&7} 呬H œJ½™ŸAÓPˆ4»?òvþ„*<ŸEÓ|J!LNC®ÁvˆZÎU@uº ?¤(ø„¿7J½CjÔó¯~)Ê®yæä££«ërÛ£~ƒ‹€£Rä<[+‚¨²q{ÖäTqp"‚§QŒàA÷û Ц-=iž«è jÌëP­FÚùý~¤œiš¢Â_&ÆŒÏ Ø‡µ_ñÔè,^]—»;rK^D¾~²Ï³ÕÂgþÿš­çý-¸]ôGYƒ?¢Y_@Ħ–ëV!«"-ý~?RÊð ê…?™çh#mð–.l³fÖè·OÊGý‹‘©Ú–Œ©xB}æJ þˆï^CWcQºxšѱŸÞþBûý~¤ ¤ ðW ?𑢜êçúà(–Ó™ùW %#øq€OûŸDób'ðúT¬s/ÊLFU¿.EÏlsq$˜‹,º´©ôÛRÂðU€õ"ùÂo ¿-çð¢Kÿ1zê¯Ú,g q/"©ŒðÇy(€Ú7*ˆJ©ŸCiÄaÈxe^C]CX…æG|Gšûý~$ÝðiþN¨ºïè$žWZ7åWN9èÝcCéÎ@4Uè\2>¾)Qw£7» ¥ÈB#{ð2JמŠfE\†RµÞó¬þ¼Ê6à÷û‘T À'üу9…ä Ù à_ËùuQÇeïŒ~ï˜Ë9¥™.Hây¥5Ü5BÁ¿xßãÈ¢|uUæ *ÃwQÉv øÝ ÔmKÂI´bÊ{/@]}ÉÈó‡Q™ç…¡ð~tD0˲·GZb,Á7 ¯ñçW˜¹×AÔ]¸;õÖÀld üÍ‘¸Õv´ú ?MERÀ'üˆo 9Â_ |dÁ¸Âv«fþß)í²,{  &ãç…Oû‰¦™¼ßíñÀáHà¿qß;#ÿßÙÖ´?$7†öfK†ð¯&-çºs¾ÜݪÚÜÞ¨€äwH#m+H6Éy?‰°´‚¨s ²^–'ùú“Š„€Ëú4Þênä§%rzQþGò‚ÑûF|X(dÙ{×"34ÕÚ‹[Š:4~ÌAÍ-±fnÉ3}½Ê¾}IÜZôšAAÇ?£Š¿hùø’´˜ôÛÔ›‘0øÒ}ýQpm‰ DF™ÀŸ Cá×F|X‘¥ª±›Q:ÙÚ°¹ðö¨tßëPõb•.—»×þµûõB_<ãþ-S&N0~ÂîZÈA£¶¯"9V  ntïM5¤Ç¸ïÆ"a Þ'üÝQ©å‘$Ö©FÂaQÛÕ³F¿urgT r­Ïä¯BÂî ]ì¾¾pß— úø [øŒ\~9D4ŠÊi…–Eùø ùߣN¾d‘¯ƒ ¾nCSÖÁ¶C ¹é1Ó{oByõDF×7¯Y8×f…þèð¾¨y粟GsA»üT žø/€ÏÑÂɦ‚žGýìýl÷bÓÆ˜ *õ»;²~¤¾Æï6ø-‹"Ÿ_ 1¤à þüÔ˜ïP†úþ‚‚Û $’²Q°çIÛÓ_¼aÁ¥g̱®<’µ=zÐG’Úþ~-JM•!õ`ð¡ïoò"÷=ÕS ¤~_ïÕiÙæà$ðËe1Ï=§¯9Ô"bòö5Ø ‚» ‘ðC ¤¹£–ã§Ð=O{0N¾ ß^À$ô[L XÎgÎQ¾.œ=|ìAŠ”AÇÀ›'¸ ÞëÈGõ"ÕEÈŠ*F³ô·GùíAHð»$ø|¢Þõ˜ƒö%\Hw=îZ<2(_@1—÷Ph*ÁFÙqˆì€ô%£‚èóûKP}öž$NðVç#ñá‘V^0²'ðWR3ØWŽÈj6ªP{i\P|¢"ÎP}ûî(šŠø íYø{=k©m::i¯wB¶c‹vq*!õܰ(ðÚ`®û}Z’€1AðùýÉ}6‰{Ð+€‡–s篦œ°œÑÈ´dòš› Æ"4sî5`ªû»v¨p¥0 ìŸìn*ÝkzÏ}­ʦÌZîœ{[Qe]îÎA¤Ü†¥ g¹˜Ž;W ÝHÀ4ä ¦ŒI\šgðpÐrî¶äûîa;p5ÎÚ‡äX6ð&z® À¼%àþl¤d—NKI .‚â3ýw^Á¼ùí ßú‚ùUE^ñíðíP…ß¡$§¼w%*Ñý#ªŠk‡´Ô¨Þ=UjZ+*¹?µ>œ=ûóIW—ï{òÇ÷BeЉFY›w¢¢-#–@ŒÖï… Á~…6.YA I .ÂrÝÁmA‘Ú›Q.×´®®]V“ÿÖ¥ßìÝÍÒC8*ÇE•‹>†º ¡¢–3Ñ9íIFøãlt/G棕}¿¶ºÎNÛ½íêÉ6VGÔÑ&Áç䥹ëPCVíu·åO﬋ˇ:a,ýõ¾-B»O]ƒZ—wE}"3ÈüÙ_4û8-^œ®ö¢é>/b¾Ñ§x¤:¼åäÏÊ àŒC[G'ºœt*y½™ƒÅHÝHë¬Úk-pÐv`-­ÉÿfökÔUÜSrªØLâ•@š"õ/â4TÔ§õhzñQ¨{Õ+ÿv²9Í1°›kÄòߌù4MðºwŸÿÕ~Vçä‡%ZøWÿF“ŒßD¾þXd¦f„ß,,da=×=·êØQ'ŽíR<îõ§Ð:x—-Ï@0¶¨§aÀòÛŒQ'Œõ 4¬æn4µÈßûa¡`ò¥´°F¢Eléëë>Ã}™D·6ûßKú-ûb]ÇýPn½‰5³ç»Ç½ùþ»¡qbã©–AbÐ8Ô‚¬~ƒ§/^Éž9¼Ýª)Vgä+'*.`!!ÜxXÛWÀ'ø9ÀNHžîEæ~CëÛÛKa:ðs¿ÁCæ¸Í_Åßvh®ºÉÀŸƒÒkc¿-o÷öÍsv w#±QàYh²ìÓh회vMà9dðK¼üßúpögÓF¼–_ ^…6rIdÊ5‚F«]*?Œ òõAµ×¢€úÖE[ ¬œ2qB“g;¶TxòÒ ³Z¸ ø{u48ù¦9{¡6Í]âpþE1í•Hø» ¹ò##ü©€C€§ÛdÕ>âÃÃí¢v+oEÚsvÏ!œæ¾¶ê ǘûmPðüv”FnŒðƒ¬€½p‡ÙÆÖ 4Í _Úo°{Á¦Mÿw¸çÜ/÷·,œcÑB‰š"A¾å™HÓôFDp/™¢žTÂà™¼`ô´ƒÞ<­Cñ݃žÓ· <‡, Ûl.äÛÅ’^N§é`YÀù(×d%Ü š‡òÜ&+²äsßúê²>¢¡=€[I\ÐÏþKPÝÁŽ(ÿzÉoPÉà—(î XÎŞأ(oÃ[(6“Hhƒwýˆ Æhý®¨íû~÷ï›;Í)€b'Ò + É£ýÁ¬0V,«Éÿæï tA)6ÓXáßEcÏoÎ}K1T¢Æ¤†^5-øÜT@p“¿õþÑ=Šò+ßF$ðM‚Ž@kå2|J"&È·‡{NÏ¡mËZŠl4Z½Ém÷M$— ÐÓ1GQ”òûíiŸ¬v;7<žؼúì‚XzLsîYàMí-sß«‘p‡Ñ}Mýþ~XH€JÐBÎó½òQ…c²jð›Š(*!þË”ƒþ³¤bCñÁ¨1«±þuKQü5ÅÙÇÎ@÷õdAîçãÕ¡õm@]cëšäGÇhÿ£1'Œ*¯¼ý•e}Ê«£ÁaÀï ÏšÔsõÂ?Mê ¥$× aœ €¥|c;ÖÒ°Xúñ’¾?]³ÓôHN0JA0R¿€c1uMWêìšvÈâêê¾ï‚òÑP®=ªKOÅû.·Àõîq÷N=ñ튊¶"òD@¤°f¡Që9¨’ïmd Ü;åà/©Xß~ õ–€égAƒh¯>nÆ(¬'q}uCÇ«Cié?ÒH+ ©DõÏobn\“ ̰pŽ<ë‹åëÂÙ— !4½°4íå·Hø=³?•„¿ ý"àÞ.«Ëù蔞?•å£\2è‹Âšš‚Ža;Ði Î(Õ g1*-õ«ûJ`dñ¸ÒÊÆœÀfˆ¡röCãÉûºÇK•ûf8pï”CŸ_\QÖi R*Û%àØeÀ‰Ï-Þnê‹KúîLD®€‰{c£‘s£e© hôIøÚ}D-L äzà·Ÿ¯ëøÚßï:µƒšÞ:à;”6š‚RJ:¿iù¯x«¬.çíS{ý´vÜÌÔžøH—Z;Ø ™åC‘ÕÒ-îÆLDªz+-oÄßþ B>нHî$IàÑ]>YÖ-·ê·¨Ëtçj˜fÁ¯/üzŸ5Ëjò¯BÁlS»!Õ¢Þ„'ðÖ¬€¦@"ªþ¢À$Û±N=fÆè`ÐrAu&Míü;°é…L¨“I~´=š1ð®íX/×½ô³K§BÝ)÷w©±ƒÝ‘p탊Hv¦y‚Vôj.øCY¨RòTDLýH|Ç^,là^Û±îøÇîT´Íª»•µ5|Ü:àÊŸ6ÿõÊïöìj)ç¿;fÖ—¦* ¬Ý4ê\ퟅꓻbFøÔdsdz‹·³ƒ–³'ªp2í÷¯CL^C ô÷¨´2™Â¿U>\gÎ>¡{éõïžòÄ—gõž7¨ü¤‡Ž«±ƒ×O £—£´R*hÙȲ?"ÒwQ“ÔyÈäžI65t~ç,ç´‹¿Þ'´œÇQ*Îô9…€K¶+(ï=  |¹{/* ^ã`TR¼Õº€Æ.r ™˜'bN £ÀKÕÑଗ–– ‚Ó£-€‡Pói¨«,Y{Öâ ~Ô±~{|÷Ò¦Ž|eÉi½~ܹb~ÉYÜãžïeHƒ¤ Ü…–*á^DVÔ‰(ÝõÒ´ÿDsõìæ'(†9bÚÁUŽÜ€÷hdÔ¼™ èÂ? ž™eἊâMÑ–}ìf‘…ÖÈV¢¶*Ì¾Ž¿1Ôç‡ã ¥®~øçÁ¶¥cÀ¬ :HCÝ€ãQ¨­3YUþˆLýNè1ÖEC§fU¬ê¾wEMþAÈœ; IçÕXXÈ:ÜOB±€iˆ´fŸº¯#Pi4ä3Ñ(î ZÎú×–÷þ訮 ¯EË`Ì؀߿¢ïºxæË{Ýï^[Ç !+l{4¡j³dÛXaÎÅlý}˜°´&þGk»tFÚÂdÔßAùÙëPlT@‘ŒvÞJÔFzÃ…}çœÿîÄ'fÞëÇ]*Vu?5†ü~Wû‡ÀûKZsP0ðqTòz P¾rÖB›m$Ú-°P¦âÆ¿-ØgIMþ,Dþ+1k ž_27;h9S1kq[ô·ä4†‚¨þ@Ì€„{vü¼±T`´C#Ï­¹Ç[‹LÔYHý·vÛÐ17‡€ÇÃNଓzüüïC:/)©¸§äd$,"ÔZP„ænnˆ¦Ü,š<äz‹6LY‘àó ZpÉE_í[¼¢6o"æj’Œ²€#£ŽµóèNKl”ijqðu Ç:†­j5FÈ,$$ß àT¼&,«É_YZUØ -ÓÝ…/£HlªíÑÈ{/DÑÑ›Î-ùþÊy~ÙÉ=Þß[Ð"‘Às‰‚È×ß-»m¹h¤g‘º#Š\Šº+¿Lðy‡€s–süõ³÷°–s'ån2>Q\xq¿9Y†­/n7mÎ hÌ¢ a¹1ÖÛ@)Jm´*lyî®y;;ˆ±LkÿÙ¨ÀÇFs /%±~9ð¢íXœÑëÇÑea¯ŠõÎ@ƒEÏÂüLŸÂgþOãc6ÿCÏá8÷ÿîFkl æÌâ†P\½¶.g—OÖt©tÏk1æ\VÀ˜Î ±Na 5[4ßT“‘†:…zM5íkß\D€‰+kóV-¨*ìŒRF&û Ö s>Ê¡ß@bÛz ;±ï^ôÄÜc»—îî`]‹Ak2÷ýðö<†¦?»Ã«sª1y­0' _àÖ;çíÜyQuÁL³0ÙY\tQß9YAËùMõ‰8Neû³Yo”¦-Wê'‚µh*Îh.ûxT<‹¦Y^Þÿù§p_hRûÛÀ5“òýƒ /ó€»þ6ôë?óBMÅ%¿BfïE˜« 3 ŸöCóg3vBÚÿf4ó~!ªx5„%Aà N¿A®ÀÃhâ³)W u¬]í²ØF$XmèXÜ>š†Ü€&-~|dð%"€SQ?òÈRXÒˆ‹oWECs>YÛ¹³Y¯Fz¼ûýáÈM”éÿð‡É/Nx¤cvMqEE»SPñQJG÷‰<ôìZj¹êŽB¸A–&è:BÀ呬?-ë´Á=—å˜s ±§ôüÉr”‹ÂÉB÷´Á6îfk¿"X‡jö/A&Ü(Ðö%Js5„ àùÿ,-q,Í5ÛsyÿY_€ú ®#q)¿ÀÕgõž÷ô†?÷éu¬sQ‡XI‚Žod^Æk3˜€¿' ¼Yóp-ž+sÝísw+¨ˆdýYŒ¦â!`LA0R2¸h]%ÚÐ6là8` Òúe‹Ð€Uð2iNB‘Þ‡Q½?°b3ƒ–óÑ KúYÀ9˜ÕþŸ¡ê4ÐÞ…&] ?¦×L~qÂûÇt[Pb;Öe¨Þ [YÅ>óÿâ{/KPÃÙ¹¨Hæ1G!à˜€åŒynñv :Œù˜ÑÌáœzY¿Y“0| "+à5q‚"£àƵHènGyàÏ‘Ÿ÷Ü7ëÛ;!ËPCÍ=ÈÙY(‰0ý¿®›üâ„*ï)éëè>ÜH¿ 9žøn;¢ÙcQ9ø_ üŒÙB°\ù択K« g£€ I+ਮ¹ÕíÚeÕ®F$` ¡€ë/2LF´`VÁbt#‹¬‚»ÿÞÿó ÅLÕüGQ^÷T•6ŽÄL°ù ¸}ç6k§VÞSÒÏ1; &Y¡{cf ”ܹh}<Žâ&e†¯+ µà”ûÚ žG­â&¬Ï<ßÿ´^?9(@m¢I(€J·#æY7ƒ ƒiÈEX¹¢&ÏFúÌT‚­G)6õÉŠyí¿ÿùºŽ/ݾÃÌ¶Ž² ç‘sâ׌ ¢r^“÷³¥œ½â°¿£‘W^Ò„€óK« û}ZÖi%² « 딃;/±¢Žõ²MXAüÞÄZKhË«Ÿ ŠÇ•°œ:ts/E»éNro@<Ê1=íÿ Џ^ŽùÀ_ð×.9Õ–ú|[۱Ƣ➴~Þ¦•{`žP PŠð”f¾ MÕ1Y²k¡Á&çÝ÷ÓËÂyu4šÌ0"êXý­É€‰š€R€›´'uà…oXÁ×ȼ;¥Az‹Zðñžöw€‘˜Ÿ)X L´àÏì<-u¬SÑÂM«ýÜÅ“…\·DeR пƒÑ柷!r7'WECýf®ë¸Y­&Ó»¾Îï;TS³ÖÀµ‘ÜŸBJöĦLœà'‚õÈ_¿Õè߆ªÂ>¥i¾Qøiÿl÷³L·Osàö‹úÍÙ°Ç "K—€_,ŠH̰Š5Ü‚f5.D©æy˜#¯–þ¼?ÿ¸“e)Íý3fb!àÐ~ù9!Ë^ˆjiLXT´•åÿAJÀ#üˆÒ0§£q]÷ÿE¹ü­=ôj”JrÐäÚa˜#o–Á]»·]½pt§%ƒÑBíCë0ý›z_B¨}{»~,4Zì&Ôrü1fëè½k=©*êõã†â ¨ÐÍTÉî® Ü»ýJ/h"$fRPÊ€1DFÛQ0èV šŽjûcEHÓТ¹³¦j ððvå“oôe{”îJj ݳ٨t{Mˆpûrÿ¦ƒ›ƒWÖzÒbÿÄÝxÃÐñ,”’<éîy;ü›Æ) æ^Ûa¿éõ“…Ö®)7`ʈÉ›~Ó(øº‹o)þgPëéÀpTåÔ=°(ª#wÜŸbð:£À;¶c=~ó /Û±N!ùóý¨Dõô ЖÕKQ¶å{êMÌo| P¿…u²Æ¦‘E8 ñfüû~½¢6ï¯"¡Õ¡È$”ÊŽ÷ ÆpX—œêûC–½&â¾D–N<×nU– `»Òà‡G.„‘0åˆA=ôƒQ±ÃdDgÒôÝV  Ôé²xCq(¼Z˜É µHà¿s_³°”Òr¿2ˆîs¢ögÜ PQÕwˆÌþ‚*NMÄ\À@ üÇ¢¯]ÔwÎD”Ùéçãè;¸hÝÜoÊÛ¿ƒÛËoà8û"+°®Õ€‡¬‚õÈ4û70 /Y‰ ~ŽÅìÞ…OuË­úúâ¾s:¸Ã%›¼9cQƒæ¿}‚‚¦o»÷føê1šozÓs(¢œÈnJ?îñÏEâ—Qs×!†Î' 8ÿ­=ß>·äû¯³,û3L‹·ü€‘göž7÷Êï†ÿ¹»ñžáÅþ©é§6 15Î!4‘öo˜°a£y{`§ékzçWž‹š’A¨QÔpô!„þ_¶Pà7ïg¡”ÜQh1Å{£ËÆÀk'?‘Þ^hÒÐ&i®8¢ 8ôªß~½oû¿F•‰ñ^[à­€åœxÔôƒ- ç=TkORsPöd(°!U|Õ#&pÂl{q5pß®mÖ®í_Ùõô'ã^ÎE™’+Qðq4X}Äfß@\סTmKÃ4^šî÷(È;5|™ÊÕç‡ýcá _ƒ™ Ý.¶cuè˜]m£éHñÎX(а҆|ð¶aÞ3>³kÒ5¾™¡IüLÁÉ(3rZüF„>1D[‡ˆàTÉùiïH`£~ ·¿£¸Œ‰(}8|emnNÄ ¬A…Hñ&¯Áj÷ƒ;/Y6¦êöBéH!Tól*øW <²o‡UyÁÈ@4È"‘¦ÊG_‰4^Ä×Ôo bˆ`Þq-ê¡7¥…BÍ¡(B­è ?ìdÁ ×tñ¦K™8NØgT§eª­ÄŒ¥±éF¾&•Ã0§ý¿¶ëKÕÃ}>bìDÅR~DÅ/W 4NB´þ–C‹§0ßµç!쉬°ƒ¹úçogQ_‡ïã„€}:f×X–W³ï:oK ­€úæC1“Ž‹O k·ª:/Ýž-ÏÁ7¾G›„ü w~\2?>X \*1×4û›†²ŠIþ3Ú9 ¹¹a;°‘@¼Mô °ƒ;eW;h~†‰clGÆB¨ñÇD>ØJøïjÚ8Äh ÿhlT-¤–ð{ð‘@Š ‡R ñÖÎ`G ú}]ÞÞF;&Jvó€÷n¿TÕoð=Ò†|eª£0§ý_îœS³¦cvMGàH£ýW£Qæÿ„ä›ü[ƒªQ•ÞC$fë¯ -—ƒFÀÍÀ܆#Ÿ]ÔßB©WãÃÀƒ:-u07؆¦ ¸ÈÅLºƒ4ÊKçôùÁB'›µG”£ ·g 5µ~Cˆ!ñ$n î}мGÇ=¦©¾úQ?o( XŠ5˜˜†ô+¨°PVÃDf#ôJ'ð¶`îNüÍrø2êXßíÓ~……ÌÓÚßFZ<`·á÷à#*Ôù>æçùå£êO µƒ¯6pÌ °SÀrŠÊ#YêJŒ7Ñ¡¶c¿‰)A`@:@å6M\SxeÇâ2'êX}Qªéù3ÐìDSF‡KÞöow£é!G¢`àZD&rõí!3ÖvvPŸ… áì· …½ûg"P’Ndë›S6žùÿö%s-ÌÖxÇ["þ‹§µi?\°‘0>†Ù-·¨(kº/a†p²á“Wu·Pÿ…‰@pè3¸¨ÌA¨Æû!``«kjn0€†Ä›l`¶íXKK *,Û±LÕxˆ¢ÒÚ·i…¦C˜2q£Në¹4{ }MeO¨ÀAiº©@»8#„fW´µ‘Uó!² â‰Z ÍNmÊìée¿DU–ñî=¤P_Þø»དྷü Çv¬^˜5ÿm4Rü1ÒDø}pO~+JA™Ür"‚¨m¸øŽ”-Çs/n2p XØ>«6ŠHæ& ¬»t!€ aB«D)'ô(ÅLnäA›`üˆÙ=ê× ðj¾Oà¡g´ü#’w [Kz ª²Œ;Ò…6–6ÆùsÔåöýžíVY¨õÕÔ=sÐäçhši`ÓYé‚≦PŠIK— ` §ˆ·‰¾³±ªsQ¯ƒÊ”ùF3áË0Ÿ.Ë  }À›Uïë‰_tË©²ÅJ ÃC5*ø §£öÏ 5‘.`afN]ønŸ+@SoLE®£(4‡4óý3Hm´zð©êkàzl`ö°¶«¡ãoÂNºEþ3Hq´z ~çS~s-Pºcq™…ŽšLÿ½GbidAÚ@ f €D+€¹m°½ãÌ%cþg`¤8HsÆÛpÐxq‡úA#¦ê >§•—üfÐ:‘à¹&ü¯ ó2LY?‘Ñþ$éB%®ÅJ£Že»_›P¯ž>ƒ 6æ|Op–cTCwÏ«²0c¢o´b,ûbÌí9ßhKÆ È ÁļÇÐuAUa3[([@Çþåf‚tÞ^m׆sD¦vim‹v³1¹ÝXüÜ®7Ìhèž ª AÚÄÛeÕYÈ0Ìì5»¼­|‡™í¦A‚ʸ$˜Ø|ÐЖ׿9˜sz8õµ ¦Ž±ãäU=¼M M¥³€ƒÐV7 ƒ„Á3ýM˜è Ï‚ª" 3Û4oôÑs=õ!`ŸÙm(¸ÎÀu€„¾0ŠL6 ƒ¤ `=æUƒa½}V­©-”ƒÀîuv °œà[̹kÉX$žù¼3º×²š|ÐöZ¦|ôCÛ®q0#œ^‘N·U…6ð‰c|6½ ø/™ÉÀ$!Ìåéƒ@ÿ€åx‘í…h åxf‚À;.ûdʪî¦|ô\`7WôZ|^ÉÜOâtŒ(Úðí=Ô1ù9ziD81:Ù—ÜbÔi)—ö›mìó=0‘ã¶Ð°N–µë[´»N< X¸Þr¯¡ È'¾ft8è£5]&W2÷;4ç o3Q‰„þ}D_ -ÁÒºØþp,Ðs®Ž ÌÉ F?r¿?›øw[ÚÀÏÀ»î÷'Åq>F˜q³€®ÀáÄ?>dy`"‚îUv[XU¸¸gÞ†eކdlË æ¡ÖæxæÓCÀÈòHV^\Ÿˆ¾CÓÕBä>¼ |Ìöß‹t|ØDøwnº`ŽjÛ?ZÓeª°¼(ˆó1ꀧ€©¨ÕýdÝÆóšªË»çV}JÅo þ-ûëý€ €þß–·[Ü3oÃ÷˜IÓõÚwÈ®]³¶.g&ñßÁ×zX°ÛëË{tB÷ù/g4ò¿¦ áÿe*6"Ý?ùÀõH(Mv>®>þ÷â~pšC‘çcÔ¢QîÛ£ N<…Dów,.mKוøZ2°Ò{µÈ¤1" ¯íb*n!Ókç‘—9HÐLÑ‚Àa¯-ïðr6ç2E€ÉÀÿWw/ã ñ¸Ò¯m>í!ð+Ì ø8h9?Í­l*®2q¼šx°;ñ·f¼Àü¢ë-`gLj³S&N×o"þî °Ç•m,”¦+#þ±† °Ï!ƒH¦ºe× BÀáåáìkërÖ“øeíDð/$ô×w!°M ½Wø-à8à2P5‰ðü‡kº:AË‚ÆÝ™Ø2n>°Ô½¶}ˆ¿on?8P“°¯ciÈ÷ÍÌçаXPãÀ˜ñÑ÷éœSp`µ¥=ööŠó1@?`äS L¼¢ÿw/ç “¬xõ¦cIÅCèý“›]ÂNyø"þ{!ÿ5Þ#ábá ¡|ÿ ûOüÍrÉLAʲYñÎ(0+/uëÔ‹øß;˜í™GQàÌäÑ{í×Ôå8À,CÇì`µï˜]k£‡cÊ 8ezYg+h9³€ÇKÑPÕgðU#ÆKãû„??æû”EŒðßìŠù§0iu]îêµy€c0SUEYʦ³/ãyŒ™{µ_é í_là6ðŸæ`&ž œ¹®£ˆ7xq€áÇu/uÐÃ1Q­†‡íÀ°›ç ­îîAÚ%Äß¿÷ û©ÀŸPQRJ“@ŒðßÊ›MÃó™ÿ>¡t mÁH”ª·ÿï ŠÍ™h݈™NøbLç%»¸«¼Àû`¯™Æ„ÿöx}yoPàÄÉ„€Ãè°<€Rl[ Ò5Þ˜³S?_×1P -ín4áßû„ü àfà|DÃc~Ÿ(_âþÃÝs=(A‡«£Áï§­í’Ü3SÚ:ªlµ0Ó»á¹2‹”[˜q1¼òÿr?T`¦' ìSZU°$˜&v # CáüÜ`t=ðfœBÀ¡!ËpÚçLö|Â}&òŸ·G–ÔÉÀ(½Å¨Ʀø?ÕíH;&j6ûëƒ?!`9c07[Á3ÿA5 ƒ '|n;–Ü—ø“Lµ·ÛØX²QIj¼ýç °[Àrò«£AS©: ‡Ýu¼‚7ÀB1³1´­ZŒð_ ôý:„ëvà Ô<”4ˆÑúý? šÀÓˆ«£¡>^Ó%¸3Á?h&£up8f²Qà“]Ú¬uÐ=튙Ãl ˆùá—˜ñÑ;;L+ëì ÚwS>úñGv]d9ÊÕ›Úr;œ ξ™OÏâ—ÂïÇnÀ5(q¸÷¿‰"‚ÁÏAnÊýHøz%ä$oVã_?È XÎÁ¨jΔöŸŽ¬dPA¼ Œ@kö“c»—‚RŒ¦®ås â_À÷‡¦_Žž¸¤/(PeàYÀ!…¡p‡Î95Àk˜ÉxVÀUÄÑ h‚ð{èŒïÈ-Ø0E1‚Ÿ\‘Pyïá˜Ñ¼[BxbQuÁS×tí€9íïë%D:;#·ÌDq 0§â²ææCTã–£û/À+pXKüh!àÀÅÕ€å,wnÂÒè tVïyž°!þ÷nãõ¢ÍÁ– ›ïÿÏFÂ? ‘ÿv.GÖÀ(úò>7ÎDàYsG¢@ßXT#‘hØÀ7<ûÐσ±à$ÌùþÞLÈ©î=0UcÞÊ F«C–HÝDÁÜIÚ±ìR |üéx2OØ!`9=ço(ZÔ'¿r °§‹ § k·êEÛ±¾XÎL\!óq@©Ç«Y¸¾¹âÐߢ ÂÆ ¿¹È\ €ê->AGß5DÍ,( G¡ª¾IînF€?|°¦ëÒ*Ûl‚¦´•s¯Ax æ² oÑe(úo¢Æ ‚Ö†]<®´þº "Š™¡P {qi_xÓÀ1@eX–eïrH—ŵÀ_1“ ·iÀf¹1ÂÍ~?r½Qìà!àïÀÕHkï”xÇn†»àåÛM7ôl Qà‰:;ðö}?î˜ï^ß@Ìmþí^ÿ(4ŠÎ„ù¿øò0ÀŽõ2¡xì`J85cm§€¥’`¥Ç \ýégõžg9JÙÌÃÜ0Ï<äwîšB¾¿= ÿ8ž—7cpŠ)\ƒÜƒ‡)> ü‘Ã(×x‹ÇTÑXc`ŸXðЭs‡Ö92ÇÆÜHõ0jãþÝ×S + L:ÖêŽÙ5^‰ cšC…_€×dbDXØ?â:®®Ë©Ål®þÈü`¤C·Üªr´ÈMYÒ¨7" §Qp…ßBÖõÄWøB{¤µNGij€‹i<xõ"¦¬9þBà¯.ï½pvEÛíßÿa~DQï-Н ÃŒù^Õi©ã`튙 £—þÛç‹=€ƒ¦Ö|7 °÷s‹·³ç Ã;NWà×× ø¹]„¹ÝŠH¨~O#‚>á?‡ÄÿÖÐèanÑSx­“Dcðç°xÿï ·ân«fèx^êï3ôœÇ" 3ÞðÌÿ©§÷úÑB)ÆÆ’rS°‰ÿ1àÆ¼~v Ÿ?uu·€¥J¤¯0CYÀy}ò+;uΩ^üsVèa‡LQks$ЀæßÎà95M%FoÖTkØÀcAËyþ¢¯÷ ;X¿GiHÓóDVÏ@d–›2ÿ§Ekm™ÿ¦j ¢¨Yn£,4tó"Èw6U°_ı:¬¨Í­þ‡™Eú9pÒe¨øfÜ^%âMÈÌþE< Fø¯!5„¿Ép5GYp‰"€(ð¤žýÅe«ërOAQZÒÌiH,¨5• ä?Oè^ê8Xû ²1düŸl7t¯Qà+ÌUußO;:ÀDÌ™’!àÜ‹Ë:öÊÛð# €™´,”“¿à6ú|Â.©£ù[‚ð!f]+?>²`üžµhM]Îþ(hj¢DÖ(*rªDþøq˜ñý`‘ÓNè1ßBV¤‰ã"´J|Ïls,S|€†'|WÞÎ ZÎϘé?ð®­Ÿ'_=à E¾çb6zm¡Èû]hð¥'üÙHc]CrŠfâ O›¼„YR¹׿¹¢çÝr«£ãÁ˜þð*õ?ç`ŽpÂÀºäTW#1gþGPú=âo`ûÄÄLÕìï´œ¡SVuø'æLÉpn¯¼ {æmX¨¦Á”ùÿ>›)½ßÚÅ•Ÿ‡ë¡¥.A%¨¯‡,;âžäÓ˜5͋놵]ÝyhÛ5ߣhr"\ïØ'!â†ÈïN”2lµq_eàÓ´ÌRœ \œŒ<2½¬ó‚ÑS<¸50%BødŽG{WìZ³Mícà•Ü¿~q¿9 3¦öK £ý*" ¯Û,ĸ-Õ”e(°r6 ªL@šÐ3m´LnmCøÝåÛ}ˆ:Ö›À ˜z¡}?p"ªiŸ€zù[³Kà-ææWkPSÒ…E]NöþÑ‘;Øù"í7Ñ.¾9D9†‚¶·`.Ê'ûäW®ì–[Õ ™ÿ¦j V³™g³µƒz‘ÞЄצ>ïQ:ñu4 t“ {Ò‡—€J S¨›{þ¿)…gÞ1xæÄ›ç ý êkߓĴ·Q»n;TÃ~7"ØŸQ”öb cÎ#Þ¨u¯ã¿Ff÷Y0yÒ²Þó'LÓ§}víh÷àDŽ‹"³ÿi¤|®D?SëÁ#Ì—¯Q}ÊiÈ54q¼0r­7;ä§1­FeŸMq>D£¬Ç"6ÿ¾¶Ü)'l2Â5Ml”z|½‰Çj ,T sýÎÅkûÕuá<”ªK”+àaÚPäITÔò-² ÎG–R«Ï ø/ äÖ Òì Šò+Ÿ~sEÏù_8ðð å<„,ÄD ¿†§ÜF}@=&KŒÃÀ„’üÊUÝr«:£L‘)7'Š›¥ÙÜôê-ZS&N`Ô c½¢ UhcŠÍ™Eµh¿ôIȼ/ÅÝÏû¬F ÇŽ@-¬&`ƒ¸æ¬>?\ùöÊÔØÁ»€Û¶v?âŒöÈè…ˆày”*üÝï±Èm ðfÙ½ÌÙÍ-èÿO[0ã‰Û—þgiIÇœ@ô¤õMì·5T ‚­Y(Sdz 3ÏÕ}åêzíßÇÐñ¼rí-ñcx’WhX3¯F…/W¢þû¯p…?VÛo X&Ótà8 ÆÞ·óôˆ#ü‰‹x¢¹ï·¸Ç? Kù4r32S¾ÊÀÑðsû8ß‚qÅãæ¿ðæŠž¥o,ïubN :„w$ñÂAØÉh=Ü„Ü3“® «ý+¡ý½çQ³¥?jóŒ:al)ü5ó€æ$÷Î!f7žæÌœóm%=i “lì +å¼ùUEï]ñíðí‘DrÆ]ÙÈxxÝÓ6ÈEN}QJ¼Qôš2qBy‹>DÏ®=Zû!‹p R3Šº.šóÂ×û„/Ýþ@KšojŠJƽö†|\üã‹ ›Š=«Ü8âÑ]>YÝ-·êJä~f8–7Ät`Iñ¸Òͺe5ymÐûmRù:”ðr 6¢%»×+¥||‰Ì–¿"V6qƒ ~šÏŸúåWœzrÏŸü×â~@{ÊõÃ|Éi,¼ ¿} ½ <:¸¾BîÕ04|d4æ\¤– Â=çéÀt fîXúã#oŽŽ¼8}Ìà åœfɽÛsÏuk°QñÒ]Èz=ü´5|Ü0pÇ~V¬ê–[Õ³Ú?Œ6«ÝjL¦±hrÿ§ßÈíxoY]>¾ÄB¾ñ+H(Lj xÃß=}LUÐrEäcºÝtkXE}<àedA” ñTcÜûÓÍ 6>·6Y–÷«W>9ê%žYÔDÀrAÓ™a^ж¹˜ç ¹xýQêî˜ï.|ÍÁ:ûù=Þ­É Fï.À\Ìi ^òmñ¸Ò-¦ð}Ñq-½U¸æduÐ=ymQ<´œkOþldmU4t&* -2|ÜÆ`5JÎ@Dðbø"4¶3ò¡÷A•kƒšøù-&om„,›~¼°¸_ö®m×l)¸7Ì=O£´šŠŸ¹ÿr?î#1 FeÀ¯~ÓëÇOï^:M6Ù^ü,Ê2UnmïÊdj¸-ÂÕ&P íQ˜­î[ξýô™# ‚–ó$JÕIÌGéÔP„w¦û»\4‘¨£ûÚi·nh¡weóš·èÚTh@!ä"«d?TàY*É2õýp‰^Ž2y(ízjÎ/ Ü—Œþßs{¼—k)öp8æÖó÷ó?Ç7ûosHdÚ«©ðêÌÇ#-b2 ÒPçµÉª[ÿÞˆWïùÁQÏ-ÇA¾b*@> ŽFÑê3Q‘Ö'È*ø ¨ã°-øYìþo>ªèskÝ×±™9‡í‘Õ±/úîîçwØÚç% Pû…ÿNd˜~¹qÞ6hfØÝ½Ètwá4Üm¿³suÊZ°ÑÈCwo!1Úd1poQvÍý#§þª0h9§¡crôtsá¥hW Wa.ê3øY KbþÞB÷36¦ÒØ¡; ËbŠ=ôEÂÞ•Ô J‚“W ‚ ù<•Ä<Ï2àœ1—¼vQß9ýQÅᘓ»4ºìmhým©lø/êiä«íyW 'pEE]®õÞ¯ß7rê‘Ïú,T#¯²±‹ûýÞÈj*w_ë‘U°»DÜŸÏgÓâ‹úEé¥aC¨.~;äJtAB^ì¾Ú“˜F–à'”j›„R¿N&1Ï1—N]ƒÜšwH'€$ƒ"¸w’8ͳøKQVÝ}#?<¢ h9'£á]“}O2Ø"æ!;¹<7¡â£î :~pöðv«^¿nà×Ý  †¹tv™ý§ë+ü³Â_‡ÊÖgÑ í­Œ|¸ÀË$¶…·8Ôv¬ÇnÜþ«CßýèðÒ:;ðÒ4_%ûþlƒx Qùª€;¸eŒ™µØŽõòã»~äX0õi˜”/oZ×ã4SûC+ÉÄÂÍ X¨îýŸÀn$–Ì¢(øTa(òHŸ·NÚ0°pý.¨Úì Zé}mE¨@½ÿFEOÞ¿ãq÷eL ¼iÂ<ºëÇ˺æT‡ÊŒc+.ãZ´Þžd3?ƒV»P}$0 miZ‘)¬Þ XÎož³û¼¯Ö·ïmɽUÌe|„6w™ŽŠž†@åȉ®Ñ°ÑBg]=à›Ù{·_9)¤í1ß]øÚsbÕ–úý·†VK°‘²Q¤÷~’Ójº5^ _ÑBá''k®ªM¿ `’Elå¨E÷Ñâqó_l;úº¬ÝÛ®î¦ø\ŒnÇ—¨cÚ@¥M> 5Õä%é¼Ö¿·ë¥ {·6/½Å~L§£(ßÊãló›@WTÿ}ÉkT±Ñ¦ï,çÁ³fðå’š‚Âü`dš|6™Râ­á+4\v 2³Ã¨Ýøw¨á)™÷¯¸Âv¬§ÿ»Ï[Õa;p5ª ID7d tþ8hH€MH ;rN$ñ~¡Õ¨íùì€ý·û²ü¿+zvYN_Ô²yê²Ë _"ÁiüJÔAw ²¢ú’Ü#ànþ"€eH« yÎA…<½HÁ ÿ]<ôÊðÉ«Ñóð4"uïٙȂŒ›éï!mÀþ,àZTR \ˆÌË>h‘]Cê±Z ,î)ʪ{aôG‡{ۯ墤öˆ F ­ÃZK…áWˆ„?@B¿Ê½Ö(*“=‘ò¨Š³ Ù'ƒõÀ<üòðÉ«­Ä ¿ƒÜ¢“жåq5ý=¤ø„ÿ:”oïŒÌÊOQ§ØghÓ%ˆ RªÑn?3ŠÇ•F73r»-êkïŠ6²ØÃ}OBø ª>G¹êu¨h§ }"±ƒÜkõ,T{ ¢ºÜÿýgÏ)eË9 "ë8jP€ø?@ á‡4 Ÿð_JC;û~E·ÐvfíQyî$nHÄÖE….GÐÀ8§ÈÀB'Þ«5C BÃKºï1ƒ•ÈoÿÒ}ÿåêË}¯j÷o» QX#ÝWDd©:WÑÛSïZÛ±&½{à¤šŠšüsPoÇv <0ðp;îÜ‹ 4€­¿?¢ ÁW‘Àf öJ{PƒfÖ=ÀV˜~3³ù¡~ËAVC$lmܯîõvs_±×V að¾_|ÐeÔï#Péž{ T´üîHè÷@AÍ6HèSao…-ÁAîâEa;ðÙ{¿q´®ÎE„š(D²ºͤŒ»ßïG²³ákò„¿ÓVþe>ñ C“Z'ñ>xsý‡‹›ÓÕµRAнÞ\ßuf»¯ØëvöŽÆ|AB»Ïܮȇ„2.ƒÀ"ÏIÒ}m*¢(Hù§ÙµŸ?5tjQın@å½[[Wñ„HèLda~h¥Ð á÷°Šº™ª; VÒ#IN†ÀÛÇíB &ž{+ÄÐXìŒÈ£ù¿íÜ÷È‚ðF†çQoy´6xæöáÈÂgw¿­£ŸCIlw©ƒŠÆ~ƒ*!æ…Z!ø„ÿ$8Meèu¨—úeíƒ÷rØ€Ú´âq¥‰œlÀ¨Æá¨j²¡Æš´F‚îï½÷lÒ£“t-R"/OóâªÊòvCY‰ÃH|£‘ô{ÅýÚ˜ßïG«zˆq~«…üÿ«PîAÔc~ f·nò#Š"çߑرf~XH»ï‚\„m6Ê]u¬/ÞñZuEy»c5¸=‰·½bŸ7H ðCjTÇ5 >á¿‘¦™ý !i¾§Ð†åhÐèÙ$nß0šcP•€cm N‚®7UAƒ4Ïì”Sóñ÷~+«¢6÷à/(”há£Yá—M”ðC+±|ÂpÊ!·A”2ûªF»õò_Žv×½sUirEþE‚|½ 6Ö\¼;yä+++« ‡DœÀ-¨6!ˆ¤xþ„‚Á ~h€o[°x ¿=P•à«Àž(ü$rÞÁŒvŒÿEAŸÌ¾æAÁß“¢Žõò䑯¬®¨*<ÖQëñ‘$Gø£(Ø7wçd(‚”&€¿‡"4hâŸèT£^ÎEq‚µÍÿèQ‡ºà2Úß,Ô^|pmܪÏ_ßû킊ªÂ?÷!Ë/±Íò¿¥§§û6‡”u~Óm¡Tíu*f¹èžDh/BÃG[ZºêuJfðo[@xxÈSœT[Q]0Êv¬ÿCJÉ*L²Ñ³?ø–$ ?¤(ø„ÿf´ñC"{Â;"ó”xõ\ƒš[®@Á¢æZO^𯺙ÿŸÁ–EþÛ€™“ï°ªúß½;TTü 85%Ëòõ„,*ôIäÎV "U]€$h瑜9(@x ðʬ@A»c15ÇwPsÌ dÌÿxÃA ç§ÚŽ5eòˆWË*^(9>âÞ@žÞ$Wø¿%Fø“½RÒ@,>=T‡ä,uD-¸Ï¢úì?£9¡áé(ض‘çA¹Þµd‚ñ‚71ç^àE`ñä#Ÿ®­\Õ}HEMþ•h‹®®$WÙù…ÿ+RDø!u ‚v€¹åGMï°º%d%hˆç>¨vàYDP?¡íg ͳµ•µd‚ñ‚gM=‡žÁ“¯™°¾úƒ>+Vu¿ õÑw#y£Ã=¤¬ðCê¨@æM¸ a2Y¼5¿Ü‚Ü€GPÙæLÉ}ÕqMÃü›M&ø×x‚ÿ,ÊÚü¼_‡ë®ðu›Š÷K.Cd<€Ôh9¶oðMŠ ?¤(+õÕˆ£]Pw&¹$@1‰ýQɰg¡LB“o¾Aµäg × õDFÃ2Á¿æÁF““žE÷±´gÞ†²§†½¬¨Í=6â.B룩ۊ¢TòåHø£ZÂ)Þ äðDQù è!§Êl¿uõ.Šì¿Št[d±Œ,˜^(ˆ¸°¼%›9Æ£NF¡ýöRm$—‡ʼ¼dÁêŠÖU<°ëÇÁŠºœc AÄœ* ÍËô\‹ªSRø!Å ~A;¢î­cI­QR5hhÆ,d¼Bý¬Ž¨Â0ù«)ãÿ§0Øè~NA|3ƒ–³þ­1/VÖT´m¶Ç wk0©%øPoéC šÂ©uã„ψ¢`Ê•È<–äîãG®ûê€Hê"´p_A©©eˆl›½û6iûÏ‘öœ ,) …×½|à¤È†Ê6}*Ö·ÿÞ Iµõ[&P?žyÊ ¾‡”·<ø, ¥uÎ@¹dGyBi°Åh3‡g¨þ9 #É€çEQ+ö$$ôóB–]>ªÓÒêq;|iUÖä w”n=Õì’>~쵬BU¤¯¡F¯”xÆ[C«!>"hƒöH¿ÍLÅkqÐÐu(ô Zäeîï’Zšðê:¢È¯ŸŒÌüŸ–S¾]AEõ£{N¶7Tõ¶ëT¹×ÅTò“v£¶ Ï2½ UŠn€Ö!üšB³UøH ù×÷’ZÁÁ†P‡ú½ËÑ¢ ÃÔ FBŽap|ïQ4=x†ûú X‘eÙú–W?¸÷[Ñêòví"N`ríö¦~¶`ª™ù~„Ñs¼Þ½¾Zh=­”`ÈBÃ(¯GþaªÄ¶„j¤)–"˜Œ2 eÔÞ4nÄ‘6õ?E?u_«€êâP¸ö¨n jÏêÿª zÙŽ5í0 åî i³k€GQuè2ÜçÖš„Z1À&$@nÀ…ÈËk%׿P?Z»’z2˜ö3ˆøþ.î„°p¶ð½÷µÿB¼[ˆüùï* ꊳêjëVZ{Úà™•mB•áìaìåwGêGš§b,§!xÛº]…jT6¶Š·6á‡Ö!$[DÌf mP‘έÔO´mM¨EÖA5°iNÏlžK}q£¶mÉ¢s à TCï÷±m´×z÷ûõ ,KÜ×b÷= D,¨- ÕE{çoß·×;a ª(TÉò~8ªƒ(@“÷(…¶!ðCÅh°Ç%(e˜Ë¶ØVa£<þÓhÆc)¾mÚÒ]ð=lsà!fÎ@`$jã݇L| a£ôé[ÈÜŸÍ6bî7„mz‘ÇXÙˆŽ@{xñmú¥<ÁŸ†Æ¶}€¢ûS¡ÛšðCfq ÆÚ'"‹ C­±‚?ÕAlLn‹‚ï!³¨}ˆ!‚\Ô‡~p1Зúâ— RžàB½àWüM!€Ð´Žw_{!+!“5HMDQî~:Áß*2°4@À~(u8ÚýYÌ}L6¼y‚5h ç#¨t·’m(¥×dn#CY¨Ç`p&j8êHÆ=H¢HÀKÑLƒ¢æ¨*¶ñà^c‘!€& †‚È(D$pêyÏAµó™{k6ÒöµhxÊKh,×dllaÎþÖ‘Y¤Í@ €„>e Žs_}¨ï”ËÜç–Á3ñëÐ<„—ÐèðHè7ivÊ~ã‘Y˜-DVA6"ƒŒ!CÍ×Ê -ÿOÔ¥W,€s2Bߌ˜ÎF¦ý 4Ø´ »÷Ú¡7 $[ OàC¨/a”bì6ÆÜƒz·Áò½{Äà}oÞb¨rïgTgïM-^è¾F½)ï™ü±ó3Ÿd …°BðÛxoF_4™·=õz·GÖÄ@4ëÀbËn„Ÿ(ü‚í‡_ÀThãíðìÐ Ï©7ßýæ~¤¡ƒg>ùÈ@ c3„àÁ#†Øw|ß[ÈbèÉ/I €,‹¶H°£ñâ±$àíÞë·©÷Ï#¾¿qØŒ CFØSÿß`™ŸÁW+%tEXtdate:create2014-01-30T07:32:57+08:00 7¿r%tEXtdate:modify2014-01-30T07:32:57+08:00{jÎtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚(!B   ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò@ ‹òP ‹ò€ ‹òP ‹ò@ ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òP ‹ò  ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹ò  ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òP ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹òÀ ‹ò Dwµ˜Gu¯¸Cx·Ð ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò° ‹òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò0 ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òñ ‹ò¾ ‹ò0ÿÿÿÿÿÿÿÿÿdTCÀdTCÿdTCÿdTCÿ`ZXÉPm–Ä ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹ò0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÎ ‹òñ ‹òÿ ‹ò ÿÿÿÿÿÿÿÿÿdTCÀdTCÿdTCÿdTCÿdTCÿdTCÿdTFäJr§É ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò0 ‹òð ‹òñ ‹òÎ ‹òÿ ‹òÿ ‹òÿ ‹òÿÿÿdTC dTC@dTC”dTCÐdTCädTCÿdTCÿdTCÿdTCÿdTCÿ_]bÄ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò= ‹òÙ ‹òÿ ‹òÿ ‹òÿ!‹ññJs¨¸dTCàdTCÿdTCÿdTCÿdTCÿdTCÿdTCädTCÀdTCñdTCÿdTCÿdTCÿcWMÜ*‡åÿ ‹òÿ ‹òÿ ‹òÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òÀ ‹òÿ ‹òÿ ‹òÿ8ÎÄcWOñdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCñdTCÉdTCÿdTCÿdTCÿ_]bÄ ‹òÿ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò€ ‹òÿ ‹òÿ ‹òÿMožÊdTCÿdTCÿdTCÿdTCÿdTCðdTCÐdTCÈdTCÀdTCÿdTCÿdTCÿdTCÿdTCÿdTCÄdTCÿdTCÿdTCÿUhˆÁ ‹òÿ ‹òÿ ‹òàÿÿÿÿÿÿÿÿÿdTC`dTC dTC ‹ò ‹òð ‹òÿ ‹òÿ>{ÁÁdTCÿdTCÿdTCÿdTCädTCÄdTCÿdTCÿdTC€ÿÿÿ ‹ò@8ÎÄcWOñdTCÿdTCÿdTCÿdTCÄdTCÿdTCÿdTDñ+‡äñ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿdTCÐdTCÿdTCÿ9~̳ ‹òÿ ‹òÿ"ŠïädTCÿdTCÿdTCÿdTCÜdTCñdTCÿdTCÿdTCÿdTC€ÿÿÿ ‹ò  ‹òÿ!‹ññ^_hÙdTCÿdTCÿdTCñdTCädTCÿdTCÿZdzÀ ‹òÿ ‹òÿ ‹ò°ÿÿÿdTC dTCÿdTCÿdTCÿ!‹ññ ‹òÿ ‹òÿQl“ÄdTCÿdTCÿdTCÐdTC=dTCðdTCÿdTCÿdTCÿdTCp ‹ò ‹òÿ ‹òÿ ‹òÿ!‹ññ`[[ädTCÿdTCÿdTCÄdTCÿdTCÿdTFä ‹òÿ ‹òÿ ‹òÿ ‹òdTC`dTCÿdTCÿ[buÐ ‹òÿ ‹òÿ!‹ñâdTCÿdTCÿdTCÿ^^fLÿÿÿdTC0dTCðdTC€dTCÿÿÿ ‹ò ‹òÀ ‹òÿ ‹òÿ ‹òÿ-†âÐdTCÿdTCÿdTCðdTCðdTCÿdTCÿ;}ÇÙ ‹òÿ ‹òÿ ‹ò@dTC€dTCÿdTCÿJs¨¸ ‹òÿ ‹òÿ6Ò¾dTCÿdTCÿbXPÓ ‹òÿ ‹ò  ‹ò0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÀ ‹òÀ ‹òÀbXSµdTCÀdTCÀdTC¥dTCÀdTCÀLp¡” ‹òÀ ‹òÀ ‹ò`dTC€dTCÿdTCÿCx·¸ ‹òÿ ‹òÿEv²ÀdTCÿdTCÿSk¨ ‹òÿ ‹òÿ ‹òàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTCdTCÀdTCÀ<|Ä~ ‹òÀ ‹òÀZcw˜dTCÿdTCÿEv²À ‹òÿ ‹òÿ ‹ò°ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC€dTCÿdTCÿEv²À ‹òÿ ‹òÿ ‹ò°ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTCpdTCÿdTCÿUhˆ¾ ‹òÿ ‹òÿ ‹òÿ ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC0dTC€dTC€dTC€dTCtdTC€dTC€:}Èf ‹ò€ ‹ò€Lp¡pdTC€dTC€dTC@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC0dTCÿdTCÿcWOñ!‹ññ ‹òÿ ‹òÿ ‹òÐ ‹òÿÿÿÿÿÿÿÿÿdTCPdTCðdTCÿdTCÿdTCñdTCÿdTCÿdTCÿ$ŠíÖ ‹òÿ ‹òÿZcxÄdTCÿdTCÿdTC`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC dTCdTCädTCÿdTCÿEv²À ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò ‹ò@dTC dTCÿdTCÿdTCÿdTCÿdTCÁdTCÿdTCÿ^_hÙ ‹òÿ ‹òÿ ‹òÿdTCðdTCÿdTCÿdTC0ÿÿÿÿÿÿ ‹ò ‹ò``ZX¥dTCÿdTCÿdTCÄdTCÿdTCÿdTCÿ8ÎÄ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò€ÿÿÿdTCàdTCÿdTCÿdTCÄdTCÿdTCÿdTCÿ8ÎÄ ‹òÿ ‹òÿ3‚ØÉdTCÿdTCÿdTCàÿÿÿÿÿÿ ‹ò` ‹òð ‹òÿPm–ÄdTCÿdTCÿdTCÿdTCÙdTCÿdTCÿdTCÿ>{ÁÁ ‹òÿ ‹òÿ ‹òÿ ‹ò€ÿÿÿdTCpdTCÿdTCÀdTCÿdTCÿdTCÿ[buÐ ‹òÿ ‹òÿ ‹òÿ[buÐdTCÿdTCÿdTCÿÿÿÿÿÿ ‹ò@ ‹òÿ ‹òÿ ‹òÿbXQÐdTCÿdTCÿdTCòdTCñdTCÿdTCÿdTCÿ`[[äJs¨¸-†âÐBy¹ˆdTC@dTC—dTCädTCÿdTCÿdTCÿ^_hÙ!‹ññ ‹òÿ ‹òÿ.…ßÔdTCÿdTCÿdTCÿdTC ÿÿÿÿÿÿÿÿÿ ‹òÀ ‹òÿ ‹òÿ:~ÉædTDñdTCÿdTCÿdTCædTCÙdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿ^_hÙ!‹ññ ‹òÿ ‹òÿ ‹òð ‹ò dTC€dTCðdTC ÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÿ ‹òÿ ‹òÿNnšÎdTCÿdTCÿdTCÿdTCÿdTCÀdTCñdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿcWOñEv²À ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò0ÿÿÿÿÿÿdTC dTCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òp ‹òÿ ‹òÿ ‹òÿPm–ÄdTDñdTCÿdTCÿdTCÿdTCñdTCÄdTCÄdTCÐdTCÈdTCÀdTCdTC`"ŠïÓ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò  ‹òÿ ‹òÿ ‹òÿ;}ÇÙcVKÙdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTC€ÿÿÿÿÿÿÿÿÿ ‹ò ‹òÿ ‹òÿ ‹òÙ ‹ò¥ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò  ‹òÿ ‹òÿ ‹òÿ ‹òÿPm–ÄdTFädTCÿdTCÿdTCÿdTCÿdTC€ÿÿÿÿÿÿÿÿÿ ‹ò ‹òä ‹òÄ ‹òÿ ‹òÿ ‹òpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò€ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ<|ÄÌZdzÀbXQÐcWMÌdTC` ‹ò0 ‹ò@ ‹ò€ ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò0 ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÀ ‹ò0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò` ‹òÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÀ ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò` ‹ò  ‹òÀ ‹òÀ ‹òÀ ‹òÀ ‹òÀ ‹ò ‹ò` ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿ€ÿÀÿ€ÿ€ü€ø€ø€ø€ü€ü€ø€€@€€@€€àÿÿ€ÿÿ€üÿÿ€üàüÀøÀ@€€@€€€À€À€à?€ðp€øp?€ü€þ?€ÿ€ÿ€ÿàÿ€(1b „%  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò@ ‹ò@ ‹òP ‹ò€ ‹òP ‹ò@ ‹ò@ ‹òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò` ‹ò° ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹ò° ‹ò` ‹òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò@ ‹ò° ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò° ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò@ ‹òÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÀ ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò° ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò  ‹òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò0 ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹ò  ‹ò` ‹ò0ÿÿÿÿÿÿdTCdTCÀdTCÀ`\_¬Zcx¦?z¾² ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò` ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò¬ ‹òpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTCÀdTCÿdTCÿdTCÿdTCÿdTCÿdTCà`\_¬8ά ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òP ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÈ ‹ò¬ ‹òÿ ‹òÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTCÀdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿcWO¬6ÒÈ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò¨ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTCÀdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿ]`k¨ ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÐ ‹òÿ ‹òÓ ‹òÈ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿÿÿÿÿÿÿÿÿdTC dTC@dTCˆdTC dTC¬dTCÐdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿcVJÈ,†ãÓ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò¦ ‹òÓ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ$ŠíµdTCpdTCÀdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÄdTC¦dTCÄdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTFÓ,†ãÓ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ"ŠïÓWe€¬dTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCðdTC¬dTCÓdTCÿdTCÿdTCÿdTCÿdTCÿdTFÓ,†ãÓ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ<|ĨdTCðdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCðdTC¨dTCÿdTCÿdTCÿdTCÿdTCÿdTFÓ ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òPÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿNoœ°dTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTC¦dTCÿdTCÿdTCÿdTCÿdTCÿ`\_¬ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò@ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿOm—ºdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCàdTC³dTC dTCºdTC`dTC dTC@dTCdTCàdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTC°dTCÿdTCÿdTCÿdTCÿdTCÿGu¯¸ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿdTCÐdTC dTC@ÿÿÿÿÿÿ ‹òÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿEv³¦dTCÿdTCÿdTCÿdTCÿdTCÿdTCàdTC°dTCðdTCÿdTCÿdTCÿdTC€ÿÿÿÿÿÿ ‹ò ‹òð@z½°dTCðdTCÿdTCÿdTCÿdTCÿdTCÿdTC¦dTCÿdTCÿdTCÿdTCÿcUGÄ ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òàÿÿÿÿÿÿÿÿÿdTC0dTCÿdTCÿdTCÿdTCÐGu­d ‹òÿ ‹òÿ ‹òÿ ‹òÿ&ˆêÈdTCðdTCÿdTCÿdTCÿdTCÿdTCÌdTCÓdTCÿdTCÿdTCÿdTCÿdTCÿdTC€ÿÿÿÿÿÿ ‹òp ‹òÿ ‹òÿ"ŠïÓ_\^ÈdTCÿdTCÿdTCÿdTCÿdTCðdTCÈdTCÿdTCÿdTCÿdTCÿHt«¬ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò0ÿÿÿÿÿÿdTCdTCÿdTCÿdTCÿdTCÿ)‡çº ‹òÿ ‹òÿ ‹òÿ ‹òÿ_\`ºdTCÿdTCÿdTCÿdTCÿdTC°dTCÀdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTC€ÿÿÿÿÿÿ ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òð]`m¿dTCÿdTCÿdTCÿdTCÿdTC¬dTCÿdTCÿdTCÿdTCÿcWMº ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿÿÿÿÿÿdTCÐdTCÿdTCÿdTCÿbXPÓ ‹òÿ ‹òÿ ‹òÿ ‹òÿ/„ݲdTCÿdTCÿdTCÿdTCÿdTCÐdTCdTCdTCÐdTCÿdTCÿdTCÿdTCÿdTCÿdTCpÿÿÿ ‹ò@ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ"ŠïÓcWOâdTCÿdTCÿdTCÿdTCÿdTC¬dTCÿdTCÿdTCÿdTCÿ.†áÄ ‹òÿ ‹òÿ ‹òÿ ‹òÐÿÿÿdTCdTCÿdTCÿdTCÿdTCÿKq¢¦ ‹òÿ ‹òÿ ‹òÿ ‹òÿ\ap²dTCÿdTCÿdTCÿdTCÿdTC@ÿÿÿÿÿÿdTCdTCÐdTCÿdTCÀdTC@ÿÿÿÿÿÿÿÿÿ ‹ò0 ‹òÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ6€Ñ¬dTCÿdTCÿdTCÿdTCÿdTC²dTCÿdTCÿdTCÿdTCÿUiˆ¦ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òdTC@dTCÿdTCÿdTCÿdTCÿ/„ݲ ‹òÿ ‹òÿ ‹òÿ ‹òàdTCðdTCÿdTCÿdTCÿ_\^È ‹ò`ÿÿÿÿÿÿÿÿÿdTCdTCpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò  ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿbXQÄdTCÿdTCÿdTCÿdTCàdTCàdTCÿdTCÿdTCÿaYT² ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò@dTCpdTCÿdTCÿdTCÿdTCÿ ‹òÀ ‹òÿ ‹òÿ ‹òÿ/„ݲdTCÿdTCÿdTCÿdTCÿEv³¦ ‹òÿ ‹òð ‹ò€ ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÀ ‹òÀ ‹òÀ ‹òÀJq¥‡dTCÀdTCÀdTCÀdTCÀdTCdTCÀdTCÀdTCÀdTC ‹òÀ ‹òÀ ‹òÀ ‹òÀ ‹òPdTC€dTCÿdTCÿdTCÿdTCÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ<|ĨdTCÿdTCÿdTCÿdTCÿ#ŠîÄ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC€dTCÿdTCÿdTCÿdTCÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿVg„ dTCÿdTCÿdTCÿdTCð ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTCpdTCÀdTCÀdTCÀdTC ‹òÀ ‹òÀ ‹òÀ ‹òÀZcw˜dTCÿdTCÿdTCÿdTCÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò°ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC€dTCÿdTCÿdTCÿdTCÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTCpdTCÿdTCÿdTCÿdTCÿ ‹òÀ ‹òÿ ‹òÿ ‹òÿ ‹òðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC@dTCÿdTCÿdTCÿdTCÿ/„ݲ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC@dTC€dTC€dTC€dTC€dTCXdTC€dTC€dTC€dTC€@z½X ‹ò€ ‹ò€ ‹ò€ ‹ò`dTC€dTC€dTC€dTC€dTC@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTCdTCÿdTCÿdTCÿdTCÿTiŒ³ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC0dTCðdTCÿdTCÿdTCÿdTCÿdTC¬dTCÿdTCÿdTCÿdTCÿ#ŠîÄ ‹òÿ ‹òÿ ‹òÿ#ŠîÄdTCÿdTCÿdTCÿdTCÿdTCPÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTCÐdTCÿdTCÿdTCÿdTCð!‹ñâ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC@dTCðdTCÿdTCÿdTCÿdTCÿdTCâdTCðdTCÿdTCÿdTCÿbXSµ ‹òÿ ‹òÿ ‹òÿ ‹òÿ<|ĨdTCÿdTCÿdTCÿdTCÿdTC0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿdTC0dTC dTC’dTCÿdTCÿdTCÿdTCÿWe€¬ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÐ ‹ò€ ‹ò@ÿÿÿdTC`dTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTC¬dTCÿdTCÿdTCÿdTCÿEv³¦ ‹òÿ ‹òÿ ‹òÿ ‹òÿ\ap²dTCÿdTCÿdTCÿdTCðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òdTC`dTCÐdTCÿdTCÿdTCàdTCðdTCÿdTCÿdTCÿdTCÿ<|Ĩ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò€ÿÿÿdTCdTCÿdTCÿdTCÿdTCÿdTCÿdTC°dTCÿdTCÿdTCÿdTCÿdTCà ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òàdTCðdTCÿdTCÿdTCÿdTC°ÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò@ ‹ò  ‹òÿ]`k¨dTCÿdTCÿdTCÿdTCÿdTC¦dTCÿdTCÿdTCÿdTCÿdTCð6€Ñ¬ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò€ÿÿÿÿÿÿdTC dTCÿdTCÿdTCÿdTC³dTCðdTCÿdTCÿdTCÿdTCÿEv³¦ ‹òÿ ‹òÿ ‹òÿ ‹òÿ@z½°dTCÿdTCÿdTCÿdTCÿdTC`ÿÿÿÿÿÿÿÿÿ ‹òà ‹òÿ ‹òÿ ‹òÿ.†áÄdTCðdTCÿdTCÿdTCÿdTCðdTCÈdTCÿdTCÿdTCÿdTCÿdTCÿSk¨ ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò€ÿÿÿÿÿÿdTCPdTCÿdTCàdTC¨dTCÿdTCÿdTCÿdTCÿdTCÿ_\^È ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òðbXQÄdTCÿdTCÿdTCÿdTCðdTCÿÿÿÿÿÿÿÿÿ ‹ò  ‹òÿ ‹òÿ ‹òÿ ‹òÿ]`k¨dTCÿdTCÿdTCÿdTCÿdTCÓdTCÓdTCÿdTCÿdTCÿdTCÿdTCÿcWOâIs©œ#ŠîÄ ‹òÿ ‹òÿ ‹ò€ÿÿÿÿÿÿdTCdTCœdTCâdTCÿdTCÿdTCÿdTCÿdTCÿbXPÓ"ŠïÓ ‹òÿ ‹òÿ ‹òÿ ‹òÿ<|ĨdTCÿdTCÿdTCÿdTCÿdTC ÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÿ ‹òÿ ‹òÿ ‹òÿ+†äâdTDâdTCÿdTCÿdTCÿdTCÿdTCÙdTCâdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÀ\ap²^_hdTC dTCÀdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿbXPÓ$ŠíÖ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÐdTC€dTCÿdTCÿdTCÿdTCÿdTC ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò  ‹òÿ ‹òÿ ‹òÿ ‹òÿ?z¾²dTCÿdTCÿdTCÿdTCÿdTCÿdTCÓdTCÓdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿ`[\Ö"ŠïÓ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò0ÿÿÿdTC0dTCÐdTCÿdTC ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿWf‚œdTCÿdTCÿdTCÿdTCÿdTCÿdTCðdTC¨dTCðdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCðNoœ° ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿdTC`dTCÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò` ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿWf‚œdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÄdTC¨dTCàdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCàSk¨#ŠîÄ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò  ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿWf‚œdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCàdTC¥dTC¨dTC²dTCÀdTCÀdTCÀdTC dTC€dTC0ÿÿÿ ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿHt«¬dTCðdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTC€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹òpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ,†ãÓ`\_¬dTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTC€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÿ ‹òÿ ‹òÿ ‹ò¬ ‹òÓ ‹òÿ ‹ò0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ8άaYT²dTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTC€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òÀ ‹ò² ‹ò¬ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò° ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ,†ãÓUiˆ¦cWMºdTCðdTCÿdTCÿdTCÿdTC€ÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òp ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò°ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹òp ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà.†áÄJr¦ Jr¦ ;}Ç ‹ò€ ‹ò€ ‹òÀ ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹òpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹òÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÀ ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò@ ‹òÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÀ ‹ò@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹ò ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ‹ò ‹ò` ‹ò€ ‹òÀ ‹òÀ ‹òÀ ‹òÀ ‹òÀ ‹òÀ ‹òÀ ‹ò€ ‹ò` ‹ò ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿ€ÿÿÿ€ÿüÿ€ÿðÿ€ÿÀÿ€ÿ€ÿ€ÿ?€þ??€þ€ÿ€ÿ€€ÿ€€ÿ€€ÿ€þ€Æ`€€`€€`€€@€ÁÀçðøÿÿÿ€ÿÿÿ€ÿÿÿ€ÿ€ÿÿÿ€ÿ€ÿÿÿ€ÿ€øÿ€ðÿÀàÿ@€ø@€à`€À`€À`€À€àC€àó€ðÿ€øÿ€ø|ÿ€ü|ÿ€þ~ÿ€ÿxÿ€ÿÀÿ€ÿàÿ€ÿøÿ€ÿþ?ÿ€ÿÿÀÿÿ€chef-12.14.60/omnibus/resources/chef/msi/assets/oc_16x16.ico000066400000000000000000000024061276456504500233210ustar00rootroot00000000000000 ð(" È  ‹ò0 ‹ò@ ‹ò€ ‹ò@ ‹ò ‹ò` ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÐ ‹òP ‹ò ‹òÀ ‹òÿ ‹òô ‹òž ‹òP`\_¬bXSÔZcxÖ3‚×ò ‹òÿ ‹ò  ‹ò ‹ò ‹òð ‹òÖ ‹òÿ)‡æˆdTC@dTC¬dTCèdTCÿdTCÿVh†Ø ‹òÿ ‹ò  ‹ò¬ ‹òÿ4Õà`[ZòdTCÿdTCÿdTCÿdTCôdTCßdTCÿXd|Ý ‹òÿ ‹òpdTCdTCp.…ßj ‹òÿ:~ÊÝdTCÿdTCâdTC¸dTCˆPm–_^dódTCÿdTCâdTCÿ?z¿ô ‹òàdTC`dTCÿ-†ââ!‹ñòcWNÿdTCÐdTCñdTCÿdTC€ ‹òà ‹òÿ]aoôdTCòdTCÿ_]bØ ‹òÿ ‹ò@dTC cWNÿ ‹òÿ<}ÅÐdTCÿ3‚׸Gu­ddTCP ‹ò` ‹òÀ9~̳dTCÀdTC¸bXQ¿ ‹òÀ ‹ò`dTC^_jÌ ‹òÀSkÎ`ZXÿ ‹òÿ ‹ò dTC cWNÿ ‹òÿ ‹òÀdTCdTC€dTCtdTC€By¹ˆ1ƒÛdTC€dTC@dTC dTCœdTCÿ4Õà ‹òÿ ‹ò° ‹ò@dTCÐdTCÿdTCÝdTCÿ"‹ðó.…àådTCÿdTC` ‹ò ‹òÀ\aqÜdTCÿdTCêcWNÿ3‚ØÜ ‹òÿ ‹ò€dTC¦dTCÝdTCÿLp¡à ‹òÿMožèdTCÿdTC ‹òÐ3‚ØÿdTEódTCódTCèdTCÿ_^dó_\^ÈdTCÿdTCÿUiˆâ ‹òÿ ‹òàbXQÄdTC  ‹ò0 ‹òÿ9~ÌódTEódTCÿdTCÜdTCØdTCÈdTCÀ8ÎÄ ‹òÿ ‹òð ‹ò0 ‹ò` ‹òÿ3‚×ò_]bØdTCÿdTCÿdTC€ ‹ò ‹òÖ ‹òâ ‹ò ‹ò@ ‹òà ‹òÿ*‡åÿByºÜ6ÒÈ ‹òÐ ‹òÿ ‹òÿ ‹òÐ ‹ò ‹ò` ‹ò° ‹òÀ ‹òÀ ‹òÀ ‹ò  ‹òPü€ð€À€À€à€€€ÿ€á€À€€€€ÀC€à€ø€chef-12.14.60/omnibus/resources/chef/msi/assets/oc_32x32.ico000066400000000000000000000111121276456504500233070ustar00rootroot00000000000000!! 4(!B   ‹ò ‹ò@ ‹òP ‹ò€ ‹òP ‹ò@ ‹ò ‹òP ‹ò  ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹ò  ‹ò@ ‹òP ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹ò@ ‹ò ‹òÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹òÀ ‹ò Dwµ˜Gu¯¸Cx·Ð ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò° ‹ò ‹ò0 ‹òð ‹òÿ ‹òÿ ‹òÿ ‹òñ ‹ò¾ ‹ò0dTCÀdTCÿdTCÿdTCÿ`ZXÉPm–Ä ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òà ‹ò0 ‹òà ‹òÿ ‹òÿ ‹òÿ ‹òÎ ‹òñ ‹òÿ ‹ò dTCÀdTCÿdTCÿdTCÿdTCÿdTCÿdTFäJr§É ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò0 ‹ò0 ‹òð ‹òñ ‹òÎ ‹òÿ ‹òÿ ‹òÿ ‹òdTC dTC@dTC”dTCÐdTCädTCÿdTCÿdTCÿdTCÿdTCÿ_]bÄ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò ‹ò= ‹òÙ ‹òÿ ‹òÿ ‹òÿ!‹ññJs¨¸dTCàdTCÿdTCÿdTCÿdTCÿdTCÿdTCädTCÀdTCñdTCÿdTCÿdTCÿcWMÜ*‡åÿ ‹òÿ ‹òÿ ‹òÀ ‹òÀ ‹òÿ ‹òÿ ‹òÿ8ÎÄcWOñdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCñdTCÉdTCÿdTCÿdTCÿ_]bÄ ‹òÿ ‹òÿ ‹òÿ ‹ò` ‹ò€ ‹òÿ ‹òÿ ‹òÿMožÊdTCÿdTCÿdTCÿdTCÿdTCðdTCÐdTCÈdTCÀdTCÿdTCÿdTCÿdTCÿdTCÿdTCÄdTCÿdTCÿdTCÿUhˆÁ ‹òÿ ‹òÿ ‹òàdTC`dTC dTC ‹ò ‹òð ‹òÿ ‹òÿ>{ÁÁdTCÿdTCÿdTCÿdTCädTCÄdTCÿdTCÿdTC€ ‹ò@8ÎÄcWOñdTCÿdTCÿdTCÿdTCÄdTCÿdTCÿdTDñ+‡äñ ‹òÿ ‹òÿ ‹ò`dTCÐdTCÿdTCÿ9~̳ ‹òÿ ‹òÿ"ŠïädTCÿdTCÿdTCÿdTCÜdTCñdTCÿdTCÿdTCÿdTC€ ‹ò  ‹òÿ!‹ññ^_hÙdTCÿdTCÿdTCñdTCädTCÿdTCÿZdzÀ ‹òÿ ‹òÿ ‹ò°dTC dTCÿdTCÿdTCÿ!‹ññ ‹òÿ ‹òÿQl“ÄdTCÿdTCÿdTCÐdTC=dTCðdTCÿdTCÿdTCÿdTCp ‹ò ‹òÿ ‹òÿ ‹òÿ!‹ññ`[[ädTCÿdTCÿdTCÄdTCÿdTCÿdTFä ‹òÿ ‹òÿ ‹òÿ ‹òdTC`dTCÿdTCÿ[buÐ ‹òÿ ‹òÿ!‹ñâdTCÿdTCÿdTCÿ^^fLdTC0dTCðdTC€dTC ‹ò ‹òÀ ‹òÿ ‹òÿ ‹òÿ-†âÐdTCÿdTCÿdTCðdTCðdTCÿdTCÿ;}ÇÙ ‹òÿ ‹òÿ ‹ò@dTC€dTCÿdTCÿJs¨¸ ‹òÿ ‹òÿ6Ò¾dTCÿdTCÿbXPÓ ‹òÿ ‹ò  ‹ò0 ‹ò ‹òÀ ‹òÀ ‹òÀbXSµdTCÀdTCÀdTC¥dTCÀdTCÀLp¡” ‹òÀ ‹òÀ ‹ò`dTC€dTCÿdTCÿCx·¸ ‹òÿ ‹òÿEv²ÀdTCÿdTCÿSk¨ ‹òÿ ‹òÿ ‹òàdTCdTCÀdTCÀ<|Ä~ ‹òÀ ‹òÀZcw˜dTCÿdTCÿEv²À ‹òÿ ‹òÿ ‹ò°dTC€dTCÿdTCÿEv²À ‹òÿ ‹òÿ ‹ò°dTCpdTCÿdTCÿUhˆ¾ ‹òÿ ‹òÿ ‹òÿ ‹ò dTC0dTC€dTC€dTC€dTCtdTC€dTC€:}Èf ‹ò€ ‹ò€Lp¡pdTC€dTC€dTC@dTC0dTCÿdTCÿcWOñ!‹ññ ‹òÿ ‹òÿ ‹òÐ ‹òdTCPdTCðdTCÿdTCÿdTCñdTCÿdTCÿdTCÿ$ŠíÖ ‹òÿ ‹òÿZcxÄdTCÿdTCÿdTC`dTC dTCdTCädTCÿdTCÿEv²À ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò ‹ò@dTC dTCÿdTCÿdTCÿdTCÿdTCÁdTCÿdTCÿ^_hÙ ‹òÿ ‹òÿ ‹òÿdTCðdTCÿdTCÿdTC0 ‹ò ‹ò``ZX¥dTCÿdTCÿdTCÄdTCÿdTCÿdTCÿ8ÎÄ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò€dTCàdTCÿdTCÿdTCÄdTCÿdTCÿdTCÿ8ÎÄ ‹òÿ ‹òÿ3‚ØÉdTCÿdTCÿdTCà ‹ò` ‹òð ‹òÿPm–ÄdTCÿdTCÿdTCÿdTCÙdTCÿdTCÿdTCÿ>{ÁÁ ‹òÿ ‹òÿ ‹òÿ ‹ò€dTCpdTCÿdTCÀdTCÿdTCÿdTCÿ[buÐ ‹òÿ ‹òÿ ‹òÿ[buÐdTCÿdTCÿdTC ‹ò@ ‹òÿ ‹òÿ ‹òÿbXQÐdTCÿdTCÿdTCòdTCñdTCÿdTCÿdTCÿ`[[äJs¨¸-†âÐBy¹ˆdTC@dTC—dTCädTCÿdTCÿdTCÿ^_hÙ!‹ññ ‹òÿ ‹òÿ.…ßÔdTCÿdTCÿdTCÿdTC ‹òÀ ‹òÿ ‹òÿ:~ÉædTDñdTCÿdTCÿdTCædTCÙdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿ^_hÙ!‹ññ ‹òÿ ‹òÿ ‹òð ‹ò dTC€dTCðdTC  ‹ò ‹òÿ ‹òÿ ‹òÿNnšÎdTCÿdTCÿdTCÿdTCÿdTCÀdTCñdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿcWOñEv²À ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò0dTC dTC ‹òp ‹òÿ ‹òÿ ‹òÿPm–ÄdTDñdTCÿdTCÿdTCÿdTCñdTCÄdTCÄdTCÐdTCÈdTCÀdTCdTC`"ŠïÓ ‹òÿ ‹òÿ ‹òÿ ‹òð ‹ò@ ‹ò  ‹òÿ ‹òÿ ‹òÿ;}ÇÙcVKÙdTCÿdTCÿdTCÿdTCÿdTCÿdTCÿdTC€ ‹ò ‹òÿ ‹òÿ ‹òÙ ‹ò¥ ‹ò  ‹òÿ ‹òÿ ‹òÿ ‹òÿPm–ÄdTFädTCÿdTCÿdTCÿdTCÿdTC€ ‹ò ‹òä ‹òÄ ‹òÿ ‹òÿ ‹òp ‹ò€ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ<|ÄÌZdzÀbXQÐcWMÌdTC` ‹ò0 ‹ò@ ‹ò€ ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹ò ‹ò0 ‹òÐ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÀ ‹ò0 ‹ò` ‹òÀ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÿ ‹òÀ ‹ò@ ‹ò ‹ò` ‹ò  ‹òÀ ‹òÀ ‹òÀ ‹òÀ ‹òÀ ‹ò ‹ò` ‹ò ÿøÿ€ÿÀÿ€ÿ€ü€ø€ø€ø€ü€ü€ø€€@€€@€€àÿÿ€ÿÿ€üÿÿ€üàüÀøÀ@€€@€€€À€À€à?€ðp€øp?€ü€þ?€ÿ€ÿ€ÿàÿ€chef-12.14.60/omnibus/resources/chef/msi/localization-en-us.wxl.erb000066400000000000000000000037661276456504500251070ustar00rootroot00000000000000 1033 <%= friendly_name %> Chef Software, Inc. {\WixUI_Font_Bigger}Welcome to the [ProductName] Setup Wizard {\WixUI_Font_Title_White}End-User License Agreement {\WixUI_Font_Normal_White}Please read the following license agreement carefully {\WixUI_Font_Title_White}Destination Folder {\WixUI_Font_Normal_White}Click Next to install to the default folder or click Change to choose another. {\WixUI_Font_Title_White}Installing [ProductName] {\WixUI_Font_Title_White}Ready to install [ProductName] <%= friendly_name %> Service Runs <%= friendly_name %> on regular, configurable intervals. <%= friendly_name %> <%= friendly_name %> Service <%= friendly_name %> PowerShell wrappers This package requires minimum OS version: Windows 7/Windows Server 2008 R2 or greater. A newer version of [ProductName] is already installed. Extracting files, please wait... chef-12.14.60/omnibus/resources/chef/msi/parameters.wxi.erb000066400000000000000000000004571276456504500235240ustar00rootroot00000000000000 " ?> " ?> " ?> <% parameters.each do |key, value| -%> ="<%= value %>" ?> <% end -%> chef-12.14.60/omnibus/resources/chef/msi/source.wxs.erb000066400000000000000000000213151276456504500226670ustar00rootroot00000000000000 <% if fastmsi %> NOT Installed REMOVE~="ALL" !(loc.FileExtractionProgress) <% end %> chef-12.14.60/omnibus/resources/chef/pkg/000077500000000000000000000000001276456504500200445ustar00rootroot00000000000000chef-12.14.60/omnibus/resources/chef/pkg/background.png000066400000000000000000001546631276456504500227100ustar00rootroot00000000000000‰PNG  IHDR5²Á<õ pHYs.#.#x¥?vtEXtSoftwareAdobe ImageReadyqÉe<Ù@IDATxÚìÝAŽIú7à˜¿f‹`N€ëÐ'À-±§zÁ÷‚5…ij/iŠ5‹6k]ì‘Úœ`Š”9ÁG‹ðå‹ÃMeg¦¶3Ï#¥Ü3.è®×™iÇÏoDüëË—/  +þO €.j"Ô:E¨ tŠPè¡&Ð)BM S„š@§5€Nj"Ô:E¨ tŠPè¡&Ð)BM S„š@§5€Nj"Ô:E¨ tŠPè¡&Ð)BM S„š@§5€Nj"Ô:E¨ tŠPè¡&Ð)BM S„š@§5€Nj"Ô:E¨ tŠPè¡&Ð)BM S„š@§5€Nj"Ô:E¨ tŠPè¡&Ð)BM S„š@§5€Nj"Ô:E¨ tŠPè¡&Ð)BM S„š@§5€Nj"Ô:E¨ tŠPè¡&Ð)BM S„š@§5€Nj"Ô:E¨ tŠPè¡&Ð)BM S„š@§5€Nj"Ô:E¨ tŠPè¡&Ð)BM S„š@§5€Nj"Ô:åßJÐ.ŸO†%?r·8n­ñW*Ž‹’Ÿ¹¸ñìò“W€6û×—/_T A?„’Wÿy…&ïtàWúæèÂ, Ó+ÿ,`넚|>=ŠònþŸÃü8HßBÊ{ªô“÷ùq–¾… Óü(ü`mBM€ô]h9¸æ¸­B[ó1} =¿;n<»œ)×j½q%¸\¬I9LÝ™ÞW‹©ïÓômMP'@Ï 5€ƒóùôhæ–Ãô­ÛÒôðÃÓÛgù˜&a'@o5€NË›ò,¦Ç£ð’EØy±8¬ß pX„š@'\™:>Lߦ[ë’ªbíÎEÈ9M‚N€Nj­t¥sñ(À¤iß7ž]N• „šÀÞå50‡é[ˆiãö%6&ZtsN­Ñ ÐNBM`ç>Ÿ-ÂË8taÒf§p¦yÈy¡$û'Ô¶î‡3Ž›ªB‡ÅFDÓdÊ:ÀÞ5€Æ 1é™·I''ÀN 5€åÉÓ·Ótrúj1]ýp½Øxhšæç¹r4C¨ Tr¥sdšRõÅTu]œjK]Ys”tcBÓ¢‹3ÎskqÔ#Ô¾“ƒÌQšwdZvãcúÖÁiš:@ ¡&AæbZy¦•Ã~ÅfC‹N'À5„šÐS‚Lè'À5„šÐ#‚Lè4'@&Ô€gL8H‹€óÌ&C@ 5à}>=¤y‡ Ûb“¡8gÊôPÄçÓ£[iÞyRwTzéCqLâ¸ñìò“r‡J¨ ÷ùôh˜æ™T¸âmš‡›ÖߎP:Èôr ÓÓ€ƒ#Ԁɻ—ŠãjkxŸæÝ›¥ºL¨ -§+Ø‚Ø=}’to%Ô€–Ò• ìHtožY{è¡&´HÞÁ|”æ;˜ëÊv)ÖÞœ¤yÀiçt Õ„šÐyŠù¸8¢;ó¦Š{ö:ÍÃÍ ¥ÚH¨ {”§˜GWæ=ÕZÈÆB@+ 5`Çòó3ÇÉs bjúYšœ¦¦{'Ô€ÉaæI>L1ºÈ®é@+5`Ë®¬—ùH5€ënŽ…›À>5`K>ŸÝMó®La&pÈbÝÍ7§JìŠPöùôh˜æ™6ÿúD¸ ìŒP"ÌøÊŽéÀÖ 5`CÂL€kÅŽécá&° BMX“0 á&Ð8¡&Ô$ÌX‹phŒP*f4"ÂÍ‘ …€M5 „0`+ì–¬M¨ K|>=“$ÌØ&á&P›P~ÃÌqqU 5è›tšÍ„€¯„šô†u3Æ‹d½Mè5¡&ïóéÑqšo8aÝL€Ã› XoúI¨ ÀÁÊSÍc°kÝL€Ãõ>ÍÃÍ ¥€þjpp>ŸÝJóiæÏU 7^¦ùN馤@58(¦šôš)éÐBMBžjaæÕ轘’>²K:®ÿSº.ïjk© 4±–òe~NM:«¬ÞMó€î¨K|Ló®Í©RÀájÐ9y# qqд"l˜&f_ï/‹ŽÎEÐiY šKUœ÷—‰RÀ~ 5؉<…ô¬8© X¬9£«ëÝÝøxøÃÿ5¼æÇ†5þʸÎ>½{ójèùçÞ3Lß:9ãÑ*4Á&B°gBM¶Îf@4 BÌiúb¶¶ óJP¹Øð&,þ¿[;¸Þ 5KïGÃ+‡“uÅ&BǺÂ`?„šlÕçÓ£Qšwh ¨+:¡¦©E!æý‡›Ö ~8vVV%Ô¬wr²©§Å=êL`·„šl…鿬!ÖÄwec+¡æf÷®x½cíßa²™Õý¢cvëßJÀ–BI2ÝœÕbJù×3÷±6ÝáåâŸo{iú+Sù^v+Ÿ‹Ó¹Áu^4`÷tjÐ(ÓÍ)ݘÓâ˜ì2¸2m|˜¾u_jè®Ss{÷·Eçqò¥ ùz+îe®7Øš45Ø7Ýœ¥ƒþ4ïÈŒnÌÙ.þ…¹sbÆ£;6v¥‹s\Üóé[çÕ饿óëìP€åÁ}„V:—Xx›¾™[V~ÿáã8ÿ†é[ˆé–Íæ„šl¤ÌÓ<¼2Ýœ™÷>¾Úº0Ù«|¾O’€³O^ì{C3è;kj°¶bð>.ž«D¯m=ȼ҉¹Ø¬E€¾š55Ûspè5fMØ?š¬;PŸ¤÷Ö‡üúoe̼©Ï0ÙqšŽ[ÒÁy’,‘ÐeÖÑ€–jPKÞýwbPÞ;Ó¼#ólKA¦]¥9h?œƒâq”¡}·XGZÂôs*³~f/½NóŽÌó&ÿÒº1S2ý¼{÷Õ‘ë bͱ2@;èÔ êÀ;¦LþW%za1½|ÒdGR^s1¥ÜÒ¾vpN‹‡©éé­÷^  í"Ô T1ØžTâ Å:q‹éåMý¥W‚ÌQÔÀR?LOåN’îÍ6Ý­£ -#Ô`©Ü94M¨CkeŽSƒ»— 2a3ù‹…‘îÍÖ°Ž&´P€kåN¡èܳ‰Åaе2'yêëÆ™Ð¼%Ý›ºæwëES÷I Y6 à'Åàù8¤M{<,1…ò,ÍÃÌÙ¦YÞìGYûØ(è°ïÏ·ò57J¾tÚúµTÜ+]KÐR:5øqÀå?Tâ |b^ Î'Müe÷>^tdÚìv,woŽãÈ÷k_*lÇßù>´”P€Øèà¼Oó0sºé_tÿáã»y€‡^hüEELM¦y¸é‹†æŒšèh¶G¨ Àb:cLKh†X/s¼é€ÜôrØÙ=8®³éº›Ñä/.¦Åß3Hó.N÷òͼ,jz® ÐnÖÔ0˜¶Ãùaˆ©’1o"Ì\lHA‹®Ìf·îÁƒâáòÊ5|–w?ßô¾~’×p=ŠúßUh?šýLÇÀm’š]¶ØüçlÝ.¯ +öæ$?Fø–Š{ó‡|]Ÿ¯s]/ÖÝ,þž³$ܬ{?=Vèš=•Í©n§ßM„™ƒôm'eçÂaЩ٭{ñ,-ßÅ<®óI¾Îgü;nåkü$Ù1}•ßL;€îjôs}œÊB¬îi*̦ùÚ{÷”ôà5»u/þ³ê뚯ûó ÿ£|í 7¿ëhž(t‡P ƒèÐþ¡³q˜yeŠù8 4™P³;÷ã(ëîXþ1_Ãç›|±!ÜüŽu4 ƒ„šý@Ç V Ù-M…™ÖÕë¡f7îǃ4ß hÓ{Ãdééq_÷øÞu¼»ékÀî 5ú3€ž¤ù&tg ½i˜9Hó°ÂëÞ/BÍnÜ“ãÚ|ÞÐ_÷:®õuƒ¹žï–nMè(¡&@?Ï“$Øê’—iP¬fÓ|S¯y? 5»q_ž¥æ§~¿Mó/B¦kþ7õ-Ü´Ž&t˜PàðΓ$Üꊺ­lþC&Ôlÿ}9^Ÿ¿¶yä{ÉtÍÿ¾A:ü.oëh@Çý[ vÐ71¥NÀÕ~@œì‹uþ°0:g´å¿?îïk…›ù‹•Qž"?9À{K,ïqì4€nÓ© p€r ƒØ;ªÑj±‹ñhÝn*a&KèÔlÿýùÿíúœH›unÆùtv@ï)ÖÑ€ Sà0ÌÓ$Ðl³èŠ€ál?,Ì„Níáß¹içfüüÝâÏò½çv‡ëÿR  ‡áÿ”àp4»1 .ŽÁ:f„™Å¯o¬Å'Єníñß½7§¹û²–â¾5)bÊ­ýÀá0ýà@4[/:¤Fël¤3“ºçšéç­½OG ø¿–Ý—Öš–ž7Š/gt¤üÑ!wÝØ€ö1ýà0ÊÍöŠu3OÖ™îxÿáãA:üˆ¡OÚÖ%¸èÜ|›ïS³ª0ÿìqîøœ¤öOI?hÀaÑ© ÐqÍV‹)šgÅ@úS?tÿáãxM£J˜É:tj¶÷^=+Ž›-þÏ|æáß§5~¿qš‡¶müý^¿ÓÈY‡E§&@÷ÉÓ$Ðl›µ¦šç0ó¤ÅÁ°¾ã\×ñEJt_Æ—*µ¾)~v\ü¹Ijß”ô©}²@lÐQÍVŠ5Û~+÷Ã5ÍQñpQÏ“@Ѩ#ÿ7ó}h–w;¯,î{Åáí¯i¾ôFîÉ£u:O€öjt@³•»š×Z;óÊŽæ¤ö¯I¬wϤîmôáæÅûEÝÒóÆCmØ%=¦Ò_8à0™~нÁ±@³]>äó´Î² ôJ—§?Ç{MíÍ„rwdLI/z&{xÏŠu4'N=8\:5:D Ù:/ŠAóÝ5ÍqšO5hB?Œàwˆu2£ksœß‹*‰NɸOÿø4ͧƒï‚u4 „š!Ðl•Øè—Ø£ÎÊSÍgɺ™Ð§{÷ñ]ï‹õ6/òïUYq¿Œ „îæûç6YGzÂôs€n ŠšíƒåqœW–§š·mG``7Fø;Åú¿ïMPŽjLIŸ.º(Çi;a¯u4 'tj´œ@³5bð~w@3î1ÀhBÿî߃¿öcó£Ë˜’^çm±kÓ:šÐ#:5Ú= h¶ÃÓ5ÂÌašwgzí ¿F=ù=ŸïWñ»Žª®1¼…®Mëh@ÏèÔh7¡Ø~Å ù—:æý‡oGüü_^;è½Q~ט’»¤Ojn$ÔDצu4 ‡„š-Ãdwì}Zìl^ym¶ûÇÆ³âx¢|Ðû{ø0̓¾¾‰÷­Y„¢k³8¢^O×üwZGzH¨ ÐÎÁð$ 4÷åcª¹³yîÎ~Ë}ü«¸/^äu3+‰ûotÉÿø²Â[GzÌFAíЇç*±s‹õØÎ«þèÎ,&I˜ ül¤ÿˆ®Íÿæé裼AP©âçN¢Ó³øÇó´¼Þ:šÐc:5Z"š¨ÄÎE§ÏÝš¦îL`•‘üä^ªßµ9-éúM„žZGúí__¾|Q€=+y1Õî*±s/ê®™tgÒ~ïß½y5T÷ó{›jvYæ0ô¿‹?_üÙce€~Ó© ÐŽðT%v*¦›ÿZ3ÐÔ TaÇrq­»Cúb¡¯¨:5ö(ï Óç옽;1ñ¸j‡PîÎÇ¥£+ç¸NͽÝÓã~1sO¯%6h;±6&P—NM€ý~Ï ~w*¦›kš‹.Z&Pű{zm±K|¬µ9T  ¡&ÀþL‹ãŽ2ìDL7ÿ­ætó˜Bú?¯PÃH Ör»8þú|z4V  ª+Àî·I–íJìnÓÍgU~8O7Ú{JÔ¸¯Ü76öŽ×#º9M7§iÑe¹.gù¸x÷æ•N²Ã¿ÏÓ| öã÷â=`¢ ÐOBM€í t£#ð*±5ÑýVgýÌ3Ÿ( œw‹àrïÞ¼š)K¯ïõ“¤ß^ï#e€þj4?È5gIGà¶¼Mó)ç¥f^?3¦›ÛÝœº¢ûòbq¼{óÊ4W®»×ÿ?•h…Z_t‡ÁFAÍ›&æ¶TîÈÉëgN’Mš(·èÀŒkW€IU#%h¸ÏÏb9ël@èÔh©ˆ[Ug‡óašwh —¹Î¢ s‡µ/Yó~ç/MÚ%ÖZ>±Î&ôƒP ¹î(Ùh[*o‘7ò:pUtbN‹ã¼k;Žç%î^óÔ²ÿ¿ÌböŸþán­û½u“ÛíEñž1V8lBMÜ6‹®›ãbp:­òÃ÷>ž$²¤ô1å3µ¬³8GÅà ÿÏa~¼PÆ?·¡û/‚àEÝfù‹P´×›$éÊïÀjl>¸µ1ÐvD Yi}´ÜÍ6)ŽÊÖ[1¥|bîmM½+¡e„”·~x¼y ×é"è¼úx°¡§{~§Ø@˜P`óî4Ù]{ÑQ@3^kÛõK„içéÛ´ò†y#ªAš‡•‹àÒ}àúky–rЙ`#&Kîû Ð-BM€Í·±qÍ•h|Z©³&KÓ¤cª/Af„˜ç»ø^YÓòê!@oæ:Ÿ¥ïwïD7/²:{ï°3:¡&ÀúÛãâáO•hT@s˜ìpÞ; 2óyµ/ãŸo{ v&ÖB½ÈÇ´›:÷ýAñpé¥êì½ÄÎèp@„šëlcà-PkN@s”Lÿb†ó Þsâúˆ÷áf{ 6 …„šß.c}¿ÿ©ÄFš‡aã €L1§Gbjú8m°±p³õ›Ð2BM€ï•1XÀ¬O Ù}ÎŒÖ 3ók»˜b~[9陾bÝͳ ÂÍãüw¸~Úùú 6 %„šß’ãdÚù¦ƒ½@³³¾vš½{ój²Î¾fÆa½LÜ7\w³xO¥y¸ézêà{°}BM€dÚyCƒ¼Òîfk_»µ;Ëlþ¥ÖÞd«xoòeA;Uš•l—P ™v¾!fw­¶ä0s\”¶{½ïQq½Å—6ÛjÁ&ì™Pè=ÓÎ7ö[1¨+Ýù÷þÃÇÓdŒ6 ÆOÖY73‡Ó®3a=›„›Ã|ýùr¨=÷RÁ&ì‰Pè5ÓÎ7ö{1˜›”ýÐý‡ãg„`û]µ'묛iÍLhÜ&áf\‡c×b+|(Þï*ìžPè5ÓÎ7ò¢ÈË~H Ù œÔ]7S˜ Û¿—¦5Ö´ÍëmêšnÉýµx?)ì–Pè-Óη?€»ÿðq ¸Ÿ(×^m2Õ<^c»/Ãö}ݰ«¸NÇk¼— “)éy_š#ÔzÉ´ó¼/nòÊØʵ7k‡$Åk¯ï¤8n+#ìÔÇ4Ÿ’>Yã}-®uÕûõ²x©»_^7Óæ"Ð±Þæh)龸Øß½Y° [$Ôz£ØªDm¥ä@3ڲݪ½v¦©æÐyµ»²umîM¥u¨©çÊfvgu—[à°5^(tº×<ƒ±ã Œ\î+ƒ|ŸaûÎòìV¿‡ ‹c–ªš!–:Q9€þÑ© ¬¼9P|(ÖR]éÆ@v:ß©ZSJugÙ:]›q¯y®t[÷µ«ÐŽèK?_l²N÷‘MƒúE§&pÈb€&Ь9®ðs1àhnWL7ÿµf ©;XX§k3~ö×|ÿa{¢«pª ×¾‡ÍÒfNT _tjÉæ@µUÝ(½:y¶+¦W],w¶Ä@îÒKî)£ªly–C,Aâ ’íz]¼çŽú^„¼>÷Yƒç›MƒzD¨ $›ÕVºyA1ðˆié*ÕVÕÚÝ<ÖK6ùÊÅWlž×xµ;z Þ{UþB.Þïšþ¢ôcqžœZý ÔN1¾ÕSÚ-’×jŒ.NáÙvDàpR Ä*n‹×DàÔ¾ßç{M¥Nð¼ÙÞ™{ÿVýR6KâÐä/äâýîö–þµÖ£ »„šÀAÉÓæ.¶øAùÐÄ:šÃUØhëbÓˆãªzä€ùÜëlp߯¼‰P^ÎåÜûêVßz±qЗK‰/ UÃ{ºËFAÀ¡91ðªõ¡ÿ¸Â@ÊÆ@ÛkÝÝ­hFò…ר@Ü?¦ÅýdTå‡sáÝ|¿¢yñ™åüÐÉ+íbýç›ù³ N§&p0>Ÿ ’)ÒuTYG3!ÿUª­xýîÍ«Q¡éæ@ã÷¡To:z¼g+ŒN‡C§&Ð9º4+‹iç'%ƒ•øpÿ\©6÷a•‚ípt\Ì8®¸3º ÐvøžÞ¤nTÇ/UÎ_ÚO§&ÐEgH•ã’ËÝ$ÐlB@3j?'к*ÖRœæûÙJyÊô0éØl“ϧGÇ»øut# ºŸ#8:5N)>ÐЇK•(õk1˜œ®°èlFÝ@3j.åþkl–îЭc³Ñš*,+³–¼Æv~úð9©8w§N)€nÓ© tÍX J½\h^©£@sóÁeÕ@sh@˜¸ŸýYÜßFe?¨c³ÑšO¶ñç€âuzГZú< ptj¡K³’*»ÇÀò/¥ÚH@3üvY¥MÒtl6æ·â}þ¼‰¿(Ï"ˆ×®_têÖè8¡&ÐÅ`(>tÛ-ºäz…içÄÝVªµ 4Ù‡÷?üïY>êäãª{ÊKž÷ÅÒµ ›½m4 =Ç“>ßW‹svètè.¡&Ð º4+©²ÛùYÏ0M $š4:¨ÎÓüçÖ× bD¹këV>Á,ýÂOʼ.ÎÛQ…÷tÁææÞïùÇk^çñçâó€/8uktšPè]š¥J»6L;o¤ÆMÖËBÌÒ<Ä™åã¢8—>uñ—É÷’EèÇ Y£—o›»SkzÏ6ªJ·&@‡ 5ÖÓ¥¹ùÀÆ´ó 4©<@Î×Z³>uå.Ï«‡ÎÎþlîÒ4ô¼ÐX­¯¥[ £þ­@Œ•`¥÷:5¢†Íõ 4¹Ft`Æ@økˆÙ÷Aq¾F.~¸&ç0îCýð¨xíSY°»¢>=&Á溻¡—¼7E ù_åZùi¨ Ý£Sh5]š¥¢K#v;Ÿ­ÌD ð?¥Z[¥]}š½°1¿Åy1S’zr×ø0Ä9[ÕŽÍ8,²¾*³5â~%8^N·&@ 5V³–f©Å@f\"Dç”õîÖ#Ðì·øÒ`šs!fóòÃô-亞ªÁ¦ûèúâ —»%ëjÇõõ§R-emM€j­U p¢³àÿ©ÄRŠÌÝ’À`\<œ”\_Ód­ÛU~©²Ô í!ÔZ«ÜŒ“@n•_‹ÌtÅàeæëÛé|ÚÒ\ y0>¤yy¾ïmÞ8åVú¶»xúáŸV±Îî°ë/J¾‡E¸×™nóžÜW‹kÀúÛý\`IŸ ÏQÚC¨ ´RîÒœ%ÜÒÞÅÀeTÄÀFGÆ–5Í΋ŽÌI»œVžïm‹ÍsëK¦†¯Õƒ5¸ÞIÀy*uÀ[zfmfplîÈR#Ý!ÔZI—æJ±ÎßÀÚYÛƒ™»e?$Ðìôµ3Ió së™yó“8ŸùqW_2\¨ùÃõõŒn>SÔ»I°¹]+×ÚΛ]¸v–Ò­ Ð!BM •ŠÁLvº4¯÷´°œ°4.¦ ‹Á̧U?d7ùNz›æAæÖÖÈÌÓÆ‡é[æ>» :ÔüázŒ`3áW·”›¹«yštæÖ_ÞĦA³’ëÆŸËéÖè¡&Ð:6 Xéc1P” ò#ð|¢Tõeƒ˜hÆ [àÞk%mqzyîÂ\w[vNô&Ô¼rmFïÑÁé nø­ì‹†lú’®¾·Å=à¸äš‰÷2KÔ\ïEqnŽ• ý„š@냘™ÌRe›è"¬/ÍaÙtdfg¼/޳¦»2¯tbÆñ í5è[¨ùõ¿ûI^'÷Þ ÷Þ|í¹÷ú¼°ëssP6s€ýû?%Ú$wi 4¯÷~Õ%;S¦ÚN*šÑ-41¨nõôušO6h÷£ãX×/уÿØ‘YPÖrÅë?-ŽèR;*Ž—ùü }â~:ÍáÚRÅû^ÜŸ•«¶IÉur‘¯®?7GÊÐ~:5Vù|z4M¦C-sd¬Æ=-v+ƒàhÆyi]·ö‰°*^¿³&:jòT×Å]/{Ý©¹ä%SÓÛªÒzÆ–¦YK•Mƒâs…/ì~ö±8'ÊÐnBM 5òu©Äµ^““’‰uÇê©´ÃiQÛèúÓײÁfqÄ@ý|Ó0ó€‚Ì«„šË¯çQ>wÜ+Û¥j°9I6…ªãë4êâ~ðiÅ5×Ãs¥ºVé†Vì—éç@›Œ”`é d\ò3:ê OÊ~¨ìÅ`F Ùó 3Ö:›lh.¦–§y—Ò^ç~ÈçÍ Î£|>ÑÑ _º|Êg—ñ9á½rUv³¬®yC×ÂòÏV´˜Ph…ϧG1ÈÔ}q½qI—ÅÀïZ›S¬ ÄrG—s²¾ 37¹ÏÇY^#óÏüúšvÙQùÞ·áf+=*^Ó*ëB{ÍêÕ5Ï„YÅgˆëÝÉÐRBM -FJp­7ž]– òÆI0SG•@3ÍÖnÛ¿ ›3Gy½ÞËâx’t5w^4/‹Ç‹üÄZ„›­ó¤ìõÌ_ò'@Õ1.¹b™°>ŸtŽ55½ËkÚÍ’`î:¿¸ÉŠý0Y‡´V=˱¼ïÔù¸Wo”»¿c0zÒÃ×òà×Ô¼fÀ8g&ùœ™mð÷Æù2výïݯ±‹}É5×·/Ÿ|žØ…£Mî+lNM  Ž ¯õ~Õ$+Se¯+š°Ÿ;÷êeqDgæx@óóéÑݼVfte>÷Z¬Ñÿ;^çèÂîÍóu§Œç\„éƒâx¡Ä{u^¶¼@~|©T•KÎýiñðV™*Ýoh ¡&ÐúÚêr½”à'Ñ¥9[ödá|¨.÷uÍ*å$ Û•èn”uÎþèÊ@±fæe쯼£y“×ë"ÜœÖÝ-=/™][vßÞ¯Uø9ëkVó¨¤[3¦ô›ÁpMÝÊ6¯`·„šÀ^¦Ëv¯í«q…çu–;©°ŽfÔòRmÝ¢;ó¸Î®æ1à.ŽI²ߌ¶ô÷Æù»¥Ÿ—u^•»6ãK&]›»q'oè¶”õ5k9«ð¼€xw÷!Ö Ô|(l².Íl?Q¦ò:¾{ójåÀ7we=Wª­«Ý;óǸøÇ˜jnÍ]×ltGm;¬Š/9.ã :ÝXº6wêQÙ’y}M¯E…ó=:áWœ×º5¯g¶ @‹5ËSžtÈýl¼áóÌ»¥ª®£Éö¬Û¡U„™8ëHæªãžqþÕZoó‡®MÝmÛuV¶aËg—ñZ|Pª?wèÖüÙíºËU°=BM`FJð“*]šºÖ*œ[B4m×bgó:Ý™1Õ<~þÏdY ®·ëG,ÖÛTýC¹k3~ÞéÛ}m&?käV»§[ÓçX€.j> ¶ÃxÃçIéE–ºÿðq#ÖgÜîkPkgób@¯ItgêÞfÙu]yûÚ$*î—y ÞJ®ìþÔ«·5±¾æÊ°íÆ³Ë ï|þЭù3´„P؇aš¯wåCòœ.ÍÍ}x÷æÕÊYFþ«T[Óþ){ ®ú|zt·8.òk¢s–UF-øox^ÜCfu¦×C„A¿$Ó ·åIñz¬\gµxo×à½R­¤[s=6¤h¡&°sàÅzWÅßrÿžLÓoø<%¡G(ÓV,¦›_Týy# ÿ¥ýußq@×÷ÅÒ±KúYÕ.­|] ‹ãµ—q+&^‹8|‰ºÙç Ýš?³a@ 5½ºñìrRñm÷Qq¼Hóޝ>y«Ksc/*j1` m§öê›]éδó<•äÍzÚÖÉû¤8.ªvmæéèñ{˜ŽÞ¼Òõ5ó{ìX©VªÒ­iƒ½ïÝ)Û° €íj­»7ÇÅ1(þço©?]-eSº ÄV«2íÞ/ÊË•ukÎ’ej}`û„š@kÝxvù)º+r÷æ/ùÃô! HÆ%Ï[¯iµ*ÓÎ'É&4MŠõo+ïn^ £K6^#;ÎsÈAtOóTùRy:úÝ$ j’i軹æÔï‡ó®l³*¶ë__¾|Q 3>ŸEç]|€F0»b0¿©ç˽¬°žcÔW(¼¹Å†@“*?|eº¹Í€ØÔ¨Ãÿíu§£Çõ5L‚Í&T†þB©–z_N­8_§É¦K?Ò© °'BMà äîÍåÁJ›7šTøp,»^ úÇ«~ wx™×L­‡56Š×åOç.›Ê_ìtýŽëàâw©ÔՖ׎îNmî^…ÝÐÇép7!lBÙ«%ªU/¶D¨ ”èÀˆÁJÞ\è·4ߨ¤M"(*䎽’KV­UgÚyc"XT؈éë:·Åk¬=W6šºÎèwyRÜ—¦U¦§æ ¸†I°ÙÓÐ7¼óæËÎÕxŸ s§ÂùÀ5ƒuãÙåyqD×ÞŠãiK>€ÇÓªP.Y·½z×z_ ¤Î˲I§à¦"PVÙè$OQœ&±4ëкžbSš‹*ëlæën˜LïÝT•ièqï²ýòú•]‡ÖÖüž)è{ Ô^Þ\è,woþš1ûZ»l\òüÈ+¶^mî?|A€Íi6AJÕ@3ëgÒ¨|â;ñ;Móò+ÅõWÃ$pÛÔ½ õ>IÖ2]÷óÈDí|~Ø7¡&Ð+Ñ™QñÁsæ› íršßÛ¼AÁªÁü=¯Òµ^ä©™e,Ö÷:‚”Šf\CÿKºb Ô×ËŸ56ŠŸlnf²jêž91V¦kÝÎ÷úeçgÔî\™þq§J76Íj½”»7cs¡øúKq¼LÛï8˜ôx0¿‰X6`å4·b ƒRÓö×÷:(¥ŠAnt6ý¡d4-‡O}˜ÂMªü `sc$K>Äû‹uL¯W6}¬D>Çì“Pè½b@sQ'ÅêèÞÜÆZfcσùAñðÈ«qý ©ds A²óè&êš“âá¿JÆ–D Ù—îßG‚ÍyR¡ƒÎ{Èõî÷ýáŠss–¬ÿúã= €j\‘»7ãüQq¼HÍm.T¶ þHõ¯õ>ï²ZV[Ó ×S7м³M} –"ؼ¨¸3z\§‚ÍõMJÞû§ÅÃ[eZëó‰ ƒ¾¹m :Àn 5®àÌŠcœ7úmÃÁÎß©|ê¹.‘ëW=™×!µóöz*šŸOnGl$ÐdkrÐÇM§âwž 6·_ç k™z¾Þ£â=`°â¼ŒY(•é#%Ø¡&@‰˜6^1¥è?Åñtïçy3‚eƒùø¬ÓðgºMK~f¢Lk×¶tàfñ¯Îl`s7ÎJ6 š¥ù ê_ŸÞ‹¿1`‡„šåÍ…Îr÷æ¯y`Yes¡²©YºC~u¯ú›­M IzþûÇuvQeêª`sm¥›å÷ë¿•ªöõ9Q¢˜‚°CBM€5Äú[Åòi¾¹Ð²S?ÄFDËþžO¹,s–7 XV·Ü„Áõ}hÒ6ºÕÿ_ÒL›[õ$o.·ì½=fUŒ•éçs³x_­8ãýÚš¤ßèÖØ¡&Àr÷fl.ƒÐ_Šãeú¾ËC—f}W¨›Íê‹à}XöCMö`¤ÿˆûZ`óƒ’Õ6)y_÷kDÖ¿N'Jô¡&ÀŽ5™ÅqR E÷ft-œ/ûùÜmèƒïÏ¢KsÕ¤ƒdÓšº¾š«êšìZ¾žï©Äw*›iþE…`³ž{y“¹UÆÊôsÝlTÙUÁ4G¨ °¹{óxÕAihê6üÞÇb`T6˜œ(S-Ñùz,Ф¥FJp­JÁf¾®‡I°Y×YÙ{xñð^™~R6»Äûó÷ŸñØ2¡&@{}4^õdî®ÑÕU]šÃUë“&{4R‚¥"Øœ”튞ƒÍQ²ÁMwòZ®k¿¹^¯5Q"÷6€]jìA¤ÞR‰ïD—fÙ€èL™ê ªŠš^¬ú&{¼F'Óm•X)®Ëi…`3®ó¡rÕ2^õdl˜tkþèf… ƒÔ,_»e×-›jìAtÖÇ øÇ_Ó|[6å]š£$x«ã÷¼ÆY™sueOFJPI`ówåªìvQÓñ&ïK=U6­z¢D•kÀ†„š{T B§yÛAŒöu]´*]š—Õ½®PÏèÒŒŸ1ŸËÝ•¨,‚ÍÒ/)òuÿB¹*;YëÖ¼ÖƒUåóÔµsBM€-j´@îÜœGl ñKq¼ìÙ `¼êÉÜ¥išj5ïsP¾R4í"ϾŒ” ¶Øµ{Ráý$•«’X·´l}ë±2U¿~ó¯çJôÕP ¶K¨ Ð21…°8NŠ#ºG¢{óлDti6XËT¡3$¯‰&ÐdŸl”¶žG÷>>«X_;¢W<ukÖ6*y~¢D_ÝÌk°%BM€ËÝ›ÃâÒ|JáÇü5Ç«žÔ¥YYtöç.™¥r ù‡r±/Å5=tMoäIÙÎÝù>pœL®B·f}·‹÷’»+οé~^YÇP ¶G¨ б£hL)Ì› ýVoäWÓ¥Ùœ“ ;Ç ÔòìÛH 6öG‡W¾o$kúU¾êÖ¬_³’çMAŸs l‘P cbGëâˆÉÿ)ާ©ÛÝãUOêÒ¬ìeY8üùô(ì10¿©\ìKŽ ò›q^ÔónÉûE\ó6*§[³>» Ws»¸NʰBM€ŽÊ› åîÍ_Ó|cˆ.M5Ô¥ÙŒ±ëªhÒ"ÇÎÃÆD'«: ó{EÜGu–Ó­Yóü+Þ[ŽWœw1sÀº®ßî{lPàD7NÞñzæ› ua ±r´.ÍJþ®8XŠZßQ.ZÀAÍŠëºÊ4߸OXãpµ*ÝšeúÎH½**Àv5HîތͅbJâ/©½Ý›W쌽¢åʼnÞRŸObn§sö.O•®7ï^ÙŽèyã ‘R•*ëÖŒ÷-áð7òL€e¬«™ë¤Û!Ô8P1õ+º7‹#ѽ٦isg«véÖ¥YI¬£¹rÀ˜7ú¯RÑ#%ØšØý¸ä=aš¬¯YÆÚšõ­š‚>K¦ />× U yBM€ÈÝ›ñú¨8^¦ýwš”íÀmиZu4¡-FJ°U“² I¬¯YÉÊ{kîÖü[™þaàfêÀ„š=]†åÍ…~+Ž·{øÏx]Ò¥9Lº4W‰Áô¨ÂÏE§ Yh…Ü}í|Ü®¨ïyÙÆAùþ!”[QÇ|¾®r¦Lÿ0½š¡4O¨ ÐS1u¹8¢s º7Ÿ¦ÝuoŽ7|¾ïÆyWÙ¥ŠfÔðžRÑ"#%؉;e÷Ð<%Øë±ÙûPó{¦ W¸6+|á@MBM€žËÝ›g¹{ó×4ß\h[Þ¯ÚØ&o$"Œ[]¿•ƒéϧGÃâá¹RÑyJ´ëzwª¬¯Ýso•j©Û«jxãÙå§-¿Wv)èÕ • YBM®t§±¹PñÿIóÍ…šî®(ën9ñ*,U:íænÐeÿžxîRÅ—zZ¡K3º¯(ÕÁˆ/fiR~JßÂÊ‹Ü)Ö•{HüÖÉݽ÷yƒ¸U¯MÜ×ÿTª¥~YµÜGqÏ&]È ¿çM”ÜVÜÓ‹óé®S 9ÿVVɺQ”Œò?¯3ˆ+ëj©öRU¦GýšÝÝrqMóãìÆ³Ë‹CøÅrh&ÐÜ{EýÇyÇóe÷÷ØXè­{ÇR'%ïM“$Ô\8N«g Ä—nOz^£¯ëj®Ú,€zL? ²âƒø$wþÄæB/S½Í…&ü¬Ê´óA2²k¯iI±A×/7ž]Þ*ŽaqŒ‹ãüPÍläåÞ«ç¹Û¾ì52 ýzVmî’;Õn®l úD‰¾*@s„šÔ–7:ÉÓÉKåN¼^Õ™;@oªìµÎVm®te°¨~íSÉ_¤o!æqqœX€ùãu‡Àý›”ÜÏãÞ¤[ó›XqU—Psn¸âz¶®¦NM€ 5Ø%]šß+ëÒŒ¡¸Y1Ýü8O¥còéqyªÒ­YßhÙ¦ ÿcXòü´çõ¹i³ €Í5Ø%¡æ÷ª¬¥I3¢Ëì7ÓÍû7¯âÚù5élRÙµ¡[³Þû™nÍ”îXW³”nM€ 5Ø SÏòºB—æ=ejD¬Ã8¼ñìRÈp@Šëgšæ`vGo†nÍzLA¯f¸â¹©ò56!Ô`Wti~o¼áóT—ÝÍT^gs˜›»º/ 5¿7\öDž‚þ^‰–‡vÖÕ\}PN¨ À®5¿‰.ÍÙ²'?ŸÅ P—fuNóMëg°¼ÎæÝüz³™*ÝšêüͨäyÝšå¡]ß¿pÒ© °¡&»bêù7eÝN'J´±×7ž]ŽšýñîÍ«Q¸5a¼áó}r§d£¡fùtÓž×çf^ž€55غâ».ÍoÞç)³×ú|zäGÊ´‘¯¦2ô`³eÝš³dgï«–¾¿÷¡¨ÕǾ(¯½Œ¥Atk¬M¨ À^}=4)y~¬Dhö\6W‰”u‹[[ó›aÉóº5W¯«¡æßÎ!Ö!ÔÀöÝùX à&Ëžü|zSÐÀëhòU¾ÎžªÄÚ¬šVwžÿ¨LÿÔjÕôá©YW³ÄÀ)°¡&[U ö¢Cã¶J|5)y>º£n*ÓZš|çÝ›WÑMh*úúÆ>ß'ÃeO÷%šåÓ«§=¯ÏÀ)°¡&Û¦óð›²)›#%Z‹@“kYcs³{wIb„u+S¥÷¹¾¯Az;¯½Ì´çõ¹çXP€m*ÁW¯ß½yµt'îbÀ7J:Z×ñ^ É*‚͵E×øhE]ã~¦ ±ÚûÜT‰Vvkö~³ <«€š„šlóCztùè@˜›”$ÀTƒÍ*Q› ƒª¹]JM•håfA÷}ÖS >¡&Û4T‚¯>æ5®•§å ë‰i¯ÃÏ.?)5îG‚Íz"¬[zÏ;W«iÉû]qŸ²Ã·Í‚ÊèÔXƒP€}búbRòü‰Õ"Ф¶Ü 6JÂ¥ºFÞß¼ßÍõ}ª~Yhgtjj°M¦Wô”¨–“ÜùµäÎB÷¥z•l4Q¢¯†%ÏO{^Ÿ›6 Zià¨O¨ ÀVƒàø€nã›”Þ¿{ój¶ìɼAÐMeªìåg—e`]y)ˆ*QËhE=£ö­¥›ÖÕ,µª>³ž×Æ4kj°-C%øjRò¼®±ê>Üxviª>{÷æÕ8 âêmxŸë‹¥÷óâÞ5K6ÃYµYPÔ§×KC”tDp ¡&Û2T‚¯–®£–§â=P¢Jb°+¦I£$dªêΪ.Äwo^'k•Vyß»PõYÁfA5 5Ø×à¥Þæ©™ËéªåN'hÄ•ƒ¨x –<®D¥¡Ô´çõ¨ÏFç?jи<…Êzšåƒü‘Uëh Lhœõ5k)ûÆ5j]Í2·?Ÿ­šb=ëy}L?¨I¨ À6 •`õ ?O=¿£D¥bzðXØ–¼¾æ•(uûþÃÇÇ+êh zÉûßg—Êc³ Ÿš#ÔÀóí(›znÛjbÚù'e`Ûç™T¢[só÷¿÷=¯ÏªµY§Nêj°ÓAK” î­§Y.¦ä²uïÞ¼Š:ÓÐË 57ÿëû=mPò|Ÿ7ïºçò¨G¨ €æÛQ6õÜš£«Å4Ö±2°CgÉnèenV˜‚Þw1M°ây;|¯6ësqòšäT$Ô éäº4Sú`×ó˜vÎ.Ù ½²aÉóo•hep'Ô\mª>T%Ô`×Þ>˜”ªBi¨©FËåõæVjФ,ß$ãóéÑ0•o’ÐWÑ¥y¦ @BMšÔ÷õ4?¼{ójÕέC§ÈRÑ¥ùI€*„š4©ï¡æ´äù¡SäZº4€Z„š4I¨¹Ú=§ÈµÎuiu5hÄý‡o%ëE^,{"¯§ÉõÆJÔ!Ô )}ïÒüøîͫيç‡N‘k½¶ã9P—P€¦ô=Ô¼(y~è¹ÖD €º„š4åVÏÿiÉóÖÓüÙÇÏ.§ÊÔ%Ô )Þÿþ«ÖÓ¼ëô¸–Ï€µ5hÊ Ï¿ü»7¯¦+žj^o¢À:„š4åv÷%Ï?y{ãÙå'eÖ!Ô`c÷>ô¼e› œ%?™(°.¡&Môü÷Ÿ•ŸÝU IBMàªOJ@ïÞ¼šªX—þ?{÷“ãÄÕþ»~_=SÏ p¯ àHÌ!Ƙãt$æ˜yKéŒÄŒ¤™#Ŭ Í 0+xA½€ç­»}L»¡íª²Ëvý¹.©ÔI I¸]|>¾Ï9ÔJ¨ |óþík;\CyºD¨ ›¹[ðúT‰vC¨ ìÚP €: 5€Vj$Ÿ<¨ì„k ¨•PàÚ`Ík3uá{Ÿ<:gœCÀþ 5Ê™õøÏ>(xý£ÓÃ9³å9P‰P€m}Q ÜW NBMØÜ`Ík3å¹vyzt¬ @]„šÀ÷>(”6XõŸfÊS®VU 5®é$»Ýƒ‚×/”×°OBM€k÷V½pçŧ©ò¬Ôç55‡¯ÛDéšP¨PvÇ&Jׄš@m„š*ØäeÖãÒ ^Ÿ9{¾¹ŸŸG÷”¨ƒPø^Ÿ×GÔI¶Úº0jÖãºÜ/x}æÔ¹a¨@„šÀ÷ú<]¶¨‹ÌÎðüàá“çƒ5/ÛDé&_µjP†éç« Ö¼fMÍ›†JÔA¨ pÍzÔæýÛ׳ž×f]à«Só¦JÔA¨ |oÖã?ûOj³’ÀwƒÚÜyñ):5¿*ѵËӣǪlK¨ |o¦js‹¢µû¼Þè°àuÝšÕêPH¨ °äá“ç:©ªèœ™)Ñ :5€­ 5n²!ÎíŠÖBìs7âOjSÉýËÓ#» [j7¼ûzª +Í”`¥^ïòýðÉs›U3T`BM€›Jp»‚wjSÍH €m5n¬zá΋OÓž×fÝÚ‘_œ7+Ï›¨Íg—Ö ?]ž ”Ø”P¸ÍG%à+»-[`ô Œ”Ø”P¸MŸ»î†¯÷¹ãÎÎð«m|#ÔüÑH €M 5ª™õøÏ>,xýCks÷á“çƒ5¯O]:?ˆ]ЇÊlB¨ ÜfÚã?{QÇ]Ÿ»Xï©ÍÆçŽNÍ۔؄P঻¯÷9œúImÖZj¦Í‚¬Uû£§—§G–5*j·éuÇÝÃ'ÏÕæv;V÷=Ô¼®[óv'JT%ÔnÓ÷ðåžÚ¬4XóÚ¬çµyPðúÔ­åVC%ªjüH§æjÃU/¼ûº÷ˆŸ<®yyêÒº!6–úå΋OC¥ªj?xÿöõ´ç%XÙ©yçÅ'šëõ}ÝÈuëjÎòŸÝa²7ùqaf~L•Ø„PàGE; íqm¯Ïz~î ^Ÿö´.æ¾ÊÿÞyñi”^€ýG €"„¸ßÓ?{ÑnÌÑ­ù §µyP¢6z|Ý ^ŸæÇÓÕ#:wÏî¼ø4qKê¤SXeÖã?û±Ú¬V°ú´ç×Í݇Ož¯;Î{R‡wÙ|½Ìc&° BM`•YÿìwÕf­cµYëñªî¼øMuuÝÑX–áÏl¾^æcëe»$ÔV™õù_°‹uß7 Zj¾û:Λ¯=¯Ï°àõ®ukÆR¿çÇà΋O'ÖËöA¨ ¬ò¥çþ{j³Ò°àõ¾‡¾>y¾îüéJ¨ù!?žÝyñ)Â̳ԅ °6 VѸ"|Šiµ—§G}¯Í:Ó¬¿)- ל?ùùݬw[úg{“Í7ÿéû=‚ŽÊ¯ÏaVüåM¯å×ÿxÕkŸ<ä?F=/ÑôýÛ×Ó55:ÉŠ7%쬼6ã5×_|ÆxÜãsçÜó <¡&°Ê¬çþAÁë}Þþnl´fš±ãóÙºŽÌx­M» G{–ÓËé²üÞv/]ŸwUc¥¯G`÷[ÏkëÓ5¯ÿÑóúŒ žŸ/{\›©[ @y¦Ÿ·Jk#öÙ àõ¾×g]·¦P³;ëjFxÿ,›¯—9hÒãL Y¤èþõX‰V? ÖìÍò=¯¥\*jë|îñŸ½húô´ççFÑfAŸ{^ŸûùÀueî¼ø¡@“7TŠN¬_Òz™ëeÒiÚëo*Qhe¨™¦žßï{ÖM=ÏŠ—péºYÁëƒ>ÇÔs€j„šÀ6<;- ÎÔævÂ×}(/^S®‰Ýš±^æQ>¨ÆÚ±ÞBzf¢…>tlëÒ,žžß÷P³èóAŸ×äþìò¨F¨ ¬3ëùŸ 6ʦ.ŸÂÁ}SBÍD½ÊÿÞyñidŠ9}tyz4Îü¤…&¯”¨0´ªÏÊëpàs7U5®6xèb»Ú,h]°9uù4~ z¬kö,M1›bN_¥{ÙK•(¥hê¹`xÍó/¯QlDÕ÷éùë>[Ô€*„šÀ:}ŸB\ÔØ÷iBÃU/¼ûÚôó¹Ñ¦Á½ËæëeÇz™Þ"0í¼¤¦ž—2Ýä¹ÙŸú^Ÿ™Ë ¡&°Nß;·¯ } À.¡ÂAþdOÿÑúg6_/ó±Nc˜»<=:Ët–Ut¿)QöùýÛ×ë>; {^ëú\ P+¡&°RÁî}P4Ðíû‡Ï¢ÁÙÔUT8=j´ËŽßøwÿž1ÅüÄz™píòô(¾t°Ûyy¦ž+zîÙù|½úP…P(Òë)Öë©L¨y¿`Qÿ©ËçʨàõÉþ›Ñ ókZ/óÌz™pSºwMT¢´7÷‘‘•zî=èy}Š>7õ:ϯ1š 5"³žÿù…šë W½ Ó·ô`RãëM~üœŒ†i#"à;—§G±YK\wU£´¢ûÔH‰®¬|î=|ò|¨ï{}>;=ªjí‰uS‡gÙaw¯n‚¢Aˆu5³ìn>˜}\pmS§8_eóõ2G:= u4«ù¼nÞ4£á¾2eß¿}=ÛâyÙy_vz^ž™K ºÿ(àCÖZEë_E€ÔçédEƒ´óÌt»0ÊÖït>Ù NÑÕ1¶ƒ9”wyzt’ÿxª•ÝcN”èÊtËçe×}ÜòóVßÏn¡S(Ò÷®¯¢ ©ïB­«YΣ‡Ožß[õb &ËvýFWç/i½Ì‰ÒB9ù½j”ÿøC%*;[õBº¯=V¢õÏ»T'ëi®×÷PÓÚ×jÛ~í<›®záýÛ×QŸ¯Jte´ipjëe¥õ2§Ê å]ž\cÜ®hƒ 4­M:7Ýä9éóä7B_*jk½û:3}¥„šëu騰f®hŠæä–SÌc½ÌAZ/s¦ŒPM 4§™ðm“-ïk}ñ!}^Ze¨Dk7 :VŸ'6!Ô|Ð*V´YPßw¬,¬M]BWî¯Ûý6KoÒ߯ÚcÏÒóqA§°BZ#îAÍê>–Ø È†KsE_Þõ~Š~Á&AÞ—ç³ç<Àf„š@ÓžÿùËlÔgw º,tj^¼Ócc½ÌcëeÂvòûÒ½tÿhn¦hº¾.ÍŸ“>y>ÈìÿaËÏY]7s lF¨ ø°U¬h'S†Öt¡¤)y•èÊÓ4À½ÕŸ.¬—¹¹ØŒc]}éhƵ¤“p3Ÿ×}±’6¾±‹|ªUZ?ºòó±Gl´žç>À†„š@³¾`Ý´aFK Ú&JôÍH vr.B¬‹‚ͽè8f-ŠîÙº4¯ÍF*ÑÚõ4ï¹V}ÎØ”P(T°R_¬[WS}òIZ»n5Z RGM–ÍÇTã©`³ŸÒR™d±9`ÑÔó‘2•~¾=R¢µ5r¯6ã`cBM ¬¾O¼þÁ)²ºFijÞg%º¡›éˆ5ù.Ð\®±`³g–v9¿¯[9[·iI~]Ôø›¯ùóí|M­ÜëçÓóg[|¾ê¼XzÆi°¡&PÖ¬ç~›+¼Ù0èÚX ¶·"Ð\X›#•ê¾ËÓ£af—ó:”éÒ4õ¼üsM¨YüùhØóúXs` BM ®¥]w¿`’©S¤pŠÝD‰nœO#eØ\A ¹×_jÝm—§Gñþþ“ 4ëp^Ð¥9ÌLí¯òì*QaúÞQïKq€-5º>”öÁP}Ö»<=Z· º)è7évÚPÉ@sY›•ëä='º ÿR‰ÚŒ·|½oÖM=°Î4ý5ŸÒ’}ÿ2B¨ °¡&àCWyÃU/¤ÎSˆLA¯â§ÔõDš Oóß;µIS7ÄŽÉù÷“ßT£6æÏ²Ùšk/îW”é›wïß¾þ²æõ‘]­9z±Éç*Ÿ¯(C¨ ”’>¸÷½Ë®èÃ÷Ô™RjN”膱”·E ¹Ì… „Úmi‡s»J×çk¦K³*ëi+ú\4ì{î¼øä³#À„š@ÖÕ´®f‘»¦ Wò@·f95šß®ãüø7ÿ÷™þßBùý%Þ·3ÓzëvVb-M]š7™z^¬èsQߟfølI¨ Ôùá´†êSÈôjÆJ°^æ²?òï¹éèí°4ÝüÕ¨]™Ïݧn2õ|ËÏÖÓ¼bê9À–„š€_Õ W½`]ÍoLA¯F·æ; 4búò,ÿo˜&Ú`—§Gq}Ì2ÓÍwE—fuEÏ1÷ëiú\ °BM ´üÃéT¬«Y‚)èÕ•àG;4¿¯ùñ·®ÍæIÝ™ÑAøO¦£kW>ßyñ©èþãþtS„u¦ž+š•1T"¡&À¶„š@U}ïD,ZWÓÔê9ÝšÕD·æHnû4—éÚlôÅÈ,³»ù® ®Ãa¦K³êsÞ½|nZðú°ï²IÀö„š@U¾U^?ÝÔ¹§Ñeµæõ‰ý`¬s4]›Ó‚//Ø‘ü¾1ÈxïÿÎtgîÚ‡ü™Ut/v_ªþüòÅÈÜtÍun=ÍüúsŠlO¨ Ôö!µGŠ,ï”h}Þ¿}=óþ÷íÈ}Ð@sYt¦}Êÿ_Ʀ¤ïGšj>Žºg:÷å¤àZy/~ðyÝR<©ÓÛÔó,û˜žó›~ŽêM5j>„U7,x}ªD¥-%úA¯C´†šË^fó)é#§æî\žE}g©ÞìÇ›;/>=ÏÇÊTù¹%¬+÷9h¨D>+ÔA¨ T’6yùÚó2ÜM¬b]͹G1•´ N_•éæ¹•tOuUÍå÷ä¯üÿO¸Y³3ócõÍLEݧ¯Yq—æ8Óqx›IÁ=L¨Yð9(-M£X“@-„š€b›Y9p¹óâS Òíî]P§÷o_ÉÀ·yÙ·õh.‹€G¸YƒïÂLÁÙþäÏ©/×ã‰2ýà]‰)ÕÂùùîðÓ5¯•(ûœ>+°%¡&°‰©vcëÒà¹àõ3%ºÕ¤/ЖšËn„›ÖÜ,'­™y"Ì<¸2›Å}Y8Wý¹>R¢RŸu³j¨PØÅÖ>ø© ÌP£¹û—§GÃU/¦å >*Óäç×°ëÈš7ÎílÎE¸yf·ôÛ¥ÝÌ#$›åÇ™0óЊ¦Ç}ç©2ý º'kê׿)ÕsEáïP‰|F¨‹P¨¬`ZQŸ¬›‚n½Èk£‚×ukÞnÒå?\ËÍeÑÑö[6ß-}jjúµËÓ£x?¥úèü;¼W%6r?Þì~lºþµéš{B¬Gî‹ ¡&@m„šÀ¦>(]ÐKzœ6XE|»ûi³ŽÎéP ù½èÔŠ©é_òcR°¡XÌ\Æññ΋Oã‚ëò¤ƒ×d]ŠÂ^SªÓyV°îèP‰²¯%¾\ $¡&°©©XW³¤»™ ƒ6Õ¹Mƒ:h~ÎÇÞÓÚ›1=½¡‡ëº9F×eÜgÆÊt«µ¥k[÷áÜd›óÐçgªj>”mînAPa@m¼åë‰Г@ó{vÄôë¿SçyÚ`hÐõ?xZ†ã³KøàÊN;·DÀf÷à‘6L36tûü P+¡&°ëj~3\3 DÓôçŠ6 š©ÕJÒ´ÐVëi ù½eó †>¥.Θ¦~Òá¡&.áƒ*3íüq:/ùÑçüùt¾¦vµ»Q«uá¹)ús>?Ôè?Jl!B¨¾ïöÒ×NçjôͨàÃü™Z­4Ž¿‚µÊK ¹Rtq>MGÔ)~|Ìf©f_ ‚‚¦›äÇKoõA|ÍŠ§ßËÏYÁsiÝü¼Sôy©÷פõ4ê%Ô¶P±™ËñšÐ!>äÿáT¹òôòôhœ ŸÝöbtÃ䵌©ªÖ&ûQtøM²n² Ь짥Z½L5ŒqmÌ–î½ qïùòÝ¿£1Ah\ïùuÿ.ÓÍvãÊ$3í|•¯ÙšÀ7ÝÛFÊtã\ZÇ=@—&@íL?|8ÛÞhÝ€>›w^QP«äL‰VjÝ4tf­"쎗KÇßùñÏwGÓ®#ë ïß»üùsVp}šv¾Þ$md·JÔO <·vêùåé‘.MŸ›vB¨ l̺šß ‹FJôÍI‰Z}U¦•ÆmÙ`F ÉŸ\×ûeÚy=о+Ñ7¦ž—ãs3@Í„šÀ¶lî’e?Mº”®Ý½<=Z9ØN]1º5×Ô/kA!Ðä%Ø›Çi£º¢÷C—ájoÖ­aœ6ö²TJùë[¨i=M€jÛš*Áú즠ÿ L·&«Å4ôqSÿ皬àËŠýx•?s¦×h܃M;ßî|+Ñ7e¦ž Ð}^Ø ¡&°-]ˆs£‚×'JôÍOù g¸êÅÔóF™ÖzT5íJ É*¾ÜÙ‹yÇ×hÜ7ÆJµ¾ŽëBº43ã2•þ¨KÓçe€j[Iü­•f zUã-_'?§RˆØMJЭ¹;Ÿ³ráÑ$Ó5çùT¯IÁëB͹©ÔO¨ ÔA`WðÁ=u)YôÚƒËÓ£Áªuk–ë¹5"$hRáYáK°úEM ×Ñ̯Ó3×h¡ë6AL_^>U¦oL=/Y§ô9€š 5:L•àʨàõ‰Ý0ÞòuòÁu>Èò@ IY)tó%XýNŠ6 ɯÓ—~Sª­ŸK#%ºÁÔózêÀ†„š€kõù©`Cuºéi‰nÍwÊTèìPëk 4Ùä|U‚ZýyçŧIÁu÷Ù‰R*êÒŒû݉2Uºž…šsS%Ø ¡&°µ|Ý76€˜­z!u) éno9`b>µoïëk 4ÙDê(ü¬µx—×ó¤Äuzž™\ÇóèDoø˜¾|¼ÕåéÑH½¾Ý÷|© °#BM .>°Íu%L”膢nÍif-Ò2îïóÜh²%_Vl/¾H•¬µë´˜.Íꊞ9º4ç|™ °CBM .B͹û릧oëm”qÓ¸àuÉråçÞx×ÿ&5˜(ÁVâ2,±1PÜ;mjSßsH×aÉëøòô(ž”èÊT vG¨ Ô"í~)¬»üЗWÔ­ç–ÐËy™6Ù &uHaœkz3eÍaþãå*E—fuïÒÒC«Œ”è_úìPðÁ­~¦ W7Þòu–ί]l$ÐÄó¢†%v:?VßZŸ?º4«Ž)Ñ•ùõ:S€Ýjuš*Á•»ù råú4 µ±ÒMevB×ÙUòüËjÞ8H IÝÒR6 ªæY‰@3®ÕI&„+K—fu_óš­ ÍӳܳÂçb€½juÒrM·fuã-_çZl4­#Øh²CîƒåE Y¦^ç®ÕZŸ;º4«_·B`÷8€½jµIë+Ùåq.6mø [I™nÍWÊTZ[í2-ÐÄ€¿Jšùõ¿ær•öF—æFŠž+v=ŸûZÔY Àö„š@Ýtk–ø`o£Œ•Æ%S6¤*ïi :*h²ki­9_„­÷¦d ÷N;×û¼Ñ¥ù£é Æ[]žÅçžûÊäó0À¾5ºM•àÆ€h‰ý ¨[3Âà3eªVÓuk¼ÞF ‰#D 9*q½Æ¯y©\•ü¹.œÓ¥¹ñç–‘¹·ì“P¨U$Øgî~>0®z1¬N3eT4åçØXÝ*û«b°ç¦@“K]ˆº¯T%ÐüK¹*‰óm\ðkâË3]šßÕ-þ®|>§/$)Ó¼Vi34vL¨ ìÂD ¾)”ê:üу|p4,ø5:hª‹`³ìZgÎK<3§l yìZÝÈ8uý¯ªë 3•“ëÔZš×¦J°BM`|;}íiÁÔ1HÐ¥tË sÝ‹ù€4αÊT}Pš‚¬ ¾q^>S.öD0w­J 9ÍtVõ9¿¿9wrú²Ñç`€½jµ3ý+¨ià ~ÝšE{¨ê"™ 6i’´agF–ý)Ð<Üó8Õv˜™B}›¢ ‚¢n6ºæsÀž5]™(Á7Eá›®Û×½˜°."P¦Ê›4QßïƒÏî¼øTøE@s+ÌM·yî¸>7þœÓ'ïÒÖìPØßR_+Ú0(Â9S©o©ÛåéQÑ3^7}¿:Á&M|fôõZ~–6LZK ¹µQA}ãõÊôƒÏiÉ—[Ù Èç_€Cj;a zµÁT¦³u•“|ÀtoÍyÝceÚHÕ`óçL€ÌŽôt)ޏžšûñjÝôé´öµgÉ튺4GJtƒP`„šÀ.M”à›§iGÕUú¨ÕgeúÁÝ¢UÚôA€¾y}Ë›ÑQ<Ì›ìNŸ¦ Çu4hîÅç¬ÜôikBÞ~žNJÔŽ9SÏöL¨ ì’o«o¼>Q¢[=M›lS[V«l2!2;–âè×;ñg¦?ïZÍZœ¤®þU5Ž{ÚKeºýsܺڥ ýœ›>÷ŒPØSÐX¼$ºàV×fݹfÓ íT 6c€;̬Ë®õˆgâ±@soÞ­[²'çÜ6Ư”è¡&Àž 5]›(Á7wÓF·êéšreýtyzT ÇàËþ-ÎÏllR#ØÌaþ—o” ÏŒÒâz–™ž*ЬE|IxRPç¸ÙäævïÖ­CšfPØXiéú6õ`ÿ„šÀ® én*̱¢6%6 ²¶×v"@ù«L°™j¿ÎÎèÔ&…] Ë_å¶QÉ@3®«i&ÐÜú™Qbs ‰2­dƒ ŸwO¨ ì”)è7|.úЛxg™î·UÊlõ}§T[‹`ó¤ä5¡€ÑÜ.®‹_ó{û¸Ì/Næ_™@s[Ò&rëØhµyý¦«^¼<=ä?ž*Óõuž_ãBM€jûÐ÷õª"Ô}–ùQf`;qʬTvÓ Ûöþxøäy©sÑBÔ)…]XJ"®‡aÙ°#¿Þâùð—3 £‚ZÇýÊæ@›n)Ñ M€j>ìíNt\þòþíëãÔÍVv@?Íl²ÎÚZ¦iè\õxšþÏÓ4ͬ¨îq®g6lb×yKîÿ¥v8é ![=^­›vÞ‘ók—>¯ûÌ’–±ÔËM6›8¡&°s)dêË”àèŒPç(Ö\7}«ÀØ™³Òý|P5.8çLC¯Ol¢1-l¦ÚÇ`÷Y¦[–íLZüÿþ{…õ3ïåGŸ¦òÖãcÑŒˆ´´† n6ÿü1Ê,°ìsÙ//¨ŸP0@­éCm dó#¦˜Ÿ”èY+ukÚÉ{µ——§GÇ%^‚µzü”³´#s¡Ôå3ÌLGgó{`ÜCÛöÅDܳÎÿßKum-ípþ“w¼6£‚šÇ—3ceZék‰™%º4ûõù Ñ„šÀ^¤Î¹.L1Mü×´^æYêJ­‹× ÓÐk9Ó ;£_˜ŽÎ–Ú´tIL7?®0Ýüq&ЬÛïi}ߢç†.ÃÕÖò—§Gqÿ·¹R…Ï"ì–P0@Ý|ûs>€¦À¶vùà8>(ëÖ\í'ÓÐ÷.€Ø½ôúai:ú/™®Y6»6ý¼Yìn^jºyHý ×êT¸Ûy ’)ÕÚs¹èÞ>V¦›ç]ê*à@„šÀ>µ}!õøÀÿ*»^/sk(@¬gúaüöðÉó*ëlN³ùîèfªš4øÿ-:õ+ìnëgƯµ!PýÏæQQí3u…ŸÑÖÍ6ÉŸµ ëÒlÏý  „šÀÞ¤°‡ñÿü,ÿÿ¿l»^fº5·T¤AÚceª]l´qQaÍØ=Þ‡_3!3å5ñ˰8c3 aÙ.­¥õ3u Öï¤änç:cןÓEךµ4¬Ù¹2–P0@]-ºp~IëeNøÿ1vÚ¬Ufú4³¶ã.D×οi7áRÒ’ƒL×&%¤Ð°IN-º3K?ËÒ:´q²~fýÞ=ŸM;/÷Ù¬ Ks˜Ù1þ{çe—œ`w„šÀ¾Mþÿß¼Çz™Gi½ÌiõQ3ÝšëNCOk;Ú{7þˆiµ¦£ëÚ¤Š&|¶IwfL7û÷_™.Á]ˆçâ¨à=d¦—9·­¥Ù½Ï³½ Ôö*u¼ièà(Öˤõ2g ûÿ3 (1À¸<=* ÕF™mW¢*¦£+Ü]›ºhYçüÀ×mt*vg.¦›?õöíÌh]wáâ¹ ”‹èÒÜà3c~?˜*Àá 5C P›":÷ž¥)æã¤ƒÐ­YJLí¯ûi]Wë‚íNLGÿ'íî\JêÚ\쮓–Ûî_ô܈{î/ùÿq•i¦i9†3ÓÍwéUÑLŠô>ãÖÓ¥¹™3%h†ÿ÷¿ÿýO€½Ë³ì°»hF·è¤ ÓË˺<=eóiŒ¬÷KQEšªƒj·" ¥ ¹Ê½á$ ¢uWmîC,ŸÑ¥?PZ^âß=ý箂žü>2®xî²yg  íÀçwê”ýW© E8<^sÝEÿQ¦ü×zšÍ S8”Éþ›1Pi®GiŠù´MÓ­YÚy‰ièÖ×ܽèR›VÙD(ä×etÀ ²f.SÁáî{ºfã¼;Þ ÐŒó<þš»Ž?.x/îeÖ;,[K]šÜ#šÍ!ÔeŸŽÏæëež4p½Ì*L.v·èüJË Œ2ëkî㽈M„¦©‹­”4%=ÞŸŸ³ùnÓ°ëçFœgÑå=*»Pˆó:Îï8Ï3ÝÅûð¸Ä21ãÌÔÿ2¬¥Ùüϯ0ý8˜Ø-9›o.²ËAêYÚŒ¤3òÆÔ@£”ß‹6öÈÏÁQfJÿ¾|M×ãxƒ{E ®ã½T”¼÷umúyº÷EÞÿWó¿6¾ô:Éï眗–JØó==ur¯{O¢‹óo¥*>ïc-qŸ5ª×-¿W ” 9tj‡´«…ÖcúàÏ1¨ïZ ™Œ:¥ü‘Öá[)??&™·÷%‚Ÿ—Ÿ<¿HëÝ•KEäGüžg™%z+Mù¬kY‚8žE@Q5ÐÔyïJšƒL]-Ÿ#tiîýs+Ò© T-Ö†š´|zy):(J‹àâ¸hý«Ú2]€ûaò¸ÄTÒÛÞ¯Q”ßWÆ[u²S3ÝûâÏõÏ–÷„qZ£¸êy¢ÑùÒ)¶W±–ê°è^á>^þÐ¥¹ñçÌõ4šE§&phÛ~ë}Õm“@¿ÓZûh&ÖÖ,'B¯I‰_7̬¯¹o¿åÇ,”•D‡m”ëÜì™;/>M7|Ï—;3'UsšÖ¡™@s¿â¾<*hZž¢¼µ÷ÜËÓ£8ך?:h4P8´I¶Y˜tµ©Ci qßö1¸¶;t9òAÚÚ8 ˜‡Jµw1u÷¯´‘ÐqÕßü]¸i7û~=7ÊÚ6Ì\L5uuï_šïQ„p¿)U¹ÏN±œGÁ¯1ÅZ]ZÃôsààòI 4Ÿ–ø¥~ÆÚg}êÈ\éòôhÍ;‡¬éVÎ/©Ëkݹ8ÊltHÔŸl2%=½Ãl>-½ï]F~¾tïûTTƒü˜ld¦sé^:—„e‡óªhc±ôeÈÔs°üsp]¨™_[ž+î'ù½d¨ Í#Ô.-î¿n€610=Û4ìèðà>|¦C–Sj=¬4QqØ÷él›ë=ÝSâÚxÚÓv:ÔL÷¾ø‚ëÑ-/E0>)ú£àü‰s'º»e‡ó&?‡GïSÏñ>›v^CMók*ê_”êHþѳM¿ `·L?.u]~¸å¥˜Nú,M1 4oᵠ˹›ÀEçc„ï”ë ïSõ­·¹¸§¤Áûóã÷̺›]4Yúëx_åÇÑŸF›šq¾¥Íë^fÍCŠg™u£­£Y^|Nüš¨¹@óGŸšÍ¥Sh„´&Ößéo¯:mJ¬ûDfºØÞDðQp>êjЀ2›/9±Õ 2MM÷½Ý›ïÔL÷¾èÖŒ®Ìó-Ï8/Æ™@§ ®:êKl ÜÊUÚÚ©ü©Ks– óo­]~+@3 5ÆHSþ&ÖËÜhpSÆpåN%KS˜­YÚu…›1x¥£«×L/BÍž9Q£xîØé¹"ЖÜèoåªtï<^çŸ!,»²úœØõ ¹„šHbpþJTRfã ›P4s€¾u¸¹ôþŽò#B’.ué 5׿ïñž3™Mók~Þž»'×îÙºûeÉ·úªpf‡%Ôèˆ|`ƒ–§*QZt`ç–YÁ :4¦÷7ÏU¸™çu¬·Û±€S¨¹úZgÂÌ&zVôE…eAvs/È?;DMu+ßî¨è3‡%ÔèˆÔmaºt5±!ŰĎè£L°ÙT‹ÝÒk[ºb)àŒ0 ŠPóú½Œ ì$½ŸÂÌfú3mÐVô^N3á[U?¯›Îo–ÇZïòÏ• Ù„š’PÆÙ|ç^Êû\†%ÔÖk¾Ødì¬hM¾*ÒÚª1°¦£ _ô>ÔLïÛ8½w¾èið5›Ÿ«£ïç$3¡öÚæŸf™°•Â%j8<¡&@Ǥl6ø+³n–uk|Èæ›“ºÿÅis™ÅÑÔ®±Þ†š©«z”éèkÅuZæ<µÓùF w‘Ï?+¨ëšs³Ì—žP cL'ÛØïù æ¬Ä{’ 6Û4°÷묮©é·œq½-Ž˜¶Þ„®À^…š©+s”™bÞ&WK­‡kéÍŸgymÏÖ|Nˆef™.æUžåŸ&ÊÐ|BM€Ê,±ƒì#•¨ c³ŠÖŠ%ùµl,´æüˆ`sù8DÇ`çCÍtÆÔòQ¦+³×b™@ÓNç;ºþm,¸ÖçüsÀ@ÚA¨ ÐAº0¶òs> ¹(l 6Ûí]6Ÿž~¾ÿXê$Œc˜~Æ±Ë ®³¡f^Ë2‡û[û4÷ð +Ø(jû¯2­¤K E„še½¬Å”å¡`³7ïu›çû 8o9‡"`Xü Ãôsm>•ºS¡¦ ³[÷Ö¢¼Ü[·R¸“|þÙ j«»ùvº4ZF¨ Ða/[ ¾ùন›Èà»[ïùyz?w:E½ªÔµvoé}ÿ÷ß›íb“¤=ÿ™™Ý»¾š»õ9î 6ÚÊ«ü¹?V€öjt˜if[¹š&)Øì­ØAý*ä, bØ^š¢æ0³p×” 4Óypá^º±_×uœ[–¦ÔyZøe&Í"Ôè¸| 3μT‰” 6­ÿÖmŸÓûG£º8Û*}0LG„™v-ï¦*æ$³yͦÞå5~\ðY@}×Ó¥ ÐBBM€ŽKÝBƒ 6¹íœ˜¦ë*:9gJ²^êÄŒkd˜ÝxÝ'ÐÜ_ÓÎãšûG©Ö×P—&@û5zÀ€fkoòÁΨÄÀ\°ÙOÑÉy‘Žxÿ/úÞÍ™_ qÏY„˜ñÓ—*ý"ÐÜŸgEkèæŸf®Áµti´”P 'òAÍYþã7•ØX•`3˜:Ñúm9èŒcÖŵ9SfÃl^œû½'ÐÜŸy‡Ïþqf š¢óU—&@K 5zÂ4ôZ” 6mÄ*1uýK:?¾¤kòK“Ï¥Ý×—œßÜ¢J 9΄mÛÖúxÝò6 ,E—&@‹ 5zÄ4ôZ6Ù¥éç,a~~óþíëé¦ÿ¥raŽïÿú·ƒ ªšqýKɶò{^볂gþÔu\xÎêÒh1¡&@Ϙ†^ Á&Àµè@~\fÓ,f-ÊL;?Éü¡TkéÒh¹ÿS€Þ‰ðŸ•a+Oóã¤è¥Íb†iÀÐEq 4÷&º Gë~Aþ|¤g=ëëx¦ í&Ôè™4Íj¤[«l¾Q2 cfáô]fmNJÈñlº«Tk™vÐ~¦Ÿô”ièµ)5= êc i§_  ÞåǨd éySSÍóz?.x¶›v^ÌZš!Ôè±|ð:Xïq{U‚ÍqfÇ_ å÷¼÷o_—½çM2_æÔá*ˆ["§içñ\×¥¹žµ4:Âôs€~)A-b*úy~Ü+ú…ù€4RÏ” h©WÍÃ<¯KtÅF½šëYK C„š=vçŧèèø]%jñ(?¦%ƒÍxþšWmñ,}1SH Y«?󺟯ûiÚù¥*tbÚ9@w˜~@ †¦Cµ¹Ú8£Ì )ôç?¢ö:k€&‹/`¿ûzZâ¾_ìD'œ@³ÆgJÁ´sÏ’r>çÏæ2t‡NMÂãL×`]bÒiZÛl­|²ƒ4hh¢ÏÙWŸß‹jŸ?gÆj^î\Î?ëL” [L?`ypSS‰Ú\MÙÌRÓ2¿øá“çê4E•Îã œXóñ¾²Õæ]^ÿÇÏìaþã¥*å—²ÏbÚC§&ßäøc£S¡ëÓÿÉž¥‚|õÑ-ÒïÍi&ЬSLù_[ÿ´)ÝD©Jù Ðè&¡&ß³¾fýþJS ¥Ñ‡iP °Oqïÿ%¿•ùÅŸ<å?þͬçXûs¸ä:š‚ärN” ›„šÜpçŧYVÐ!ÂF^^žMÊü´†Zt?}P6`OëgNËüâ‡Ožó)[í^•XG3žÑ”ª”7ùçš eè&kj°jÐd}Ç݈à`˜6g*dM`ÞäÇI‰îÀÅ}i’Ùá|ʬ£9ÈDH§;¶XtÊ>oh¡&ëO1p²«jý"Ø•íyøäy r'±ÀRº‰®Ù2ëhN2ëhVñ¦ì&|´Ÿéç”–®b ;ÍtŒìZ¥[>y>ÌæÓÑï+°$‚³Qêî.{?eÖÏ܇_ò÷eZðÌ÷â/¥ªt¾Ê.å@û 5¨ÄÆA{ó!?WXgó^þcœÙD˜«ºPÜC"Ì|ªt;Wfc _"VWiã=ÚO¨ @e6Ú›«é‰ù mZö7ØDÜ7òã¤(4ûî¾qœî¦9ï^™"`Ž)ÔºïËû˜?+• _¬© @e©âJì\“ÿäÜqÙߦ™2;AE‡÷qÅ@s”Í;š»÷±(ÐLâ>.Ð¬Ææ@=¤S€]ž]ïo0œÍ»6geCêÚ<38†ÎÛ¤;3ºã×Û h?>góÀ¹hc ¸g[F¤šØh¤ ý£S€m Ó@Ý«´;zH]›1ïOåƒÎŠ®ìAÅ@3îÝv7ߟ²;2æ&µÕ¥ ÐS:5ØŠÍ âj*;¼Ú!:'¾P:©²³yºŒó/•o¯ÊìtîYº›ô˜P€­Ùý "ÐUÙD(4 ¢ûz\vgótíÛ è0Êìtnc Í|ÈŸCeè/¡&µHÓæþR‰½» 7*vm²y¸ñ@ù UbmÝÑû·¯/ªü¦üšé¹ãLàÞïÏù{U85ÚúÔû9ö](@ 5¨ fÓ®M A;ĺљyVñd¾À8”7ev:ÏŸ›ñþÀˆŽ”7*±wJþ¡ršÆXÊÒFB¯”+º±šq?Ž.6æþEGm™Íø5Íêâ‹<ëh S€ú嵩ôA{›tmÒ ÑnÈÐ ²ùF@U§š§kÙ=ø0"ЖÜéÜ’-›ù¥ê3€njP»Ô-k„NtÌžTYk3¤]Òϼwp0íjž®ßqf#°CŠeŽó÷nVðŒ´Óù϶ü¹6R‚P€l6f€Áæ¤êo|øäy Ç™õ6aŸ×kåu3Óõ:Ìækgº^ûþ ‹:kógã ›/ Ðܬƃª_ÖÐ]BMvF7JcÄ4Ö˜’>«ò›>yÁôI:¼‡°ÔDyV4eyÅ5¿×ºŒ‡Ëš¾ìÛίùsì\Xj°S‚ÍF‰ Î6˜’.܄݈e"ÆEÓ•W\—q=Ž]“ð,'%ž‡z 47ó.v=V– 5Ø9Áf£\­×·I·KÚLhœé ƒmmf3ëÞ6IÙ@sâÞ¹1Óθ•P€½ÈtÑañ·J4ÆÕÎÊù ñ¢êonÂÆ¶ 3M5oæ~˜vÀ­„šìM>°å?þR‰Fù3?Æ›tÀ7¡´ÃÌt­Åufù‡fù½Ì¦Nùs/Þ·?”kc¦°’P€½l6ÒÕF%ùÀq¼Éo^ 7cà)tkÛ†™£tmÙÕ¼aïkþžŽ<ïöòl2퀕„šì^cÅz›Ñµ9Ùä7ÛP®DSe· 3‡Ù<Ì| œ#ÐÜÓÎXK¨ ÀAð5ÚÇl¾Þæt“ßœÂÍx#ÜÔaF_\u<Çñþíë/^;ƒüÇ$f6UÙ@36ÇûW¹¶bÚ9…„šŒ`³ñb3¡ñ¦áfHÓgãÒÐUWÎe6ŒYs 2ëÓ6]•@3Õ7gÚ9¥58(Áf+Ôn³y¸)´¡+Þeó®Ìm®‹A&Ìlæ~ý²Íó€þjpp‚ÍÖ¨#Üdóp3SÓi›è ›dó0s¶åu0΄™m Ðܯ?óg̉2P†P€Fl¶ÊÖáfxøäy¬—ïû#%¥çüd›)æéœdÂÌ6)hÆûz‘ 4·ë9M; ,¡&!ØlºÂÍF™îMš¥–®Ì¥s|œ 3Û¤l £Å=ð'%ÛÚÏùóäB(K¨ @£6[骋-ŒN¶ý¥îÍ8„?J¬•]™ç5œÏÃÌZ²m$ÐÜ¿ßógÈ™2P…P€Æl¶ÖÕ.Ðùq¾íôÁ‡OžGX°˜žnçtv-¦½N²y˜¹õÔ×Ο8w[I ¹ògÆP¨J¨ ÀÞ¤Aà Ìô2Áf«Å´Ý踉îÍÙ¶ÿ²4uwp ¨Ë"È<ßvzy:OAü8³ŒB[ 4ó¼XG€M5Ø‹¥Aà ›o ØìIHÍÃÍiÿ2'[ª5È\:'OÒ9i£˜özVf#(fí~ÍŸçÊÀ&„šìÜ-ƒÀèÌ(lóç‚֋0)º7ÏëêÈY 8㱃:ëνIVc™Î¿E¸îÜk?æaü™?N”€M 5Ø©5ƒÀ*Áæqúw6Û/Þ÷QÝ9KS‡é§s¥ßçXÜ/â;¯cÌ¥ólίbL1ïæa|ÌŸÇÊÀ6„šìL‰A `³Ÿ~©k:ú*Ÿ<óe±“º¢û>¦ûC„˜ÓœOº2»çê –2»Ü§çÏĽ¤ÖÚ×±æ2ý&Ô`'*tµT 6Ù¼ûÊÀ²½>çïõ`ŸÿÁÔÅ9Ì®;9uØuàŸò …UÃt;—Zaç!f:7âþ2ÊlDÕeÍòŽ&µjP«-CÇ*Áfü:SAÛç¿umT—¥NÎãôó·éà>äÇEº—\ì*Ä\zÿK¸§t[,S0,³Îª@s7õ·Ž&ujP›šº(K›é¿¿î©Ê·Æ›ü½µá4­É¹|:w'‚¦‹tLËtÐÕðþ 2û'‚òÇ%Ía6_êD YëhP;¡&µ¨yZø«|à3.ùßilxZaçíR :ÙuGgüµµË‹)ä³tŸˆŸû0—Þ¿Av½¶ª ³_ÞäçÚ¨ä3%~Ý_JV;ëhP;¡&[ÛÑ:—¥»ú B[aïíËÃ'χù¸]‹¿îk—WtÄE7\–³8v±#yÉ÷f@2kdöÕ«üü{–ö=(ûE%T!Ô`+;Þ¸§J°iý³f;øA‡ϰø¹=ïeí ÙÝ–YºÞ¾ýä÷þ¡2° BM6¶§È¯6v(³¹L 6'™Ž¬&jÜAM‘º 勵¤caùµ…mѸ¦¾/•·ýý—}Nß ~Ãìz7{kŸJïpžžñܰ>sýâKc÷~vE¨ ÀFöh.T 6ãÿ+Öín4Gk6¢ù„˜”x^ŒÊšéy1ɬ±º _ÓsûB)Ø¡&•í9ÐÜh€¤ó¦QZ½A‡“¦“/ÖÅŒCˆÉ:Uv8?Äs¬Ožå÷ý‰2°KBM*9ð@0‚ÍÇe2›>4Bg7¢~© s±áRüµ51)ëÏ÷o_Ÿ”|6XªdÇïE~ß?QvM¨ @i êl)Ýb¡ƒëåA¬÷]æ ýµ€‰Ÿ 6òLØ-°7BMJiàT½Ò ¦” ‚zìá“çƒì:´\üŒC Dªn4Êtïï’Ø+¡&e‚M ßäÇIÙ”u6÷ÿþØ ¨ûÒ”ñ°üsÛÚ¡Héõ3Óýœÿx©l;cc öN¨ @Ñ@°é]Ž¥wFOžQ¦Sg_lÔRŸZ7€Cjðý`°íÓõ~Ëÿ Óôç(”ÈÆù_²ù´FêñÆî·@ â¾üìýÛ×£ M2ë&ïCìt>TI¨ Àò`°+ëÅ”òÅóBi3›A6ßM—íM”ØÒÕ&pUÖÏÌèä´!ÐîEØüØ—WšP€o¬[*Ä&@Ó4 ±éèµùlÇs`KU§›ÇX³Ì†@ûò¸ìÆ|°KBMº¼Cl¥u6CšŽþs6ŸZGu6Œ6€¿Vœn~’Y?sŸžùâ €¦jô\‡Íe±ÎfLG”ùÅ©%:Þ8C*›(°˜n~üþíëó²Ï®üˆ_û‡ÒíÍ«üùè@c5z¬'æBü#Ø–ùÅi:ú(ÿË_3›•eƒ `¯Þ¿}æ¬ä³ë8=»)Ý^ïïce I„š=Õ³@s!¦'þ“ÿÙKÌòA\tÅÚ&BÅ&JTË|üüþíëÒ÷ä´Nrßž]‡ö.}É"Ô衞šË^æ5˜V˜Ž>K›ýîìYÉA@fóéæe7Šéæ“ü/ÿʬŸ¹O±,ÀHh"¡&@Ï4¿yͧ£?.ûî¼ø›àüœyÜdƒ  ŒXÎã—÷o_ŸTØ (ºå#ü|ª|{Ϻ¡eEh*¡&@4Ý>WÜý"?b€ýJùn˜(Pà]~ Þ¿}=­ðÜZìn~_ùö*Âç‘@€&ûÿûßÿT š…>¦ÜE…šF¸9QÓ« $FN¡fxøäù8ÿñR%h«€¬ìÎæKϬøõ”ï ï×°ÊóA§&@4K‰ÚLSWP)º6¿™8}€ÞdóîÌ*f, 2Ëš‡ Р5tjtœ@s#±Óùã*ÓîR׿Yá±AÐÀ)Ó:5iʽ!›wgN+>¯âüýMùæY~OŸ(m S Ãš‹`rVq¡‹¥Ò¿ö¨V6¾·ØÙ|ZáyµØ H y8MZE¨ ÐQÍ­-6:Oµ,%íƒów=©“0°kÿ\egóô¼g6:4&­ó%èf­e©k3ðMËü†ü×ÍòS§çY‡êoìŒ dóîôñû·¯+unÛl­1š´’NM€ŽhîDtmþ“×ö¬b×flŒÑå„ ‚èJ?Þ ÐgóîLϪÃúS  @[Ù( Cš{›_œ¤À²Ê{Óµ„lÔP6 b÷ÂJ-Ý 'žSÝö#e ­tjt„@sob*ù&km.6z–€¶³AôSL5õþíëÁæ8ÓÙMZO¨ Ð̓ø¶Öf•ß”¦ùuaJúÄ)½³˜j>®øŒæÇ,ÓAÜM:Áôs€–h6‡ü¥ ‚ª¼wƒlÞñøÈ€˜º˜~ÎÄ®æ'tfÆó)ÎÇß”Ðýê¦S Åšëd^¤©•¥EšÑéùK Úbâ-‡^ˆ©æÏÞ¿}}¼A ÷¶Y&Ðl&"Ôh·a&ÐlŠØ!ýe>psXå7æƒÌi~Ä”ô6¬·M½ÝÐiWëfæG¬›9©ò£=?âñwº/Ò M:G¨ Ðbiîg*Ñ(2ÿ“ê'U6Jïç$»^oókCÿ|6‚n{“¥u3óãKÙß÷»Ô­þ)›w¯Ó ÷T  @ 5Z.a‚ÍæyšÍ7:©ø~~ÉÙ<\hš‰·:)ÖþùýÛ×£ü˜UùiªùEf-×&hÐY6 èˆ|Pƒ–¿T¢‘®6ÙØdÚvÚLhœÍCRƒc Ù(ˆŠ"ÌW]3séþ4Étf6•{6&ÔèÁfó˜ù1®ºKzzocZzLý>dxð‹õ4›O¨II±~oìh~¾Áý(–Ö8qž5ûy#Р넚#Øl¼X+3Âɳ˜j¾Áû;Ìæ›û7cƒ ·¯ù„š]ËÙ¼3s²Å3&îa6j.&½`MM€Ž±Æfã]í’ž)¨úþÆNéÃü/ÉæÓF÷ÅAÐnf>{ÿöõ`“@3¾PÉX7ó¯L Ùd¯šô…NM€ŽJ7L >ïj=»M§uï±só¿›t–²:5ùζ™ƒÌº™mñ,}± ½ Ôè°´ã4l¶A„›£MÖÛLïõ0Û]¸i*c‹5I¶ 3cÝÌèÐ~ª”­ Рw„š'Øl7Zz¿c:ƒµˆP³÷â ’É–aæI:<7ÚA  @/ 5z@°Ù:[m&”ÞóA6ïÜÜ6Ü´APË5{ëj)‹÷o_O·xVĹ#Ìl׳":üÏ•€>jôD꾉ÁîOªÑªë¶áæ¶]W¿çÿm›µˆP³w¢»ûìýÛ×[<FÙüKûÊÙªçÃ0¿?_(}%ÔèÁf«¯u„›±yÔ8«\Ø ¨e„š½¹'L²y˜9Ûâ™0Ê„™m}ÿšôžP gR¸ƒáGªÑÊìVáf:"ÜŒÎÍ¢M…lÔBBÍNûœî±fæ6÷€¸®ã<f¶ÏÇlhú² €ÞjôT>¨dvµm«ºÂÍA66"ä¼mjº ‚ZH¨ÙI±^ftenµv¢0³õš°D¨ Ðcù7‚±ßT¢µê 7£{w”Í»7a‡ ‚ZJ¨Ù©ë{’m9Å<]ãq}3af›ÅÚ©'M¸&Ôè¹4ØýK%Z­–p3Ãlp^Ø ¨„š­÷1]Ïç[N1_l׳0³Ý,·j°Xcq’m¶;6 üæÇ8Ï”¢Ÿ„š­_LÄÔò­v1O÷óE˜yâžÞ ¿û‚ n'Ô`1>Îæ;£wC„›kböP³Ub­ÌI¶eWfº‡²ùsk%wdzü>Q¸P€ïÅÑ-ô“jtÆUhb`ÜBÍÆ‹Ìãzœl»Vfºo³yWæ#¥íŒèÜ .”Vjðý9¦.F°ù@5:%‚”˜Â8±ÑD· 5i1½<‚ÌiM÷êQ63} Õ-±¦êH  Å„š¬0O2Ó»h®Xw³£„šò.›O-ŸÔt_dó¬—ÙMÑYÿØOPŽP€uè<Û½ÛhSÓ;F¨ypWAfVÃ:™K÷âa63}ÑÔ]v8€Š„š”LÇ]WPw}[ãO÷fû 5bAf,2Êæ]™÷•¸ÓljPfp;£Ç€ËÚmÝáL„›çJÑNBͽˆe¦YÍAfºß3]™}:bºùT) :¡&eÚ6ê—Ïéý>Ó½Ù.BÍ^ÓlbÖú§µ2gº2ûĆ@°%¡&Uß±ƒöo*Ñ»Áw¼ïç6°h>¡fíçþ¢³öð)­[aæ#¥î•船ŸÀv„šl:Ë:›ýó&›‡›¦§7”Ps+‹nÌ8jV¾tÿŒå<¢#ó±{h/½ÊïŸce€í 5Øf`Á–©’ýkÁM²ùú›¦O6ˆP³òy<]»èÆ\º_޲yéžÙßsmä !¨P€mêÖÙ$,Ößp6€Ps­½„˜éþ8Èæ!æ(³ÉZßY?v@¨ @ƒwël² à<0¡æ { 1Ó½p 2¹Éú™°#BMêÌÇ@~’Y#ŽkÎèy¨ù!?.Ò!æl÷¾ÅÔòa&Èä¦ßó{ß™2Àn5¨sp?Èæ!–=ßûšÎ › íXB͘һ0/Þ¿}=Ýã½.¾ÄfÖÈdõýîq~¯›*ìŽP€ºû±Îft¦ê ;ZL¨ @kØ!€è΀jÐ:vH`C¯òãLw&´ŸP€VJ]›gùñH5(ð1›wg^(tƒP€V»<=zœÍÃÍûªÀw¾fóu3Ï”ºåÿ”€6˪çùØ!ýOÕ`É»x>4 ›tjЗ§GnÆàÕFBý¤/½€ŽjÐ96è-@O5è¤ËÓ£{Ù¼kó©jtÞ‡l¾ÐL) „štÚåéÑ0›wmš’Ð=¦š@O 5è…ËÓ£Q6ïÜ4% ý¾¦{º©æÐSBMz#MIõ6_ª@kÅ®æ'¦š@¿ 5èËÓ£Aþc’™’Ð&³y˜9U @¨ @o¥õ6cúâOªÐX±næøÎ‹O¥„šô^Zosœ÷U 1¬› ¬$Ô€ìÆz›qØLà°Þdó©æÂLàVBMX’ÂÍè zª{g  ¡&Ü"m&4΄›ûð!›¯›9U   ¡&¬qyztœÍ;7í”P?;šj@ i§ôq&ܨƒÍ€­5 á&ÀV„™@-„š°á&@%ÂL VBMØ‚p`-a&°BM¨pàa&°SBM¨‘pè9a&°BMØnžäÇ#ÕzàC~œÝyñé\)€}jÀ]ž ²yçæSÕ:(ÂÌèÌœ*°OBM؃nFçæ(?îªÐro²y˜9S à„š°G—§G÷²ëpó¾Š-ò5?Îòc"ÌM¨ ryz4ÊæçOª4Xlþ³3¿(ÐBM8°´©Ð(³î&Ð,6ÿK¨ ‘ÖÝeóîMën‡SÌ#Ä´^&ÐhBMh SÓ=3Åh¡&4ØåéÑq67gº7ú½ËæŞJ´‰PZ íšÁ¦îM`[Ñ•9Éìb´˜PZF÷&°¡èÊœØøè¡&´”îM ke$Ô€H;§/º7ï«ôÚbóX+óB9€.j@Ç\žE°¹8LO‡þˆéåçw^|š(ÐuBM訥ééqͬ P ¡&°±4M}˜éâ„ï-wcNM+¨—P¨ÍÒfCÃÌZœôχ,™º1vK¨ ìÌÒTõ8©!æ4›‡˜S娡&°7—§GÃüÇrЩ““6b4„P8˜¥NÎÅOkrÒ±&æ4BL€fj±´ñÐ"ä´»:û]˜é°±@à 5€FKݜˇ “m}Ì®L]˜-$ÔZç– 3ësr›Eæ,`t†Pè„ËÓ£Aþ#Žaú‡®ÎþˆîËYv³s¦,Ý$Ô:mEØ©³³½¢ór–]˜³;/>]( @¿5€Þº<=æ?bs¢9™À³ b×ñ)¿d×ÓÆãˆÎË/Ê@jÜ"­Ûçà»#þÙO*´±˜&áä,‹ðò‹ŽKÊjlh)ø\t{f·üuÐÏÙ< Ì²ë°2,:.3ôP'¡&Àž,…  ßÿýª¶¬ŽÍ>¬ymÑ9¹öŸ )8$¡&Ð*ÿ§@›5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZE¨ ´ŠPh¡&Ð*BM U„š@«5€Vj­"ÔZåÿgï~rÚÈö¿Ÿ¾êéUò®÷  W€[bî€1΀qhéÎã̯Ôô˜A̘A“9R›\XA“ü@wyëÄLJ@p•«ì:UÏ#•œ¦±±¿U®òùøüjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYjYùQ `u{G;ÅÍË… Ò¶Šëb»[øï»ËóÓkÕúî‡OŸ>©|ÇÞÁÑ0ÌËô£aºÛÖŸÚMøzNÓí<½¾Šï?{òrq|gs\Ž‹›·}x­Å1ùƒ=Þûs”þÜVœ;†Ê༠xWœ_ÆÊ@ÛXýœ®|‰CÈ÷Ó¶­"µÙMÛÛ¢ÆóžœarÞ*° BM²•‚ÌQ˜™zc6/Î;ú*m±þq¢I±]8€uj’Af«Ä±¿Å­Ø/qá¡“ àÖ@¨I륅~bˆy™m÷Ë<àŒ=8ççÒujÒZ{G1È…4Ü™lÄœïãVìóâvb NBMZ%­\{dŽ‚^™]p·…áé½7X"7ozøGWtâoþ^üîq¯kf ò.úEg òþ¡´äd5(¶IñÏÿK'f·Ì‡§ßûù$M)m÷¦hÃëx 4"uWIë¡§&•N coêÞˆ+¨¿I…84}la!ZnRlƒU L(e}ôÔd#b˜™º\ÿš}‡¦ÿƒº¾õ€l¥¡é«˜„YGj¢§&k¥g&ˆÇŸűqf=7§JÐ{“b{®}¸fS-úµØ®Ÿ¹ßm…çó¶h·^mÖë²wLYˆEk&Ôd-„™,an–Ðs©MøÝvaÑ~|ìÇ× v–™„Yº4ÃΛcø9J Å“‰aæ,k>,ý$ü  ¶+ C¿o1ä5iD £Òjæa&ÕÄ…âjéÇJÀ†Ü?øï8 }©ÞšiÔê›gŠ„šÔ.…P·aÖãV'Qþ­8¦n-&ÀŒùÙÉ’÷}ì÷ÆJZ¡&µI+šÇ sã$½Vô¢N±«~œosbH:ëry~zQÜ|xðãÝçF¦aêÛ~ü.<¿ˆKj²²4Ô<~ûðç#oX¨Sìý{mŽ”€5‰mÐ‡ÃÆÇq‘Ç~9 OûàÇ7—ç§c¥¬P“•oÔý0jþF5X“Ø ø}\€ê© Ôåòüô.|; =¶M'OÜ屟T²^BM*I½3cì?‚¡ælF\€êÚBB4mÙaèé¿¿v^Üß°óš 5)m¡wæ+Õ`Ãæ ]˜k€†ÅÀò±aèŸÛ£i4áøÁÿ7ì¼!BM––zgN‚Þ™´O Ø­@c.ÏOo÷¡åâ0ôIø6/1º°!BM–’&¹]¥Uƒ–ŠŽ?Ó¢UP»ËóÓØæ¼zðãWiоÝ?ÿ½øý©ª5C¨É³Ò|ÿ-¶-Õ oŠcöÚ"B4d¾†þpоáÛ^ÔH¨É“úM5ÈLœ”ùÚptêöÄ0ô‡FiÕt"ÔäQi¸ù4X ˆ|͇£›¿€Z=1 }ΰó5jò½ƒ£Q˜šÛªAÄÕÑ'VG fu¢1ì|M„š|eïà(¾ñÞ«›Ó-q«©y6¨Ëåùi\Pù݃v¾&BM>KógNоU :j>ÏæŽRP‡ËóÓqqs“þÓ°ó5jÒ°Üø¦;T :.ö@ž¦) ±ù!v¾V?*A¿¥^k“`þLú#›ï‹c?~£6QV‘†¡ï«Äzé©Ùc +œ 4é£÷iÊ 3BÍžZ4-DŸ 6 ?BÍJó Nƒ@¢l^¤¹e€ 5{&šïƒ@½ ³„›¡f,šÀ·âܲ‚MÈ€ÕÏ{B  K™›ÃËóÓ;åhµ¸êø/ü¬ªI˜M×—Ósî-¡f4¡”lžÛH)Ú+uF™Öøx·ÅÍmNÏ¹Ï ?ï¸Øã,4¡Œ›b;Vh/¡f‡íí7*K»/¶‘¡çÐnBÍŽJæ4Xå–Í8—¦¹L å„š”Vož&”q,Ѐ<5;F  •üzy~:QȃP³{âšÛÊK;»\žŸŽ”ò"Ô숽ƒ£Qqs¨°´›b)äG¨Ùi¥ó÷*K›¯t~§¡fæ–#Ð€Ì 5ó7 V:‡2ö/ÏO¯•ò%ÔÌØÞÁQ\±ÙJç°¼×—ç§Se€¼ý¨yÚ;8Ú/nÞ¨D+},¶ÛôïØ#pÙaÎq*ôïA±m)e­~¿6ÌãR]†7?¯ed@÷ 5[,-ð2V‰Êâ@Ç]Ÿ3³.™‡›7a6O(ÐÿP‚V‹Ãh_(C%ïŠmG Y^ 7‹mXüó_a ·Ý|¥ó;{úAOÍ–J½åU¢´Ï=ö¬|½º_¤)Æ¡»@zHOÍö+Aiï.ÏOwšõJ ï Âl¡¥¶Ù·¿ „š-´wp4 *#‘þùòüt¬͈=!Ó"<¿„ö Imñ'è'¡fËX¨´a6w¦ÞzkæÛ„Ùœ¥›{åNìè'¡fûÄù ·”a)¿^žŸî›OqýR¯ØŸÃlÓu;Ó+úM¨Ù"©—æ±J<+.ó¯4×#{ÇÆ9LÃz{mÞ¤að@ 5Û%š/”á»æ«]_(E;¤^“ë˜k3ö ª8 Ôl ½4—C­ù3Û'-Ø{m~hèOÄ0ÛTÀgBÍöÐKóû>÷ÒjµWZ!}¿øç¯5?ô¼wî­*‘P³ôÒ|–@3#i®Ó¸ˆP]ÃÑõÎ 5ÛA/ͧ 43”BÈ8ýjŇz]<ÖDE€EBÍ ÓKó»šKÃчÅ?¯øgMà1BÍÍÓKóqÍŽ(öa<Æ_‡ÙܘËúPÜo¤zÀc„š›7R‚ŏhvDêq9 Ë›7ÞÀ÷57hïàhTÜl©ÄWš•æÙ„Yhù”ö?ð¡æf”àÛšX麻RX9 / í}&ð¡æ†ìÅ•¡wUâ+ï.ÏO/”¡Û:{ð¿öÚÀ2„š›cÅó¯Å…aÆÊÐi! wé?_ÿ=U`BÍ Ø;8zYܪÄßâ<Š#eèŸdÿ”XŠPs3ö•àëz˜G±¿Š}« @BÍÍ0ôü‹wæQ  ¡æš¥‚¶Uâ³óhP–PsýFJ T'Ô\?óiÎv@%BÍ5Ú;8Šæ–J„†P•Ps½ôÒœ)U 5×K¨‡ËóÓ©2P•PsMÒÐó*Ž•€U5×G/ÍÙâ@·ÊÀ*„šë3ìùë¿/¶‡«j®ÁÞÁÑN°êùÉåù飀U 5×£ïCÏõÒ 6BÍõè{¨©—&µj6lïàèeq³Ýó2è¥ @m„šÍöüõŸé¥ @„šÍöüõë¥ @­„šÍöøµ_]žŸ^;¨“P³AæÓ Guj6kØã×~y~:qP7¡f³†=~ív?Mj6k§Ç¯ÝA4B¨Ù¬Ýž¾î )B͆ìõ¹—¦¡ç4æG%hLŸC͉Ýät¾Þ;8R…< ”ˆ„š 6’{úºï =2ó›-3Ø;8+C¯ÜŸ¡'ÊËj6§¯¡¦¡ç«Ù*¶·ÊÐ+WÁh'(ÅœšÍéë"AS»€& 5°wp4èñË×S€F~ÞŒAO_÷ÍåùéÝÐ:'{GÚký31g/]%ÔlF_çÓœÚõ­´­ÚéÐ%†Ÿ7cÐÓ×mÕs'Ôl†žšÐ¡f3^öð5ß_žŸÞÚõ4M¨ÙŒ>ÎUbè9k!Ô¬ÙÞÁÑËž¾ô©½À:5ë××ù4oízÖA¨I]n•€ujÖoØÇ}y~:µëX¡&u¸WÖE¨I¬|ÀÚ5ë7ThŽP“:L•€ujYjÖïe_óÝÀº5ë·ÝÃ×l¡ ÖF¨ dE¨ dE¨ dE¨ dE¨ÉÊ.ÏO§ªÀº5€¬5€¬5€¬5YÙÞÁÑ@X¡&u(ë"Ô²"Ô²"Ô²"Ô¤/•€ujR‡%`]„šõ»RhŽP“: ”€ujR‡°.BMê`¡ ÖF¨Y¿i_ó¶ÝÀº5©ÅÞÁÑ@X¡fýîzúºv=ë Ô¬ßuO_÷Ю`„šÔe ¬ƒP³f—ç§Óž¾ô{€ujR+ °BÍfÜôñEï ízš&ÔlF_W@7€Æ 5›1íéëÚõ4M¨ÙŒ¾öÔÚõ4M¨ÙŒëž¾î{G† Ш• ×=~íÞ¿~€UÝû<¥ý|ŸP³—ç§w{GñÃ苾üa±8 €ŒÜ„þN’›A±m)=p]|ž*ÀÓ„š ~-¶Ý¾îW{G/c°ë2q\œ³¦ÊÐ~Åõe\ܼU ÀœšÍésyh÷Сfsn{üÚ÷í~šbøysú<Ù·P ]ÎB¿;ßôÕT è*¡fC.ÏO¯{¼XЋâµï5¸p$´ÂÄ<â@—~Þ¬>÷ÖÙý4A¨Ù¬i_ûçUÐÔM¨Ù¬iÏ_ÿÈ!@Ý„šÍºîùë?vP7¡fƒ.ÏO›—`kïàhèH NBÍæM{þúõÖ VBÍæ]ôüõǃê"ÔlØåùéTÂX ¨‹Ps=>ôüõê­ @]„šë1U½5¨‡Ps=.”@oMê!Ô\ƒËóÓÛâæF%ôÖ`uBÍõ™*Þš¬N¨¹>%PV'Ô\“ËóÓëâæ£J„ݽƒ£}e *¡æzY0hædïàè¥2P…Ps½&JðÙV±+U5×Èô¯¼Ý;8ÚQÊj®ß‰üm¢”%Ô\?ój~±½wp4VÊj®ÙåùémqóA%þf:Ÿ9€e 57CoÍõ°z¿ûRÜü·¸©ð¡æ\žŸNŠ›{•ø[\ }¢ ýÃìb‹!ÿaúÑ{Á&ð¡ææX0èk¯öŽŽ•¡?RïÜiÜ÷þ—`ø.¡ææL”à¿íí+C÷¥ù3§Å¶ýį6€' 57$-t¦ߘX0¦Û–4ç›À£„š›5Q‚o¼ê¬Rþ7íçe6€o57èòütZÜ\©Ä7âÂASÁf·ûs\ܼ¯pWÁ&ð¡ææM”àQqh²`³V8»ÂÃ6€¿ 57ìòütRÜ|T‰GÅ`s¢ ùÚ;8„ÇW8¯B° |&Ôl‡±<éÕÞÁÑDòSì·aqsž_¨ Á& Ôl½5Ÿu¸wpd(zFÒü™†å*C° ='Ôl±|×n0ÇfëÕ4æ2›ÐcBÍ–Ð[s)óŃJÑ> ÃÍ_­éO 6 §„ší2V‚gÅ`ózïàhG)Úca¸ùÖšÿ´`zH¨Ù"zk.-ÎÓ8fm^ì5[l±wæÛ >÷©—(ÐBÍö9V‚¥Ä`3†Y'J±Eíã±Z÷êæU]è½ ý!Ôl™ËóÓ¸ÈÊ•J,íMì)hžÍõI½3§Å? ͬn^ż÷®`z@¨ÙNc%(e>ÏæH)š•æÎŒ½3w[øô›ÐBͺî‘0óEG^š`:H¨ÙriÑ ßUbe1ÜŠaÝ­pó‹4gfœæà6t+Ì|¸ï›Ð!BÍ<Œ‹í£2Ôbnþ_Ÿ‡¥¯{?®fsf¾ Ý 3îwÁ&t„P3iÑ ‘JÔ.Kÿ+-(Ôùú¦^™±—êmñŸ„ÙJñ}"Ø€ŽøQ ò‡¡ïÅaèoT£vqA¡Ý4 ;ö^¼(ê}Ñ…–z¢î‡Y(¾mWÿl‹}|­'¡f^ÆaPm)E#bà{oî݇pÛ4õ–ÍBZ^Ü Ó&È||? 6 cBÍŒÄ`- “þS5÷wÀÿ£¨ûUq3MÛu[BÎÔs'mÃ0ëuÊrûW° @[Å$Ÿ”ŠÞŸoÆÊ@× 53“†¡¿ ³ÅnXcã"mŸë^샛â&†a·atÞ5Ž¥ðrq†Yù®©L° ™jf(~óš†Z¼9Û õŸñ&®R›~>}pŸéÃÉ— ÿý2ý,Ó 4I° jæ+†š1„ÑS¯]¶Â—òáPp½kÛI° ™ù‡äéòüô6ÌV´V76w”ÚO¨™±ËóÓ¸2÷;•€Z6 BÍÌ¥• ¯Tj!Ø€ 5»!ίùQ ‚Mh9¡f\žŸÞ…Y°y¯P Á&´˜P³#ÒªÍ#•€Ú¼ðž€vjvHZ8èµJ@-Ί÷Ô±2@û5;æòütRÜœ©¬$š#e€vjvP c›P@ZN¨ÙQ)”¹Q (E  jvÛ06aYMÈ„P³Ã.ÏOï‚`–!ЀŒ5;N° Ïh@f„š= Ø„' 4 CBÍžlÂ7š)¡f6áo¿ 4 _BÍžlBx]¼Ž•ò%Ôì!Á&=͉2@Þ„š=ƒÍbÛ)þy¦ôÀ}±ý"Ѐnjö\šWP°I—Å@sXëS¥€nj26_«§XÇøµR@w5ù, ËýW˜õjƒ.øf=4E¨Éß.ÏO/‚„è†ß‹ãy_  Ý$Ôä+i˜î°Ø®Tƒ ÅžÆq…óc¥€îjò´2ú°øç;Õ #Ãl¸ùD) Û„š<éòüt̳Iâü™;€~jò]ižÍ`8:íõ«ù3 _„š<ëòüôÖptZ(7ÿ¹86O”úE¨ÉÒÒpôŸÃ,L‚M2ÜzL¨I))DŠÃÑW 6 Îïú/ÃÍ ß„š”–VG?.þùKÐk“õ‰óºî¤y^€jRÙåùé4èµIóbï̸Ð0Î婢P“•,ôÚŒsmÞ¨5‹sg,,úQ ¨Ã|®Í½ƒ£pŽ‹í…ª°‚8­Á±¡æÀcôÔ¤V©GÝ ’Nu3€ïÐS“Ú¥U©÷ŽbÀ9)¶]Ua q¨ù±y3€ç5iL §†{GÃ0’.Üä1qUóqZx àYBM—ªnŽÂ,ÜÜR‚y3€Š„š¬Íåù餸™7{/†™ãt<”&Ôdí„›q3,½„™@-„šlÌB¸9,n‹í•ªtRœ3óÄ0s .BM6.͹9Ý;8„Ù°ôýb{¡2Ù; ³0óZ)€: 5i´Zúhïàèe˜›±÷æ¶Êd%1Ÿ„Y˜y§@„š´N Ã&a64}'ÌÂM½7Û-öʼ0ÄX¡&­–†.â¿ÓÂB1Ü4÷f;Ĺ2'afê• ¬P“l,,,4ž.à\¿›ð%ȼU`„šdçÁðôÅ€s Q¯Û}±M‹-+×#h¡&Y[ 8ãï ×€Ó"CÕÄÞ˜Ó0 1§Ê´ÍŸ>}R:)õâ.lBÎÇŹ1§Åç/ê ´P“ÞH!g\M}˜¶øï¾ W½0¯ç›ž˜@Ž„šôÚÞÁÑ ¸‰Û0ÌBÎøï.ôèŒáåmø`Þ¦•ä²'Ô„G,„1è|¹p¶µá§ï™”ñ6¿MÛµáã@× 5¡¢´(ÑÜ mÍÃЧÌCÉÇLþ}§§%ÀŒPÈÊ?”ȉPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈŠPÈÊJPÍÿþóÓNq󲨆éGñß; ¿rWl× ÿ=?ûç¿ÿºV½ïÖu^ÇAÚBúï—j©®@—Î}ósÞpáÇC×€ÇýðéÓ'UX®Á9L Ì¸í®øpW©q:-¤òŸkºfáåÖŠu ý‹®7ô‹ºÂ—Ðwnç[QÛÛ§ë®w­Š}6voÝ1µ“Î{u\SnÒ¹oš®+w*üÕµûÏ%ýcQ»AOê_çhÙóQ—IfÏÙy „šÏ7:SÃóECæ¾Øb°9)>€O{R×—©a·í†þÌÇ…º^w°†ñXÙÝàSø;˜=QZPï•ûçgôVGƒ…kÊVƒêC:÷]¨yé÷î¿úP·’ao+êRö9;ïtŸPàéÎã°þ#qã6ôˆh°Aëz¸æ?}•ê:íqC}5ž‡Èw0§ð"~hÞD€{î¼/žÃm±íw¨®/‹m’—‡x q_þ÷mjÑL+¶ÿ‹û:õr†¾_S ×”Wx ñšò¶ØnÓ }3®r.sþzT­2I#- „šá«ÐmSaæc Ñ?R7ȼ¶±wÒmØL˜ùMc5ÌÂÍ ³FÅ}ýßtü èë5ef_ä´á𩸅Ùu_Þ“é_õËÁcGñ£ât1'Ê@[5ÏYϽÛÐŽÐí¡Ø¾Î±‡ÍB¥ßBsó‘V{Luª7lKÅã÷¿©ç¦™¾\SbhÏ}o[øô¶Ó{r܃]1ZáÚsèœõÝÚ}h¡&Ð÷ÆglØýÚº-š÷°™dTׯy·v[^רV¯“54‚ƒ™~\SFahn·ü©¾íAõã ß¿Ë~Ó €6j}n|NB;{Ò<å0 |ÙòºÆFý¡ÝAñ¢7†£¯Å'¡z½8Ôò—â5êl,ÆÇJuý×’u}òµµuA¦6ˆoÜwÅ?ÿ_±½[ñávÓ"S‹6žûæ×”_ÒßxÎYñûãŽí—ãÌ·KâTÎ㬕PèƒØhÛªxߨ“fØdïÈTŠíÃ3ÏV5Òpþª½!ãkÝirž²ÔË2ÖõªâC¼Ð@[ªÎw)ùi…ZGoRO3hµÏ}Wk8÷ÅÇŽ£~ÿÞóH_Jti¿ŒÂòAóÇt}_ÖÈÂfK9´b<ë$ÔºÞø„ê+rŸ¥ž4¥JÁPÊûkÏT×ñ uÝ_c]‡%¯‹^ Ú–®õ¼‡ì¯+<ÌDp@˯)/W<÷ ×xî‹áëc#âèƒ..†Vf¿LB¹ébX:òXÊû4å4N¨ tݤâýÎ6$¦Þ˜¿,4BÛÚø‡jC/7U×ø7ÏÖ| õR:†«ý=ª ó¤Ír;÷Åó×páýo÷»6ïaúò©ÌˆŒ“Ô£õ¦Ä}œ›–wá *ÖA¨ tVjäT"x¶Éž‘©¡Ÿ{׺I÷S/Í*‹Ã|Øp]ãß®2÷ÅéSéü7ìè‚7edz…ëj™)F¶Òi,Q«à AÖ@¨ tY•FäMhAoŒØ-¶AK{ÓŒ+ÜçchÇнý°Üu¼æ^KAÊë w}ôˆ¢ª—÷¡½íçSq49?ô¦¤/Ú^•¸ËÉB]&¡\¯rç¦åÅé[\;h”Pè¤zŽº6,¯æº¾Ì¹®é9Œ*ÜuËÜš•êƒß+ÜUoMÚ¨J 5êhÏÈ6—øÝ«G‚Ý2½5w{<_d•)EÞºvÐ$¡&ÐUUzÆœu±KÍFë:mË HÏålM¯YàP¶wì‹ÐÍ…LÈTÉ•µç®ÒT 4·_^–Ÿ8\¨›Pè"=jÔu'kªÕë=T66­â—'*×ø~…r_`>¹OÒÔ3e¾xõµîE­b {SᮇiŸ@m„š@ Kþþ…úW×ôÜ®®_ꇖ[SˆL”]æÞ\šk1*ñ»gKŒÆ˜”x¼­žtñZXe~Í“/´@„š€hùaÈêÚº–}Ž[Vå^ÉEÃÇ4aØðqNI)Û-q—g{Φ¡Õe¾xéí¼¿) ®ò¥Sœ_sâ: @]„š@×:ñƒrÙù4§*×ÛºVyŽ‚¶õÕ[ˆLì4|œS^™@ñ* /_ƤÄãÆy"‡}ÝiÎØ_+Üu;ø2€šü¨@ÏŸ¡DcG];V×8½h”Æž9[%k1uHTª÷EQï*Ç^nõÞ)^ç&Ÿóuš÷Ž¥PýEÉ»9?4»OÅÍa‰»LJünìÑù¶Äïú¼¿‹óÌI vË.¤õª¸ßq¼¿#€U5®”üý+%ë}]oC¹PSÏÁÕÄ&¶<öÚ †`»vu'TùBçVÙ5*ñ»Ó°òe÷ÝÝÿþóÓYX>4‹ßŒ{¾Ïãþ˜–<¯G¿µ»N=> ÃÏ®”üý;%ë}]§ ׂՎ õ&'¾(k^™^È“ Òàóéœ4¿æ(T[8èÂ#¬B¨ ô¡çêZÖÀî]ÉT Ȉ9t[$­8^f:€ÒÛÓÔ)eÂéQ߃¹T³*áî ×V!Ô ïÛÀSô"k—2ÁÙYêEXE™04sû}ß1i˜ÿY…»Æ—Ì­ @%BMúÎ-—¤)3ocå ,.jVÜ|,q—±=ô¹n£0›7¹¬7©.”"ÔÚ®L/Í«4$zeBÑ­ÿýç§}»è³X‡*ókÆ•ÔM÷@)BMúN#  Åþ÷ŸŸÅÍ«w™Ôðgãc” çŽí©Ï½5oCµáøqÿÄÂA”!ÔúN ÕŒAFϵlÊpõþ`ÎÝv(~Ló;®$ÍÇyQâ.»)|í½¢vÓâæ]…»Æé̯ ÀÒ„š@ßéÐŒA‡Ÿ«c½õ"³Ie¿]%«Wê¹7*q—I~ÜðïwÖ?ÿýW¬Å‡ w=,ö¹^¯,åG%:fZlo5@Õµ„AÉß²­¦lïèCä¸ÀÈdƒÿÖaV›Òï÷Ø[/ Á¥£0š¼¬ÚzúÅýXìϫ״ÏÜ «®wqßÅsøVÉûýVÔqZü¨tœPÐýÏO;>8÷º®eXÇJEiˆ%ïv›áK½M½”È\<‡ÇmÙ»íÁrÊôÚ;k P<)yˆÏ×ûöþ¹K (M+œû§é 1Oj ! ƒ ª—u-^Ó°ÂÝ+Õ•­÷½o´ÀM˜Íõ·¬â\([-çèXË2½übOÉà ?íQj>üüƒÞ÷%ïú"½†ªÀSÌ© tÑU…Ë5ì»Vײ+´~Ôkd­õž*-Pö‹Œ¡’Õ&ǹ·þ÷ŸŸ|®Xn:«p׸ø’…ƒx’PÐ aÛŠ¥K™v°®B¶5IÇ®zÓƒsßVÅ^à|}ÎØ ùÎ{m¡›ÇkrSá~oR]ø†PÐ)[#ummÃ.õ¤ÙZC ¨~,‹kŠsF޶Û_K£b8y_áî“rÀW„š@?8_TøÐ|\|`~©zµ×uÔ⺎*ÜGÈVA:ÊÖûÆ|š´äÜÃ%ïvhÀÊçŒÃÌ_ÆÈž|ô½T¥.q~ÍI±ùœÀW„š@W• ŸâfÃÅš©ë¸… æØ[¤ì°ÆæÓ¬ì$”_ùv¢l´H•ãq¬l•uáz,Ø~Dú‚ô]…»n{OðPèª*=êŽ5@iØ¿iÓ°±ÔèdM¯½÷ÒÌCõ¦‡ç¾CC+;ö:ºëŸÿþkÊ/êm«‹„š@W?0ÇP³ìpÁAò\]§êú9hÑ0ôؘ*;—æÇtLQBÚçUêv¦W,-;÷݆j!ÌÄÔ&¥Ï£P¾gw[ìÿ'íWü<j]6®pŸÝ¢2nÓ/žGl O:Rר»â¤5å7kzͽ–òÓP-œPoºrMÙjù¯å×”‡ºÔ»1žÿ¬Üýˆ……ƒ 2¡&Ðå̱ñV¥ÀÛ~mªáù²Ø®ÃlÈîaÛ¡+Ô5¾–ã Ö5¯.|L¯™Çp˜šU† žY ˆ–^Sâ1]¥·æFÏ}é=9ië5åÁó†î 1{÷<ùžŠŸu^«Uý¨@ÇÅÆÄû ÷{_4®ÂºÃ¬¼M4êÓsu ®¿¯ånCu½7u /ë>˜ƒŽö_SþÌèÜ÷Ø m¼¦Ì•}ÿÿ²Áã`ÙÅæ¶bX›Bqˆï‰æ^ ç„š@>,‡jËZƒÍ´"wü[o­j„¦ºŽBùÄçu®ëµ¼‘¹4iù5eZ犾ªxîÛ)ãxMïÇa˜ÍiÛúkJz¾ƒ’u=ÛTPX<ד’׿x^z=)¾'v‚…€(Éðs Vi´½Oó56Ñnž„ï‡Am6ëz_ñ¾ñµ\7½Ú|jx>WקܯxìôF OŠ-† ¿…êæ‹1‘‰ãÎ}oâù~ ×”q˜õ(ÍéšR6ìÝØ\¥#Ümúz—³ôeÖ*Ÿ)è)¡&ЇËqΦw+[çýfÆðdw…‡º dò¹¦ÄóÂ*½-ãùþ¶‰¹›SÏôxÍ{›Ó5%…¼eêq•®í›T¶ncïžg?«¹PŠPèË‡åØ˜¸Yá!â ¶Æ'5Wj¼ÅÆl Ýâ¼”e{¶m|щ…ºÆž2VxˆøÚãÂL±?^µ÷Òƒmk…‡:3ìüÑúî¤c7ö^Ž=kb/ØÝösXÃÎÉìšÏg+žûÞ§sߨ†÷æ(-0÷G…s_‚ÍQÉkaÎÏe{Šî7ÙC·#ï«Øöw•`Y?|úôI€^H‰ÛP}xì¢8ì,~øžÆí¹@&-ž·ˆ¾ªé%ýÔ†ž„+®tý˜©¶×ÏõÄI{˜¶QMû6†ßö‡l)¼]6PŒÁá*½š^†fæ:»Oµ¾ÎàüQ¦Þñü0iÙK˜Z¨¤Õç¾ûtÞ»Xòš2X8÷í×tî{½©/sÒ—|ˆ±÷Åó|Ù’c Ö«Ì7ïÒ—¬mxîñØ)³èÕ/ë:‡”<ß>©x¾?8St›…‚€ÞˆÄô!~ZC06¾Þ¤-~!Æí¿»ÛÀËyÝ–¡ÑêZGãþUÚb]ãÍÕ¿·SSC~Qf/:W‘M Yñüð¶…ÏkêJÐÚs_|¦-žû¾÷EDïå³ šû¡\ïÒ“'¡\¨9 †¡/c?Ô÷%4fø9зFhl$ÆFhÝ“Ño¥†æc[Ý^·mhtÓü?UWf¾ºhÒ¯kJSç¾k¼¦œmxô²Ó©LZ´ÿ¯C¹©m¶š˜Kµ£ï«¡Jð¡&ÐÇËM›ëðº­s=¦ºÂjs—nŠ@s½µÞhÒ±kJ®ç¾_7h¦aôe‚Ú³.àV¶çèÈ»fé÷Õ¯*À÷5>XfÔìÏm_¼f¡wÅUF‡C\ìC ¹¿uÞ±ª<¼¦ÜexMy{Û¤qÉߟ´pßOB¹/IwÓ´<_Û“°Ú‚\tœPèó‡åy°ù¡åO5„ƒ\z¶ÅÆ}±Åº¾Ëàé~î¥$Ðl\œs6.2q¬tøšÏ}q®ß¶¯Þ<ï™>Ùä“H -•™ò¦Å‹]é­Ùœãg/hÖ@¨ h„þû¯8!ýëÐÎáèq¥Ô,{¦^ ³@«úŸ[ÐK©ëâ{*Ç+oÓ£ëÊq‹Ï}1pmË|¶e¿ähóùzRò÷ÓÐ{–øœš›³€Ì 5ÂßÃÇb£-ÜbïÌŸR0˜s]§1Ð ³^›mhÌC6s:6ëcÚçÁ1=½¦L‹›Ðžëñ‹œÏ½¥[ô%٨̹»ÍÓ¯¤)5>4øúûþ~Š×k=ýø†Pàˇ滴`ÂÏasCÒ¯RÃsØ¥yS8;› 7ïƒmâûæ_©gæØ°~\S>Ÿû~ ›ûÂ,~Áð:}‘3mKmÒ à[%î’Ãy»ìsÙ»ô·¯{bæ>]Ám  ×”q˜}¹3¿žì‡úÎáË ­Ýßé¼—ÎÕKw2:—C¹aåð¹P³Ì>˜ÿ~ Ó5Ÿ>}R€ò ²…-<Ñ0½Zh \§`áÚ|ŽOÖv˜j:¯qH·{ÊÞ‡/!Ót^_‹Ñ¹¦Äí±Þh7áK¸4uMúL¨ dÅBA@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@V„š@Vþ¿È—o[ãP5IEND®B`‚chef-12.14.60/omnibus/resources/chef/pkg/license.html.erb000066400000000000000000000261451276456504500231330ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright <%= Time.new.year %> Chef Software Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. chef-12.14.60/omnibus/resources/chef/pkg/welcome.html.erb000066400000000000000000000002501276456504500231310ustar00rootroot00000000000000 This installer will help you install <%= friendly_name %> version <%= build_version %>. You will be guided through the steps necessary to install this software. chef-12.14.60/omnibus_overrides.rb000066400000000000000000000014251276456504500167550ustar00rootroot00000000000000# DO NOT EDIT. Generated by "rake dependencies". Edit version_policy.rb instead. override :rubygems, version: "2.6.6" override :bundler, version: "1.12.5" override "libffi", version: "3.2.1" override "libiconv", version: "1.14" override "liblzma", version: "5.2.2" override "libtool", version: "2.4.2" override "libxml2", version: "2.9.4" override "libxslt", version: "1.1.29" override "libyaml", version: "0.1.6" override "makedepend", version: "1.0.5" override "ncurses", version: "5.9" override "openssl", version: "1.0.2h" override "pkg-config-lite", version: "0.28-1" override "ruby", version: "2.3.1" override "ruby-windows-devkit-bash", version: "3.1.23-4-msys-1.0.18" override "util-macros", version: "1.19.0" override "xproto", version: "7.0.28" override "zlib", version: "1.2.8" chef-12.14.60/spec/000077500000000000000000000000001276456504500136225ustar00rootroot00000000000000chef-12.14.60/spec/data/000077500000000000000000000000001276456504500145335ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/000077500000000000000000000000001276456504500153175ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test-1.0/000077500000000000000000000000001276456504500220165ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test-1.0/debian/000077500000000000000000000000001276456504500232405ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test-1.0/debian/changelog000066400000000000000000000002521276456504500251110ustar00rootroot00000000000000chef-integration-test (1.0-1) unstable; urgency=low * Initial release (Closes: #CHEF-1718) -- Joshua Timberman Thu, 30 Sep 2010 09:53:45 -0600 chef-12.14.60/spec/data/apt/chef-integration-test-1.0/debian/compat000066400000000000000000000000021276456504500244360ustar00rootroot000000000000007 chef-12.14.60/spec/data/apt/chef-integration-test-1.0/debian/control000066400000000000000000000006731276456504500246510ustar00rootroot00000000000000Source: chef-integration-test Section: ruby Priority: extra Maintainer: Joshua Timberman > Build-Depends: debhelper (>= 7.0.50~) Standards-Version: 3.8.4 Homepage: http://tickets.opscode.com Package: chef-integration-test Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: Chef integration tests for APT in Cucumber This package is used for cucumber integration testing in Chef. chef-12.14.60/spec/data/apt/chef-integration-test-1.0/debian/copyright000066400000000000000000000017121276456504500251740ustar00rootroot00000000000000This work was packaged by: Joshua Timberman > on Thu, 30 Sep 2010 09:53:45 -0600 Upstream Author(s): Opscode, Inc. Copyright: Copyright 2010-2016, Chef Software Inc. License: Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. The Debian packaging is: Copyright 2010-2016, Chef Software Inc. () and is licensed under the Apache 2.0 license. See "/usr/share/common-licenses/Apache-2.0" chef-12.14.60/spec/data/apt/chef-integration-test-1.0/debian/files000066400000000000000000000000611276456504500242620ustar00rootroot00000000000000chef-integration-test_1.0-1_amd64.deb ruby extra chef-12.14.60/spec/data/apt/chef-integration-test-1.0/debian/rules000077500000000000000000000006721276456504500243250ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 %: dh $@ chef-12.14.60/spec/data/apt/chef-integration-test-1.0/debian/source/000077500000000000000000000000001276456504500245405ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test-1.0/debian/source/format000066400000000000000000000000141276456504500257460ustar00rootroot000000000000003.0 (quilt) chef-12.14.60/spec/data/apt/chef-integration-test-1.1/000077500000000000000000000000001276456504500220175ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test-1.1/debian/000077500000000000000000000000001276456504500232415ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test-1.1/debian/changelog000066400000000000000000000005131276456504500251120ustar00rootroot00000000000000chef-integration-test (1.1-1) unstable; urgency=low * New upstream release (1.1) -- Joshua Timberman Thu, 30 Sep 2010 10:09:34 -0600 chef-integration-test (1.0-1) unstable; urgency=low * Initial release (Closes: #CHEF-1718) -- Joshua Timberman Thu, 30 Sep 2010 09:53:45 -0600 chef-12.14.60/spec/data/apt/chef-integration-test-1.1/debian/compat000066400000000000000000000000021276456504500244370ustar00rootroot000000000000007 chef-12.14.60/spec/data/apt/chef-integration-test-1.1/debian/control000066400000000000000000000006731276456504500246520ustar00rootroot00000000000000Source: chef-integration-test Section: ruby Priority: extra Maintainer: Joshua Timberman > Build-Depends: debhelper (>= 7.0.50~) Standards-Version: 3.8.4 Homepage: http://tickets.opscode.com Package: chef-integration-test Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: Chef integration tests for APT in Cucumber This package is used for cucumber integration testing in Chef. chef-12.14.60/spec/data/apt/chef-integration-test-1.1/debian/copyright000066400000000000000000000017121276456504500251750ustar00rootroot00000000000000This work was packaged by: Joshua Timberman > on Thu, 30 Sep 2010 09:53:45 -0600 Upstream Author(s): Opscode, Inc. Copyright: Copyright 2010-2016, Chef Software Inc. License: Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. The Debian packaging is: Copyright 2010-2016, Chef Software Inc. () and is licensed under the Apache 2.0 license. See "/usr/share/common-licenses/Apache-2.0" chef-12.14.60/spec/data/apt/chef-integration-test-1.1/debian/files000066400000000000000000000000611276456504500242630ustar00rootroot00000000000000chef-integration-test_1.1-1_amd64.deb ruby extra chef-12.14.60/spec/data/apt/chef-integration-test-1.1/debian/rules000077500000000000000000000006721276456504500243260ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 %: dh $@ chef-12.14.60/spec/data/apt/chef-integration-test-1.1/debian/source/000077500000000000000000000000001276456504500245415ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test-1.1/debian/source/format000066400000000000000000000000141276456504500257470ustar00rootroot000000000000003.0 (quilt) chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/000077500000000000000000000000001276456504500221005ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/000077500000000000000000000000001276456504500233225ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/changelog000066400000000000000000000002531276456504500251740ustar00rootroot00000000000000chef-integration-test2 (1.0-1) unstable; urgency=low * Initial release (Closes: #CHEF-1718) -- Joshua Timberman Thu, 30 Sep 2010 09:53:45 -0600 chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log000066400000000000000000000011741276456504500322460ustar00rootroot00000000000000dh_auto_configure dh_auto_build dh_auto_test dh_prep dh_installdirs dh_auto_install dh_install dh_installdocs dh_installchangelogs dh_installexamples dh_installman dh_installcatalogs dh_installcron dh_installdebconf dh_installemacsen dh_installifupdown dh_installinfo dh_pysupport dh_installinit dh_installmenu dh_installmime dh_installmodules dh_installlogcheck dh_installlogrotate dh_installpam dh_installppp dh_installudev dh_installwm dh_installxfonts dh_bugfiles dh_lintian dh_gconf dh_icons dh_perl dh_usrlocal dh_link dh_compress dh_fixperms dh_strip dh_makeshlibs dh_shlibdeps dh_installdeb dh_gencontrol dh_md5sums dh_builddeb chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars000066400000000000000000000000161276456504500315620ustar00rootroot00000000000000misc:Depends= chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/000077500000000000000000000000001276456504500276075ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/000077500000000000000000000000001276456504500305315ustar00rootroot00000000000000conffiles000066400000000000000000000000601276456504500323410ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/usr/share/doc/chef-integration-test2/copyright chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control000066400000000000000000000005261276456504500321370ustar00rootroot00000000000000Package: chef-integration-test2 Version: 1.0-1 Architecture: amd64 Maintainer: Joshua Timberman > Installed-Size: 36 Section: ruby Priority: extra Homepage: http://tickets.opscode.com Description: Chef integration tests for APT in Cucumber This package is used for cucumber integration testing in Chef. chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums000066400000000000000000000001331276456504500320460ustar00rootroot000000000000008b3b9ff6cdfe7d7b2b8b8d4f7b9e381f usr/share/doc/chef-integration-test2/changelog.Debian.gz chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/compat000066400000000000000000000000021276456504500245200ustar00rootroot000000000000007 chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/conffiles000066400000000000000000000000601276456504500252110ustar00rootroot00000000000000/usr/share/doc/chef-integration-test2/copyright chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/control000066400000000000000000000006751276456504500247350ustar00rootroot00000000000000Source: chef-integration-test2 Section: ruby Priority: extra Maintainer: Joshua Timberman > Build-Depends: debhelper (>= 7.0.50~) Standards-Version: 3.8.4 Homepage: http://tickets.opscode.com Package: chef-integration-test2 Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: Chef integration tests for APT in Cucumber This package is used for cucumber integration testing in Chef. chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/copyright000066400000000000000000000017121276456504500252560ustar00rootroot00000000000000This work was packaged by: Joshua Timberman > on Thu, 30 Sep 2010 09:53:45 -0600 Upstream Author(s): Opscode, Inc. Copyright: Copyright 2010-2016, Chef Software Inc. License: Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. The Debian packaging is: Copyright 2010-2016, Chef Software Inc. () and is licensed under the Apache 2.0 license. See "/usr/share/common-licenses/Apache-2.0" chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/files000066400000000000000000000000621276456504500243450ustar00rootroot00000000000000chef-integration-test2_1.0-1_amd64.deb ruby extra chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/rules000077500000000000000000000006721276456504500244070ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 %: dh $@ chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/source/000077500000000000000000000000001276456504500246225ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/chef-integration-test2-1.0/debian/source/format000066400000000000000000000000141276456504500260300ustar00rootroot000000000000003.0 (quilt) chef-12.14.60/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz000066400000000000000000000025311276456504500246710ustar00rootroot00000000000000‹˜rhVíYmoÛ6ö×éWÜ’ˆ K–ü¦Î}A'E¼uI9-úi %Zâ"‰IÍ5†í·ïH)®“´hP4°êC&u:Þ‘|ŽwLj.É»­û$ÉÍéæÓü÷z£~Ïõ½^_÷ûިׂakTJE@ë;¥¨ZÿçJðôþÖ4|výGþ°^ü7µ\Ïyƒ¸Íúß;¼!C˜Ð¥ÍrEcAã¹­¨T=+ ¡nA”‹µõF0.˜Z~P‚X¿üTŒá.“’Àœe *2’óÛ=˜ž—¼!¨òìÅ ë°didÑ‚æ‘nÈ„¦pðâ9øŽë ÝÛV HIû-Ò¨Ôwž8ë„g´ 1š(UŒ»]ÅÂKª¤³5Še½!á¥aúŒ&L¡±¥@&’¯­Bþ–IÊr\÷üÓÁ®ŒÉpÓ¼2¬¨fjŠ#ÀÖ G°ä&oæø¦eXê9±`ž0 E¥àßRÒÈp†5Ë-A,ı¾%þ³‚¨ÖÃàßu{ü|_ã€] þw@¾Õjè;¦çÿrÉR*ÿ£­øÏÕø÷\ ü¥]™A»»Ÿ>q{kÁâD5ÞâŠÿ„ä1Myüøï ûãÿ~ßàà7øß}ðpà9®íµ¡Ìqz)} ¥ˆi®Ÿ§|eYa–3ÅH ‚¦”H Ó”KŠóþôäø•íùÞ“6rÚ6Ü) †Ëô]h=ŒÁýy<ìC°ñ„p×sø—& ¼§2ÐÝë?›øèšúÏî×SÏìÛ§_Œÿ¼ÁõGçÑä;¡¾ãÂÁŸ%KU»q°ß/þEyO¹ß]üÿÈ¿Uÿéù^ƒÿ]Ðþ&\°¼›‘K öÒÚû± º¥+º]É lloP Qºd)?md5EMóéŠHà˜7²œ¤éV‚)EsX¬1$¤k8¡RÉ#˜ Âb2äÒ&Ø ² ¡/釚ÊjV ~¬6Òñ‰y)£‘%¶ÑCY…\uðR¥2_t`ÍK4l­•®´ßz +¦lc@+•`¦î½1ç–6Æ6EÕè[èêì_U‰\§ï_^éâX(î"Çh7£¹ª,AmU)ràæ«FÐéØÚ§ .üþöøüð,8~îYÖOcë‡(G/Áú¦øß$øÿy#tóü÷† þwBfw¯¸¸4º¾ŠÐ{z¬³<øêk½¡¿”ÐY"’ &%BOÈv=êY%ªƒIfˆ¨™^íÎúõ¦Ig»½ý…e½f!Í%­ÙëV„ÙlDâŽÂ-ÅGý¦õ¥ôt8¤öêW{í§FÆ•çȹª½Ç•ª\‚†½¾DIMaåKÌ@µÇy_ á }k†NNÃN;ˆ-N ªÒº¾ÎZ­V1Ú:\ÄÝ´â’Ý׳éñipl£ÆÿEžj*(Fs¢rK¤@}B¿CJVèŠÄ‚â;ô:8¾öÇ,; ùR­ˆ FNÄ´û[”êÚ„]i‡Vo3à”áØ›0 öàpÌ‚Ž‘òn6?9»˜Ã»Éùùät>;àì¦g§G³ùìì[¯`rú~u€âtá@èò„¶ez*iTÍ[@é5%ô ™n¯¼d!Z—Ç¥¾C‹9zÑ\_’áa”1©Õœ1FNÊ2¦L™CÞ6 ÷Ù[GÆ!ÖH0·mòn»ž¥4&é5´-ËÒ'N[úÙ]¨7]ºQB»·U–ÕÇÏíÍÂWéußk"ö†j¨¡†¾’þ´ÙóÚ(chef-12.14.60/spec/data/apt/chef-integration-test2_1.0-1.dsc000066400000000000000000000015651276456504500230220ustar00rootroot00000000000000Format: 3.0 (quilt) Source: chef-integration-test2 Binary: chef-integration-test2 Architecture: any Version: 1.0-1 Maintainer: Joshua Timberman > Homepage: http://tickets.opscode.com Standards-Version: 3.8.4 Build-Depends: debhelper (>= 7.0.50~) Checksums-Sha1: 755c304197c6559128aed206ea70643fec2bb90d 248 chef-integration-test2_1.0.orig.tar.gz 8b7df49a9e2c57b4460c2738852db1156a21a089 1369 chef-integration-test2_1.0-1.debian.tar.gz Checksums-Sha256: 8b206a7b3d422290bc8d82bd700cb89f1c6e3962b96be6a3955c7a0159f9031c 248 chef-integration-test2_1.0.orig.tar.gz 77a7956e222c35afcddc4a5a8d338ca6e36dc1fbd720af255ce2412885f82702 1369 chef-integration-test2_1.0-1.debian.tar.gz Files: f1f7d7bbe63ad631d25d707f564a8d33 248 chef-integration-test2_1.0.orig.tar.gz 4fab5c1cd9a7b47c4f319af776f48a1d 1369 chef-integration-test2_1.0-1.debian.tar.gz chef-12.14.60/spec/data/apt/chef-integration-test2_1.0-1_amd64.build000066400000000000000000000063671276456504500243500ustar00rootroot00000000000000 dpkg-buildpackage -rfakeroot -D -us -uc dpkg-buildpackage: warning: using a gain-root-command while being root dpkg-buildpackage: set CFLAGS to default value: -g -O2 dpkg-buildpackage: set CPPFLAGS to default value: dpkg-buildpackage: set LDFLAGS to default value: -Wl,-Bsymbolic-functions dpkg-buildpackage: set FFLAGS to default value: -g -O2 dpkg-buildpackage: set CXXFLAGS to default value: -g -O2 dpkg-buildpackage: source package chef-integration-test2 dpkg-buildpackage: source version 1.0-1 dpkg-buildpackage: source changed by Joshua Timberman dpkg-buildpackage: host architecture amd64 fakeroot debian/rules clean dh clean dh_testdir dh_auto_clean dh_clean dpkg-source -b chef-integration-test2-1.0 dpkg-source: info: using source format `3.0 (quilt)' dpkg-source: info: building chef-integration-test2 using existing ./chef-integration-test2_1.0.orig.tar.gz dpkg-source: warning: ignoring deletion of directory cache dpkg-source: warning: ignoring deletion of directory cache/chef-integration-test2 dpkg-source: warning: ignoring deletion of file cache/chef-integration-test2/contents dpkg-source: info: building chef-integration-test2 in chef-integration-test2_1.0-1.debian.tar.gz dpkg-source: info: building chef-integration-test2 in chef-integration-test2_1.0-1.dsc debian/rules build dh build dh_testdir dh_auto_configure dh_auto_build dh_auto_test fakeroot debian/rules binary dh binary dh_testroot dh_prep dh_installdirs dh_auto_install dh_install dh_installdocs dh_installchangelogs dh_installexamples dh_installman dh_installcatalogs dh_installcron dh_installdebconf dh_installemacsen dh_installifupdown dh_installinfo dh_pysupport dh_installinit dh_installmenu dh_installmime dh_installmodules dh_installlogcheck dh_installlogrotate dh_installpam dh_installppp dh_installudev dh_installwm dh_installxfonts dh_bugfiles dh_lintian dh_gconf dh_icons dh_perl dh_usrlocal dh_link dh_compress dh_fixperms dh_strip dh_makeshlibs dh_shlibdeps dh_installdeb dh_gencontrol dpkg-gencontrol: warning: unknown substitution variable ${shlibs:Depends} dh_md5sums dh_builddeb dpkg-deb: building package `chef-integration-test2' in `../chef-integration-test2_1.0-1_amd64.deb'. dpkg-genchanges >../chef-integration-test2_1.0-1_amd64.changes dpkg-genchanges: including full source code in upload dpkg-buildpackage: full upload (original source is included) Now running lintian... warning: lintian's authors do not recommend running it with root privileges! E: chef-integration-test2 source: maintainer-address-malformed Joshua Timberman > W: chef-integration-test2 source: changelog-should-mention-nmu W: chef-integration-test2 source: source-nmu-has-incorrect-version-number 1.0-1 W: chef-integration-test2: new-package-should-close-itp-bug W: chef-integration-test2: wrong-bug-number-in-closes l3:#CHEF E: chef-integration-test2: file-in-usr-marked-as-conffile /usr/share/doc/chef-integration-test2/copyright E: chef-integration-test2: maintainer-address-malformed Joshua Timberman > W: chef-integration-test2: empty-binary-package Finished running lintian. chef-12.14.60/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes000066400000000000000000000032111276456504500246420ustar00rootroot00000000000000Format: 1.8 Date: Thu, 30 Sep 2010 09:53:45 -0600 Source: chef-integration-test2 Binary: chef-integration-test2 Architecture: source amd64 Version: 1.0-1 Distribution: unstable Urgency: low Maintainer: Joshua Timberman > Changed-By: Joshua Timberman Description: chef-integration-test2 - Chef integration tests for APT in Cucumber Changes: chef-integration-test2 (1.0-1) unstable; urgency=low . * Initial release (Closes: #CHEF-1718) Checksums-Sha1: 7e065fdf71f4d798312b318a29cec43b7bc1c8e1 885 chef-integration-test2_1.0-1.dsc 755c304197c6559128aed206ea70643fec2bb90d 248 chef-integration-test2_1.0.orig.tar.gz 8b7df49a9e2c57b4460c2738852db1156a21a089 1369 chef-integration-test2_1.0-1.debian.tar.gz f3f89c051bce36d40ef1be12d231c44b2d5be05b 1694 chef-integration-test2_1.0-1_amd64.deb Checksums-Sha256: 80d314349e1d978f242d05482ca81c9361739047daa4adcecc9e5e622fdc6fb4 885 chef-integration-test2_1.0-1.dsc 8b206a7b3d422290bc8d82bd700cb89f1c6e3962b96be6a3955c7a0159f9031c 248 chef-integration-test2_1.0.orig.tar.gz 77a7956e222c35afcddc4a5a8d338ca6e36dc1fbd720af255ce2412885f82702 1369 chef-integration-test2_1.0-1.debian.tar.gz 19a767db0a947a350fb1c8492699e8a808fbe1838d4a582001106cfbe520ad8f 1694 chef-integration-test2_1.0-1_amd64.deb Files: 9f927b32d95b5406c696b5b0b23177e8 885 ruby extra chef-integration-test2_1.0-1.dsc f1f7d7bbe63ad631d25d707f564a8d33 248 ruby extra chef-integration-test2_1.0.orig.tar.gz 4fab5c1cd9a7b47c4f319af776f48a1d 1369 ruby extra chef-integration-test2_1.0-1.debian.tar.gz 9914e6152e278b6828bcade3b3f5580c 1694 ruby extra chef-integration-test2_1.0-1_amd64.deb chef-12.14.60/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb000066400000000000000000000032361276456504500237730ustar00rootroot00000000000000! debian-binary 1449685658 0 0 100644 4 ` 2.0 control.tar.gz 1449685658 0 0 100644 474 ` ‹íÕÍnœ0`Î~ŠyÌïUQ£äÐVª´RV½3€ÀÈ6R7O_‡ô°MÚ¦—Ý(Ñ|‚µÍã!Œ‚“‹½2Ï׳÷ô¼^ó¤H“¸äIšøö²ŒËòà 넌Öî_ã^ê£ÂHêÉ=œxÿ‹,ûëþçIñûþsžò<€˜öÿävBÞ‰+=¶59ìŒpJO‡Ö%ìëUÀÃxÃÙ•‘½r(Ýbü=blŠŒ}þ6 ©à‹¶ý"`¯ÆÍ(&øð¼åûÚòQÏVêC©ÇËKöyòû0 ØlnÕ½ìÖO³Nm–úÀvFi£Ü¡üáŒ`Ÿôˆóúä½ssENÉ;t6<ŠËnÐJ£æÇ0×~…p´BxX¡…V¸Úí}\/ryxLû^Y˜ß øËÅb³Ž”¿†< ¤¦n á' ÙËÿ¶UÚWËÿ¸8ªÿ¾ðûü/|7åÿD‹5‘í…Á¨Ñ2úsðÿ‘ù`T×;wöý›Ü.£=á/å?OÓ§ßÿ$É(ÿÏa[§õEÛ²i±lÊ:©·õ¶ÉÚ²¾ÀtË[€ÿ+½˜:tÞ`­Äv÷T*!„B!„B!„B9·Ÿ*P¸ (data.tar.gz 1449685658 0 0 100644 1028 ` ‹í—_ˆEÇ£R­ ñÅúPýðz¹d³I6x­Å˜;iôÌi’k9ÏÉf²;½ÍÎvfÖôDì©/UᵟD´ >ô¥¾ˆE|¬/R>D¡ˆÿð·›ô¸³œU¡×æó²™™ßþæ÷›ïï7I²¹ÔUÇDÊ¥RòDþúL>ç-»`™å¼UÈã|¹lÚ)(¥6H*"R‚sõwvWZÿŸ’ÍERä¶’þ8Ÿ7Ky­ÿ&ê/="hnë蟷-Së¿Ùúw¸“Ûú[»¤õ¿ú;펳@QWÅx0®¨TVnõ·PÛ.µþ[HÇ#K}îf'i›‘ ë>ýïô·‹Å õ·Jù¡þv¹T(¤LË*°ÿM­ÿUçÎcÛ“çõ7ìúôk㨹óÆyòü õâþ¹“¿ß²ÿÑßî˜þõìÇ_uŽOöçÕ/?wÝûºõÜc»—ÞqÛwU3Ï&~þ3,<üøKïÝýÅ3Ï}øÖö×Îj=Òøhyä»›¿ýîîù'OÔÝ‘í¿8§æ¾yùÖW/,ˆÓÅ/Oÿ!>Xâß/=sñ¦7/œ¹]¼²gÇ'§VVŽ·—oûiÛÎyqdz×-K=ÿNJsûŸ‡‹‚¹Þj+ôÞ.Û«ý_,—±ÿÑ<¯û3hyLBŸ‹è !qˆK;Ð^œ0 @äÒ‹´X¯ME°÷ò™CÉÌ}<”ïЬÃ{ûö åE(˜Ð¤!Xø§Ì{&J…‰b ÆMÛ4 c6”JPÒƒJ¤<.FåØp×™« Ô'kÕKÕ7\^Ãhulàzí†1ÍH:4Ž:*@y*˜)>†+8@…Äj+kÂhl.¥Çö$>y=²WIŠNð̧ܺ@84TÀÀÄC¿ }¦¼d£¡›lâdnè„·A{q[﮵¢Q{J…¹\¿ßÏ’$Ú,nÎXÉÜt­:UoNcÄûÙÀ§R‚ ‡#& „CÚ¥OúÀWP\S<Ž·/˜b›É»ª7@â§ÃPÖŽÔº»f½Ö + ]iB­™†û+ÍZ3“x9XkퟙmÁÁJ£Q©·jSM˜i@u¦>YkÕfê8z*õ9x¨VŸÌÅãÂè‘PÄ9` ,>JÚœ[“ÒuAtù (R‡u™ƒÙn„… .ŠŠ“‚‹“ÉXT‰!v?>ë1•\kòòÔ°ÎZ8ü¾vBìˆÉVu0º×§.ñ×õÁ˜a¸{|lþ†U¿DœlzÍ­Œnzx¯ ?x)Ö=mèï/F£Ñh4F£Ñh4F³1,7ú«(chef-12.14.60/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz000066400000000000000000000003701276456504500242500ustar00rootroot00000000000000‹c}SVíÔAŽ‚0†á®=E/@äoi;û‰Æk4X•Œ)•óû³0êB‰¢1ó=Ð&м…ÞÇ©YΜ1ÃU;Eç×#AʪÂ:k”9‘ÒFH3ö‹ ö]òQJ±õ»¦N×çÝÿP=÷/}¹ #î‚¿ô/ Ëý•æ[èÿ§þ|®²ªNa}ªš:K¡KêÛâWý©0DšžGÖh‡þ¯p·É«uêxƘ?ìëýy좿Ê5ñÿ?Ú*oøçý¿³yF޾¤o“lcÓWËåÐ^¶¾üñë0y÷;Àm2Á©(chef-12.14.60/spec/data/apt/chef-integration-test_1.0-1_amd64.changes000066400000000000000000000014601276456504500245640ustar00rootroot00000000000000Format: 1.8 Date: Thu, 30 Sep 2010 09:53:45 -0600 Source: chef-integration-test Binary: chef-integration-test Architecture: amd64 Version: 1.0-1 Distribution: unstable Urgency: low Maintainer: Joshua Timberman > Changed-By: Joshua Timberman Description: chef-integration-test - Chef integration tests for APT in Cucumber Changes: chef-integration-test (1.0-1) unstable; urgency=low . * Initial release (Closes: #CHEF-1718) Checksums-Sha1: b44685ff59626bc94c67e60665f06c4643fe9767 1680 chef-integration-test_1.0-1_amd64.deb Checksums-Sha256: da176f4405fa21fd7207d4785680c6996d395a1ca132f2d5565a61c5479b1116 1680 chef-integration-test_1.0-1_amd64.deb Files: 713722480408ecc8e7220aea52bdd76e 1680 ruby extra chef-integration-test_1.0-1_amd64.deb chef-12.14.60/spec/data/apt/chef-integration-test_1.0-1_amd64.deb000066400000000000000000000032201276456504500237020ustar00rootroot00000000000000! debian-binary 1285863052 0 0 100644 4 ` 2.0 control.tar.gz 1285863052 0 0 100644 468 ` ‹íÔÝn›0`®ý~68$hªVµÛ¤I‘íÞØð0²´ôéçÐ]dë~z“T“Î'!Œ±mŽi–\\UB,÷è×ûÒf¬Œge쯪¼L¨H®`öA:JgmøÛ¸½ÿO¥Ù …Ÿáü¯Ëòùç‚ÿœÆ8/šcþ/Ž·e«¡­@çëÛò )ùVoùvCéì]æ{é ÓVeª‡veÆ“ÁØqÀ‡Ø+ǶKï¡1rL»'ÂÄ:¯XÕ´%@#dÙärSh­ªŠå²RðÊÈv::Óõ$è"õ¯ìœ=¼aý .Îê¿8ÕÁr¬ÿkØIõ(;¨éoË|çãCMYš¯¹uª7T˜]œ"½.ÉggÅ \M?YßÏ’îÍЀäHß½ìùºô¼·“WVCªìpsC>Ž1 ‡èÕƒyŠ¡ Nâ2ËÒnnŽdçŒu&k ß‚“äƒ`Z6Þ‡0ÕYŒz„àÓ³¸ä¼rfzsHÏHOô´µŽÞîöñ ½›Õ|Ú&¡ûÞx:=›³½ŒT?†¼dÆn IñW…B!„B!„B!„BèÚ¾7‘(data.tar.gz 1285863052 0 0 100644 1020 ` ‹í—_ˆEÇc * RÁÅ?ôG^L ·ÿ’ÍáYÓ܉kÛ.9ÛÊ N6“Ýémv¶;³Ý^ߤ¾O´}Q8­Å—"”"B©/‚'"È©ø¢ ¾ˆèƒ (g7¹óÎrV…¦'Ìçe2;¿üæ÷›ïï7›¨Z Æ-+³Ï†Q± £\6 S<×ÍX¹3Ž"€\D)ÿ;»ë­ÿOQµ˜EÚÓßÐ-£,õþÌCÖ¶ŽþFÕ°¤þ£Ö¿Kmkèo–«òþ¿)ú;î‘€c7BœÐ`ŒcƵQê_úW­jUê¿uôw<¸Ø§®:…;ª{ü_ê_­T6Õß´þÔ_HŸÓMÓ¬ˆ÷¿.õ¿áì:q{6nÛþÐ'ß(Ïë;o½ÌÎ}÷B£rÚ?uõëÏÝý%ùöÃóÚ3Ÿî¿`ó_§ßÙÑ\ùü•â]gc³0vÝyb±|òÙÊäÇO¯Ü»4¿\yãâ…Ÿ Ûμÿ=‹_„oÙÇ÷Ìþaù¾×_[9õÒ«¾´¼3ø*Yšzû·ÓsÚGÏ]zðÇËüþæù÷®Þröƒ#/¾|¥±ýû¹ðçý'Ýò·]ÙûÙ’vFù¥þnNr“ûŸ† q½ÿÖ×é£:^]í³b ;S·ÊUÙÿ£ í æ!A BäÌ#w¡³0¡( x’2/FÐ&ýŽú(€Ý×>9œ=yŒ†Ì¡]¬:´?9 4€¶— ¬C ‡`Š?u ?2a•'*ŒéU]W”Ùñ£>ÔbîѨÀŠÃ]›W%°GU”újù —׿P¨®×CQö ͇³.ÄAGÀ= 5‘©†+%x GLT;˜ª…Ô ?\ÊÍ|,ÐúhÊ!fX8çÖ#>|ÌÁ!€H<ôÅÛÑÁîe ݨ™“CC'´Ã‘°GöÐÞzK@|µÇy8¡iI’¨(‹V¥‘«ù+¦í³ëÓÖô˜ˆx`?ø˜1ˆð‘˜D™„€Bƒ:"J%@#@n„ŧi¼ID8 Ü0Ú㉸2?]"D!˜o8°ÕèDÖë Ä‘‰ È×Z`·ò°§Ö²[¥ÌË»ýDs¶ j33µFÛžnAsêÍÆ”ݶ› 1{jC°×nL•‹ãáca”æ %éQâîàÜZo¢GA±;¤G‘]àÆ¢pÁ¥Gqˆ¤ ÅIX**!v3?>éž]kìÚÔDµÅlðgØ ©#ÂþYÕAa·]äo胢¢(b÷ôØüM«0-:-ˆ4Ùüº[Y¸é‹{xMøÁ—RÝóŠ|I$‰D"‘H$‰D"‘H$’ÍùWÔ¨(chef-12.14.60/spec/data/apt/chef-integration-test_1.0.orig.tar.gz000066400000000000000000000003551276456504500241710ustar00rootroot00000000000000‹²¤LíÒÑ Â †a» o`ä¿ivE·aËjEÛp¶ëÏDµ1jEô=0¦¼ÖÆÙÀD •jFÒJÜŽŒH*¢„„’LP¬E¸ú`Såãœí}v\Yw4ùã}]ë?ªýS“î쀯 ÿDË úµø6Q–{»uÆgEy[ù·¼Š>ý) ûbAŠÐÿºú§E˜É}õÊ?šÀ)Ÿ÷k÷ýIÇšqñ®K¶ùóþ³å|‘¦)7¥ç¥+êlmoÚóÒ¤³µ£oŸÚãçv*(chef-12.14.60/spec/data/apt/chef-integration-test_1.1-1_amd64.changes000066400000000000000000000014461276456504500245710ustar00rootroot00000000000000Format: 1.8 Date: Thu, 30 Sep 2010 10:09:34 -0600 Source: chef-integration-test Binary: chef-integration-test Architecture: amd64 Version: 1.1-1 Distribution: unstable Urgency: low Maintainer: Joshua Timberman > Changed-By: Joshua Timberman Description: chef-integration-test - Chef integration tests for APT in Cucumber Changes: chef-integration-test (1.1-1) unstable; urgency=low . * New upstream release (1.1) Checksums-Sha1: 43c5653a9a5b9419849173a4ec3a9855cf0327a3 1722 chef-integration-test_1.1-1_amd64.deb Checksums-Sha256: 84e2f087f7e11d1b73743007ecfc6b8b34e03f6917c0993b35c0758ee59702c1 1722 chef-integration-test_1.1-1_amd64.deb Files: 4b05bace483dbca54efc21f97ee47e1d 1722 ruby extra chef-integration-test_1.1-1_amd64.deb chef-12.14.60/spec/data/apt/chef-integration-test_1.1-1_amd64.deb000066400000000000000000000032721276456504500237120ustar00rootroot00000000000000! debian-binary 1285863038 0 0 100644 4 ` 2.0 control.tar.gz 1285863038 0 0 100644 464 ` ‹íÔÁnœ0PÎþŠù 6¨Š%‡¶R¥•²êݘÜ,ÙFêæëëÒ$mrÙ­*Í“ÆØc›Ñ'ÑÉ¥A%Äzžß×6ç…à<ÏyU†þªây":ƒÅyi"kŒÿÛ¸·Þÿ§âdl…[Fwâü—EñÇüg"û=ÿœgyAJù?¹®•e‡Û²”ºh„l3Þ e¶UÛ‹\ €ÅÙÄ ÒbÒ•¨»ž<öVzm¦GçC¯œz<˜>¾ÁFË)îïeZñªé ĸhR¹ÍÛV…ÚNe¥ð‘Í|´º<‹ÈIê_™É[sø‡õ/2ñ¼þó,¥ú?‡Tw²Ç^-?ö ­ 5ð˜o8»²jЕ_l˜"Ƕ,ØWf… m _Œ {=6hG9Á‡—=ßמfvÊ´+3^^²ÏSHÃá€íæV߇ÐyÆnÃ2ëÒviŽlgµ±ÚkÀÞJöÉŒ8¯¼Ÿë$ñZÝ¡wñ“¸ì²z~ sOtÐ W»}x׋Z¶É`?hóã§Ð\¶ëHõkÈ‹@zê×a‘˜~U„B!„B!„B!„BÎí'”1œü(data.tar.gz 1285863038 0 0 100644 1065 ` ‹í—_hEÇcôAöAÅ‚ ¢ùqæô²·{w{ÑB/l®I/»3ŠanwnwÒ½íÎl/±ÔRDKmµ© yiŸú⃠‚ô‹H,¨´F­ú"øçApvïK¬ ‰æó277¿ýÍï7ßßoöNMwl8š ×0âQðç1þ¬ë9C׳Y½7²ëíÕ3`tl!ã(è(åew«õÿ)j:dAz‹é¯kF&+õß<ý™ƒœÞ:úëùŒ!õßlý-j¦·†þ™l¯¼ÿÿýM×{ˆÇ± N¨×Ã1ãéÍÔ?/ôÏbúoýMy6v©­ãAžj?÷õÏçrëêŸÕ?ôì2™LNô¿&õßpºŽÝ·Ÿ¾üñL§þÀ‹_ÿ¶çÂaïܹûw4ÞšIŽŽ}ö•kÛ/d“'­‹wsuêÔòÎ=üÀ³wõ¿÷Ý šºo[%síèyÁXòú=Ÿ_8µ~úµ¡s¥…û®tëœ=ÿ手ÇW|0_½~‰Îþ0¸üå‡ùÇÝ]ï~ûIòȯ‹xîçƒKÏ/žþéêƒo7~ÜöØÙ—½÷‘ÏrgÎèwì:úòàWŸ>´üÑIëõ÷¿˜¿²ãUãáùé®…ÁKoš\¼\¬žxôÐÒô/KOXc·uH6¬ÿ©?Ûùwp‹þ×óâ²o÷¿èû¨ÿ5#›—ý¿T Iƒ½ÐD |dîE6¶ 6×§( ØI™"¨F  äAÿÍßÌÄßl§>3©…U“6€zPqÂd5(c2âOhOöÙ¾œ=Z^Ó¥ê3`Ô€BÈt³d{׉–«=SU”¡å×^^™C÷P²åzõŠ2NLì1Ü6oÏ,= À ‘©Ú+)xLT;dT º#ƒD{)‘|*ö1GCh 9ð(‡aáDœ[¸ð¬‰}Ä‘¸ïŠ·£‰¡I¸oÔv£ÆNv·ÐGÂAÔW@ë«-ñVÔç~_:Ýl6UG«ÒÀN»-+–/”Ê#="â–}Õs1cà}! b ù"ÕD”.j ÙkœFñ6‰g§€Ñ:oŠ öc! ©…|Í݈Nd½Ú@™¨€D¡ År åb9{™*VF'ª˜*LNJ•âH&&ah¢4\¬'Jbö4J»a¬XNÇ%6³~å %ÑQb«uneŒ×Q§­ ˜MR'¦ÈγCQ¸`Óý8ðDRà‹â$,•‰­ØK„Ç×»95Qg1kýÀiwB䈰¿WuÐÝïb¹kú ©(ŠØ=:6wÝ*ŒŠÎ] "J6±êVnâ^¾õP¤{B‘ï/‰D"‘H$‰D"‘H$‰D²>¿Vžœ§( chef-12.14.60/spec/data/apt/chef-integration-test_1.1.orig.tar.gz000066400000000000000000000003551276456504500241720ustar00rootroot00000000000000‹øµ¤LíÒÑ Â †a» o`ä¿ivE·aËjEÛp¶ëÏDµ1jEô=0¦¼ÖÆÙÀD •jFÒJÜŽŒH*¢„„’LP¬E¸ú`Såãœí}v\Yw4ùã}]ë?ªýS“î쀯 ÿDË úµø6Q–{»uÆgEy[ù·¼Š>ý) ûbAŠÐÿºú§E˜É}õÊ?šÀ)Ÿ÷k÷ýIÇšqñ®K¶ùóþ³å|‘¦)7¥ç¥+êlmoÚóÒ¤³µ£oŸÚãçv*(chef-12.14.60/spec/data/apt/var/000077500000000000000000000000001276456504500161075ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/000077500000000000000000000000001276456504500167335ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/000077500000000000000000000000001276456504500175175ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/conf/000077500000000000000000000000001276456504500204445ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/conf/distributions000066400000000000000000000002021276456504500232630ustar00rootroot00000000000000Origin: localhost Label: apt repository Codename: sid Architectures: amd64 Components: main Description: Apt repository Pull: sid chef-12.14.60/spec/data/apt/var/www/apt/conf/incoming000066400000000000000000000001171276456504500221710ustar00rootroot00000000000000Name: default IncomingDir: /tmp/incoming TempDir: /tmp Allow: sid unstable>sid chef-12.14.60/spec/data/apt/var/www/apt/conf/pulls000066400000000000000000000000451276456504500215250ustar00rootroot00000000000000Name: sid From: sid Components: main chef-12.14.60/spec/data/apt/var/www/apt/db/000077500000000000000000000000001276456504500201045ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/db/checksums.db000066400000000000000000000400001276456504500223720ustar00rootroot00000000000000b1   ½þ‘^·ý“] ðøð ½þ‘^·ý“] poolb1   ½þ‘^·ý“] ´–:1:43c5653a9a5b9419849173a4ec3a9855cf0327a3 :2:84e2f087f7e11d1b73743007ecfc6b8b34e03f6917c0993b35c0758ee59702c1 4b05bace483dbca54efc21f97ee47e1d 1722Hpool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.debchef-12.14.60/spec/data/apt/var/www/apt/db/contents.cache.db000066400000000000000000000400001276456504500233040ustar00rootroot00000000000000b1   ½þÛ-W¸3ä àèà ½þÛ-W¸3ä compressedfilelistsb1   ½þÛ-W¸3ä chef-12.14.60/spec/data/apt/var/www/apt/db/packages.db000066400000000000000000000400001276456504500221630ustar00rootroot00000000000000b1   ½þ±–àÚSP äìä ½þ±–àÚSP sid|main|amd64b1   ½þ±–àÚSP Œ äŒ TPackage: chef-integration-test Version: 1.1-1 Architecture: amd64 Maintainer: Joshua Timberman > Installed-Size: 32 Homepage: http://tickets.opscode.com Priority: extra Section: ruby Filename: pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb Size: 1722 SHA256: 84e2f087f7e11d1b73743007ecfc6b8b34e03f6917c0993b35c0758ee59702c1 SHA1: 43c5653a9a5b9419849173a4ec3a9855cf0327a3 MD5sum: 4b05bace483dbca54efc21f97ee47e1d Description: Chef integration tests for APT in Cucumber This package is used for cucumber integration testing in Chef. chef-integration-testchef-12.14.60/spec/data/apt/var/www/apt/db/references.db000066400000000000000000000400001276456504500225260ustar00rootroot00000000000000b1   ½þ>`·üóÖ èðè ½þ>`·üóÖ  referencesb1  a½þ>`·üóÖ  ´ sid|main|amd64Hpool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.debchef-12.14.60/spec/data/apt/var/www/apt/db/release.caches.db000066400000000000000000000500001276456504500232530ustar00rootroot00000000000000b1   ½þÇp7¢_P ðøð ½þÇp7¢_P sida ½þÇp7¢_PÑh^pqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰ äN3:1:91ca9531e3afa7a540cabdc6030c6f75d315fec7 :2:098c599ac5b0a98785336afb2bc9c47002570ffa07dd62321c6f70b9fdb74325 46cd71c965ce0813c94ef78c836cc7d3 104main/binary-amd64/Release:1:cde25071c5fcee59cee8dcd773ca419dcb40d946 :2:af601ce143f33405425746462973adc0fda3aceb381d1c739851b95ee0814ca3 92ed2cc14e37e9ab23466b27857d29ac 596main/binary-amd64/PackagesK áK‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦:1:ce04daff75d4b27371d691d645282b198045544a :2:15e98119705a08018d4583caabc91d36ba12e6f1c8af0f799a3ec8ca5bfaa80d c7726773341137b71cc971d44ddec4f5 394main/binary-amd64/Packages.gzchef-12.14.60/spec/data/apt/var/www/apt/db/version000066400000000000000000000000371276456504500215140ustar00rootroot000000000000004.2.0 3.3.0 bdb4.8.30 bdb4.8.0 chef-12.14.60/spec/data/apt/var/www/apt/dists/000077500000000000000000000000001276456504500206455ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/dists/sid/000077500000000000000000000000001276456504500214245ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/dists/sid/Release000066400000000000000000000015711276456504500227330ustar00rootroot00000000000000Origin: localhost Label: apt repository Codename: sid Date: Thu, 30 Sep 2010 16:33:01 UTC Architectures: amd64 Components: main Description: Apt repository MD5Sum: 92ed2cc14e37e9ab23466b27857d29ac 596 main/binary-amd64/Packages c7726773341137b71cc971d44ddec4f5 394 main/binary-amd64/Packages.gz 46cd71c965ce0813c94ef78c836cc7d3 104 main/binary-amd64/Release SHA1: cde25071c5fcee59cee8dcd773ca419dcb40d946 596 main/binary-amd64/Packages ce04daff75d4b27371d691d645282b198045544a 394 main/binary-amd64/Packages.gz 91ca9531e3afa7a540cabdc6030c6f75d315fec7 104 main/binary-amd64/Release SHA256: af601ce143f33405425746462973adc0fda3aceb381d1c739851b95ee0814ca3 596 main/binary-amd64/Packages 15e98119705a08018d4583caabc91d36ba12e6f1c8af0f799a3ec8ca5bfaa80d 394 main/binary-amd64/Packages.gz 098c599ac5b0a98785336afb2bc9c47002570ffa07dd62321c6f70b9fdb74325 104 main/binary-amd64/Release chef-12.14.60/spec/data/apt/var/www/apt/dists/sid/main/000077500000000000000000000000001276456504500223505ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/000077500000000000000000000000001276456504500245455ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages000066400000000000000000000011241276456504500262040ustar00rootroot00000000000000Package: chef-integration-test Version: 1.1-1 Architecture: amd64 Maintainer: Joshua Timberman > Installed-Size: 32 Homepage: http://tickets.opscode.com Priority: extra Section: ruby Filename: pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb Size: 1722 SHA256: 84e2f087f7e11d1b73743007ecfc6b8b34e03f6917c0993b35c0758ee59702c1 SHA1: 43c5653a9a5b9419849173a4ec3a9855cf0327a3 MD5sum: 4b05bace483dbca54efc21f97ee47e1d Description: Chef integration tests for APT in Cucumber This package is used for cucumber integration testing in Chef. chef-12.14.60/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz000066400000000000000000000006121276456504500266240ustar00rootroot00000000000000‹mRÑjä }÷+üI4jLB);´”v¡00þ½¹™¸Ä ¶ûõëdö¡Ð>×ã=Çs® ¼›3vFvnNx&9?ïÆD~aˆyÓQ^ð'û£Ki ™b¦¾–äÕdV^:úÓÇq5ôä&‹a23½ûŠüÞ~‰à{,ÀO÷÷äeŽÉ\.ØïŽîo–yö.›µ1¥¥+ËäàS,>1É!8\úè(þIÁcö¶ù «ý O™²Äâý¥œ²ËÊo“~¾m±ß¶ E–ÜÜq]Uäø¼¯TÝÑFb5°F9ï¹ÕBKÁ˜F ¶™ê–k`m+¬PÀ´jU«Yü*Ä;*¨Z Óe[ÉÛFf†0!cR00Qi#Èë£Šë” –)ke#z FI âC«e¶Ò“GŒÜr›ÇCŽG?Å£×x‘>Ðýá”Oèà ëõ‘=.Òåö1h.׈ýÖ ÿ[¾¹ù¼IäK Bþ…G„ÆTchef-12.14.60/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release000066400000000000000000000001501276456504500260440ustar00rootroot00000000000000Component: main Origin: localhost Label: apt repository Architecture: amd64 Description: Apt repository chef-12.14.60/spec/data/apt/var/www/apt/dists/sid/main/binary-i386/000077500000000000000000000000001276456504500243235ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/dists/sid/main/binary-i386/Packages000066400000000000000000000000001276456504500257520ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/pool/000077500000000000000000000000001276456504500204705ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/pool/main/000077500000000000000000000000001276456504500214145ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/pool/main/c/000077500000000000000000000000001276456504500216365ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/000077500000000000000000000000001276456504500260415ustar00rootroot00000000000000chef-integration-test_1.0-1_amd64.deb000066400000000000000000000032201276456504500343450ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test! debian-binary 1285863052 0 0 100644 4 ` 2.0 control.tar.gz 1285863052 0 0 100644 468 ` ‹íÔÝn›0`®ý~68$hªVµÛ¤I‘íÞØð0²´ôéçÐ]dë~z“T“Î'!Œ±mŽi–\\UB,÷è×ûÒf¬Œge쯪¼L¨H®`öA:JgmøÛ¸½ÿO¥Ù …Ÿáü¯Ëòùç‚ÿœÆ8/šcþ/Ž·e«¡­@çëÛò )ùVoùvCéì]æ{é ÓVeª‡veÆ“ÁØqÀ‡Ø+ǶKï¡1rL»'ÂÄ:¯XÕ´%@#dÙärSh­ªŠå²RðÊÈv::Óõ$è"õ¯ìœ=¼aý .Îê¿8ÕÁr¬ÿkØIõ(;¨éoË|çãCMYš¯¹uª7T˜]œ"½.ÉggÅ \M?YßÏ’îÍЀäHß½ìùºô¼·“WVCªìpsC>Ž1 ‡èÕƒyŠ¡ Nâ2ËÒnnŽdçŒu&k ß‚“äƒ`Z6Þ‡0ÕYŒz„àÓ³¸ä¼rfzsHÏHOô´µŽÞîöñ ½›Õ|Ú&¡ûÞx:=›³½ŒT?†¼dÆn IñW…B!„B!„B!„BèÚ¾7‘(data.tar.gz 1285863052 0 0 100644 1020 ` ‹í—_ˆEÇc * RÁÅ?ôG^L ·ÿ’ÍáYÓ܉kÛ.9ÛÊ N6“Ýémv¶;³Ý^ߤ¾O´}Q8­Å—"”"B©/‚'"È©ø¢ ¾ˆèƒ (g7¹óÎrV…¦'Ìçe2;¿üæ÷›ïï7›¨Z Æ-+³Ï†Q± £\6 S<×ÍX¹3Ž"€\D)ÿ;»ë­ÿOQµ˜EÚÓßÐ-£,õþÌCÖ¶ŽþFÕ°¤þ£Ö¿Kmkèo–«òþ¿)ú;î‘€c7BœÐ`ŒcƵQê_úW­jUê¿uôw<¸Ø§®:…;ª{ü_ê_­T6Õß´þÔ_HŸÓMÓ¬ˆ÷¿.õ¿áì:q{6nÛþÐ'ß(Ïë;o½ÌÎ}÷B£rÚ?uõëÏÝý%ùöÃóÚ3Ÿî¿`ó_§ßÙÑ\ùü•â]gc³0vÝyb±|òÙÊäÇO¯Ü»4¿\yãâ…Ÿ Ûμÿ=‹_„oÙÇ÷Ìþaù¾×_[9õÒ«¾´¼3ø*Yšzû·ÓsÚGÏ]zðÇËüþæù÷®Þröƒ#/¾|¥±ýû¹ðçý'Ýò·]ÙûÙ’vFù¥þnNr“ûŸ† q½ÿÖ×é£:^]í³b ;S·ÊUÙÿ£ í æ!A BäÌ#w¡³0¡( x’2/FÐ&ýŽú(€Ý×>9œ=yŒ†Ì¡]¬:´?9 4€¶— ¬C ‡`Š?u ?2a•'*ŒéU]W”Ùñ£>ÔbîѨÀŠÃ]›W%°GU”újù —׿P¨®×CQö ͇³.ÄAGÀ= 5‘©†+%x GLT;˜ª…Ô ?\ÊÍ|,ÐúhÊ!fX8çÖ#>|ÌÁ!€H<ôÅÛÑÁîe ݨ™“CC'´Ã‘°GöÐÞzK@|µÇy8¡iI’¨(‹V¥‘«ù+¦í³ëÓÖô˜ˆx`?ø˜1ˆð‘˜D™„€Bƒ:"J%@#@n„ŧi¼ID8 Ü0Ú㉸2?]"D!˜o8°ÕèDÖë Ä‘‰ È×Z`·ò°§Ö²[¥ÌË»ýDs¶ j33µFÛžnAsêÍÆ”ݶ› 1{jC°×nL•‹ãáca”æ %éQâîàÜZo¢GA±;¤G‘]àÆ¢pÁ¥Gqˆ¤ ÅIX**!v3?>éž]kìÚÔDµÅlðgØ ©#ÂþYÕAa·]äo胢¢(b÷ôØüM«0-:-ˆ4Ùüº[Y¸é‹{xMøÁ—RÝóŠ|I$‰D"‘H$‰D"‘H$’ÍùWÔ¨(chef-integration-test_1.1-1_amd64.deb000066400000000000000000000032721276456504500343550ustar00rootroot00000000000000chef-12.14.60/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test! debian-binary 1285863038 0 0 100644 4 ` 2.0 control.tar.gz 1285863038 0 0 100644 464 ` ‹íÔÁnœ0PÎþŠù 6¨Š%‡¶R¥•²êݘÜ,ÙFêæëëÒ$mrÙ­*Í“ÆØc›Ñ'ÑÉ¥A%Äzžß×6ç…à<ÏyU†þªây":ƒÅyi"kŒÿÛ¸·Þÿ§âdl…[Fwâü—EñÇüg"û=ÿœgyAJù?¹®•e‡Û²”ºh„l3Þ e¶UÛ‹\ €ÅÙÄ ÒbÒ•¨»ž<öVzm¦GçC¯œz<˜>¾ÁFË)îïeZñªé ĸhR¹ÍÛV…ÚNe¥ð‘Í|´º<‹ÈIê_™É[sø‡õ/2ñ¼þó,¥ú?‡Tw²Ç^-?ö ­ 5ð˜o8»²jЕ_l˜"Ƕ,ØWf… m _Œ {=6hG9Á‡—=ßמfvÊ´+3^^²ÏSHÃá€íæV߇ÐyÆnÃ2ëÒviŽlgµ±ÚkÀÞJöÉŒ8¯¼Ÿë$ñZÝ¡wñ“¸ì²z~ sOtÐ W»}x׋Z¶É`?hóã§Ð\¶ëHõkÈ‹@zê×a‘˜~U„B!„B!„B!„BÎí'”1œü(data.tar.gz 1285863038 0 0 100644 1065 ` ‹í—_hEÇcôAöAÅ‚ ¢ùqæô²·{w{ÑB/l®I/»3ŠanwnwÒ½íÎl/±ÔRDKmµ© yiŸú⃠‚ô‹H,¨´F­ú"øçApvïK¬ ‰æó277¿ýÍï7ßßoöNMwl8š ×0âQðç1þ¬ë9C׳Y½7²ëíÕ3`tl!ã(è(åew«õÿ)j:dAz‹é¯kF&+õß<ý™ƒœÞ:úëùŒ!õßlý-j¦·†þ™l¯¼ÿÿýM×{ˆÇ± N¨×Ã1ãéÍÔ?/ôÏbúoýMy6v©­ãAžj?÷õÏçrëêŸÕ?ôì2™LNô¿&õßpºŽÝ·Ÿ¾üñL§þÀ‹_ÿ¶çÂaïܹûw4ÞšIŽŽ}ö•kÛ/d“'­‹wsuêÔòÎ=üÀ³wõ¿÷Ý šºo[%síèyÁXòú=Ÿ_8µ~úµ¡s¥…û®tëœ=ÿ手ÇW|0_½~‰Îþ0¸üå‡ùÇÝ]ï~ûIòȯ‹xîçƒKÏ/žþéêƒo7~ÜöØÙ—½÷‘ÏrgÎèwì:úòàWŸ>´üÑIëõ÷¿˜¿²ãUãáùé®…ÁKoš\¼\¬žxôÐÒô/KOXc·uH6¬ÿ©?Ûùwp‹þ×óâ²o÷¿èû¨ÿ5#›—ý¿T Iƒ½ÐD |dîE6¶ 6×§( ØI™"¨F  äAÿÍßÌÄßl§>3©…U“6€zPqÂd5(c2âOhOöÙ¾œ=Z^Ó¥ê3`Ô€BÈt³d{׉–«=SU”¡å×^^™C÷P²åzõŠ2NLì1Ü6oÏ,= À ‘©Ú+)xLT;dT º#ƒD{)‘|*ö1GCh 9ð(‡aáDœ[¸ð¬‰}Ä‘¸ïŠ·£‰¡I¸oÔv£ÆNv·ÐGÂAÔW@ë«-ñVÔç~_:Ýl6UG«ÒÀN»-+–/”Ê#="â–}Õs1cà}! b ù"ÕD”.j ÙkœFñ6‰g§€Ñ:oŠ öc! ©…|Í݈Nd½Ú@™¨€D¡ År åb9{™*VF'ª˜*LNJ•âH&&ah¢4\¬'Jbö4J»a¬XNÇ%6³~å %ÑQb«uneŒ×Q§­ ˜MR'¦ÈγCQ¸`Óý8ðDRà‹â$,•‰­ØK„Ç×»95Qg1kýÀiwB䈰¿WuÐÝïb¹kú ©(ŠØ=:6wÝ*ŒŠÎ] "J6±êVnâ^¾õP¤{B‘ï/‰D"‘H$‰D"‘H$‰D²>¿Vžœ§( chef-12.14.60/spec/data/bad-config.rb000066400000000000000000000000321276456504500170440ustar00rootroot00000000000000monkey_soup("tastes nice")chef-12.14.60/spec/data/bootstrap/000077500000000000000000000000001276456504500165505ustar00rootroot00000000000000chef-12.14.60/spec/data/bootstrap/encrypted_data_bag_secret000066400000000000000000000000261276456504500236350ustar00rootroot00000000000000supersekret_from_file chef-12.14.60/spec/data/bootstrap/no_proxy.erb000066400000000000000000000000411276456504500211120ustar00rootroot00000000000000bash -c ' <%= config_content %>' chef-12.14.60/spec/data/bootstrap/secret.erb000066400000000000000000000003321276456504500205250ustar00rootroot00000000000000bash -c ' <% if encrypted_data_bag_secret -%> awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP' <%= encrypted_data_bag_secret %> EOP chmod 0600 /etc/chef/encrypted_data_bag_secret <% end -%> <%= config_content %>' chef-12.14.60/spec/data/bootstrap/test-hints.erb000066400000000000000000000005031276456504500213420ustar00rootroot00000000000000<%# Generate Ohai Hints -%> <% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> mkdir -p /etc/chef/ohai/hints <% end -%> <% @chef_config[:knife][:hints].each do |name, hash| -%> ( cat <<'EOP' <%= Chef::JSONCompat.to_json(hash) %> EOP ) > /etc/chef/ohai/hints/<%= name %>.json <% end -%> chef-12.14.60/spec/data/bootstrap/test.erb000066400000000000000000000000541276456504500202200ustar00rootroot00000000000000<%= Chef::JSONCompat.to_json(first_boot) %> chef-12.14.60/spec/data/cb_version_cookbooks/000077500000000000000000000000001276456504500207355ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/cookbook2/000077500000000000000000000000001276456504500226255ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/cookbook2/files/000077500000000000000000000000001276456504500237275ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/cookbook2/files/test.txt000066400000000000000000000000001276456504500254350ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/cookbook2/templates/000077500000000000000000000000001276456504500246235ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/cookbook2/templates/test.erb000066400000000000000000000000001276456504500262620ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/000077500000000000000000000000001276456504500220575ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/README.rdoc000066400000000000000000000001761276456504500236710ustar00rootroot00000000000000= THIS RECIPE * is for testing CookbookLoader/CookbookVersion * has at least one of every kind of file that cookbooks can havechef-12.14.60/spec/data/cb_version_cookbooks/tatft/attributes/000077500000000000000000000000001276456504500242455ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/attributes/default.rb000066400000000000000000000000401276456504500262100ustar00rootroot00000000000000#one_of_each default attributes chef-12.14.60/spec/data/cb_version_cookbooks/tatft/definitions/000077500000000000000000000000001276456504500243725ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb000066400000000000000000000001001276456504500275670ustar00rootroot00000000000000# IRL the runit_service is a definition to set up runit serviceschef-12.14.60/spec/data/cb_version_cookbooks/tatft/files/000077500000000000000000000000001276456504500231615ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/files/default/000077500000000000000000000000001276456504500246055ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz000066400000000000000000000000331276456504500274270ustar00rootroot00000000000000# not really a giant blob #chef-12.14.60/spec/data/cb_version_cookbooks/tatft/libraries/000077500000000000000000000000001276456504500240335ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb000066400000000000000000000000101276456504500256270ustar00rootroot00000000000000# 0wnagechef-12.14.60/spec/data/cb_version_cookbooks/tatft/providers/000077500000000000000000000000001276456504500240745ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb000066400000000000000000000000071276456504500252200ustar00rootroot00000000000000# a LWPchef-12.14.60/spec/data/cb_version_cookbooks/tatft/recipes/000077500000000000000000000000001276456504500235115ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/recipes/default.rb000066400000000000000000000000241276456504500254560ustar00rootroot00000000000000# the default recipechef-12.14.60/spec/data/cb_version_cookbooks/tatft/resources/000077500000000000000000000000001276456504500240715ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb000066400000000000000000000000111276456504500252120ustar00rootroot00000000000000# a LWR #chef-12.14.60/spec/data/cb_version_cookbooks/tatft/templates/000077500000000000000000000000001276456504500240555ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/templates/default/000077500000000000000000000000001276456504500255015ustar00rootroot00000000000000chef-12.14.60/spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb000066400000000000000000000000001276456504500310300ustar00rootroot00000000000000chef-12.14.60/spec/data/checksum/000077500000000000000000000000001276456504500163355ustar00rootroot00000000000000chef-12.14.60/spec/data/checksum/random.txt000066400000000000000000000020001276456504500203460ustar00rootroot00000000000000e4010abcac515ef3b78e92ee1f848a0d3bc3a526fcb826e7b4d39a6d516aa0487085c9b1be35e8d909617b250dca36dd4a55f01b7cdd310826bfd748cb27e0e43dd52b22968383c8086b06ee2d16e13574f98c058ce2bc3475b92ecf9c16e504022d60b132643986a8e7908d067526e20b4bafe1eb75349f27a4d3de02b077e76a2f59b73c14413f11e7208ae0bf6a408d51a97d490530e23476960ab8780ad86349947d82f1c9e57c85f86d71f80a6709b58be5f993a6a6df80c5a0857627d4a01e71484f6a6e983985089c00fe538e947230813c3a3e19baf6dae6db7082d07392a239ec1be385646356db3e3d76571488a6c72f0b96997f6191beea9846fc99f82a828f05af95cfc234cf681002f830915b1f3d35b2178b54a861c05d2694c5f6cfeb613a4a3670d849180461cdedf2c3cbb022608d8b19c86179d2d6da6b9acefccfc34b59663ef1282fec262bef79b2fbdd9b6669c90d6817b3762164dc309616469b33b83b1ded3420ae9177bc8f456d83939ff3c91b0a3683f3157401ceadf679c9f876da2aa413e081ee4c41d4b04f49e0c254d0082fd9bf2cb8eb8b966285be2cdcaab0ab70ea970737244b6683283598c30bdc206a05df72048b342eb40c2cd750c815d5fa944167b103ec40d60a99c49941a9e76d874149524f35ca294d081cf221757df77e027640556d983978be6b4b51aff26cd74a2f300d71chef-12.14.60/spec/data/checksum_cache/000077500000000000000000000000001276456504500174605ustar00rootroot00000000000000chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0000066400000000000000000000000231276456504500310550ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-0000066400000000000000000000000001276456504500311540ustar00rootroot00000000000000chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0000066400000000000000000000000231276456504500312040ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0000066400000000000000000000000231276456504500312540ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0000066400000000000000000000000231276456504500310400ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0000066400000000000000000000000231276456504500313210ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0000066400000000000000000000000231276456504500310450ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0000066400000000000000000000000231276456504500312660ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0000066400000000000000000000000231276456504500311450ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0000066400000000000000000000000231276456504500313050ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0000066400000000000000000000000231276456504500311650ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0000066400000000000000000000000231276456504500312430ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0000066400000000000000000000000231276456504500307730ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0000066400000000000000000000000231276456504500311710ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0000066400000000000000000000000231276456504500311610ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0000066400000000000000000000000231276456504500310640ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0000066400000000000000000000000231276456504500311500ustar00rootroot00000000000000checksum data here chef-12.14.60/spec/data/client.d_00/000077500000000000000000000000001276456504500165325ustar00rootroot00000000000000chef-12.14.60/spec/data/client.d_00/00-foo.rb000066400000000000000000000000631276456504500200560ustar00rootroot00000000000000# 00-foo.rb # d6f9b976-289c-4149-baf7-81e6ffecf228 chef-12.14.60/spec/data/client.d_00/01-bar.rb000066400000000000000000000000141276456504500200340ustar00rootroot00000000000000# 01-bar.rb chef-12.14.60/spec/data/client.d_00/bar000066400000000000000000000000061276456504500172150ustar00rootroot000000000000001 / 0 chef-12.14.60/spec/data/client.d_01/000077500000000000000000000000001276456504500165335ustar00rootroot00000000000000chef-12.14.60/spec/data/client.d_01/foo/000077500000000000000000000000001276456504500173165ustar00rootroot00000000000000chef-12.14.60/spec/data/client.d_01/foo/bar.rb000066400000000000000000000000061276456504500204030ustar00rootroot000000000000001 / 0 chef-12.14.60/spec/data/client.d_02/000077500000000000000000000000001276456504500165345ustar00rootroot00000000000000chef-12.14.60/spec/data/client.d_02/foo.rb/000077500000000000000000000000001276456504500177215ustar00rootroot00000000000000chef-12.14.60/spec/data/client.d_02/foo.rb/foo.txt000066400000000000000000000000121276456504500212360ustar00rootroot00000000000000# foo.txt chef-12.14.60/spec/data/config.rb000066400000000000000000000001371276456504500163260ustar00rootroot00000000000000# # Sample Chef Config File # cookbook_path "/etc/chef/cookbook", "/etc/chef/site-cookbook" chef-12.14.60/spec/data/cookbooks/000077500000000000000000000000001276456504500165245ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/angrybash/000077500000000000000000000000001276456504500205025ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/angrybash/metadata.rb000066400000000000000000000000411276456504500226020ustar00rootroot00000000000000name "angrybash" version "1.0.0" chef-12.14.60/spec/data/cookbooks/angrybash/recipes/000077500000000000000000000000001276456504500221345ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/angrybash/recipes/default.rb000066400000000000000000000003631276456504500241070ustar00rootroot00000000000000bash "go off the rails" do code <<-END for i in localhost 127.0.0.1 #{Socket.gethostname()} do echo "grant all on *.* to root@'$i' identified by 'a_password'; flush privileges;" | mysql -u root -h 127.0.0.1 done END end chef-12.14.60/spec/data/cookbooks/apache2/000077500000000000000000000000001276456504500200275ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/apache2/files/000077500000000000000000000000001276456504500211315ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/apache2/files/default/000077500000000000000000000000001276456504500225555ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl000066400000000000000000000001001276456504500306300ustar00rootroot00000000000000# apache2_module_conf_generate.pl # this is just here for show. chef-12.14.60/spec/data/cookbooks/apache2/metadata.rb000066400000000000000000000000371276456504500221340ustar00rootroot00000000000000name "apache2" version "0.0.1" chef-12.14.60/spec/data/cookbooks/apache2/recipes/000077500000000000000000000000001276456504500214615ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/apache2/recipes/default.rb000066400000000000000000000000321276456504500234250ustar00rootroot00000000000000# # Nothing ot see here # chef-12.14.60/spec/data/cookbooks/borken/000077500000000000000000000000001276456504500200045ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/borken/metadata.rb000066400000000000000000000000361276456504500221100ustar00rootroot00000000000000name "borken" version "1.0.0" chef-12.14.60/spec/data/cookbooks/borken/recipes/000077500000000000000000000000001276456504500214365ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/borken/recipes/default.rb000066400000000000000000000001341276456504500234050ustar00rootroot00000000000000a cat walked on the keyboard one day... (*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===))))))chef-12.14.60/spec/data/cookbooks/borken/templates/000077500000000000000000000000001276456504500220025ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/borken/templates/default/000077500000000000000000000000001276456504500234265ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/borken/templates/default/borken.erb000066400000000000000000000001231276456504500253740ustar00rootroot00000000000000a cat walked on the keyboard one day... <%= (*&)(*^^^^*******++_+_--- }}}}]]]end)%>chef-12.14.60/spec/data/cookbooks/chefignore000066400000000000000000000002611276456504500205570ustar00rootroot00000000000000# # The ignore file allows you to skip files in cookbooks with the same name that appear # later in the search path. # recipes/ignoreme.rb # comments can be indented ignored chef-12.14.60/spec/data/cookbooks/ignorken/000077500000000000000000000000001276456504500203405ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/ignorken/files/000077500000000000000000000000001276456504500214425ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/ignorken/files/default/000077500000000000000000000000001276456504500230665ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/ignorken/files/default/not_me.rb000066400000000000000000000001351276456504500246730ustar00rootroot00000000000000a cat walked on the keyboard one day... (*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===)))))) chef-12.14.60/spec/data/cookbooks/ignorken/metadata.rb000066400000000000000000000000401276456504500224370ustar00rootroot00000000000000name "ignorken" version "1.0.0" chef-12.14.60/spec/data/cookbooks/ignorken/recipes/000077500000000000000000000000001276456504500217725ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/ignorken/recipes/default.rb000066400000000000000000000000171276456504500237410ustar00rootroot00000000000000# This is fine!chef-12.14.60/spec/data/cookbooks/ignorken/recipes/ignoreme.rb000066400000000000000000000001341276456504500241220ustar00rootroot00000000000000a cat walked on the keyboard one day... (*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===))))))chef-12.14.60/spec/data/cookbooks/ignorken/templates/000077500000000000000000000000001276456504500223365ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/000077500000000000000000000000001276456504500243175ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/ignorken/templates/ubuntu-12.10/not_me.rb000066400000000000000000000001351276456504500261240ustar00rootroot00000000000000a cat walked on the keyboard one day... (*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===)))))) chef-12.14.60/spec/data/cookbooks/java/000077500000000000000000000000001276456504500174455ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/java/files/000077500000000000000000000000001276456504500205475ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/java/files/default/000077500000000000000000000000001276456504500221735ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/java/files/default/java.response000066400000000000000000000001341276456504500246720ustar00rootroot00000000000000# Hi, I'm pretending to be the preseed file for installing the Sun JDK on debian # or Ubuntuchef-12.14.60/spec/data/cookbooks/java/metadata.rb000066400000000000000000000000341276456504500215470ustar00rootroot00000000000000name "java" version "0.0.1" chef-12.14.60/spec/data/cookbooks/name-mismatch-versionnumber/000077500000000000000000000000001276456504500241435ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/name-mismatch-versionnumber/README.md000066400000000000000000000000751276456504500254240ustar00rootroot00000000000000# name-mismatch TODO: Enter the cookbook description here. chef-12.14.60/spec/data/cookbooks/name-mismatch-versionnumber/metadata.rb000066400000000000000000000003411276456504500262460ustar00rootroot00000000000000name 'name-mismatch' maintainer '' maintainer_email '' license '' description 'Installs/Configures name-mismatch' long_description 'Installs/Configures name-mismatch' version '0.1.0' chef-12.14.60/spec/data/cookbooks/name-mismatch-versionnumber/recipes/000077500000000000000000000000001276456504500255755ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/name-mismatch-versionnumber/recipes/default.rb000066400000000000000000000001261276456504500275450ustar00rootroot00000000000000# # Cookbook Name:: name-mismatch # Recipe:: default # # Copyright 2014-2016, # # # chef-12.14.60/spec/data/cookbooks/openldap/000077500000000000000000000000001276456504500203265ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/.root_dotfile000066400000000000000000000000001276456504500230060ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/attributes/000077500000000000000000000000001276456504500225145ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/attributes/default.rb000066400000000000000000000007351276456504500244720ustar00rootroot00000000000000chef_env ||= nil case chef_env when "prod" default[:ldap_server] = "ops1prod" default[:ldap_basedn] = "dc=hjksolutions,dc=com" default[:ldap_replication_password] = "yes" when "corp" default[:ldap_server] = "ops1prod" default[:ldap_basedn] = "dc=hjksolutions,dc=com" default[:ldap_replication_password] = "yougotit" else default[:ldap_server] = "ops1prod" default[:ldap_basedn] = "dc=hjksolutions,dc=com" default[:ldap_replication_password] = "forsure" end chef-12.14.60/spec/data/cookbooks/openldap/attributes/smokey.rb000066400000000000000000000000361276456504500243470ustar00rootroot00000000000000default[:smokey] = "robinson" chef-12.14.60/spec/data/cookbooks/openldap/definitions/000077500000000000000000000000001276456504500226415ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/definitions/client.rb000066400000000000000000000001641276456504500244450ustar00rootroot00000000000000define :openldap_client, :mothra => "a big monster" do cat "#{params[:name]}" do pretty_kitty true end end chef-12.14.60/spec/data/cookbooks/openldap/definitions/server.rb000066400000000000000000000001641276456504500244750ustar00rootroot00000000000000define :openldap_server, :mothra => "a big monster" do cat "#{params[:name]}" do pretty_kitty true end end chef-12.14.60/spec/data/cookbooks/openldap/files/000077500000000000000000000000001276456504500214305ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/000077500000000000000000000000001276456504500230545ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/.dotfile000066400000000000000000000000661276456504500245050ustar00rootroot00000000000000I am here to test .dotfiles work in file directories. chef-12.14.60/spec/data/cookbooks/openldap/files/default/.ssh/000077500000000000000000000000001276456504500237275ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa000066400000000000000000000000101276456504500251020ustar00rootroot00000000000000FAKE KEYchef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/000077500000000000000000000000001276456504500250465ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/000077500000000000000000000000001276456504500267115ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir000066400000000000000000000000361276456504500331720ustar00rootroot00000000000000this is a dotfile in a dotdir chef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/not_a_template.erb000066400000000000000000000001611276456504500305310ustar00rootroot00000000000000# This file is not a chef template despite being and erb. # It should not be included in a cookbook syntax check chef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt000066400000000000000000000001301276456504500310120ustar00rootroot00000000000000# remote directory # file specificity: default # relpath: remotedir/remote_dir_file1.txtchef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt000066400000000000000000000001301276456504500310130ustar00rootroot00000000000000# remote directory # file specificity: default # relpath: remotedir/remote_dir_file2.txtchef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/000077500000000000000000000000001276456504500275525ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile000066400000000000000000000000621276456504500314770ustar00rootroot00000000000000this is a file with a name beginning with a . dot remote_subdir_file1.txt000066400000000000000000000001451276456504500341570ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir# remote directory # file specificity: default # relpath: remotedir/remotesubdir/remote_dir_file1.txtremote_subdir_file2.txt000066400000000000000000000001451276456504500341600ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir# remote directory # file specificity: default # relpath: remotedir/remotesubdir/remote_dir_file2.txt000077500000000000000000000000001276456504500344345ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir000077500000000000000000000000001276456504500372765ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdirsome_file.txt000066400000000000000000000002051276456504500417760ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir# remote directory # file specificity: default # relpath: remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt chef-12.14.60/spec/data/cookbooks/openldap/libraries/000077500000000000000000000000001276456504500223025ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/libraries/openldap.rb000066400000000000000000000000751276456504500244330ustar00rootroot00000000000000require_relative './openldap/version.rb' class OpenLDAP end chef-12.14.60/spec/data/cookbooks/openldap/libraries/openldap/000077500000000000000000000000001276456504500241045ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/libraries/openldap/version.rb000066400000000000000000000000501276456504500261110ustar00rootroot00000000000000class OpenLDAP VERSION = '8.9.10' end chef-12.14.60/spec/data/cookbooks/openldap/metadata.rb000066400000000000000000000006541276456504500224400ustar00rootroot00000000000000name "openldap" maintainer "Opscode, Inc." maintainer_email "cookbooks@opscode.com" license "Apache 2.0" description "Installs and configures all aspects of openldap using Debian style symlinks with helper definitions" long_description "The long description for the openldap cookbook from metadata.rb" version "8.9.10" recipe "openldap", "Main Open LDAP configuration" chef-12.14.60/spec/data/cookbooks/openldap/recipes/000077500000000000000000000000001276456504500217605ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/recipes/default.rb000066400000000000000000000000521276456504500237260ustar00rootroot00000000000000 cat "blanket" do pretty_kitty true end chef-12.14.60/spec/data/cookbooks/openldap/recipes/gigantor.rb000066400000000000000000000000521276456504500241140ustar00rootroot00000000000000cat "blanket" do pretty_kitty false end chef-12.14.60/spec/data/cookbooks/openldap/recipes/one.rb000066400000000000000000000003521276456504500230660ustar00rootroot00000000000000## # Nodes should have a unique name ## name "test.example.com-default" ## # Nodes can set arbitrary arguments ## sunshine "in" something "else" ## # Nodes should have recipes ## recipes "operations-master", "operations-monitoring" chef-12.14.60/spec/data/cookbooks/openldap/recipes/return.rb000066400000000000000000000000501276456504500236170ustar00rootroot00000000000000# CHEF-5199 regression test. return nil chef-12.14.60/spec/data/cookbooks/openldap/spec/000077500000000000000000000000001276456504500212605ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/spec/spec_helper.rb000066400000000000000000000000001276456504500240640ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/templates/000077500000000000000000000000001276456504500223245ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/templates/default/000077500000000000000000000000001276456504500237505ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb000066400000000000000000000001101276456504500314720ustar00rootroot00000000000000Template rendering libraries should support different line endings chef-12.14.60/spec/data/cookbooks/openldap/templates/default/helper_test.erb000066400000000000000000000000251276456504500267550ustar00rootroot00000000000000<%= helper_method %> chef-12.14.60/spec/data/cookbooks/openldap/templates/default/helpers.erb000066400000000000000000000004531276456504500261060ustar00rootroot00000000000000<%= @cookbook_name %> <%= @recipe_name %> <%= @recipe_line_string %> <%= @recipe_path %> <%= @recipe_line %> <%= @template_name %> <%= @template_path %> <%= cookbook_name %> <%= recipe_name %> <%= recipe_line_string %> <%= recipe_path %> <%= recipe_line %> <%= template_name %> <%= template_path %> chef-12.14.60/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb000066400000000000000000000000471276456504500315170ustar00rootroot00000000000000<%= render("helper_test.erb").strip %> chef-12.14.60/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb000066400000000000000000000001231276456504500315010ustar00rootroot00000000000000before <%= render 'nested_partial.erb', :variables => { :hello => @test } %> after chef-12.14.60/spec/data/cookbooks/openldap/templates/default/nested_partial.erb000066400000000000000000000000201276456504500274300ustar00rootroot00000000000000{<%= @hello %>} chef-12.14.60/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb000066400000000000000000000001041276456504500313410ustar00rootroot00000000000000Template rendering libraries should support different line endings chef-12.14.60/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb000066400000000000000000000000471276456504500304000ustar00rootroot00000000000000slappiness is <%= node[:slappiness] -%>chef-12.14.60/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb000066400000000000000000000000401276456504500322360ustar00rootroot00000000000000super secret is <%= @secret -%> chef-12.14.60/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb000066400000000000000000000001061276456504500316720ustar00rootroot00000000000000Template rendering libraries should support different line endings chef-12.14.60/spec/data/cookbooks/openldap/templates/default/test.erb000066400000000000000000000000371276456504500254210ustar00rootroot00000000000000We could be diving for pearls! chef-12.14.60/spec/data/cookbooks/preseed/000077500000000000000000000000001276456504500201535ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/preseed/files/000077500000000000000000000000001276456504500212555ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/preseed/files/default/000077500000000000000000000000001276456504500227015ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/preseed/files/default/preseed-file.seed000066400000000000000000000001141276456504500261030ustar00rootroot00000000000000chef-integration-test chef-integration-test/sample-var string "hello world" chef-12.14.60/spec/data/cookbooks/preseed/files/default/preseed-template.seed000066400000000000000000000004211276456504500270000ustar00rootroot00000000000000# This file should never be executed by the preseeding tests # This is here to verify that templates are preferred over cookbook_files when # preseeding packages. chef-integration-test chef-integration-test/sample-var string "WRONG-cookbook file used instead of template!" chef-12.14.60/spec/data/cookbooks/preseed/metadata.rb000066400000000000000000000000371276456504500222600ustar00rootroot00000000000000name "preseed" version "1.0.0" chef-12.14.60/spec/data/cookbooks/preseed/templates/000077500000000000000000000000001276456504500221515ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/preseed/templates/default/000077500000000000000000000000001276456504500235755ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed000066400000000000000000000001331276456504500316420ustar00rootroot00000000000000chef-integration-test chef-integration-test/sample-var string "<%= @template_variable -%>" chef-12.14.60/spec/data/cookbooks/preseed/templates/default/preseed-template.seed000066400000000000000000000001351276456504500276760ustar00rootroot00000000000000chef-integration-test chef-integration-test/sample-var string "<%= node[:preseed_value] -%>" chef-12.14.60/spec/data/cookbooks/supports-platform-constraints/000077500000000000000000000000001276456504500246125ustar00rootroot00000000000000chef-12.14.60/spec/data/cookbooks/supports-platform-constraints/metadata.rb000066400000000000000000000001661276456504500267220ustar00rootroot00000000000000name 'supports-platform-constraints' version '0.1.0' supports 'centos', '>= 6' supports 'freebsd', '> 10.1-fake-p12' chef-12.14.60/spec/data/definitions/000077500000000000000000000000001276456504500170465ustar00rootroot00000000000000chef-12.14.60/spec/data/definitions/test.rb000066400000000000000000000001521276456504500203500ustar00rootroot00000000000000define :rico_suave, :rich => "smooth" do zen_master "test" do something "#{params[:rich]}" end endchef-12.14.60/spec/data/dsc_lcm.pfx000066400000000000000000000050451276456504500166620ustar00rootroot000000000000000‚ !0‚ ç *†H†÷  ‚ Ø‚ Ô0‚ Ð0‚‡ *†H†÷  ‚x0‚t0‚m *†H†÷ 0 *†H†÷  0ÅIª— ½€‚@º"¿²ËvòŽoÀ‘¸±àíǧJÐ'" r¦C}-~Ö¿Û*í&šƒ›ñ&ffÄæ/ØÃ&p’¿ëYÂ^é}Òlª°[@HY*Ð;1É.&™’âI’2ÿÞÀÝ£†"ó_Ó#,‚¸ï–±ñ˜Tø µzٔ⎀³K!ã› BÇž{¬«ƒP¶ ]€îþiaFþ­¨ÏßÝö€…öÑHç I¥S-/X‡Þdœ6‘^žî­ô“QAò Þ¸°az”¼£0 eôíò†€mëFëiN·e›ÆQ‡‹ÂÀù¥yÊ2­&\ßéÁÃËíÇÕ`‰Á¨éÓúEK¨8tÉ‹5'³!ÎØTÿóó‹Ô´÷AZÉ£‚ìŽw°æNÔ óÍåÝå)Ò‰µÁG˜³¡—è©õ>§©†t”¸ŽDüª†«õ$ DV¦ZÑ?/Y ƒé‹Eýse¦fú¥ aÝÄÛ+¡`ŽY Îï D$Æ]?Êã9«çUU?ŠŠ¦ÐX‰ž?‚ˆçH?ßx§“E˜œÐ-lJK‹¾Éİ^Ôê­¢)ðk@§Ì ò>ºìpT‡3—øÜ[ÇC¦«þ™¹k}&F۲ÎÅ·F‚V¬3 :˜ÙYQ?´<ËBâ¿ ¨Íâܘzh|þü^ZRT4²¢g“Ç‚a»=…‡ÿ,ƒûˬ+ÿ1«†­‚ÕÑÿ<ŸÓá;vSqE®b ‚aêáÊùŠ©Œ«O›,`ÀHGüJ_“Gßí6Ê.4Ëß\:kMÐvѨÊi[¤ÉÔíF µåï_;ø7§ËŸ&±0,–‘u$=Î?ç6’ÜÚØ*¶¿,¶[%>]AH;8!òª|Äe,Õ¥¥²‡Ns±iÓÓ--:ˆ4²f¼ì’«©‚ÑØ©²ìãôuHਃ{“[§XŽQ¹ÆÂ¼!Ý4Iá]âß¼#¤ÆëÀnúKZ;¸ïTíê¯Æ;<¸­Œ¬ª$ÇsOÊí èÙËQAÒݫ߼@ãáT<ÕjLÀz 6O)¨¢˜N(„kŸShPÂ`æ½³Àtä÷v%²1d…Õ]’ŠZ¦#ÆoRFÎ5)ºj†Îj° ÍC{í ¬Ràdo¾Æ¦ì{ô#"bµ*15Ñ, (%.ž*0ÿE^9ù 2éZ6WlØòÒà¹5Ä…>öãüƒ_‹ìA®z‚;ÓÈáæü ]KKA(o‘ ·LÁSX[ˆFÈÇAƒ™•jä ÑÔ2^ÿ]„å,ü!iOÑë«éü߇‚?íŸ~û˜úø¨!@  vùD $#íó-H ®4ÙLÖ×k¤³>À !Oâé/_=IdØ` –¿ÍÈ‚a`ûi¸òÀ}×|!†—œc{°´¡ßÕÁ·@WM†¹Ñ§#kßÅ®(W••¼yí[ ORTýýà ¼J)êþé^j‡Ã|å$šH:;¤“„Kg „êŠzûVæ „7 °½‘ŒiíPŽ Ä$\3¨¢´mª§Fûr¿çLä_»–|¥ ¦c»½Ôèi¨úXZ·%Ä냒«âɰ@X,þßÔµÜN³†L±œ±l](¤Pƒ Æ„1 ˆšâ¡øYiˆ|G€Vª®o4¸F•KEQ’w¡›Ó·lQ]r™¥˜=ùt®\Ú7ñXzÝ™šŠÝ27­@K\zƒÄõŽ+9˜$t“¡#r$$Ã|³‹Þ }™ ;à á&ã WE¯CfpE=v%Ð̦Žë‰Úqmaóš“9—.²GK0›Žže 8] ’a£¤ÝÍã®ñÍ~KµkêœáÒCW‡´µè0Xhfí3WJ³©¢?X'WfTÀÐ}Àò8#}4å¿Cx™S@¥@÷›TªQmc¯œ%—t¸Ì}~º+`æRžçmÛ6¾’ãÀ?³­–‡á1Ã,,‰-{Ó Áâü…y™"¾—5n…+™±Ëê+ÕâÎ{‚W±ÕO…ÉH€v9NêPÉ[þ.Œo”ÞqƒVñt$´ŒÚvÞ,mØË3à§9¥´Ks &_.ùZí­z»Í !ÜØWò xfêzèZê¶)øcþÖì¯ A§*ºyù(—’ŒDé»!qÊ&—·1%0# *†H†÷  1Ìݼ£˜°òÅæ]<Š¿Q„š-ä010!0 +'ð‡EpK‡gòÚ®Õm«Qz|1Ýá²þƒÔchef-12.14.60/spec/data/environment-config.rb000066400000000000000000000000671276456504500206720ustar00rootroot00000000000000# # Sample Chef Config File # environment "production"chef-12.14.60/spec/data/file-providers-method-snapshot-chef-11-4.json000066400000000000000000000056011276456504500247600ustar00rootroot00000000000000{ "Chef::Provider::CookbookFile": [ "action_create", "file_cache_location", "resource_cookbook", "backup_new_resource", "content_stale?", "diff_current_from_content", "is_binary?", "diff_current", "setup_acl", "compare_content", "set_content", "update_new_file_state", "set_all_access_controls", "action_create_if_missing", "action_delete", "action_touch", "backup", "deploy_tempfile", "shell_out", "shell_out!", "run_command_compatible_options", "checksum", "access_controls", "enforce_ownership_and_permissions" ], "Chef::Provider::RemoteFile": [ "action_create", "current_resource_matches_target_checksum?", "matches_current_checksum?", "backup_new_resource", "source_file", "http_client_opts", "diff_current_from_content", "is_binary?", "diff_current", "setup_acl", "compare_content", "set_content", "update_new_file_state", "set_all_access_controls", "action_create_if_missing", "action_delete", "action_touch", "backup", "deploy_tempfile", "shell_out", "shell_out!", "run_command_compatible_options", "checksum", "access_controls", "enforce_ownership_and_permissions" ], "Chef::Provider::Template": [ "action_create", "template_finder", "template_location", "resource_cookbook", "rendered", "content_matches?", "render_template", "diff_current_from_content", "is_binary?", "diff_current", "setup_acl", "compare_content", "set_content", "update_new_file_state", "set_all_access_controls", "action_create_if_missing", "action_delete", "action_touch", "backup", "deploy_tempfile", "shell_out", "shell_out!", "run_command_compatible_options", "checksum", "access_controls", "enforce_ownership_and_permissions" ], "Chef::Provider::Directory": [ "action_create", "action_delete", "diff_current_from_content", "is_binary?", "diff_current", "setup_acl", "compare_content", "set_content", "update_new_file_state", "set_all_access_controls", "action_create_if_missing", "action_touch", "backup", "deploy_tempfile", "shell_out", "shell_out!", "run_command_compatible_options", "checksum", "access_controls", "enforce_ownership_and_permissions" ], "Chef::Provider::File": [ "diff_current_from_content", "is_binary?", "diff_current", "setup_acl", "compare_content", "set_content", "update_new_file_state", "action_create", "set_all_access_controls", "action_create_if_missing", "action_delete", "action_touch", "backup", "deploy_tempfile", "shell_out", "shell_out!", "run_command_compatible_options", "checksum", "access_controls", "enforce_ownership_and_permissions" ] } chef-12.14.60/spec/data/fileedit/000077500000000000000000000000001276456504500163205ustar00rootroot00000000000000chef-12.14.60/spec/data/fileedit/blank000066400000000000000000000000001276456504500173200ustar00rootroot00000000000000chef-12.14.60/spec/data/fileedit/hosts000066400000000000000000000001541276456504500174030ustar00rootroot00000000000000127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost chef-12.14.60/spec/data/gems/000077500000000000000000000000001276456504500154665ustar00rootroot00000000000000chef-12.14.60/spec/data/gems/chef-integration-test-0.1.0.gem000066400000000000000000000170001276456504500230130ustar00rootroot00000000000000data.tar.gz0000644000000000000000000001007300000000000013320 0ustar00wheelwheel00000000000000‹cÚJìZmoG’ÞÏü½+#Zvœd“`?(–¼áž#’¼¾ ȇæLÙçá ·{Fï×ïSUÝ3=$-¸Ë-ð ˆEr¦ºº^žzªzÞÌ_]]ß]ýá÷¼Îq}óò%ÿ‹kïß_?ù"~'ß?ñâÛó?¨óßU«pu¾ÕËoWÆTOÜ÷©ß÷7÷or©'¯‹ÎWF½±¹©½™|ì¶¿çmS«³óLýM×v;õâüüåñ'Vm»ùþÙ³ív;Ó¼À¬qËg•,âŸM&÷W·?ß©‹ëKõêæúr~?¿¹¾S¯onÕ»»«LÝ^½½½¹|÷Š¾Îø®ËùÝýíüÇwôÍdò|¦.MikÛB'?›Ó°…©ò+]Ujmt­Zl­5ní•® •7u!¨²qªó&SÎl\St9}‘º±°¾uvÑÑ—J{UÐb¦P‹º3¹Hxá®é–+õjJ|°¸¯É»µ©Û‘F;P)o6;g—«V5ÛÚ8eð”mwJwíªqöx1rìöv¥[…å–Nã©zÉ7…íÇ¥ÍRWꊅ,ßÕ´/VÚ(³ˆ¸>v{IFƒ_ƒ^ÖxYl]SeJ;?T¬kF› o»ºÀcy³^75‰ w©­mW"D–š©×c 6Û4‹ÁŒ½oÙ#Ó bÊ;ðêÄžÊsÍÖ¸ Îrð -okù;Sm£r ÿÒ}$B¾ç];µÖµ^ò­è»|TÊ €w Gó¢š÷ÖØZ ˆ8±ÐáWvCbJ[Â|ãr’{òõùœòZ L"–f)] 0‚š°8œâŒâ oajl<·pÜHt¢aðî/M7U'xþrÓÓÔÁøìð`‹Ž9•†=m¡¤õ¤Ô][ï9ž9˜$ÆÙ ãxºÃ:9r y³Þ§3¥qÏò¯%›øÉ_7…ÅŽ4g ûÒÖyÕñö‘]ªnZUÙµ¥uá2ß”í–bÈójpAsǤb)$C~ÍbJ—vÙ9þN¨L„ƒ›ÅÃå‡ëz'ßÁø]ű_ºfó•®¡,?\_{ºMǨáoªð±TZ‰IXV6Þ ØÛòac)SV+lm Cu|=ÚdCØÝƒ¯'!’‘kSX­ÚݦßêûÆ}8Hò-¾dEQ(–†ð¶uÔ^‚[l¶²ÖPáAÛJ/ª˜Ï Âd„ˆb¹ñ¢%Ï#Ha븳G)1 î´lGݶTØ*QOzþz›G½Þ`M<TFËStÛÅfc°æ#²¤j¶§aç—ÆÙØìÁ(2‚Ÿî{š¤ßwØ4‰‘}G}Ú“ŸjN°‚ ÈFˆêÐ:ìŠóíÊæ«˜ÜðK ìF²9ó`Ùk¤0GHe`ÒÆÅOx>x4M’DEÉx„ÛZc™¦â€Ç3vik,qèÞC@eÄ)G霩}“‹Q¼O±ìøÎ¬µ•¬3í8"ȬýÚ8Síæõ6ÖQAñPëµ9þµ@Wêœñ=‹%­·â:dÓ”ÁÁ¯…C%>êÜýï1®Ô[,dR¬{½$iäŽÒ"0Óˆ1øüø1³$æ[ìëV º¾[ D^À!Ä ³b!ÒyFáƒÊÏåêô$ʧ,‚`•צˆ^°„>Â,>¯&«i¿›) ’ªÜã*ž02Ë5@ӌ̾ÐÇËÖÑC5“ƒ®æV罕Í`²Më‡\`ƒûìÉ"@”Jǃ6À6[Ñ“D%u¦ç)~ç[³ö=£>v†Ð?ç’~OS­6ÑS ÔÊYD†‘Ãó’­À5óÎs9æåÖŒ|Ô½gø %Å<Æ·ƒ;ð›wMç‘•kí>޹º02Þ.kFnÄy„-y4Ü|¦×0°ViΦ{¹¹ÇpûÝÆìzš¤#¤[ï-§VPca7àp†Ñꦋ„óæ⤢ó–ÒJÄ3I-Ë‹™ú+±ZðU¿åHxÔ]'å0äÑþ!¦PЬuM%FQ„ P•™p°5l¬kcZXƒc V[Kt nê3v²Ç.éã(‰[R—ÒìtÕîÎJgðÉ‚l=49ñ¸ò†‹–Š} nGþl(R`+àñ¦[àA˜ ¡¸©4â¸ÿªJqôüM(ÿi‡ÔSìO™®¬u¤ô2\À_%îx« 8ÿ}}q‚g̦¥ÌÑo#j^Sµ‘-&¾[†¤•~0L¾XnN›²$î7 Tþ„h\+nèS;ÖÀÔ6xC´mñH\Oo6õsM ÿ²Y ˆ‚Ry¥- ,÷Æ=Ál,!5g5rÒ{í,§]é€&±‰0– VšË'þíeS›PÆd =©ægöà}HóŠ#´â5V+Èß’ícš©yI®–ÞÃs(d{´v)‹ë¥¦Ÿ®B+|2!¸®ñþŒ-DÚçMGÌF>ÃÉZUzë;ÛÒ+³‡•¢Ú¡xïÛSPň.*ûÐÂ!ùàŠ]ÜM´þšy#dI‡ó™Øê…,ˆ ÈŸP§"ål§Ü#_qLhyTob„õæ„(êÅ d÷Ë™º5é@eÆ‹®õnÀ¨}H¢ÙÈ=py‚y±ˆÇa¥pÅñBŒÿ6R=ǽ¨ÔÚ`R6tl„?kcÄ›eS¡‘BQèû‰41§²»±´$5I+aùpŸÅ¶~R *]›Ó íûþ.y¼Ú"YM晥¦…Úa™…8Šðv[S0H‡æã„T}¸’@ê„—¼{#B’5ódMgZ¤M™kÒ3-‡.û{ŠKöK ^Ï(o†b–…ÈÍÚ Ct&‹Åž.ô‘}…ýH#D“&ª•ø‹X­¢aV‰Ò@»#ûI¹6”Øóþæ+§<½“CgE.^ßÜÏ_]M‘O-[—’)H'ÆWHs&Éç#)p`JöN”»: wé‚;¸!¬ÌQ;¼hšxF˜8ÍEÖ<ûCFÇíyÔQPí©qéçÒáþ!ÁW°Ü÷QAµŒ;Xeÿäê?¤8<Ф>UÇãeË1¨¼-‡‚u(¼qÙžYud^É@(0ò#–)Ó,àK\i®8£½ízOÔ4ÄBJ…ßh´x÷+iv‰ö울–‹»ô§ý ´}è ‰>$Š„¤aìÙÒ=Ü£§¦¿5iØEQã`•Ï ôLÌíaù~+ܶЄ (L]tëÈG‘B¬è¼:±Eã[?š(<ÞAw"¥Úu£ cеÉ@ä™;ò„ZjôÞŒ(ž$ÝSMine‰:Žxæê†_GNBDFr Ò”GôÈBJ”܇í>BýÓVŸ&,ŒÖó®a郘Q¹ì/T™ÉR¼ ¾-Øã߃ù¿æ¶" ¾¥™Ÿ©w5*žg™G,‘[ê)Y\r óÝ>›Kæ>ÉÐ磃žÀ¯i­ýù‡°®E:ýìö'ðV0 y^øcÏð0z|z¢?šàº°h¤ñ¡d\rÿD€•òÀÜ›ÂÈEyt@XBª¿Ì a¶¾ùX¢iâ¸Þ…à®Ç<š<4#gog–ÚÉqÉ>×§é÷7€³ÈB›¥rs ˨kf4¡ýsæ ‡"Ty=<@µ—½‡òܬé•ô€YA rì+Ø^H>-†•1S¢—pXçÛ™º´ž;:d,Õ{ð@Øb×Çx¯äb'!7²ÔÉ„Ìf‡q§0L‹²Á=!ý ä iIÝ÷¨õKo¥¡Þȧ4`O/îÔünª~¼¸›ß±5ßÏïºyw¯Þ_ÜÞ^\ßϯîÔÍmzb|óZ]\ÿ¢þs~} .båÄò‘†>lÀ2HÉØpÈžê:;tŽln=ÜLÂz÷óû7Wl|}6¿~};¿þëÕÏW×÷™úùêöÕOÐïâÇù›ùý/*¯ç÷×Wwr¦}AÞ^ÜÂ7ïÞ\ܪ·ïnßÞÜ]Ie”ó®ŠéP{ƒå,ÙùðAz®$,à$×lœ%jÌ›,Bô;Ù€šÉüPÆpÞƒ°Ð.o­g\öMnûÆS 9œòh2=Ô§]ŒFÆÐY%íéL\6õŸúsì˜yQìO7¿Üz0xôº× Ðò¤ÄÆãDJ¿?ö'xÜ#ËÒÈ{@+e’i‘Ôb^ŽŒÅgb¦ä™kªÉñw¬Öá¡c…;¿Šípùp³÷§XÕs±í RŒ„yšà„3¢;ìöCÿ?Ýf]A¸Ãs‡fæ~žCQœ xœ•ËÍ Â0 @á{¦ð çÇn"!ĉ˜ÀI\‰6Uö§+p|ŸôFW"’ 1æ*vvb]Ê1¸€ÈjS`ò9U"³K×m@š\µä™¡Ï¥hqif…jõœ"!¶|ÇÒ:¼t,ð”OiÜŽ3m?J«z-m½ƒõ‚EòœÍ©ë{ ýÿ4sk […,Ýü^=@&ž xœ•ËÍ Â0 @á{¦ð 'ÎO#!ĉ˜ q\Z‰6Ukö§+p|ŸôtŸ*qÁÜl@ªn”Jˆ:bá4ŽU¼˜­ì²*T§åL¶Hò8ä"¥q‰ÒØ& .EK[6å«Sßá%:Á³|¸¯p;ÎxôíàÞäÊ}¹ƒ¥è=ÆÁf¸`B4§.³ªü•Cçõ ùc¢B{˜ xœ•ËÁ ƒ0 @Ñ{¦ð­H0HUÕSè©íˆH€+p÷/+ôøŸô}Wå)‘ö"Tˆr'Hc*ÈU”R¦w«Ö<†òõÙvx©Ïð, ۷㌇}6Ñ+Ûz‡Ø)á@y‚ b8umîúÿÚÖ¼•%üP 3ŒÆxœ-ËM 1 @á}O‘ Xû 2¸òž “†QqìàÁÛ;‚»÷_ï¦FM¦\J 1Žx(bRU²U%iÏ™‚åZ\ÿ,ÚæùÖ]— ÞäÑã/'{ÁÅúÎòÐö„ãºÍ©-«¶j~#PÈ1bfN°CFtîÏ¿ô#'†©xœ340031QHÎ/Í+)fä’`®I9b—-è1÷Ë~îyÛ.ÜoQ‘–ŸŸ”XÄ.v*²ýôÏÚì­ž û–­½§¢Ûþ ª"#5''_¯¤¢„a}˜Ú–…"«O7]³Ý~‚CÇÆ=ì©4®)KgmxœËL› 3Qß †gxœKsŸ 9ÑC P^¥xœ340031QÈHÍÉÉ×+©(aX¦¶e¡ÈêÓM×l·Ÿàбq{* ñ`4xœ342æΡ4xœKJ,â@>xœóHÍÉÉ×Q(Ï/ÊIQä$ò”}ëÏ2ű¼Õ©µtÃ`â€$¨Ûåchef-12.14.60/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle000066400000000000000000000067071276456504500277420ustar00rootroot00000000000000# v2 git bundle 2404d015882659754bdb93ad6e4b4d3d02691a82 refs/heads/with-deploy-scripts PACK&šxœ•Œ[jD!Dÿ]Eo`B;>Z! ÙJ_mÉ«¨ÉîãòQ§àÔ"€$Xˆã½¯ãDw|6"Ps0%ù¢:¹d§ƒYÂb²ˆµ)8&½×6#G.I—ƒâ÷z¶™¯*çÌrJƒÏM_­ÏÔ²|¤öz€6û/8-ÜÕ^_u-ù¿©JýõÛÛ„zÁzÊ®ÛÞ ñyœ¾a¦QûšêêÈM§xœ•ËMn! @á=§ðÁØüIUÕ«€íª£’YôöͲ|ŸôöT…”b¢`¹D¤¢”k”˜µ†ˆjæì|¨f”©×Ôê¹FœP²‹Yë‘‘¢ÇœŽ\ɺo4å¹ú)שm‰6íðñª¯>wÑ;÷Ç'8ô.%ŠtÀÍFkÍKçÞúþiŠœ×mêè :Zÿƒ©«?'+pi­þ…Åó{™?Nx”xœ•ŒAnÅ ÷œÂh‘ªªW1¶ÑGJpDøêõ˺|£73‡ø,%—L®–Ý“E‹G¨¾†"'ï­G:(å`nÒ'ììܹQ–-ÕX¼Oi 1çHv=·Õ«Å|Ï—`ìM·å…¯µ~ô~HY>I¯op[tËÉ1ÇMÖšE¯6§üß4È ,vϦ´ÂKa*МåÝùháKÌ\ZQ1–xœ•ËAŽÃ @Ñ=§ð:2' UÕ\Å[AjCnÏ?½Â,ÿ“¾ H/$9ò¾hP]‹dÕ"÷¨YWR¢½*&wñÓSØÄï(„qY1qAN!V¦B”$Pð¹¢ã·}@å³ÉsVyJ‡û·~û5K¯òSúë~!¿aÙà 7D÷ÕW3“ÿŸÎŽ6*|dÌÖOè vÌv² “iÀ×åþCÜL Ÿxœ•‹Û !ÿ©bаÀñHŒ±•–Ü%'@ë÷Zðs&3k0ÆÒDˆ•¢äKAv} DNÚœ”´ÚöAt\(“-•¨Œó>²Î:éª1–>i§Ã}ÖÞdªŸ3óÉ ½ZŸ©e¾§ö~ê Êz¸I'¥¸ìûX‹ÿ?ÅäÔj†/y´ ­ÀÚÏÔ»ø’yIG• xœ•ŒQ à ÿ=Å^ EcÕJèUŒ>‰dƒnï_¡'èç¼á4€"üÂêl(ÚÙ¯ xœ340031QpOÍMËÌIeè~'y§0ß6ÞÿŒ°ýwÑwÞ7DQ¢—“ŸœÍz`úáÿ ÛùÔÙ½Z¼Õ%àUÞǨº WG_W½Ü[>†ßré'¢îËžSߺ¹ô›¢‰($0m1›YõàõÄ:¦A§›‰?'½ w‡È¦¤ääW2üôzÈx”µé“Ûny¾€¾ÚŽ‹›W"VG™>xœKOÍUP*ÎÌK,)JTâ$¹š¿xœswõåRP(.HM.¶2Š“³4 õLô 5áºEù%©É%™ùy 9#=ˆDÌ,ÎÌK,)JIë#K+hÔÙ)EuìlA´ž²,ŠÑ`…F0é’Ìœ ÍP£!rP®Ç7ÿ ß` \QiR%—‹k€«Ÿ‹«Ÿ³§+Hê:.zQ:¼¹xœ=AK1…ïùzð¢»ÕÞz+RÄ‹»à9ÍΚÁÝ$M&Öþ{'X„0ä½÷f…ap|}Û ï;쬌<è±(R(¼¤™ô V²…M µÐ‰*ñ„‘Ò¯ÈTbÍŽÀÁ8OSgÌý«G54ø…Å+þd ;dËsiÐ{œjƒYQ̹rVS9Ï,´ £)‰Oêø¦\8†‚8ÝÜʾÄüÕÝÂë<âDXªóXb¦–Œg¦ *2+ìRÌÂáSÌxÖ¨­ú ¼mÕO5ŒÚy"+UÝO:¯±ÂÙ—õ™Ú‚ÐT¬C#¶M„ÖÖ(G1<å®á>¹•J±Ã‘ ¶…I۾ׯ‡â–ÎÅ¥Z?®ûõ¦×ù t^–ÙüœF¢xœ340031QH,(Ð+JbÐXºþ™Ehª”Ëú»è÷ì ³3?¾L ³xœK Ã0 ÷:ÅÛl=“j«µ¨±Yýäö5Y½y0.Ç[]°MíÎÑSé–PŒ€U'QSQñŸjö¸FcÇW—ÈÜÚókýû¥ŠŒf'Ê ä0?I/ô¬'£ xœ340031QHL+I-Š/J-.I,*Ñ+Jb¸º2U¨®M:Æ»æbMÇÝSm^.†µI©iùE©ñ¹™éE‰%© ÅI º525‹NÔxü’ ]fÈ0o»ªb$“ ¾ìʱ¶»/Éÿô¥µç®@ëP6Ó5¨Š‹+ss2ó²AŠÍÓÄ’Ò?M=਴ãsŸF„ÆÑéÊ;ðC›¹xœ ÊA € н§t¯—è2á#Dw¯·~AÆUd¶ãìEVÄСy(›«[‡øð,›Å¿¾‰ûHZ ˆI5FÞôî¶æ¶xœSV((OQ(ÎÈ/ÍIQHJU(ÉHU(JÍIM,N/H,ÉàJËÌIUPR®vÉ,Ò*­ÕOJMË/JÏÍL/J,IÕ+JRââg?TjOxœss›àÈZRQ¢Äoï»xœ ÊA € н§t¯—è¢øÅÀPÆÑÝë­Ÿ“yYmœ½H†°Ai!ÎÄfêÞ!Ö=Û®þ¯oà1CFЍXLJϛ֘ëï|·xœSV((OQ(ÎÈ/ÍIQHJU(ÉHU(JÍIM,N/H,ÉàJËÌIUPR®vÉ,Ò*­ÕOJMË/J/®ÌÍÉÌËÖ+©(Qââƒþìˆwxœ[ϸžqB•é‹ç³žÛEmÚÚ¹¬>Gs•öÇ f PHI-ÈɯdxÿDâï6ó ë{&ñÇNj=¨~Â;-Fz¢xœ340031QH,(Ð+JbP ¼ô\ÿÅZŽ—«’÷œØ¿ÅwýAÔîsk„Ixœ+.ž Ëœ–™6ÑÀf࣠xœ340031QHL+I-Š/J-.I,*Ñ+Jb\ežÍÚ°Áâà©OÂ7ªr_Å\¾bQ›”š–_”Ÿ›™^”X’ R,·‡SkÕ»…¹ß«&¿þOáØATÅH&³;lÞ½ª/ƒg‡a¬usզȅ7¤QWæædæeƒ?U°¿ròÃ2=÷s©!¶ ¬¶rv|Æ”F iƒxxœóô˜àÂR”¤Ä ²jdƒëxœëcìcœP%ÒúãðéÀ™Œ3d9.ÚhÈ·.ИM  ¢xœ340031QH,(Ð+Jb˜ÍÑ]QbÖߺFëmóÎ7œe[ßǯ ‘¼xœÈ1€0ÐSü°tôLEËÐókLÞôB®[CÐR­WôFtJ7Æp¸¦&>5)»ÛÀ#‘ê?þík1‰ z¶FŸë‚3xœëcìcœP%2}ùÄØ.§i·íÖlP³XlÉþƒŸ= Q¢xœ340031QH,(Ð+JbØÔ˪wê•­êž?5ÒÚ]ÅgtWZÚÄK ©»xœÈ1€0ÐSü°tôL$¥–…V@ϯ1yÓ ½n EKs©FtjF_pMK|j*†E´åXãOÙ›I½Ó ÇK^¹-;“žd©Úd*6ý®žpÖèchef-12.14.60/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle000066400000000000000000000044321276456504500267500ustar00rootroot00000000000000# v2 git bundle 5a4748c52aaea8250b4346a9b8ede95ee3755e28 refs/heads/master PACKŸxœ•ËM @á=§˜ h€¡ü$Æx•) ±Ñ)ãÂÛÛ+¸|_òä`†jЫ¥-šìtuTcžýšx]0'5èà&€gfòOŒX’ ©\mBLѦÕiSQÑGžý€YøM­Ãm '·G3÷Â×Ü÷;\Ü’¼óµV§î›ÿ=**æwoí5A:Ì­‘ÂS€ÆP?ƒCI‚”xœ•ŒAnÅ ÷œÂh‘ªªW1¶ÑGJpDøêõ˺|£73‡ø,%—L®–Ý“E‹G¨¾†"'ï­G:(å`nÒ'ììܹQ–-ÕX¼Oi 1çHv=·Õ«Å|Ï—`ìM·å…¯µ~ô~HY>I¯op[tËÉ1ÇMÖšE¯6§üß4È ,vϦ´ÂKa*МåÝùháKÌ\ZQ1–xœ•ËAŽÃ @Ñ=§ð:2' UÕ\Å[AjCnÏ?½Â,ÿ“¾ H/$9ò¾hP]‹dÕ"÷¨YWR¢½*&wñÓSØÄï(„qY1qAN!V¦B”$Pð¹¢ã·}@å³ÉsVyJ‡û·~û5K¯òSúë~!¿aÙà 7D÷ÕW3“ÿŸÎŽ6*|dÌÖOè vÌv² “iÀ×åþCÜL Ÿxœ•‹Û !ÿ©bаÀñHŒ±•–Ü%'@ë÷Zðs&3k0ÆÒDˆ•¢äKAv} DNÚœ”´ÚöAt\(“-•¨Œó>²Î:éª1–>i§Ã}ÖÞdªŸ3óÉ ½ZŸ©e¾§ö~ê Êz¸I'¥¸ìûX‹ÿ?ÅäÔj†/y´ ­ÀÚÏÔ»ø’yIG• xœ•ŒQ à ÿ=Å^ EcÕJèUŒ>‰dƒnï_¡'èç¼á4€"üÂêl(ÚÙ® xœ340031QpOÍMËÌIeè~'y§0ß6ÞÿŒ°ýwÑwÞ7DQ¢—“ŸœÍz`úáÿ ÛùÔÙ½Z¼Õ%àUÞǨº WG_W½Ü[>†ßré'¢îËžSߺ¹ô›¢‰($0”P(<ÛœÍýϋӸŠKüwüÖKٜ̼ìbÛl·5Œw*ìsþºþ£WðàêßɃãFÌ>xœKOÍUP*ÎÌK,)JTâ$¹š¿xœswõåRP(.HM.¶2Š“³4 õLô 5áºEù%©É%™ùy 9#=ˆDÌ,ÎÌK,)JIë#K+hÔÙ)EuìlA´ž²,ŠÑ`…F0é’Ìœ ÍP£!rP®Ç7ÿ ß` \QiR%—‹k€«Ÿ‹«Ÿ³§+Hê:.zQ:¼¹xœ=AK1…ïùzð¢»ÕÞz+RÄ‹»à9ÍΚÁÝ$M&Öþ{'X„0ä½÷f…ap|}Û ï;쬌<è±(R(¼¤™ô V²…M µÐ‰*ñ„‘Ò¯ÈTbÍŽÀÁ8OSgÌý«G54ø…Å+þd ;dËsiÐ{œjƒYQ̹rVS9Ï,´ £)‰Oêø¦\8†‚8ÝÜʾÄüÕÝÂë<âDXªóXb¦–Œg¦ *2+ìRÌÂáSÌxÖ¨­ú ¼mÕO5ŒÚy"+UÝO:¯±ÂÙ—õ™Ú‚ÐT¬C#¶M„ÖÖ(G1<å®á>¹•J±Ã‘ ¶…I۾ׯ‡â–ÎÅ¥Z?®ûõ¦×ù t^–ÙüœF¢xœ340031QH,(Ð+Jb˜,ZÿqædŸß÷–H{++oò|Õì˜ÌM W¼xœÈ1À ÐSü°8öL&beìùÛ4yÓsÙG]PBgM¯…è–_Œfp |rºÏG<Ô&¬ÿ[×b’Ùè¹G»§xœ342… WG_W½œÌ¼l£+y/š·öfYؽe{=ã³Õ²`Þ·ûçeëxœëcìcœP%ÒúãðéÀ™Œ3d9.ÚhÈ·.ИM  ¢xœ340031QH,(Ð+Jb˜ÍÑ]QbÖߺFëmóÎ7œe[ßǯ ‘¼xœÈ1€0ÐSü°tôLEËÐókLÞôB®[CÐR­WôFtJ7Æp¸¦&>5)»ÛÀ#‘ê?þík1‰ z¶FŸë‚3xœëcìcœP%2}ùÄØ.§i·íÖlP³XlÉþƒŸ= Q¢xœ340031QH,(Ð+JbØÔ˪wê•­êž?5ÒÚ]ÅgtWZÚÄK ©»xœÈ1€0ÐSü°tôL$¥–…V@ϯ1yÓ ½n EKs©FtjF_pMK|j*†E´åXãOÙ›I½Ó ÇKl ª¸£ÅÓáú±Λ¨ÄšùÈchef-12.14.60/spec/data/git_bundles/sinatra-test-app.gitbundle000066400000000000000000000040051276456504500241240ustar00rootroot00000000000000# v2 git bundle 3eb5ca6c353c83d9179dd3b29347539829b401f3 refs/heads/master PACK”xœ•ŒAnÅ ÷œÂh‘ªªW1¶ÑGJpDøêõ˺|£73‡ø,%—L®–Ý“E‹G¨¾†"'ï­G:(å`nÒ'ììܹQ–-ÕX¼Oi 1çHv=·Õ«Å|Ï—`ìM·å…¯µ~ô~HY>I¯op[tËÉ1ÇMÖšE¯6§üß4È ,vϦ´ÂKa*МåÝùháKÌ\ZQ1–xœ•ËAŽÃ @Ñ=§ð:2' UÕ\Å[AjCnÏ?½Â,ÿ“¾ H/$9ò¾hP]‹dÕ"÷¨YWR¢½*&wñÓSØÄï(„qY1qAN!V¦B”$Pð¹¢ã·}@å³ÉsVyJ‡û·~û5K¯òSúë~!¿aÙà 7D÷ÕW3“ÿŸÎŽ6*|dÌÖOè vÌv² “iÀ×åþCÜL Ÿxœ•‹Û !ÿ©bаÀñHŒ±•–Ü%'@ë÷Zðs&3k0ÆÒDˆ•¢äKAv} DNÚœ”´ÚöAt\(“-•¨Œó>²Î:éª1–>i§Ã}ÖÞdªŸ3óÉ ½ZŸ©e¾§ö~ê Êz¸I'¥¸ìûX‹ÿ?ÅäÔj†/y´ ­ÀÚÏÔ»ø’yIG• xœ•ŒQ à ÿ=Å^ EcÕJèUŒ>‰dƒnï_¡'èç¼á4€"üÂêl(ÚÙ®xœ340031QpOÍMËÌIeè~'y§0ß6ÞÿŒ°ýwÑwÞ7DQ¢—“ŸœÍz`úáÿ ÛùÔÙ½Z¼Õ%àUÞǨº WG_W½Ü[>†ßré'¢îËžSߺ¹ô›¢‰($0”P(<ÛœÍýϋӸŠKüwüÖKÄ8>xœKOÍUP*ÎÌK,)JTâ$¹š¿xœswõåRP(.HM.¶2Š“³4 õLô 5áºEù%©É%™ùy 9#=ˆDÌ,ÎÌK,)JIë#K+hÔÙ)EuìlA´ž²,ŠÑ`…F0é’Ìœ ÍP£!rP®Ç7ÿ ß` \QiR%—‹k€«Ÿ‹«Ÿ³§+Hê:.zQ:¼¹xœ=AK1…ïùzð¢»ÕÞz+RÄ‹»à9ÍΚÁÝ$M&Öþ{'X„0ä½÷f…ap|}Û ï;쬌<è±(R(¼¤™ô V²…M µÐ‰*ñ„‘Ò¯ÈTbÍŽÀÁ8OSgÌý«G54ø…Å+þd ;dËsiÐ{œjƒYQ̹rVS9Ï,´ £)‰Oêø¦\8†‚8ÝÜʾÄüÕÝÂë<âDXªóXb¦–Œg¦ *2+ìRÌÂáSÌxÖ¨­ú ¼mÕO5ŒÚy"+UÝO:¯±ÂÙ—õ™Ú‚ÐT¬C#¶M„ÖÖ(G1<å®á>¹•J±Ã‘ ¶…I۾ׯ‡â–ÎÅ¥Z?®ûõ¦×ù t^–ÙüœF¢xœ340031QH,(Ð+Jb˜,ZÿqædŸß÷–H{++oò|Õì˜ÌM W¼xœÈ1À ÐSü°8öL&beìùÛ4yÓsÙG]PBgM¯…è–_Œfp |rºÏG<Ô&¬ÿ[×b’Ùè¹G»®xœ340031QpOÍMËÌIeè~'y§0ß6ÞÿŒ°ýwÑwÞ7DQ¢—“ŸœÍz`úáÿ ÛùÔÙ½Z¼Õ%àUÞǨº WG_W½Ü†Í¾âÓÝÓúª[O¾¿Ø³¹m[Í“Û&@ XPÀ`g%~Ò•iÃ+«Üö””‡?V0¹6ž:fƒ*xœ[ÉüqÂw ,¢xœ340031QH,(Ð+JbHaéñ´:ñHàÀ¥Ýú{½Öm‹ÎŘ Á»xœÈ1€0FáSüaéè™HŠ–…V@Ïocò¾å…Þ…¢¥¹TH#º´À£O¸†%v5t³èx5Ò¦cžÿ”µ˜Ô;}œ—>ëxœëcìcœP%ÒúãðéÀ™Œ3d9.ÚhÈ·.ИM  ¢xœ340031QH,(Ð+Jb˜ÍÑ]QbÖߺFëmóÎ7œe[ßǯ ‘¼xœÈ1€0ÐSü°tôLEËÐókLÞôB®[CÐR­WôFtJ7Æp¸¦&>5)»ÛÀ#‘ê?þík1‰ z¶FŸë‚3xœëcìcœP%2}ùÄØ.§i·íÖlP³XlÉþƒŸ= Q¢xœ340031QH,(Ð+JbØÔ˪wê•­êž?5ÒÚ]ÅgtWZÚÄK ©»xœÈ1€0ÐSü°tôL$¥–…V@ϯ1yÓ ½n EKs©FtjF_pMK|j*†E´åXãOÙ›I½Ó ÇKTãNÉD;†€Nà‰#YíÃ븬chef-12.14.60/spec/data/incomplete-metadata-chef-repo/000077500000000000000000000000001276456504500223165ustar00rootroot00000000000000chef-12.14.60/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/000077500000000000000000000000001276456504500262335ustar00rootroot00000000000000chef-12.14.60/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/README.md000066400000000000000000000001031276456504500275040ustar00rootroot00000000000000# incomplete-metadata TODO: Enter the cookbook description here. chef-12.14.60/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/metadata.rb000066400000000000000000000006101276456504500303350ustar00rootroot00000000000000# ## INCOMPLETE METADATA ## # This cookbook is invalid b/c it does not set the `name' of the cookbook. # Commented out for illustrative purposes # name 'incomplete-metadata' maintainer '' maintainer_email '' license '' description 'Installs/Configures incomplete-metadata' long_description 'Installs/Configures incomplete-metadata' version '0.1.0' chef-12.14.60/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/recipes/000077500000000000000000000000001276456504500276655ustar00rootroot00000000000000chef-12.14.60/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/recipes/default.rb000066400000000000000000000001341276456504500316340ustar00rootroot00000000000000# # Cookbook Name:: incomplete-metadata # Recipe:: default # # Copyright 2014-2016, # # # chef-12.14.60/spec/data/invalid-metadata-chef-repo/000077500000000000000000000000001276456504500216055ustar00rootroot00000000000000chef-12.14.60/spec/data/invalid-metadata-chef-repo/invalid-metadata/000077500000000000000000000000001276456504500250115ustar00rootroot00000000000000chef-12.14.60/spec/data/invalid-metadata-chef-repo/invalid-metadata/README.md000066400000000000000000000001001276456504500262570ustar00rootroot00000000000000# invalid-metadata TODO: Enter the cookbook description here. chef-12.14.60/spec/data/invalid-metadata-chef-repo/invalid-metadata/metadata.rb000066400000000000000000000004121276456504500271130ustar00rootroot00000000000000name 'invalid-metadata' maintainer '' maintainer_email '' license '' description 'Installs/Configures invalid-metadata' long_description 'Installs/Configures invalid-metadata' version '0.1.0' raise "THIS METADATA HAS A BUG" chef-12.14.60/spec/data/invalid-metadata-chef-repo/invalid-metadata/recipes/000077500000000000000000000000001276456504500264435ustar00rootroot00000000000000chef-12.14.60/spec/data/invalid-metadata-chef-repo/invalid-metadata/recipes/default.rb000066400000000000000000000001311276456504500304070ustar00rootroot00000000000000# # Cookbook Name:: invalid-metadata # Recipe:: default # # Copyright 2014-2016, # # # chef-12.14.60/spec/data/kitchen/000077500000000000000000000000001276456504500161605ustar00rootroot00000000000000chef-12.14.60/spec/data/kitchen/chefignore000066400000000000000000000002151276456504500202120ustar00rootroot00000000000000# # The ignore file allows you to skip files in cookbooks with the same name that appear # later in the search path. # recipes/ignoreme\.rb chef-12.14.60/spec/data/kitchen/openldap/000077500000000000000000000000001276456504500177625ustar00rootroot00000000000000chef-12.14.60/spec/data/kitchen/openldap/attributes/000077500000000000000000000000001276456504500221505ustar00rootroot00000000000000chef-12.14.60/spec/data/kitchen/openldap/attributes/default.rb000066400000000000000000000000461276456504500241210ustar00rootroot00000000000000# # Nothing to see here, move along # chef-12.14.60/spec/data/kitchen/openldap/attributes/robinson.rb000066400000000000000000000000271276456504500243250ustar00rootroot00000000000000# # Smokey lives here #chef-12.14.60/spec/data/kitchen/openldap/definitions/000077500000000000000000000000001276456504500222755ustar00rootroot00000000000000chef-12.14.60/spec/data/kitchen/openldap/definitions/client.rb000066400000000000000000000000231276456504500240730ustar00rootroot00000000000000# # A sad client # chef-12.14.60/spec/data/kitchen/openldap/definitions/drewbarrymore.rb000066400000000000000000000000531276456504500255040ustar00rootroot00000000000000# # Was in people magazine this month... #chef-12.14.60/spec/data/kitchen/openldap/recipes/000077500000000000000000000000001276456504500214145ustar00rootroot00000000000000chef-12.14.60/spec/data/kitchen/openldap/recipes/gigantor.rb000066400000000000000000000000501276456504500235460ustar00rootroot00000000000000cat "blanket" do pretty_kitty true endchef-12.14.60/spec/data/kitchen/openldap/recipes/ignoreme.rb000066400000000000000000000000421276456504500235420ustar00rootroot00000000000000# # this file will never be seen #chef-12.14.60/spec/data/kitchen/openldap/recipes/woot.rb000066400000000000000000000000321276456504500227240ustar00rootroot00000000000000# # Such a funny word.. # chef-12.14.60/spec/data/knife-home/000077500000000000000000000000001276456504500165555ustar00rootroot00000000000000chef-12.14.60/spec/data/knife-home/.chef/000077500000000000000000000000001276456504500175405ustar00rootroot00000000000000chef-12.14.60/spec/data/knife-home/.chef/plugins/000077500000000000000000000000001276456504500212215ustar00rootroot00000000000000chef-12.14.60/spec/data/knife-home/.chef/plugins/knife/000077500000000000000000000000001276456504500223155ustar00rootroot00000000000000chef-12.14.60/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb000066400000000000000000000000001276456504500275030ustar00rootroot00000000000000chef-12.14.60/spec/data/knife-site-subcommands/000077500000000000000000000000001276456504500211025ustar00rootroot00000000000000chef-12.14.60/spec/data/knife-site-subcommands/plugins/000077500000000000000000000000001276456504500225635ustar00rootroot00000000000000chef-12.14.60/spec/data/knife-site-subcommands/plugins/knife/000077500000000000000000000000001276456504500236575ustar00rootroot00000000000000chef-12.14.60/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb000066400000000000000000000000001276456504500300350ustar00rootroot00000000000000chef-12.14.60/spec/data/knife_subcommand/000077500000000000000000000000001276456504500200375ustar00rootroot00000000000000chef-12.14.60/spec/data/knife_subcommand/test_explicit_category.rb000066400000000000000000000003551276456504500251440ustar00rootroot00000000000000module KnifeSpecs class TestExplicitCategory < Chef::Knife # i.e., the cookbook site commands should be in the cookbook site # category instead of cookbook (which is what would be assumed) category "cookbook site" end endchef-12.14.60/spec/data/knife_subcommand/test_name_mapping.rb000066400000000000000000000001041276456504500240510ustar00rootroot00000000000000module KnifeSpecs class TestNameMapping < Chef::Knife end end chef-12.14.60/spec/data/knife_subcommand/test_yourself.rb000066400000000000000000000006111276456504500232710ustar00rootroot00000000000000module KnifeSpecs class TestYourself < Chef::Knife class << self attr_reader :test_deps_loaded end deps do @test_deps_loaded = true end option :scro, :short => '-s SCRO', :long => '--scro SCRO', :description => 'a configurable setting' attr_reader :ran def run @ran = true self # return self so tests can poke at me end end end chef-12.14.60/spec/data/lwrp/000077500000000000000000000000001276456504500155175ustar00rootroot00000000000000chef-12.14.60/spec/data/lwrp/providers/000077500000000000000000000000001276456504500175345ustar00rootroot00000000000000chef-12.14.60/spec/data/lwrp/providers/buck_passer.rb000066400000000000000000000014521276456504500223640ustar00rootroot00000000000000provides :buck_passer def without_deprecation_warnings(&block) old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] Chef::Config[:treat_deprecation_warnings_as_errors] = false begin block.call ensure Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors end end action :pass_buck do lwrp_foo :prepared_thumbs do action :prepare_thumbs # We know there will be a deprecation error here; head it off without_deprecation_warnings do provider :lwrp_thumb_twiddler end end lwrp_foo :twiddled_thumbs do action :twiddle_thumbs # We know there will be a deprecation error here; head it off without_deprecation_warnings do provider :lwrp_thumb_twiddler end end end chef-12.14.60/spec/data/lwrp/providers/buck_passer_2.rb000066400000000000000000000014401276456504500226020ustar00rootroot00000000000000def without_deprecation_warnings(&block) old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] Chef::Config[:treat_deprecation_warnings_as_errors] = false begin block.call ensure Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors end end action :pass_buck do lwrp_bar :prepared_eyes do action :prepare_eyes # We know there will be a deprecation error here; head it off without_deprecation_warnings do provider :lwrp_paint_drying_watcher end end lwrp_bar :dried_paint_watched do action :watch_paint_dry # We know there will be a deprecation error here; head it off without_deprecation_warnings do provider :lwrp_paint_drying_watcher end end end chef-12.14.60/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb000066400000000000000000000017211276456504500306610ustar00rootroot00000000000000# This action tests that embedded Resources have access to the enclosing Provider's # lexical scope (as demonstrated by the call to new_resource) and that all parameters # are passed properly (as demonstrated by the call to generate_new_name). attr_reader :enclosed_resource def without_deprecation_warnings(&block) old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] Chef::Config[:treat_deprecation_warnings_as_errors] = false begin block.call ensure Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors end end action :twiddle_thumbs do @enclosed_resource = lwrp_foo :foo do monkey generate_new_name(new_resource.monkey){ 'the monkey' } # We know there will be a deprecation error here; head it off without_deprecation_warnings do provider :lwrp_monkey_name_printer end end end def generate_new_name(str, &block) "#{str}, #{block.call}" end chef-12.14.60/spec/data/lwrp/providers/inline_compiler.rb000066400000000000000000000006701276456504500232340ustar00rootroot00000000000000 use_inline_resources action :test do ruby_block "interior-ruby-block-1" do block do # doesn't need to do anything end notifies :run, "ruby_block[interior-ruby-block-2]", :immediately end ruby_block "interior-ruby-block-2" do block do $interior_ruby_block_2 = "executed" end action :nothing end end action :no_updates do ruby_block "no-action" do block {} action :nothing end end chef-12.14.60/spec/data/lwrp/providers/monkey_name_printer.rb000066400000000000000000000001701276456504500241240ustar00rootroot00000000000000attr_reader :monkey_name action :twiddle_thumbs do @monkey_name = "my monkey's name is '#{new_resource.monkey}'" end chef-12.14.60/spec/data/lwrp/providers/paint_drying_watcher.rb000066400000000000000000000001511276456504500242620ustar00rootroot00000000000000action :prepare_eyes do "Prepared" end action :watch_paint_dry do "This isn't very interesting" end chef-12.14.60/spec/data/lwrp/providers/thumb_twiddler.rb000066400000000000000000000001361276456504500230760ustar00rootroot00000000000000action :prepare_thumbs do "Prepared" end action :twiddle_thumbs do "Twiddle twiddle" end chef-12.14.60/spec/data/lwrp/resources/000077500000000000000000000000001276456504500175315ustar00rootroot00000000000000chef-12.14.60/spec/data/lwrp/resources/bar.rb000066400000000000000000000002101276456504500206130ustar00rootroot00000000000000provides :lwrp_bar # This makes sure that we cover the case of lwrps using provides actions :pass_buck, :prepare_eyes, :watch_paint_dry chef-12.14.60/spec/data/lwrp/resources/foo.rb000066400000000000000000000001521276456504500206370ustar00rootroot00000000000000actions :prepare_thumbs, :twiddle_thumbs default_action :pass_buck attribute :monkey, :kind_of => String chef-12.14.60/spec/data/lwrp/resources_with_default_attributes/000077500000000000000000000000001276456504500245365ustar00rootroot00000000000000chef-12.14.60/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb000066400000000000000000000001101276456504500266730ustar00rootroot00000000000000attribute :penguin, :kind_of => String, :default => node[:penguin_name] chef-12.14.60/spec/data/lwrp_const_scoping/000077500000000000000000000000001276456504500204475ustar00rootroot00000000000000chef-12.14.60/spec/data/lwrp_const_scoping/resources/000077500000000000000000000000001276456504500224615ustar00rootroot00000000000000chef-12.14.60/spec/data/lwrp_const_scoping/resources/conflict.rb000066400000000000000000000000001276456504500245750ustar00rootroot00000000000000chef-12.14.60/spec/data/lwrp_override/000077500000000000000000000000001276456504500174165ustar00rootroot00000000000000chef-12.14.60/spec/data/lwrp_override/providers/000077500000000000000000000000001276456504500214335ustar00rootroot00000000000000chef-12.14.60/spec/data/lwrp_override/providers/buck_passer.rb000066400000000000000000000002621276456504500242610ustar00rootroot00000000000000# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore action :buck_stops_here do log "This should be overwritten by ../lwrp_override/buck_passer.rb" end chef-12.14.60/spec/data/lwrp_override/resources/000077500000000000000000000000001276456504500214305ustar00rootroot00000000000000chef-12.14.60/spec/data/lwrp_override/resources/foo.rb000066400000000000000000000003131276456504500225350ustar00rootroot00000000000000# Starting with Chef 12 reloading an LWRP shouldn't reload the file anymore actions :never_execute attribute :ever, :kind_of => String class ::Chef def method_created_by_override_lwrp_foo end end chef-12.14.60/spec/data/mac_users/000077500000000000000000000000001276456504500165145ustar00rootroot00000000000000chef-12.14.60/spec/data/mac_users/10.7-8.plist.xml000066400000000000000000000757001276456504500211330ustar00rootroot00000000000000 KerberosKeys MIIBVKEDAgEBoIIBSzCCAUcwc6ErMCmgAwIBEqEiBCBxHUxawMNiov49kfZn M38ddgXFivE9SNpYgPamy+6prKJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEVB OUJFMDZCQzExQjAxODdEMzQ1MkI3QTA5NjE3QjBCOTI2OTY4RXZhZ3JhbnQw Y6EbMBmgAwIBEaESBBBD9mGbvFTNIUKAvAbnjh8ookQwQqADAgEDoTsEOUxL REM6U0hBMS40RUE5QkUwNkJDMTFCMDE4N0QzNDUyQjdBMDk2MTdCMEI5MjY5 NjhFdmFncmFudDBroSMwIaADAgEQoRoEGG4TEFIf416UH7MvFW7sAXC8ArC6 AhbCraJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEVBOUJFMDZCQzExQjAxODdE MzQ1MkI3QTA5NjE3QjBCOTI2OTY4RXZhZ3JhbnQ= ShadowHashData YnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50 cm9weVRzYWx0Wml0ZXJhdGlvbnNPEIDqTC0mXYAboOwN/M0lPfwd6Ry+CAa0 rMHtf+Iq689r61NE0PRC5ZD/oE1nkHXaOvsRnkG3K16vCO5KpUaTciZG1Rnu BIQ964o+l3Qo0z9iXoOIeRPlwTtwA1lhXgCte8PnoMmK/D4Z0TYCckVPjTOp IU0vvovmjR+YIbJmiTEjZk8QIPmU7y9zt8VZTr0VUzAJdrIHM84OJNZZeD2H 89gcu7apEZugCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA AAAAAOo= _writers_hint vagrant _writers_jpegphoto vagrant _writers_passwd vagrant _writers_picture vagrant authentication_authority ;ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2> ;Kerberosv5;;vagrant@LKDC:SHA1.4EA9BE06BC11B0187D3452B7A09617B0B926968E;LKDC:SHA1.4EA9BE06BC11B0187D3452B7A09617B0B926968E generateduid 11112222-3333-4444-AAAA-BBBBCCCCDDDD gid 80 home /Users/vagrant jpegphoto /9j/4AAQSkZJRgABAQAAAQABAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBw bAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBs AAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAA AG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAH MAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFk AAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRH ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJv ZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAo AAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUA AAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtv S1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAAD Hmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAa AAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwA AAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZp RkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE 8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAg AFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcA QgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDo AHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8E FwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUABy AG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIA RwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBl AHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkA bMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAg AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEA bABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBs AHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIA RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBv AGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACAD wAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBl AG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8A ZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBH AGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA bgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBs AG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYn BkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwA ZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABz AGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdo dHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRN AAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8A ALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA /ZH///ui///9owAAA9wAAMBs/9sAQwACAgICAgECAgICAgICAwMGBAMDAwMH BQUEBggHCAgIBwgICQoNCwkJDAoICAsPCwwNDg4ODgkLEBEPDhENDg4O/9sA QwECAgIDAwMGBAQGDgkICQ4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O Dg4ODg4ODg4ODg4ODg4ODg4ODg4O/8AAEQgBMQEuAwEiAAIRAQMRAf/EAB8A AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQE AAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3 eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfI ycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJico KSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWG h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW 19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiis6/1Wz0 6P8AfyAyY+WJeWP4dvxoA0aw9R1+zsN0at9puR/Ah4H1NcjqHiG9vQ0cZ+yQ H+FD8x+p/wAK5+gDWu9a1C8u1kad4gpyiRkqF/z71u6d4pI2xaiu4dPOQc/i P8K43v0P5UlMZ7LDPDcW6ywSJLGejKcipa8gtL65sLjzLaV4z3HVT9RXb6d4 mt7grFegW0398fcP+H40hHUUUgIZQykEEZBHeloAKKKKACiiigAooooAKKKK ACiiigAooooAKKKKACiiigAooooAKKKguLmC1tzLcSpDGO7H/OaAJ6K4y48W gXyi2tvMtwfmLnDN9PSuisNVs9RjzBJiQfeibhh+Hf8ACgC3cgHT5wQCDG2Q fpX4u/CD9rf4h/C+aHRtXkfxt4PjbaLG/mP2i2XP/LGc5YADorbl7AL1r9o7 j/jxn/65n+VfzYS/8fMn+8f51+qeG+W4XHUcXSxEFKPub/8Ab2z3T9D8p8S8 zxWBq4Srh5uMvf2/7d3WzXkz99/hb8cPh38XtCW48J6yn9ppGGutHvMRXtv6 5TJ3L/tIWX3zxXrtfgD8JPhh8TviF4ut9Q+Hlvd6ZDZTgv4nluHtLSxYd1mX 5nkGfuRbm+lftD4C1HXfDvw+0vRfGHiW48batBFtudbeyS2aZvaNf4QOAxO4 9W5r57jHh7BZXX5cPXUr/Z3lH1a0++z8nufQ8G8R43NaHNiKDjb7X2Zeiev3 XXpseu0VBb3MF1biW3lSaM91NT18Yfankuk/FDQfHPgxNb8B6zYa1ojna17a ybnjb+5Ihw0T/wCy4Bqkzs8hd2Z3JyWJyTX4a+HPF3ijwL4/bxB4O13UPDus K5DTWr/LMufuSocrKn+y4Ir76+Fv7YXhzXfs+j/FO1tvB+sHCLrtorNplwfW VOXtifX5o/da/Rc/8O8bg71cL+9h2+0vl19Vr5I/OOHvEbBYxqli/wB1Pv8A Zfz6fPTzPs6jt61HFLDPY293bTwXVpcIJLe4t5VlimQ9GR1JVgfUE0/mvzux +jppq6F70lL6elHagBPyo7elHejvQM1tP1m905wI3MkPeJ+V/D0ru9O1yy1D CK3k3B/5ZOeT9D3ry/tR0YH360CPaaK8607xJd2m2K6zdQDjJPzj6Hv+NdzZ 39rfweZbSq/95Twy/UUgLlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS MyohZ2CqBkknAFYmo69Z2AZFb7Rcj/lmh6fU9q4S/wBWvNRc+dJiLPyxJwo/ x/GgDrNR8TwQho7BRcydPMP3B/jXE3V3c3lwZrmV5X9+g+g7VXzj2PrS8EZP FMBvtT1ZkkDozI68gqcEU0gjP86Tv9aBnWWPiWXyWttQ/eIyFRMo+YccZHQ/ pXwD8MP2PdC0iSDWvivd2/inVc+Yvh+xkZdOgPXE0gw9wR3Vdqf71fadHQ5r 1MvzvG4GlUp4efKp2u1vpfZ7rfpqeRmGR4LHVadTEQ5nC9k9tbbrrt10IoIY LTTbaxs7e2srC2Ty7a1toVihgX+6iKAqj2AqXtSVla7r2ieFvDjav4j1S00f TRwstw3Mp/uxqPmkb2UH8K86EJ1JqMVeT6LVtnpznClByk0orvokv0N62u7i yuRLbTPE4646H6jvV3U/i74Q8M3MVh4p1SGw1Z03rZwRvNMU/vtGgLIuSOWx nPFfEXjj9ojWNT87TvAcE/hzTzlW1S4UG+lHqi8rAD6/M/uK8d8JPJL4xvbi eSWe5lgZ5ZpXLySMWXJZjyT7k1+g5XwBXnTdbGPkX8q+L5vZfi/Q/PM08QKE aipYKPO/5n8PyWjfrovU+P5/+P2b/fP86irqfG/hfUvBXxe8SeE9XXGoaVqE ttK23Ak2sdrj/ZZcMPYiuWr97pVI1IRlF3TV0fz/AFacqc5QkrNOzPpr4GfE bxb4B8GyDQdQEmlm/czaReZks5OFzhc5jb/aQg/WvvvwJ8ZvCPjeSGwkk/4R rxG/A02/lGyZv+mE3Cv/ALp2t7GvzM8Af8iHL/1+yf8AoK128dvJeXcFlBbT XtzO4WG2hiMkkrdgqgEk/SviOIuE8BmUpTkuSf8AMv1Wz+evmj7vhzizH5bC MIPmh/K/0e6+Wnkz9V2VkdlYMrDhlIwRTfzr5Ut/E3xQ+CX7Nlz4x8foniHQ bW7tbW38O3FyDqduk0mzd9p5CbRyIn389Ste2fDz4peBPipob3ngvW0vbmJN 15pNyvk6hZ/9dISclf8AbQsnvX4hj8jr4eMqsGqlJO3PHWN9N+268uibP3DA Z/h8TONKadOq1fklpK3l32fn3SO/780d6Xr3/Wkrxj3Be1JS/likoAU9KfFL LBOssMjxSA5DKcGmUn50Adrp3in7sOornt5yD+Y/w/KuximingWWGRJY26Mp yDXjXOenWuL8V/Fzw58MWYahqMlzrRXcmiWJElxJ6GQH5Yl/2nwfQGt8LhK2 JqKnRi5SfRf1+OxzYrF0cNSdWtNRiur/AK/A+naK5vwdr0nij4VeHfEktqtl JqenxXTW6ybxEXUNt3YGcZxnFdJWVWnKnNwlutDWlUjUgpx2auvmFFFFQWFF FFABRRRQAUUV4D8evjfbfBHQ/Cmpajo17qmk6tqD2l3NYyL9otFEe4SJG3yy e67lPoc104PB1sVWVGjHmk9l30ucuMxlHCUXWrStFbv52PcL3UbTT4d1zKFY jKoOWb6CuG1LxHd3ZaODNrbnsp+Zvqf6CvOvCnjfwv8AETwqfEfhHxDZ+JNP JHnyRMfOt2P8E8bfPE3swx6E10fFZVaU6U3CpFqS3T0a9UbUa1OtBVKclKL2 a1T+Yd6KPyrk/F3jjwv4F0lbnxLqaWs0i7rawhXzbu5/3Igc4/2mwvvVUKFW tUVOlFyk9ktWLEYilQpupVkoxW7bsjq6O9fK+hftaeBrj4n3nhvxlpl34It9 yHT9Wkm+02xVlyFudq5ibP8AGoZPp1r6lhlgudOt7y0uLe8sbhBJbXVtKssM yf3kdSVYe4NduZZPjcvmo4mm4327P0aun566HDlmdYHMIuWGqKVt+69U7P8A Akyce1LwTx1pM0qqzyBEVmduiqMk15h6YnQ1HPNBa6bc315cW1lY2yeZc3Vz MsUMCf3ndiFUe5NeXeOvjH4R8DtLY+b/AMJJ4hQY/s2wlG2E/wDTaYZVP90Z b2Ffm58ZfGXxR+IOpvdeKdU/tDwvFIXtNJ0pDFY2Y7F4cku4/wCejlifUV9n w9wTjczanP8Ad0+73f8AhXX1dl2bPiuIuOMFlicIfvKnZbL1fT0V33sfUfxR /bE0LR/tOjfCmzt/FWqDKN4gv42GnQHpmGM4a4I7M21P96vmXR/FPiTxra3f iLxdrmoeItblvJAbq8kzsXC4SNRhY0HZVAFfP4IIyCCD0INexeAf+RCk/wCv 2T+S1+1Zbw1gMqo2w8Pe6yesn8+i8lZeR+JZjxNmGbV74ifu9IrSK+X6u78z ta6zwd/yM0//AF6t/wChLXJnpXt3wO+HWp/EHx1rENldR2FtZWAae5kjLKGe RQicdyFcj/dNGZ4inQws6lR2iupeWYepXxUKdNXk+g79vb4c/wBm/Erw78Tr CDbaaxENP1RlXgXMS5iYn1eIFfpBX58V+/nx0+HafFH9lzxX4TWJZNTktTca UxxlbqL54sHtuI2E/wB12r8BXR45njkVkkUkMrDBBHUEV4/h1m/1vLPYyfvU tPl9n9V8j1vEfJ/qmae2ivdq6/P7X6P5n1L+zt8Orj4lWd/pltr+j6StndNL eRvKJL7yyF+aK3zlx1+c4UY5r9FfBnw/8KeArBk8OacFvZE2z6pdESXk/wBX x8i/7KAD61+JVpd3mna1aalpt5eabqdrIJLW8tJmimgYfxI6kFT9DX218Lv2 x9V082+jfFuym16yGEXxJpsCi9jHTNxAMLOPV02v3IauDjnh/OcYnPDVOan1 gtH/APbej26Js7+A+IMmwbUMTT5anSb1X/2vqvm0j6A/a0/5MP8AEIH/AEGN N/8AR5r8p7G+vtK16z1XSr+90rVbSQSWt7ZTtDPA3qjqQR/XvX6f/tJ+I/D3 i3/gnHrOv+Fdc0zxFos+sad5d5Yzb1z5/KsPvIwyMqwBHpX5b11eGlJxympC as+eSaf+GOjX6HL4l1oyzenUpyuuSLTT85apo+5/hd+2TqFobfR/i9Yy6va8 KnibS7cC6QetxbjCy+7x7W7lWr700TW9F8TeErTX/Der6dr+h3IHkX1jMJIm OM7T3Vh3VgGHpX4SV2Hgjx/4y+HHis6z4L1670S7fi5iXD212v8Admhb5JB9 Rn0IrHiDw4wmKvVwbVOfb7L+X2flp5G3DviTi8JaljF7SHf7S+f2vnr5n7fY 70vavkv4XftbeDfF722j+PobbwB4kchFvDIW0q7Y8cOfmtyf7r5XPRhX1hcT QWelSajeXVraaakQle8mmVYAhHD+ZnaVPYg89s1+MZnk+My+t7LE03F9Oz9H s/kfteV51gsxo+1w9RSS37r1XQf+VYviHxLoHhLw7/aviXVrXSLM5ERlOZJz /djjHzOfoMepFfP3jj9ouxtPO074fW0eqXPKnWr6I/Zk94Yjgyf7z4X2NfKm r6vq3iDxFNq+u6le6xqkv37m7k3vj+6Oyr/sqAPavrcj4BxWKtUxX7uHb7T+ XT56+R8jnnH+Fw16eEXtJ9/sr5/a+Wnme7eOP2hde1kzad4Khn8LaU2Va/kw dQnHqpGVgB/2ct/tCvng5aaSRmeSWRi8kkjFndj1LMeSfc0UV+t5blOEy+n7 PDw5V17v1e7/AE6H5JmWbYvH1faYibk+nZei2X9XP1/+E3/JsXgD/sAWv/op a9Crz34S/wDJsPgD/sAWv/opa9Cr+ccx/wB7q/4n+Z/R+W/7pS/wr8kFFFFc R2hRRRQAUUUUAFfn7/wUE/5Ij4B/7Dkv/og1+gVfn7/wUE/5Ij4B/wCw5L/6 INfVcEf8jzD+r/JnynHH/IixHovzR+YfhzxJ4h8H+MbfxD4U1vUvDutw8JeW MuxyP7rjo6HurAg+lffXws/bF0rVWtdD+LFjFoOpMRHH4i0yBmspm6Dz4Blo ST/Em5OfurX511Ys/wDkNWP/AF8x/wDoYr98zvhvAZrC2Ih7y2ktJL5/o7ry P5+yPiTMMqqXw89OsXrF/L9VZ+Z+mPjr9oq7lln0z4fWr2EQJR9bv4QZ294Y TkJ7M+W7gCvmO6ubq/1a41C/urm/1Cdt091cymSWU+rMeT/Smy/8fMn+8f51 H+PNcGVZLg8up8mHhbu+r9X+m3ZHqZrnWMzGpz4id+y6L0X6792eJ+N/+SmX n/XCH/0Cul+Gnxj8ffCfUifCmrB9Hkk33WhX4M1hcep2ZzG3+3GVP1rmvG// ACUy8/64Q/8AoFcpX0VXCUcTh/ZVoKUWtU1dHzFLF1sNiPa0ZuMk9GnZn6se B/2q/hr4s8I3Fxq0eqeFfEdrDvn0UxG5+084JtpVADrnGQ+1lByc9a8s8d/H bxT4shuNO0USeEvD7gq0NtNm7uF/6azDoD/dTA9zXxj4A/5Hub/ryk/mtewZ z/8AWr43DcFZVgsS6sIXe6UndR9P83d+Z9riONs2x2FVKpUstnZWcvX/ACVl 3QgAVcAADPQU4Eq2VJU+opMcUV9KfOHJa14O0vVWknt9ulX7cmSJP3ch/wBt P6rg/WrHhTS7zR/DM1jfJGswu3ZTG+5XUhcMD+B6810tFW6knHlZkqUVLmW4 vb0r9RP2cPBX/CI/s3afd3MIj1XXD/aFwSOQjDEK/TZhsdi7V+fXwu8HP48+ Ovh7w3tY2c1wJL5hn5bdPnk57EqCoPqwr9f0RIoUjjRY40UKiKMBQOgA7Cvy zxHzTlp08JF7+8/Tp+N/uP1Xw3yvmqVMZJbe6vV7/hp8x1fiL+1z8Of+Ff8A 7YmtXFpB5WieIh/a1jtHyq0jHz09OJQ5wOiulft1Xx7+2r8OP+Ez/ZQk8S2U Hm6z4TmN8hVcs1q+FuF+gASQn0iNfL8B5v8AUc1gpP3anuv57P7/AMGz6jj7 J/r2UzcV71P3l8t1934pH400UUV/SB/NRPDdXVtZXtrb3VzBaXmz7Zbxyssd zsO5PMUHDFTyCRkHpUPB6cH0pKKVkF29wopc8YPIpKYAQCCCAQRgg969m8Ba tq138N7jRLvVtTutFsb4NZafNdO9vblkydiE4XJ54HHbFeM16v8ADv8A5F3V /wDr8T/0CufFRThdrY6sJKSqaPc9AzRTXdY03SMqr6k/kK9Av/hh400j9nPx H8Utb0ibRvDOk2iXEcV4DHd3wZ0QeXGeVX5wdz4z2B615FfE0qPL7SSXM0lf q3sl3PZo4atW5uSLfKm3bolq2+xwPOKWs/TdV0/V7Iz6ddJcIo/eJjbJH/vK eR9envV/vW7TWjME76o/UT9m7xcnij9mTS7OR1/tDQ2OnTqODsUAxNj02FVz 3KtXvtfmv+yz4y/4R/8AaAfw/cS7NP8AEFv5GCcKLiPLxH8R5ifVxX6UV/Pf GOWfU80qJL3Ze8vnv+Nz+heDsz+u5XTbfvR91/Lb8LBRRRXy59SFFFFABRRR QAV+Vv7fPj9NS+KHhf4c2bo8Wj2xv9QK8kTzDEaH0Kxru9xKPSv1D1TU7LRf DOo6xqU62unWNrJc3UzdI441Lux9gATX883xA8YXvj/42eJ/GWob1uNX1CS5 EbHPlITiOPPoiBVHsor9K8M8q9vmEsTJaU1p/ien5X/A/M/E/NvYZfHCxetR 6/4Vr+dvxOPqzZ/8hqx/6+Y//QxVapYJBDfwTEFhHKrlQcZwQcfpX70z8BTP paX/AI+JiSFVSSzMcADPUnsK4HWfHVjZl4NIVNTuhwZmyIEPt3c/TA964TXf EOq69LIbiZUsN2VtYMrGv+8OrH3PFc7XHSwyteR21cW3pEtXt7d6jqct7fTG 4upMbnIA4HAAA6AdhVWiiutHG3c7fwB/yPM3/Xk/81r2DvXj/gD/AJHqb/ry f+a17BXn4n+IelhP4YdqPzoorA6gooxV/StNvNa8Tado+nxGe/vrlLe3jz95 3YKo/M0pSUU29kOMXJqK3Puj9kbwV9k8J6347u4cT37/AGHT2Yc+ShBkYezO FX6xGvsquf8ACnh2z8JfDbRPDVgB9l060SBWC48wgfM5HqzZY+5NdBX81Z7m Tx+OqV+jenotF+B/SuRZasBgadDqlr6vV/iFVb6ytNS0W806/gjurG6geC4g kGVkjdSrKR6EEirVFeUm07o9ZpNWZ/PN8U/A118Nv2g/Ffgq68xhpl8yW0j9 ZYGw8Mh92jZCfQkiuAr9Lv2+vhxlPC3xTsIOR/xKNXKj/ekt3OP+2qlj/wBM x6V+aNf1LwzmyzHLaVe+trP1Wj/z+Z/KvE+UvLcyq0Oid1/heq+7b1QUUUV7 x4AUUUUAFfSn7PXw18V/E2fXdM8MQ2IW3u4mvry8uAkVqrIcMVB3ueOig+5F fNdd18OdfvvD3xQtLnTtQvNKvJh5cF3aTGKWKQcoQw9TkEHg55BrjzCFaeHm qMkpdG1dfddf132O3LqlGGJg6ybjfVJ2f32f9dtz9oPhn+zv4I+H0ttql3Gf FXimPkalfxjZA3/TCLlY+3zctx96qH7XH/KOb4pf9g6L/wBKYa8Z+G37WV7Z m30j4nWbX1uMIuvafBiRfeeAdf8Aej9PuV6f+05r2i+Jv+CYPxK1nw/qljrG lz6ZEYrm0mEiH/SITjI6EZ5B5HevwSrgM1o55hp468r1I2lvH4lt0Xpp6H9A UsflNbI8TDA2janO8dpfC9+r9bv1PxQgnntb1Lm1nltrlD8ksTbWX8f6V6Ro /j4ErBr0WD0F7AnH/A0H81/KvMu9B6Gv6EnTjPc/nanVlB3R9Q6Tqk2n6xpm t6VcgT280d1Z3EZyNysGRh+IBr9lvCfiG08WfDXQ/Ellj7PqNmk4UHPlsR8y H3VsqfcV+Jehf8iLov8A15J/Kv0U/ZH8Y/bvh9rXgm6lzcaXN9rslY8mCU/O oHosnJ/661+U+ImWe2wUcRFa03r6PT87fifrPhzmnscY8PJ6VFp6rX8r/gfY NFFFfih+2hRRRQAUUUUAfHP7bPxC/wCER/ZNPhmzn8rVvFdz9jUKcMLWPDzs PY/u4z7Smvxtr6r/AGxfiF/wnH7Y+q6daT+bo/hmMaVbBW+UyqSbhsevmEp7 iNa+VK/pXgfKfqOU00170/efz2+5W+Z/MvHWbfX83qNP3Ye6vlv+N/kFFFFf Xnx4qkq4ZSQw6EU5ni2M0pWHAyX6KPc+n1Fdx4A+Gfjj4oeJH03wVoU2prEw F5qEreTY2XvLO3yqf9kZY9hX6I/Cz9lPwL4Ee11jxa0HxC8WR4dGurfbplm/ X91bt/rGB/jlz7IK+Yz7i3L8qTjVlzT/AJVq/n2Xr8kz6jh/hHMc2d6UbQ/m e3y7v0+dj8vLuyvrAWRv7K9sVvIBPZNcW7xi6iPSSMsBvXjquRVav3R8WeFv DXjzwlJoHjPQ7DxJpDcrBdpzAf70LjDRMOxQj8RxX5//ABR/Y88Q6J9o1j4V 3dz4v0kZZtCvGVdTgHUiJ+EuQPT5ZPZq8PIvEbAY2Xs8SvZSe13eL/7e0s/V JefQ93PvDjH4GPtMO/ax62VpL/t3W/ybfkfMvgD/AJHqb/ryf+a17Aa8i8Cx TW3xKvrS5hntLy3tZI7i3njaOWFgVyrowBU+xFeu19liPjPjsIrQDsaKD60d qwOgK+qf2UvBX9t/Ge88WXcW6x0GD9wSOGuZQVX67U3n2JU18rd6/WL4GeCv +EH/AGcNDsJ4fJ1S9X7fqIIwwllAIU+6oEQ+6mvjeOc0+qZbKEX71T3V6dfw 0+Z9nwNlf1vMozkvdp+8/Xp+OvyPX6KKK/BT97CiiigDg/if4Gs/iV8A/FPg i9aONdUsWjgmdciCcYeGTHfbIqNjvjFfhh8Svg/8QPhN4jFh4z0Kezhkcra6 jD+8s7rv+7lAwTjnacMO4Ff0F1maxoukeIfDd1o+u6ZY6xpVymy4tLyBZYpB 6FWBFfY8K8YV8mbhy81OTu1s790/6+R8ZxXwbQzlKfNy1Iqye6a7Nf1bzP5u qK/QP9qT9lbwp8PPhzqHxJ8D6jPpmlRXMcd1oVzmVVMsgQGGQncACfuvnvhu gr8/K/fsmznDZnhlXoPTbXRp9v8Ahj+fs6yXE5XiXh8Qtd9HdNd/w66hRRRX qnkhSqzpIskTFJUYMjDswOQfzpKKAPovTr9NU0Cy1GPgXEQdh/dbow/76BrQ M9z/AMItrehpeX1vo+sweRqtpBOUjukDBhuXpuBAIbGRjrivM/h7qO63v9Hk blD9ptwfQ4Dj88H8TXpFeTVppSaa/rdHtUKjlFST/rZnj2s+CNRsA9xppfVb MclQuJ4x7qPvD3X8q4jOQfbg+1fTIODkZFc7rXhjStbDSTRm0vyOLuAAMf8A fHR/x5966aeJe0jkq4PrEu6F/wAiLov/AF5R/wAq9n+DHjL/AIQf9ovw9rE0 vladLN9j1DJwvky/KzH2U7X/AOACvIdPtmsvD9hZSOsr28CxF1GA2O4Bq53r zcZhoYmjOlPaSa+89TA4mphqsKsN4tP7j9wKK8l+B/jH/hNv2bvD+pTS+bqV rH9hvyTlvNiAXcfdl2P/AMCr1qv5jxmFnhq86M94tr7j+nsHioYmhCtDaST+ 8KKKK5jpCvPPix45g+G37Oni3xpMY/M02wZrRH6SXDYSFD7GRkB9ia9Dr82/ 2/PiF5en+EvhhZT/ADSsdX1VVPO0bo4FPsT5zEH+6hr3eGsq/tHMqVBrRu79 Fq/8jweJs2WXZZVxF9UrL1ei/wAz80rm5nvNRuLu6mkuLqeRpJpXOWdmOSxP ckkmoKKK/qZK2h/KbberCj8jz0PIPsfUe1FFMD9DPg5+1n4Qj8NaX4N8e6Fp Pw+itVEVnqWh2nl6Qe2ZYFy1ux7uNynvivt6Ce3u9Mtr6yuba+sLmPzLW6tZ llhnX+8jqSrD3Br8Fa9M+Gvxf8ffCfVTJ4Q1cDSZJN93oV8pm0+59SY8/u2/ 24yrfWvy3iLw2o4hyrYGXLJ6uL+F+j3T+9eh+qcN+JVbDKNHGx54LRSW6Xps 19z9T9ozXJ+LvG/hbwNpK3XifVFtJJF3W1jCvm3dz/uRDnH+02F96+Urr9qb WvFHw4sZ/Cfh2PwlfzqyXt3c3Au2hkU4YWwwAF7h3BYenevBrq5ur/VrjUL+ 6utQ1C4bdPdXUpkllPqzHk/yFfLZN4d4mpLmxr5Euis5P56pL735Lc+szjxG w1OHLgVzt9Wmkvlo2/uXqeg/E/4gxfEnxxbawfC2j6LJao0UF75YfUp4zgbZ 5xjevAwmCF7GvOPeijvX6zg8HRwlGNGirRjstf1PyXGYytiq0q1V3lLd7fkF FBo4rqOY9a+CHgr/AITr9ozQ9Mni87S7R/t2ogjKmGIg7T7MxRP+BV+s9fKX 7KHgr+xvg9feL7qLbfa5Pttyw5W2iJUY9Nz7z7hVNfVtfg3HOafW8ycIv3af u/Pr+OnyP3rgXK/qmWqpJe9U975dPw1+YUUUV8YfZhRRRQAUUUUAfKX7af8A yYH4j/7CFl/6UJX4qV+1f7af/JgfiP8A7CFl/wClCV+Klfv3hh/yKJf43+UT +ffFL/kbx/wL85BRRRX6Mfm4UUUUAaWj6idI8U2OojJSKT96B/FGeGH5HP4V 9DHGcq25DyrDowPQ/lXzP1HPIr27wbqX9o+BYI5G3XFk32eTPUgcofxXj8K5 MVDRSO3Bz1cTqaDRRXEegA6UUfrQaAPrn9knxl/ZvxQ1fwZdTYttXt/tFmrH pPECSAP9qPcT/wBcxX6EV+LXhnXrzwv8QdF8R2B/0rTryO4QZwH2tkqfYjIP sTX7K6TqdnrfhfTtY0+TzrC+to7i3f8AvI6hlP5EV+LeImWexxkcTFaVFr6r /gW/E/afDrM/bYKWGk9YPT0f/Bv+BoUUUV+eH6IFfhh+1hd3N1+3/wDEP7TP JP5NzBFFuP3EW2iwo9AP6n1r9z6/Cn9qj/k//wCJP/X9D/6TQ1+meFiX9p1f 8D/9KifmHiq2sspf41/6TI+faKKK/eD8DCiiigYUV3Ph7whHrXhCe9nuZrOd 5ito4XcuF4YsvcE8ZB7VzuraFqeiThb+3xCxxHcxndE/0bsfY4NQqkW7X1NH SkoqVtD1XwR/yTS0/wCvib/0KurrlfBH/JNLTr/x8Tf+hV1Wa82p8bPVo/Av QKKKKgsO9b3hjw/eeK/iHo3hzTwfteo3aQIduQgY8ufZRlj7A1g9q+xv2R/B X23xrrPjq7izBpsf2OwYjgzyDMjD3WMgfSWvJzzMlgMDUrvdLT1ei/E9bI8t ePx1Ogtm9fRav8D7q0jSrPQ/Cmm6Lp0fk2Fjapb26eiIoUZ98DrWjRRX81yk 5NtvVn9LRiopJLRBRRRUlBRRRQAUUUUAfKf7aKO37AXiUqjMFvrIsQM7R9oQ ZPoMkD8a/FOv6MvGfhbTvG/wp8Q+EdWXOn6tYSWsrbcmPcpAcf7SnDD3Ar+e XxDoWo+F/Hms+G9Xi8jVNLvZbS6TsJI3Ktj1GRwe4r9x8LMdTlg6uG+1GXN8 mkv0/I/CvFXA1I42lifsyjy/NNv8b/mY9FISFUsegGTV+/02/wBMliS/tpLc SoHhc8pICMgqw4PB6da/Urn5VZlGjtRR2pgFdj4H1H7F42W0dsQX6eScngOO UP55H41x1KrOkiyRMUlRgyMOzA5B/OpnHmi0VCXLJM+lqOtU9Ov01TQLPUY+ BcRB2Ufwt0YfgQaufyryWrHtJpq6DvR2oooGH41+kP7KvjH+3fgRceGrmXdf aBc+WgJ5NvKS8Z/BvMX2AWvzer3P9nfxj/wiP7TOkJPL5em6wP7NusngGQjy 2/CQIM9gWr5ji/LPruWVIpe9H3l8v81dH0/CGZ/Uszpyb92Xuv5/5OzP1Ooo or+ej+hgr8Kf2qP+T/8A4k/9f0P/AKTQ1+4Wta5o/hzw1dazr+qWGjaVbJvn u7ydYo4x7sTj8O9fg58f/FOheNf2wvHPifw1eNqGh314htLkxNH5oWGNCwVg CBuU4yASMHAr9R8LKNT6/Vqcr5eW1+l7rS/c/K/FatT+oUqfMubnvbraz1t2 PHqKKK/cz8JYU5I3lnjhjx5kjhEz0yTgfzptWbL/AJDlj/19R/8AoYoGj6Gt LOPTtJtdPhGIraIRD3x1P4nJ/Gp2VZIHjkRJYnGHjdQysPcHrUsv/H1J/vn+ dR9ulePe57dkirZWNppuni0sYRb2wkZ1jBJCljkgZ7Z7dqtUUAEsAAST0Aob BK2gUfyqkupWL682lx3Mct+sZkeKM7vLAx94jgHnp1q7RYE09hyqzyqkas7s cKqjJJPYV+vfwp8Gr4D+A3h/w6yKt9Hbia/I/iuJPmk574J2g+iivz6/Z08F /wDCYftJ6ZPcReZpWij+0LrI+VmQjyl/GQqcdwrV+pNfkniPmnNUp4OL295+ vT8Lv5o/XPDfK7U6mMkt/dXp1/Gy+TCiiivy4/UgooooAKKKKACiiigAr8kv 27Phz/YHx90r4gWMGzTvEtt5V4VXhbuBQpJ7DfH5ZHqUc1+tteFftIfDj/hZ 37I3ifQ7aDz9as4v7S0gBcsbiEFgi+7oXj/4HX1HB+b/ANnZpTqN2jL3Zej/ AMnZ/I+W4yyf+0cqqU0ryj70fVf5q6+Z+DMn/HvJ/umvo6OGG58NWttdQRXN s9rFvilXcrfIO39etfOMn/HvJ/umvpWwjlntdKtbeGa5upoIkhghjLySsUXh VGST9K/pHFuyTP5rwSu2jznWfAJ+e40GTPc2U78/8Ac/yb8682mjkt7+S0uY 3t7uP/WQyLtdfqP61+lHgf8AZ11XURFqXj65m0GxOGXSLVwb2Uekj8rCPYbn +le8eIPg38LfE/w4h8Kar4L0oaTbg/Y5bRfJvLRj1kjuBmTeep3Fge4NfDY3 xFy/CVlSV6ndxtZfPaXy08+h91gvDjMcXRdXSn2Ur3fy6fPXy6n4uUV9V/FP 9k7xt4KS61nwVJcfELwtGC7xwwhdUs0HUyQLxKo/vxZPHKivlJWVgdpzglWH QqR1BHYj0PNfZ5Zm2EzCl7XDVFJfivVbr5nxOZ5Ti8ureyxNNxf4P0ezXoep fD3Ud1tf6PI3KH7Tbj2PDgfjg/ia9Hr560bUW0nxTY6iM7IpP3o/vRnhx+R/ SvoY4yNrBlIyrDuD0P5U8TC0r9ysJO8LdhO/vR60Ud65zqD605HaOdZEdkdS GVlOCpHQg+tNo7UAfsL8MvFyeOfgX4c8S71a5ubULeAfwzp8kox2G5SR7EV5 J+0T+0ZpnwM0PTLODST4g8WarFJJZWjS+XDAikL5sxHzYJOFVR821uVxmvL/ ANkLxjsvvEfgS6l+WRRqNgpP8Q2pMo+o8sgf7LGvgb9pT4hf8LJ/bC8V6zbz +fo9lN/ZmlMGypggJXcp/uu/mSD/AH6/Isp4Np1eIKtCrG9KHveqfwr+uzP1 zN+M6lHh+lXpStVn7vo18T/rujkviT8XvH/xZ8SjUfGmuz30UblrXT4v3dpa 9v3cQ4BxxuOWPcmvNKKK/b8Ph6VCmqdKKjFbJaI/DMRiateo6lWTlJ7tu7Ci iitjEKs2X/Icsf8Ar5j/APQxVarNl/yHLH/r6j/9DFDBbn0lL/x9Sf75/nUf 50+X/j5k/wB8/wA6ZXjo90zdT1jTNItRLqF0kRI+SJfmlf6KOfxOB715ZrXj XUtSV7exDaVYtwQjZmkH+0/b6L+del6v4f0rW483sBS5C4S7h+WVB6Z/iHsf 0rynWvCWqaMHnCjUNPH/AC8wKcoP9teq/Xke9dWHVPrucWJdXpsaHw+AHjmY AAD7E5/8eWvYO9eP/D8g+OJiCCPsL4IPutfSHgDwpceOPjJ4f8LwbwL66VZ3 XrHCvzSv+CBj9cVhj60KSlUm7JK79Eb5bRnV5acFdt2Xqz9Af2YfBX/CM/s9 x63cw+XqfiGQXbEjDCBcrCv0ILOP+ulfSFQWttb2Wm29naRJb2sESxQxIMKi KMKo9gABU9fy9meOnjcXUry3k7/5L5LQ/qTLMBDBYSnQjtFW+fV/N6hRRRXC d4UUUUAFFFFABRRRQAUUUUAfhP8AtQfDf/hW/wC154n0u1g+z6Jqrf2ppW1c KsM5YsgHYJIJEA9FHrX0L+zp8cPg/pumWOgazpVv8PvGjRJbtr99OZ7bUSAF AFw3Nrn/AJ5sAn+0a91/bm+HP/CTfs42Pjmxg36p4Xuc3BUcvZzFUfp12uI2 9l3n1r8hCAVIIBBGCCODX9BZTTo8S5DCnXm04+62nbVd+jurOzXpZ6n885vU rcNZ/OpQgmn7yur6Pt1VndXT/wAj98dpAQ8FXQPGykFXU9GUjgg9iODTec1+ Pfws+PvxC+FDQ2GlXqa/4TD5k8O6s7PbqO5gcfPbt/ufL6qa+w9T/bR+HkPw wh1PR/DniW/8WzAr/YF2FhitXA+9LdLlXjz08tdzei1+b5p4fZtha6hSh7WL 2a/9uT+H1vbzP0rKvEXKcVQc60vZSS1T1/8AAWt/S1/I+vLi6t9P0u51O9vL XTbC0Xzbi9uZ1hht1H8TyMQFHuT9K/MX9pX4lfBfx3r7f8IP4X/tPxYsg+1+ NbcmyguADynlYzd57SuFx1BavGfiR8W/HvxX1dZvGGsmXTYpC9nolmph0+1/ 3Ys/O3+25Zj6ivNq/QeE+Anl1WOJxFRuoukW0l5N6OXpovJn53xbx9/aVN4a hTSp95JNv06R9dX5oOo55Fe3eDdS/tDwLBG7brmzb7PKSeSByh/FePwrxGux 8Daj9i8araO2IL9PJOTwHHKH88j8a/Q8RDmh6H55hp8tT1PZqKKO1eaeqFFH tRQMv2HivWvBMl54m8PXItNXs7KcQSldwXfG0Z4+jHHocGvlkdBX0Hr3/Ii6 1/15P/KvnwdK6sHTinKaWrsr+S2/N/ecONqTajBvRXdvN2v+S+4KKKK7jgCi ijnsGY9gqkk/gKACrVgM6/p49bqL/wBDFVFZXjV0ZWVhkMDkEVd07/kYtO/6 +4v/AEMUnsNbn0fL/wAfMh/2j/Oo6fJ/x8yH/aNMrx0e4B9e9KCQcg4PsaTt RTEZcOi6bbeJH1W1tltbt42jlEXyxuCQclegbjqK+9/2RPBWItf8fXkXLf8A Eu04sO3DzMP/ACGoI9HFfEtnaXN/q1rY2cL3F5czLDBEg5d2IVVHuSQK/Yvw J4VtvBPwh0DwvbbCLC0VJXXpJKfmkf8A4E5Y/jXwfiDmroYBUE/eqaf9urf9 F95974e5Sq+Pddr3aev/AG89v1f3HW0UUV+Hn7iFFFFABRRRQAUUUUAFFFFA BRRRQBl63o+n+IfBuraBq0AudL1KzktLuI/xxyIUYfkTX89HjzwjqHgL4yeJ fBuqBjeaTfyWxcrjzVB+SQD0dCrj2YV/RXX5cft8fDj7D438NfFCwt9tvqUf 9masyrx58aloXPqWjDr9IRX6V4aZv9Xx8sNJ+7UWn+Jbfer/AIH5n4nZP9Yw EcVFe9Tev+F7/c7fifndRRRX70fgNgooooAKVWdJFkjYpKjBkb0YHIP50lFA H0Xp1+mq6BZ6lHwLiIOw/ut0YfmDVyvN/h7qO62v9HkblD9otx7HhwPxwfxN ekV5VSHLJo9ilPngmHeijtRUGpl67/yImtf9eUn8q+fB0FfQeu/8iNrX/XlJ /KvnwdBXbhPhZ52N+JBRRRXWcYV9p/sNfDr/AISr9qa+8bXsHmaR4Rs90JYA q19cKyRjB/uRea3sWSvip3WOF5HO1EUsx9AOTX7kfsofDhvhx+xf4bt7238j X9bB1nVg33lknAKRnv8AJEIkx2KmvieP82+pZTKMX71T3V6P4vw0+aPuPD7K PrubRnJe7T95+q2/HX5M89+Mv7FfgPx415rvgJ4Ph54ukJkdLeHOmXj8n97A MeWxOPnjx3yrV+Y/jT4WePvhP8TNM0nx54eudIaW+jWzv0Pm2V786/6qcDaT gg7Dhxnla/dXxv8AEXwf8O/D39o+K9ZgsN4P2e1X95c3JH8McQ+Zj74wO5Ff n78Wf2ifEHxH0q98O6bplpoHg2f5ZYLmJLi7u1BBBdiCsXIBAT5h/fr5HgbO c9qWhKPPR7ydrej1b9LP1R9jx1kuRQvOMuSt2jrf1WiXrdejPnqX/j6k/wB4 /wA6Z29aPxor9MPzMPWiiigD6V/Zd8Ff8JJ+0B/b11D5mm+HohckkZU3DZWE fUYdx7xiv0srwz9njwV/wh37NmlNcReXqmsf8TG7yPmUOB5aeoxGF47MWr3O v574wzT69mc2n7sfdXy3+93P6F4Pyv6jlkE170vefz2+5WCiiivlz6gKKKKA CiiigAooooAKKKKACiiigAryz41/D6L4ofsyeLPB5RGvrm0Mumu2Bsuo/nhO ewLKFJ/us3rXqdFb4bETw9aNWm7Si016owxWGp4ijOlUV4yTT9GfzVyxSwXM kM0bwzRsVkjdcMrA4IIPQio6+qP2wfhz/wAIH+2DqmoWcBi0TxMn9q2hA+VZ WJFwmfXzMvjsJFr5Xr+r8sx8MbhKeIhtJJ/8D5PQ/krNMBUwWLqYee8G1/k/ mtQoooruOEKKKKANLRtROk+KbHUeSkUn70f3ozw4/I5/CvoY4zlSGU4Kt/eB 5Br5n6jnkV7b4N1L+0PAsCO265sm+zyknkgDKH8V4/CuTFQ0Ujtwc7PlOqoo oriPQMzXP+RH1r/ryk/lXz2Ogr6E1z/kR9a/68pP5V89joK7cJ8LPOxnxIKK KK6zjPT/AINeENO8bftJeGdJ165tbHwrazf2n4hurqQRxR2VuRJIrMem9tkY 9fMr9IPiT+1nI5n0j4W2iiMZQ+INQg4+sEDdfZpMDn7pr85/AGmiLQbvVJky 91II4cj+BDkn8W7/AOzXoHU18pm+SYbH4uNXELmUFZR6X6t93su2mzPrsnzr FYDBypYd8rm7uXW3RJ9Fv567lzUtS1LWfENxq+s6je6xq1wf315eTGSV/bJ6 D0UYA7CqdHr2or0oxUUklZI89tyd3uw9qO1FB6GmIK9I+EvgxvHnx98P+H3j aSwM/n6gewt4/mcE9t2AgPqwrzev0A/ZI8Ff2f4A1jxzdw7bnVJPslixHIgj b52B9Gk4P/XIV4HE+afUMuqVU/eei9X/AJb/ACPf4Yyv6/mNOk17q1fov89v mfYCqqoFUBVAwABgAUtFFfzkf0cFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF FFFAHyP+2d8OP+E2/ZHutes4PN1rwpKdRhKrlmtiNtyvsNmJD/1xFfi/X9KN 1a299plzZXkMdzaXETRTxSLlZEYEMpHcEEiv59fi54CuPhn+0b4s8FzCQw6f fN9ikfrLbPh4XJ9TGy59Dkdq/bPC7N+ehUwUnrH3l6Pf7nr8z8Q8U8n5K9PG wWkvdfqtvvWnyPOKKKK/WD8kCiiigArsfA2o/YvGq2jtiC/TyTk8CQcofzyP xrjqVWdJFkiYpKjBkb0YHIP51M480WioT5ZJn0tQKp6dfpqnh+z1GPhbiIOw /ut0YfgQa6zwz4U8R+MtfbTfDGlT6rcJjz5AQkFsP70sp+VB9efQGvFrVYUY OdSSilu3ol8z3qFKdaajTTbeyWrfojjdbOPBOs/9eUn8q+eh0FfrN4W/Zz8J 6foNx/wnDjxjfXMDRTWsbPBZQhhg7MEO7Y6OSMHkLXzj8T/2ONc0o3Gr/Ce/ m8UaaMu3h/UJVXUIh1xDKcJcD0Vtrn/arwMv47yepiHQ9pbtJq0X8+nq0l5n u5lwJnFPDqv7O/eKd5L5dfk2+58T1LDBLdXsNrbqWnmkEcYHqTgUt1bXVhrF 1p2oWl3p+o2rmO5tLqFopoGH8LowBU/UV2vgHTvtPiifUnXMVlH+79DK4IH4 hcn8RX3EppQ5j4aFNufKz1W1tYbDS7axgA8m2iEaY74HX8Tk/jU9H40nO8fS vKPYQUveiigYe+KKKKBGnoukXuv+MNM0PTo/Nv7+6jtoF7bnYKM+3PJ7Cv2T 8N6DZeF/AOj+HdOGLPTrRLeIkYLbRgsfcnJPuTXwX+yb4L/tb4saj4xu4t1l osHlWpYcNcSgjI9dse/PoXU1+h9fjPiJmntcXHCxekNX6v8AyX5s/aPDrK/Z YSWKktZ6L0X+b/JBRRRX5yfowUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU UAFfm/8At9fDnztI8LfFKwgy9uf7J1cqP4GLPA59AG8xST/fQV+kFcR8SPBV l8RfgV4o8FX5RYdWsHhjkcZEMo+aKTH+xIqN/wABr3OG82eXZjSxHROz9Ho/ 8/U8LiXKVmWW1cP1auvVar/L0P53qu6dYy6nrltp8DxRzzsVjaQ4XOCeT26U up6be6P4k1DSNSt3tNRsbmS2uoH+9FIjFXU+4IIrT8J/8lK0b/rv/wCytX9S ua5OZH8rKHv8sjKvtPvdMv8A7LqFtLaz9g44ceqnow+lU6+kLq1tb/T2tL63 hu7dv+Wcq5APqO4PuK801jwBcRu02gu96h5+xyn96P8Acbo/0OD9axp4lPSW htVwso6x1R51RSsrJIyOpV1JDKeoI6ikrpOVnvfwL1f4ax+Ln0f4q6vqmkaC 0olsprcbbcyH7yXEgy8cZwDuUeuSK/WTR7LRtP8ABun2/hqDSrfw4ybrH+y9 ptZBj7ysvDH1JJb1r8IK9U+Gnxm8ffCnU93hfVt+kO4a60W+BmsrjnJzGT8j dfmTBycnNfn/ABjwfXzX95RrNNfZfw+q7Pzd/kfoPBnGVDKX7OtRTT+0viXk +68tPmfsy33OxpSPXkV88/Df9pr4a/EDTlttVvYPAPiVY90tjqtx/o0uBljB cHhuhOxsNgVgeOv2i0iefTPh7aebICVbXNQh+Ue8EB6+zycdwtfj1HhPNqmJ eH9i1Jbt6Jed9n8r36H7LW4uymnhViPbKSeyWsn5W3Xzsu53/wAbfC3wf8Qe CEuvi1DbW90Iium6laNs1gccCAqC0i/7Lho/pX5/6bo+n6FaXFhpU97d2P2q SSG4vIkjuJUJ+UyKhKhgoAwpIra1HUNQ1jXrjVNYv7zVdTnOZru7lMkj+2T0 HsMAdhVTvX7Rw3kdTK8N7KVZzv0+yv8ACunn33sj8W4kzunmmJ9rGioW6/af +J7P7tNrsPek/wCWg+lL3qhqOo2Wk6Y1/qMxtrRCFaTYW+Y/dXjucHGcZr6F anzzdi/39axdT8Q6XpN3FazzGa+kkVFtYcM67iBluyjnvz7V53rXjm/vd9vp avpVoeDJnM8g+vRB7Dn3rj7H/kOWR6k3UZJPJJ3jk+tdUMM95HHUxa2ifSDA rIynkg4yBTakl/4+ZP8AeP8AOvYfgN4K/wCE2/aR0W0uIvN0rT2/tC/BGQUi IKqfUM5RSPQn0ry8Zi4YbDzrT2imz18FhJ4rEQow3k0vvP0H+C3gr/hBP2dt B0iaLytTnj+2aiCMN58oBKn3Vdqf8Ar1Wiiv5lxeJnia86095Nt/M/pvCYWG GoQow2ikl8gooornOgKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig D8df23fhx/wiX7UUXi6xg8vSPFdubhiowq3ce1Jx/wACBjkz3Lt6V8peE/8A kpWjf9d//ZWr9pf2rfhz/wALF/Y71+O0g87W9DH9r6dtHzMYlbzUHc7ojIAv dtvpX4g2l3cWOoxXdpKYLqI5jkABKnBGRnvzX9GcB5t9fyiMJP3qfuv06P7t Pkz+buPco+oZxKcV7tT3l6/aX36+jR71q2t6ZokAbULjZKwzHbxjdLJ9F7D3 OBXlOteMdU1ZXt4CdM09uDFE/wC8cf7b9fwGB9a5V3eSd5ZXeWVzl5HYszH1 JPWm19jToRjq9WfHVcTKe2iCiiitznCiiigAIDAhgGB6gius0XxhqmkqkE5O qaeOBDM/zoP9h+o+hyK5QdaSplFSVmVGbi7pn0FpOt6ZrcG7T590yjMltINs qf8AAe49xkVroju+1FLt1OOw9T6V80I7xzpLE7xSoco6MVZT6gjpX0B4fv7u /wDh9pM15N5sskO6VtoBkbcRubHU4A59q4K9Hk1R6WHxHO7M1yAOjBj3K9Pz 719Ffs3WlnqPjLx9p2pWVlqenXOhQx3NneQLNDOv2j7rowIYfX8K+c6+k/2Y /wDkpfjT/sCw/wDpQK+U4vbWTV2uy/8ASon1vCEVLOaCa3b/APSWYvxR/Y40 rURc6z8JL2HQb7ln8N6lOTZSnri3nOWhPokm5P8AaWvhHVPDniDwh8TIPD3i rRdS8O65BcxmSzvoTG5G8fMp6Oh7MpIPrX7h319Y6VodxqeqX1npem24zPd3 cojiT6se/sMk9ga+OvjJ8XPCnjrww3hTS/C+n+ItPik3Qa3rNsQ9s4Od9mvD xngfOxAP9w18twVxVnFaaoVIOrBbyejj6yekvR+8+59Pxvwnk1CDr05qlUe0 VqpeiWsfX4fI+epf+PmT/fP86/Rr9lXwV/YPwQufFF3Ft1DX590RYcrbRkqn 03MXb3BWvgXwh4cvPGPxR0Pw1ZFvtOo3iwmTbny1Jy8h9lUMx9ga/ZDTNOtN I8O2GlafEILGyt0t7eIdERFCqPwAFaeIuaeyw0MJF6z1fov83+RPhzlftcTP FyWkNF6vf7l+Zdooor8cP2QKKKKACiiigAooooAKKKKACiiigAooooAKKKKA CiiigAooooAQgMpDAEEYIPevwP8A2gfh0fhd+1d4q8MwwGHSGn+2aRxhTazZ dFHqEO6PPrGa/fGvgX9vL4cf2z8HtC+JFhb7r7QJ/smpMq8taTMAjE+iS4AH /TZjX3vh3m/1PNFSk/dq+78/s/5fM+A8Rsn+uZW6sV71L3vl9r/P5H5Q0UUV /Q5/OgUUUUAFFdT4P8D+MPiB4wj0DwT4c1PxLqzYLxWsfyQKTgPNIcJEmeNz kD0zX6U/Bz9hbw9ootdd+MF3B4t1cYdNAtGZdNtz1xK3DXDDjrtTI+6w5rwM 74mwGVQvXn73SK1k/l09XZH0GR8MZhmsrUIe71k9Ir59fRXZ+dfhX4Y+OfGX gDxF4u0PQZ28I6HYT3uo63dHybQJChd44nb/AF0uBwiZ5IyRXAA5UH1r98Pj rZ2em/sE/FGx0+0trCxt/CN5HBb28QjjiUQMAqquAAPQV+B6/wCrX6Vw8J8R 1M5p1asoKKjKyW+lur7/ACR3cXcN08mqUaUZuTlG7e2t7aLsLXu/hT/km2i+ 8B/9DavCK938Kf8AJNdG/wCuB/8AQ2r6TFfCj5vB/G/Q6DHNekfDX4ht8N9U 8SalBpK6xf3+nx2tpHLKY4Y2WTeXkI+YjHQLyT3Feb96K8fGYOjiqMqNZXi9 16O/T0PcweLq4WtGtRdpR2fyt1Ol8VeMPE3jbXFv/E+qy6i8Zzb2yr5dtbD0 iiHyr9eWPc1zVH+TUkUUk9zFBBG8s0jBI0RSWZicAAdya0o0adGmoU4qMVsl okZ1q1StUc6knKT3b1bPsv8AZF8FfaPEeu+PLuEGKzT7Bp7MP+WrANKw9Cqb V+kjV95Vwnwz8Hx+BPgf4e8NBUFzbWwa8Zed87/PIc9xuJA9gK7uv514kzT6 /mFSsn7uy9Ft9+/zP6L4byv+z8up0Wvetd+r3+7b5BRRRXhHuhRRRQAUUUUA FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUNzbW95p89peW8N1azIUmh mQOkikYKsp4II7GpqKabTuDVz4O+Mf7EPhbxOLrXPhdPb+D9cbLtpU2Tp9wf RMZaAk+m5ewVetfmX44+HvjL4ceMX0LxpoN9od+MmLzlzHOoON8cgysi+6k1 /RLXNeK/B3hfxz4Rn0Hxdoen6/pMv3oLuPdtP95W6ow7MpBHrX6Hw/4h43BW p4n95D/yZfPr8/vR+dcQ+HWCxt6mG/dz/wDJX6rp6r7mfzmUV+hnxi/YZ1fS vteu/CO8k13Txl30G+kAuohySIpDhZR6K2G93NfAGo6bqOj65daZq1jeaZqV tIY7i1uoWiliYdVZWAIPsa/acoz3BZnT58NO/ddV6r+kfiWb5DjssqcmJhbs +j9H/TNTwv4u8VeCfFsGveD/ABDqvhvV4iMXFjMV3gHO2RD8siequCPav0g+ Dv7dul34tdC+M9jDoF7gIviXTomNlKcYzPFy0BPdl3Jk/wAAr8vqKxzrhvAZ pC2Ihr0ktJL5/o7ryNsk4lx+VTvh56dYvWL+X6qz8z96/jjqWnax+wF8UNT0 m/stU0258JXr293aTrLDMphb5ldSQw9wa/BJf9Wv0r0PwL478YeGrLU/B2ia /eWfhXxLBJp2saS4EttLHMux3SNsiOYA8SJg565rI1nwfqmjo80QOpaev/Le FTvQf7adR9RkV5vCvD7yWFWjKfMpO6ezta2vn8z0uLOIVnc6VeMHFxVmt1e9 9PL5HK17v4T/AOSa6N/1wP8A6G1eDgggEEEHoQa948KD/i2ujf8AXA/+htX0 uL+FHzWDfvv0Ogorq/CXgjxR468RDTPDGkXWpzjHmug2xQg/xO5+VR9Tz2zX 3V8Nv2XPDnh0W+qeN5IfFGsDDCzAP2KE+hB5l/4Fhf8AZ718lnPEmBy2P72V 5fyrf/gfM+xybhvHZlL91G0f5nov+D8j5C+HfwX8b/Ei5jm0ux/s/RN2JNVv QUgHrs7yH2Xj1Ir7++HHwI8EfDsQ3sVt/bviJME6pfICyN6xJyI/qMt/tGva Ioo4LaOGGNIYY1CpGihVUAYAAHQCn1+P55xjjsxvBPkh2XX1fX8F5H7DkfBu By602uefd9PRdPxfmFFFFfJH1oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU UUAFFFFABRRRQAUUUUAFFFFABRRRQAV5L8Ufgj8O/i7obW/i7RI21JY9ttq9 piK9t/TbJj5h/suGX2r1qit8Niq2HqKpSk4yXVaHPisLRxNJ060VKL6PVH4s fGP9kX4ifDIXWsaJHJ448IxksbywhP2m2X/ptCMnAHV13L3O3pXydX9LFfK/ xj/ZL+HXxSF1q2mQp4K8Xvlv7R0+EeTct/03h4Dc9WXa3qT0r9a4f8TNqWYL /t9fqv1X3H5HxD4Yb1cvf/bj/R/o/vPxl0H/AJHvRf8Ar9j/AJ19B5YPlSQc 8EVzvjX4FfEb4QfFTR4vFWjPJpLagi22s2OZbOf5uPnx8jH+64VvYjmvrD4b /s1+MPGbQalr6yeE/D74YSXEf+lTr/sRHoCP4mx6gNX3+YZ5gKdCOJlVXI1o 73v6d35HwGW5FmFSvLDKk+dbq1reb7LzPlG4+Hsfi3W4rXRLG5TX7l9sSWMB k89vRo16/VcGvtv4M/si6jB4O0ib4o3S2fkR86Pp8253+Yn95KPujn7q5P8A tA19keB/hp4O+Hmj/ZfDOkx287qFnvpv3lzP/vyHnH+yML6Cu8r8oz3xDxOI TpYT3I938Xy7fi/Q/Wci8OsNh5Kti/fl2Xw/Pv8AgvJmPoegaL4Z8Ow6ToGm Wek6dF9yC2jCrnuT3LHuTknvWxRRX5xOcpycpO7Z+kQhGEVGKskFFFFSUFFF FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU AFFFFABRRRQAUUUUAYHib/kUZP8Ar5t//R8db9FFbS/hL1f6GS/iv0X6hRRR WJqFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q== name vagrant passwd ******** passwordpolicyoptions PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp bGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr ZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt MDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8 L2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgo8L2Rp Y3Q+CjwvcGxpc3Q+Cg== realname vagrant shell /bin/bash uid 501 chef-12.14.60/spec/data/mac_users/10.7-8.shadow.xml000066400000000000000000000005071276456504500212560ustar00rootroot00000000000000 SALTED-SHA512 b3XXGQRB+sw0KR676h/HVrJC1P6bz/FBvMuE8ZeeJ+U5U5qjH599zJLAzqlZ6hjhi3IO NY5/vjz76qVhRW9roAiTejA= chef-12.14.60/spec/data/mac_users/10.7.plist.xml000066400000000000000000000754771276456504500210010ustar00rootroot00000000000000 KerberosKeys MIIBVKEDAgEBoIIBSzCCAUcwc6ErMCmgAwIBEqEiBCCBdluECwTg7Fe5bsZ+ kxWTdvLPPtNGBCZOK2+aEFrkBaJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuREZG QTkxRjM1QjUxNjMzMDNDMDc5RTk5ODc2NDAzMEQwOTU2QUYyNnZhZ3JhbnQw Y6EbMBmgAwIBEaESBBAHZXv8koch6fiOdgRkDXyjokQwQqADAgEDoTsEOUxL REM6U0hBMS5ERkZBOTFGMzVCNTE2MzMwM0MwNzlFOTk4NzY0MDMwRDA5NTZB RjI2dmFncmFudDBroSMwIaADAgEQoRoEGKs+5dPs07zLf/0Vhu+YWCXZ6iwg NLpkqKJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuREZGQTkxRjM1QjUxNjMzMDND MDc5RTk5ODc2NDAzMEQwOTU2QUYyNnZhZ3JhbnQ= ShadowHashData YnBsaXN0MDDRAQJdU0FMVEVELVNIQTUxMk8QRG911xkEQfrMNCkeu+ofx1ay QtT+m8/xQbzLhPGXniflOVOaox+ffcySwM6pWeoY4YtyDjWOf748++qlYUVv a6AIk3owCAsZAAAAAAAAAQEAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAGA= _writers_LinkedIdentity vagrant _writers_hint vagrant _writers_jpegphoto vagrant _writers_passwd vagrant _writers_picture vagrant authentication_authority ;ShadowHash;HASHLIST:<SALTED-SHA512> ;Kerberosv5;;vagrant@LKDC:SHA1.DFFA91F35B5163303C079E998764030D0956AF26;LKDC:SHA1.DFFA91F35B5163303C079E998764030D0956AF26 generateduid 11112222-3333-4444-AAAA-BBBBCCCCDDDD gid 80 home /Users/vagrant jpegphoto /9j/4AAQSkZJRgABAQAAAQABAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBw bAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBs AAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAA AG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAH MAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFk AAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRH ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJv ZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAo AAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUA AAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtv S1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAAD Hmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAa AAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwA AAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZp RkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE 8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAg AFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcA QgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDo AHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8E FwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUABy AG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIA RwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBl AHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkA bMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAg AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEA bABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBs AHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIA RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBv AGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACAD wAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBl AG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8A ZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBH AGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA bgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBs AG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYn BkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwA ZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABz AGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdo dHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRN AAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8A ALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA /ZH///ui///9owAAA9wAAMBs/9sAQwACAgICAgECAgICAgICAwMGBAMDAwMH BQUEBggHCAgIBwgICQoNCwkJDAoICAsPCwwNDg4ODgkLEBEPDhENDg4O/9sA QwECAgIDAwMGBAQGDgkICQ4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O Dg4ODg4ODg4ODg4ODg4ODg4ODg4O/8AAEQgBMQEuAwEiAAIRAQMRAf/EAB8A AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQE AAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3 eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfI ycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJico KSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWG h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW 19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiis6/1Wz0 6P8AfyAyY+WJeWP4dvxoA0aw9R1+zsN0at9puR/Ah4H1NcjqHiG9vQ0cZ+yQ H+FD8x+p/wAK5+gDWu9a1C8u1kad4gpyiRkqF/z71u6d4pI2xaiu4dPOQc/i P8K43v0P5UlMZ7LDPDcW6ywSJLGejKcipa8gtL65sLjzLaV4z3HVT9RXb6d4 mt7grFegW0398fcP+H40hHUUUgIZQykEEZBHeloAKKKKACiiigAooooAKKKK ACiiigAooooAKKKKACiiigAooooAKKKguLmC1tzLcSpDGO7H/OaAJ6K4y48W gXyi2tvMtwfmLnDN9PSuisNVs9RjzBJiQfeibhh+Hf8ACgC3cgHT5wQCDG2Q fpX4u/CD9rf4h/C+aHRtXkfxt4PjbaLG/mP2i2XP/LGc5YADorbl7AL1r9o7 j/jxn/65n+VfzYS/8fMn+8f51+qeG+W4XHUcXSxEFKPub/8Ab2z3T9D8p8S8 zxWBq4Srh5uMvf2/7d3WzXkz99/hb8cPh38XtCW48J6yn9ppGGutHvMRXtv6 5TJ3L/tIWX3zxXrtfgD8JPhh8TviF4ut9Q+Hlvd6ZDZTgv4nluHtLSxYd1mX 5nkGfuRbm+lftD4C1HXfDvw+0vRfGHiW48batBFtudbeyS2aZvaNf4QOAxO4 9W5r57jHh7BZXX5cPXUr/Z3lH1a0++z8nufQ8G8R43NaHNiKDjb7X2Zeiev3 XXpseu0VBb3MF1biW3lSaM91NT18Yfankuk/FDQfHPgxNb8B6zYa1ojna17a ybnjb+5Ihw0T/wCy4Bqkzs8hd2Z3JyWJyTX4a+HPF3ijwL4/bxB4O13UPDus K5DTWr/LMufuSocrKn+y4Ir76+Fv7YXhzXfs+j/FO1tvB+sHCLrtorNplwfW VOXtifX5o/da/Rc/8O8bg71cL+9h2+0vl19Vr5I/OOHvEbBYxqli/wB1Pv8A Zfz6fPTzPs6jt61HFLDPY293bTwXVpcIJLe4t5VlimQ9GR1JVgfUE0/mvzux +jppq6F70lL6elHagBPyo7elHejvQM1tP1m905wI3MkPeJ+V/D0ru9O1yy1D CK3k3B/5ZOeT9D3ry/tR0YH360CPaaK8607xJd2m2K6zdQDjJPzj6Hv+NdzZ 39rfweZbSq/95Twy/UUgLlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS MyohZ2CqBkknAFYmo69Z2AZFb7Rcj/lmh6fU9q4S/wBWvNRc+dJiLPyxJwo/ x/GgDrNR8TwQho7BRcydPMP3B/jXE3V3c3lwZrmV5X9+g+g7VXzj2PrS8EZP FMBvtT1ZkkDozI68gqcEU0gjP86Tv9aBnWWPiWXyWttQ/eIyFRMo+YccZHQ/ pXwD8MP2PdC0iSDWvivd2/inVc+Yvh+xkZdOgPXE0gw9wR3Vdqf71fadHQ5r 1MvzvG4GlUp4efKp2u1vpfZ7rfpqeRmGR4LHVadTEQ5nC9k9tbbrrt10IoIY LTTbaxs7e2srC2Ty7a1toVihgX+6iKAqj2AqXtSVla7r2ieFvDjav4j1S00f TRwstw3Mp/uxqPmkb2UH8K86EJ1JqMVeT6LVtnpznClByk0orvokv0N62u7i yuRLbTPE4646H6jvV3U/i74Q8M3MVh4p1SGw1Z03rZwRvNMU/vtGgLIuSOWx nPFfEXjj9ojWNT87TvAcE/hzTzlW1S4UG+lHqi8rAD6/M/uK8d8JPJL4xvbi eSWe5lgZ5ZpXLySMWXJZjyT7k1+g5XwBXnTdbGPkX8q+L5vZfi/Q/PM08QKE aipYKPO/5n8PyWjfrovU+P5/+P2b/fP86irqfG/hfUvBXxe8SeE9XXGoaVqE ttK23Ak2sdrj/ZZcMPYiuWr97pVI1IRlF3TV0fz/AFacqc5QkrNOzPpr4GfE bxb4B8GyDQdQEmlm/czaReZks5OFzhc5jb/aQg/WvvvwJ8ZvCPjeSGwkk/4R rxG/A02/lGyZv+mE3Cv/ALp2t7GvzM8Af8iHL/1+yf8AoK128dvJeXcFlBbT XtzO4WG2hiMkkrdgqgEk/SviOIuE8BmUpTkuSf8AMv1Wz+evmj7vhzizH5bC MIPmh/K/0e6+Wnkz9V2VkdlYMrDhlIwRTfzr5Ut/E3xQ+CX7Nlz4x8foniHQ bW7tbW38O3FyDqduk0mzd9p5CbRyIn389Ste2fDz4peBPipob3ngvW0vbmJN 15pNyvk6hZ/9dISclf8AbQsnvX4hj8jr4eMqsGqlJO3PHWN9N+268uibP3DA Z/h8TONKadOq1fklpK3l32fn3SO/780d6Xr3/Wkrxj3Be1JS/likoAU9KfFL LBOssMjxSA5DKcGmUn50Adrp3in7sOornt5yD+Y/w/KuximingWWGRJY26Mp yDXjXOenWuL8V/Fzw58MWYahqMlzrRXcmiWJElxJ6GQH5Yl/2nwfQGt8LhK2 JqKnRi5SfRf1+OxzYrF0cNSdWtNRiur/AK/A+naK5vwdr0nij4VeHfEktqtl JqenxXTW6ybxEXUNt3YGcZxnFdJWVWnKnNwlutDWlUjUgpx2auvmFFFFQWFF FFABRRRQAUUV4D8evjfbfBHQ/Cmpajo17qmk6tqD2l3NYyL9otFEe4SJG3yy e67lPoc104PB1sVWVGjHmk9l30ucuMxlHCUXWrStFbv52PcL3UbTT4d1zKFY jKoOWb6CuG1LxHd3ZaODNrbnsp+Zvqf6CvOvCnjfwv8AETwqfEfhHxDZ+JNP JHnyRMfOt2P8E8bfPE3swx6E10fFZVaU6U3CpFqS3T0a9UbUa1OtBVKclKL2 a1T+Yd6KPyrk/F3jjwv4F0lbnxLqaWs0i7rawhXzbu5/3Igc4/2mwvvVUKFW tUVOlFyk9ktWLEYilQpupVkoxW7bsjq6O9fK+hftaeBrj4n3nhvxlpl34It9 yHT9Wkm+02xVlyFudq5ibP8AGoZPp1r6lhlgudOt7y0uLe8sbhBJbXVtKssM yf3kdSVYe4NduZZPjcvmo4mm4327P0aun566HDlmdYHMIuWGqKVt+69U7P8A Akyce1LwTx1pM0qqzyBEVmduiqMk15h6YnQ1HPNBa6bc315cW1lY2yeZc3Vz MsUMCf3ndiFUe5NeXeOvjH4R8DtLY+b/AMJJ4hQY/s2wlG2E/wDTaYZVP90Z b2Ffm58ZfGXxR+IOpvdeKdU/tDwvFIXtNJ0pDFY2Y7F4cku4/wCejlifUV9n w9wTjczanP8Ad0+73f8AhXX1dl2bPiuIuOMFlicIfvKnZbL1fT0V33sfUfxR /bE0LR/tOjfCmzt/FWqDKN4gv42GnQHpmGM4a4I7M21P96vmXR/FPiTxra3f iLxdrmoeItblvJAbq8kzsXC4SNRhY0HZVAFfP4IIyCCD0INexeAf+RCk/wCv 2T+S1+1Zbw1gMqo2w8Pe6yesn8+i8lZeR+JZjxNmGbV74ifu9IrSK+X6u78z ta6zwd/yM0//AF6t/wChLXJnpXt3wO+HWp/EHx1rENldR2FtZWAae5kjLKGe RQicdyFcj/dNGZ4inQws6lR2iupeWYepXxUKdNXk+g79vb4c/wBm/Erw78Tr CDbaaxENP1RlXgXMS5iYn1eIFfpBX58V+/nx0+HafFH9lzxX4TWJZNTktTca UxxlbqL54sHtuI2E/wB12r8BXR45njkVkkUkMrDBBHUEV4/h1m/1vLPYyfvU tPl9n9V8j1vEfJ/qmae2ivdq6/P7X6P5n1L+zt8Orj4lWd/pltr+j6StndNL eRvKJL7yyF+aK3zlx1+c4UY5r9FfBnw/8KeArBk8OacFvZE2z6pdESXk/wBX x8i/7KAD61+JVpd3mna1aalpt5eabqdrIJLW8tJmimgYfxI6kFT9DX218Lv2 x9V082+jfFuym16yGEXxJpsCi9jHTNxAMLOPV02v3IauDjnh/OcYnPDVOan1 gtH/APbej26Js7+A+IMmwbUMTT5anSb1X/2vqvm0j6A/a0/5MP8AEIH/AEGN N/8AR5r8p7G+vtK16z1XSr+90rVbSQSWt7ZTtDPA3qjqQR/XvX6f/tJ+I/D3 i3/gnHrOv+Fdc0zxFos+sad5d5Yzb1z5/KsPvIwyMqwBHpX5b11eGlJxympC as+eSaf+GOjX6HL4l1oyzenUpyuuSLTT85apo+5/hd+2TqFobfR/i9Yy6va8 KnibS7cC6QetxbjCy+7x7W7lWr700TW9F8TeErTX/Der6dr+h3IHkX1jMJIm OM7T3Vh3VgGHpX4SV2Hgjx/4y+HHis6z4L1670S7fi5iXD212v8Admhb5JB9 Rn0IrHiDw4wmKvVwbVOfb7L+X2flp5G3DviTi8JaljF7SHf7S+f2vnr5n7fY 70vavkv4XftbeDfF722j+PobbwB4kchFvDIW0q7Y8cOfmtyf7r5XPRhX1hcT QWelSajeXVraaakQle8mmVYAhHD+ZnaVPYg89s1+MZnk+My+t7LE03F9Oz9H s/kfteV51gsxo+1w9RSS37r1XQf+VYviHxLoHhLw7/aviXVrXSLM5ERlOZJz /djjHzOfoMepFfP3jj9ouxtPO074fW0eqXPKnWr6I/Zk94Yjgyf7z4X2NfKm r6vq3iDxFNq+u6le6xqkv37m7k3vj+6Oyr/sqAPavrcj4BxWKtUxX7uHb7T+ XT56+R8jnnH+Fw16eEXtJ9/sr5/a+Wnme7eOP2hde1kzad4Khn8LaU2Va/kw dQnHqpGVgB/2ct/tCvng5aaSRmeSWRi8kkjFndj1LMeSfc0UV+t5blOEy+n7 PDw5V17v1e7/AE6H5JmWbYvH1faYibk+nZei2X9XP1/+E3/JsXgD/sAWv/op a9Crz34S/wDJsPgD/sAWv/opa9Cr+ccx/wB7q/4n+Z/R+W/7pS/wr8kFFFFc R2hRRRQAUUUUAFfn7/wUE/5Ij4B/7Dkv/og1+gVfn7/wUE/5Ij4B/wCw5L/6 INfVcEf8jzD+r/JnynHH/IixHovzR+YfhzxJ4h8H+MbfxD4U1vUvDutw8JeW MuxyP7rjo6HurAg+lffXws/bF0rVWtdD+LFjFoOpMRHH4i0yBmspm6Dz4Blo ST/Em5OfurX511Ys/wDkNWP/AF8x/wDoYr98zvhvAZrC2Ih7y2ktJL5/o7ry P5+yPiTMMqqXw89OsXrF/L9VZ+Z+mPjr9oq7lln0z4fWr2EQJR9bv4QZ294Y TkJ7M+W7gCvmO6ubq/1a41C/urm/1Cdt091cymSWU+rMeT/Smy/8fMn+8f51 H+PNcGVZLg8up8mHhbu+r9X+m3ZHqZrnWMzGpz4id+y6L0X6792eJ+N/+SmX n/XCH/0Cul+Gnxj8ffCfUifCmrB9Hkk33WhX4M1hcep2ZzG3+3GVP1rmvG// ACUy8/64Q/8AoFcpX0VXCUcTh/ZVoKUWtU1dHzFLF1sNiPa0ZuMk9GnZn6se B/2q/hr4s8I3Fxq0eqeFfEdrDvn0UxG5+084JtpVADrnGQ+1lByc9a8s8d/H bxT4shuNO0USeEvD7gq0NtNm7uF/6azDoD/dTA9zXxj4A/5Hub/ryk/mtewZ z/8AWr43DcFZVgsS6sIXe6UndR9P83d+Z9riONs2x2FVKpUstnZWcvX/ACVl 3QgAVcAADPQU4Eq2VJU+opMcUV9KfOHJa14O0vVWknt9ulX7cmSJP3ch/wBt P6rg/WrHhTS7zR/DM1jfJGswu3ZTG+5XUhcMD+B6810tFW6knHlZkqUVLmW4 vb0r9RP2cPBX/CI/s3afd3MIj1XXD/aFwSOQjDEK/TZhsdi7V+fXwu8HP48+ Ovh7w3tY2c1wJL5hn5bdPnk57EqCoPqwr9f0RIoUjjRY40UKiKMBQOgA7Cvy zxHzTlp08JF7+8/Tp+N/uP1Xw3yvmqVMZJbe6vV7/hp8x1fiL+1z8Of+Ff8A 7YmtXFpB5WieIh/a1jtHyq0jHz09OJQ5wOiulft1Xx7+2r8OP+Ez/ZQk8S2U Hm6z4TmN8hVcs1q+FuF+gASQn0iNfL8B5v8AUc1gpP3anuv57P7/AMGz6jj7 J/r2UzcV71P3l8t1934pH400UUV/SB/NRPDdXVtZXtrb3VzBaXmz7Zbxyssd zsO5PMUHDFTyCRkHpUPB6cH0pKKVkF29wopc8YPIpKYAQCCCAQRgg969m8Ba tq138N7jRLvVtTutFsb4NZafNdO9vblkydiE4XJ54HHbFeM16v8ADv8A5F3V /wDr8T/0CufFRThdrY6sJKSqaPc9AzRTXdY03SMqr6k/kK9Av/hh400j9nPx H8Utb0ibRvDOk2iXEcV4DHd3wZ0QeXGeVX5wdz4z2B615FfE0qPL7SSXM0lf q3sl3PZo4atW5uSLfKm3bolq2+xwPOKWs/TdV0/V7Iz6ddJcIo/eJjbJH/vK eR9envV/vW7TWjME76o/UT9m7xcnij9mTS7OR1/tDQ2OnTqODsUAxNj02FVz 3KtXvtfmv+yz4y/4R/8AaAfw/cS7NP8AEFv5GCcKLiPLxH8R5ifVxX6UV/Pf GOWfU80qJL3Ze8vnv+Nz+heDsz+u5XTbfvR91/Lb8LBRRRXy59SFFFFABRRR QAV+Vv7fPj9NS+KHhf4c2bo8Wj2xv9QK8kTzDEaH0Kxru9xKPSv1D1TU7LRf DOo6xqU62unWNrJc3UzdI441Lux9gATX883xA8YXvj/42eJ/GWob1uNX1CS5 EbHPlITiOPPoiBVHsor9K8M8q9vmEsTJaU1p/ien5X/A/M/E/NvYZfHCxetR 6/4Vr+dvxOPqzZ/8hqx/6+Y//QxVapYJBDfwTEFhHKrlQcZwQcfpX70z8BTP paX/AI+JiSFVSSzMcADPUnsK4HWfHVjZl4NIVNTuhwZmyIEPt3c/TA964TXf EOq69LIbiZUsN2VtYMrGv+8OrH3PFc7XHSwyteR21cW3pEtXt7d6jqct7fTG 4upMbnIA4HAAA6AdhVWiiutHG3c7fwB/yPM3/Xk/81r2DvXj/gD/AJHqb/ry f+a17BXn4n+IelhP4YdqPzoorA6gooxV/StNvNa8Tado+nxGe/vrlLe3jz95 3YKo/M0pSUU29kOMXJqK3Puj9kbwV9k8J6347u4cT37/AGHT2Yc+ShBkYezO FX6xGvsquf8ACnh2z8JfDbRPDVgB9l060SBWC48wgfM5HqzZY+5NdBX81Z7m Tx+OqV+jenotF+B/SuRZasBgadDqlr6vV/iFVb6ytNS0W806/gjurG6geC4g kGVkjdSrKR6EEirVFeUm07o9ZpNWZ/PN8U/A118Nv2g/Ffgq68xhpl8yW0j9 ZYGw8Mh92jZCfQkiuAr9Lv2+vhxlPC3xTsIOR/xKNXKj/ekt3OP+2qlj/wBM x6V+aNf1LwzmyzHLaVe+trP1Wj/z+Z/KvE+UvLcyq0Oid1/heq+7b1QUUUV7 x4AUUUUAFfSn7PXw18V/E2fXdM8MQ2IW3u4mvry8uAkVqrIcMVB3ueOig+5F fNdd18OdfvvD3xQtLnTtQvNKvJh5cF3aTGKWKQcoQw9TkEHg55BrjzCFaeHm qMkpdG1dfddf132O3LqlGGJg6ybjfVJ2f32f9dtz9oPhn+zv4I+H0ttql3Gf FXimPkalfxjZA3/TCLlY+3zctx96qH7XH/KOb4pf9g6L/wBKYa8Z+G37WV7Z m30j4nWbX1uMIuvafBiRfeeAdf8Aej9PuV6f+05r2i+Jv+CYPxK1nw/qljrG lz6ZEYrm0mEiH/SITjI6EZ5B5HevwSrgM1o55hp468r1I2lvH4lt0Xpp6H9A UsflNbI8TDA2janO8dpfC9+r9bv1PxQgnntb1Lm1nltrlD8ksTbWX8f6V6Ro /j4ErBr0WD0F7AnH/A0H81/KvMu9B6Gv6EnTjPc/nanVlB3R9Q6Tqk2n6xpm t6VcgT280d1Z3EZyNysGRh+IBr9lvCfiG08WfDXQ/Ellj7PqNmk4UHPlsR8y H3VsqfcV+Jehf8iLov8A15J/Kv0U/ZH8Y/bvh9rXgm6lzcaXN9rslY8mCU/O oHosnJ/661+U+ImWe2wUcRFa03r6PT87fifrPhzmnscY8PJ6VFp6rX8r/gfY NFFFfih+2hRRRQAUUUUAfHP7bPxC/wCER/ZNPhmzn8rVvFdz9jUKcMLWPDzs PY/u4z7Smvxtr6r/AGxfiF/wnH7Y+q6daT+bo/hmMaVbBW+UyqSbhsevmEp7 iNa+VK/pXgfKfqOU00170/efz2+5W+Z/MvHWbfX83qNP3Ye6vlv+N/kFFFFf Xnx4qkq4ZSQw6EU5ni2M0pWHAyX6KPc+n1Fdx4A+Gfjj4oeJH03wVoU2prEw F5qEreTY2XvLO3yqf9kZY9hX6I/Cz9lPwL4Ee11jxa0HxC8WR4dGurfbplm/ X91bt/rGB/jlz7IK+Yz7i3L8qTjVlzT/AJVq/n2Xr8kz6jh/hHMc2d6UbQ/m e3y7v0+dj8vLuyvrAWRv7K9sVvIBPZNcW7xi6iPSSMsBvXjquRVav3R8WeFv DXjzwlJoHjPQ7DxJpDcrBdpzAf70LjDRMOxQj8RxX5//ABR/Y88Q6J9o1j4V 3dz4v0kZZtCvGVdTgHUiJ+EuQPT5ZPZq8PIvEbAY2Xs8SvZSe13eL/7e0s/V JefQ93PvDjH4GPtMO/ax62VpL/t3W/ybfkfMvgD/AJHqb/ryf+a17Aa8i8Cx TW3xKvrS5hntLy3tZI7i3njaOWFgVyrowBU+xFeu19liPjPjsIrQDsaKD60d qwOgK+qf2UvBX9t/Ge88WXcW6x0GD9wSOGuZQVX67U3n2JU18rd6/WL4GeCv +EH/AGcNDsJ4fJ1S9X7fqIIwwllAIU+6oEQ+6mvjeOc0+qZbKEX71T3V6dfw 0+Z9nwNlf1vMozkvdp+8/Xp+OvyPX6KKK/BT97CiiigDg/if4Gs/iV8A/FPg i9aONdUsWjgmdciCcYeGTHfbIqNjvjFfhh8Svg/8QPhN4jFh4z0Kezhkcra6 jD+8s7rv+7lAwTjnacMO4Ff0F1maxoukeIfDd1o+u6ZY6xpVymy4tLyBZYpB 6FWBFfY8K8YV8mbhy81OTu1s790/6+R8ZxXwbQzlKfNy1Iqye6a7Nf1bzP5u qK/QP9qT9lbwp8PPhzqHxJ8D6jPpmlRXMcd1oVzmVVMsgQGGQncACfuvnvhu gr8/K/fsmznDZnhlXoPTbXRp9v8Ahj+fs6yXE5XiXh8Qtd9HdNd/w66hRRRX qnkhSqzpIskTFJUYMjDswOQfzpKKAPovTr9NU0Cy1GPgXEQdh/dbow/76BrQ M9z/AMItrehpeX1vo+sweRqtpBOUjukDBhuXpuBAIbGRjrivM/h7qO63v9Hk blD9ptwfQ4Dj88H8TXpFeTVppSaa/rdHtUKjlFST/rZnj2s+CNRsA9xppfVb MclQuJ4x7qPvD3X8q4jOQfbg+1fTIODkZFc7rXhjStbDSTRm0vyOLuAAMf8A fHR/x5966aeJe0jkq4PrEu6F/wAiLov/AF5R/wAq9n+DHjL/AIQf9ovw9rE0 vladLN9j1DJwvky/KzH2U7X/AOACvIdPtmsvD9hZSOsr28CxF1GA2O4Bq53r zcZhoYmjOlPaSa+89TA4mphqsKsN4tP7j9wKK8l+B/jH/hNv2bvD+pTS+bqV rH9hvyTlvNiAXcfdl2P/AMCr1qv5jxmFnhq86M94tr7j+nsHioYmhCtDaST+ 8KKKK5jpCvPPix45g+G37Oni3xpMY/M02wZrRH6SXDYSFD7GRkB9ia9Dr82/ 2/PiF5en+EvhhZT/ADSsdX1VVPO0bo4FPsT5zEH+6hr3eGsq/tHMqVBrRu79 Fq/8jweJs2WXZZVxF9UrL1ei/wAz80rm5nvNRuLu6mkuLqeRpJpXOWdmOSxP ckkmoKKK/qZK2h/KbberCj8jz0PIPsfUe1FFMD9DPg5+1n4Qj8NaX4N8e6Fp Pw+itVEVnqWh2nl6Qe2ZYFy1ux7uNynvivt6Ce3u9Mtr6yuba+sLmPzLW6tZ llhnX+8jqSrD3Br8Fa9M+Gvxf8ffCfVTJ4Q1cDSZJN93oV8pm0+59SY8/u2/ 24yrfWvy3iLw2o4hyrYGXLJ6uL+F+j3T+9eh+qcN+JVbDKNHGx54LRSW6Xps 19z9T9ozXJ+LvG/hbwNpK3XifVFtJJF3W1jCvm3dz/uRDnH+02F96+Urr9qb WvFHw4sZ/Cfh2PwlfzqyXt3c3Au2hkU4YWwwAF7h3BYenevBrq5ur/VrjUL+ 6utQ1C4bdPdXUpkllPqzHk/yFfLZN4d4mpLmxr5Euis5P56pL735Lc+szjxG w1OHLgVzt9Wmkvlo2/uXqeg/E/4gxfEnxxbawfC2j6LJao0UF75YfUp4zgbZ 5xjevAwmCF7GvOPeijvX6zg8HRwlGNGirRjstf1PyXGYytiq0q1V3lLd7fkF FBo4rqOY9a+CHgr/AITr9ozQ9Mni87S7R/t2ogjKmGIg7T7MxRP+BV+s9fKX 7KHgr+xvg9feL7qLbfa5Pttyw5W2iJUY9Nz7z7hVNfVtfg3HOafW8ycIv3af u/Pr+OnyP3rgXK/qmWqpJe9U975dPw1+YUUUV8YfZhRRRQAUUUUAfKX7af8A yYH4j/7CFl/6UJX4qV+1f7af/JgfiP8A7CFl/wClCV+Klfv3hh/yKJf43+UT +ffFL/kbx/wL85BRRRX6Mfm4UUUUAaWj6idI8U2OojJSKT96B/FGeGH5HP4V 9DHGcq25DyrDowPQ/lXzP1HPIr27wbqX9o+BYI5G3XFk32eTPUgcofxXj8K5 MVDRSO3Bz1cTqaDRRXEegA6UUfrQaAPrn9knxl/ZvxQ1fwZdTYttXt/tFmrH pPECSAP9qPcT/wBcxX6EV+LXhnXrzwv8QdF8R2B/0rTryO4QZwH2tkqfYjIP sTX7K6TqdnrfhfTtY0+TzrC+to7i3f8AvI6hlP5EV+LeImWexxkcTFaVFr6r /gW/E/afDrM/bYKWGk9YPT0f/Bv+BoUUUV+eH6IFfhh+1hd3N1+3/wDEP7TP JP5NzBFFuP3EW2iwo9AP6n1r9z6/Cn9qj/k//wCJP/X9D/6TQ1+meFiX9p1f 8D/9KifmHiq2sspf41/6TI+faKKK/eD8DCiiigYUV3Ph7whHrXhCe9nuZrOd 5ito4XcuF4YsvcE8ZB7VzuraFqeiThb+3xCxxHcxndE/0bsfY4NQqkW7X1NH SkoqVtD1XwR/yTS0/wCvib/0KurrlfBH/JNLTr/x8Tf+hV1Wa82p8bPVo/Av QKKKKgsO9b3hjw/eeK/iHo3hzTwfteo3aQIduQgY8ufZRlj7A1g9q+xv2R/B X23xrrPjq7izBpsf2OwYjgzyDMjD3WMgfSWvJzzMlgMDUrvdLT1ei/E9bI8t ePx1Ogtm9fRav8D7q0jSrPQ/Cmm6Lp0fk2Fjapb26eiIoUZ98DrWjRRX81yk 5NtvVn9LRiopJLRBRRRUlBRRRQAUUUUAfKf7aKO37AXiUqjMFvrIsQM7R9oQ ZPoMkD8a/FOv6MvGfhbTvG/wp8Q+EdWXOn6tYSWsrbcmPcpAcf7SnDD3Ar+e XxDoWo+F/Hms+G9Xi8jVNLvZbS6TsJI3Ktj1GRwe4r9x8LMdTlg6uG+1GXN8 mkv0/I/CvFXA1I42lifsyjy/NNv8b/mY9FISFUsegGTV+/02/wBMliS/tpLc SoHhc8pICMgqw4PB6da/Urn5VZlGjtRR2pgFdj4H1H7F42W0dsQX6eScngOO UP55H41x1KrOkiyRMUlRgyMOzA5B/OpnHmi0VCXLJM+lqOtU9Ov01TQLPUY+ BcRB2Ufwt0YfgQaufyryWrHtJpq6DvR2oooGH41+kP7KvjH+3fgRceGrmXdf aBc+WgJ5NvKS8Z/BvMX2AWvzer3P9nfxj/wiP7TOkJPL5em6wP7NusngGQjy 2/CQIM9gWr5ji/LPruWVIpe9H3l8v81dH0/CGZ/Uszpyb92Xuv5/5OzP1Ooo or+ej+hgr8Kf2qP+T/8A4k/9f0P/AKTQ1+4Wta5o/hzw1dazr+qWGjaVbJvn u7ydYo4x7sTj8O9fg58f/FOheNf2wvHPifw1eNqGh314htLkxNH5oWGNCwVg CBuU4yASMHAr9R8LKNT6/Vqcr5eW1+l7rS/c/K/FatT+oUqfMubnvbraz1t2 PHqKKK/cz8JYU5I3lnjhjx5kjhEz0yTgfzptWbL/AJDlj/19R/8AoYoGj6Gt LOPTtJtdPhGIraIRD3x1P4nJ/Gp2VZIHjkRJYnGHjdQysPcHrUsv/H1J/vn+ dR9ulePe57dkirZWNppuni0sYRb2wkZ1jBJCljkgZ7Z7dqtUUAEsAAST0Aob BK2gUfyqkupWL682lx3Mct+sZkeKM7vLAx94jgHnp1q7RYE09hyqzyqkas7s cKqjJJPYV+vfwp8Gr4D+A3h/w6yKt9Hbia/I/iuJPmk574J2g+iivz6/Z08F /wDCYftJ6ZPcReZpWij+0LrI+VmQjyl/GQqcdwrV+pNfkniPmnNUp4OL295+ vT8Lv5o/XPDfK7U6mMkt/dXp1/Gy+TCiiivy4/UgooooAKKKKACiiigAr8kv 27Phz/YHx90r4gWMGzTvEtt5V4VXhbuBQpJ7DfH5ZHqUc1+tteFftIfDj/hZ 37I3ifQ7aDz9as4v7S0gBcsbiEFgi+7oXj/4HX1HB+b/ANnZpTqN2jL3Zej/ AMnZ/I+W4yyf+0cqqU0ryj70fVf5q6+Z+DMn/HvJ/umvo6OGG58NWttdQRXN s9rFvilXcrfIO39etfOMn/HvJ/umvpWwjlntdKtbeGa5upoIkhghjLySsUXh VGST9K/pHFuyTP5rwSu2jznWfAJ+e40GTPc2U78/8Ac/yb8682mjkt7+S0uY 3t7uP/WQyLtdfqP61+lHgf8AZ11XURFqXj65m0GxOGXSLVwb2Uekj8rCPYbn +le8eIPg38LfE/w4h8Kar4L0oaTbg/Y5bRfJvLRj1kjuBmTeep3Fge4NfDY3 xFy/CVlSV6ndxtZfPaXy08+h91gvDjMcXRdXSn2Ur3fy6fPXy6n4uUV9V/FP 9k7xt4KS61nwVJcfELwtGC7xwwhdUs0HUyQLxKo/vxZPHKivlJWVgdpzglWH QqR1BHYj0PNfZ5Zm2EzCl7XDVFJfivVbr5nxOZ5Ti8ureyxNNxf4P0ezXoep fD3Ud1tf6PI3KH7Tbj2PDgfjg/ia9Hr560bUW0nxTY6iM7IpP3o/vRnhx+R/ SvoY4yNrBlIyrDuD0P5U8TC0r9ysJO8LdhO/vR60Ud65zqD605HaOdZEdkdS GVlOCpHQg+tNo7UAfsL8MvFyeOfgX4c8S71a5ubULeAfwzp8kox2G5SR7EV5 J+0T+0ZpnwM0PTLODST4g8WarFJJZWjS+XDAikL5sxHzYJOFVR821uVxmvL/ ANkLxjsvvEfgS6l+WRRqNgpP8Q2pMo+o8sgf7LGvgb9pT4hf8LJ/bC8V6zbz +fo9lN/ZmlMGypggJXcp/uu/mSD/AH6/Isp4Np1eIKtCrG9KHveqfwr+uzP1 zN+M6lHh+lXpStVn7vo18T/rujkviT8XvH/xZ8SjUfGmuz30UblrXT4v3dpa 9v3cQ4BxxuOWPcmvNKKK/b8Ph6VCmqdKKjFbJaI/DMRiateo6lWTlJ7tu7Ci iitjEKs2X/Icsf8Ar5j/APQxVarNl/yHLH/r6j/9DFDBbn0lL/x9Sf75/nUf 50+X/j5k/wB8/wA6ZXjo90zdT1jTNItRLqF0kRI+SJfmlf6KOfxOB715ZrXj XUtSV7exDaVYtwQjZmkH+0/b6L+del6v4f0rW483sBS5C4S7h+WVB6Z/iHsf 0rynWvCWqaMHnCjUNPH/AC8wKcoP9teq/Xke9dWHVPrucWJdXpsaHw+AHjmY AAD7E5/8eWvYO9eP/D8g+OJiCCPsL4IPutfSHgDwpceOPjJ4f8LwbwL66VZ3 XrHCvzSv+CBj9cVhj60KSlUm7JK79Eb5bRnV5acFdt2Xqz9Af2YfBX/CM/s9 x63cw+XqfiGQXbEjDCBcrCv0ILOP+ulfSFQWttb2Wm29naRJb2sESxQxIMKi KMKo9gABU9fy9meOnjcXUry3k7/5L5LQ/qTLMBDBYSnQjtFW+fV/N6hRRRXC d4UUUUAFFFFABRRRQAUUUUAfhP8AtQfDf/hW/wC154n0u1g+z6Jqrf2ppW1c KsM5YsgHYJIJEA9FHrX0L+zp8cPg/pumWOgazpVv8PvGjRJbtr99OZ7bUSAF AFw3Nrn/AJ5sAn+0a91/bm+HP/CTfs42Pjmxg36p4Xuc3BUcvZzFUfp12uI2 9l3n1r8hCAVIIBBGCCODX9BZTTo8S5DCnXm04+62nbVd+jurOzXpZ6n885vU rcNZ/OpQgmn7yur6Pt1VndXT/wAj98dpAQ8FXQPGykFXU9GUjgg9iODTec1+ Pfws+PvxC+FDQ2GlXqa/4TD5k8O6s7PbqO5gcfPbt/ufL6qa+w9T/bR+HkPw wh1PR/DniW/8WzAr/YF2FhitXA+9LdLlXjz08tdzei1+b5p4fZtha6hSh7WL 2a/9uT+H1vbzP0rKvEXKcVQc60vZSS1T1/8AAWt/S1/I+vLi6t9P0u51O9vL XTbC0Xzbi9uZ1hht1H8TyMQFHuT9K/MX9pX4lfBfx3r7f8IP4X/tPxYsg+1+ NbcmyguADynlYzd57SuFx1BavGfiR8W/HvxX1dZvGGsmXTYpC9nolmph0+1/ 3Ys/O3+25Zj6ivNq/QeE+Anl1WOJxFRuoukW0l5N6OXpovJn53xbx9/aVN4a hTSp95JNv06R9dX5oOo55Fe3eDdS/tDwLBG7brmzb7PKSeSByh/FePwrxGux 8Daj9i8araO2IL9PJOTwHHKH88j8a/Q8RDmh6H55hp8tT1PZqKKO1eaeqFFH tRQMv2HivWvBMl54m8PXItNXs7KcQSldwXfG0Z4+jHHocGvlkdBX0Hr3/Ii6 1/15P/KvnwdK6sHTinKaWrsr+S2/N/ecONqTajBvRXdvN2v+S+4KKKK7jgCi ijnsGY9gqkk/gKACrVgM6/p49bqL/wBDFVFZXjV0ZWVhkMDkEVd07/kYtO/6 +4v/AEMUnsNbn0fL/wAfMh/2j/Oo6fJ/x8yH/aNMrx0e4B9e9KCQcg4PsaTt RTEZcOi6bbeJH1W1tltbt42jlEXyxuCQclegbjqK+9/2RPBWItf8fXkXLf8A Eu04sO3DzMP/ACGoI9HFfEtnaXN/q1rY2cL3F5czLDBEg5d2IVVHuSQK/Yvw J4VtvBPwh0DwvbbCLC0VJXXpJKfmkf8A4E5Y/jXwfiDmroYBUE/eqaf9urf9 F95974e5Sq+Pddr3aev/AG89v1f3HW0UUV+Hn7iFFFFABRRRQAUUUUAFFFFA BRRRQBl63o+n+IfBuraBq0AudL1KzktLuI/xxyIUYfkTX89HjzwjqHgL4yeJ fBuqBjeaTfyWxcrjzVB+SQD0dCrj2YV/RXX5cft8fDj7D438NfFCwt9tvqUf 9masyrx58aloXPqWjDr9IRX6V4aZv9Xx8sNJ+7UWn+Jbfer/AIH5n4nZP9Yw EcVFe9Tev+F7/c7fifndRRRX70fgNgooooAKVWdJFkjYpKjBkb0YHIP50lFA H0Xp1+mq6BZ6lHwLiIOw/ut0YfmDVyvN/h7qO62v9HkblD9otx7HhwPxwfxN ekV5VSHLJo9ilPngmHeijtRUGpl67/yImtf9eUn8q+fB0FfQeu/8iNrX/XlJ /KvnwdBXbhPhZ52N+JBRRRXWcYV9p/sNfDr/AISr9qa+8bXsHmaR4Rs90JYA q19cKyRjB/uRea3sWSvip3WOF5HO1EUsx9AOTX7kfsofDhvhx+xf4bt7238j X9bB1nVg33lknAKRnv8AJEIkx2KmvieP82+pZTKMX71T3V6P4vw0+aPuPD7K PrubRnJe7T95+q2/HX5M89+Mv7FfgPx415rvgJ4Ph54ukJkdLeHOmXj8n97A MeWxOPnjx3yrV+Y/jT4WePvhP8TNM0nx54eudIaW+jWzv0Pm2V786/6qcDaT gg7Dhxnla/dXxv8AEXwf8O/D39o+K9ZgsN4P2e1X95c3JH8McQ+Zj74wO5Ff n78Wf2ifEHxH0q98O6bplpoHg2f5ZYLmJLi7u1BBBdiCsXIBAT5h/fr5HgbO c9qWhKPPR7ydrej1b9LP1R9jx1kuRQvOMuSt2jrf1WiXrdejPnqX/j6k/wB4 /wA6Z29aPxor9MPzMPWiiigD6V/Zd8Ff8JJ+0B/b11D5mm+HohckkZU3DZWE fUYdx7xiv0srwz9njwV/wh37NmlNcReXqmsf8TG7yPmUOB5aeoxGF47MWr3O v574wzT69mc2n7sfdXy3+93P6F4Pyv6jlkE170vefz2+5WCiiivlz6gKKKKA CiiigAooooAKKKKACiiigAryz41/D6L4ofsyeLPB5RGvrm0Mumu2Bsuo/nhO ewLKFJ/us3rXqdFb4bETw9aNWm7Si016owxWGp4ijOlUV4yTT9GfzVyxSwXM kM0bwzRsVkjdcMrA4IIPQio6+qP2wfhz/wAIH+2DqmoWcBi0TxMn9q2hA+VZ WJFwmfXzMvjsJFr5Xr+r8sx8MbhKeIhtJJ/8D5PQ/krNMBUwWLqYee8G1/k/ mtQoooruOEKKKKANLRtROk+KbHUeSkUn70f3ozw4/I5/CvoY4zlSGU4Kt/eB 5Br5n6jnkV7b4N1L+0PAsCO265sm+zyknkgDKH8V4/CuTFQ0Ujtwc7PlOqoo oriPQMzXP+RH1r/ryk/lXz2Ogr6E1z/kR9a/68pP5V89joK7cJ8LPOxnxIKK KK6zjPT/AINeENO8bftJeGdJ165tbHwrazf2n4hurqQRxR2VuRJIrMem9tkY 9fMr9IPiT+1nI5n0j4W2iiMZQ+INQg4+sEDdfZpMDn7pr85/AGmiLQbvVJky 91II4cj+BDkn8W7/AOzXoHU18pm+SYbH4uNXELmUFZR6X6t93su2mzPrsnzr FYDBypYd8rm7uXW3RJ9Fv567lzUtS1LWfENxq+s6je6xq1wf315eTGSV/bJ6 D0UYA7CqdHr2or0oxUUklZI89tyd3uw9qO1FB6GmIK9I+EvgxvHnx98P+H3j aSwM/n6gewt4/mcE9t2AgPqwrzev0A/ZI8Ff2f4A1jxzdw7bnVJPslixHIgj b52B9Gk4P/XIV4HE+afUMuqVU/eei9X/AJb/ACPf4Yyv6/mNOk17q1fov89v mfYCqqoFUBVAwABgAUtFFfzkf0cFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF FFFAHyP+2d8OP+E2/ZHutes4PN1rwpKdRhKrlmtiNtyvsNmJD/1xFfi/X9KN 1a299plzZXkMdzaXETRTxSLlZEYEMpHcEEiv59fi54CuPhn+0b4s8FzCQw6f fN9ikfrLbPh4XJ9TGy59Dkdq/bPC7N+ehUwUnrH3l6Pf7nr8z8Q8U8n5K9PG wWkvdfqtvvWnyPOKKKK/WD8kCiiigArsfA2o/YvGq2jtiC/TyTk8CQcofzyP xrjqVWdJFkiYpKjBkb0YHIP51M480WioT5ZJn0tQKp6dfpqnh+z1GPhbiIOw /ut0YfgQa6zwz4U8R+MtfbTfDGlT6rcJjz5AQkFsP70sp+VB9efQGvFrVYUY OdSSilu3ol8z3qFKdaajTTbeyWrfojjdbOPBOs/9eUn8q+eh0FfrN4W/Zz8J 6foNx/wnDjxjfXMDRTWsbPBZQhhg7MEO7Y6OSMHkLXzj8T/2ONc0o3Gr/Ce/ m8UaaMu3h/UJVXUIh1xDKcJcD0Vtrn/arwMv47yepiHQ9pbtJq0X8+nq0l5n u5lwJnFPDqv7O/eKd5L5dfk2+58T1LDBLdXsNrbqWnmkEcYHqTgUt1bXVhrF 1p2oWl3p+o2rmO5tLqFopoGH8LowBU/UV2vgHTvtPiifUnXMVlH+79DK4IH4 hcn8RX3EppQ5j4aFNufKz1W1tYbDS7axgA8m2iEaY74HX8Tk/jU9H40nO8fS vKPYQUveiigYe+KKKKBGnoukXuv+MNM0PTo/Nv7+6jtoF7bnYKM+3PJ7Cv2T 8N6DZeF/AOj+HdOGLPTrRLeIkYLbRgsfcnJPuTXwX+yb4L/tb4saj4xu4t1l osHlWpYcNcSgjI9dse/PoXU1+h9fjPiJmntcXHCxekNX6v8AyX5s/aPDrK/Z YSWKktZ6L0X+b/JBRRRX5yfowUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU UAFfm/8At9fDnztI8LfFKwgy9uf7J1cqP4GLPA59AG8xST/fQV+kFcR8SPBV l8RfgV4o8FX5RYdWsHhjkcZEMo+aKTH+xIqN/wABr3OG82eXZjSxHROz9Ho/ 8/U8LiXKVmWW1cP1auvVar/L0P53qu6dYy6nrltp8DxRzzsVjaQ4XOCeT26U up6be6P4k1DSNSt3tNRsbmS2uoH+9FIjFXU+4IIrT8J/8lK0b/rv/wCytX9S ua5OZH8rKHv8sjKvtPvdMv8A7LqFtLaz9g44ceqnow+lU6+kLq1tb/T2tL63 hu7dv+Wcq5APqO4PuK801jwBcRu02gu96h5+xyn96P8Acbo/0OD9axp4lPSW htVwso6x1R51RSsrJIyOpV1JDKeoI6ikrpOVnvfwL1f4ax+Ln0f4q6vqmkaC 0olsprcbbcyH7yXEgy8cZwDuUeuSK/WTR7LRtP8ABun2/hqDSrfw4ybrH+y9 ptZBj7ysvDH1JJb1r8IK9U+Gnxm8ffCnU93hfVt+kO4a60W+BmsrjnJzGT8j dfmTBycnNfn/ABjwfXzX95RrNNfZfw+q7Pzd/kfoPBnGVDKX7OtRTT+0viXk +68tPmfsy33OxpSPXkV88/Df9pr4a/EDTlttVvYPAPiVY90tjqtx/o0uBljB cHhuhOxsNgVgeOv2i0iefTPh7aebICVbXNQh+Ue8EB6+zycdwtfj1HhPNqmJ eH9i1Jbt6Jed9n8r36H7LW4uymnhViPbKSeyWsn5W3Xzsu53/wAbfC3wf8Qe CEuvi1DbW90Iium6laNs1gccCAqC0i/7Lho/pX5/6bo+n6FaXFhpU97d2P2q SSG4vIkjuJUJ+UyKhKhgoAwpIra1HUNQ1jXrjVNYv7zVdTnOZru7lMkj+2T0 HsMAdhVTvX7Rw3kdTK8N7KVZzv0+yv8ACunn33sj8W4kzunmmJ9rGioW6/af +J7P7tNrsPek/wCWg+lL3qhqOo2Wk6Y1/qMxtrRCFaTYW+Y/dXjucHGcZr6F anzzdi/39axdT8Q6XpN3FazzGa+kkVFtYcM67iBluyjnvz7V53rXjm/vd9vp avpVoeDJnM8g+vRB7Dn3rj7H/kOWR6k3UZJPJJ3jk+tdUMM95HHUxa2ifSDA rIynkg4yBTakl/4+ZP8AeP8AOvYfgN4K/wCE2/aR0W0uIvN0rT2/tC/BGQUi IKqfUM5RSPQn0ry8Zi4YbDzrT2imz18FhJ4rEQow3k0vvP0H+C3gr/hBP2dt B0iaLytTnj+2aiCMN58oBKn3Vdqf8Ar1Wiiv5lxeJnia86095Nt/M/pvCYWG GoQow2ikl8gooornOgKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig D8df23fhx/wiX7UUXi6xg8vSPFdubhiowq3ce1Jx/wACBjkz3Lt6V8peE/8A kpWjf9d//ZWr9pf2rfhz/wALF/Y71+O0g87W9DH9r6dtHzMYlbzUHc7ojIAv dtvpX4g2l3cWOoxXdpKYLqI5jkABKnBGRnvzX9GcB5t9fyiMJP3qfuv06P7t Pkz+buPco+oZxKcV7tT3l6/aX36+jR71q2t6ZokAbULjZKwzHbxjdLJ9F7D3 OBXlOteMdU1ZXt4CdM09uDFE/wC8cf7b9fwGB9a5V3eSd5ZXeWVzl5HYszH1 JPWm19jToRjq9WfHVcTKe2iCiiitznCiiigAIDAhgGB6gius0XxhqmkqkE5O qaeOBDM/zoP9h+o+hyK5QdaSplFSVmVGbi7pn0FpOt6ZrcG7T590yjMltINs qf8AAe49xkVroju+1FLt1OOw9T6V80I7xzpLE7xSoco6MVZT6gjpX0B4fv7u /wDh9pM15N5sskO6VtoBkbcRubHU4A59q4K9Hk1R6WHxHO7M1yAOjBj3K9Pz 719Ffs3WlnqPjLx9p2pWVlqenXOhQx3NneQLNDOv2j7rowIYfX8K+c6+k/2Y /wDkpfjT/sCw/wDpQK+U4vbWTV2uy/8ASon1vCEVLOaCa3b/APSWYvxR/Y40 rURc6z8JL2HQb7ln8N6lOTZSnri3nOWhPokm5P8AaWvhHVPDniDwh8TIPD3i rRdS8O65BcxmSzvoTG5G8fMp6Oh7MpIPrX7h319Y6VodxqeqX1npem24zPd3 cojiT6se/sMk9ga+OvjJ8XPCnjrww3hTS/C+n+ItPik3Qa3rNsQ9s4Od9mvD xngfOxAP9w18twVxVnFaaoVIOrBbyejj6yekvR+8+59Pxvwnk1CDr05qlUe0 VqpeiWsfX4fI+epf+PmT/fP86/Rr9lXwV/YPwQufFF3Ft1DX590RYcrbRkqn 03MXb3BWvgXwh4cvPGPxR0Pw1ZFvtOo3iwmTbny1Jy8h9lUMx9ga/ZDTNOtN I8O2GlafEILGyt0t7eIdERFCqPwAFaeIuaeyw0MJF6z1fov83+RPhzlftcTP FyWkNF6vf7l+Zdooor8cP2QKKKKACiiigAooooAKKKKACiiigAooooAKKKKA CiiigAooooAQgMpDAEEYIPevwP8A2gfh0fhd+1d4q8MwwGHSGn+2aRxhTazZ dFHqEO6PPrGa/fGvgX9vL4cf2z8HtC+JFhb7r7QJ/smpMq8taTMAjE+iS4AH /TZjX3vh3m/1PNFSk/dq+78/s/5fM+A8Rsn+uZW6sV71L3vl9r/P5H5Q0UUV /Q5/OgUUUUAFFdT4P8D+MPiB4wj0DwT4c1PxLqzYLxWsfyQKTgPNIcJEmeNz kD0zX6U/Bz9hbw9ootdd+MF3B4t1cYdNAtGZdNtz1xK3DXDDjrtTI+6w5rwM 74mwGVQvXn73SK1k/l09XZH0GR8MZhmsrUIe71k9Ir59fRXZ+dfhX4Y+OfGX gDxF4u0PQZ28I6HYT3uo63dHybQJChd44nb/AF0uBwiZ5IyRXAA5UH1r98Pj rZ2em/sE/FGx0+0trCxt/CN5HBb28QjjiUQMAqquAAPQV+B6/wCrX6Vw8J8R 1M5p1asoKKjKyW+lur7/ACR3cXcN08mqUaUZuTlG7e2t7aLsLXu/hT/km2i+ 8B/9DavCK938Kf8AJNdG/wCuB/8AQ2r6TFfCj5vB/G/Q6DHNekfDX4ht8N9U 8SalBpK6xf3+nx2tpHLKY4Y2WTeXkI+YjHQLyT3Feb96K8fGYOjiqMqNZXi9 16O/T0PcweLq4WtGtRdpR2fyt1Ol8VeMPE3jbXFv/E+qy6i8Zzb2yr5dtbD0 iiHyr9eWPc1zVH+TUkUUk9zFBBG8s0jBI0RSWZicAAdya0o0adGmoU4qMVsl okZ1q1StUc6knKT3b1bPsv8AZF8FfaPEeu+PLuEGKzT7Bp7MP+WrANKw9Cqb V+kjV95Vwnwz8Hx+BPgf4e8NBUFzbWwa8Zed87/PIc9xuJA9gK7uv514kzT6 /mFSsn7uy9Ft9+/zP6L4byv+z8up0Wvetd+r3+7b5BRRRXhHuhRRRQAUUUUA FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUNzbW95p89peW8N1azIUmh mQOkikYKsp4II7GpqKabTuDVz4O+Mf7EPhbxOLrXPhdPb+D9cbLtpU2Tp9wf RMZaAk+m5ewVetfmX44+HvjL4ceMX0LxpoN9od+MmLzlzHOoON8cgysi+6k1 /RLXNeK/B3hfxz4Rn0Hxdoen6/pMv3oLuPdtP95W6ow7MpBHrX6Hw/4h43BW p4n95D/yZfPr8/vR+dcQ+HWCxt6mG/dz/wDJX6rp6r7mfzmUV+hnxi/YZ1fS vteu/CO8k13Txl30G+kAuohySIpDhZR6K2G93NfAGo6bqOj65daZq1jeaZqV tIY7i1uoWiliYdVZWAIPsa/acoz3BZnT58NO/ddV6r+kfiWb5DjssqcmJhbs +j9H/TNTwv4u8VeCfFsGveD/ABDqvhvV4iMXFjMV3gHO2RD8siequCPav0g+ Dv7dul34tdC+M9jDoF7gIviXTomNlKcYzPFy0BPdl3Jk/wAAr8vqKxzrhvAZ pC2Ihr0ktJL5/o7ryNsk4lx+VTvh56dYvWL+X6qz8z96/jjqWnax+wF8UNT0 m/stU0258JXr293aTrLDMphb5ldSQw9wa/BJf9Wv0r0PwL478YeGrLU/B2ia /eWfhXxLBJp2saS4EttLHMux3SNsiOYA8SJg565rI1nwfqmjo80QOpaev/Le FTvQf7adR9RkV5vCvD7yWFWjKfMpO6ezta2vn8z0uLOIVnc6VeMHFxVmt1e9 9PL5HK17v4T/AOSa6N/1wP8A6G1eDgggEEEHoQa948KD/i2ujf8AXA/+htX0 uL+FHzWDfvv0Ogorq/CXgjxR468RDTPDGkXWpzjHmug2xQg/xO5+VR9Tz2zX 3V8Nv2XPDnh0W+qeN5IfFGsDDCzAP2KE+hB5l/4Fhf8AZ718lnPEmBy2P72V 5fyrf/gfM+xybhvHZlL91G0f5nov+D8j5C+HfwX8b/Ei5jm0ux/s/RN2JNVv QUgHrs7yH2Xj1Ir7++HHwI8EfDsQ3sVt/bviJME6pfICyN6xJyI/qMt/tGva Ioo4LaOGGNIYY1CpGihVUAYAAHQCn1+P55xjjsxvBPkh2XX1fX8F5H7DkfBu By602uefd9PRdPxfmFFFFfJH1oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU UUAFFFFABRRRQAUUUUAFFFFABRRRQAV5L8Ufgj8O/i7obW/i7RI21JY9ttq9 piK9t/TbJj5h/suGX2r1qit8Niq2HqKpSk4yXVaHPisLRxNJ060VKL6PVH4s fGP9kX4ifDIXWsaJHJ448IxksbywhP2m2X/ptCMnAHV13L3O3pXydX9LFfK/ xj/ZL+HXxSF1q2mQp4K8Xvlv7R0+EeTct/03h4Dc9WXa3qT0r9a4f8TNqWYL /t9fqv1X3H5HxD4Yb1cvf/bj/R/o/vPxl0H/AJHvRf8Ar9j/AJ19B5YPlSQc 8EVzvjX4FfEb4QfFTR4vFWjPJpLagi22s2OZbOf5uPnx8jH+64VvYjmvrD4b /s1+MPGbQalr6yeE/D74YSXEf+lTr/sRHoCP4mx6gNX3+YZ5gKdCOJlVXI1o 73v6d35HwGW5FmFSvLDKk+dbq1reb7LzPlG4+Hsfi3W4rXRLG5TX7l9sSWMB k89vRo16/VcGvtv4M/si6jB4O0ib4o3S2fkR86Pp8253+Yn95KPujn7q5P8A tA19keB/hp4O+Hmj/ZfDOkx287qFnvpv3lzP/vyHnH+yML6Cu8r8oz3xDxOI TpYT3I938Xy7fi/Q/Wci8OsNh5Kti/fl2Xw/Pv8AgvJmPoegaL4Z8Ow6ToGm Wek6dF9yC2jCrnuT3LHuTknvWxRRX5xOcpycpO7Z+kQhGEVGKskFFFFSUFFF FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU AFFFFABRRRQAUUUUAYHib/kUZP8Ar5t//R8db9FFbS/hL1f6GS/iv0X6hRRR WJqFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q== name vagrant passwd ******** passwordpolicyoptions PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp bGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr ZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt MDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8 L2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgo8L2Rp Y3Q+CjwvcGxpc3Q+Cg== realname vagrant shell /bin/bash uid 501 chef-12.14.60/spec/data/mac_users/10.7.shadow.xml000066400000000000000000000005071276456504500211110ustar00rootroot00000000000000 SALTED-SHA512 b3XXGQRB+sw0KR676h/HVrJC1P6bz/FBvMuE8ZeeJ+U5U5qjH599zJLAzqlZ6hjhi3IO NY5/vjz76qVhRW9roAiTejA= chef-12.14.60/spec/data/mac_users/10.8.plist.xml000066400000000000000000000757001276456504500207670ustar00rootroot00000000000000 KerberosKeys MIIBVKEDAgEBoIIBSzCCAUcwc6ErMCmgAwIBEqEiBCBxHUxawMNiov49kfZn M38ddgXFivE9SNpYgPamy+6prKJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEVB OUJFMDZCQzExQjAxODdEMzQ1MkI3QTA5NjE3QjBCOTI2OTY4RXZhZ3JhbnQw Y6EbMBmgAwIBEaESBBBD9mGbvFTNIUKAvAbnjh8ookQwQqADAgEDoTsEOUxL REM6U0hBMS40RUE5QkUwNkJDMTFCMDE4N0QzNDUyQjdBMDk2MTdCMEI5MjY5 NjhFdmFncmFudDBroSMwIaADAgEQoRoEGG4TEFIf416UH7MvFW7sAXC8ArC6 AhbCraJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEVBOUJFMDZCQzExQjAxODdE MzQ1MkI3QTA5NjE3QjBCOTI2OTY4RXZhZ3JhbnQ= ShadowHashData YnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50 cm9weVRzYWx0Wml0ZXJhdGlvbnNPEIDqTC0mXYAboOwN/M0lPfwd6Ry+CAa0 rMHtf+Iq689r61NE0PRC5ZD/oE1nkHXaOvsRnkG3K16vCO5KpUaTciZG1Rnu BIQ964o+l3Qo0z9iXoOIeRPlwTtwA1lhXgCte8PnoMmK/D4Z0TYCckVPjTOp IU0vvovmjR+YIbJmiTEjZk8QIPmU7y9zt8VZTr0VUzAJdrIHM84OJNZZeD2H 89gcu7apEZugCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA AAAAAOo= _writers_hint vagrant _writers_jpegphoto vagrant _writers_passwd vagrant _writers_picture vagrant authentication_authority ;ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2> ;Kerberosv5;;vagrant@LKDC:SHA1.4EA9BE06BC11B0187D3452B7A09617B0B926968E;LKDC:SHA1.4EA9BE06BC11B0187D3452B7A09617B0B926968E generateduid 11112222-3333-4444-AAAA-BBBBCCCCDDDD gid 80 home /Users/vagrant jpegphoto /9j/4AAQSkZJRgABAQAAAQABAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBw bAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBs AAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAA AG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAH MAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFk AAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRH ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJv ZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAo AAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUA AAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtv S1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAAD Hmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAa AAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwA AAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZp RkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE 8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAg AFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcA QgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDo AHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8E FwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUABy AG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIA RwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBl AHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkA bMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAg AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEA bABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBs AHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIA RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBv AGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACAD wAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBl AG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8A ZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBH AGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA bgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBs AG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYn BkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwA ZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABz AGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdo dHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRN AAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8A ALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA /ZH///ui///9owAAA9wAAMBs/9sAQwACAgICAgECAgICAgICAwMGBAMDAwMH BQUEBggHCAgIBwgICQoNCwkJDAoICAsPCwwNDg4ODgkLEBEPDhENDg4O/9sA QwECAgIDAwMGBAQGDgkICQ4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O Dg4ODg4ODg4ODg4ODg4ODg4ODg4O/8AAEQgBMQEuAwEiAAIRAQMRAf/EAB8A AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQE AAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3 eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfI ycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJico KSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWG h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW 19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiis6/1Wz0 6P8AfyAyY+WJeWP4dvxoA0aw9R1+zsN0at9puR/Ah4H1NcjqHiG9vQ0cZ+yQ H+FD8x+p/wAK5+gDWu9a1C8u1kad4gpyiRkqF/z71u6d4pI2xaiu4dPOQc/i P8K43v0P5UlMZ7LDPDcW6ywSJLGejKcipa8gtL65sLjzLaV4z3HVT9RXb6d4 mt7grFegW0398fcP+H40hHUUUgIZQykEEZBHeloAKKKKACiiigAooooAKKKK ACiiigAooooAKKKKACiiigAooooAKKKguLmC1tzLcSpDGO7H/OaAJ6K4y48W gXyi2tvMtwfmLnDN9PSuisNVs9RjzBJiQfeibhh+Hf8ACgC3cgHT5wQCDG2Q fpX4u/CD9rf4h/C+aHRtXkfxt4PjbaLG/mP2i2XP/LGc5YADorbl7AL1r9o7 j/jxn/65n+VfzYS/8fMn+8f51+qeG+W4XHUcXSxEFKPub/8Ab2z3T9D8p8S8 zxWBq4Srh5uMvf2/7d3WzXkz99/hb8cPh38XtCW48J6yn9ppGGutHvMRXtv6 5TJ3L/tIWX3zxXrtfgD8JPhh8TviF4ut9Q+Hlvd6ZDZTgv4nluHtLSxYd1mX 5nkGfuRbm+lftD4C1HXfDvw+0vRfGHiW48batBFtudbeyS2aZvaNf4QOAxO4 9W5r57jHh7BZXX5cPXUr/Z3lH1a0++z8nufQ8G8R43NaHNiKDjb7X2Zeiev3 XXpseu0VBb3MF1biW3lSaM91NT18Yfankuk/FDQfHPgxNb8B6zYa1ojna17a ybnjb+5Ihw0T/wCy4Bqkzs8hd2Z3JyWJyTX4a+HPF3ijwL4/bxB4O13UPDus K5DTWr/LMufuSocrKn+y4Ir76+Fv7YXhzXfs+j/FO1tvB+sHCLrtorNplwfW VOXtifX5o/da/Rc/8O8bg71cL+9h2+0vl19Vr5I/OOHvEbBYxqli/wB1Pv8A Zfz6fPTzPs6jt61HFLDPY293bTwXVpcIJLe4t5VlimQ9GR1JVgfUE0/mvzux +jppq6F70lL6elHagBPyo7elHejvQM1tP1m905wI3MkPeJ+V/D0ru9O1yy1D CK3k3B/5ZOeT9D3ry/tR0YH360CPaaK8607xJd2m2K6zdQDjJPzj6Hv+NdzZ 39rfweZbSq/95Twy/UUgLlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS MyohZ2CqBkknAFYmo69Z2AZFb7Rcj/lmh6fU9q4S/wBWvNRc+dJiLPyxJwo/ x/GgDrNR8TwQho7BRcydPMP3B/jXE3V3c3lwZrmV5X9+g+g7VXzj2PrS8EZP FMBvtT1ZkkDozI68gqcEU0gjP86Tv9aBnWWPiWXyWttQ/eIyFRMo+YccZHQ/ pXwD8MP2PdC0iSDWvivd2/inVc+Yvh+xkZdOgPXE0gw9wR3Vdqf71fadHQ5r 1MvzvG4GlUp4efKp2u1vpfZ7rfpqeRmGR4LHVadTEQ5nC9k9tbbrrt10IoIY LTTbaxs7e2srC2Ty7a1toVihgX+6iKAqj2AqXtSVla7r2ieFvDjav4j1S00f TRwstw3Mp/uxqPmkb2UH8K86EJ1JqMVeT6LVtnpznClByk0orvokv0N62u7i yuRLbTPE4646H6jvV3U/i74Q8M3MVh4p1SGw1Z03rZwRvNMU/vtGgLIuSOWx nPFfEXjj9ojWNT87TvAcE/hzTzlW1S4UG+lHqi8rAD6/M/uK8d8JPJL4xvbi eSWe5lgZ5ZpXLySMWXJZjyT7k1+g5XwBXnTdbGPkX8q+L5vZfi/Q/PM08QKE aipYKPO/5n8PyWjfrovU+P5/+P2b/fP86irqfG/hfUvBXxe8SeE9XXGoaVqE ttK23Ak2sdrj/ZZcMPYiuWr97pVI1IRlF3TV0fz/AFacqc5QkrNOzPpr4GfE bxb4B8GyDQdQEmlm/czaReZks5OFzhc5jb/aQg/WvvvwJ8ZvCPjeSGwkk/4R rxG/A02/lGyZv+mE3Cv/ALp2t7GvzM8Af8iHL/1+yf8AoK128dvJeXcFlBbT XtzO4WG2hiMkkrdgqgEk/SviOIuE8BmUpTkuSf8AMv1Wz+evmj7vhzizH5bC MIPmh/K/0e6+Wnkz9V2VkdlYMrDhlIwRTfzr5Ut/E3xQ+CX7Nlz4x8foniHQ bW7tbW38O3FyDqduk0mzd9p5CbRyIn389Ste2fDz4peBPipob3ngvW0vbmJN 15pNyvk6hZ/9dISclf8AbQsnvX4hj8jr4eMqsGqlJO3PHWN9N+268uibP3DA Z/h8TONKadOq1fklpK3l32fn3SO/780d6Xr3/Wkrxj3Be1JS/likoAU9KfFL LBOssMjxSA5DKcGmUn50Adrp3in7sOornt5yD+Y/w/KuximingWWGRJY26Mp yDXjXOenWuL8V/Fzw58MWYahqMlzrRXcmiWJElxJ6GQH5Yl/2nwfQGt8LhK2 JqKnRi5SfRf1+OxzYrF0cNSdWtNRiur/AK/A+naK5vwdr0nij4VeHfEktqtl JqenxXTW6ybxEXUNt3YGcZxnFdJWVWnKnNwlutDWlUjUgpx2auvmFFFFQWFF FFABRRRQAUUV4D8evjfbfBHQ/Cmpajo17qmk6tqD2l3NYyL9otFEe4SJG3yy e67lPoc104PB1sVWVGjHmk9l30ucuMxlHCUXWrStFbv52PcL3UbTT4d1zKFY jKoOWb6CuG1LxHd3ZaODNrbnsp+Zvqf6CvOvCnjfwv8AETwqfEfhHxDZ+JNP JHnyRMfOt2P8E8bfPE3swx6E10fFZVaU6U3CpFqS3T0a9UbUa1OtBVKclKL2 a1T+Yd6KPyrk/F3jjwv4F0lbnxLqaWs0i7rawhXzbu5/3Igc4/2mwvvVUKFW tUVOlFyk9ktWLEYilQpupVkoxW7bsjq6O9fK+hftaeBrj4n3nhvxlpl34It9 yHT9Wkm+02xVlyFudq5ibP8AGoZPp1r6lhlgudOt7y0uLe8sbhBJbXVtKssM yf3kdSVYe4NduZZPjcvmo4mm4327P0aun566HDlmdYHMIuWGqKVt+69U7P8A Akyce1LwTx1pM0qqzyBEVmduiqMk15h6YnQ1HPNBa6bc315cW1lY2yeZc3Vz MsUMCf3ndiFUe5NeXeOvjH4R8DtLY+b/AMJJ4hQY/s2wlG2E/wDTaYZVP90Z b2Ffm58ZfGXxR+IOpvdeKdU/tDwvFIXtNJ0pDFY2Y7F4cku4/wCejlifUV9n w9wTjczanP8Ad0+73f8AhXX1dl2bPiuIuOMFlicIfvKnZbL1fT0V33sfUfxR /bE0LR/tOjfCmzt/FWqDKN4gv42GnQHpmGM4a4I7M21P96vmXR/FPiTxra3f iLxdrmoeItblvJAbq8kzsXC4SNRhY0HZVAFfP4IIyCCD0INexeAf+RCk/wCv 2T+S1+1Zbw1gMqo2w8Pe6yesn8+i8lZeR+JZjxNmGbV74ifu9IrSK+X6u78z ta6zwd/yM0//AF6t/wChLXJnpXt3wO+HWp/EHx1rENldR2FtZWAae5kjLKGe RQicdyFcj/dNGZ4inQws6lR2iupeWYepXxUKdNXk+g79vb4c/wBm/Erw78Tr CDbaaxENP1RlXgXMS5iYn1eIFfpBX58V+/nx0+HafFH9lzxX4TWJZNTktTca UxxlbqL54sHtuI2E/wB12r8BXR45njkVkkUkMrDBBHUEV4/h1m/1vLPYyfvU tPl9n9V8j1vEfJ/qmae2ivdq6/P7X6P5n1L+zt8Orj4lWd/pltr+j6StndNL eRvKJL7yyF+aK3zlx1+c4UY5r9FfBnw/8KeArBk8OacFvZE2z6pdESXk/wBX x8i/7KAD61+JVpd3mna1aalpt5eabqdrIJLW8tJmimgYfxI6kFT9DX218Lv2 x9V082+jfFuym16yGEXxJpsCi9jHTNxAMLOPV02v3IauDjnh/OcYnPDVOan1 gtH/APbej26Js7+A+IMmwbUMTT5anSb1X/2vqvm0j6A/a0/5MP8AEIH/AEGN N/8AR5r8p7G+vtK16z1XSr+90rVbSQSWt7ZTtDPA3qjqQR/XvX6f/tJ+I/D3 i3/gnHrOv+Fdc0zxFos+sad5d5Yzb1z5/KsPvIwyMqwBHpX5b11eGlJxympC as+eSaf+GOjX6HL4l1oyzenUpyuuSLTT85apo+5/hd+2TqFobfR/i9Yy6va8 KnibS7cC6QetxbjCy+7x7W7lWr700TW9F8TeErTX/Der6dr+h3IHkX1jMJIm OM7T3Vh3VgGHpX4SV2Hgjx/4y+HHis6z4L1670S7fi5iXD212v8Admhb5JB9 Rn0IrHiDw4wmKvVwbVOfb7L+X2flp5G3DviTi8JaljF7SHf7S+f2vnr5n7fY 70vavkv4XftbeDfF722j+PobbwB4kchFvDIW0q7Y8cOfmtyf7r5XPRhX1hcT QWelSajeXVraaakQle8mmVYAhHD+ZnaVPYg89s1+MZnk+My+t7LE03F9Oz9H s/kfteV51gsxo+1w9RSS37r1XQf+VYviHxLoHhLw7/aviXVrXSLM5ERlOZJz /djjHzOfoMepFfP3jj9ouxtPO074fW0eqXPKnWr6I/Zk94Yjgyf7z4X2NfKm r6vq3iDxFNq+u6le6xqkv37m7k3vj+6Oyr/sqAPavrcj4BxWKtUxX7uHb7T+ XT56+R8jnnH+Fw16eEXtJ9/sr5/a+Wnme7eOP2hde1kzad4Khn8LaU2Va/kw dQnHqpGVgB/2ct/tCvng5aaSRmeSWRi8kkjFndj1LMeSfc0UV+t5blOEy+n7 PDw5V17v1e7/AE6H5JmWbYvH1faYibk+nZei2X9XP1/+E3/JsXgD/sAWv/op a9Crz34S/wDJsPgD/sAWv/opa9Cr+ccx/wB7q/4n+Z/R+W/7pS/wr8kFFFFc R2hRRRQAUUUUAFfn7/wUE/5Ij4B/7Dkv/og1+gVfn7/wUE/5Ij4B/wCw5L/6 INfVcEf8jzD+r/JnynHH/IixHovzR+YfhzxJ4h8H+MbfxD4U1vUvDutw8JeW MuxyP7rjo6HurAg+lffXws/bF0rVWtdD+LFjFoOpMRHH4i0yBmspm6Dz4Blo ST/Em5OfurX511Ys/wDkNWP/AF8x/wDoYr98zvhvAZrC2Ih7y2ktJL5/o7ry P5+yPiTMMqqXw89OsXrF/L9VZ+Z+mPjr9oq7lln0z4fWr2EQJR9bv4QZ294Y TkJ7M+W7gCvmO6ubq/1a41C/urm/1Cdt091cymSWU+rMeT/Smy/8fMn+8f51 H+PNcGVZLg8up8mHhbu+r9X+m3ZHqZrnWMzGpz4id+y6L0X6792eJ+N/+SmX n/XCH/0Cul+Gnxj8ffCfUifCmrB9Hkk33WhX4M1hcep2ZzG3+3GVP1rmvG// ACUy8/64Q/8AoFcpX0VXCUcTh/ZVoKUWtU1dHzFLF1sNiPa0ZuMk9GnZn6se B/2q/hr4s8I3Fxq0eqeFfEdrDvn0UxG5+084JtpVADrnGQ+1lByc9a8s8d/H bxT4shuNO0USeEvD7gq0NtNm7uF/6azDoD/dTA9zXxj4A/5Hub/ryk/mtewZ z/8AWr43DcFZVgsS6sIXe6UndR9P83d+Z9riONs2x2FVKpUstnZWcvX/ACVl 3QgAVcAADPQU4Eq2VJU+opMcUV9KfOHJa14O0vVWknt9ulX7cmSJP3ch/wBt P6rg/WrHhTS7zR/DM1jfJGswu3ZTG+5XUhcMD+B6810tFW6knHlZkqUVLmW4 vb0r9RP2cPBX/CI/s3afd3MIj1XXD/aFwSOQjDEK/TZhsdi7V+fXwu8HP48+ Ovh7w3tY2c1wJL5hn5bdPnk57EqCoPqwr9f0RIoUjjRY40UKiKMBQOgA7Cvy zxHzTlp08JF7+8/Tp+N/uP1Xw3yvmqVMZJbe6vV7/hp8x1fiL+1z8Of+Ff8A 7YmtXFpB5WieIh/a1jtHyq0jHz09OJQ5wOiulft1Xx7+2r8OP+Ez/ZQk8S2U Hm6z4TmN8hVcs1q+FuF+gASQn0iNfL8B5v8AUc1gpP3anuv57P7/AMGz6jj7 J/r2UzcV71P3l8t1934pH400UUV/SB/NRPDdXVtZXtrb3VzBaXmz7Zbxyssd zsO5PMUHDFTyCRkHpUPB6cH0pKKVkF29wopc8YPIpKYAQCCCAQRgg969m8Ba tq138N7jRLvVtTutFsb4NZafNdO9vblkydiE4XJ54HHbFeM16v8ADv8A5F3V /wDr8T/0CufFRThdrY6sJKSqaPc9AzRTXdY03SMqr6k/kK9Av/hh400j9nPx H8Utb0ibRvDOk2iXEcV4DHd3wZ0QeXGeVX5wdz4z2B615FfE0qPL7SSXM0lf q3sl3PZo4atW5uSLfKm3bolq2+xwPOKWs/TdV0/V7Iz6ddJcIo/eJjbJH/vK eR9envV/vW7TWjME76o/UT9m7xcnij9mTS7OR1/tDQ2OnTqODsUAxNj02FVz 3KtXvtfmv+yz4y/4R/8AaAfw/cS7NP8AEFv5GCcKLiPLxH8R5ifVxX6UV/Pf GOWfU80qJL3Ze8vnv+Nz+heDsz+u5XTbfvR91/Lb8LBRRRXy59SFFFFABRRR QAV+Vv7fPj9NS+KHhf4c2bo8Wj2xv9QK8kTzDEaH0Kxru9xKPSv1D1TU7LRf DOo6xqU62unWNrJc3UzdI441Lux9gATX883xA8YXvj/42eJ/GWob1uNX1CS5 EbHPlITiOPPoiBVHsor9K8M8q9vmEsTJaU1p/ien5X/A/M/E/NvYZfHCxetR 6/4Vr+dvxOPqzZ/8hqx/6+Y//QxVapYJBDfwTEFhHKrlQcZwQcfpX70z8BTP paX/AI+JiSFVSSzMcADPUnsK4HWfHVjZl4NIVNTuhwZmyIEPt3c/TA964TXf EOq69LIbiZUsN2VtYMrGv+8OrH3PFc7XHSwyteR21cW3pEtXt7d6jqct7fTG 4upMbnIA4HAAA6AdhVWiiutHG3c7fwB/yPM3/Xk/81r2DvXj/gD/AJHqb/ry f+a17BXn4n+IelhP4YdqPzoorA6gooxV/StNvNa8Tado+nxGe/vrlLe3jz95 3YKo/M0pSUU29kOMXJqK3Puj9kbwV9k8J6347u4cT37/AGHT2Yc+ShBkYezO FX6xGvsquf8ACnh2z8JfDbRPDVgB9l060SBWC48wgfM5HqzZY+5NdBX81Z7m Tx+OqV+jenotF+B/SuRZasBgadDqlr6vV/iFVb6ytNS0W806/gjurG6geC4g kGVkjdSrKR6EEirVFeUm07o9ZpNWZ/PN8U/A118Nv2g/Ffgq68xhpl8yW0j9 ZYGw8Mh92jZCfQkiuAr9Lv2+vhxlPC3xTsIOR/xKNXKj/ekt3OP+2qlj/wBM x6V+aNf1LwzmyzHLaVe+trP1Wj/z+Z/KvE+UvLcyq0Oid1/heq+7b1QUUUV7 x4AUUUUAFfSn7PXw18V/E2fXdM8MQ2IW3u4mvry8uAkVqrIcMVB3ueOig+5F fNdd18OdfvvD3xQtLnTtQvNKvJh5cF3aTGKWKQcoQw9TkEHg55BrjzCFaeHm qMkpdG1dfddf132O3LqlGGJg6ybjfVJ2f32f9dtz9oPhn+zv4I+H0ttql3Gf FXimPkalfxjZA3/TCLlY+3zctx96qH7XH/KOb4pf9g6L/wBKYa8Z+G37WV7Z m30j4nWbX1uMIuvafBiRfeeAdf8Aej9PuV6f+05r2i+Jv+CYPxK1nw/qljrG lz6ZEYrm0mEiH/SITjI6EZ5B5HevwSrgM1o55hp468r1I2lvH4lt0Xpp6H9A UsflNbI8TDA2janO8dpfC9+r9bv1PxQgnntb1Lm1nltrlD8ksTbWX8f6V6Ro /j4ErBr0WD0F7AnH/A0H81/KvMu9B6Gv6EnTjPc/nanVlB3R9Q6Tqk2n6xpm t6VcgT280d1Z3EZyNysGRh+IBr9lvCfiG08WfDXQ/Ellj7PqNmk4UHPlsR8y H3VsqfcV+Jehf8iLov8A15J/Kv0U/ZH8Y/bvh9rXgm6lzcaXN9rslY8mCU/O oHosnJ/661+U+ImWe2wUcRFa03r6PT87fifrPhzmnscY8PJ6VFp6rX8r/gfY NFFFfih+2hRRRQAUUUUAfHP7bPxC/wCER/ZNPhmzn8rVvFdz9jUKcMLWPDzs PY/u4z7Smvxtr6r/AGxfiF/wnH7Y+q6daT+bo/hmMaVbBW+UyqSbhsevmEp7 iNa+VK/pXgfKfqOU00170/efz2+5W+Z/MvHWbfX83qNP3Ye6vlv+N/kFFFFf Xnx4qkq4ZSQw6EU5ni2M0pWHAyX6KPc+n1Fdx4A+Gfjj4oeJH03wVoU2prEw F5qEreTY2XvLO3yqf9kZY9hX6I/Cz9lPwL4Ee11jxa0HxC8WR4dGurfbplm/ X91bt/rGB/jlz7IK+Yz7i3L8qTjVlzT/AJVq/n2Xr8kz6jh/hHMc2d6UbQ/m e3y7v0+dj8vLuyvrAWRv7K9sVvIBPZNcW7xi6iPSSMsBvXjquRVav3R8WeFv DXjzwlJoHjPQ7DxJpDcrBdpzAf70LjDRMOxQj8RxX5//ABR/Y88Q6J9o1j4V 3dz4v0kZZtCvGVdTgHUiJ+EuQPT5ZPZq8PIvEbAY2Xs8SvZSe13eL/7e0s/V JefQ93PvDjH4GPtMO/ax62VpL/t3W/ybfkfMvgD/AJHqb/ryf+a17Aa8i8Cx TW3xKvrS5hntLy3tZI7i3njaOWFgVyrowBU+xFeu19liPjPjsIrQDsaKD60d qwOgK+qf2UvBX9t/Ge88WXcW6x0GD9wSOGuZQVX67U3n2JU18rd6/WL4GeCv +EH/AGcNDsJ4fJ1S9X7fqIIwwllAIU+6oEQ+6mvjeOc0+qZbKEX71T3V6dfw 0+Z9nwNlf1vMozkvdp+8/Xp+OvyPX6KKK/BT97CiiigDg/if4Gs/iV8A/FPg i9aONdUsWjgmdciCcYeGTHfbIqNjvjFfhh8Svg/8QPhN4jFh4z0Kezhkcra6 jD+8s7rv+7lAwTjnacMO4Ff0F1maxoukeIfDd1o+u6ZY6xpVymy4tLyBZYpB 6FWBFfY8K8YV8mbhy81OTu1s790/6+R8ZxXwbQzlKfNy1Iqye6a7Nf1bzP5u qK/QP9qT9lbwp8PPhzqHxJ8D6jPpmlRXMcd1oVzmVVMsgQGGQncACfuvnvhu gr8/K/fsmznDZnhlXoPTbXRp9v8Ahj+fs6yXE5XiXh8Qtd9HdNd/w66hRRRX qnkhSqzpIskTFJUYMjDswOQfzpKKAPovTr9NU0Cy1GPgXEQdh/dbow/76BrQ M9z/AMItrehpeX1vo+sweRqtpBOUjukDBhuXpuBAIbGRjrivM/h7qO63v9Hk blD9ptwfQ4Dj88H8TXpFeTVppSaa/rdHtUKjlFST/rZnj2s+CNRsA9xppfVb MclQuJ4x7qPvD3X8q4jOQfbg+1fTIODkZFc7rXhjStbDSTRm0vyOLuAAMf8A fHR/x5966aeJe0jkq4PrEu6F/wAiLov/AF5R/wAq9n+DHjL/AIQf9ovw9rE0 vladLN9j1DJwvky/KzH2U7X/AOACvIdPtmsvD9hZSOsr28CxF1GA2O4Bq53r zcZhoYmjOlPaSa+89TA4mphqsKsN4tP7j9wKK8l+B/jH/hNv2bvD+pTS+bqV rH9hvyTlvNiAXcfdl2P/AMCr1qv5jxmFnhq86M94tr7j+nsHioYmhCtDaST+ 8KKKK5jpCvPPix45g+G37Oni3xpMY/M02wZrRH6SXDYSFD7GRkB9ia9Dr82/ 2/PiF5en+EvhhZT/ADSsdX1VVPO0bo4FPsT5zEH+6hr3eGsq/tHMqVBrRu79 Fq/8jweJs2WXZZVxF9UrL1ei/wAz80rm5nvNRuLu6mkuLqeRpJpXOWdmOSxP ckkmoKKK/qZK2h/KbberCj8jz0PIPsfUe1FFMD9DPg5+1n4Qj8NaX4N8e6Fp Pw+itVEVnqWh2nl6Qe2ZYFy1ux7uNynvivt6Ce3u9Mtr6yuba+sLmPzLW6tZ llhnX+8jqSrD3Br8Fa9M+Gvxf8ffCfVTJ4Q1cDSZJN93oV8pm0+59SY8/u2/ 24yrfWvy3iLw2o4hyrYGXLJ6uL+F+j3T+9eh+qcN+JVbDKNHGx54LRSW6Xps 19z9T9ozXJ+LvG/hbwNpK3XifVFtJJF3W1jCvm3dz/uRDnH+02F96+Urr9qb WvFHw4sZ/Cfh2PwlfzqyXt3c3Au2hkU4YWwwAF7h3BYenevBrq5ur/VrjUL+ 6utQ1C4bdPdXUpkllPqzHk/yFfLZN4d4mpLmxr5Euis5P56pL735Lc+szjxG w1OHLgVzt9Wmkvlo2/uXqeg/E/4gxfEnxxbawfC2j6LJao0UF75YfUp4zgbZ 5xjevAwmCF7GvOPeijvX6zg8HRwlGNGirRjstf1PyXGYytiq0q1V3lLd7fkF FBo4rqOY9a+CHgr/AITr9ozQ9Mni87S7R/t2ogjKmGIg7T7MxRP+BV+s9fKX 7KHgr+xvg9feL7qLbfa5Pttyw5W2iJUY9Nz7z7hVNfVtfg3HOafW8ycIv3af u/Pr+OnyP3rgXK/qmWqpJe9U975dPw1+YUUUV8YfZhRRRQAUUUUAfKX7af8A yYH4j/7CFl/6UJX4qV+1f7af/JgfiP8A7CFl/wClCV+Klfv3hh/yKJf43+UT +ffFL/kbx/wL85BRRRX6Mfm4UUUUAaWj6idI8U2OojJSKT96B/FGeGH5HP4V 9DHGcq25DyrDowPQ/lXzP1HPIr27wbqX9o+BYI5G3XFk32eTPUgcofxXj8K5 MVDRSO3Bz1cTqaDRRXEegA6UUfrQaAPrn9knxl/ZvxQ1fwZdTYttXt/tFmrH pPECSAP9qPcT/wBcxX6EV+LXhnXrzwv8QdF8R2B/0rTryO4QZwH2tkqfYjIP sTX7K6TqdnrfhfTtY0+TzrC+to7i3f8AvI6hlP5EV+LeImWexxkcTFaVFr6r /gW/E/afDrM/bYKWGk9YPT0f/Bv+BoUUUV+eH6IFfhh+1hd3N1+3/wDEP7TP JP5NzBFFuP3EW2iwo9AP6n1r9z6/Cn9qj/k//wCJP/X9D/6TQ1+meFiX9p1f 8D/9KifmHiq2sspf41/6TI+faKKK/eD8DCiiigYUV3Ph7whHrXhCe9nuZrOd 5ito4XcuF4YsvcE8ZB7VzuraFqeiThb+3xCxxHcxndE/0bsfY4NQqkW7X1NH SkoqVtD1XwR/yTS0/wCvib/0KurrlfBH/JNLTr/x8Tf+hV1Wa82p8bPVo/Av QKKKKgsO9b3hjw/eeK/iHo3hzTwfteo3aQIduQgY8ufZRlj7A1g9q+xv2R/B X23xrrPjq7izBpsf2OwYjgzyDMjD3WMgfSWvJzzMlgMDUrvdLT1ei/E9bI8t ePx1Ogtm9fRav8D7q0jSrPQ/Cmm6Lp0fk2Fjapb26eiIoUZ98DrWjRRX81yk 5NtvVn9LRiopJLRBRRRUlBRRRQAUUUUAfKf7aKO37AXiUqjMFvrIsQM7R9oQ ZPoMkD8a/FOv6MvGfhbTvG/wp8Q+EdWXOn6tYSWsrbcmPcpAcf7SnDD3Ar+e XxDoWo+F/Hms+G9Xi8jVNLvZbS6TsJI3Ktj1GRwe4r9x8LMdTlg6uG+1GXN8 mkv0/I/CvFXA1I42lifsyjy/NNv8b/mY9FISFUsegGTV+/02/wBMliS/tpLc SoHhc8pICMgqw4PB6da/Urn5VZlGjtRR2pgFdj4H1H7F42W0dsQX6eScngOO UP55H41x1KrOkiyRMUlRgyMOzA5B/OpnHmi0VCXLJM+lqOtU9Ov01TQLPUY+ BcRB2Ufwt0YfgQaufyryWrHtJpq6DvR2oooGH41+kP7KvjH+3fgRceGrmXdf aBc+WgJ5NvKS8Z/BvMX2AWvzer3P9nfxj/wiP7TOkJPL5em6wP7NusngGQjy 2/CQIM9gWr5ji/LPruWVIpe9H3l8v81dH0/CGZ/Uszpyb92Xuv5/5OzP1Ooo or+ej+hgr8Kf2qP+T/8A4k/9f0P/AKTQ1+4Wta5o/hzw1dazr+qWGjaVbJvn u7ydYo4x7sTj8O9fg58f/FOheNf2wvHPifw1eNqGh314htLkxNH5oWGNCwVg CBuU4yASMHAr9R8LKNT6/Vqcr5eW1+l7rS/c/K/FatT+oUqfMubnvbraz1t2 PHqKKK/cz8JYU5I3lnjhjx5kjhEz0yTgfzptWbL/AJDlj/19R/8AoYoGj6Gt LOPTtJtdPhGIraIRD3x1P4nJ/Gp2VZIHjkRJYnGHjdQysPcHrUsv/H1J/vn+ dR9ulePe57dkirZWNppuni0sYRb2wkZ1jBJCljkgZ7Z7dqtUUAEsAAST0Aob BK2gUfyqkupWL682lx3Mct+sZkeKM7vLAx94jgHnp1q7RYE09hyqzyqkas7s cKqjJJPYV+vfwp8Gr4D+A3h/w6yKt9Hbia/I/iuJPmk574J2g+iivz6/Z08F /wDCYftJ6ZPcReZpWij+0LrI+VmQjyl/GQqcdwrV+pNfkniPmnNUp4OL295+ vT8Lv5o/XPDfK7U6mMkt/dXp1/Gy+TCiiivy4/UgooooAKKKKACiiigAr8kv 27Phz/YHx90r4gWMGzTvEtt5V4VXhbuBQpJ7DfH5ZHqUc1+tteFftIfDj/hZ 37I3ifQ7aDz9as4v7S0gBcsbiEFgi+7oXj/4HX1HB+b/ANnZpTqN2jL3Zej/ AMnZ/I+W4yyf+0cqqU0ryj70fVf5q6+Z+DMn/HvJ/umvo6OGG58NWttdQRXN s9rFvilXcrfIO39etfOMn/HvJ/umvpWwjlntdKtbeGa5upoIkhghjLySsUXh VGST9K/pHFuyTP5rwSu2jznWfAJ+e40GTPc2U78/8Ac/yb8682mjkt7+S0uY 3t7uP/WQyLtdfqP61+lHgf8AZ11XURFqXj65m0GxOGXSLVwb2Uekj8rCPYbn +le8eIPg38LfE/w4h8Kar4L0oaTbg/Y5bRfJvLRj1kjuBmTeep3Fge4NfDY3 xFy/CVlSV6ndxtZfPaXy08+h91gvDjMcXRdXSn2Ur3fy6fPXy6n4uUV9V/FP 9k7xt4KS61nwVJcfELwtGC7xwwhdUs0HUyQLxKo/vxZPHKivlJWVgdpzglWH QqR1BHYj0PNfZ5Zm2EzCl7XDVFJfivVbr5nxOZ5Ti8ureyxNNxf4P0ezXoep fD3Ud1tf6PI3KH7Tbj2PDgfjg/ia9Hr560bUW0nxTY6iM7IpP3o/vRnhx+R/ SvoY4yNrBlIyrDuD0P5U8TC0r9ysJO8LdhO/vR60Ud65zqD605HaOdZEdkdS GVlOCpHQg+tNo7UAfsL8MvFyeOfgX4c8S71a5ubULeAfwzp8kox2G5SR7EV5 J+0T+0ZpnwM0PTLODST4g8WarFJJZWjS+XDAikL5sxHzYJOFVR821uVxmvL/ ANkLxjsvvEfgS6l+WRRqNgpP8Q2pMo+o8sgf7LGvgb9pT4hf8LJ/bC8V6zbz +fo9lN/ZmlMGypggJXcp/uu/mSD/AH6/Isp4Np1eIKtCrG9KHveqfwr+uzP1 zN+M6lHh+lXpStVn7vo18T/rujkviT8XvH/xZ8SjUfGmuz30UblrXT4v3dpa 9v3cQ4BxxuOWPcmvNKKK/b8Ph6VCmqdKKjFbJaI/DMRiateo6lWTlJ7tu7Ci iitjEKs2X/Icsf8Ar5j/APQxVarNl/yHLH/r6j/9DFDBbn0lL/x9Sf75/nUf 50+X/j5k/wB8/wA6ZXjo90zdT1jTNItRLqF0kRI+SJfmlf6KOfxOB715ZrXj XUtSV7exDaVYtwQjZmkH+0/b6L+del6v4f0rW483sBS5C4S7h+WVB6Z/iHsf 0rynWvCWqaMHnCjUNPH/AC8wKcoP9teq/Xke9dWHVPrucWJdXpsaHw+AHjmY AAD7E5/8eWvYO9eP/D8g+OJiCCPsL4IPutfSHgDwpceOPjJ4f8LwbwL66VZ3 XrHCvzSv+CBj9cVhj60KSlUm7JK79Eb5bRnV5acFdt2Xqz9Af2YfBX/CM/s9 x63cw+XqfiGQXbEjDCBcrCv0ILOP+ulfSFQWttb2Wm29naRJb2sESxQxIMKi KMKo9gABU9fy9meOnjcXUry3k7/5L5LQ/qTLMBDBYSnQjtFW+fV/N6hRRRXC d4UUUUAFFFFABRRRQAUUUUAfhP8AtQfDf/hW/wC154n0u1g+z6Jqrf2ppW1c KsM5YsgHYJIJEA9FHrX0L+zp8cPg/pumWOgazpVv8PvGjRJbtr99OZ7bUSAF AFw3Nrn/AJ5sAn+0a91/bm+HP/CTfs42Pjmxg36p4Xuc3BUcvZzFUfp12uI2 9l3n1r8hCAVIIBBGCCODX9BZTTo8S5DCnXm04+62nbVd+jurOzXpZ6n885vU rcNZ/OpQgmn7yur6Pt1VndXT/wAj98dpAQ8FXQPGykFXU9GUjgg9iODTec1+ Pfws+PvxC+FDQ2GlXqa/4TD5k8O6s7PbqO5gcfPbt/ufL6qa+w9T/bR+HkPw wh1PR/DniW/8WzAr/YF2FhitXA+9LdLlXjz08tdzei1+b5p4fZtha6hSh7WL 2a/9uT+H1vbzP0rKvEXKcVQc60vZSS1T1/8AAWt/S1/I+vLi6t9P0u51O9vL XTbC0Xzbi9uZ1hht1H8TyMQFHuT9K/MX9pX4lfBfx3r7f8IP4X/tPxYsg+1+ NbcmyguADynlYzd57SuFx1BavGfiR8W/HvxX1dZvGGsmXTYpC9nolmph0+1/ 3Ys/O3+25Zj6ivNq/QeE+Anl1WOJxFRuoukW0l5N6OXpovJn53xbx9/aVN4a hTSp95JNv06R9dX5oOo55Fe3eDdS/tDwLBG7brmzb7PKSeSByh/FePwrxGux 8Daj9i8araO2IL9PJOTwHHKH88j8a/Q8RDmh6H55hp8tT1PZqKKO1eaeqFFH tRQMv2HivWvBMl54m8PXItNXs7KcQSldwXfG0Z4+jHHocGvlkdBX0Hr3/Ii6 1/15P/KvnwdK6sHTinKaWrsr+S2/N/ecONqTajBvRXdvN2v+S+4KKKK7jgCi ijnsGY9gqkk/gKACrVgM6/p49bqL/wBDFVFZXjV0ZWVhkMDkEVd07/kYtO/6 +4v/AEMUnsNbn0fL/wAfMh/2j/Oo6fJ/x8yH/aNMrx0e4B9e9KCQcg4PsaTt RTEZcOi6bbeJH1W1tltbt42jlEXyxuCQclegbjqK+9/2RPBWItf8fXkXLf8A Eu04sO3DzMP/ACGoI9HFfEtnaXN/q1rY2cL3F5czLDBEg5d2IVVHuSQK/Yvw J4VtvBPwh0DwvbbCLC0VJXXpJKfmkf8A4E5Y/jXwfiDmroYBUE/eqaf9urf9 F95974e5Sq+Pddr3aev/AG89v1f3HW0UUV+Hn7iFFFFABRRRQAUUUUAFFFFA BRRRQBl63o+n+IfBuraBq0AudL1KzktLuI/xxyIUYfkTX89HjzwjqHgL4yeJ fBuqBjeaTfyWxcrjzVB+SQD0dCrj2YV/RXX5cft8fDj7D438NfFCwt9tvqUf 9masyrx58aloXPqWjDr9IRX6V4aZv9Xx8sNJ+7UWn+Jbfer/AIH5n4nZP9Yw EcVFe9Tev+F7/c7fifndRRRX70fgNgooooAKVWdJFkjYpKjBkb0YHIP50lFA H0Xp1+mq6BZ6lHwLiIOw/ut0YfmDVyvN/h7qO62v9HkblD9otx7HhwPxwfxN ekV5VSHLJo9ilPngmHeijtRUGpl67/yImtf9eUn8q+fB0FfQeu/8iNrX/XlJ /KvnwdBXbhPhZ52N+JBRRRXWcYV9p/sNfDr/AISr9qa+8bXsHmaR4Rs90JYA q19cKyRjB/uRea3sWSvip3WOF5HO1EUsx9AOTX7kfsofDhvhx+xf4bt7238j X9bB1nVg33lknAKRnv8AJEIkx2KmvieP82+pZTKMX71T3V6P4vw0+aPuPD7K PrubRnJe7T95+q2/HX5M89+Mv7FfgPx415rvgJ4Ph54ukJkdLeHOmXj8n97A MeWxOPnjx3yrV+Y/jT4WePvhP8TNM0nx54eudIaW+jWzv0Pm2V786/6qcDaT gg7Dhxnla/dXxv8AEXwf8O/D39o+K9ZgsN4P2e1X95c3JH8McQ+Zj74wO5Ff n78Wf2ifEHxH0q98O6bplpoHg2f5ZYLmJLi7u1BBBdiCsXIBAT5h/fr5HgbO c9qWhKPPR7ydrej1b9LP1R9jx1kuRQvOMuSt2jrf1WiXrdejPnqX/j6k/wB4 /wA6Z29aPxor9MPzMPWiiigD6V/Zd8Ff8JJ+0B/b11D5mm+HohckkZU3DZWE fUYdx7xiv0srwz9njwV/wh37NmlNcReXqmsf8TG7yPmUOB5aeoxGF47MWr3O v574wzT69mc2n7sfdXy3+93P6F4Pyv6jlkE170vefz2+5WCiiivlz6gKKKKA CiiigAooooAKKKKACiiigAryz41/D6L4ofsyeLPB5RGvrm0Mumu2Bsuo/nhO ewLKFJ/us3rXqdFb4bETw9aNWm7Si016owxWGp4ijOlUV4yTT9GfzVyxSwXM kM0bwzRsVkjdcMrA4IIPQio6+qP2wfhz/wAIH+2DqmoWcBi0TxMn9q2hA+VZ WJFwmfXzMvjsJFr5Xr+r8sx8MbhKeIhtJJ/8D5PQ/krNMBUwWLqYee8G1/k/ mtQoooruOEKKKKANLRtROk+KbHUeSkUn70f3ozw4/I5/CvoY4zlSGU4Kt/eB 5Br5n6jnkV7b4N1L+0PAsCO265sm+zyknkgDKH8V4/CuTFQ0Ujtwc7PlOqoo oriPQMzXP+RH1r/ryk/lXz2Ogr6E1z/kR9a/68pP5V89joK7cJ8LPOxnxIKK KK6zjPT/AINeENO8bftJeGdJ165tbHwrazf2n4hurqQRxR2VuRJIrMem9tkY 9fMr9IPiT+1nI5n0j4W2iiMZQ+INQg4+sEDdfZpMDn7pr85/AGmiLQbvVJky 91II4cj+BDkn8W7/AOzXoHU18pm+SYbH4uNXELmUFZR6X6t93su2mzPrsnzr FYDBypYd8rm7uXW3RJ9Fv567lzUtS1LWfENxq+s6je6xq1wf315eTGSV/bJ6 D0UYA7CqdHr2or0oxUUklZI89tyd3uw9qO1FB6GmIK9I+EvgxvHnx98P+H3j aSwM/n6gewt4/mcE9t2AgPqwrzev0A/ZI8Ff2f4A1jxzdw7bnVJPslixHIgj b52B9Gk4P/XIV4HE+afUMuqVU/eei9X/AJb/ACPf4Yyv6/mNOk17q1fov89v mfYCqqoFUBVAwABgAUtFFfzkf0cFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF FFFAHyP+2d8OP+E2/ZHutes4PN1rwpKdRhKrlmtiNtyvsNmJD/1xFfi/X9KN 1a299plzZXkMdzaXETRTxSLlZEYEMpHcEEiv59fi54CuPhn+0b4s8FzCQw6f fN9ikfrLbPh4XJ9TGy59Dkdq/bPC7N+ehUwUnrH3l6Pf7nr8z8Q8U8n5K9PG wWkvdfqtvvWnyPOKKKK/WD8kCiiigArsfA2o/YvGq2jtiC/TyTk8CQcofzyP xrjqVWdJFkiYpKjBkb0YHIP51M480WioT5ZJn0tQKp6dfpqnh+z1GPhbiIOw /ut0YfgQa6zwz4U8R+MtfbTfDGlT6rcJjz5AQkFsP70sp+VB9efQGvFrVYUY OdSSilu3ol8z3qFKdaajTTbeyWrfojjdbOPBOs/9eUn8q+eh0FfrN4W/Zz8J 6foNx/wnDjxjfXMDRTWsbPBZQhhg7MEO7Y6OSMHkLXzj8T/2ONc0o3Gr/Ce/ m8UaaMu3h/UJVXUIh1xDKcJcD0Vtrn/arwMv47yepiHQ9pbtJq0X8+nq0l5n u5lwJnFPDqv7O/eKd5L5dfk2+58T1LDBLdXsNrbqWnmkEcYHqTgUt1bXVhrF 1p2oWl3p+o2rmO5tLqFopoGH8LowBU/UV2vgHTvtPiifUnXMVlH+79DK4IH4 hcn8RX3EppQ5j4aFNufKz1W1tYbDS7axgA8m2iEaY74HX8Tk/jU9H40nO8fS vKPYQUveiigYe+KKKKBGnoukXuv+MNM0PTo/Nv7+6jtoF7bnYKM+3PJ7Cv2T 8N6DZeF/AOj+HdOGLPTrRLeIkYLbRgsfcnJPuTXwX+yb4L/tb4saj4xu4t1l osHlWpYcNcSgjI9dse/PoXU1+h9fjPiJmntcXHCxekNX6v8AyX5s/aPDrK/Z YSWKktZ6L0X+b/JBRRRX5yfowUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU UAFfm/8At9fDnztI8LfFKwgy9uf7J1cqP4GLPA59AG8xST/fQV+kFcR8SPBV l8RfgV4o8FX5RYdWsHhjkcZEMo+aKTH+xIqN/wABr3OG82eXZjSxHROz9Ho/ 8/U8LiXKVmWW1cP1auvVar/L0P53qu6dYy6nrltp8DxRzzsVjaQ4XOCeT26U up6be6P4k1DSNSt3tNRsbmS2uoH+9FIjFXU+4IIrT8J/8lK0b/rv/wCytX9S ua5OZH8rKHv8sjKvtPvdMv8A7LqFtLaz9g44ceqnow+lU6+kLq1tb/T2tL63 hu7dv+Wcq5APqO4PuK801jwBcRu02gu96h5+xyn96P8Acbo/0OD9axp4lPSW htVwso6x1R51RSsrJIyOpV1JDKeoI6ikrpOVnvfwL1f4ax+Ln0f4q6vqmkaC 0olsprcbbcyH7yXEgy8cZwDuUeuSK/WTR7LRtP8ABun2/hqDSrfw4ybrH+y9 ptZBj7ysvDH1JJb1r8IK9U+Gnxm8ffCnU93hfVt+kO4a60W+BmsrjnJzGT8j dfmTBycnNfn/ABjwfXzX95RrNNfZfw+q7Pzd/kfoPBnGVDKX7OtRTT+0viXk +68tPmfsy33OxpSPXkV88/Df9pr4a/EDTlttVvYPAPiVY90tjqtx/o0uBljB cHhuhOxsNgVgeOv2i0iefTPh7aebICVbXNQh+Ue8EB6+zycdwtfj1HhPNqmJ eH9i1Jbt6Jed9n8r36H7LW4uymnhViPbKSeyWsn5W3Xzsu53/wAbfC3wf8Qe CEuvi1DbW90Iium6laNs1gccCAqC0i/7Lho/pX5/6bo+n6FaXFhpU97d2P2q SSG4vIkjuJUJ+UyKhKhgoAwpIra1HUNQ1jXrjVNYv7zVdTnOZru7lMkj+2T0 HsMAdhVTvX7Rw3kdTK8N7KVZzv0+yv8ACunn33sj8W4kzunmmJ9rGioW6/af +J7P7tNrsPek/wCWg+lL3qhqOo2Wk6Y1/qMxtrRCFaTYW+Y/dXjucHGcZr6F anzzdi/39axdT8Q6XpN3FazzGa+kkVFtYcM67iBluyjnvz7V53rXjm/vd9vp avpVoeDJnM8g+vRB7Dn3rj7H/kOWR6k3UZJPJJ3jk+tdUMM95HHUxa2ifSDA rIynkg4yBTakl/4+ZP8AeP8AOvYfgN4K/wCE2/aR0W0uIvN0rT2/tC/BGQUi IKqfUM5RSPQn0ry8Zi4YbDzrT2imz18FhJ4rEQow3k0vvP0H+C3gr/hBP2dt B0iaLytTnj+2aiCMN58oBKn3Vdqf8Ar1Wiiv5lxeJnia86095Nt/M/pvCYWG GoQow2ikl8gooornOgKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig D8df23fhx/wiX7UUXi6xg8vSPFdubhiowq3ce1Jx/wACBjkz3Lt6V8peE/8A kpWjf9d//ZWr9pf2rfhz/wALF/Y71+O0g87W9DH9r6dtHzMYlbzUHc7ojIAv dtvpX4g2l3cWOoxXdpKYLqI5jkABKnBGRnvzX9GcB5t9fyiMJP3qfuv06P7t Pkz+buPco+oZxKcV7tT3l6/aX36+jR71q2t6ZokAbULjZKwzHbxjdLJ9F7D3 OBXlOteMdU1ZXt4CdM09uDFE/wC8cf7b9fwGB9a5V3eSd5ZXeWVzl5HYszH1 JPWm19jToRjq9WfHVcTKe2iCiiitznCiiigAIDAhgGB6gius0XxhqmkqkE5O qaeOBDM/zoP9h+o+hyK5QdaSplFSVmVGbi7pn0FpOt6ZrcG7T590yjMltINs qf8AAe49xkVroju+1FLt1OOw9T6V80I7xzpLE7xSoco6MVZT6gjpX0B4fv7u /wDh9pM15N5sskO6VtoBkbcRubHU4A59q4K9Hk1R6WHxHO7M1yAOjBj3K9Pz 719Ffs3WlnqPjLx9p2pWVlqenXOhQx3NneQLNDOv2j7rowIYfX8K+c6+k/2Y /wDkpfjT/sCw/wDpQK+U4vbWTV2uy/8ASon1vCEVLOaCa3b/APSWYvxR/Y40 rURc6z8JL2HQb7ln8N6lOTZSnri3nOWhPokm5P8AaWvhHVPDniDwh8TIPD3i rRdS8O65BcxmSzvoTG5G8fMp6Oh7MpIPrX7h319Y6VodxqeqX1npem24zPd3 cojiT6se/sMk9ga+OvjJ8XPCnjrww3hTS/C+n+ItPik3Qa3rNsQ9s4Od9mvD xngfOxAP9w18twVxVnFaaoVIOrBbyejj6yekvR+8+59Pxvwnk1CDr05qlUe0 VqpeiWsfX4fI+epf+PmT/fP86/Rr9lXwV/YPwQufFF3Ft1DX590RYcrbRkqn 03MXb3BWvgXwh4cvPGPxR0Pw1ZFvtOo3iwmTbny1Jy8h9lUMx9ga/ZDTNOtN I8O2GlafEILGyt0t7eIdERFCqPwAFaeIuaeyw0MJF6z1fov83+RPhzlftcTP FyWkNF6vf7l+Zdooor8cP2QKKKKACiiigAooooAKKKKACiiigAooooAKKKKA CiiigAooooAQgMpDAEEYIPevwP8A2gfh0fhd+1d4q8MwwGHSGn+2aRxhTazZ dFHqEO6PPrGa/fGvgX9vL4cf2z8HtC+JFhb7r7QJ/smpMq8taTMAjE+iS4AH /TZjX3vh3m/1PNFSk/dq+78/s/5fM+A8Rsn+uZW6sV71L3vl9r/P5H5Q0UUV /Q5/OgUUUUAFFdT4P8D+MPiB4wj0DwT4c1PxLqzYLxWsfyQKTgPNIcJEmeNz kD0zX6U/Bz9hbw9ootdd+MF3B4t1cYdNAtGZdNtz1xK3DXDDjrtTI+6w5rwM 74mwGVQvXn73SK1k/l09XZH0GR8MZhmsrUIe71k9Ir59fRXZ+dfhX4Y+OfGX gDxF4u0PQZ28I6HYT3uo63dHybQJChd44nb/AF0uBwiZ5IyRXAA5UH1r98Pj rZ2em/sE/FGx0+0trCxt/CN5HBb28QjjiUQMAqquAAPQV+B6/wCrX6Vw8J8R 1M5p1asoKKjKyW+lur7/ACR3cXcN08mqUaUZuTlG7e2t7aLsLXu/hT/km2i+ 8B/9DavCK938Kf8AJNdG/wCuB/8AQ2r6TFfCj5vB/G/Q6DHNekfDX4ht8N9U 8SalBpK6xf3+nx2tpHLKY4Y2WTeXkI+YjHQLyT3Feb96K8fGYOjiqMqNZXi9 16O/T0PcweLq4WtGtRdpR2fyt1Ol8VeMPE3jbXFv/E+qy6i8Zzb2yr5dtbD0 iiHyr9eWPc1zVH+TUkUUk9zFBBG8s0jBI0RSWZicAAdya0o0adGmoU4qMVsl okZ1q1StUc6knKT3b1bPsv8AZF8FfaPEeu+PLuEGKzT7Bp7MP+WrANKw9Cqb V+kjV95Vwnwz8Hx+BPgf4e8NBUFzbWwa8Zed87/PIc9xuJA9gK7uv514kzT6 /mFSsn7uy9Ft9+/zP6L4byv+z8up0Wvetd+r3+7b5BRRRXhHuhRRRQAUUUUA FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUNzbW95p89peW8N1azIUmh mQOkikYKsp4II7GpqKabTuDVz4O+Mf7EPhbxOLrXPhdPb+D9cbLtpU2Tp9wf RMZaAk+m5ewVetfmX44+HvjL4ceMX0LxpoN9od+MmLzlzHOoON8cgysi+6k1 /RLXNeK/B3hfxz4Rn0Hxdoen6/pMv3oLuPdtP95W6ow7MpBHrX6Hw/4h43BW p4n95D/yZfPr8/vR+dcQ+HWCxt6mG/dz/wDJX6rp6r7mfzmUV+hnxi/YZ1fS vteu/CO8k13Txl30G+kAuohySIpDhZR6K2G93NfAGo6bqOj65daZq1jeaZqV tIY7i1uoWiliYdVZWAIPsa/acoz3BZnT58NO/ddV6r+kfiWb5DjssqcmJhbs +j9H/TNTwv4u8VeCfFsGveD/ABDqvhvV4iMXFjMV3gHO2RD8siequCPav0g+ Dv7dul34tdC+M9jDoF7gIviXTomNlKcYzPFy0BPdl3Jk/wAAr8vqKxzrhvAZ pC2Ihr0ktJL5/o7ryNsk4lx+VTvh56dYvWL+X6qz8z96/jjqWnax+wF8UNT0 m/stU0258JXr293aTrLDMphb5ldSQw9wa/BJf9Wv0r0PwL478YeGrLU/B2ia /eWfhXxLBJp2saS4EttLHMux3SNsiOYA8SJg565rI1nwfqmjo80QOpaev/Le FTvQf7adR9RkV5vCvD7yWFWjKfMpO6ezta2vn8z0uLOIVnc6VeMHFxVmt1e9 9PL5HK17v4T/AOSa6N/1wP8A6G1eDgggEEEHoQa948KD/i2ujf8AXA/+htX0 uL+FHzWDfvv0Ogorq/CXgjxR468RDTPDGkXWpzjHmug2xQg/xO5+VR9Tz2zX 3V8Nv2XPDnh0W+qeN5IfFGsDDCzAP2KE+hB5l/4Fhf8AZ718lnPEmBy2P72V 5fyrf/gfM+xybhvHZlL91G0f5nov+D8j5C+HfwX8b/Ei5jm0ux/s/RN2JNVv QUgHrs7yH2Xj1Ir7++HHwI8EfDsQ3sVt/bviJME6pfICyN6xJyI/qMt/tGva Ioo4LaOGGNIYY1CpGihVUAYAAHQCn1+P55xjjsxvBPkh2XX1fX8F5H7DkfBu By602uefd9PRdPxfmFFFFfJH1oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU UUAFFFFABRRRQAUUUUAFFFFABRRRQAV5L8Ufgj8O/i7obW/i7RI21JY9ttq9 piK9t/TbJj5h/suGX2r1qit8Niq2HqKpSk4yXVaHPisLRxNJ060VKL6PVH4s fGP9kX4ifDIXWsaJHJ448IxksbywhP2m2X/ptCMnAHV13L3O3pXydX9LFfK/ xj/ZL+HXxSF1q2mQp4K8Xvlv7R0+EeTct/03h4Dc9WXa3qT0r9a4f8TNqWYL /t9fqv1X3H5HxD4Yb1cvf/bj/R/o/vPxl0H/AJHvRf8Ar9j/AJ19B5YPlSQc 8EVzvjX4FfEb4QfFTR4vFWjPJpLagi22s2OZbOf5uPnx8jH+64VvYjmvrD4b /s1+MPGbQalr6yeE/D74YSXEf+lTr/sRHoCP4mx6gNX3+YZ5gKdCOJlVXI1o 73v6d35HwGW5FmFSvLDKk+dbq1reb7LzPlG4+Hsfi3W4rXRLG5TX7l9sSWMB k89vRo16/VcGvtv4M/si6jB4O0ib4o3S2fkR86Pp8253+Yn95KPujn7q5P8A tA19keB/hp4O+Hmj/ZfDOkx287qFnvpv3lzP/vyHnH+yML6Cu8r8oz3xDxOI TpYT3I938Xy7fi/Q/Wci8OsNh5Kti/fl2Xw/Pv8AgvJmPoegaL4Z8Ow6ToGm Wek6dF9yC2jCrnuT3LHuTknvWxRRX5xOcpycpO7Z+kQhGEVGKskFFFFSUFFF FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU AFFFFABRRRQAUUUUAYHib/kUZP8Ar5t//R8db9FFbS/hL1f6GS/iv0X6hRRR WJqFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q== name vagrant passwd ******** passwordpolicyoptions PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp bGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr ZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt MDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8 L2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgo8L2Rp Y3Q+CjwvcGxpc3Q+Cg== realname vagrant shell /bin/bash uid 501 chef-12.14.60/spec/data/mac_users/10.8.shadow.xml000066400000000000000000000014171276456504500211130ustar00rootroot00000000000000 SALTED-SHA512-PBKDF2 entropy 6kwtJl2AG6DsDfzNJT38HekcvggGtKzB7X/iKuvPa+tTRND0QuWQ/6BNZ5B1 2jr7EZ5BtyterwjuSqVGk3ImRtUZ7gSEPeuKPpd0KNM/Yl6DiHkT5cE7cANZ YV4ArXvD56DJivw+GdE2AnJFT40zqSFNL76L5o0fmCGyZokxI2Y= iterations 39840 salt +ZTvL3O3xVlOvRVTMAl2sgczzg4k1ll4PYfz2By7tqk= chef-12.14.60/spec/data/mac_users/10.9.plist.xml000066400000000000000000000760331276456504500207700ustar00rootroot00000000000000 KerberosKeys MIIBVKEDAgEBoIIBSzCCAUcwc6ErMCmgAwIBEqEiBCAJxcIcjX3sMb98++d0 YvKqc351+CJJTMpyJO5mwWFMCaJEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEEy NDA4NDVBMjU0OUZCOEUwRjI4NEU1NkUyODE3NzU2RUU5Q0QyMnZhZ3JhbnQw Y6EbMBmgAwIBEaESBBDzYvuM3CLsLOGCIX4FJ8vdokQwQqADAgEDoTsEOUxL REM6U0hBMS40QTI0MDg0NUEyNTQ5RkI4RTBGMjg0RTU2RTI4MTc3NTZFRTlD RDIydmFncmFudDBroSMwIaADAgEQoRoEGCkvuVvN92vqnm0cy+9GWNBoIEoW XtUNx6JEMEKgAwIBA6E7BDlMS0RDOlNIQTEuNEEyNDA4NDVBMjU0OUZCOEUw RjI4NEU1NkUyODE3NzU2RUU5Q0QyMnZhZ3JhbnQ= ShadowHashData YnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50 cm9weVRzYWx0Wml0ZXJhdGlvbnNPEIASYBqQ2xfL+LpICOY4L7DTudimwaGQ R3v2gKshr7YGVGcTblXMIIpvdBVuPa8g+xM2nvS3uvoEfYA1n7RqSKStzNVI 67M4UbCTR8yoQ0Gn+TonFHND+J+4Q/tGwAF9Jkr6SXa6rPlBuRW9HsHKJMML PnWeAkA+AvWf5/9ZOKdjbE8QIO6VS+Ry/cYN34lIR4FDOZNiXwBq9uyBDAj0 mn5BOUahEYayCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA AAAAAOo= _writers_hint vagrant _writers_jpegphoto vagrant _writers_passwd vagrant _writers_picture vagrant authentication_authority ;ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2> ;Kerberosv5;;vagrant@LKDC:SHA1.4A240845A2549FB8E0F284E56E2817756EE9CD22;LKDC:SHA1.4A240845A2549FB8E0F284E56E2817756EE9CD22 generateduid 11112222-3333-4444-AAAA-BBBBCCCCDDDD gid 80 home /Users/vagrant jpegphoto /9j/4AAQSkZJRgABAQAAAQABAAD/4ge4SUNDX1BST0ZJTEUAAQEAAAeoYXBw bAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBs AAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAA AG9kc2NtAAABeAAABWxjcHJ0AAAG5AAAADh3dHB0AAAHHAAAABRyWFlaAAAH MAAAABRnWFlaAAAHRAAAABRiWFlaAAAHWAAAABRyVFJDAAAHbAAAAA5jaGFk AAAHfAAAACxiVFJDAAAHbAAAAA5nVFJDAAAHbAAAAA5kZXNjAAAAAAAAABRH ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJv ZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAbWx1YwAAAAAAAAAeAAAADHNrU0sAAAAoAAABeGhySFIAAAAo AAABoGNhRVMAAAAkAAAByHB0QlIAAAAmAAAB7HVrVUEAAAAqAAACEmZyRlUA AAAoAAACPHpoVFcAAAAWAAACZGl0SVQAAAAoAAACem5iTk8AAAAmAAAComtv S1IAAAAWAAACyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAAD Hmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAa AAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwA AAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZp RkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE 8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQBjAG4A/QAg AFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGkBDQBrAGkAIABSAEcA QgAgAHAAcgBvAGYAaQBsAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDo AHIAaQBjAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8E FwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUABy AG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCkBp1KAAgAFIA RwBCACCCcl9pY8+P8ABQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBl AHIAaQBjAG8ARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkA bMd8vBgAIABSAEcAQgAg1QS4XNMMx3wATwBiAGUAYwBuAP0AIABSAEcAQgAg AHAAcgBvAGYAaQBsBeQF6AXVBeQF2QXcACAAUgBHAEIAIAXbBdwF3AXZAEEA bABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGwAwQBs AHQAYQBsAOEAbgBvAHMAIABSAEcAQgAgAHAAcgBvAGYAaQBsZm6QGgAgAFIA RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBv AGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACAD wAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBl AG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8A ZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBH AGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUA bgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAFUAbgBpAHcAZQByAHMAYQBs AG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYn BkQGOQYnBkUARwBlAG4AZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwA ZQBHAGUAbgBlAHIAZQBsACAAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABz AGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdo dHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRN AAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8A ALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA /ZH///ui///9owAAA9wAAMBs/9sAQwACAgICAgECAgICAgICAwMGBAMDAwMH BQUEBggHCAgIBwgICQoNCwkJDAoICAsPCwwNDg4ODgkLEBEPDhENDg4O/9sA QwECAgIDAwMGBAQGDgkICQ4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O Dg4ODg4ODg4ODg4ODg4ODg4ODg4O/8AAEQgBMQEuAwEiAAIRAQMRAf/EAB8A AAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQE AAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYX GBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3 eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfI ycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEB AQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMR BAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJico KSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWG h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW 19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiis6/1Wz0 6P8AfyAyY+WJeWP4dvxoA0aw9R1+zsN0at9puR/Ah4H1NcjqHiG9vQ0cZ+yQ H+FD8x+p/wAK5+gDWu9a1C8u1kad4gpyiRkqF/z71u6d4pI2xaiu4dPOQc/i P8K43v0P5UlMZ7LDPDcW6ywSJLGejKcipa8gtL65sLjzLaV4z3HVT9RXb6d4 mt7grFegW0398fcP+H40hHUUUgIZQykEEZBHeloAKKKKACiiigAooooAKKKK ACiiigAooooAKKKKACiiigAooooAKKKguLmC1tzLcSpDGO7H/OaAJ6K4y48W gXyi2tvMtwfmLnDN9PSuisNVs9RjzBJiQfeibhh+Hf8ACgC3cgHT5wQCDG2Q fpX4u/CD9rf4h/C+aHRtXkfxt4PjbaLG/mP2i2XP/LGc5YADorbl7AL1r9o7 j/jxn/65n+VfzYS/8fMn+8f51+qeG+W4XHUcXSxEFKPub/8Ab2z3T9D8p8S8 zxWBq4Srh5uMvf2/7d3WzXkz99/hb8cPh38XtCW48J6yn9ppGGutHvMRXtv6 5TJ3L/tIWX3zxXrtfgD8JPhh8TviF4ut9Q+Hlvd6ZDZTgv4nluHtLSxYd1mX 5nkGfuRbm+lftD4C1HXfDvw+0vRfGHiW48batBFtudbeyS2aZvaNf4QOAxO4 9W5r57jHh7BZXX5cPXUr/Z3lH1a0++z8nufQ8G8R43NaHNiKDjb7X2Zeiev3 XXpseu0VBb3MF1biW3lSaM91NT18Yfankuk/FDQfHPgxNb8B6zYa1ojna17a ybnjb+5Ihw0T/wCy4Bqkzs8hd2Z3JyWJyTX4a+HPF3ijwL4/bxB4O13UPDus K5DTWr/LMufuSocrKn+y4Ir76+Fv7YXhzXfs+j/FO1tvB+sHCLrtorNplwfW VOXtifX5o/da/Rc/8O8bg71cL+9h2+0vl19Vr5I/OOHvEbBYxqli/wB1Pv8A Zfz6fPTzPs6jt61HFLDPY293bTwXVpcIJLe4t5VlimQ9GR1JVgfUE0/mvzux +jppq6F70lL6elHagBPyo7elHejvQM1tP1m905wI3MkPeJ+V/D0ru9O1yy1D CK3k3B/5ZOeT9D3ry/tR0YH360CPaaK8607xJd2m2K6zdQDjJPzj6Hv+NdzZ 39rfweZbSq/95Twy/UUgLlFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABS MyohZ2CqBkknAFYmo69Z2AZFb7Rcj/lmh6fU9q4S/wBWvNRc+dJiLPyxJwo/ x/GgDrNR8TwQho7BRcydPMP3B/jXE3V3c3lwZrmV5X9+g+g7VXzj2PrS8EZP FMBvtT1ZkkDozI68gqcEU0gjP86Tv9aBnWWPiWXyWttQ/eIyFRMo+YccZHQ/ pXwD8MP2PdC0iSDWvivd2/inVc+Yvh+xkZdOgPXE0gw9wR3Vdqf71fadHQ5r 1MvzvG4GlUp4efKp2u1vpfZ7rfpqeRmGR4LHVadTEQ5nC9k9tbbrrt10IoIY LTTbaxs7e2srC2Ty7a1toVihgX+6iKAqj2AqXtSVla7r2ieFvDjav4j1S00f TRwstw3Mp/uxqPmkb2UH8K86EJ1JqMVeT6LVtnpznClByk0orvokv0N62u7i yuRLbTPE4646H6jvV3U/i74Q8M3MVh4p1SGw1Z03rZwRvNMU/vtGgLIuSOWx nPFfEXjj9ojWNT87TvAcE/hzTzlW1S4UG+lHqi8rAD6/M/uK8d8JPJL4xvbi eSWe5lgZ5ZpXLySMWXJZjyT7k1+g5XwBXnTdbGPkX8q+L5vZfi/Q/PM08QKE aipYKPO/5n8PyWjfrovU+P5/+P2b/fP86irqfG/hfUvBXxe8SeE9XXGoaVqE ttK23Ak2sdrj/ZZcMPYiuWr97pVI1IRlF3TV0fz/AFacqc5QkrNOzPpr4GfE bxb4B8GyDQdQEmlm/czaReZks5OFzhc5jb/aQg/WvvvwJ8ZvCPjeSGwkk/4R rxG/A02/lGyZv+mE3Cv/ALp2t7GvzM8Af8iHL/1+yf8AoK128dvJeXcFlBbT XtzO4WG2hiMkkrdgqgEk/SviOIuE8BmUpTkuSf8AMv1Wz+evmj7vhzizH5bC MIPmh/K/0e6+Wnkz9V2VkdlYMrDhlIwRTfzr5Ut/E3xQ+CX7Nlz4x8foniHQ bW7tbW38O3FyDqduk0mzd9p5CbRyIn389Ste2fDz4peBPipob3ngvW0vbmJN 15pNyvk6hZ/9dISclf8AbQsnvX4hj8jr4eMqsGqlJO3PHWN9N+268uibP3DA Z/h8TONKadOq1fklpK3l32fn3SO/780d6Xr3/Wkrxj3Be1JS/likoAU9KfFL LBOssMjxSA5DKcGmUn50Adrp3in7sOornt5yD+Y/w/KuximingWWGRJY26Mp yDXjXOenWuL8V/Fzw58MWYahqMlzrRXcmiWJElxJ6GQH5Yl/2nwfQGt8LhK2 JqKnRi5SfRf1+OxzYrF0cNSdWtNRiur/AK/A+naK5vwdr0nij4VeHfEktqtl JqenxXTW6ybxEXUNt3YGcZxnFdJWVWnKnNwlutDWlUjUgpx2auvmFFFFQWFF FFABRRRQAUUV4D8evjfbfBHQ/Cmpajo17qmk6tqD2l3NYyL9otFEe4SJG3yy e67lPoc104PB1sVWVGjHmk9l30ucuMxlHCUXWrStFbv52PcL3UbTT4d1zKFY jKoOWb6CuG1LxHd3ZaODNrbnsp+Zvqf6CvOvCnjfwv8AETwqfEfhHxDZ+JNP JHnyRMfOt2P8E8bfPE3swx6E10fFZVaU6U3CpFqS3T0a9UbUa1OtBVKclKL2 a1T+Yd6KPyrk/F3jjwv4F0lbnxLqaWs0i7rawhXzbu5/3Igc4/2mwvvVUKFW tUVOlFyk9ktWLEYilQpupVkoxW7bsjq6O9fK+hftaeBrj4n3nhvxlpl34It9 yHT9Wkm+02xVlyFudq5ibP8AGoZPp1r6lhlgudOt7y0uLe8sbhBJbXVtKssM yf3kdSVYe4NduZZPjcvmo4mm4327P0aun566HDlmdYHMIuWGqKVt+69U7P8A Akyce1LwTx1pM0qqzyBEVmduiqMk15h6YnQ1HPNBa6bc315cW1lY2yeZc3Vz MsUMCf3ndiFUe5NeXeOvjH4R8DtLY+b/AMJJ4hQY/s2wlG2E/wDTaYZVP90Z b2Ffm58ZfGXxR+IOpvdeKdU/tDwvFIXtNJ0pDFY2Y7F4cku4/wCejlifUV9n w9wTjczanP8Ad0+73f8AhXX1dl2bPiuIuOMFlicIfvKnZbL1fT0V33sfUfxR /bE0LR/tOjfCmzt/FWqDKN4gv42GnQHpmGM4a4I7M21P96vmXR/FPiTxra3f iLxdrmoeItblvJAbq8kzsXC4SNRhY0HZVAFfP4IIyCCD0INexeAf+RCk/wCv 2T+S1+1Zbw1gMqo2w8Pe6yesn8+i8lZeR+JZjxNmGbV74ifu9IrSK+X6u78z ta6zwd/yM0//AF6t/wChLXJnpXt3wO+HWp/EHx1rENldR2FtZWAae5kjLKGe RQicdyFcj/dNGZ4inQws6lR2iupeWYepXxUKdNXk+g79vb4c/wBm/Erw78Tr CDbaaxENP1RlXgXMS5iYn1eIFfpBX58V+/nx0+HafFH9lzxX4TWJZNTktTca UxxlbqL54sHtuI2E/wB12r8BXR45njkVkkUkMrDBBHUEV4/h1m/1vLPYyfvU tPl9n9V8j1vEfJ/qmae2ivdq6/P7X6P5n1L+zt8Orj4lWd/pltr+j6StndNL eRvKJL7yyF+aK3zlx1+c4UY5r9FfBnw/8KeArBk8OacFvZE2z6pdESXk/wBX x8i/7KAD61+JVpd3mna1aalpt5eabqdrIJLW8tJmimgYfxI6kFT9DX218Lv2 x9V082+jfFuym16yGEXxJpsCi9jHTNxAMLOPV02v3IauDjnh/OcYnPDVOan1 gtH/APbej26Js7+A+IMmwbUMTT5anSb1X/2vqvm0j6A/a0/5MP8AEIH/AEGN N/8AR5r8p7G+vtK16z1XSr+90rVbSQSWt7ZTtDPA3qjqQR/XvX6f/tJ+I/D3 i3/gnHrOv+Fdc0zxFos+sad5d5Yzb1z5/KsPvIwyMqwBHpX5b11eGlJxympC as+eSaf+GOjX6HL4l1oyzenUpyuuSLTT85apo+5/hd+2TqFobfR/i9Yy6va8 KnibS7cC6QetxbjCy+7x7W7lWr700TW9F8TeErTX/Der6dr+h3IHkX1jMJIm OM7T3Vh3VgGHpX4SV2Hgjx/4y+HHis6z4L1670S7fi5iXD212v8Admhb5JB9 Rn0IrHiDw4wmKvVwbVOfb7L+X2flp5G3DviTi8JaljF7SHf7S+f2vnr5n7fY 70vavkv4XftbeDfF722j+PobbwB4kchFvDIW0q7Y8cOfmtyf7r5XPRhX1hcT QWelSajeXVraaakQle8mmVYAhHD+ZnaVPYg89s1+MZnk+My+t7LE03F9Oz9H s/kfteV51gsxo+1w9RSS37r1XQf+VYviHxLoHhLw7/aviXVrXSLM5ERlOZJz /djjHzOfoMepFfP3jj9ouxtPO074fW0eqXPKnWr6I/Zk94Yjgyf7z4X2NfKm r6vq3iDxFNq+u6le6xqkv37m7k3vj+6Oyr/sqAPavrcj4BxWKtUxX7uHb7T+ XT56+R8jnnH+Fw16eEXtJ9/sr5/a+Wnme7eOP2hde1kzad4Khn8LaU2Va/kw dQnHqpGVgB/2ct/tCvng5aaSRmeSWRi8kkjFndj1LMeSfc0UV+t5blOEy+n7 PDw5V17v1e7/AE6H5JmWbYvH1faYibk+nZei2X9XP1/+E3/JsXgD/sAWv/op a9Crz34S/wDJsPgD/sAWv/opa9Cr+ccx/wB7q/4n+Z/R+W/7pS/wr8kFFFFc R2hRRRQAUUUUAFfn7/wUE/5Ij4B/7Dkv/og1+gVfn7/wUE/5Ij4B/wCw5L/6 INfVcEf8jzD+r/JnynHH/IixHovzR+YfhzxJ4h8H+MbfxD4U1vUvDutw8JeW MuxyP7rjo6HurAg+lffXws/bF0rVWtdD+LFjFoOpMRHH4i0yBmspm6Dz4Blo ST/Em5OfurX511Ys/wDkNWP/AF8x/wDoYr98zvhvAZrC2Ih7y2ktJL5/o7ry P5+yPiTMMqqXw89OsXrF/L9VZ+Z+mPjr9oq7lln0z4fWr2EQJR9bv4QZ294Y TkJ7M+W7gCvmO6ubq/1a41C/urm/1Cdt091cymSWU+rMeT/Smy/8fMn+8f51 H+PNcGVZLg8up8mHhbu+r9X+m3ZHqZrnWMzGpz4id+y6L0X6792eJ+N/+SmX n/XCH/0Cul+Gnxj8ffCfUifCmrB9Hkk33WhX4M1hcep2ZzG3+3GVP1rmvG// ACUy8/64Q/8AoFcpX0VXCUcTh/ZVoKUWtU1dHzFLF1sNiPa0ZuMk9GnZn6se B/2q/hr4s8I3Fxq0eqeFfEdrDvn0UxG5+084JtpVADrnGQ+1lByc9a8s8d/H bxT4shuNO0USeEvD7gq0NtNm7uF/6azDoD/dTA9zXxj4A/5Hub/ryk/mtewZ z/8AWr43DcFZVgsS6sIXe6UndR9P83d+Z9riONs2x2FVKpUstnZWcvX/ACVl 3QgAVcAADPQU4Eq2VJU+opMcUV9KfOHJa14O0vVWknt9ulX7cmSJP3ch/wBt P6rg/WrHhTS7zR/DM1jfJGswu3ZTG+5XUhcMD+B6810tFW6knHlZkqUVLmW4 vb0r9RP2cPBX/CI/s3afd3MIj1XXD/aFwSOQjDEK/TZhsdi7V+fXwu8HP48+ Ovh7w3tY2c1wJL5hn5bdPnk57EqCoPqwr9f0RIoUjjRY40UKiKMBQOgA7Cvy zxHzTlp08JF7+8/Tp+N/uP1Xw3yvmqVMZJbe6vV7/hp8x1fiL+1z8Of+Ff8A 7YmtXFpB5WieIh/a1jtHyq0jHz09OJQ5wOiulft1Xx7+2r8OP+Ez/ZQk8S2U Hm6z4TmN8hVcs1q+FuF+gASQn0iNfL8B5v8AUc1gpP3anuv57P7/AMGz6jj7 J/r2UzcV71P3l8t1934pH400UUV/SB/NRPDdXVtZXtrb3VzBaXmz7Zbxyssd zsO5PMUHDFTyCRkHpUPB6cH0pKKVkF29wopc8YPIpKYAQCCCAQRgg969m8Ba tq138N7jRLvVtTutFsb4NZafNdO9vblkydiE4XJ54HHbFeM16v8ADv8A5F3V /wDr8T/0CufFRThdrY6sJKSqaPc9AzRTXdY03SMqr6k/kK9Av/hh400j9nPx H8Utb0ibRvDOk2iXEcV4DHd3wZ0QeXGeVX5wdz4z2B615FfE0qPL7SSXM0lf q3sl3PZo4atW5uSLfKm3bolq2+xwPOKWs/TdV0/V7Iz6ddJcIo/eJjbJH/vK eR9envV/vW7TWjME76o/UT9m7xcnij9mTS7OR1/tDQ2OnTqODsUAxNj02FVz 3KtXvtfmv+yz4y/4R/8AaAfw/cS7NP8AEFv5GCcKLiPLxH8R5ifVxX6UV/Pf GOWfU80qJL3Ze8vnv+Nz+heDsz+u5XTbfvR91/Lb8LBRRRXy59SFFFFABRRR QAV+Vv7fPj9NS+KHhf4c2bo8Wj2xv9QK8kTzDEaH0Kxru9xKPSv1D1TU7LRf DOo6xqU62unWNrJc3UzdI441Lux9gATX883xA8YXvj/42eJ/GWob1uNX1CS5 EbHPlITiOPPoiBVHsor9K8M8q9vmEsTJaU1p/ien5X/A/M/E/NvYZfHCxetR 6/4Vr+dvxOPqzZ/8hqx/6+Y//QxVapYJBDfwTEFhHKrlQcZwQcfpX70z8BTP paX/AI+JiSFVSSzMcADPUnsK4HWfHVjZl4NIVNTuhwZmyIEPt3c/TA964TXf EOq69LIbiZUsN2VtYMrGv+8OrH3PFc7XHSwyteR21cW3pEtXt7d6jqct7fTG 4upMbnIA4HAAA6AdhVWiiutHG3c7fwB/yPM3/Xk/81r2DvXj/gD/AJHqb/ry f+a17BXn4n+IelhP4YdqPzoorA6gooxV/StNvNa8Tado+nxGe/vrlLe3jz95 3YKo/M0pSUU29kOMXJqK3Puj9kbwV9k8J6347u4cT37/AGHT2Yc+ShBkYezO FX6xGvsquf8ACnh2z8JfDbRPDVgB9l060SBWC48wgfM5HqzZY+5NdBX81Z7m Tx+OqV+jenotF+B/SuRZasBgadDqlr6vV/iFVb6ytNS0W806/gjurG6geC4g kGVkjdSrKR6EEirVFeUm07o9ZpNWZ/PN8U/A118Nv2g/Ffgq68xhpl8yW0j9 ZYGw8Mh92jZCfQkiuAr9Lv2+vhxlPC3xTsIOR/xKNXKj/ekt3OP+2qlj/wBM x6V+aNf1LwzmyzHLaVe+trP1Wj/z+Z/KvE+UvLcyq0Oid1/heq+7b1QUUUV7 x4AUUUUAFfSn7PXw18V/E2fXdM8MQ2IW3u4mvry8uAkVqrIcMVB3ueOig+5F fNdd18OdfvvD3xQtLnTtQvNKvJh5cF3aTGKWKQcoQw9TkEHg55BrjzCFaeHm qMkpdG1dfddf132O3LqlGGJg6ybjfVJ2f32f9dtz9oPhn+zv4I+H0ttql3Gf FXimPkalfxjZA3/TCLlY+3zctx96qH7XH/KOb4pf9g6L/wBKYa8Z+G37WV7Z m30j4nWbX1uMIuvafBiRfeeAdf8Aej9PuV6f+05r2i+Jv+CYPxK1nw/qljrG lz6ZEYrm0mEiH/SITjI6EZ5B5HevwSrgM1o55hp468r1I2lvH4lt0Xpp6H9A UsflNbI8TDA2janO8dpfC9+r9bv1PxQgnntb1Lm1nltrlD8ksTbWX8f6V6Ro /j4ErBr0WD0F7AnH/A0H81/KvMu9B6Gv6EnTjPc/nanVlB3R9Q6Tqk2n6xpm t6VcgT280d1Z3EZyNysGRh+IBr9lvCfiG08WfDXQ/Ellj7PqNmk4UHPlsR8y H3VsqfcV+Jehf8iLov8A15J/Kv0U/ZH8Y/bvh9rXgm6lzcaXN9rslY8mCU/O oHosnJ/661+U+ImWe2wUcRFa03r6PT87fifrPhzmnscY8PJ6VFp6rX8r/gfY NFFFfih+2hRRRQAUUUUAfHP7bPxC/wCER/ZNPhmzn8rVvFdz9jUKcMLWPDzs PY/u4z7Smvxtr6r/AGxfiF/wnH7Y+q6daT+bo/hmMaVbBW+UyqSbhsevmEp7 iNa+VK/pXgfKfqOU00170/efz2+5W+Z/MvHWbfX83qNP3Ye6vlv+N/kFFFFf Xnx4qkq4ZSQw6EU5ni2M0pWHAyX6KPc+n1Fdx4A+Gfjj4oeJH03wVoU2prEw F5qEreTY2XvLO3yqf9kZY9hX6I/Cz9lPwL4Ee11jxa0HxC8WR4dGurfbplm/ X91bt/rGB/jlz7IK+Yz7i3L8qTjVlzT/AJVq/n2Xr8kz6jh/hHMc2d6UbQ/m e3y7v0+dj8vLuyvrAWRv7K9sVvIBPZNcW7xi6iPSSMsBvXjquRVav3R8WeFv DXjzwlJoHjPQ7DxJpDcrBdpzAf70LjDRMOxQj8RxX5//ABR/Y88Q6J9o1j4V 3dz4v0kZZtCvGVdTgHUiJ+EuQPT5ZPZq8PIvEbAY2Xs8SvZSe13eL/7e0s/V JefQ93PvDjH4GPtMO/ax62VpL/t3W/ybfkfMvgD/AJHqb/ryf+a17Aa8i8Cx TW3xKvrS5hntLy3tZI7i3njaOWFgVyrowBU+xFeu19liPjPjsIrQDsaKD60d qwOgK+qf2UvBX9t/Ge88WXcW6x0GD9wSOGuZQVX67U3n2JU18rd6/WL4GeCv +EH/AGcNDsJ4fJ1S9X7fqIIwwllAIU+6oEQ+6mvjeOc0+qZbKEX71T3V6dfw 0+Z9nwNlf1vMozkvdp+8/Xp+OvyPX6KKK/BT97CiiigDg/if4Gs/iV8A/FPg i9aONdUsWjgmdciCcYeGTHfbIqNjvjFfhh8Svg/8QPhN4jFh4z0Kezhkcra6 jD+8s7rv+7lAwTjnacMO4Ff0F1maxoukeIfDd1o+u6ZY6xpVymy4tLyBZYpB 6FWBFfY8K8YV8mbhy81OTu1s790/6+R8ZxXwbQzlKfNy1Iqye6a7Nf1bzP5u qK/QP9qT9lbwp8PPhzqHxJ8D6jPpmlRXMcd1oVzmVVMsgQGGQncACfuvnvhu gr8/K/fsmznDZnhlXoPTbXRp9v8Ahj+fs6yXE5XiXh8Qtd9HdNd/w66hRRRX qnkhSqzpIskTFJUYMjDswOQfzpKKAPovTr9NU0Cy1GPgXEQdh/dbow/76BrQ M9z/AMItrehpeX1vo+sweRqtpBOUjukDBhuXpuBAIbGRjrivM/h7qO63v9Hk blD9ptwfQ4Dj88H8TXpFeTVppSaa/rdHtUKjlFST/rZnj2s+CNRsA9xppfVb MclQuJ4x7qPvD3X8q4jOQfbg+1fTIODkZFc7rXhjStbDSTRm0vyOLuAAMf8A fHR/x5966aeJe0jkq4PrEu6F/wAiLov/AF5R/wAq9n+DHjL/AIQf9ovw9rE0 vladLN9j1DJwvky/KzH2U7X/AOACvIdPtmsvD9hZSOsr28CxF1GA2O4Bq53r zcZhoYmjOlPaSa+89TA4mphqsKsN4tP7j9wKK8l+B/jH/hNv2bvD+pTS+bqV rH9hvyTlvNiAXcfdl2P/AMCr1qv5jxmFnhq86M94tr7j+nsHioYmhCtDaST+ 8KKKK5jpCvPPix45g+G37Oni3xpMY/M02wZrRH6SXDYSFD7GRkB9ia9Dr82/ 2/PiF5en+EvhhZT/ADSsdX1VVPO0bo4FPsT5zEH+6hr3eGsq/tHMqVBrRu79 Fq/8jweJs2WXZZVxF9UrL1ei/wAz80rm5nvNRuLu6mkuLqeRpJpXOWdmOSxP ckkmoKKK/qZK2h/KbberCj8jz0PIPsfUe1FFMD9DPg5+1n4Qj8NaX4N8e6Fp Pw+itVEVnqWh2nl6Qe2ZYFy1ux7uNynvivt6Ce3u9Mtr6yuba+sLmPzLW6tZ llhnX+8jqSrD3Br8Fa9M+Gvxf8ffCfVTJ4Q1cDSZJN93oV8pm0+59SY8/u2/ 24yrfWvy3iLw2o4hyrYGXLJ6uL+F+j3T+9eh+qcN+JVbDKNHGx54LRSW6Xps 19z9T9ozXJ+LvG/hbwNpK3XifVFtJJF3W1jCvm3dz/uRDnH+02F96+Urr9qb WvFHw4sZ/Cfh2PwlfzqyXt3c3Au2hkU4YWwwAF7h3BYenevBrq5ur/VrjUL+ 6utQ1C4bdPdXUpkllPqzHk/yFfLZN4d4mpLmxr5Euis5P56pL735Lc+szjxG w1OHLgVzt9Wmkvlo2/uXqeg/E/4gxfEnxxbawfC2j6LJao0UF75YfUp4zgbZ 5xjevAwmCF7GvOPeijvX6zg8HRwlGNGirRjstf1PyXGYytiq0q1V3lLd7fkF FBo4rqOY9a+CHgr/AITr9ozQ9Mni87S7R/t2ogjKmGIg7T7MxRP+BV+s9fKX 7KHgr+xvg9feL7qLbfa5Pttyw5W2iJUY9Nz7z7hVNfVtfg3HOafW8ycIv3af u/Pr+OnyP3rgXK/qmWqpJe9U975dPw1+YUUUV8YfZhRRRQAUUUUAfKX7af8A yYH4j/7CFl/6UJX4qV+1f7af/JgfiP8A7CFl/wClCV+Klfv3hh/yKJf43+UT +ffFL/kbx/wL85BRRRX6Mfm4UUUUAaWj6idI8U2OojJSKT96B/FGeGH5HP4V 9DHGcq25DyrDowPQ/lXzP1HPIr27wbqX9o+BYI5G3XFk32eTPUgcofxXj8K5 MVDRSO3Bz1cTqaDRRXEegA6UUfrQaAPrn9knxl/ZvxQ1fwZdTYttXt/tFmrH pPECSAP9qPcT/wBcxX6EV+LXhnXrzwv8QdF8R2B/0rTryO4QZwH2tkqfYjIP sTX7K6TqdnrfhfTtY0+TzrC+to7i3f8AvI6hlP5EV+LeImWexxkcTFaVFr6r /gW/E/afDrM/bYKWGk9YPT0f/Bv+BoUUUV+eH6IFfhh+1hd3N1+3/wDEP7TP JP5NzBFFuP3EW2iwo9AP6n1r9z6/Cn9qj/k//wCJP/X9D/6TQ1+meFiX9p1f 8D/9KifmHiq2sspf41/6TI+faKKK/eD8DCiiigYUV3Ph7whHrXhCe9nuZrOd 5ito4XcuF4YsvcE8ZB7VzuraFqeiThb+3xCxxHcxndE/0bsfY4NQqkW7X1NH SkoqVtD1XwR/yTS0/wCvib/0KurrlfBH/JNLTr/x8Tf+hV1Wa82p8bPVo/Av QKKKKgsO9b3hjw/eeK/iHo3hzTwfteo3aQIduQgY8ufZRlj7A1g9q+xv2R/B X23xrrPjq7izBpsf2OwYjgzyDMjD3WMgfSWvJzzMlgMDUrvdLT1ei/E9bI8t ePx1Ogtm9fRav8D7q0jSrPQ/Cmm6Lp0fk2Fjapb26eiIoUZ98DrWjRRX81yk 5NtvVn9LRiopJLRBRRRUlBRRRQAUUUUAfKf7aKO37AXiUqjMFvrIsQM7R9oQ ZPoMkD8a/FOv6MvGfhbTvG/wp8Q+EdWXOn6tYSWsrbcmPcpAcf7SnDD3Ar+e XxDoWo+F/Hms+G9Xi8jVNLvZbS6TsJI3Ktj1GRwe4r9x8LMdTlg6uG+1GXN8 mkv0/I/CvFXA1I42lifsyjy/NNv8b/mY9FISFUsegGTV+/02/wBMliS/tpLc SoHhc8pICMgqw4PB6da/Urn5VZlGjtRR2pgFdj4H1H7F42W0dsQX6eScngOO UP55H41x1KrOkiyRMUlRgyMOzA5B/OpnHmi0VCXLJM+lqOtU9Ov01TQLPUY+ BcRB2Ufwt0YfgQaufyryWrHtJpq6DvR2oooGH41+kP7KvjH+3fgRceGrmXdf aBc+WgJ5NvKS8Z/BvMX2AWvzer3P9nfxj/wiP7TOkJPL5em6wP7NusngGQjy 2/CQIM9gWr5ji/LPruWVIpe9H3l8v81dH0/CGZ/Uszpyb92Xuv5/5OzP1Ooo or+ej+hgr8Kf2qP+T/8A4k/9f0P/AKTQ1+4Wta5o/hzw1dazr+qWGjaVbJvn u7ydYo4x7sTj8O9fg58f/FOheNf2wvHPifw1eNqGh314htLkxNH5oWGNCwVg CBuU4yASMHAr9R8LKNT6/Vqcr5eW1+l7rS/c/K/FatT+oUqfMubnvbraz1t2 PHqKKK/cz8JYU5I3lnjhjx5kjhEz0yTgfzptWbL/AJDlj/19R/8AoYoGj6Gt LOPTtJtdPhGIraIRD3x1P4nJ/Gp2VZIHjkRJYnGHjdQysPcHrUsv/H1J/vn+ dR9ulePe57dkirZWNppuni0sYRb2wkZ1jBJCljkgZ7Z7dqtUUAEsAAST0Aob BK2gUfyqkupWL682lx3Mct+sZkeKM7vLAx94jgHnp1q7RYE09hyqzyqkas7s cKqjJJPYV+vfwp8Gr4D+A3h/w6yKt9Hbia/I/iuJPmk574J2g+iivz6/Z08F /wDCYftJ6ZPcReZpWij+0LrI+VmQjyl/GQqcdwrV+pNfkniPmnNUp4OL295+ vT8Lv5o/XPDfK7U6mMkt/dXp1/Gy+TCiiivy4/UgooooAKKKKACiiigAr8kv 27Phz/YHx90r4gWMGzTvEtt5V4VXhbuBQpJ7DfH5ZHqUc1+tteFftIfDj/hZ 37I3ifQ7aDz9as4v7S0gBcsbiEFgi+7oXj/4HX1HB+b/ANnZpTqN2jL3Zej/ AMnZ/I+W4yyf+0cqqU0ryj70fVf5q6+Z+DMn/HvJ/umvo6OGG58NWttdQRXN s9rFvilXcrfIO39etfOMn/HvJ/umvpWwjlntdKtbeGa5upoIkhghjLySsUXh VGST9K/pHFuyTP5rwSu2jznWfAJ+e40GTPc2U78/8Ac/yb8682mjkt7+S0uY 3t7uP/WQyLtdfqP61+lHgf8AZ11XURFqXj65m0GxOGXSLVwb2Uekj8rCPYbn +le8eIPg38LfE/w4h8Kar4L0oaTbg/Y5bRfJvLRj1kjuBmTeep3Fge4NfDY3 xFy/CVlSV6ndxtZfPaXy08+h91gvDjMcXRdXSn2Ur3fy6fPXy6n4uUV9V/FP 9k7xt4KS61nwVJcfELwtGC7xwwhdUs0HUyQLxKo/vxZPHKivlJWVgdpzglWH QqR1BHYj0PNfZ5Zm2EzCl7XDVFJfivVbr5nxOZ5Ti8ureyxNNxf4P0ezXoep fD3Ud1tf6PI3KH7Tbj2PDgfjg/ia9Hr560bUW0nxTY6iM7IpP3o/vRnhx+R/ SvoY4yNrBlIyrDuD0P5U8TC0r9ysJO8LdhO/vR60Ud65zqD605HaOdZEdkdS GVlOCpHQg+tNo7UAfsL8MvFyeOfgX4c8S71a5ubULeAfwzp8kox2G5SR7EV5 J+0T+0ZpnwM0PTLODST4g8WarFJJZWjS+XDAikL5sxHzYJOFVR821uVxmvL/ ANkLxjsvvEfgS6l+WRRqNgpP8Q2pMo+o8sgf7LGvgb9pT4hf8LJ/bC8V6zbz +fo9lN/ZmlMGypggJXcp/uu/mSD/AH6/Isp4Np1eIKtCrG9KHveqfwr+uzP1 zN+M6lHh+lXpStVn7vo18T/rujkviT8XvH/xZ8SjUfGmuz30UblrXT4v3dpa 9v3cQ4BxxuOWPcmvNKKK/b8Ph6VCmqdKKjFbJaI/DMRiateo6lWTlJ7tu7Ci iitjEKs2X/Icsf8Ar5j/APQxVarNl/yHLH/r6j/9DFDBbn0lL/x9Sf75/nUf 50+X/j5k/wB8/wA6ZXjo90zdT1jTNItRLqF0kRI+SJfmlf6KOfxOB715ZrXj XUtSV7exDaVYtwQjZmkH+0/b6L+del6v4f0rW483sBS5C4S7h+WVB6Z/iHsf 0rynWvCWqaMHnCjUNPH/AC8wKcoP9teq/Xke9dWHVPrucWJdXpsaHw+AHjmY AAD7E5/8eWvYO9eP/D8g+OJiCCPsL4IPutfSHgDwpceOPjJ4f8LwbwL66VZ3 XrHCvzSv+CBj9cVhj60KSlUm7JK79Eb5bRnV5acFdt2Xqz9Af2YfBX/CM/s9 x63cw+XqfiGQXbEjDCBcrCv0ILOP+ulfSFQWttb2Wm29naRJb2sESxQxIMKi KMKo9gABU9fy9meOnjcXUry3k7/5L5LQ/qTLMBDBYSnQjtFW+fV/N6hRRRXC d4UUUUAFFFFABRRRQAUUUUAfhP8AtQfDf/hW/wC154n0u1g+z6Jqrf2ppW1c KsM5YsgHYJIJEA9FHrX0L+zp8cPg/pumWOgazpVv8PvGjRJbtr99OZ7bUSAF AFw3Nrn/AJ5sAn+0a91/bm+HP/CTfs42Pjmxg36p4Xuc3BUcvZzFUfp12uI2 9l3n1r8hCAVIIBBGCCODX9BZTTo8S5DCnXm04+62nbVd+jurOzXpZ6n885vU rcNZ/OpQgmn7yur6Pt1VndXT/wAj98dpAQ8FXQPGykFXU9GUjgg9iODTec1+ Pfws+PvxC+FDQ2GlXqa/4TD5k8O6s7PbqO5gcfPbt/ufL6qa+w9T/bR+HkPw wh1PR/DniW/8WzAr/YF2FhitXA+9LdLlXjz08tdzei1+b5p4fZtha6hSh7WL 2a/9uT+H1vbzP0rKvEXKcVQc60vZSS1T1/8AAWt/S1/I+vLi6t9P0u51O9vL XTbC0Xzbi9uZ1hht1H8TyMQFHuT9K/MX9pX4lfBfx3r7f8IP4X/tPxYsg+1+ NbcmyguADynlYzd57SuFx1BavGfiR8W/HvxX1dZvGGsmXTYpC9nolmph0+1/ 3Ys/O3+25Zj6ivNq/QeE+Anl1WOJxFRuoukW0l5N6OXpovJn53xbx9/aVN4a hTSp95JNv06R9dX5oOo55Fe3eDdS/tDwLBG7brmzb7PKSeSByh/FePwrxGux 8Daj9i8araO2IL9PJOTwHHKH88j8a/Q8RDmh6H55hp8tT1PZqKKO1eaeqFFH tRQMv2HivWvBMl54m8PXItNXs7KcQSldwXfG0Z4+jHHocGvlkdBX0Hr3/Ii6 1/15P/KvnwdK6sHTinKaWrsr+S2/N/ecONqTajBvRXdvN2v+S+4KKKK7jgCi ijnsGY9gqkk/gKACrVgM6/p49bqL/wBDFVFZXjV0ZWVhkMDkEVd07/kYtO/6 +4v/AEMUnsNbn0fL/wAfMh/2j/Oo6fJ/x8yH/aNMrx0e4B9e9KCQcg4PsaTt RTEZcOi6bbeJH1W1tltbt42jlEXyxuCQclegbjqK+9/2RPBWItf8fXkXLf8A Eu04sO3DzMP/ACGoI9HFfEtnaXN/q1rY2cL3F5czLDBEg5d2IVVHuSQK/Yvw J4VtvBPwh0DwvbbCLC0VJXXpJKfmkf8A4E5Y/jXwfiDmroYBUE/eqaf9urf9 F95974e5Sq+Pddr3aev/AG89v1f3HW0UUV+Hn7iFFFFABRRRQAUUUUAFFFFA BRRRQBl63o+n+IfBuraBq0AudL1KzktLuI/xxyIUYfkTX89HjzwjqHgL4yeJ fBuqBjeaTfyWxcrjzVB+SQD0dCrj2YV/RXX5cft8fDj7D438NfFCwt9tvqUf 9masyrx58aloXPqWjDr9IRX6V4aZv9Xx8sNJ+7UWn+Jbfer/AIH5n4nZP9Yw EcVFe9Tev+F7/c7fifndRRRX70fgNgooooAKVWdJFkjYpKjBkb0YHIP50lFA H0Xp1+mq6BZ6lHwLiIOw/ut0YfmDVyvN/h7qO62v9HkblD9otx7HhwPxwfxN ekV5VSHLJo9ilPngmHeijtRUGpl67/yImtf9eUn8q+fB0FfQeu/8iNrX/XlJ /KvnwdBXbhPhZ52N+JBRRRXWcYV9p/sNfDr/AISr9qa+8bXsHmaR4Rs90JYA q19cKyRjB/uRea3sWSvip3WOF5HO1EUsx9AOTX7kfsofDhvhx+xf4bt7238j X9bB1nVg33lknAKRnv8AJEIkx2KmvieP82+pZTKMX71T3V6P4vw0+aPuPD7K PrubRnJe7T95+q2/HX5M89+Mv7FfgPx415rvgJ4Ph54ukJkdLeHOmXj8n97A MeWxOPnjx3yrV+Y/jT4WePvhP8TNM0nx54eudIaW+jWzv0Pm2V786/6qcDaT gg7Dhxnla/dXxv8AEXwf8O/D39o+K9ZgsN4P2e1X95c3JH8McQ+Zj74wO5Ff n78Wf2ifEHxH0q98O6bplpoHg2f5ZYLmJLi7u1BBBdiCsXIBAT5h/fr5HgbO c9qWhKPPR7ydrej1b9LP1R9jx1kuRQvOMuSt2jrf1WiXrdejPnqX/j6k/wB4 /wA6Z29aPxor9MPzMPWiiigD6V/Zd8Ff8JJ+0B/b11D5mm+HohckkZU3DZWE fUYdx7xiv0srwz9njwV/wh37NmlNcReXqmsf8TG7yPmUOB5aeoxGF47MWr3O v574wzT69mc2n7sfdXy3+93P6F4Pyv6jlkE170vefz2+5WCiiivlz6gKKKKA CiiigAooooAKKKKACiiigAryz41/D6L4ofsyeLPB5RGvrm0Mumu2Bsuo/nhO ewLKFJ/us3rXqdFb4bETw9aNWm7Si016owxWGp4ijOlUV4yTT9GfzVyxSwXM kM0bwzRsVkjdcMrA4IIPQio6+qP2wfhz/wAIH+2DqmoWcBi0TxMn9q2hA+VZ WJFwmfXzMvjsJFr5Xr+r8sx8MbhKeIhtJJ/8D5PQ/krNMBUwWLqYee8G1/k/ mtQoooruOEKKKKANLRtROk+KbHUeSkUn70f3ozw4/I5/CvoY4zlSGU4Kt/eB 5Br5n6jnkV7b4N1L+0PAsCO265sm+zyknkgDKH8V4/CuTFQ0Ujtwc7PlOqoo oriPQMzXP+RH1r/ryk/lXz2Ogr6E1z/kR9a/68pP5V89joK7cJ8LPOxnxIKK KK6zjPT/AINeENO8bftJeGdJ165tbHwrazf2n4hurqQRxR2VuRJIrMem9tkY 9fMr9IPiT+1nI5n0j4W2iiMZQ+INQg4+sEDdfZpMDn7pr85/AGmiLQbvVJky 91II4cj+BDkn8W7/AOzXoHU18pm+SYbH4uNXELmUFZR6X6t93su2mzPrsnzr FYDBypYd8rm7uXW3RJ9Fv567lzUtS1LWfENxq+s6je6xq1wf315eTGSV/bJ6 D0UYA7CqdHr2or0oxUUklZI89tyd3uw9qO1FB6GmIK9I+EvgxvHnx98P+H3j aSwM/n6gewt4/mcE9t2AgPqwrzev0A/ZI8Ff2f4A1jxzdw7bnVJPslixHIgj b52B9Gk4P/XIV4HE+afUMuqVU/eei9X/AJb/ACPf4Yyv6/mNOk17q1fov89v mfYCqqoFUBVAwABgAUtFFfzkf0cFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAF FFFAHyP+2d8OP+E2/ZHutes4PN1rwpKdRhKrlmtiNtyvsNmJD/1xFfi/X9KN 1a299plzZXkMdzaXETRTxSLlZEYEMpHcEEiv59fi54CuPhn+0b4s8FzCQw6f fN9ikfrLbPh4XJ9TGy59Dkdq/bPC7N+ehUwUnrH3l6Pf7nr8z8Q8U8n5K9PG wWkvdfqtvvWnyPOKKKK/WD8kCiiigArsfA2o/YvGq2jtiC/TyTk8CQcofzyP xrjqVWdJFkiYpKjBkb0YHIP51M480WioT5ZJn0tQKp6dfpqnh+z1GPhbiIOw /ut0YfgQa6zwz4U8R+MtfbTfDGlT6rcJjz5AQkFsP70sp+VB9efQGvFrVYUY OdSSilu3ol8z3qFKdaajTTbeyWrfojjdbOPBOs/9eUn8q+eh0FfrN4W/Zz8J 6foNx/wnDjxjfXMDRTWsbPBZQhhg7MEO7Y6OSMHkLXzj8T/2ONc0o3Gr/Ce/ m8UaaMu3h/UJVXUIh1xDKcJcD0Vtrn/arwMv47yepiHQ9pbtJq0X8+nq0l5n u5lwJnFPDqv7O/eKd5L5dfk2+58T1LDBLdXsNrbqWnmkEcYHqTgUt1bXVhrF 1p2oWl3p+o2rmO5tLqFopoGH8LowBU/UV2vgHTvtPiifUnXMVlH+79DK4IH4 hcn8RX3EppQ5j4aFNufKz1W1tYbDS7axgA8m2iEaY74HX8Tk/jU9H40nO8fS vKPYQUveiigYe+KKKKBGnoukXuv+MNM0PTo/Nv7+6jtoF7bnYKM+3PJ7Cv2T 8N6DZeF/AOj+HdOGLPTrRLeIkYLbRgsfcnJPuTXwX+yb4L/tb4saj4xu4t1l osHlWpYcNcSgjI9dse/PoXU1+h9fjPiJmntcXHCxekNX6v8AyX5s/aPDrK/Z YSWKktZ6L0X+b/JBRRRX5yfowUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUU UAFfm/8At9fDnztI8LfFKwgy9uf7J1cqP4GLPA59AG8xST/fQV+kFcR8SPBV l8RfgV4o8FX5RYdWsHhjkcZEMo+aKTH+xIqN/wABr3OG82eXZjSxHROz9Ho/ 8/U8LiXKVmWW1cP1auvVar/L0P53qu6dYy6nrltp8DxRzzsVjaQ4XOCeT26U up6be6P4k1DSNSt3tNRsbmS2uoH+9FIjFXU+4IIrT8J/8lK0b/rv/wCytX9S ua5OZH8rKHv8sjKvtPvdMv8A7LqFtLaz9g44ceqnow+lU6+kLq1tb/T2tL63 hu7dv+Wcq5APqO4PuK801jwBcRu02gu96h5+xyn96P8Acbo/0OD9axp4lPSW htVwso6x1R51RSsrJIyOpV1JDKeoI6ikrpOVnvfwL1f4ax+Ln0f4q6vqmkaC 0olsprcbbcyH7yXEgy8cZwDuUeuSK/WTR7LRtP8ABun2/hqDSrfw4ybrH+y9 ptZBj7ysvDH1JJb1r8IK9U+Gnxm8ffCnU93hfVt+kO4a60W+BmsrjnJzGT8j dfmTBycnNfn/ABjwfXzX95RrNNfZfw+q7Pzd/kfoPBnGVDKX7OtRTT+0viXk +68tPmfsy33OxpSPXkV88/Df9pr4a/EDTlttVvYPAPiVY90tjqtx/o0uBljB cHhuhOxsNgVgeOv2i0iefTPh7aebICVbXNQh+Ue8EB6+zycdwtfj1HhPNqmJ eH9i1Jbt6Jed9n8r36H7LW4uymnhViPbKSeyWsn5W3Xzsu53/wAbfC3wf8Qe CEuvi1DbW90Iium6laNs1gccCAqC0i/7Lho/pX5/6bo+n6FaXFhpU97d2P2q SSG4vIkjuJUJ+UyKhKhgoAwpIra1HUNQ1jXrjVNYv7zVdTnOZru7lMkj+2T0 HsMAdhVTvX7Rw3kdTK8N7KVZzv0+yv8ACunn33sj8W4kzunmmJ9rGioW6/af +J7P7tNrsPek/wCWg+lL3qhqOo2Wk6Y1/qMxtrRCFaTYW+Y/dXjucHGcZr6F anzzdi/39axdT8Q6XpN3FazzGa+kkVFtYcM67iBluyjnvz7V53rXjm/vd9vp avpVoeDJnM8g+vRB7Dn3rj7H/kOWR6k3UZJPJJ3jk+tdUMM95HHUxa2ifSDA rIynkg4yBTakl/4+ZP8AeP8AOvYfgN4K/wCE2/aR0W0uIvN0rT2/tC/BGQUi IKqfUM5RSPQn0ry8Zi4YbDzrT2imz18FhJ4rEQow3k0vvP0H+C3gr/hBP2dt B0iaLytTnj+2aiCMN58oBKn3Vdqf8Ar1Wiiv5lxeJnia86095Nt/M/pvCYWG GoQow2ikl8gooornOgKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig D8df23fhx/wiX7UUXi6xg8vSPFdubhiowq3ce1Jx/wACBjkz3Lt6V8peE/8A kpWjf9d//ZWr9pf2rfhz/wALF/Y71+O0g87W9DH9r6dtHzMYlbzUHc7ojIAv dtvpX4g2l3cWOoxXdpKYLqI5jkABKnBGRnvzX9GcB5t9fyiMJP3qfuv06P7t Pkz+buPco+oZxKcV7tT3l6/aX36+jR71q2t6ZokAbULjZKwzHbxjdLJ9F7D3 OBXlOteMdU1ZXt4CdM09uDFE/wC8cf7b9fwGB9a5V3eSd5ZXeWVzl5HYszH1 JPWm19jToRjq9WfHVcTKe2iCiiitznCiiigAIDAhgGB6gius0XxhqmkqkE5O qaeOBDM/zoP9h+o+hyK5QdaSplFSVmVGbi7pn0FpOt6ZrcG7T590yjMltINs qf8AAe49xkVroju+1FLt1OOw9T6V80I7xzpLE7xSoco6MVZT6gjpX0B4fv7u /wDh9pM15N5sskO6VtoBkbcRubHU4A59q4K9Hk1R6WHxHO7M1yAOjBj3K9Pz 719Ffs3WlnqPjLx9p2pWVlqenXOhQx3NneQLNDOv2j7rowIYfX8K+c6+k/2Y /wDkpfjT/sCw/wDpQK+U4vbWTV2uy/8ASon1vCEVLOaCa3b/APSWYvxR/Y40 rURc6z8JL2HQb7ln8N6lOTZSnri3nOWhPokm5P8AaWvhHVPDniDwh8TIPD3i rRdS8O65BcxmSzvoTG5G8fMp6Oh7MpIPrX7h319Y6VodxqeqX1npem24zPd3 cojiT6se/sMk9ga+OvjJ8XPCnjrww3hTS/C+n+ItPik3Qa3rNsQ9s4Od9mvD xngfOxAP9w18twVxVnFaaoVIOrBbyejj6yekvR+8+59Pxvwnk1CDr05qlUe0 VqpeiWsfX4fI+epf+PmT/fP86/Rr9lXwV/YPwQufFF3Ft1DX590RYcrbRkqn 03MXb3BWvgXwh4cvPGPxR0Pw1ZFvtOo3iwmTbny1Jy8h9lUMx9ga/ZDTNOtN I8O2GlafEILGyt0t7eIdERFCqPwAFaeIuaeyw0MJF6z1fov83+RPhzlftcTP FyWkNF6vf7l+Zdooor8cP2QKKKKACiiigAooooAKKKKACiiigAooooAKKKKA CiiigAooooAQgMpDAEEYIPevwP8A2gfh0fhd+1d4q8MwwGHSGn+2aRxhTazZ dFHqEO6PPrGa/fGvgX9vL4cf2z8HtC+JFhb7r7QJ/smpMq8taTMAjE+iS4AH /TZjX3vh3m/1PNFSk/dq+78/s/5fM+A8Rsn+uZW6sV71L3vl9r/P5H5Q0UUV /Q5/OgUUUUAFFdT4P8D+MPiB4wj0DwT4c1PxLqzYLxWsfyQKTgPNIcJEmeNz kD0zX6U/Bz9hbw9ootdd+MF3B4t1cYdNAtGZdNtz1xK3DXDDjrtTI+6w5rwM 74mwGVQvXn73SK1k/l09XZH0GR8MZhmsrUIe71k9Ir59fRXZ+dfhX4Y+OfGX gDxF4u0PQZ28I6HYT3uo63dHybQJChd44nb/AF0uBwiZ5IyRXAA5UH1r98Pj rZ2em/sE/FGx0+0trCxt/CN5HBb28QjjiUQMAqquAAPQV+B6/wCrX6Vw8J8R 1M5p1asoKKjKyW+lur7/ACR3cXcN08mqUaUZuTlG7e2t7aLsLXu/hT/km2i+ 8B/9DavCK938Kf8AJNdG/wCuB/8AQ2r6TFfCj5vB/G/Q6DHNekfDX4ht8N9U 8SalBpK6xf3+nx2tpHLKY4Y2WTeXkI+YjHQLyT3Feb96K8fGYOjiqMqNZXi9 16O/T0PcweLq4WtGtRdpR2fyt1Ol8VeMPE3jbXFv/E+qy6i8Zzb2yr5dtbD0 iiHyr9eWPc1zVH+TUkUUk9zFBBG8s0jBI0RSWZicAAdya0o0adGmoU4qMVsl okZ1q1StUc6knKT3b1bPsv8AZF8FfaPEeu+PLuEGKzT7Bp7MP+WrANKw9Cqb V+kjV95Vwnwz8Hx+BPgf4e8NBUFzbWwa8Zed87/PIc9xuJA9gK7uv514kzT6 /mFSsn7uy9Ft9+/zP6L4byv+z8up0Wvetd+r3+7b5BRRRXhHuhRRRQAUUUUA FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUNzbW95p89peW8N1azIUmh mQOkikYKsp4II7GpqKabTuDVz4O+Mf7EPhbxOLrXPhdPb+D9cbLtpU2Tp9wf RMZaAk+m5ewVetfmX44+HvjL4ceMX0LxpoN9od+MmLzlzHOoON8cgysi+6k1 /RLXNeK/B3hfxz4Rn0Hxdoen6/pMv3oLuPdtP95W6ow7MpBHrX6Hw/4h43BW p4n95D/yZfPr8/vR+dcQ+HWCxt6mG/dz/wDJX6rp6r7mfzmUV+hnxi/YZ1fS vteu/CO8k13Txl30G+kAuohySIpDhZR6K2G93NfAGo6bqOj65daZq1jeaZqV tIY7i1uoWiliYdVZWAIPsa/acoz3BZnT58NO/ddV6r+kfiWb5DjssqcmJhbs +j9H/TNTwv4u8VeCfFsGveD/ABDqvhvV4iMXFjMV3gHO2RD8siequCPav0g+ Dv7dul34tdC+M9jDoF7gIviXTomNlKcYzPFy0BPdl3Jk/wAAr8vqKxzrhvAZ pC2Ihr0ktJL5/o7ryNsk4lx+VTvh56dYvWL+X6qz8z96/jjqWnax+wF8UNT0 m/stU0258JXr293aTrLDMphb5ldSQw9wa/BJf9Wv0r0PwL478YeGrLU/B2ia /eWfhXxLBJp2saS4EttLHMux3SNsiOYA8SJg565rI1nwfqmjo80QOpaev/Le FTvQf7adR9RkV5vCvD7yWFWjKfMpO6ezta2vn8z0uLOIVnc6VeMHFxVmt1e9 9PL5HK17v4T/AOSa6N/1wP8A6G1eDgggEEEHoQa948KD/i2ujf8AXA/+htX0 uL+FHzWDfvv0Ogorq/CXgjxR468RDTPDGkXWpzjHmug2xQg/xO5+VR9Tz2zX 3V8Nv2XPDnh0W+qeN5IfFGsDDCzAP2KE+hB5l/4Fhf8AZ718lnPEmBy2P72V 5fyrf/gfM+xybhvHZlL91G0f5nov+D8j5C+HfwX8b/Ei5jm0ux/s/RN2JNVv QUgHrs7yH2Xj1Ir7++HHwI8EfDsQ3sVt/bviJME6pfICyN6xJyI/qMt/tGva Ioo4LaOGGNIYY1CpGihVUAYAAHQCn1+P55xjjsxvBPkh2XX1fX8F5H7DkfBu By602uefd9PRdPxfmFFFFfJH1oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUU UUAFFFFABRRRQAUUUUAFFFFABRRRQAV5L8Ufgj8O/i7obW/i7RI21JY9ttq9 piK9t/TbJj5h/suGX2r1qit8Niq2HqKpSk4yXVaHPisLRxNJ060VKL6PVH4s fGP9kX4ifDIXWsaJHJ448IxksbywhP2m2X/ptCMnAHV13L3O3pXydX9LFfK/ xj/ZL+HXxSF1q2mQp4K8Xvlv7R0+EeTct/03h4Dc9WXa3qT0r9a4f8TNqWYL /t9fqv1X3H5HxD4Yb1cvf/bj/R/o/vPxl0H/AJHvRf8Ar9j/AJ19B5YPlSQc 8EVzvjX4FfEb4QfFTR4vFWjPJpLagi22s2OZbOf5uPnx8jH+64VvYjmvrD4b /s1+MPGbQalr6yeE/D74YSXEf+lTr/sRHoCP4mx6gNX3+YZ5gKdCOJlVXI1o 73v6d35HwGW5FmFSvLDKk+dbq1reb7LzPlG4+Hsfi3W4rXRLG5TX7l9sSWMB k89vRo16/VcGvtv4M/si6jB4O0ib4o3S2fkR86Pp8253+Yn95KPujn7q5P8A tA19keB/hp4O+Hmj/ZfDOkx287qFnvpv3lzP/vyHnH+yML6Cu8r8oz3xDxOI TpYT3I938Xy7fi/Q/Wci8OsNh5Kti/fl2Xw/Pv8AgvJmPoegaL4Z8Ow6ToGm Wek6dF9yC2jCrnuT3LHuTknvWxRRX5xOcpycpO7Z+kQhGEVGKskFFFFSUFFF FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU AFFFFABRRRQAUUUUAYHib/kUZP8Ar5t//R8db9FFbS/hL1f6GS/iv0X6hRRR WJqFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB//2Q== name vagrant passwd ******** passwordpolicyoptions PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp bGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr ZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt MDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8 L2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtl eT5wYXNzd29yZExhc3RTZXRUaW1lPC9rZXk+Cgk8ZGF0ZT4yMDE0LTAzLTA2 VDE4OjU0OjQ1WjwvZGF0ZT4KPC9kaWN0Pgo8L3BsaXN0Pgo= realname vagrant shell /bin/bash uid 501 chef-12.14.60/spec/data/mac_users/10.9.shadow.xml000066400000000000000000000014171276456504500211140ustar00rootroot00000000000000 SALTED-SHA512-PBKDF2 entropy EmAakNsXy/i6SAjmOC+w07nYpsGhkEd79oCrIa+2BlRnE25VzCCKb3QVbj2v IPsTNp70t7r6BH2ANZ+0akikrczVSOuzOFGwk0fMqENBp/k6JxRzQ/ifuEP7 RsABfSZK+kl2uqz5QbkVvR7ByiTDCz51ngJAPgL1n+f/WTinY2w= iterations 34482 salt 7pVL5HL9xg3fiUhHgUM5k2JfAGr27IEMCPSafkE5RqE= chef-12.14.60/spec/data/metadata/000077500000000000000000000000001276456504500163135ustar00rootroot00000000000000chef-12.14.60/spec/data/metadata/quick_start/000077500000000000000000000000001276456504500206445ustar00rootroot00000000000000chef-12.14.60/spec/data/metadata/quick_start/metadata.rb000066400000000000000000000007411276456504500227530ustar00rootroot00000000000000maintainer "Opscode, Inc." maintainer_email "cookbooks@opscode.com" license "Apache 2.0" description "Example cookbook for quick_start wiki document" version "0.7" %w{ redhat fedora centos ubuntu debian macosx freebsd openbsd solaris }.each do |os| supports os end attribute "quick_start/deep_thought", :display_name => "Quick Start Deep Thought", :description => "A deep thought", :default => "If a tree falls in the forest..." chef-12.14.60/spec/data/nested.json000066400000000000000000000037501276456504500167150ustar00rootroot00000000000000{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":"test" }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} chef-12.14.60/spec/data/nodes/000077500000000000000000000000001276456504500156435ustar00rootroot00000000000000chef-12.14.60/spec/data/nodes/default.rb000066400000000000000000000004031276456504500176110ustar00rootroot00000000000000## # Nodes should have a unique name ## name "test.example.com-default" ## # Nodes can set arbitrary arguments ## default[:sunshine] = "in" default[:something] = "else" ## # Nodes should have recipes ## run_list "operations-master", "operations-monitoring" chef-12.14.60/spec/data/nodes/test.example.com.rb000066400000000000000000000004211276456504500213530ustar00rootroot00000000000000## # Nodes should have a unique name ## name "test.example.com" ## # Nodes can set arbitrary arguments ## normal[:sunshine] = "in" normal[:something] = "else" ## # Nodes should have recipes ## run_list "operations-master", "operations-monitoring" chef_environment "dev" chef-12.14.60/spec/data/nodes/test.rb000066400000000000000000000004011276456504500171420ustar00rootroot00000000000000## # Nodes should have a unique name ## name "test.example.com-short" ## # Nodes can set arbitrary arguments ## default[:sunshine] = "in" default[:something] = "else" ## # Nodes should have recipes ## run_list "operations-master", "operations-monitoring" chef-12.14.60/spec/data/null_config.rb000066400000000000000000000000561276456504500173600ustar00rootroot00000000000000$__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole" chef-12.14.60/spec/data/object_loader/000077500000000000000000000000001276456504500173275ustar00rootroot00000000000000chef-12.14.60/spec/data/object_loader/environments/000077500000000000000000000000001276456504500220565ustar00rootroot00000000000000chef-12.14.60/spec/data/object_loader/environments/test.json000066400000000000000000000002511276456504500237260ustar00rootroot00000000000000{ /* testing that we support c-style comments */ // testing that we support c++-style comments as well "name": "test", "description": "prod", "run_list": [] } chef-12.14.60/spec/data/object_loader/environments/test.rb000066400000000000000000000000371276456504500233620ustar00rootroot00000000000000name "test" description "prod" chef-12.14.60/spec/data/object_loader/environments/test_json_class.json000066400000000000000000000003161276456504500261460ustar00rootroot00000000000000{ /* testing that we support c-style comments */ // testing that we support c++-style comments as well "name": "test", "json_class": "Chef::Environment", "description": "prod", "run_list": [] } chef-12.14.60/spec/data/object_loader/nodes/000077500000000000000000000000001276456504500204375ustar00rootroot00000000000000chef-12.14.60/spec/data/object_loader/nodes/test.json000066400000000000000000000002511276456504500223070ustar00rootroot00000000000000{ /* testing that we support c-style comments */ // testing that we support c++-style comments as well "name": "test", "environment": "prod", "run_list": [] } chef-12.14.60/spec/data/object_loader/nodes/test.rb000066400000000000000000000000371276456504500217430ustar00rootroot00000000000000name "test" environment "prod" chef-12.14.60/spec/data/object_loader/nodes/test_json_class.json000066400000000000000000000003071276456504500245270ustar00rootroot00000000000000{ /* testing that we support c-style comments */ // testing that we support c++-style comments as well "name": "test", "json_class": "Chef::Node", "environment": "prod", "run_list": [] } chef-12.14.60/spec/data/object_loader/roles/000077500000000000000000000000001276456504500204535ustar00rootroot00000000000000chef-12.14.60/spec/data/object_loader/roles/test.json000066400000000000000000000002511276456504500223230ustar00rootroot00000000000000{ /* testing that we support c-style comments */ // testing that we support c++-style comments as well "name": "test", "description": "prod", "run_list": [] } chef-12.14.60/spec/data/object_loader/roles/test.rb000066400000000000000000000000371276456504500217570ustar00rootroot00000000000000name "test" description "prod" chef-12.14.60/spec/data/object_loader/roles/test_json_class.json000066400000000000000000000003071276456504500245430ustar00rootroot00000000000000{ /* testing that we support c-style comments */ // testing that we support c++-style comments as well "name": "test", "json_class": "Chef::Role", "description": "prod", "run_list": [] } chef-12.14.60/spec/data/old_home_dir/000077500000000000000000000000001276456504500171575ustar00rootroot00000000000000chef-12.14.60/spec/data/old_home_dir/my-dot-emacs000066400000000000000000000000001276456504500213670ustar00rootroot00000000000000chef-12.14.60/spec/data/old_home_dir/my-dot-vim000066400000000000000000000000001276456504500210720ustar00rootroot00000000000000chef-12.14.60/spec/data/partial_one.erb000066400000000000000000000001221276456504500175150ustar00rootroot00000000000000partial one <%= render('test.erb', :cookbook => 'openldap').strip %> calling home chef-12.14.60/spec/data/recipes.tgz000066400000000000000000000004451276456504500167160ustar00rootroot00000000000000‹¯¨ÅTíÖíjƒ0`÷*‚ûoO’“äzb›±R?†¦[/‘16ÇÚ­ lïÑßxLµ-VGDΑF픜ÆÉÛøz"ke 3“ )-«B˜õK+ŠÓýJi|ÛwñòuÏ!4Wî3Ÿ”XºÌµTÛ]ßëtŒ«­„æo¥V¬H¥üµUùçð1ÿóJ+à–ü¥™ògrùç0Ï¿ Ñï}ôÕP/øŒô>,ó•üµ|ÏŸMÊßJç A ÖpÑ?Ï¿ómå¹Ü<…a<ô(©¢J–›ß. ²˜ÿCØÃÒ;›ú¿öÆ(‡þŸÃ×ùïý?5q¡ßÀ·ý_~Îß±EÿÏâNÔïŽh÷Ä 6 (chef-12.14.60/spec/data/recipes/000077500000000000000000000000001276456504500161655ustar00rootroot00000000000000chef-12.14.60/spec/data/recipes/test.rb000066400000000000000000000001431276456504500174670ustar00rootroot00000000000000 file "/etc/nsswitch.conf" do action :create owner "root" group "root" mode 0644 end chef-12.14.60/spec/data/remote_directory_data/000077500000000000000000000000001276456504500211035ustar00rootroot00000000000000chef-12.14.60/spec/data/remote_directory_data/remote_dir_file.txt000066400000000000000000000000711276456504500247720ustar00rootroot00000000000000I'm a file inside a the root remote_directory source dir.chef-12.14.60/spec/data/remote_directory_data/remote_subdirectory/000077500000000000000000000000001276456504500251745ustar00rootroot00000000000000chef-12.14.60/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt000066400000000000000000000000751276456504500316010ustar00rootroot00000000000000I'm a file in a subdirectory inside a remote_directory sourcechef-12.14.60/spec/data/remote_file/000077500000000000000000000000001276456504500170255ustar00rootroot00000000000000chef-12.14.60/spec/data/remote_file/nyan_cat.png000066400000000000000000000355421276456504500213400ustar00rootroot00000000000000‰PNG  IHDRúúŽÍjsRGB®ÎégAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<:àIDATx^í]`ÅúŸ»:ˆ"Ez/I€ä’ÐÄò,XЧ 5Ä*`Å(ˆÒIri—PDT|ŠOß_Ÿú©TÄ†ŠŠï)"HI™Ýÿofî6{{{—»$ÄÜí¬ãÎÞ”o¾üöÛo¾i6⸕TÇe·EGG‰’ÊÊJ)±Ù‚/Icc¢ Ï¡(ji >¯L)%´÷ªR:I¾•ôžHºÝ⦄ ì‰#=„’“Ò˳÷Bƪ2_õæËÂGU– Èî“Öê²»†ÏXyù´Û_y/ÐÏž‰Å¤ô:ý§^:uò‚RÇ. ‰“lAæ•ɤ‚—@°ˆô_bTêd(æ1ºTÏ5{ÙFÒéæh<‚öVôxÞewQªˆ¶}ò5‰gOÎ&»L#%‚BHê» îÝÇ\WΉS§;ú'ÂÉ­#G…÷´—Îúþðä½ô·$~¼„{Õÿ4²£ª.¡ÝÇ>T¨i÷»–o Q»§wù]ZöŸ~#µ{Õÿ.² T](6X z^ÿн«žŸ½lBÇèGI\(ê9qRƒó§Ý¾xò‚þ~_é›&m÷ªÿid Õ¯ÝY‰ñÐènŠÏža¸—§IL‡ýãÎOHyeb) %P]À‚©ÓoŠ { °Š’óÓo²È¤Ñ_]œËr,$ 55H “E°$Ü¥,$šjjzTŠÛVÑ»¡![;5ÅjУc¬#µi5„!Œ%uSÞ—MœTCõFêŸM¶«r¨رqÓ„ “<åzqkþ ï87½}îÅ31ï@ºk@ø² / Ô€8˜£¦çØWßûDHêzÍœÐó•{•e.)ƒjî½Æ­õƒÓÅ¥'NùãDÇ«î#ñä4¾¬â¯Ñî/¾µWhwLë<â~9+Fñ/@ U™˜ÞfØ=½nx¸×ȇ{\ÿPl¿)Üå§VJ †%Ps˜Ã,ƒ^ãÅ—X¯9±×0žjyu5&wÌ$CŸUPU*+’¨iÛ]J\J ¶H ¶ðQË?‚’½È€„»”€…$`¡¦F†~’­¨Š$Ü¥,$ 5µ*ZAæ H¸K XHjjdè'ÙŠªH@Â=Ü%€yԂ½!5ÁMÔ!ÿgTX+Ó—“œ†T¡œ%ÜÃZPêõMkxþt–GJÄWð× ë?¶•™g»YõIk2dÆŽ}ßüyòôÑã'†¤/f‹f䔤:Þʈ ß¶ceLtꔨÄôs.ºcßW‡°Š ´¬ì’©Ë¢&b£ž(‰xˆß?¹¥9Ç>mØ¿ öÍøêûÃbÝÌ ‰‹Hû¿“^cÙOÒŽ7E¼¥ASaϦv&HJïví'.8vÁE“—<ÿïÝïíýòݽÆÒá&¬›ygÏBÍÿzôXëaw‘>åj`ã7¼þØ’[÷‰Ïþk§@öžì~݃Âzù÷ŽÏÄÃC¿ü† ÄÑ‘•p—pïá÷¬oúå÷C¿ùì۟Ο¸¨õEw¶~÷æ7÷:|Ï?Ø÷u‹¿Í’p7QŽR_†¥DO´÷D8"5ÏÌÐŒ%0iØû û©Ò3–°ößW~÷³.¼ýÃ/¾‡òTq1ƒ»ô»ö.D¬Ó÷¦Coÿî§_…Éþ·Û–C»Ëa¦@°>"¬¥üÄBvÈϪ ÿ~ñí_xsOï›æ’„PÎ÷¬nÖ3ÊU„ÀrÍÇ/ãœPŸ4Ë5?ÔwC (Ü%€³}…pns¨(‰˜ôáþÇ–üK „ ’FÌ+.bY H¸K XHjªeUšl¸& w) IÀBM•JNJ@Â]JÀB°PS¥n“p—°,ÔT©Û¤$Ü¥,$ 5Uê6) w) IÀBM•ºMJ@Â]JÀB°PS¥n“p—°,ÔT©Û¤$Ü¥,$ 5Uê6) w) IÀBM•º­Ú$ŽÍ›pn‚ ¾ñ*ßįöS°Xû…h5ï¸ñ¤ãM¤ÓÍ,ŒŸ@â%ÜÃB’ÉP$ÀtyÜøã>¾úÕÇ\¯ L5Ÿ$L°cW³PÊùkÿ5µÖ~¹HýHû7‘.£g-Û(6¦Ä•> é1&<„’p—I î]o¹õ±µ'Oã¸?„£îÏÇy îáðu“ZG~`4º›Âhçaù÷“¨„p˜ΤN;W¢©2‹”@¸J \ù±ƒ%›)%À$ ¥ %`* ' ªœ­kG+¡I¸±"ví‘ÎÀÁÔ€øñŒÐ+­cÈ%²£œÚsðeeZR‰ÆË,a"vp_„vWܳíã¯?ýúÇϾùiùÓ¯Ãó?c®F6Ë aBþæw‘„rνd&+¡6Ì2p—ÐK€Á=~\×kçüyò´Hzñí½8ä,4¸Ç{kç~‘åœw;±^Â=2bÂúýápßåš9Gx}þÍÝ¡Ã}ü|&²£œVîa‰fž3ã;^uìŸþûûÏÿ;š¿ùáž0þ™í@^Êiqé,©Ý¥j¯ÕÖKð6ŒAT1û™R(gªÜ0é™Éæû•Ü)‚*ý§¬z •®Ú_ÆÊ7¦ÚY‘J œi H¸K XHjê™ÖV*^Etjé¯H«LX ("pLúÇh#ì›6 Få+Q;$Àty⤦ÞÑãú‡z\÷ Â&Cn'‰UèÑÖd»$Ü¥B’€X¼7ý‰g´Å{ã.ŒÅ{QI“b’&E{“áIà¨È«¥7gÂÚC™úŠ <ûþT!ÛnV“&‘°¤3¥nÅÒìŒëN—;q áè9²4;¤N#ýf~ÓyX]48ÎľSÕÍguµ·†Ëᬔ©ghg/¡Ýg.}VÓî“æ…ÿÆ6Œ$g ïrù¸CÆv¸auÑ-†6ê;ˆgUTÝÆÊA™£:^T]LŠrné0¤Y‡!¤ÃÄ.d|öáÑ)Õ%d÷0›eÐkÜE“—½ôæ Äí°ÙV)*)Ý”bÓ¢’§|Vï,•j§îq7F9&Ç  ?µ‡ô<:)-Ê‘Ñ3îÆjç$ìŠâa8]gw!ÉS¸‚¯f¸3 …)¾˜ÈŽó%`Ó¼Z=!¢\«’ÔÛÌ _Ãþ·ÿ»ÑyÅ6û {Ôi›]#< >*RŠ,§øÍïQ±çöãþàú«=´çSaÆ4ï=æ¿ÑuQ>jѳW9¶ÛìÚìÿ³ÙûGGÇÄÄÔÅ¿p¹bcÁiT‹8ö7NJcT=ŸP= Ó1©ï‡:9̶DÔýjSº ÛUƒz^ûq½³BJm6JˆFx|T¤Ô²àæ”=ê†Nêq «ÂOí!=?¿ûU(íÆN·Çx žO‘Q„ú¯DÏpRé:^v%ýg²Î GõÃ=Lt¹oÃ+4ôˆ1`7˜¨o@ªÂJ+— Öõ¯\`¶QÈBfÛl™½|âp×¼‰Œ)xpŒëÁ± XÈnGƒIã¿„`k1”€Jóãz4mÌu—“f=lçt'mû{à~¬š°{‘ ¹Ktdˆ–y+ò`ðmŠ9ÃÃÀ•~ì“Å÷Í I»—àËCH1!ßrvL4‰Ž" êí{~žzh“º­z`úåS,<€p½úÅzõÀÓžP» Q$~Z—˜Gi‚ÈR^w à‡1ÆyÃÍþ5êÏϯŸ7>†XB¢›´ò8RaØXñ-SUŘ©®æL°­}RNÒÌc¼;«äÂSïeoË*Ùž°x[vñöUÅ;—ìZ‚°˜…ìÆåÏ=‘À5Ë¢+a1+3¨–2fcœ·íÙ'ß[U²¯pñ7¸[ÓÒãAž~漓ac/ ÜTÑþ æûp&ØÞHÈBB'dA½:÷]?øîñ—ß?aØÁ—({ Êv8é§²3‡îÈ¡»²é;èKÛ”-ÛèËïS²C”?÷r½_xÏì1»;møÈ+†’Öý8¥’îW3§Š•u œ¥üWÿÅKÈQ•w6*»³c;Ý|ôêÇ.fÞÞ¼5–Pó¬a-z3oe¸W &í„FIñ~8*²ÓTÁ·È2ãVã¿B¶¯²‘› &oÛfM¾Ü<¿xWîÉ÷2Ë>€âÌ€èwäÑòè®,ú¯×©³LÉ-¥9¥!¿QrËôQ“‡Î%§„:‹iN±Èå•…EKÝ¿:Küë]K™Rp‚¾ó,ÝédŒ¹ù„ŽÏ…Ž?±uUñG®,™Jlvb‹²!l•,áÈ«h)Û}¸G»·jÖä[Ø0ºJÖtÀ”­ì]A÷¬ -¦o¼BŠ’Ki¥ù’«è£&]ª²F¥EœØéÑþÒ¸À¼XïZ¥à}-ýp)cl÷ ºK˜[¹K¶;•O×¼²|šPîL»·rH¸û»?¿žÐ; Êé6NS‰Âà £^Yk¡DiÌ Uè¼ò¦ÑŠÓظ¿¼ ¡é¼|Á›> Ì6O‰F oïþæ›ÀÆñ{O)¿¤Ï¤¾¡káp÷Âw@¸ó”é;”«·Ðk^¡£Þ¤ËOÒQï‡<å5ÿTûf+I9Ê…ë•%Ç•|UÅCQ^—ªNÞ£$f+}³”›ÞTòx–ŠjQYÕ¬ÁJX÷³²£@ÙžY·–˜ú™4f¼€•‘„ê¨ µÎÄláZ5¯&°ešU`f3TÂþ)g©jT°6˜¹1¸ç—nË£ë~¦Ù e>un½ð>%}€Æ¯TúfÓ¸tò÷l Ï«çjÚ5ïÝr?&zêjNk<¡¸wRZß!Æ ge¾Ê|—z›Š½eŒáç÷q¸ûïªb¼Éw–õYeV›¦OªøŽsû„Q1DÅÃòÐ,M0%Nã÷WÿŒSi°lóZP Þ˜9øòBõ£‚²íùʺà ôºÖ7eÞqE™óráÓêÅÏ*CÖ+w~®À©âdxÕ÷\Mû¯¦½Û2(i—r`þKz^Ò¿cÿþú³°cÿ ܾA)TXw‡˜÷n—Wý¦<~D}⨒…ñ)6à©2†Ÿÿ´tG¦ß®*¦`„µAsÒ 95lAzÝ@’'Gò°kÚ=$#ØÌÔ1·Ýƒì솪Ý=½‹j°Ý_Â0×îkf½@f9è<0E„feÆ 7Ù}ü’Ákwf±¸èîvGGEûή_tý"ZD‘¦LðMGþ‹Æ/§}2•ÔºðWø4™•åöŠònësþ´ûfÃîÎ3™ÉI5)€û¤3²ª6X5ûÌ€ ؉ßÀ™|Ô<‹Ö¡ ¢lÿX°§ñ<Û<%eÐšO×.7f´Î¨ßAŽªz¹•1w5ÂhÙ÷ð¾¦õ›Ú0âÿØ (Æý£®¼i¥ºVeið‚1×$¢7¼N{¯¢‰N¥!}üWÖ{ÆËàâÆÌ§%;2£#2Ù½Ú#e iÜÎî=®%©XÓ ókçk“R]Ì‚; Ï Èå ùJTä5fî ]=Át4Å #ÞÔ¥ñ<Û<%8Þ[ïˆôØîÐîší^­p“ ¶.?z,ûØöû¶7?«y:uêÖ­‹W½ºõ.¾qñ±œc¿/ÿýØÊcHÌàí~ó›49‡ö+P.XGÿ½¸3íÏÌj¯®j˾ÌPÁºøÄ´˜s:òJÜuñÛºöW3ÕÎà<¦Hœîpcÿ@h ¡§ -.'O‚ŽŠ”åY÷wwÂëRp‡†F™Ç9·:Þ‚çæî¾ÆŒ™õjWŽ¥Hù¿Ûÿ¯q½Æ ê4èܾó¢E‹233Wx®åË—çää\zñ¥øÔ¤^“­wm…_ê,¥°2‹éªb滄ãéÈr7ŽfÌèáÎ'Í`–˜=ZЭ(U¬\¹"Ä}‡Žñ“ êCúŒc6O„Þ/Üóø¨ÍaBUBzHÑÝãaà¨È啦”(OW+ÜÑÜQ&€îÍ[HlçsÄØLªfMÐU d̘Y/~ýîl, –·¼u¾y÷ÕÊ«Ó_µñé[çwçr¹òeÊŽ* ãQ!BÜwìØÑ ÷:M"îêQ¢ü^N^Ñ#ÌŽWThë £"¥WJÔ?ˆ‚Â=õâ!¸4*’¡Lo>õLy0°Ä£ínîˆô?IÆèˆ3^ ÑûŸ2÷'eÞÏÊŠS€)›Ü§ÙÀ&Aõµ¯ ‡ à¾lÙ2hY t­/^ŽH»ÝŽ*†ºpáB”† Ö ^-}E}äýSÄLYVI» e_EsÈUVFêÕ~õúàx¾?ÞÚ]×U­œíÎ=†ÊÊSôÂõlž#fËÜÿ üël ŠOд»w½v@ `ááMh߉ÍQƵnÂ:áD9=|”ŒÜ7¥eY´lÓ'XÃJ÷a˜é6KQQXÞa1bÄSO=% XzÝBÚ‘Ù*2DM ƒj€»0üTjä' ̧ÐëÐÐÁ“o+|àÎ'èGUNp7ÚîÌ-¨(«NÑËž§˜nÐßEüCTîž%›)Àm÷¯úÓîBÁ 3!z±:vpߘ¾Q}Š[5˜GYÀCýMžªþ〺7Gýrýk+§k̰aÃ`Àdee‰µˆöjY@»4ÄÏ Ü+mý:žhI 9tˆüðƒ_Òÿzü¸®;aªÝË»ªfŽÈ`ºªÜcÈ´;f’u[LãWÐû¸vŽÜ3z8B£}ûö=‹_ùãò-;ôÝÂï-:ôÃã?ü°ˆ“ûæûú!ÿ̓¯rÁÑ®bxrÅ…O¤)R•µl•*œ†*6ð”PaWÕ }5ÛpOMME£££Í›ÊŸŠ_z÷öUäR»»õœÁ¢UáEñ% Mÿ0pT¤ôI¼‹ÐèÊô6f4cýða‹]³*ºÜ7l0±òGŒp{áØZÕà‘ð3®fKªÙÝZ¾rGa6F…8‰9ñž4vU}µ»pQî½{³à`®®]»Šo‚^¯‹{+j÷ýNôÿvSò%oSò–ŽÞVBˆŠŒú,ÿ¡ä$wnê ‰è÷wp3æ?ÿ!›7“_$……±©©ñ}ØÕ›ýëí&&&¶nÝZ3¦OgYžž…9q3} Û]ÌwÐUeã; ÿ÷k‚©ZžõÔîé+V‹®j¶»¦¡QØñ×_=ÚS^k©o“ůW]u•öY0ý\h-ሄÑR‡´h1‡ãHJZRÒ•’ÎzRB‰ŠŒž,(‰£ä ï—øT*Ø0°dú°”';ULº÷p«äØØæè€­_¿~íÚµp·­[·N ñdÓ¦M&Là.¹hnÕè©\KÚÅþlã`àÎW6Ýòíö$í¾Dõ› ¯-竨;[9¸ ïäêÕ« m4DEóq!¥h w3Co¨¤stö§$UOJ(Q‘Ñ“%…’Á”ü¯êæ0ŠÉùIt´-:š´juîªU«ày€€î6Â…à†<Û&êŒÀyá¾îÁ3|· š¶&9©#G™ðC¿X~*,ì`,Ó=«´,»´8ëô‰U'Š Š·LÛR'¶üâíÚ¶Ã0“6ªêklèm4Pkh£!ª=Ô›šZGz¿{çÎÁI4þÕkJ°?&̰íá#h®ØW*ùR)'-ú…B¾RÈÍ é¥…8ô¤†=Y’2@!𤋮ðžzýñ`ʘö¹>/!}úqínC?µé=÷ÜóÈ#<üðÃøcë¿à^ƒôôtÌKiÓ¦ BqÁ¼iÔ¨‘ÞÖàTW¶JªÜòŽÒõ ¥ÛbuÔ›ØM@l ŸÜËïùHj¡Š©¼0ôÙ„Þ§Uæ8AÝ:{«` éYM1̤U ¬ŒÿjÚ­P»·hÑÂ#’t+éÛ!žM‹”ÉÀq ÓßåäM¤ÄáK€¯þaà¨Hé&>`¥F–{†,üI¯bRÁsÃËA ļyóÄ„}Ÿ HÂC\¡D}ËÈ)ÆØÙfÌ£ÝÅܬ‡Qì 0y·òÐ4¯|cã>3Pöw|JǾ«LÜþÛØ7æ{pæ¥3g›=uØÔ‘ׄ->nÜ8|…"M»’us0ÑÀ% RØ{×]wÝ7ÞxÙð«cZ'’–}l­’H¨ùH™ Í £œ¼£&X÷Á®ÊƨܓVjdI°gÈŸ$“† ¶u´×eaT»=ÖÖ°IÌ ¸ý‡÷≸åËáŠ+®À§›î‡ٽlw÷òŽ€ÃL0Z°f”ël(ïòUÒb¥EyW•ÛôÃ^(Ãw !ó›.Ö÷¼žíº´ƒýôÓO£ƒ!ìrƒ©­M#MÂhñ5hnSí®/J3‡„Õ$ºÂàÌ Ÿ³tÉâuØL‘­è‹“&ÝlPðUÖî¼!†òÍ^ñÅH*&u»ë¾/i°ø‰%7n<¨p\âFý×+¯¼ÒÔ˜ùVÛ‰@ó̈…s0¾Ål^ ʈr?#ûU·e¤ ܯ}µ »&õ_ý]ß':Ök‰ „¸¼{ˆ 8F¿Â¦sàñ]­À¤ ¦¦È6<oÞQB}QâW‘Õá­€MX¿A#¶²*¦‰ÿ»„»Îž æ… é õ£¤Yi–ÎÃ4ÂVi±×1~üø[ø5†_âFÅ=Òî˜a{>¿0Ò.†¢¸ßÝgŠLp1»–r»Þ‰^ÑZU÷¦ye=+}œ»ÏmÓT¼f½zõÒ÷&õÞqà?mÞ¼ùm~½ù曳gÏ­xôÑGEÜŸåc0‡ E=÷Üs(ç­·ÞBxÿý÷‹¢€l1]L…dx—0³~ýL»G׉,¸îw:©ÞýNwÿ5˜BêÝû«”+øT•ôó&t‹I½Êt¸¿ð œ€o^$Ë;¼&c?Ç'ÿP± ذÍÊeÏ)ØÜ4O1 £V°Þ–…¿ÑG~T=üÝÌ]m›¸WFîzF¯ÝBX\û÷ï×ìMIIÂЇ‡Ñ×òñ×EQ˜ñ»wï^­¨K/½T…‰4ÐôZQ¢[¹p¯X7ÛÔ›”P¢"¯1KU;»š9{&é4³jXxÊ–Tlëó‹­~¯¨èºÑÑõ£YhFQ1QèÑÂ^>|8  V¯ÅÅÅip7ŽªÂøðW%%—&¬¤=—Ñë^õÚÐ4ÈòÕ2¾çõÁ'l{NÛ`´;L‹={öœâ×üqÁ€g\ð2£œŒ¢‹".ñ áÍÙ¶mÊ9qâB¼ç¢¨Ñ£Gë×y펾~„j÷ŠÌŒd•T/1§$¨j¾ëŸÀjßßHô9kwa·ÀáO»{-ÞÜ1Ák`›ÊÛ'‹Þø:ƒ»Q ~w'…Ç]É£ß-ø¶íÙåp¬Ý¿ýö[M%_tÑE¢a“&Mò§ÝQ^`ñ½Â%Vè íþé§ŸjE¡á¢¨Q£FIíî†cbIÝÄâ‰Åõyè¡Ý=ŽŠ\^iêse_íÖ¿MðÙ÷xƒŽÏ6hWР‹‡ÊoÐÞÕ Á îv°¸á Ô¯_¿þýûcЬ_ÛÃIQ¯¥Ø×Å‘Koþ7ßÝ3œ¤vÜ1m&_9¸ð îþlw1_ƒ 0²a½@ 7oÞ\p¸ûÓî°é—,Y‚…Kh.¸8…]ŽçsæÌ=\h¸( p÷Õî–3f’Ê¢àéküòëv籨ÂßíØ]œØÍ±P¢ú,GíÇ¢WÿÿGø"“¨Ý úŠ *£çÞK»—ÙPZüÑŽ1ëÙóÁíÑØþˆ}чþqŒ=Ü|4vË1ûÅ£™fÃ’{ïKçw÷ÙxHŲÿU§iæiŠi½ÚÀ¦Ú]?ªªû`Í‘/ÜMµ»è5Óxõ<êáîk»ã L‘Ç{ ®U‘«K—.¢Ë OÔÅ_¬/JŒ6¸ëm÷ˆîª*ÀœŽ<Ѥ²˜ÅÞà…íd…Jr’­''qS‰¨'£-ïtüÑ.ÉŠ=‰F»«öÃ)c&YÊ¢PZüï]í®ã$‹³—©’•*Yå µ<!Aÿ‰L·GÅÚ0_F<'½ßÝl'…-ûÏå{šòÃÞî –eThÌ? ÆÃЧv± B1Ý|ú3f `Å‚Tx™Ð9Á…©rÚ4›k®¹…ÄòSäùLi6Ô0ÚRÆŒ?»æ5œ _|‡¬¢¶¼b’SFr9±JTŸ¥”äP{Áñ„ã­S¸_]½”–p¬MTÑâ¤$·Ôç¨Ú›íœR⢤ß(Së>äQU§wà­à[ê±-µ-K5cf¹1ƒNe&fûð´*;;¿¨h°g'=Ã'N„íîÛUÚ}Á‚ 07"®=z/$ŒxôË}[}ãM7¡Ô%êÅMžËõH¤vUënØ_÷™ÏënàÄn¼¢ö‚Ä©p|#äÄn¸¾6ªÏ,"ZVçé¯TZ΃Æ|Ù®³þ+’‡× å‹Z4n ló_ýHæî's?×Ñ~Òmˆ˜K`∠⸷#G˜õ·a÷weè3êåÏcãR0ìØŽ{,-5hw¨ñìÜÜgrr~ÉÍý='çHnîOx47÷Å'Ÿ|;Ì›'v€Ï!\I†}ô.H€›í\À/hzñÒæ×J`»Ì›÷ÀܹÿY¶ µüæ©ñW§óhaáîùóÏŠH¿;ûîƒ`€Ä½… Ö)Óšå7J(Q‘×; êBᦕêyðǘO-Ê îaU0ìí|•ªÄ¥ ís•÷òa&Æ×þcrï!#¦IÂuƒ3˜°|j>}ì|¦$ÛVÉ´« #šu£ÓYš“£:ªææ"º½  ;ðÙ>ènbªØÉ0FX,btVèx1Aéµ”bû$Q›;TXÓg~>«u‰z³³U—ë›ùó›F&Ü™}‚¯?'vcˆrÐ0íÎC÷РAFE^C–À•z~ Ęo ÜÊÒX ̶³Œ€²K‰³Ô"š0ÌîfǸl/÷Ùb`Í”[–â´™ø•'1õs¹áÎç°®jÑ3#´û³99§rsiNNYnn™yôœœ•Ø8€ ¢ç #ÌbÃŒáÄ…^©€¸¸´É¡€8òâW„lê¥Ó¹";ûãœÔRÊ«C½%N'-,<¹pŒÝ3÷à^˜^*C¡°í®E! ÃËáîï°áfgg‰-ã'Ï`¯Fýl0±e騭{óž¿V¹hÅ`ªgËÒpßÈá®|å,äÑ­!‚)½+è¶b, ׬Y³|Ý)°O°‚ ¿0v‹~.1ò"=,~üš€ðÞ{ï-t¹V:ŸðZØ«Åë-u:•H†»ÁÌ0F½m·yPec¦‚J=åWÕ‚ Îs×â w¯ƒ&uG‘a7`X&wIq>^r.Û,iê^¶ˆÉ÷ôñxéá6f¼»ªÂ˜yÖé<•“£@¹ë"äÑw¡Ý1rÄçoÁY.úš˜í£ïª —åÝwß-~­_¿>Lv1©FLžA^hôË/¿\ëªNžÔÒ”e«Å™láß–)¯ žÛ¼%LXØÌYŽÃ€sîMD(œ‰0]Ú·÷ÀÑœƒ~ŽýÝÅ$3˜øbý.Ì¡ÎÛ£ýxùl± Iï±$J=‚T;àÞôqµéB¥éãœØ;zö¥é“ÊõS¦öVÒ“•[zRC‰ŠŒ,K:n’”‰©J›Ô¦O(g‹zu•£fŒùfaå<¡¶}@™Ð_ÉH䵘“`›"Ýû‡‘½>¾)þÓ^ͧrçµ4ûܦ _Ϻcß ó?yöá[W*»òweg ¯ìÌUvg)o¼¢Q×ü¦¬þMAÈoÔ5GôQÓ‡ú4¥EÿUŸ9òîƒ/%tiÓ«óy“âï¿ï>8±“,o±šNó¬ íŽéb­ZµÂ «xÍœ9S¬|â‰'P#Ö4=ôÐC­;õ$õšÚê6%Û²š"ï°š)}X:òŠÞšÄô%‡ˆžh4 ɼÒ®ÔçWÁ^ >ů>|ÙF[Òe““•.g›Ï™ñQ޶÷ ïVö•nφUÄ3Û¦üN'Ý• Ü#dÊ~»1DùsÏC$0D=YÔ=Ùìr`ÝçÏÍmP×=Ñápè7œÎD _hbmÄTܘF+L#Þ¸w0ÿ¹¼Õ8yí´1™$O‰{]ßñ€æNO¦:òŠúPUá¸RŸ_{ø¿†;³s’ŠÓ’Nßê(éqî„記èèzb!³‹M=lP·ÎV×Ý%œz/³x[VÉöl„ÅÛ2*Ùæä7Ù!júИe»óôûY'¶®:½;oçº9M³¾&.hqá,×otŠôû)ø‹V˜F”†·SÐ0-ÔŽÍÕpˆHkø1‘{®ªAïúDM1TU¸WT©¡|“ïCE%ÙÖ§$«Óû©Óú©wôW;7Œ•[¿N,”®zh“º-°úåS,<€ßˆ( Gy–òÄ>%|±Výaãw//ÜWиÏ<󌘠í†x—<›æ(³ °Xû’¡ìì'wß–#Œs†#hÛ0ƒO)TÜøZ&Á•ÒRÍpOO*ËH¦7Æ}6¸}Á® ;¸Îï;nðàÁž?ÐçÔ°Zøìbc¢çLîš71ÿ1Žq=8a ÙMàh0iD (Ü5oÂ#WÕ¯ëÞ¸S§NX¾$–;V„ÚM0Ñ Ó`ZA÷¤Á¤YO[³î¤Y³×%ÜC1Ö«Öª—à¥ÝÓ“J§¦¨ýÛ,Ñ4ú¤ôqØÐêM,èÄæ-"ĵaÃX©Ž»`>á¦õùdÀ,’:ô›Á÷“‰»iCëæIŽÒIŽ2< Ù½ÒpçI•:»¾lÃ\1c•Õ{=#¥tP»lìm¯‹pì˜qèä­\ _³{S1üO¨ ‹»¸4=û~\;3íc˜™Ï¨nX_ÂÀ“ «„*}EGÇh‡A¢ß€5G¾—Ø=]lùvSàä¹ðky–è¨X;á Kl(hNü'[Û å‰i‘l²kÐ÷wxôè4‡:ã PóKêÝ5!ûv¼ÙfžG”éÕéIê¬uh;÷¬³í$a*@µ£»¦÷÷Áç]®ÝÏÂz˜ëöèsIÔ9¤NKÒ´£›à^lØ’4lÅÖ¤¿éü7應`F¯æýÁXŸìP’[º9r»;VwsUöt¬ãø…ë\œU‚;ÊÉp(cG{9žêæp™ð™äꑲ¦y»1š‹p¸Å,Ú…±1uvРÉýÖé<˜tJÚ]@ê4M“&Mn¾ùfLED^íSjÝ/FãÖ¤ãE¤ýÒ!tB®Ž”¢.Øî†Š…9ŽÜuÛ“Õ…ŠÚ "± D®fÍša·wÆÉw޽kñ¤9Ë/u;ižà¦nW’~·“”ÛHê´rŠœýƒ0Ã|Ì·Õ‘æ(žì(=Ëñ0q\K·Ǩj¢›‰cÜ Ž3¥¨Âc~„léÙF9·:Jop"lFþw>“n&)cI;¶—Qt`Ì`§!á[véÒÕc…ÛÙt‘w²¯|£V,‹ÍÞê¼ÖðÃÑ_5^„¸à?Áéì£-û3Œ`‡JȺí@ØO¨kà A¢"­.|‹À0fDºáÞõ2Vó‘ßJê7¹:uîŒI¿Hɲåeo\¿fæô)å] àŠ\Ø-EÈ^§A`jÞ¯àPg9ÔóKíŽqQŽÛìŽÉÕD±Žã'f:ÔéÕd&¡œ;êxÇŸu3íŽt>“2ì)ÓlPÕ¸gddh]UÑOÅåèC#=°ØI<¶½Í`¥×i"tçyÍÏÆw@œ"V áB¿V¬wfpoWnëñÌ=  ¶ì+FwõOiu‰s8ÊáŽ/pc WL=‘«SûÖ®‚¤ùÖ¬]÷ÜsÏÏš}Ûs&ÂVÉ$eªUìÓ®jªãŸ¦”âx¥¿ãŸ ÷ÇÛÞ;¸·§âd ޽›ú9þ‰*üÕÒs”ƒÒPf”cªù¦ø3·;_ƒ{RRLlî~íµ×b; „¸ZöHZ%Ù ¤[§¾ãÜÂvƒl-ú–‰:¦\qÕl¾%Ò#Ä…M‹Ü³Üã+&pøv»’ÕÕ*©mü Q‘Vj·Â_Ä^¿N¹ëJœHÚ¹švI½êjw‹é…yÆßþζrTžÃŠÿ²Õ…3YqÜD004ÒGqó ±.Z‚Q ˜F†Šüñ`Êú²2ýŒèà®y?Ê?ñÚ]çádÐl·KNk5ëÌÁ¨9ò<7¶.—“¸ä&z¹ä`0@ÇUq#£ê4à¹bõyá¬e²î`Mð‰Ú“'Û: õeOÀ8·Lì:¸ãø$Îar†­Çˆ¨Øº†ŒÌ0ó´Z3¤ú¬”j~%ª“1÷6â³îÿ‚K.õ6“Õ ÌÒ˜Bz]_AvŒJVîx©€]xN: ® ®ŽC¼ê Ä€ÀlžªsÖVMu¢ªö FN³F ¿º}æS^yöÄ ¤ëð@Ù{ÝPgñºz®€U$ð:/‰çB£ë°@ãà¶Š”S–*6kÀ[Æè êý;÷<ÐO³crUõ8õ*Ç*rUÄ!›Ö¹Ó¿‚y,wŽøÀ>Á ¡PK±‘T9V+—«[¡Á 8¤4V‚»•þ®!À:‰%Ü¥,$ 5Õ::L¶ÔŸ$Ü¥,$ 5Uê<) w) IÀBM•ºMJ@Â]JÀB°PS¥n“p—°,ÔT©Û¤$Ü¥,$ 5Uê6) w) IÀBM•ºMJ@Â]JÀB°PS¥n“p—°,ÔT©Û¤$Ü¥,$ 55Bu[têä˜~S@¸‘Í $ ÞÀ†J]F“Ž71ê:ÚÒÛ£ÎÂûL #4 íJœToàmw-nñºÿÝþ䆨~ì`ö“$S H¹„©ll?î´¦Coÿñ¿«üú⻟œ? ïûIÂ]Â=’@ à~Ö…·ïýâûâ’RÐ{~Y„{À/[$!ÀRmÑàþÉW‡~mÿäk wÙULCVƒû¾¯ÆÌû¾‘p—ph¸™¡3[™1s›´Ý!ÞR@$5ÖÓU½C×U=,»ªR»G¦vg×Ätx¯¼cÕè\ Ë§¯`#M9!µr®§HRxVl ÀÝíÒ'5Œ"ÝÇH¬KíÞÚÝ–|+ìéÿ¨˜>ÛŸQL¿òIiòÍ%‡Ÿ¬q6Så>|µ$Wß4’0ÁH‰“܈‡vï=Ñý+n´úN2É…¢jI£þ*6¬Þþ¿JîAÔkOÉ qãçd½øÅw‡?ÿæ§ý„è·¯$ñ`»7<ý•­ã hÓ»0§€©ð^ãnº/OöëNÿÙ×?!:ãÉgP +6ˆÚ#3Md¶*"þœ —½Æf>û–p«ë¯QsòIÜ8À£ª¾?,~Ú»ÿ;øÝÜ{Œ™ºp½o®Ç\¯ @ ÷ð¶n#õåp·rÛ&p¿p/à}/ìúü îc§,ð†»¢ Áü‚-(PÂ]½6J@3só^>ôË‘ï~úí‡ÃG@ß>‚èˆ;3I<ƒ{ãÁ3þ½ós<my÷#·1ÓsÜ-¸ðäûŸYÜüéW„³–m’ÆLmüKGªÂ®L»Ð5%Í`ÓÿjúÐ4ADØ{!Ë3ä ÖS-nuTÊdŒ.p#ÿšÒïÑŸ/hnŒ.a¨ÇX9Ì$á¹pOJÇÐÒÐŒ%Wß™ º`ÒLÁËI>Åòó¦ÐV3úïᙋ¦\ÞØì Ó?¶dÛ3ßM.))ñÕLr°\ÍT‹;š•~oåòŽÊˆ®2y"=a'¹x¯2²Êä‘p¯Ðàþ!Œ™Ò2Ð{É¥Ù¹%$ÜÃTî±ñ†ìª†ð !i-Pi’[Ml*XßIè›b–Aþæw@÷g¾€Yï$i’œ×î'@á-÷¦y7“Ž7“®·H§»fªÈž ó¯–Ü5…BÒ0‡…l©”€\¼áº_BÜKRR’€…š*1) w) IÀBM•ºMJ@Â]JÀB°PS¥n“p—°,ÔT©Û¤$Ü¥,$ 5Uê6) w) IÀBM•ºMJ@Â]JÀB°PS¥n“¨"ÜÙ¡(ªbQ2»”À—@•*ÀÊ1œ"H;>Eª)Z+*À=ÝžœýÅagq¹P² ”«LjD•û á88œtÕéêûOœ:}üÄ©“§Š×ly›Ð²s=kí›-“¨:Ü;˜C)---Såé×>p¯œ0e®š“@åjbpßõš´cƒ6¿½'ÙJí^9yÊ\5$ÊUÃàÞ{bëËïþÏî/ÞÚ¹ÿ?{<œû|‹²ò!†ÒT¨ý¨ÜݹàÁ å‚z#xjƒ%‡V–@*NYúøB+(¼Ú^E¸ËìRá$pâ5¼‰ä¶J@Â]JÀB°PSk¡²‘,Õ°$Ü¥,$ 5µ†‰¬®J@Â]JÀB°PSk¡²‘,Õ¬þO " ?:žIEND®B`‚chef-12.14.60/spec/data/remote_file/nyan_cat.png.gz000066400000000000000000000351401276456504500217510ustar00rootroot00000000000000‹K"Qnyan_cat.png2µ»uPÁ·ˆ<¸[p‡`Á îî.Á \ÜÝÝÝÝ%@°‹kpw.îrq.A7¿÷v뽪ýgkkwªfzºªÏÌ×Ýßé>_Õé(%i4d"d(((4Y µåóîO0ÿžˆñS¶ÿ h5i1¨úi’Ó8«ïŠß¡ ~% ¼šÂÿ«S˜Ë¨)BAyÓBA†þ·qà ”;Ô™ &> ·SUðßûެÄw O£KCDœáç äIo6ÙÀƒäYœ¡[fÙ î!(F“aúÈyYg›  L ßhåmã‚gø*<¡ðèè+†/2²Y GK0Ozhó²–|z+Õ±çõqñdžg¯=Mc×¢MRZ:*y|GŽá×Z0¬¹y1ÒL½­ !špƒÍç8(Hµ¿lOÚ}¾L'h¯<é"ŠJ(49e~¢BB,6RýÕøáhk@F1í8eù¬ >£ãõ·÷Ó†±ÛìL>bÔpé=ÿ[…¿ám°Ú(+ETŠý÷£´±R¸à ÑÆÈ{c3³ôG3Ü>kRó§GÖ‚ÁZ÷yž[ªÕ‘0­~·ÜQÄVÊÓŸh T4ŸƒáÄdΨ`ШA€öt”zE€À3ÝÀXBUžt ¤/cúùýó¾û~:‚Ú¿+Ôg郫 ®®š³4*¬Qi¦;"«+ò<þê=’øSýþ׈µ IÞG‡ ÛÛèc¶»&¿ Û[lôDZÖ€þŒÏI-ä®Ôã {üîoLvšCûRýaói©¤Er3{âM>“gÚžQ!«ñ¶?Ž¡,ãeaÆDA£bØ\EºàMáƒÜHŸ|G´àØô‡¬tÎØ0w™™…›[ž"E²Žz³ Ž™:(ÛÖÖ[#zC+hnXD©/À=l;[bŽY OõÛ†;”2–Æ\}+C1WCüAê¬@±µÆ«zõ—ÓB(R ¾à¡·“Ý;ÊÕ7Þn?ð0'Ç•h'‘èS2Òbà·Ì’òãUíí b™3ï©ÜY’»4Ÿ4 Vè8ÛOà€‡…á2:%<ò¿û£5`ªì® ¢Ï=µ{ s`¶žßÚÖ ðç‚{þ|LR07l‰¢hšY™[–«BÝží+"| >Tì{à°7+´ä ÃiT\2™CŒö †ã2tø~­°çÛz¹qÓzLR‹hªÄi22%cUÚlÊȶʶ^«Nˆ'wÿI ¦†Q›•K…û'×UFÿŸôoÄ~3ê}?ŠCE’i—GÒ‘Á³µýqB·]-#: ´Ièµ<ÐMÉÝ ¸Hùe¥£Ã´‘’®Q”K³9¨£rö€¨hçùîú9]ZnHûzõ\¤5-Ë ¨û k÷õÇH¼ßö«×íýÜ>]x)›eŒåAj)ÿÖx$Òv«¹…‚ëYjåн k§ù¶vDctaKíErÕD1­YI>}”g@$ƶHGߨðÏUø^”Ǽ©§³—A3ÐdaTŒÌKoŠ‘ùšò|<¯dWÙwuÓ™rwZ¹+÷KXX:V¢oîý’)‚W «Ë·[P7∠Æ7À} ”ÐãIì%þlfG5õæ <Œ½~Ä6–^Fº{×­ðü.mmŽ—Õ ¾ØÔ‡÷½Î¹©G’0ö¡­É>9o×ûj²>:$jæÝ®Bô‘< ÿÂ2àý?34(ÞûŽ64—”È`\í@CjãIŸ!â½$/6 ¦ Ú+$=ZIý!G¼~_¸‘Uˆ[LïT2€W5^Í8¼ NB¹"ñí ÷Ìäl, »Íê ÅKÕ<'vzãã¶6ž?¤™µëm­'ÿVÉ#xD|d• øT»@ )wüW‡½·m¤‹'LËE Ñ…¿Ô–½‰¹ÿ™©Fûð¿¹Ä0ötQ¾Ê9JU™†Ôæ9,sŽk†õ9Oa޲íç’«pMp‹,zˆ#Óÿ›úf?¸0€ƒK%}Tæýîûhú«É ‚ ;~ý¦SÆÝo -ÏŒz!cw¯ÝRô%>]ú&£e»|󸽯:Ob€î¹èÕLXîO6a“I¯¾&ëR;©y檑ÏG(Цc™Kû› suæ X›þ…¤`U†Â2a¦Kñq”0´‹«¾Æ¤Š¶ËQüEÿB“Ã@ÓGMQFAcÂJ‡T[c'ÍT„'ÛŽ÷ë ®ûeÔt_ÿbàÓMì,Sœùš¦’Üu—k =µ¨‘ fÓ/ÐîãPwíÍæËý‘·çÑ™ž¿6ï>‰˜'ÖKñLP˜Š½y$á‡:Owÿ e*â™üúŸi™•ìò7X±;þçß-2Þ7—Ýߘ¹ÚG|ìŽý“Hs=~ }E4x~ÛofóI'äáxú½OŠý^‰É}iÿÒ›ÆE}²Ì'æÜ0r#¿H,xy™ùÈîý›—¸›Ïþžp)jôyܯ锹 TfÛ°„õyYþš'ö;¼ÇK–½Uõ&ûiÖØÑKÔ&C0ã[E­7RZýºñÈ8oùºn¼R®Íÿðõ,k™D‹‘ÜHÒÕþ4¡W¦WA‰ó @á9ŽxÏšê"'™XêIUà?TÙ+-ÇßšÛŽð6€P Њ;ÐEižÌ-ÿþ‡“bÉ:]¼oQV-Ã>û䬱‰Ã>ª²)fÉ´’>)»²;•jÊ0y­Öųž£Q_Þ5(7´Rh“K9ˆç/báÏ•ð3ÈœwOø0›¸òJ•¨Þ,ý¼t ¹ò¾Mõ¨.MY¹ýn ŸØ ¨M­²²¶b+Y±úi-€SŽÂ>Fˆyb1©­P¯œÿ¶£‹¸·6u›c! ç9 ‹´ÒÔHŒÏ*á7Ò0Tˆvv8e/Õ ,üLŒ1ÊúÃ÷‘`wŽa‰æ„€=ÇbÖnóßʉÓ}Ov„O1sÙ¸¦ÅÀÈJ5Ã^(N®ðÕºbNK´­;ñJñÑŽ"‹üޏäg`OšãØcãS_Dï™~3´ò Son°lçD cèÉüOzGc²Ð¥'DYlP“¼¸1‰d<øÞöœóæyAòY´É1¤DfT‹f®xšºRýuüH¶ÿ¾ó\#Ôr¬n:Âu%tÔ;ç¦*l;׬Íך°q°~ñѼö"-=:4´8Ü è03ï·ÑˆùöÈ«$g®!T "T¸ÀQ•wÁÏ7Ù*Aºøf«ÞY¸¡âmôâÔÝŒ+&÷“G.RÊËW£t±iç(Uè,\ ãÌ ­f@ù’RH¤÷E“þrD Ÿ¼m”À%_9I5ô¹Ab vÊoDìNófÕ4Š#ý0(¤àÚá8ÈÄuÉa›YéH$Z= Ç”AµüÐ``ÒªúSÌrÃûSi·ï`¥+ #–Á{ÞÿëŠÎéÙýÞP;ŽÔ@¹;2‚}o®Ÿ“n)–BöÇ®t?_|vüj³}’ ;ä]D¦{1Ýš\ÿŒæìK#òǽ€O†L¡Ê3v&Äe8(·?;»+pEÅA;»ôñÏ÷¶Né ¨ó6É{>Tg t?{?ô-ä«11ÐT©ýõ22¥ãl‚ÿ¨åy íDpŒx N½sŸÍÌ4ð ‡'êˆç´Ü~S3]Õ!» bÁV‡ÒάªÇ?Rh¬óSSšŸ4Çïʨ9 V¿²Àsœ`\ËkôÔw¿yÔÀϸРnÌ »ù|8#„‘Ô÷™{¢˜É”ƒ‰³+ý«%ynÛåݶ¾fSà†”·]ÿóÂüþ÷|ÿ£®Ss¶<¤M¹w¤£íê–Ì112ºß»ù!†"Oà›ŒmÓÕþ |z”q÷o§èMœà?EÀ¦SâùT–“‰+u¼&bñTˆ Œø"sL}(D7 7£QÑË‚ ¤%á$t}T*à"'òGKØ¹ÛØsæ‚#(ðóéSxòòÛ·²d°6gEtš› QaIeõǘq¶‰Ü§ÜòuùJ’ÉcëfÊÊ…×Ú’«Z•€ÕJÿ%P 5Ð9È--¬c<_sÜΑ‘I“[]S« YÌß<2[5·ìUûc˜Hþjš#°™À\ä s6Êj¹E²ÕÅ®rWkÊâÍ ïhÑ®]H{*E({ãõ±‰xA£¿ ûÉ×}­ý^±Ö†¦$‚4ZÇ KÅžMÁ^Omðäë„pàÞ$| Á‰ÇÁÆíI=ósÃJU³ÂK_e¯ªóĽjJÄ4ME(ŒÒ¯?‡ia’ò ^~ùWÜsCù,5@pÏ3èôGlÇßÐø™°x‘_nš7ÒõÌYröîf1M”öiÌ»dj¬6üÚ¥m¶Ëú«ìär„ËЮ}¤©u†ß!µ…ä,åŽ]byÑÉò6èeŸþÆ%ל”ÐbV7I•Iæ&®žæÁÏÁ¼cÏ·'öué\éá”íàø°¼Ìí!Èò™úŸä[Ò”Wºš|³UÀ À6g™ö— `œHoþ¬RÞø)’zÔÀ E‰‘n¢Q¡„Í$ ¾æI×mù\ôÂj{oRÑi—oŽ0ê$r”šãÕûQNéÈ2qoé I)qO‘]À.2à!Ú~—z*ZÓ6kgTT²U:¡LžFÄUÅÜ ÝÆKI[ŽÕÖÍ9ºÁÜEïLµa7Pþ¢LšÏ¼ÏA(Æ”#×èt¯2ã5mÌ)놼ôZßyo”Šq0iRèu%ÆŽR%5Èƃ ”Š™élA¢oæ `çá<·„®XªÁÿzsˆ—zûzþQ„ß. ïå¢ÀŠÔ¾5ùÍY[ÓÁ(V©/%¬z!xÈý¡ñbÉ$Ë7vžF3šæ«9,® cD–Q­´UBmÉäלš³¥që™m«ª»T‹ß/§in*]ƒÏ×mÃMÏ|Îâ²ìB LK =«ô† 艜õsùäv›¸M_$§[ÝÀÑP–ÙË@Tyµøž•eТõ`jÒê%Çeë,r‰nG°î’€Ì«Ÿ ÀA2&¬ß•F­¹þáï|"à´¼_ïU¥½*÷ã´)м¿r -­AŠb-\/MŸ¿C¯€'~ǬƒDPQC_G>‡íö`Q¡9>ýEã²¾V½c®œ (;„K‘2'.1Qh¬Æ€mžR/V5xݪ~?}´¬=þEI˜ Æžt@hEÆñÞnsݱµ¸VwÅÌû…áþÉcQcQÓÐ_M÷™ÎÐ9—U$èÞȽƒ}S~µA>a·"ø¼úŒX»` g˜«Éþº—ŸŒñÕB{)ÒÓw´Èá?Ë Ñœ: `BiRvØûl5Ùg\ˆx—ž=ðV6é¾®¼ò:páF×¼Òñk:ÖÞ{%£Åƒv+»1ZiM%$D ¶À7í¨ §(˪œ+"=ÐÙ&ª¹vHw€8ôöÀþÍvõ\ud‹cë' —øé6Pù3?¸9C ¬ÿêe¹š/-Œò{zÈ~W»¶Ñ+[ÓÄ’ScàÁªÊO£‘'³ƒ¶ý8Ãa¢äÜ¿| ÙBØÍ¡ûZ˜&Ï/p¤AãÙr¡×õ– bO1Ssl»À}r¡õ†9³U²,jòPƒÒÛ¦Aµ²è÷ÍðÎ‰îÆµˆÝ=†8µ¿\U›uy¤?êZhå”+W?Å»¼„ª¢sH ÞÎ2+²ÓÀ)íC´?báÕmíïôT ·2ùžnÅJ‚›.ÿŽw"ßw.óZl9IfÝå9ï.œ Ï0Š/3yø×÷Uíýð\‚(¾4”wo|¥·Ép6y’÷6šï5ÿxÏ{nn4ÿ€ÎC ØLìùΠ¡Cä‘ qÈ ÓÆ{+ô—ðÛ«L»aàù´S•÷5¼à£ÅUº|»ž±Ë$ô÷w—ùBx >nûïÞ Ñd:éÛF;øu!jªÕÉEŽ6PÈÅEx5H¨;ƶd<À޽¶ °ìµä;L`— µ´Aâ]ó{Mî·ó°üÕ~Ft—––|™®7v}û¢'‘T®p ¥˜@$ý3ÂVá÷5m¦óZû‰++íð1úÖáÜå½åÇr 7Í™5– ËÍøRÊÙ„PÍ£Ãô’7I˜‡¶•¸Ý—Eà»(³\„ZŒMhÿ¡ÓXqƒ'¸!HCd1 îªÉV^!eåµl™®„Þþ_í@Ø‘ÆáÁ=`½¹î)ÈYgßKøX¥ y+ù\ßeSÖ©eas̱BMœXÌÛ¥äÓP¥T@é÷ª½ì32±C¶‡;2YA#ð·„êëZ÷:•²Íæ4X¨ž¦JgíÖîØ.SÍp.X võÏ!Òß8,9nKLð²¡P¿+×0 ÕùbÙ£üF²¥y¿ žg_ÕðS>¶&°Šx3¿¬>¾´òŠÔxåñ»-×Å|Öîî|øÌÚãtó+×¼—íãmuÜ\frýÒÿ.‡‹/â‘I…UGšü#® ¡ó}fÒ¶¡Þv¢gR¥¿ói¨%Ó…ÿVnÓíf£ó‘L›sikG°\&—„ü`þ!jTTÄÂJ¯z¨›óð8W‹D„Keòµñoc“ “!‚{Fb³°lµqãñËdþön|48u qlöÜKÆ|åäÔö¬?œÈ8>80ð¾){Â],5J©óøFj|Æ\Öð5æ´ÚìJ¸úÇ™7ÞròdwšnÊ$=SHo¶ŽžD³Ö  ÷Ë_D|ÞM ÒYžøs¬9rhÐÛæÝS唩˜¦.™·ÌhßôŸ„9L+O¥WÖfÚœ BØT¥ OéËØc» ­*T[*Îj¢ý˜ÌÔeóÛ3#¥#´u¹õx!@–ÆkŒßœêçyA;wÜ j;8€³¹í¸4tHU+Û¬ºQ²Hg^3ÖÅ.±Ÿó r’·3‰8Íâ ìò mÊ‹I Ș{=æ£è˜C<ñ*íâ0Ëÿ\)H•€xé-ɼ;VEO 9Ž—a¢™‚-U(ï–°Üös©ËAæ”/‡K„ #Ã}RŽ SóeòA‚ÇãÓÓ¬ÌÌ$_DZ t^¾·µW8¨Œ,è! "¬+`oéRbpv·°Å8Ná³ z€7ÿÞÏøýö„t¶ž¬ÿöZɨèŽÕ†ß’ž¢LŽ—‚âÃÑI;»b©á(Ë(F,~’P~ƒÝ0êC ºZ²@ cI—? ðšZR0ŸÏ–üžw<{D¶Ö W4ër/§…ÂÇÇÕïìó©,'6?Ð^Ïí £• g[€Ýîø¢pp(ÈÈ&»8¸¸»ŠÖÛQSª#¿øwó$ø „|Sc7yË,t[ìètå ¦™§<òªª:[¦ÄfCêÊB4È8:øÞ ±uR¢¤5F²¯Šh!¶@öÄW Ûƒ :”ÉŠÙóÙÙþG&ßi:ÌiMX%ï:x’¥’¾Fin"¿"Óav[’ü]\œO“ÃÐ÷BØð¼ëGá„¥ôþ•ÇïŽØÎìQõ‹ÖçΔÖíS̯~ßìä•~s~³cª°C˜l44éûaaó#òÑVWˆÈ54C±Éôó˜6ßZ‰Ï5òõ:ÄxþÃûµ½^À´4¬¶ŒÿcÒY½iþ§EÀ2ÙM_a_{ö¥Ý?c–€Óû›!l‰ßË£PŒñßý[ {ÐÀ: ÿýà¾ÈkcdÊËÑ 9É3åÇTHƒ ÷IÝ¡²Hzü½UÏ@•4 X,£€äAõiÌ®ZXABGœ››)ìÜ?WŸhæé,è"<­ða4•OùY†ÝÖÿ½>rwf'–0ä%—«ÕÃX­2C‹?ëAÙš÷ùpôHl¯?‰¨h•˜œo¥Æƒ7Á?µq¾P¨° Ö<»ðæÏ†bJåWÑד{¼¢SÌ)–·Lˆ6+º¢y…¡üû—yb,…H!qú ö?ž|r×äЊFbY³8´ã~:ÀS.ePKûmŸÆ˜Îȃ~ªbª½TèǽPVT”„-))™Ê/ˆ6Æö†(sxSð©ÃÂ~UUÝø­.å¼_âÒÒÒ¶:€ô8¡Ê•­tóòÈøHœ08ýP6 '=0f5-ö‰{!v‰ÁUzù‹àS&h|­˜zÛÄV`yQêêÍŠq€Ðú'u#»*Ç”ámC·´G]ö7k –ÝÈÐ95Œ¼ˆkŽˆ}9=µù´äio5±¨èØ|æ<ú¨’Š+òŒ' ÆUöÆÏ©tš @ÍÁêêÑI`q¯ÿåúo@[~›ÊÍ…rElZ…Ök‡¥“*ùõáwûý8“imÐ-úå#õeá^rËqM¥€>_;ÊÕÁÙR ÚO. É;'Ê2k$lNqÇÊR×Ç jMe9zÕ¤¹š°äþÔ׆³£DöÄ豿°²®-,üÙ-¶nàjÍš¯À¢ÀÏ`Ì0DØpbÁ Ú³X¨>b/øÝp?§ojvk-_•tÖ5™x¯Ž?#3Þ`­ 3tЂPM 3°åÛù}”&]4Jt›Â†]+úëÞåzë { Oõ™ýÉO .»<ëÊj¡Å„œl=iï^Ÿãé¼~Y®÷%;¹FDÚÉÆË]*ëúUmÆ¿j t,Ïab=j¦OËq¾ݗ†cá é&ã|¥¼ªª½†Í÷~»1Ï|üÜ!ÌFé÷ìté .òûT²y÷‡*AðõzðÉü¼w Ñ(äþÞuRþßf…&æÔße³X™´³¼C¦¡Û*T½y?ÿ€T¶a涘³ª(jO^X°9qõWƶ(ºª‹ý~-t«Bóè[Àw¿OIá©®"Çd)¹&Zm,ì‡í:a °Ízn€*Ž Ù•é4qƒ2_'&u¬Ï yaûð€T±thW>°áýß.-sšG+lR&•çÎü*Û`¦–ÐOê28ñ%a¨Ëën(ô³_ªÌ€¸A‘Ä'?Œ Dظ? #«kq–“p% áÍÚ Õ v¯¶ÛY ®_s˜}aN•G_¹Ñž…Í÷UÞ# ÀQ§FŽÛêbÒJB(‹°Ãæ7ß0¸/ŠÀä 7ö¼¹>šBµUŸCPï–çÚu¹]rºŸ}]² yå_™,"¤)4Ø’Ü®ZH=ôøH%áf ¬–íÙF«?Ôø)á°H­n-+¦¤@¡ååFYØI$èö ²·2È^T{Ð!ìÄn4S}_ENx b­ÛÞr8¨À®×h™znOu«9oV&饶q?hD?èAAy—ʱ éÊ¿ìl É{»_P. ¡¼p©ªSLÄ¡R°¾!Åæ¶vP¿¬g“ãŒ]¢îØž,,hvÛCp[U wö&ÕÄ|N¡%Ù«\k.ƒq»(Ão§Œn†eiÝ€çq˜=l6OQ4ļ8‚•ƒ=)rÜ·¦†®ùÅÌ¿ZQ¢w“‘a.ì=ÃöFhã‘Î"T/šm¥H› ~¿Q´Æo†ÛtüËvAQ‰…¡íp]>D‘hߤpzÊñ]vn ±êúJñÑÊËÿðâ8bna«âˆù©îi'A-íÖ*êô~§jmb,'‹ú†§ó‘ŽÂNÑÈÀe¥%¥ºD6éÓƒ\ÄZ6¿Vâ9ü_ŸôŸ¸Ãÿ!š‡gšú÷²Ï³†·oƒ¬¢  øÀtg|àV”Ñœž&¤ÜËBÉ/8ó“xA£Ë²ÛûaBû°å´ƒ!òs«ëïOÿî¿G9ÍV3xuž÷sÒ’ ›‡æì‰¨’ÿüÅ…þ¸®mKÌóã ShÏa¨€öÃ3ƒºÙÿ A(½ÅÜŽö â¯ÚzSòøàÎÄ;a½Ô¤2=i¤ÝßѺÆÈw£»b´÷^  ä:QãNtÑB™‹ŒÒ}¨üB[mœ·˜aXÝäRæ@-ÑjĆÞJë“ö× zMþÌi×–‚{ÃJ5€‚Ät ^ W+èв²˜$³s+·7+„’ª™7@+£ºJy¶n œ°(®Ù{=Õb}ò@ƒïÉbNªJñ—yM`6œí)W³­Žçxº†ýù]LCåIçÜÜC‰®µ”*‡êÉÜ€¹°ï·B ­†ááao|õÀ`ûµ6 Mmm©PÆÜ¹ÑÃú,ן¼O·K_{Žúéì}”6LZb„|s÷VÈ^úáñ­ ®¿B—@çÆÇ|a½AÞZkýI<Îç´'Öu\\9W"}¾âõé2¾Ç?Ô=û#Mƒ£‚ºÒ¶‡û¼ ®Ïv{[æž×Îô­sWèò»ÉÑog‚™3¾A³õÄ$PEöð  5+Ùfyj½\;`Ô‚zßõiŒ]ç$‹MSho+@ªDF&7Ý4Z'H—ï§¶’E»Å•ª%R²—àps3YÐÐBÔðƒ×IHˆ !>ÚÀÈæÌÅÂør¹Wñz â?Ãc­=[˜0î8»µTØOvoÄnßED#Zl0î}{1®5Þ ”M WEh([]N’w|çÝÎqæâ”º#nrü¤5æk¿.$¿Û{pC… ±7™v‹¢ñ:n>µ¿,€O3˜uƒÃä­1YÛ¿…Æ»Mb X·Ó# KàtNËäñâS»×Ûs÷ã_•ۗ𫆹«S¿TŠŸ™¥/áG‹µ?°>¤Ÿ¾¤T‚О£ÑKŽ·äk*z¯sñçèkwý—s«øï‘~sïN±B›¾\î/Ã%Y–ƒÉ>WòÇ5OX™á¦Í ›#›h›îuô¾òuC‘êê“©$¶MÅßÀn˜OÜÁ³]°pI!i”̵^3©F>ƒµxEyq߸–M.–çSûùOH$¸š±/7^p®´)gxk'ìòG˜œ3Nr“zT%] Ë•ëëëX´P”°æq0„ÁœVw¼øä+ÃËú}ê˜æŒªä4FÝC¤ßÄ‚p”ß?¢Ú¤‡×Ù ¤麷Aöt0a,/3‡>Ù·yƒ0y[˜P`xät ëÉO"H#ƒ³pî¿ÐPÅ8@ø¾Ïœ–‰(&ÃÁžY³|ýaUÎë\9võaC·êlxÙ×ÕFbE€$GܹdhƾµÅ­]fÉêRÎ|¿ˆ‰ä4 ÊRT|úoB÷ëÔÛá »aRÓµŠs˜{¥Ôm‹ƒ•ë9—¹j{Å¥Ûôê窚;u;úg¿X~³Ès›[up„(YÐZ¾\naè­´n´¢ƒĦS ½ohâ®ûõã±ó~©'òþÄFëy?á§ï·¯È[„·Hs6¬˜?ñ Sê®yî°j Éš9wØrè*~ ƪøR 9´÷`ðTÏÀ0ÑÓ:>m€ÕЛ ñ]«o~gJŽXA:ŒÑãr@³¿3Æ“9Ó¹ˆ¬ÌùÏÿŒÌ”7Ù‹DÝkGo£ÂB—Î|Àn3…xXœpžΘï«°_¬bÄû0³p[{TÞ\lnZ9;ûM½ ÑË8€1•=¹x!Ãt¾Ÿ0'sèÜ?ÎÀQȆûÁݳÊæ\ô±Y{ß`Ï–¯È™‰'s }›º­¨¿_ªn´+î¬MwÁïf¢ez^À!&°CÝxah‰°0Ë Ù‡¼ÿ‰×VPÎÄvËÓμ®G3ƒ”YÆ)À¦šì —¾i8ÕCe(˜—.i[SÚÁð•w sç+JªrÙ·ÎÌFD_[a¿‹ßrê ­±–d*Òl·’Ýì”þ¼j¼££/Ý…ø˜×kÂ'fÕ «ŸÆØ¡¥t˜Í\µN<9¾:ýjöB­ “# ¡óiÁ*À_¸Ç-Ê»¹Y3ã<çï]Žíf–_pƒ»y¤ó,ûå´8$n¶ð»”¤‘xz_š·?ÚP¦#Òæø¼Údksl`“|Tó"é—löIb°å(a{^‚=6d Î Ñd*_?}¯%ë) ‡ŽãÁ¥cwú˜´±Þ6hŽ“ªå¥™¯ÂÙÅFII×Á!ë“×}^nnÚê·@ŒÂâcd ªI,h³ÁXØÝ€¡0£ªÀöö–±¢Ào·{Ú¡öge _^ösIÕÆkÄ»i”èX˜?w0bAœp£ÌÏFŒâAE—õ’”Hƒ»à«F¢]Ë +«(–}º“©·²ÅâtµÇç.¿W«ÃvؤÚjb °ýg·Ê—äù`®Ž€2˜a ›é¤ÕŒu¥V·ÙÄШ܌ԋNÀåãÏÊqûEˆÙÿ5;11ðzËçŠÅµC‹?¯àZoµføœÅ?M&Ø?É.Õý¶ÍKżLK)í€9ÿr0çQâp:vì×e}Îμ,¹ªFÎ8yl³øÿÓ îUR ù߬ï'Øz Ϋ•ò½ÜT1[^èºðNÜTmÜ[÷œI3yÙÔâ²Dß”ôãÍf´.””OzTA!ÑÑ]‡ºökß=ÜÅO>7k­ö%ŒØìK‚òxs×AWßS.$dª7ïÊ’O4ÔoŒâ¾>ˆ+¢ÜÞ£Ïi³«ç”;ì<^,©p΂+: ënbd¸ë¤Ëüì :}Þݘ!j˜·JF]<\ÓeíaÌõyÇq~ÌTèBÂõ¤ßÃÒ::|Èx'߆¼!ÌO64;⯑ΓÅç;NÝÉ®šÚÐØ¹ ã­·Uyì:mÜc\¾f? Ìþ؇ٳä|L°«¹ù༒ÉW²CÒD&o3R¿3c9ßY}9maŽ"êÔ½7 E„,x%Ÿk[¶Ç$J?¬“šK‰ U”•E½õµ€žË•wòGŽs]¦÷Dꧤ‰&»û_(,bæhúH 5‹@YjÇKE[{µµMÁÁl™îŽZ¢ê‰U9¿øñ'‘Qeê·°Ï£CæÝ]¬/¥¡ÿ$ÎDsKžx g˜Ksñ´}«+íϨŸWÃV¢­WLç׉óÉt2%G¥²LxR£gé;DøJ{‡V©{‹ö3”7Êä¥9=Eʾ Ò]Å&Åè~£¿ì°€´jxW»v.júètîûñëÍUu[¤¼¥4¥˜Š…ýûÿê²nºMû©dÅ}‘"C«¯-þÃ#·ÔÁÝÔÀø˜ú´žgòÉ¥ËQ©Ð^¯aO{\-gÊê6P4äeåóÅÞÑÿæ‘Ù]æuÚ@im| ×uùyÎìyÙÎÞ<‹Û7‚°Öëø–Ãœ”xÏ ‹÷BЮ!âKtâÏ‹HR²5ãÞýúØ+³ˆÖ±PǤMzsh Œ;jKNä¾xEóÕÙ«ª¢«LLÜŒ+彨t(~žá^ÑM–NZÚRgžÏ„&(ò>OP®HÍ¢1,/o€ÌPh_ˆ9ä 8ç+·<ËÔPªî½Ð¢ÁÏ ù_j<ÉBvPñ+©Hà²~uâR1ÿ•šªúˆB3ÃCÆü¯ëÎ3¥CûíìjËÒê„ʱŸdŸû‹ð‡Ç`ŽóÝž>ò5zu"®Îê Êw<ÄFénO½\=ðüÐ:å4›žÊ×r›4ì9ãTb²…çS-j†³ìw·Ó(ò7ºhÐuV'uãá]ÀŠô¹Qì†ëm¾9òÜÿø7Ð05gæ“ÙëdÑ4éFÉ›ç_û_h²W¿¨ å„‚5v¶î~ŸŠ•îç®Æ x?”¦Œ?¨—?jͧ¤é“z«‰GEÇ2Ëó;$§FUÉå .–+Ó[Åx»¥•XÆþµ4=,jõÍš5“9ø™b’ó…ô‘Ô-ÜÃð¾`4Uáœwü›ëe³Íx¦Ó|åNÄëq[_›x åaa…^Øp1©énÅàØUŒCªºòZ^û®øýã"·ô½êBÄ3W™ä‡fß-8„ÆÕf¡æÎ 6eïª÷Jøö¯fÇ_ÙJwµ(—ñ§fuz¿È?ÕxNåò©×BùîEÑ©P-s ÝÏŠ× [4ö†ð/šÊt%‘[7=ãÂCPü'«zþZ*HJ]b©Û‚ؤö—K÷%Kß>ÏSºŠÕÇãæ³®‚ã?‡ñÅç}ÚtËÛHY ‹^Gûò M¢Þr*Û´ ÆcdÌ#¾Ûo‚ÌMÀ&§ÜA;Ù¿-%uCæ¬Å9:÷<„}Ðw·m)éˆ>ÿ‚^hâ†ÝÉ@ž¤íóh¥mqN”ù&Z<9'IY倹¨=úéš_¿œ#€ä º½Ô»¶pŠ-ƒ¹¬@+|*Œ«É¤Iån¶~œûéü’yØÆšk´GSƒo‰sÌÔÖY×òuÀà´1·nhûŽHÿiÜEIPËK"4'a¬ \„ÜxØÎKÑUåJ»ÂïÅ_êX}cúØ-%:w·¾jáß%mËñbwð»ce{eÆšpñp- z-î QvÈ¢ VYˆ$ÿU|7!µÝj€ÞQ±â[¼#‘¿ŒwR߸ˆ[’ üO¸OJgÉ¿xºôƒ¶(W€ôÌs´À”ó2Þõ_¾^0,Ø«,‘µ[ýøam¨9§<|xBŒ!ŽH%Ô¸*+ØÚ=e‚X¶bóÅõØkjfÄC4ý‹ê®X¯V­‡%À‘LÛîügRˆâEë“”âƒ2&®[”Ë·ñÝx¶@¢9²¶‚PRúËr}0L)ÎßåZCȦËÌL¶þ† 4-É€ÖµEµæÞB½mõ~AyêžgYï~)kŽxcK ½­bïj|7iÎ3ß{ãëÙ¼KjȼŠeªyõ_ ‚ò'Î6ã55,|{°ÿåï3äÐv½G=šµÂRýŠÄž}‚Í ÷{–çÄô¨8þr§¥Oö=4óTCaŠ1›Í8„›ûL´€OOîÀä¥%ƪŸ¯ªIZø¶pmÆCF½Jøçr¹2ëèŸ;²Ýe»Ïl=b1OüF¶áß§[5ò£?š–Â9uÑ6%HØÙ¹æ¥0î ‰~±ê«¢fJ…G¿Þ˜Œ=7Å}}‹M8Ù©÷¹Y7<Þý1kß&¯dÇ-èZÅ)ó‘EÝÿÑËû‹€oÖ/u†0M{ ÊÙÏbg ³HKerív˜ç]J·ïƒÅ‘<€ærP·…ÍÙÆßáªÛÔzŸ!„v€QDдðùd%ß­ o+kF Nû­wÇ »è(¬¦UÉ×N‡Ùƒ×n-ŽEØWþ:j=…nƒíÁC¹ï[›Âûþõ´©sxÝhdåjgxì$û]‹gésmª­?Mu 4„g¯ü,ÏúÀ“²„:9Ÿ)æmhüÝ=VN ìYhè©ÄZ_X/Þu†…6„؇oŸ_íB°¬¥èV\ýˆ #¥„Ð,UØ û¹ÓïHÝÈëq©ôüZtͤBjEpÓéP¨I}ÍÏu,PrÞ(J¸¿ÓªžÎˆÈc×—§?r‚rö6Õ œ%Qj6wÖ|øöÀ8Êè¬9D™³LܱÐìÍ ëÿÔ'Ü\¤$ÿ‹(IƒO‹)£O¿¨Ÿâ[)—V00í½ž2¨?ŠæÿJ«_âçWh],øßyö‚ÿ_¤çÿ?4ãsÓw=;ÈôWmO ˜B¦¦€ÇÙÂê —3”J‰çå8ã_ŸßÈC(™xI U@¡âŠC»”ËÕpÜùéÁàpÓù z?_õ¿0yL¡VG’é­‚³··üçlÄM@oÍëó°€8Wí êEvÀp@(Y”bÇ9¬íêmïe/YmÖ‡Ü_7 1Ô§MŸš²OíçÜÎû˜vŽÿÏ;$5Í8q­—ž†œ‘IÄ—ž$@ Áº#«ˆS?‹Ã%§K¹¥† Jͽ¼ Ù-¾¢)Ä ¬VJ—»[¬ ¹z’5t™/ NT4˜P)âñQþv®¯QóÔ²Gé]âÍÃÓqÇ~ÊÔÊ_’m­ƒÒ}™.ð`O?*3…?o¨*ôÞÊÂØÜNÑDÔ:ñ¨ ûþ¦ÖÞæJfï—‰JÙŒŒp¿š{crhÁ&|Vú:Vl+[òÎÈS»õÞ+þ¾h§fçWï3`¶­õŽtÁÜ,tߘ¦[ÊðyÊ·ŸßuìÄõ#{ÇQy"Ú„ÐDh¥‹âB3*ÁÃè>?‰1ÁÒw­I¶O\¨ÁìRö¸»p¢Zñþ×HØ5%1/5þ¯cxá¸Qµõÿ÷&µpïäʨ_PEøó¡þ]²’Jõb&ÁÿA\F?b;chef-12.14.60/spec/data/run_context/000077500000000000000000000000001276456504500171035ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/000077500000000000000000000000001276456504500210745ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/000077500000000000000000000000001276456504500235275ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/attributes/000077500000000000000000000000001276456504500257155ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb000066400000000000000000000001321276456504500276620ustar00rootroot00000000000000normal_unless[:attr_load_order] = [] normal[:attr_load_order] << "circular-dep1::default" chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/definitions/000077500000000000000000000000001276456504500260425ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb000066400000000000000000000000641276456504500317550ustar00rootroot00000000000000LibraryLoadOrder.record('circular-dep1-definition') chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/libraries/000077500000000000000000000000001276456504500255035ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb000066400000000000000000000000521276456504500265730ustar00rootroot00000000000000LibraryLoadOrder.record("circular-dep1") chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/metadata.rb000066400000000000000000000000551276456504500256340ustar00rootroot00000000000000name "circular-dep1" depends "circular-dep2" chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/providers/000077500000000000000000000000001276456504500255445ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb000066400000000000000000000000621276456504500277210ustar00rootroot00000000000000LibraryLoadOrder.record('circular-dep1-provider') chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/recipes/000077500000000000000000000000001276456504500251615ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb000066400000000000000000000000001276456504500271200ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/resources/000077500000000000000000000000001276456504500255415ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb000066400000000000000000000000621276456504500277130ustar00rootroot00000000000000LibraryLoadOrder.record('circular-dep1-resource') chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/000077500000000000000000000000001276456504500235305ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/attributes/000077500000000000000000000000001276456504500257165ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb000066400000000000000000000001321276456504500276630ustar00rootroot00000000000000normal_unless[:attr_load_order] = [] normal[:attr_load_order] << "circular-dep2::default" chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/definitions/000077500000000000000000000000001276456504500260435ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb000066400000000000000000000000641276456504500317570ustar00rootroot00000000000000LibraryLoadOrder.record('circular-dep2-definition') chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/libraries/000077500000000000000000000000001276456504500255045ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb000066400000000000000000000000521276456504500265740ustar00rootroot00000000000000LibraryLoadOrder.record("circular-dep2") chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/metadata.rb000066400000000000000000000000551276456504500256350ustar00rootroot00000000000000name "circular-dep2" depends "circular-dep1" chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/providers/000077500000000000000000000000001276456504500255455ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb000066400000000000000000000000621276456504500277220ustar00rootroot00000000000000LibraryLoadOrder.record('circular-dep2-provider') chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/recipes/000077500000000000000000000000001276456504500251625ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb000066400000000000000000000000001276456504500271210ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/resources/000077500000000000000000000000001276456504500255425ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb000066400000000000000000000000621276456504500277140ustar00rootroot00000000000000LibraryLoadOrder.record('circular-dep2-resource') chef-12.14.60/spec/data/run_context/cookbooks/dependency1/000077500000000000000000000000001276456504500232735ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency1/attributes/000077500000000000000000000000001276456504500254615ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb000066400000000000000000000001311276456504500275710ustar00rootroot00000000000000normal_unless[:attr_load_order] = [] normal[:attr_load_order] << "dependency1::aa_first" chef-12.14.60/spec/data/run_context/cookbooks/dependency1/attributes/default.rb000066400000000000000000000001301276456504500274240ustar00rootroot00000000000000normal_unless[:attr_load_order] = [] normal[:attr_load_order] << "dependency1::default" chef-12.14.60/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb000066400000000000000000000001301276456504500274660ustar00rootroot00000000000000normal_unless[:attr_load_order] = [] normal[:attr_load_order] << "dependency1::zz_last" chef-12.14.60/spec/data/run_context/cookbooks/dependency1/definitions/000077500000000000000000000000001276456504500256065ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb000066400000000000000000000000621276456504500312010ustar00rootroot00000000000000LibraryLoadOrder.record('dependency1-definition') chef-12.14.60/spec/data/run_context/cookbooks/dependency1/libraries/000077500000000000000000000000001276456504500252475ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb000066400000000000000000000000501276456504500263350ustar00rootroot00000000000000LibraryLoadOrder.record("dependency1") chef-12.14.60/spec/data/run_context/cookbooks/dependency1/providers/000077500000000000000000000000001276456504500253105ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency1/providers/provider.rb000066400000000000000000000000601276456504500274630ustar00rootroot00000000000000LibraryLoadOrder.record('dependency1-provider') chef-12.14.60/spec/data/run_context/cookbooks/dependency1/recipes/000077500000000000000000000000001276456504500247255ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency1/recipes/default.rb000066400000000000000000000000001276456504500266640ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency1/resources/000077500000000000000000000000001276456504500253055ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency1/resources/resource.rb000066400000000000000000000000601276456504500274550ustar00rootroot00000000000000LibraryLoadOrder.record('dependency1-resource') chef-12.14.60/spec/data/run_context/cookbooks/dependency2/000077500000000000000000000000001276456504500232745ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency2/attributes/000077500000000000000000000000001276456504500254625ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency2/attributes/default.rb000066400000000000000000000001301276456504500274250ustar00rootroot00000000000000normal_unless[:attr_load_order] = [] normal[:attr_load_order] << "dependency2::default" chef-12.14.60/spec/data/run_context/cookbooks/dependency2/definitions/000077500000000000000000000000001276456504500256075ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb000066400000000000000000000000621276456504500312030ustar00rootroot00000000000000LibraryLoadOrder.record('dependency2-definition') chef-12.14.60/spec/data/run_context/cookbooks/dependency2/libraries/000077500000000000000000000000001276456504500252505ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb000066400000000000000000000000501276456504500263360ustar00rootroot00000000000000LibraryLoadOrder.record("dependency2") chef-12.14.60/spec/data/run_context/cookbooks/dependency2/providers/000077500000000000000000000000001276456504500253115ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency2/providers/provider.rb000066400000000000000000000000601276456504500274640ustar00rootroot00000000000000LibraryLoadOrder.record('dependency2-provider') chef-12.14.60/spec/data/run_context/cookbooks/dependency2/recipes/000077500000000000000000000000001276456504500247265ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency2/recipes/default.rb000066400000000000000000000000001276456504500266650ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency2/resources/000077500000000000000000000000001276456504500253065ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/dependency2/resources/resource.rb000066400000000000000000000000601276456504500274560ustar00rootroot00000000000000LibraryLoadOrder.record('dependency2-resource') chef-12.14.60/spec/data/run_context/cookbooks/include/000077500000000000000000000000001276456504500225175ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/include/recipes/000077500000000000000000000000001276456504500241515ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/include/recipes/default.rb000066400000000000000000000010211276456504500261140ustar00rootroot00000000000000module ::RanResources def self.resources @resources ||= [] end end class RunContextCustomResource < Chef::Resource action :create do ruby_block '4' do block { RanResources.resources << 4 } end recipe_eval do ruby_block '1' do block { RanResources.resources << 1 } end include_recipe 'include::includee' ruby_block '3' do block { RanResources.resources << 3 } end end ruby_block '5' do block { RanResources.resources << 5 } end end end chef-12.14.60/spec/data/run_context/cookbooks/include/recipes/includee.rb000066400000000000000000000000761276456504500262710ustar00rootroot00000000000000ruby_block '2' do block { RanResources.resources << 2 } end chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/000077500000000000000000000000001276456504500241025ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/attributes/000077500000000000000000000000001276456504500262705ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb000066400000000000000000000001331276456504500301200ustar00rootroot00000000000000normal_unless[:attr_load_order] = [] normal[:attr_load_order] << "no-default-attr::server" chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/definitions/000077500000000000000000000000001276456504500264155ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb000066400000000000000000000000661276456504500327050ustar00rootroot00000000000000LibraryLoadOrder.record('no-default-attr-definition') chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/providers/000077500000000000000000000000001276456504500261175ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb000066400000000000000000000000641276456504500302760ustar00rootroot00000000000000LibraryLoadOrder.record('no-default-attr-provider') chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/recipes/000077500000000000000000000000001276456504500255345ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb000066400000000000000000000000001276456504500274730ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/resources/000077500000000000000000000000001276456504500261145ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb000066400000000000000000000000641276456504500302700ustar00rootroot00000000000000LibraryLoadOrder.record('no-default-attr-resource') chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/000077500000000000000000000000001276456504500255575ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/000077500000000000000000000000001276456504500277455ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb000066400000000000000000000001441276456504500317150ustar00rootroot00000000000000normal_unless[:attr_load_order] = [] normal[:attr_load_order] << "test-with-circular-deps::default" chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/000077500000000000000000000000001276456504500300725ustar00rootroot00000000000000test_with-circular-deps_res.rb000066400000000000000000000000761276456504500357610ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/definitionsLibraryLoadOrder.record('test-with-circular-deps-definition') chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/000077500000000000000000000000001276456504500275335ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb000066400000000000000000000000641276456504500306260ustar00rootroot00000000000000LibraryLoadOrder.record("test-with-circular-deps") chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb000066400000000000000000000000671276456504500276670ustar00rootroot00000000000000name "test-with-circular-deps" depends "circular-dep1" chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/providers/000077500000000000000000000000001276456504500275745ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb000066400000000000000000000000741276456504500317540ustar00rootroot00000000000000LibraryLoadOrder.record('test-with-circular-deps-provider') chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/000077500000000000000000000000001276456504500272115ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb000066400000000000000000000000001276456504500311500ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/resources/000077500000000000000000000000001276456504500275715ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb000066400000000000000000000000741276456504500317460ustar00rootroot00000000000000LibraryLoadOrder.record('test-with-circular-deps-resource') chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/000077500000000000000000000000001276456504500237555ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/attributes/000077500000000000000000000000001276456504500261435ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb000066400000000000000000000001331276456504500301110ustar00rootroot00000000000000normal_unless[:attr_load_order] = [] normal[:attr_load_order] << "test-with-deps::default" chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/definitions/000077500000000000000000000000001276456504500262705ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb000066400000000000000000000000651276456504500324320ustar00rootroot00000000000000LibraryLoadOrder.record('test-with-deps-definition') chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/libraries/000077500000000000000000000000001276456504500257315ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb000066400000000000000000000000521276456504500270210ustar00rootroot00000000000000LibraryLoadOrder.record("test-with-deps") chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/metadata.rb000066400000000000000000000001021276456504500260530ustar00rootroot00000000000000name "test-with-deps" depends "dependency1" depends "dependency2" chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/providers/000077500000000000000000000000001276456504500257725ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb000066400000000000000000000000631276456504500301500ustar00rootroot00000000000000LibraryLoadOrder.record('test-with-deps-provider') chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/recipes/000077500000000000000000000000001276456504500254075ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb000066400000000000000000000000001276456504500273460ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb000066400000000000000000000000001276456504500272300ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/resources/000077500000000000000000000000001276456504500257675ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb000066400000000000000000000000631276456504500301420ustar00rootroot00000000000000LibraryLoadOrder.record('test-with-deps-resource') chef-12.14.60/spec/data/run_context/cookbooks/test/000077500000000000000000000000001276456504500220535ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test/attributes/000077500000000000000000000000001276456504500242415ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test/attributes/default.rb000066400000000000000000000000001276456504500262000ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test/attributes/george.rb000066400000000000000000000000401276456504500260300ustar00rootroot00000000000000default[:george] = "washington" chef-12.14.60/spec/data/run_context/cookbooks/test/definitions/000077500000000000000000000000001276456504500243665ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test/definitions/new_animals.rb000066400000000000000000000002341276456504500272070ustar00rootroot00000000000000define :new_dog, :is_cute => true do dog "#{params[:name]}" do cute params[:is_cute] end end define :new_badger do badger "#{params[:name]}" end chef-12.14.60/spec/data/run_context/cookbooks/test/definitions/new_cat.rb000066400000000000000000000001621276456504500263320ustar00rootroot00000000000000define :new_cat, :is_pretty => true do cat "#{params[:name]}" do pretty_kitty params[:is_pretty] end end chef-12.14.60/spec/data/run_context/cookbooks/test/definitions/test_res.rb000066400000000000000000000000531276456504500265410ustar00rootroot00000000000000LibraryLoadOrder.record('test-definition') chef-12.14.60/spec/data/run_context/cookbooks/test/providers/000077500000000000000000000000001276456504500240705ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test/providers/provider.rb000066400000000000000000000000511276456504500262430ustar00rootroot00000000000000LibraryLoadOrder.record('test-provider') chef-12.14.60/spec/data/run_context/cookbooks/test/recipes/000077500000000000000000000000001276456504500235055ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test/recipes/default.rb000066400000000000000000000000541276456504500254550ustar00rootroot00000000000000 cat "einstein" do pretty_kitty true end chef-12.14.60/spec/data/run_context/cookbooks/test/recipes/one.rb000066400000000000000000000001301276456504500246050ustar00rootroot00000000000000cat "loulou" do pretty_kitty true end new_cat "birthday" do pretty_kitty false end chef-12.14.60/spec/data/run_context/cookbooks/test/recipes/two.rb000066400000000000000000000001321276456504500246370ustar00rootroot00000000000000cat "peanut" do pretty_kitty true end new_cat "fat peanut" do pretty_kitty false end chef-12.14.60/spec/data/run_context/cookbooks/test/resources/000077500000000000000000000000001276456504500240655ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/cookbooks/test/resources/resource.rb000066400000000000000000000000511276456504500262350ustar00rootroot00000000000000LibraryLoadOrder.record('test-resource') chef-12.14.60/spec/data/run_context/nodes/000077500000000000000000000000001276456504500202135ustar00rootroot00000000000000chef-12.14.60/spec/data/run_context/nodes/run_context.rb000066400000000000000000000001411276456504500231040ustar00rootroot00000000000000## # Nodes should have a unique name ## name "compile" run_list "test", "test::one", "test::two" chef-12.14.60/spec/data/search_queries_to_transform.txt000066400000000000000000000054371276456504500231040ustar00rootroot00000000000000afield:[* TO *] content:afield__=__* afield:[a TO *] content:[afield__=__a TO afield__=__\ufff0] afield:[* TO b] content:[afield__=__ TO afield__=__b] *:* *:* role:mon content:role__=__mon role:mon AND role:prod content:role__=__mon AND content:role__=__prod run_list:role\[rubberband\] AND run_list:role\[whale\] content:run_list__=__role\[rubberband\] AND content:run_list__=__role\[whale\] sharable_server:[* TO *] content:sharable_server__=__* run_list:role\[nfs_server\] AND sharable_server:[* TO *] content:run_list__=__role\[nfs_server\] AND content:sharable_server__=__* run_list:role\[nfs_server\] AND sharable_server:[* TO *] content:run_list__=__role\[nfs_server\] AND content:sharable_server__=__* (role:prod AND x_y:true) (content:role__=__prod AND content:x_y__=__true) hostname:[* TO *] AND role:prod content:hostname__=__* AND content:role__=__prod role:t_mem AND role:prod NOT hostname:ip-1-2-3-4 content:role__=__t_mem AND content:role__=__prod NOT content:hostname__=__ip-1-2-3-4 ohai_time:[1234.567 TO *] content:[ohai_time__=__1234.567 TO ohai_time__=__\ufff0] ohai_time:{1234.567 TO *} content:{ohai_time__=__1234.567 TO ohai_time__=__\ufff0} ohai_time:[* TO baz] content:[ohai_time__=__ TO ohai_time__=__baz] ohai_time:{* TO baz} content:{ohai_time__=__ TO ohai_time__=__baz} tags:apples*.for.eating.com content:tags__=__apples*.for.eating.com role:safe AND ohai_time:[1234.567 TO *] AND whiz_bang:x5 content:role__=__safe AND content:[ohai_time__=__1234.567 TO ohai_time__=__\ufff0] AND content:whiz_bang__=__x5 role:safe AND ohai_time:[* TO 1234.567] AND whiz_bang:x5 content:role__=__safe AND content:[ohai_time__=__ TO ohai_time__=__1234.567] AND content:whiz_bang__=__x5 animal:[ape TO zebra] content:[animal__=__ape TO animal__=__zebra] animal:{ape TO zebra} content:{animal__=__ape TO animal__=__zebra} ((value:[1 TO 3] OR nested_b1_a2_a3:B1_A2_A3-c) OR value:[5 TO *]) ((content:[value__=__1 TO value__=__3] OR content:nested_b1_a2_a3__=__B1_A2_A3-c) OR content:[value__=__5 TO value__=__\ufff0]) ((value:{1 TO 3} OR value:{1 TO 3}) OR run_list:recipe\[alpha\]) ((content:{value__=__1 TO value__=__3} OR content:{value__=__1 TO value__=__3}) OR content:run_list__=__recipe\[alpha\]) words:"one two three" content:"words__=__one two three" words:"one \"two\" three" content:"words__=__one \"two\" three" words:"\"one two\" three" content:"words__=__\"one two\" three" words:"one two \"three\"" content:"words__=__one two \"three\"" words:"one two \"three\"" OR words:"\"one two\" three" AND words:"one \"two\" three" content:"words__=__one two \"three\"" OR content:"words__=__\"one two\" three" AND content:"words__=__one \"two\" three" words:\"* content:words__=__\"* -version:0.9.12 -content:version__=__0.9.12 !version:0.9.12 NOT content:version__=__0.9.12 ec2:* content:ec2__=__* chef-12.14.60/spec/data/shef-config.rb000066400000000000000000000017051276456504500172530ustar00rootroot00000000000000Ohai::Config[:disabled_plugins] << 'darwin::system_profiler' << 'darwin::kernel' << 'darwin::ssh_host_key' << 'network_listeners' Ohai::Config[:disabled_plugins] << "virtualization" << "darwin::virtualization" Ohai::Config[:disabled_plugins] << 'darwin::uptime' << 'darwin::filesystem' << 'dmi' << 'lanuages' << 'perl' << 'python' << 'java' Ohai::Config[:disabled_plugins] << "linux::block_device" << "linux::kernel" << "linux::ssh_host_key" << "linux::virtualization" Ohai::Config[:disabled_plugins] << "linux::cpu" << "linux::memory" << "ec2" << "rackspace" << "eucalyptus" << "ip_scopes" Ohai::Config[:disabled_plugins] << "solaris2::cpu" << "solaris2::dmi" << "solaris2::filesystem" << "solaris2::kernel" Ohai::Config[:disabled_plugins] << "solaris2::virtualization" << "solaris2::zpools" Ohai::Config[:disabled_plugins] << 'c' << 'php' << 'mono' << 'groovy' << 'lua' << 'erlang' Ohai::Config[:disabled_plugins] << "kernel" << "linux::filesystem" << "ruby" chef-12.14.60/spec/data/ssl/000077500000000000000000000000001276456504500153345ustar00rootroot00000000000000chef-12.14.60/spec/data/ssl/5e707473.0000066400000000000000000000020561276456504500164250ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC6DCCAlGgAwIBAgIJANlevg7kzqvpMA0GCSqGSIb3DQEBBQUAMFcxITAfBgNV BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEeMBwGA1UECxMVU25ha2VvaWwg Q2VydGlmaWNhdGVzMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMDkxMjE5MTkxODUy WhcNMTAwMTE4MTkxODUyWjBXMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0 eSBMdGQxHjAcBgNVBAsTFVNuYWtlb2lsIENlcnRpZmljYXRlczESMBAGA1UEAxMJ bG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDhm9En1DL3aC4H j5/SA6FXm6B/0AoVzoPfWX2rpkRcz/XX24JEhQhLXStjhDr4p/IrARnZ8shy0MA4 wNpNPEn5c0RvqKypHzX+AeQkBx8J1/8vnMAoM9b/4pd0FqgRW1UbhvqQDzkWmVyK Tz5yCiTntxDzudAtHlTo8V6E7UEDkwIDAQABo4G7MIG4MB0GA1UdDgQWBBTmAcyA CqQblJ1L4sOIzmkdIAtY6jCBiAYDVR0jBIGAMH6AFOYBzIAKpBuUnUviw4jOaR0g C1jqoVukWTBXMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxHjAc BgNVBAsTFVNuYWtlb2lsIENlcnRpZmljYXRlczESMBAGA1UEAxMJbG9jYWxob3N0 ggkA2V6+DuTOq+kwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBe4f9R s0g5GCFekabzl9AHvIn4ITxenvuyaNX9f2BJbdgoD03wlGycBxjbC57RjFVfetu7 mtUYuJSx7iojBSC+LzotGptrG9d2BxrWOKBfF2K+dyoIG8kZL5aLfS0be6Cc5O3c L/IPadJhBu/EfyGI2vL1l8GspXdOxaFzHprpgA== -----END CERTIFICATE----- chef-12.14.60/spec/data/ssl/chef-rspec.cert000066400000000000000000000031571276456504500202400ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEkjCCA3qgAwIBAgIJAKBJr4wSRUVvMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYD VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTEN MAsGA1UEChMEQ2hlZjETMBEGA1UECxMKZGV2ZWxvcGVyczESMBAGA1UEAxMJa2Fs bGlzdGVjMR4wHAYJKoZIhvcNAQkBFg9kYW5Ab3BzY29kZS5jb20wHhcNMTAwNDEw MTkxMTMxWhcNMjAwNDA3MTkxMTMxWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgT Cldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxDTALBgNVBAoTBENoZWYxEzAR BgNVBAsTCmRldmVsb3BlcnMxEjAQBgNVBAMTCWthbGxpc3RlYzEeMBwGCSqGSIb3 DQEJARYPZGFuQG9wc2NvZGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAw5l9EtBHsJrb5AIxARP695an3v+509gOXRKnjWIRnU+knbdTnEdjlGGG SxuFR7Fnp2OM8ed7iPIKSrcM0vQ+g7vYKCv5Z8UR3sbLY8UHm9AgZ/bLAHEHS2if 1WHPD5DOe1B7HwW0IfEiW4/WakkVn4uoWw5rCZ87f4YCrETomXIo1n/rMFHf+yoY guuEfGQxRcQdlEZM9YMlMByQvXlVR5IVhpiMHBCyV6KzxjZVCgTlvS8nPMiiHpoO pgB6BGEQ/nn4Kapk40baPqpT4EP/DnBnbhhR3kBQ6MQRlh7bl5vjH5xFSFwGUUA9 IcaDTwfliD27bo36aMvcBhrsmbSwqwIDAQABo4H0MIHxMB0GA1UdDgQWBBS88Zxt vG+FTu1U+VFA47ffzwStbjCBwQYDVR0jBIG5MIG2gBS88ZxtvG+FTu1U+VFA47ff zwStbqGBkqSBjzCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x EDAOBgNVBAcTB1NlYXR0bGUxDTALBgNVBAoTBENoZWYxEzARBgNVBAsTCmRldmVs b3BlcnMxEjAQBgNVBAMTCWthbGxpc3RlYzEeMBwGCSqGSIb3DQEJARYPZGFuQG9w c2NvZGUuY29tggkAoEmvjBJFRW8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUF AAOCAQEAwwMrbuJAhP5uawJi5OYEaJKSbJGyahCcOAl4+ONgsdDoCy/9AZKzuFNc C8vM/Ee6jyugrKMdckvZ883kJ4770HU6nbomCUVKMHMzJBE1Guvsn8wZP3nKyeSZ eXXbH1b/NfstNyo6XLucaBRQvyvQYDUnk6osrBh+Gekvqsahr0wkVa8VUY2UySyY 60lYt4O92XJ1jWtYoFjRxeeUgo5E0TfIWj74kXhdMqwMf4Iv9VatfYR87ps5VMdf Hp+nrCRaquDAs87LdO9e7M8l+W1ryPfP2inuGjIozsN5lLmwBdT+O6NkpmuxGPEG ArIbYatR7+4MsDn+CjfkYblnmGLuug== -----END CERTIFICATE----- chef-12.14.60/spec/data/ssl/chef-rspec.key000066400000000000000000000032131276456504500200640ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAw5l9EtBHsJrb5AIxARP695an3v+509gOXRKnjWIRnU+knbdT nEdjlGGGSxuFR7Fnp2OM8ed7iPIKSrcM0vQ+g7vYKCv5Z8UR3sbLY8UHm9AgZ/bL AHEHS2if1WHPD5DOe1B7HwW0IfEiW4/WakkVn4uoWw5rCZ87f4YCrETomXIo1n/r MFHf+yoYguuEfGQxRcQdlEZM9YMlMByQvXlVR5IVhpiMHBCyV6KzxjZVCgTlvS8n PMiiHpoOpgB6BGEQ/nn4Kapk40baPqpT4EP/DnBnbhhR3kBQ6MQRlh7bl5vjH5xF SFwGUUA9IcaDTwfliD27bo36aMvcBhrsmbSwqwIDAQABAoIBAQC+hddKaA4se+sL 4QaSoj+mwtypXjZHnv/+sJj8IjY+IMGbzmJmqzLX6VbB+gCMoMTySwmS54NxFTHp LPwUz0vFTUdzecHpzg9mDAU5HUYYA1ZNbhq2R2JvlW16j1b9NnOpse77fLbFCPgK b8TOqnmheot2hkjEipGN2Z7o5gYaz1/3PtolkP1ypCTG6Bh7V3ohBLBIEdjA552o HNGe3t6PpvoNtBqaeb/j/SAOvg+8DGF1WQtE+5Y1koSlhABYWkHzHC1fHAzRMSHH ZMfKOQNusRgBRNJabdVqkuTbvyRCQEb2YGQxPPYV2C+AxAlh3APeYTg90vUqAq/3 ivNdilcBAoGBAOLELc0mcTftDbIMWVnrzAGAJOCMz3FkwGcV8nqNeA3R77e3pWL2 5+bKadWQGjjpR3ZEYt/RxHsoGCW3NtM44icxqVCTPW/unp2xqadjuvcsKrxk+1wD OdvVrwcd/N+KzgXO+Hm7xbV/loFms3ueGfCRbOueQyP4dj9MyOBGlO2hAoGBANzQ u8IrZBG0DL8YFdmjw4YWUENIOtABPU1qHo/sugTQjI9K3/E3LA7aaGnl2P//1tao SR/aP/To90H6D989/JomhkEKKA+DyL1sRL1NMdtWwrKdEq32W8fUN0JEA+Q1FMsd Hk6Ix+KrZVg9cTb9HoGikDxeHW3pPKDWaEkWIQLLAoGAD13N4L3/JBQLPop5r487 9soRNao1EHEMXK/vC4D0prMYNHHcYjVrB4el3lPygvLD5e7CaHpVfyb7Y+rjazLK mG9UEuK3YhNgaj00yuQGMmOqzbNmGRka3ZvATZIppZhJV7lruwwPXLo1n7Uu6myP Q28HW3wQ/qoCkU2JuzDtPKECgYBUrYcTEuixEUbCEU5vw6k7RltJMe27zn3frg5C Sxmatw7v9Fqkee/fUkowMgBhS47rimVgXaWhGaWYG3jytyajRpq9XlO2f2b/nQFP RscTwdWwASQkqhDQNMVsGAEWBnUO3v+8Rh/BANFAYW+FEtQcCmcdf0nx2DtzwkUD ogTOuQKBgCbEg+/ND/p8xKwY9LtjLKnrQSL5tSH/7prhLJvVVdW7FMRfKSp1t2xc kfJFqO1Lcf2j7hiclval3xDoWUretNQ5379T0Ob30WuIomSfeqcxJjCUtyN3fUqr z/QG9dk/23OOYJhRgAmttBDqpk5uB5mOQgSftdELNyw0EOyNIBfZ -----END RSA PRIVATE KEY----- chef-12.14.60/spec/data/ssl/key.pem000066400000000000000000000015671276456504500166400ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDhm9En1DL3aC4Hj5/SA6FXm6B/0AoVzoPfWX2rpkRcz/XX24JE hQhLXStjhDr4p/IrARnZ8shy0MA4wNpNPEn5c0RvqKypHzX+AeQkBx8J1/8vnMAo M9b/4pd0FqgRW1UbhvqQDzkWmVyKTz5yCiTntxDzudAtHlTo8V6E7UEDkwIDAQAB AoGAGicC1tgdVFqqQ0wd3a14DXzH3SkTkjWPSdvI2pX6hLvCptQWRLUbIglZ1z5j y6FETEHjakVfgRe7wJhyddOQS3eeVt/aK0xBHz/JiJuIF+NzbJT9t01nPV21abYU lWIhWV8Ja39a5LKV6hee0TTYdAub7BVQ95kwrqMqRcDoXHECQQDxpAgq925Cmlz1 0Q1WZq2A/o8oqPvPS1FulPK2OgyOyQSK+DdcK2xUKGWMn0m9fDLLzj/pe/H3dN1I b8Z/iiWrAkEA7wPlesZX3GzfqQLd6GYGBa4IdrV5dHdeoCCVRnkFr06KjcqpAhg1 7i0T9frSC5EfRCfbGNgo4eutT9+D7HJhuQJAZeDBrNPbQetxDBbSp73sovkwhHUS jah0scnMtvWse7rW1nymYo7QQn8xqWMzJNerVvAjVB50ut8juLmfmAA3twJAQy9/ NBHI5Mcd365Unlz/WF1hN60vZNOhH7XJADRIqsyTGeRbuaEAl+DH+Z71qBa1CT2C 0usAIvFSmF8mADLu0QJAHSSh6zLNInvkhDjYAmEu3oeFQgQ4Rp7oiMaBZ6VVuOMo 4GU9CA18iI75NaO7FOfquJPkIJ0li0xadVofUpaJcg== -----END RSA PRIVATE KEY----- chef-12.14.60/spec/data/ssl/private_key.pem000066400000000000000000000032171276456504500203640ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh 8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD 2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/ /RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4 DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8 OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12 2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ== -----END RSA PRIVATE KEY----- chef-12.14.60/spec/data/ssl/private_key_with_whitespace.pem000066400000000000000000000032241276456504500236310ustar00rootroot00000000000000 -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh 8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD 2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/ /RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4 DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8 OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12 2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ== -----END RSA PRIVATE KEY----- chef-12.14.60/spec/data/standalone_cookbook/000077500000000000000000000000001276456504500205515ustar00rootroot00000000000000chef-12.14.60/spec/data/standalone_cookbook/Gemfile000066400000000000000000000000361276456504500220430ustar00rootroot00000000000000source "https://rubygems.org/"chef-12.14.60/spec/data/standalone_cookbook/chefignore000066400000000000000000000003011276456504500225770ustar00rootroot00000000000000# # The ignore file allows you to skip files in cookbooks with the same name that appear # later in the search path. # recipes/ignoreme.rb # comments can be indented ignored vendor/bundle/* chef-12.14.60/spec/data/standalone_cookbook/recipes/000077500000000000000000000000001276456504500222035ustar00rootroot00000000000000chef-12.14.60/spec/data/standalone_cookbook/recipes/default.rb000066400000000000000000000000321276456504500241470ustar00rootroot00000000000000# # Nothing ot see here # chef-12.14.60/spec/data/standalone_cookbook/vendor/000077500000000000000000000000001276456504500220465ustar00rootroot00000000000000chef-12.14.60/spec/data/standalone_cookbook/vendor/bundle/000077500000000000000000000000001276456504500233175ustar00rootroot00000000000000chef-12.14.60/spec/data/standalone_cookbook/vendor/bundle/ruby/000077500000000000000000000000001276456504500243005ustar00rootroot00000000000000chef-12.14.60/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/000077500000000000000000000000001276456504500247355ustar00rootroot00000000000000chef-12.14.60/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/000077500000000000000000000000001276456504500256705ustar00rootroot00000000000000chef-12.14.60/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/000077500000000000000000000000001276456504500305165ustar00rootroot00000000000000chef-12.14.60/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib/000077500000000000000000000000001276456504500312645ustar00rootroot00000000000000multi_json.rb000066400000000000000000000000331276456504500337110ustar00rootroot00000000000000chef-12.14.60/spec/data/standalone_cookbook/vendor/bundle/ruby/2.0.0/gems/multi_json-1.9.0/lib# This is a dummy ruby filechef-12.14.60/spec/data/templates/000077500000000000000000000000001276456504500165315ustar00rootroot00000000000000chef-12.14.60/spec/data/templates/seattle.txt000066400000000000000000000001101276456504500207230ustar00rootroot00000000000000Seattle is a great town. Next time you visit, you should buy me a beer.chef-12.14.60/spec/data/trusted_certs/000077500000000000000000000000001276456504500174255ustar00rootroot00000000000000chef-12.14.60/spec/data/trusted_certs/example.crt000066400000000000000000000024221276456504500215720ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDkjCCAnoCCQDihI8kxGYTFTANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC VVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMRAwDgYDVQQKEwdZb3VD b3JwMRMwEQYDVQQLEwpPcGVyYXRpb25zMRYwFAYDVQQDEw1leGFtcGxlLmxvY2Fs MR0wGwYJKoZIhvcNAQkBFg5tZUBleGFtcGxlLmNvbTAeFw0xMzEwMTcxODAxMzVa Fw0yMzEwMTUxODAxMzVaMIGKMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAO BgNVBAcTB1NlYXR0bGUxEDAOBgNVBAoTB1lvdUNvcnAxEzARBgNVBAsTCk9wZXJh dGlvbnMxFjAUBgNVBAMTDWV4YW1wbGUubG9jYWwxHTAbBgkqhkiG9w0BCQEWDm1l QGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyKBo U+Bdni0xZK/NCzdLdi2X+TyW5eahbYMx+r1GDcVqCICvrthBCVLVFsQ8rvOHwTPi AxQJGxb9TLSXRgXQSlH6FLjIUceuOtpan3qYVJ1v7AxY4DgNvYBpbtJz5MQedJnT g2F+rXzkwaD6CWBqWHeGU0oP3r7bq1AMD6XEsK2w2/zHtG7TEnL45ARv1PsyrU5M ZAW/XyoMyq1k2Lpv7YR5kAvTq1+4RSt/it2RFE7R0AVbaQ0MeAnllfySiHHHlaOT FVd/qPSiGISxsUmmzA3Z08+0sfJwkrnJXbLscCBYndd7gMGgtczGjJtul0Ch3GFa /Pn5McjwF272+usJ1wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQCzPePWifWNECsG nL8on1AtFMkczE1/pdRS4YUl/Tc926MpezptSja8rL31+4Bom37/wYPG7HygtAQl R4FHpAtuqJKPOfjUmDNsIXRFnytrnflTpctDu/Nbj4PDCy01k/sTDUQt+s+lEBL8 M8ArmfLZ8PCfAwnXmJQ5rggDFKqegjt6z1RsSglbMiASE7+KkpBnzaqH6fET6IQz WgAjv6WdRfwgfJjOTSX4XMpCSet9KaWmXExKrxiVng2Uu6E+ShVAyKaGMuc1B7VA oxnnVaVapFv5lOWucQr4KkC7EgaUZnyt8duOc8+Yvd+y3Xd2dcHUnmegRxly4jRV /lXbFAUb -----END CERTIFICATE----- chef-12.14.60/spec/data/trusted_certs/intermediate.pem000066400000000000000000000031531276456504500226040ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEjzCCA3egAwIBAgIQBp4dt3/PHfupevXlyaJANzANBgkqhkiG9w0BAQUFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEgxCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxIjAgBgNVBAMTGURpZ2lDZXJ0IFNlY3Vy ZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7V+Qh qdWbYDd+jqFhf4HiGsJ1ZNmRUAvkNkQkbjDSm3on+sJqrmpwCTi5IArIZRBKiKwx 8tyS8mOhXYBjWYCSIxzm73ZKUDXJ2HE4ue3w5kKu0zgmeTD5IpTG26Y/QXiQ2N5c fml9+JAVOtChoL76srIZodgr0c6/a91Jq6OS/rWryME+7gEA2KlEuEJziMNh9atK gygK0tRJ+mqxzd9XLJTl4sqDX7e6YlwvaKXwwLn9K9HpH9gaYhW9/z2m98vv5ttl LyU47PvmIGZYljQZ0hXOIdMkzNkUb9j+Vcfnb7YPGoxJvinyulqagSY3JG/XSBJs Lln1nBi72fZo4t9FAgMBAAGjggFaMIIBVjASBgNVHRMBAf8ECDAGAQH/AgEAMA4G A1UdDwEB/wQEAwIBhjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6 Ly9vY3NwLmRpZ2ljZXJ0LmNvbTB7BgNVHR8EdDByMDegNaAzhjFodHRwOi8vY3Js My5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMDegNaAzhjFo dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3Js MD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5k aWdpY2VydC5jb20vQ1BTMB0GA1UdDgQWBBSQcds363PI79zVHhK2NLorWqCmkjAf BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTANBgkqhkiG9w0BAQUFAAOC AQEAMM7RlVEArgYLoQ4CwBestn+PIPZAdXQczHixpE/q9NDEnaLegQcmH0CIUfAf z7dMQJnQ9DxxmHOIlywZ126Ej6QfnFog41FcsMWemWpPyGn3EP9OrRnZyVizM64M 2ZYpnnGycGOjtpkWQh1l8/egHn3F1GUUsmKE1GxcCAzYbJMrtHZZitF//wPYwl24 LyLWOPD2nGt9RuuZdPfrSg6ppgTre87wXGuYMVqYQOtpxAX0IKjKCDplbDgV9Vws slXkLGtB8L5cRspKKaBIXiDSRf8F3jSvcEuBOeLKB1d8tjHcISnivpcOd5AUUUDh v+PMGxmcJcqnBrJT3yOyzxIZow== -----END CERTIFICATE----- chef-12.14.60/spec/data/trusted_certs/opscode.pem000066400000000000000000000066051276456504500215730ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83 nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f /ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0 /RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6 Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1 oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl 5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA 8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC 2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0 j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFDTCCA/WgAwIBAgIQBZ8R1sZP2Lbc8x554UUQ2DANBgkqhkiG9w0BAQsFADBN MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTQxMTEwMDAwMDAwWhcN MTcxMTE0MTIwMDAwWjBlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv bjEQMA4GA1UEBxMHU2VhdHRsZTEbMBkGA1UEChMSQ2hlZiBTb2Z0d2FyZSwgSW5j MRIwEAYDVQQDDAkqLmNoZWYuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQC3xCIczkV10O5jTDpbd4YlPLC6kfnVoOkno2N/OOlcLQu3ulj/Lj1j4r6e 2XthJLcFgTO+y+1/IKnnpLKDfkx1YngWEBXEBP+MrrpDUKKs053s45/bI9QBPISA tXgnYxMH9Glo6FWWd13TUq++OKGw1p1wazH64XK4MAf5y/lkmWXIWumNuO35ZqtB ME3wJISwVHzHB2CQjlDklt+Mb0APEiIFIZflgu9JNBYzLdvUtxiz15FUZQI7SsYL TfXOD1KBNMWqN8snG2e5gRAzB2D161DFvAZt8OiYUe+3QurNlTYVzeHv1ok6UqgM ZcLzg8m801rRip0D7FCGvMCU/ktdAgMBAAGjggHPMIIByzAfBgNVHSMEGDAWgBQP gGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQUwldjw4Pb4HV+wxGZ7MSSRh+d pm4wHQYDVR0RBBYwFIIJKi5jaGVmLmlvggdjaGVmLmlvMA4GA1UdDwEB/wQEAwIF oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYDVR0fBGQwYjAvoC2g K4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nMy5jcmwwL6At oCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNoYTItZzMuY3JsMEIG A1UdIAQ7MDkwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3 LmRpZ2ljZXJ0LmNvbS9DUFMwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhho dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNl cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQw DAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAvcTWenNuvvrhX2omm8LQ zWOuu8jqpoflACwD4lOSZ4TgOe4pQGCjXq8aRBD5k+goqQrPVf9lHnelUHFQac0Q 5WT4YUmisUbF0S4uY5OGQymM52MvUWG4ODL4gaWhFvN+HAXrDPP/9iitsjV0QOnl CDq7Q4/XYRYW3opu5nLLbfW6v4QvF5yzZagEACGs7Vt32p6l391UcU8f6wiB3uMD eioCvjpv/+2YOUNlDPCM3uBubjUhHOwO817wBxXkzdk1OSRe4jzcw/uX6wL7birt fbaSkpilvVX529pSzB2Lvi9xWOoGMM578dpQ0h3PwhmmvKhhCWP+pI05k3oSkYCP ng== -----END CERTIFICATE----- chef-12.14.60/spec/data/trusted_certs/root.pem000066400000000000000000000024721276456504500211200ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- chef-12.14.60/spec/functional/000077500000000000000000000000001276456504500157645ustar00rootroot00000000000000chef-12.14.60/spec/functional/application_spec.rb000066400000000000000000000030541276456504500216300ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mixin/shell_out" describe Chef::Application do include Chef::Mixin::ShellOut before do @original_argv = ARGV.dup ARGV.clear @original_env = ENV.to_hash ENV.clear @app = Chef::Application.new end after do ARGV.replace(@original_argv) ENV.clear ENV.update(@original_env) end describe "when proxy options are set in config" do before do Chef::Config[:http_proxy] = "http://proxy.example.org:8080" Chef::Config[:https_proxy] = nil Chef::Config[:ftp_proxy] = nil Chef::Config[:no_proxy] = nil Chef::Config.export_proxies end it "saves built proxy to ENV which shell_out can use" do so = if windows? shell_out("echo %http_proxy%") else shell_out("echo $http_proxy") end expect(so.stdout.chomp).to eq("http://proxy.example.org:8080") end end end chef-12.14.60/spec/functional/assets/000077500000000000000000000000001276456504500172665ustar00rootroot00000000000000chef-12.14.60/spec/functional/assets/PkgA.1.0.0.0.bff000066400000000000000000000070001276456504500213550ustar00rootroot00000000000000 kêè×ÃSûQÃSûQÿÿÿby nameby namerootd kêÞyÿÿÿÿíAÁSûQÃSûQÃSûQ ./n/íA kêŒy¤ŸÃSûQÃSûQÃSûQ Ÿ./lpp_nameem¤4 R I PkgA { PkgA.rte 1.0.0.0 01 N U en_US My runtime fileset [ % /usr/PkgA 0 /usr/PkgA/bin 8 /usr/doc/PkgA 8 /usr/lib/objrepos 5 INSTWORK 16 16 % % % % % ] } kêRMþÿÿÿíAÑRûQÃSûQÃSûQ ./usraíA kê$QýÿÿÿíAÃSûQÃSûQÃSûQ ./usr/lpppíA kê^üÿÿÿíAÃSûQÃSûQÃSûQ ./usr/lpp/PkgA/píA kê`2¤\ÃSûQÃSûQÃSûQ \./usr/lpp/PkgA/liblpp.a/p¤ 1444 0 68 1258 0 87 260 0 1375425475 0 0 644 11 PkgA.rte.al` ./usr/doc/PkgA ./usr/doc/PkgA/README ./usr/PkgA ./usr/PkgA/bin ./usr/PkgA/bin/acommand 887 1258 68 1375425475 0 0 644 18 PkgA.rte.inventory` /usr/doc/PkgA: owner = root group = system mode = 755 type = DIRECTORY class = apply,inventory,PkgA.rte /usr/doc/PkgA/README: owner = root group = system mode = 644 type = FILE class = apply,inventory,PkgA.rte size = 24 checksum = "08645 1 " /usr/PkgA: owner = root group = system mode = 755 type = DIRECTORY class = apply,inventory,PkgA.rte /usr/PkgA/bin: owner = root group = system mode = 755 type = DIRECTORY class = apply,inventory,PkgA.rte /usr/PkgA/bin/acommand: owner = root group = system mode = 744 type = FILE class = apply,inventory,PkgA.rte size = 42 checksum = "23244 1 " 79 1444 260 1375425475 0 0 644 13 PkgA.rte.size` /usr/PkgA 0 /usr/PkgA/bin 8 /usr/doc/PkgA 8 /usr/lib/objrepos 5 INSTWORK 16 16 93 0 1258 0 0 0 0 0 ` 3 68 260 1258 PkgA.rte.alPkgA.rte.inventoryPkgA.rte.size kêÆáûÿÿÿíAfSûQÜRûQÜRûQ ./usr/doc/PkgA/cíA kꌤÃSûQnSûQnSûQ ./usr/doc/PkgA/README/c¤This is the PkgA README kꨣúÿÿÿíA4SûQ8SûQ8SûQ ./usr/PkgAgA/RAgíA k꾇ùÿÿÿíA8SûQASûQASûQ ./usr/PkgA/binAgíA k꬚ä*ÃSûQUSûQYSûQ *./usr/PkgA/bin/acommandAgä#!/bin/sh echo "Here is my PkgA command" 1258kê„ ./usr/doc/PkgA/README/c¤This is the PkgA README kꨣúÿÿÿíA4SûQ8SûQ8SûQ ./usr/PkgAgA/RAgíA k꾇ùÿÿÿíA8SûQASûQASûQ ./usr/PkgA/binAgchef-12.14.60/spec/functional/assets/PkgA.2.0.0.0.bff000066400000000000000000000070001276456504500213560ustar00rootroot00000000000000 kê8ÌÿkûQÿkûQÿÿÿby nameby namerootd kê ÿÿÿÿíAýkûQÿkûQÿkûQ ./n/íA kꞤŸÖkûQÿkûQÿkûQ Ÿ./lpp_nameem¤4 R I PkgA { PkgA.rte 2.0.0.0 01 N U en_US My runtime fileset [ % /usr/PkgA 0 /usr/PkgA/bin 8 /usr/doc/PkgA 8 /usr/lib/objrepos 5 INSTWORK 16 16 % % % % % ] } kê@yþÿÿÿíA®kûQÃSûQÃSûQ ./usraíA kê$QýÿÿÿíAÃSûQÃSûQÃSûQ ./usr/lpppíA kꮄüÿÿÿíAÃSûQÿkûQÿkûQ ./usr/lpp/PkgA/píA kêØ ¤\ÿkûQÿkûQÿkûQ \./usr/lpp/PkgA/liblpp.a/p¤ 1444 0 68 1258 0 87 260 0 1375431679 0 0 644 11 PkgA.rte.al` ./usr/doc/PkgA ./usr/doc/PkgA/README ./usr/PkgA ./usr/PkgA/bin ./usr/PkgA/bin/acommand 887 1258 68 1375431679 0 0 644 18 PkgA.rte.inventory` /usr/doc/PkgA: owner = root group = system mode = 755 type = DIRECTORY class = apply,inventory,PkgA.rte /usr/doc/PkgA/README: owner = root group = system mode = 644 type = FILE class = apply,inventory,PkgA.rte size = 24 checksum = "08645 1 " /usr/PkgA: owner = root group = system mode = 755 type = DIRECTORY class = apply,inventory,PkgA.rte /usr/PkgA/bin: owner = root group = system mode = 755 type = DIRECTORY class = apply,inventory,PkgA.rte /usr/PkgA/bin/acommand: owner = root group = system mode = 744 type = FILE class = apply,inventory,PkgA.rte size = 46 checksum = "62939 1 " 79 1444 260 1375431679 0 0 644 13 PkgA.rte.size` /usr/PkgA 0 /usr/PkgA/bin 8 /usr/doc/PkgA 8 /usr/lib/objrepos 5 INSTWORK 16 16 93 0 1258 0 0 0 0 0 ` 3 68 260 1258 PkgA.rte.alPkgA.rte.inventoryPkgA.rte.size kêÆáûÿÿÿíAfSûQÜRûQÜRûQ ./usr/doc/PkgA/cíA kê.¤ÿkûQnSûQnSûQ ./usr/doc/PkgA/README/c¤This is the PkgA README kꨢúÿÿÿíAkûQ8SûQ8SûQ ./usr/PkgAgA/RAgíA kꨉùÿÿÿíA±kûQASûQASûQ ./usr/PkgA/binAgíA kê‚&ä.ÿkûQºkûQºkûQ ../usr/PkgA/bin/acommandAgä#!/bin/sh echo "Here is my PkgA command 2.0" 58kê„ ./usr/doc/PkgA/README/c¤This is the PkgA README kꨢúÿÿÿíAkûQ8SûQ8SûQ ./usr/PkgAgA/RAgíA kꨉùÿÿÿíA±kûQASûQASûQ ./usr/PkgA/binAgchef-12.14.60/spec/functional/assets/chefinittest000077500000000000000000000012371276456504500217100ustar00rootroot00000000000000#!/bin/ksh function create_chef_txt { touch /tmp/chefinittest.txt } function delete_chef_txt { rm /tmp/chefinittest.txt } function rename_chef_txt { mv /tmp/chefinittest.txt /tmp/$1 } case "$1" in start ) create_chef_txt ;; stop ) delete_chef_txt ;; status ) [ -f /tmp/chefinittest.txt ] || [ -f /tmp/chefinittest_reload.txt ] || [ -f /tmp/chefinittest_restart.txt ] ;; reload ) rename_chef_txt "chefinittest_reload.txt" ;; restart ) rename_chef_txt "chefinittest_restart.txt" ;; * ) echo "Usage: $0 (start | stop | restart | reload)" exit 1 esac chef-12.14.60/spec/functional/assets/chocolatey_feed/000077500000000000000000000000001276456504500224035ustar00rootroot00000000000000chef-12.14.60/spec/functional/assets/chocolatey_feed/test-A.1.0.nupkg000066400000000000000000000051661276456504500251130ustar00rootroot00000000000000PK ÀÑHƒ+«ëë _rels/.rels ¢( PKÀÑHÓ·áÓ׈ test-A.nuspec ¢( uÍNÃ0„ïH¼ƒ•;Ù” r]õÎ; ÕfÓXÄ?x7ÐÇÇm\TH>x曵Gkç°˜/.âSÜw»~èîñÁf¤<±©4ʾ›Uó+€ÐÌ¥žJ’4iO)À–õñÏÃn€áâ*™©?ËØÕÇŒ±GT¼ªªýè”EŸŽêµ™­…«%,ÜDcêuáß™M5„«Î©ˆ«Ý¦÷KÂÂÍj‰ôùo 9þ\}á7O…Dœ#±›p¶ð/oó# ŸõÒWg/¦4ÛO÷ìº ¸Û…m»c÷PKÀÑH¶Y„´20tools/chocolateyInstall.ps1 ¢(  /Ê,IÕõÈ/.QPrÍËʯT(ÉÈ,V(I-.ÑuT(HLÎNLOUâåJ­È,Q0àåPK ÀÑHcßàvvQpackage/services/metadata/core-properties/b7d76de377764abca4b8b3d7e7cb53dc.psmdcp ¢( chef_testthis is a testtest-A1.0test-Achoco, Version=0.9.9.12, Culture=neutral, PublicKeyToken=79d02ea9cad655eb;Microsoft Windows NT 10.0.10586.0;.NET Framework 4PK ÀÑHUàd,ÀÀ[Content_Types].xml ¢( PK- ÀÑHƒ+«ëë _rels/.relsPK-ÀÑHÓ·áÓ׈ 0test-A.nuspecPK-ÀÑH¶Y„´20Ntools/chocolateyInstall.ps1PK- ÀÑHcßàvvQÕpackage/services/metadata/core-properties/b7d76de377764abca4b8b3d7e7cb53dc.psmdcpPK- ÀÑHUàd,ÀÀÖ[Content_Types].xmlPK}ãchef-12.14.60/spec/functional/assets/chocolatey_feed/test-A.1.5.nupkg000066400000000000000000000051671276456504500251210ustar00rootroot00000000000000PK ÅÑHí<ßhëë _rels/.rels ¢( PKÅÑHm ¼û؈ test-A.nuspec ¢( uÍjÃ0„ï…¾ƒð½Z»P EQȽïPy‹Z?Ñ®Û<~•X)é¡ ƒf¿Yi³?‡E}QaŸâ®tßííãƒÉè>ñHªÒÈ»nÉoìf È:xW§I´K6¯Gxî‡úWˆ+grúÌcWSÊQðªªö£by:¨×6l)ì _ ÜDcâe¡ßM5„«Ì©°­Ù¦‹ÃÀmÔé;Ò_C›4^è´úBïÞQd:8GY0:².Lþåm$vÅg¹ä•Ù³ªÕöÓ=»Vw]˜ÖÙPKÅÑH¶Y„´20tools/chocolateyInstall.ps1 ¢(  /Ê,IÕõÈ/.QPrÍËʯT(ÉÈ,V(I-.ÑuT(HLÎNLOUâåJ­È,Q0àåPK ÅÑHm‘8vvQpackage/services/metadata/core-properties/0540f9bf6c064843a2c8dfeca3b5b8ce.psmdcp ¢( chef_testthis is a testtest-A1.5test-Achoco, Version=0.9.9.12, Culture=neutral, PublicKeyToken=79d02ea9cad655eb;Microsoft Windows NT 10.0.10586.0;.NET Framework 4PK ÅÑHUàd,ÀÀ[Content_Types].xml ¢( PK- ÅÑHí<ßhëë _rels/.relsPK-ÅÑHm ¼û؈ 0test-A.nuspecPK-ÅÑH¶Y„´20Otools/chocolateyInstall.ps1PK- ÅÑHm‘8vvQÖpackage/services/metadata/core-properties/0540f9bf6c064843a2c8dfeca3b5b8ce.psmdcpPK- ÅÑHUàd,ÀÀ×[Content_Types].xmlPK}ächef-12.14.60/spec/functional/assets/chocolatey_feed/test-A.2.0.nupkg000066400000000000000000000051661276456504500251140ustar00rootroot00000000000000PK ÉÑHÆGÉëë _rels/.rels ¢( PKÉÑH—{‘»×ˆ test-A.nuspec ¢( uÁnà Dï•úÈ÷²$—J!ʽÿ¡õ:Fµ°ë&Ÿ_“*=TâÀì›…ÑØýužÔ7)îº6ÝÞ½¾ØìñËŸHUy×"ù€q¤Ù³ž–Äii†Õâ ¶fcÀ¼C\8ê+÷]}L);“øÞ‹¿«ªCï„XÞêµ [ ·ÕÆÂC4&A&úÝYUC~‘1v5Ûp¼9,chef_testthis is a testtest-A2.0test-Achoco, Version=0.9.9.12, Culture=neutral, PublicKeyToken=79d02ea9cad655eb;Microsoft Windows NT 10.0.10586.0;.NET Framework 4PK ÉÑHUàd,ÀÀ[Content_Types].xml ¢( PK- ÉÑHÆGÉëë _rels/.relsPK-ÉÑH—{‘»×ˆ 0test-A.nuspecPK-ÉÑH¶Y„´20Ntools/chocolateyInstall.ps1PK- ÉÑHuNvvQÕpackage/services/metadata/core-properties/b5267113430948e0a4eeb4fd1aa32ffe.psmdcpPK- ÉÑHUàd,ÀÀÖ[Content_Types].xmlPK}ãchef-12.14.60/spec/functional/assets/chocolatey_feed/test-B.1.0.nupkg000066400000000000000000000051661276456504500251140ustar00rootroot00000000000000PK ÎÑHé.Tðëë _rels/.rels ¢( PKÎÑHõÈS׈ test-B.nuspec ¢( uÍNÃ0„ïH¼ƒ•;Ù” r]Á™w@«Í¦±ˆðn Û¸¨|ðÌ7kÖNa1_\ħ¸ïvýÐÜýÍHxdSi”}7«æg¡™J<•$iÒžR€-ëã‡ÝÃÄU2S’±«c+ލxQUûÑ)‹>¼Z¨×f¶®–°p©×…g6Õ®:§"®v›ÞÏ W«%Òwä¿æ4^øsõ…ßchef_testthis is a testtest-B1.0test-Bchoco, Version=0.9.9.12, Culture=neutral, PublicKeyToken=79d02ea9cad655eb;Microsoft Windows NT 10.0.10586.0;.NET Framework 4PK ÎÑHUàd,ÀÀ[Content_Types].xml ¢( PK- ÎÑHé.Tðëë _rels/.relsPK-ÎÑHõÈS׈ 0test-B.nuspecPK-ÎÑH|-20Ntools/chocolateyInstall.ps1PK- ÎÑH¤*¶˜vvQÕpackage/services/metadata/core-properties/2fd1bc20021b4e3ab43d2abd1fc76f64.psmdcpPK- ÎÑHUàd,ÀÀÖ[Content_Types].xmlPK}ãchef-12.14.60/spec/functional/assets/dummy-1-0.aix6.1.noarch.rpm000066400000000000000000000017141276456504500237150ustar00rootroot00000000000000í«îÛdummy-1-0ÿŽ­èèì$TO®Z;XÞ0gp#/ÜŽ­èDdèéê ì í î4ï8ñLóPöe÷sø —üªýÇþÎÕè(ì8ô9ø:b&d8e=fBCdummy10dummy packageThis is a dummy package.Qø§Ÿlpar10ml16ed_pubDummies IncorporatedDummy LicenseLuciano Rocha Networking/Daemonshttp://www.dontbeadummy.org/aix6.1noarchdummy-1-0.src.rpm|3.0.5C Luciano Rocha 0- dummy-O2 -fsigned-charcpiogzip9‹3070704 «$Á!AŽž>®AŠŠŠ @Öéì5|chef-12.14.60/spec/functional/assets/dummy-2-0.aix6.1.noarch.rpm000066400000000000000000000017141276456504500237160ustar00rootroot00000000000000í«îÛdummy-2-0ÿŽ­èèì$áݽ6`»©}©À4厭èDdèéê ì í î4ï8ñLóPöe÷sø —üªýÇþÎÕè(ì8ô9ø:b&d8e=fBCdummy20dummy packageThis is a dummy package.QûLÃlpar10ml16ed_pubDummies IncorporatedDummy LicenseLuciano Rocha Networking/Daemonshttp://www.dontbeadummy.org/aix6.1noarchdummy-2-0.src.rpm|3.0.5C Luciano Rocha 0- dummy-O2 -fsigned-charcpiogzip9‹3070704 «$Á!AŽž>®AŠŠŠ @Öéì5|chef-12.14.60/spec/functional/assets/mytest-1.0-1.noarch.rpm000066400000000000000000000041161276456504500232400ustar00rootroot00000000000000í«îÛmytest-1.0-1Ž­èT>D è,ì0ï@f613507b95c6c5a51d7aec52373784dd2b49f4916¹”âsSÌö úÍ&k¸˜>ÿÿÿ°Ž­è5F?6dèé ê ì í îHïLñ`ödø hürýÀþÆÍèð ô ø  " $,6@X`ht·(Æ>ÎGØHàIèXìYð\ø]^b$dœe¡f¦k¨l¯tÈuÐvØwðxø{Cmytest1.01A test scriptA test script inside a simple RPM packageQòbœPrabhu1.localdomainMITUtilitieshttp://www.oracle-base.com/articles/linux/linux-build-simple-rpm-packages.phplinuxnoarchchmod 755 -R /opt/mytestAííQòb›Qòb›4cdc303079a9a328fb74f7dfc01e2008rootrootrootrootmytest-1.0-1.src.rpmÿÿÿÿÿÿÿÿmytest@JJ/bin/shrpmlib(CompressedFileNames)rpmlib(PayloadFilesHavePrefix)3.0.4-14.0-14.4.2.3/bin/shýý'„'…1.0-1mytestmytest.sh/opt//opt/mytest/-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=genericcpiogzip9noarchnoarch-redhat-linux-gnuASCII textdirectorysystem_u:object_r:usr_t:s0system_u:object_r:usr_t:s0?ÿÿü°‹307070400L12·01ÃÔìÀÈÔ0ÍÈÌÈ2 U8 —z€Ëëéç”èçV–¤—00 Ûm ’·Àm·!ªÝ†fÄÙmhŽÍn(¥WœÁ©Éù J!™Å @”¨’RâB¸(@¬:tË GO× EEE›(ešñ˜chef-12.14.60/spec/functional/assets/mytest-2.0-1.noarch.rpm000066400000000000000000000041451276456504500232430ustar00rootroot00000000000000í«îÛmytest-2.0-1Ž­èT>D è,ì0ï@d1354e1297e641203ccab95d9556699a84e59639Mmü`-Œc[®çÃ3œ>ÿÿÿ°Ž­è5V?Fdèé ê ì í îHïLñpötø xü‚ýÐþÖÝø    2 4<FPhpx„Ç(Ö>ÞGèHðIøXüY\]^!b4d¬e±f¶k¸l¿tØuàvèwx{Cmytest2.01A test ScriptA test script inside a simple RPM packageQú€økaustubh-centos5-64-cron.opscode.usMITUtilitieshttp://www.oracle-base.com/articles/linux/linux-build-simple-rpm-packages.phplinuxnoarchchmod 755 -R /opt/mytestAííQú€øQú€øfc457c3017d6f95b0876c7ebb6f58fcdrootrootrootrootmytest-2.0-1.src.rpmÿÿÿÿÿÿÿÿmytest@JJ/bin/shrpmlib(CompressedFileNames)rpmlib(PayloadFilesHavePrefix)3.0.4-14.0-14.4.2.3/bin/shüüldle2.0-1mytestmytest.sh/opt//opt/mytest/-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=genericcpiogzip9noarchnoarch-redhat-linux-gnuASCII textdirectorysystem_u:object_r:usr_t:s0system_u:object_r:usr_t:s0?ÿÿü°‹30707040004K63Ò&†©)Ø‘©aZ¢…AšªpZ²~7OO?¿ D?·²$µ¸„ÁÙnS¼n» Qí6L&ÎnCslvC)½â  HMÎÈWP ÉÈ,V¢Ä¼ü’ŒÔ"%.¸‰ĪCI0FH£§k¢¢"È].Xž@œchef-12.14.60/spec/functional/assets/testchefsubsys000077500000000000000000000002401276456504500222660ustar00rootroot00000000000000#!/bin/bash # trapchild sleep 120 & pid="$!" trap 'echo I am going down, so killing off my processes..; kill $pid; exit' SIGHUP SIGINT SIGQUIT SIGTERM wait chef-12.14.60/spec/functional/audit/000077500000000000000000000000001276456504500170725ustar00rootroot00000000000000chef-12.14.60/spec/functional/audit/rspec_formatter_spec.rb000066400000000000000000000034171276456504500236350ustar00rootroot00000000000000# # Author:: Tyler Ball () # Author:: Claire McQuin () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "rspec/core/sandbox" require "chef/audit/runner" require "rspec/support/spec/in_sub_process" require "rspec/support/spec/stderr_splitter" require "chef/audit/rspec_formatter" describe Chef::Audit::RspecFormatter do include RSpec::Support::InSubProcess let(:events) { double("events").as_null_object } let(:audits) { {} } let(:run_context) { instance_double(Chef::RunContext, :events => events, :audits => audits) } let(:runner) { Chef::Audit::Runner.new(run_context) } let(:output) { double("output") } # aggressively define this so we can mock out the new call later let!(:formatter) { Chef::Audit::RspecFormatter.new(output) } around(:each) do |ex| RSpec::Core::Sandbox.sandboxed { ex.run } end it "should not close the output using our formatter" do in_sub_process do expect_any_instance_of(Chef::Audit::RspecFormatter).to receive(:new).and_return(formatter) expect(formatter).to receive(:close).and_call_original expect(output).to_not receive(:close) runner.run end end end chef-12.14.60/spec/functional/audit/runner_spec.rb000066400000000000000000000077161276456504500217550ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "rspec/core/sandbox" require "chef/audit/runner" require "rspec/support/spec/in_sub_process" require "rspec/support/spec/stderr_splitter" require "tempfile" ## # This functional test ensures that our runner can be setup to not interfere with existing RSpec # configuration and world objects. When normally running Chef, there is only 1 RSpec instance # so this isn't needed. In unit testing the Runner should be mocked appropriately. describe Chef::Audit::Runner do # The functional tests must be run in a sub_process. Including Serverspec includes the Serverspec DSL - this # conflicts with our `package` DSL (among others) when we try to test `package` inside an RSpec example. # Our DSL leverages `method_missing` while the Serverspec DSL defines a method on the RSpec::Core::ExampleGroup. # The defined method wins our and returns before our `method_missing` DSL can be called. # # Running in a sub_process means the serverspec libraries will only be included in a forked process, not the main one. include RSpec::Support::InSubProcess let(:events) { double("events").as_null_object } let(:runner) { Chef::Audit::Runner.new(run_context) } let(:stdout) { StringIO.new } around(:each) do |ex| RSpec::Core::Sandbox.sandboxed { ex.run } end describe "#run" do let(:audits) { {} } let(:run_context) { instance_double(Chef::RunContext, :events => events, :audits => audits) } let(:control_group_name) { "control_group_name" } # Set cookbook path to include our parent, so that it will recognize this # rspec file as one that should show up in the backtrace. before(:each) { Chef::Config[:cookbook_path] = File.dirname(__FILE__) } shared_context "passing audit" do let(:audits) do should_pass = lambda do it "should pass" do expect(2 - 2).to eq(0) end end { control_group_name => Struct.new(:args, :block).new([control_group_name], should_pass) } end end shared_context "failing audit" do let(:audits) do should_fail = lambda do it "should fail" do expect(2 - 1).to eq(0) end end { control_group_name => Struct.new(:args, :block).new([control_group_name], should_fail) } end end describe "log location is stdout" do before do allow(Chef::Log).to receive(:info) do |msg| stdout.puts(msg) end end it "Correctly runs an empty controls block" do in_sub_process do runner.run end end context "there is a single successful control" do include_context "passing audit" it "correctly runs" do in_sub_process do runner.run expect(stdout.string).to match(/1 example, 0 failures/) end end end context "there is a single failing control" do include_context "failing audit" it "correctly runs" do in_sub_process do runner.run expect(stdout.string).to match(/Failure\/Error: expect\(2 - 1\)\.to eq\(0\)/) expect(stdout.string).to match(/1 example, 1 failure/) expect(stdout.string).to match(/# control_group_name should fail/) end end end end end end chef-12.14.60/spec/functional/dsl/000077500000000000000000000000001276456504500165465ustar00rootroot00000000000000chef-12.14.60/spec/functional/dsl/reboot_pending_spec.rb000066400000000000000000000073501276456504500231100ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/dsl/reboot_pending" require "chef/win32/registry" require "spec_helper" describe Chef::DSL::RebootPending, :windows_only do def run_ohai node.consume_external_attrs(OHAI_SYSTEM.data, {}) end let(:node) { Chef::Node.new } let!(:ohai) { run_ohai } # Ensure we have necessary node data let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:recipe) { Chef::Recipe.new("a windows cookbook", "the windows recipe", run_context) } let(:registry) { Chef::Win32::Registry.new(run_context) } describe "reboot_pending?" do let(:reg_key) { nil } let(:original_set) { false } before(:all) { @any_flag = Hash.new } after { @any_flag[reg_key] = original_set } describe 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations' do let(:reg_key) { 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager' } let(:original_set) { registry.value_exists?(reg_key, { :name => "PendingFileRenameOperations" }) } it "returns true if the registry value exists" do skip "found existing registry key" if original_set registry.set_value(reg_key, { :name => "PendingFileRenameOperations", :type => :multi_string, :data => ['\??\C:\foo.txt|\??\C:\bar.txt'] }) expect(recipe.reboot_pending?).to be_truthy end after do unless original_set registry.delete_value(reg_key, { :name => "PendingFileRenameOperations" }) end end end describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired' do let(:reg_key) { 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired' } let(:original_set) { registry.key_exists?(reg_key) } it "returns true if the registry key exists" do skip "found existing registry key" if original_set pending "Permissions are limited to 'TrustedInstaller' by default" registry.create_key(reg_key, false) expect(recipe.reboot_pending?).to be_truthy end after do unless original_set registry.delete_key(reg_key, false) end end end describe 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' do let(:reg_key) { 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' } let(:original_set) { registry.key_exists?(reg_key) } it "returns true if the registry key exists" do skip "found existing registry key" if original_set registry.create_key(reg_key, false) expect(recipe.reboot_pending?).to be_truthy end after do unless original_set registry.delete_key(reg_key, false) end end end describe "when there is nothing to indicate a reboot is pending" do it "should return false" do skip "reboot pending" if @any_flag.any? { |_, v| v == true } expect(recipe.reboot_pending?).to be_falsey end end end end chef-12.14.60/spec/functional/dsl/registry_helper_spec.rb000066400000000000000000000051271276456504500233210ustar00rootroot00000000000000# # Author:: Prajakta Purohit () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/dsl/registry_helper" require "spec_helper" describe Chef::Resource::RegistryKey, :windows_only do before (:all) do ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root" ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch" ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root', Win32::Registry::KEY_ALL_ACCESS) do |reg| reg["RootType1", Win32::Registry::REG_SZ] = "fibrous" reg.write("Roots", Win32::Registry::REG_MULTI_SZ, ["strong roots", "healthy tree"]) end events = Chef::EventDispatch::Dispatcher.new node = Chef::Node.new node.consume_external_attrs(OHAI_SYSTEM.data, {}) run_context = Chef::RunContext.new(node, {}, events) @resource = Chef::Resource.new("foo", run_context) end context "tests registry dsl" do it "returns true if registry_key_exists" do expect(@resource.registry_key_exists?("HKCU\\Software\\Root")).to eq(true) end it "returns true if registry has specified value" do values = @resource.registry_get_values("HKCU\\Software\\Root") expect(values.include?({ :name => "RootType1", :type => :string, :data => "fibrous" })).to eq(true) end it "returns true if specified registry_has_subkey" do expect(@resource.registry_has_subkeys?("HKCU\\Software\\Root")).to eq(true) end it "returns true if specified key has specified subkey" do subkeys = @resource.registry_get_subkeys("HKCU\\Software\\Root") expect(subkeys.include?("Branch")).to eq(true) end it "returns true if registry_value_exists" do expect(@resource.registry_value_exists?("HKCU\\Software\\Root", { :name => "RootType1", :type => :string, :data => "fibrous" })).to eq(true) end it "returns true if data_value_exists" do expect(@resource.registry_data_exists?("HKCU\\Software\\Root", { :name => "RootType1", :type => :string, :data => "fibrous" })).to eq(true) end end end chef-12.14.60/spec/functional/event_loggers/000077500000000000000000000000001276456504500206275ustar00rootroot00000000000000chef-12.14.60/spec/functional/event_loggers/windows_eventlog_spec.rb000066400000000000000000000100211276456504500255550ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # 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 "securerandom" require "chef/event_loggers/windows_eventlog" if Chef::Platform.windows? && (not Chef::Platform.windows_server_2003?) require "win32/eventlog" include Win32 end describe Chef::EventLoggers::WindowsEventLogger, :windows_only, :not_supported_on_win2k3 do def rand random.rand(1 << 32).to_s end let(:random) { Random.new } let(:run_id) { rand } let(:version) { rand } let(:elapsed_time) { rand } let(:logger) { Chef::EventLoggers::WindowsEventLogger.new } let(:flags) { nil } let(:node) { nil } let(:run_status) { double("Run Status", { run_id: run_id, elapsed_time: elapsed_time }) } let(:event_log) { EventLog.new("Application") } let!(:offset) { event_log.read_last_event.record_number } let(:mock_exception) { double("Exception", { message: rand, backtrace: [rand, rand] }) } it "is available" do expect(Chef::EventLoggers::WindowsEventLogger.available?).to be_truthy end it "writes run_start event with event_id 10000 and contains version" do logger.run_start(version) expect(event_log.read(flags, offset).any? do |e| e.source == "Chef" && e.event_id == 10000 && e.string_inserts[0].include?(version) end).to be_truthy end it "writes run_started event with event_id 10001 and contains the run_id" do logger.run_started(run_status) expect(event_log.read(flags, offset).any? do |e| e.source == "Chef" && e.event_id == 10001 && e.string_inserts[0].include?(run_id) end).to be_truthy end it "writes run_completed event with event_id 10002 and contains the run_id and elapsed time" do logger.run_started(run_status) logger.run_completed(node) expect(event_log.read(flags, offset).any? do |e| e.source == "Chef" && e.event_id == 10002 && e.string_inserts[0].include?(run_id) && e.string_inserts[1].include?(elapsed_time.to_s) end).to be_truthy end it "writes run_failed event with event_id 10003 and contains the run_id, elapsed time, and exception info" do logger.run_started(run_status) logger.run_failed(mock_exception) expect(event_log.read(flags, offset).any? do |e| e.source == "Chef" && e.event_id == 10003 && e.string_inserts[0].include?(run_id) && e.string_inserts[1].include?(elapsed_time.to_s) && e.string_inserts[2].include?(mock_exception.class.name) && e.string_inserts[3].include?(mock_exception.message) && e.string_inserts[4].include?(mock_exception.backtrace[0]) && e.string_inserts[4].include?(mock_exception.backtrace[1]) end).to be_truthy end it "writes run_failed event with event_id 10003 even when run_status is not set" do logger.run_failed(mock_exception) expect(event_log.read(flags, offset).any? do |e| e.source == "Chef" && e.event_id == 10003 && e.string_inserts[0].include?("UNKNOWN") && e.string_inserts[1].include?("UNKNOWN") && e.string_inserts[2].include?(mock_exception.class.name) && e.string_inserts[3].include?(mock_exception.message) && e.string_inserts[4].include?(mock_exception.backtrace[0]) && e.string_inserts[4].include?(mock_exception.backtrace[1]) end).to be_truthy end end chef-12.14.60/spec/functional/file_content_management/000077500000000000000000000000001276456504500226315ustar00rootroot00000000000000chef-12.14.60/spec/functional/file_content_management/deploy_strategies_spec.rb000066400000000000000000000146301276456504500277220ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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" shared_examples_for "a content deploy strategy" do def normalize_mode(mode_int) ( mode_int & 07777).to_s(8) end let(:sandbox_dir) do basename = make_tmpname("content-deploy-tests") full_path = File.join(CHEF_SPEC_DATA, basename) FileUtils.mkdir_p(full_path) full_path end after do FileUtils.rm_rf(sandbox_dir) if File.exist?(sandbox_dir) end let(:content_deployer) { described_class.new } let(:target_file_path) { File.join(sandbox_dir, "cp-deploy-strategy-target-file.txt") } describe "creating the file" do ## # UNIX Context let(:default_mode) { normalize_mode(0666 & ~File.umask) } it "touches the file to create it (UNIX)", :unix_only do content_deployer.create(target_file_path) expect(File).to exist(target_file_path) file_info = File.stat(target_file_path) expect(file_info).to be_owned expect(file_info).to be_file expect(normalize_mode(file_info.mode)).to eq(default_mode) end ## # Window Context let(:parent_dir) { File.dirname(target_file_path) } let(:parent_security_descriptor) do security_obj = Chef::ReservedNames::Win32::Security::SecurableObject.new(parent_dir) security_obj.security_descriptor(true) end let(:masks) do Chef::ReservedNames::Win32::API::Security end def ace_inherits?(ace) flags = ace.flags (flags & masks::OBJECT_INHERIT_ACE) != 0 end let(:parent_inheritable_aces) do inheritable_aces = parent_security_descriptor.dacl.select do |ace| ace_inherits?(ace) end end it "touches the file to create it (Windows)", :windows_only do content_deployer.create(target_file_path) expect(File).to exist(target_file_path) file_info = File.stat(target_file_path) expect(file_info).to be_owned expect(file_info).to be_file parent_aces = parent_inheritable_aces security_obj = Chef::ReservedNames::Win32::Security::SecurableObject.new(target_file_path) security_descriptor = security_obj.security_descriptor(true) # On certain windows systems like 2003 and Azure VMs there are some default # ACEs that are not inherited from parents. So filter out the parents before # comparing the aces self_aces = security_descriptor.dacl.select do |ace| ace_inherits?(ace) end self_aces.each_with_index do |ace, index| expect(ace.mask).to eq(parent_aces[index].mask) end end end describe "updating the file" do let(:staging_dir) { Dir.mktmpdir } let(:staging_file_content) { "this is the expected content" } let(:staging_file_path) do path = File.join(staging_dir, "cp-deploy-strategy-staging-file.txt") File.open(path, "w+", 0600) { |f| f.print(staging_file_content) } path end def unix_invariant_properies(stat_struct) unix_invariants.inject({}) do |property_map, property| property_map[property] = stat_struct.send(property) property_map end end def win_invariant_properties(sec_obj) descriptor = sec_obj.security_descriptor(true) security_descriptor_invariants.inject({}) do |prop_map, property| prop_map[property] = descriptor.send(property) prop_map end end before do content_deployer.create(target_file_path) end it "maintains invariant properties on UNIX", :unix_only do original_info = File.stat(target_file_path) content_deployer.deploy(staging_file_path, target_file_path) updated_info = File.stat(target_file_path) expect(unix_invariant_properies(original_info)).to eq(unix_invariant_properies(updated_info)) end it "maintains invariant properties on Windows", :windows_only do original_info = Chef::ReservedNames::Win32::Security::SecurableObject.new(target_file_path) content_deployer.deploy(staging_file_path, target_file_path) updated_info = Chef::ReservedNames::Win32::Security::SecurableObject.new(target_file_path) expect(win_invariant_properties(original_info)).to eq(win_invariant_properties(updated_info)) end it "updates the target with content from staged" do content_deployer.deploy(staging_file_path, target_file_path) expect(IO.binread(target_file_path)).to eq(staging_file_content) end context "when the owner of the target file is not the owner of the staging file", :requires_root do before do File.chown(1337, 1337, target_file_path) end it "copies the staging file's content" do original_info = File.stat(target_file_path) content_deployer.deploy(staging_file_path, target_file_path) updated_info = File.stat(target_file_path) expect(unix_invariant_properies(original_info)).to eq(unix_invariant_properies(updated_info)) end end end end describe Chef::FileContentManagement::Deploy::Cp do let(:unix_invariants) do [ :uid, :gid, :mode, :ino, ] end let(:security_descriptor_invariants) do [ :owner, :group, :dacl, ] end it_should_behave_like "a content deploy strategy" end describe Chef::FileContentManagement::Deploy::MvUnix, :unix_only do let(:unix_invariants) do [ :uid, :gid, :mode, ] end it_should_behave_like "a content deploy strategy" end # On Unix we won't have loaded the file, avoid undefined constant errors: class Chef::FileContentManagement::Deploy::MvWindows; end describe Chef::FileContentManagement::Deploy::MvWindows, :windows_only do context "when a file has no sacl" do let(:security_descriptor_invariants) do [ :owner, :group, :dacl, ] end it_should_behave_like "a content deploy strategy" end end chef-12.14.60/spec/functional/http/000077500000000000000000000000001276456504500167435ustar00rootroot00000000000000chef-12.14.60/spec/functional/http/simple_spec.rb000066400000000000000000000130741276456504500216000ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "tiny_server" require "support/shared/functional/http" describe Chef::HTTP::Simple do include ChefHTTPShared let(:http_client) { described_class.new(source) } let(:http_client_disable_gzip) { described_class.new(source, { :disable_gzip => true } ) } before(:each) do start_tiny_server end after(:each) do stop_tiny_server end shared_examples_for "downloads requests correctly" do it "successfully downloads a streaming request" do tempfile = http_client.streaming_request(source, {}) tempfile.close expect(Digest::MD5.hexdigest(binread(tempfile.path))).to eq(Digest::MD5.hexdigest(expected_content)) end it "successfully does a non-streaming GET request" do expect(Digest::MD5.hexdigest(http_client.get(source))).to eq(Digest::MD5.hexdigest(expected_content)) end end shared_examples_for "validates content length and throws an exception" do it "successfully downloads a streaming request" do expect { http_client.streaming_request(source) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end it "successfully does a non-streaming GET request" do expect { http_client.get(source) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end end shared_examples_for "an endpoint that 403s" do it "fails with a Net::HTTPServerException for a streaming request" do expect { http_client.streaming_request(source) }.to raise_error(Net::HTTPServerException) end it "fails with a Net::HTTPServerException for a GET request" do expect { http_client.get(source) }.to raise_error(Net::HTTPServerException) end end # see CHEF-5100 shared_examples_for "a 403 after a successful request when reusing the request object" do it "fails with a Net::HTTPServerException for a streaming request" do tempfile = http_client.streaming_request(source) tempfile.close expect(Digest::MD5.hexdigest(binread(tempfile.path))).to eq(Digest::MD5.hexdigest(expected_content)) expect { http_client.streaming_request(source2) }.to raise_error(Net::HTTPServerException) end it "fails with a Net::HTTPServerException for a GET request" do expect(Digest::MD5.hexdigest(http_client.get(source))).to eq(Digest::MD5.hexdigest(expected_content)) expect { http_client.get(source2) }.to raise_error(Net::HTTPServerException) end end it_behaves_like "downloading all the things" context "when Chef::Log.level = :debug" do before do Chef::Log.level = :debug @debug_log = "" allow(Chef::Log).to receive(:debug) { |str| @debug_log << str } end let(:source) { "http://localhost:9000" } it "Logs the request and response for 200's but not the body" do http_client.get("http://localhost:9000/nyan_cat.png") expect(@debug_log).to match(/200/) expect(@debug_log).to match(/HTTP Request Header Data/) expect(@debug_log).to match(/HTTP Status and Header Data/) expect(@debug_log).not_to match(/HTTP Request Body/) expect(@debug_log).not_to match(/HTTP Response Body/) expect(@debug_log).not_to match(/Your request is just terrible./) end it "Logs the request and response for 200 POST, but not the body" do http_client.post("http://localhost:9000/posty", "hithere") expect(@debug_log).to match(/200/) expect(@debug_log).to match(/HTTP Request Header Data/) expect(@debug_log).to match(/HTTP Status and Header Data/) expect(@debug_log).not_to match(/HTTP Request Body/) expect(@debug_log).not_to match(/hithere/) expect(@debug_log).not_to match(/HTTP Response Body/) expect(@debug_log).not_to match(/Your request is just terrible./) end it "Logs the request and response and bodies for 400 response" do expect do http_client.get("http://localhost:9000/bad_request") end.to raise_error(Net::HTTPServerException) expect(@debug_log).to match(/400/) expect(@debug_log).to match(/HTTP Request Header Data/) expect(@debug_log).to match(/HTTP Status and Header Data/) expect(@debug_log).not_to match(/HTTP Request Body/) expect(@debug_log).not_to match(/hithere/) expect(@debug_log).to match(/HTTP Response Body/) expect(@debug_log).to match(/Your request is just terrible./) end it "Logs the request and response and bodies for 400 POST response" do expect do http_client.post("http://localhost:9000/bad_request", "hithere") end.to raise_error(Net::HTTPServerException) expect(@debug_log).to match(/400/) expect(@debug_log).to match(/HTTP Request Header Data/) expect(@debug_log).to match(/HTTP Status and Header Data/) expect(@debug_log).to match(/HTTP Request Body/) expect(@debug_log).to match(/hithere/) expect(@debug_log).to match(/HTTP Response Body/) expect(@debug_log).to match(/Your request is just terrible./) end end end chef-12.14.60/spec/functional/knife/000077500000000000000000000000001276456504500170605ustar00rootroot00000000000000chef-12.14.60/spec/functional/knife/configure_spec.rb000066400000000000000000000020551276456504500224020ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/knife/configure" describe "knife configure" do let (:ohai) do OHAI_SYSTEM end it "loads the fqdn from Ohai" do knife_configure = Chef::Knife::Configure.new hostname_guess = ohai[:fqdn] || ohai[:machinename] || ohai[:hostname] || "localhost" expect(knife_configure.guess_servername).to eql(hostname_guess) end end chef-12.14.60/spec/functional/knife/cookbook_delete_spec.rb000066400000000000000000000137131276456504500235540ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 "tiny_server" describe Chef::Knife::CookbookDelete do before(:each) do @server = TinyServer::Manager.new @server.start end after(:each) do @server.stop end before(:each) do @knife = Chef::Knife::CookbookDelete.new @api = TinyServer::API.instance @api.clear Chef::Config[:node_name] = nil Chef::Config[:client_key] = nil Chef::Config[:chef_server_url] = "http://localhost:9000" end context "when the cookbook doesn't exist" do let(:log_output) { StringIO.new } before do @knife.name_args = %w{no-such-cookbook} @api.get("/cookbooks/no-such-cookbook", 404, Chef::JSONCompat.to_json({ "error" => "dear Tim, no. -Sent from my iPad" })) end around do |ex| old_logger = Chef::Log.logger old_level = Chef::Log.level begin Chef::Log.logger = Logger.new(log_output) Chef::Log.level = :debug ex.run ensure Chef::Log.logger = old_logger Chef::Log.level = old_level end end it "logs an error and exits" do allow(@knife.ui).to receive(:stderr).and_return(log_output) expect { @knife.run }.to raise_error(SystemExit) expect(log_output.string).to match(/Cannot find a cookbook named no-such-cookbook to delete/) end end context "when there is only one version of a cookbook" do before do @knife.name_args = %w{obsolete-cookbook} @cookbook_list = { "obsolete-cookbook" => { "versions" => ["version" => "1.0.0"] } } @api.get("/cookbooks/obsolete-cookbook", 200, Chef::JSONCompat.to_json(@cookbook_list)) end it "asks for confirmation, then deletes the cookbook" do stdin, stdout = StringIO.new("y\n"), StringIO.new allow(@knife.ui).to receive(:stdin).and_return(stdin) allow(@knife.ui).to receive(:stdout).and_return(stdout) cb100_deleted = false @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } @knife.run expect(stdout.string).to match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/) expect(cb100_deleted).to be_truthy end it "asks for confirmation before purging" do @knife.config[:purge] = true stdin, stdout = StringIO.new("y\ny\n"), StringIO.new allow(@knife.ui).to receive(:stdin).and_return(stdin) allow(@knife.ui).to receive(:stdout).and_return(stdout) cb100_deleted = false @api.delete("/cookbooks/obsolete-cookbook/1.0.0?purge=true", 200) { cb100_deleted = true; "[\"true\"]" } @knife.run expect(stdout.string).to match(/#{Regexp.escape('Are you sure you want to purge files')}/) expect(stdout.string).to match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/) expect(cb100_deleted).to be_truthy end end context "when there are several versions of a cookbook" do before do @knife.name_args = %w{obsolete-cookbook} versions = ["1.0.0", "1.1.0", "1.2.0"] with_version = lambda { |version| { "version" => version } } @cookbook_list = { "obsolete-cookbook" => { "versions" => versions.map(&with_version) } } @api.get("/cookbooks/obsolete-cookbook", 200, Chef::JSONCompat.to_json(@cookbook_list)) end it "deletes all versions of a cookbook when given the '-a' flag" do @knife.config[:all] = true @knife.config[:yes] = true cb100_deleted = cb110_deleted = cb120_deleted = nil @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } @api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" } @api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" } @knife.run expect(cb100_deleted).to be_truthy expect(cb110_deleted).to be_truthy expect(cb120_deleted).to be_truthy end it "asks which version to delete and deletes that when not given the -a flag" do cb100_deleted = cb110_deleted = cb120_deleted = nil @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } stdin, stdout = StringIO.new, StringIO.new allow(@knife.ui).to receive(:stdin).and_return(stdin) allow(@knife.ui).to receive(:stdout).and_return(stdout) stdin << "1\n" stdin.rewind @knife.run expect(cb100_deleted).to be_truthy expect(stdout.string).to match(/Which version\(s\) do you want to delete\?/) end it "deletes all versions of the cookbook when not given the -a flag and the user chooses to delete all" do cb100_deleted = cb110_deleted = cb120_deleted = nil @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" } @api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" } @api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" } stdin, stdout = StringIO.new("4\n"), StringIO.new allow(@knife.ui).to receive(:stdin).and_return(stdin) allow(@knife.ui).to receive(:stdout).and_return(stdout) @knife.run expect(cb100_deleted).to be_truthy expect(cb110_deleted).to be_truthy expect(cb120_deleted).to be_truthy end end end chef-12.14.60/spec/functional/knife/exec_spec.rb000066400000000000000000000030751276456504500213500ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 "tiny_server" describe Chef::Knife::Exec do before(:each) do @server = TinyServer::Manager.new #(:debug => true) @server.start end after(:each) do @server.stop end before(:each) do @knife = Chef::Knife::Exec.new @api = TinyServer::API.instance @api.clear Chef::Config[:node_name] = nil Chef::Config[:client_key] = nil Chef::Config[:chef_server_url] = "http://localhost:9000" $output = StringIO.new end it "executes a script in the context of the chef-shell main context" do @node = Chef::Node.new @node.name("ohai-world") response = { "rows" => [@node], "start" => 0, "total" => 1 } @api.get(%r{^/search/node}, 200, Chef::JSONCompat.to_json(response)) code = "$output.puts nodes.all" @knife.config[:exec] = code @knife.run expect($output.string).to match(%r{node\[ohai-world\]}) end end chef-12.14.60/spec/functional/knife/rehash_spec.rb000066400000000000000000000024051276456504500216720ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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/knife/rehash" require "chef/knife/core/subcommand_loader" describe "knife rehash" do before do allow(Chef::Knife::SubcommandLoader).to receive(:load_commands) end after do # We need to clean up the generated manifest or else is messes with later tests FileUtils.rm_f(Chef::Knife::SubcommandLoader.plugin_manifest_path) end it "writes the loaded plugins to disc" do knife_rehash = Chef::Knife::Rehash.new knife_rehash.run expect(File.read(Chef::Knife::SubcommandLoader.plugin_manifest_path)).to match(/node_list.rb/) end end chef-12.14.60/spec/functional/knife/smoke_test.rb000066400000000000000000000027541276456504500215720ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 "knife smoke tests" do # Since our specs load all code, there could be a case where knife does not # run correctly b/c of a missing require, but is not caught by other tests. # # We run `knife -v` to verify that knife at least loads all its code. it "can run and print its version" do knife_path = File.expand_path("../../bin/knife", CHEF_SPEC_DATA) knife_cmd = Mixlib::ShellOut.new("#{knife_path} -v") knife_cmd.run_command knife_cmd.error! expect(knife_cmd.stdout).to include(Chef::VERSION) end it "can run and show help" do knife_path = File.expand_path("../../bin/knife", CHEF_SPEC_DATA) knife_cmd = Mixlib::ShellOut.new("#{knife_path} --help") knife_cmd.run_command knife_cmd.error! expect(knife_cmd.stdout).to include("Usage") end end chef-12.14.60/spec/functional/knife/ssh_spec.rb000066400000000000000000000202031276456504500212110ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 "tiny_server" describe Chef::Knife::Ssh do before(:each) do Chef::Knife::Ssh.load_deps @server = TinyServer::Manager.new @server.start end after(:each) do @server.stop end let(:ssh_config) { Hash.new } before do allow(Net::SSH).to receive(:configuration_for).and_return(ssh_config) end # Force log level to info. around do |ex| old_level = Chef::Log.level begin Chef::Log.level = :info ex.run ensure Chef::Log.level = old_level end end describe "identity file" do context "when knife[:ssh_identity_file] is set" do before do setup_knife(["*:*", "uptime"]) Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa" end it "uses the ssh_identity_file" do @knife.run expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa") end end context "when knife[:ssh_identity_file] is set and frozen" do before do setup_knife(["*:*", "uptime"]) Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa".freeze end it "uses the ssh_identity_file" do @knife.run expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa") end end context "when -i is provided" do before do setup_knife(["-i ~/.ssh/aws.rsa", "*:*", "uptime"]) Chef::Config[:knife][:ssh_identity_file] = nil end it "should use the value on the command line" do @knife.run expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa") end it "should override what is set in knife.rb" do Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/other.rsa" @knife.run expect(@knife.config[:ssh_identity_file]).to eq("~/.ssh/aws.rsa") end end context "when knife[:ssh_identity_file] is not provided]" do before do setup_knife(["*:*", "uptime"]) Chef::Config[:knife][:ssh_identity_file] = nil end it "uses the default" do @knife.run expect(@knife.config[:ssh_identity_file]).to eq(nil) end end end describe "port" do context "when -p 31337 is provided" do before do setup_knife(["-p 31337", "*:*", "uptime"]) end it "uses the ssh_port" do @knife.run expect(@knife.config[:ssh_port]).to eq("31337") end end end describe "user" do context "when knife[:ssh_user] is set" do before do setup_knife(["*:*", "uptime"]) Chef::Config[:knife][:ssh_user] = "ubuntu" end it "uses the ssh_user" do @knife.run expect(@knife.config[:ssh_user]).to eq("ubuntu") end end context "when knife[:ssh_user] is set and frozen" do before do setup_knife(["*:*", "uptime"]) Chef::Config[:knife][:ssh_user] = "ubuntu".freeze end it "uses the ssh_user" do @knife.run expect(@knife.config[:ssh_user]).to eq("ubuntu") end end context "when -x is provided" do before do setup_knife(["-x ubuntu", "*:*", "uptime"]) Chef::Config[:knife][:ssh_user] = nil end it "should use the value on the command line" do @knife.run expect(@knife.config[:ssh_user]).to eq("ubuntu") end it "should override what is set in knife.rb" do Chef::Config[:knife][:ssh_user] = "root" @knife.run expect(@knife.config[:ssh_user]).to eq("ubuntu") end end context "when knife[:ssh_user] is not provided]" do before do setup_knife(["*:*", "uptime"]) Chef::Config[:knife][:ssh_user] = nil end it "uses the default (current user)" do @knife.run expect(@knife.config[:ssh_user]).to eq(nil) end end end describe "attribute" do context "when knife[:ssh_attribute] is set" do before do setup_knife(["*:*", "uptime"]) Chef::Config[:knife][:ssh_attribute] = "ec2.public_hostname" end it "uses the ssh_attribute" do @knife.run expect(@knife.get_ssh_attribute(Chef::Node.new)).to eq("ec2.public_hostname") end end context "when knife[:ssh_attribute] is not provided]" do before do setup_knife(["*:*", "uptime"]) Chef::Config[:knife][:ssh_attribute] = nil end it "uses the default" do @knife.run expect(@knife.get_ssh_attribute(Chef::Node.new)).to eq("fqdn") end end context "when -a ec2.public_ipv4 is provided" do before do setup_knife(["-a ec2.public_hostname", "*:*", "uptime"]) Chef::Config[:knife][:ssh_attribute] = nil end it "should use the value on the command line" do @knife.run expect(@knife.config[:attribute]).to eq("ec2.public_hostname") end it "should override what is set in knife.rb" do # This is the setting imported from knife.rb Chef::Config[:knife][:ssh_attribute] = "fqdn" # Then we run knife with the -a flag, which sets the above variable setup_knife(["-a ec2.public_hostname", "*:*", "uptime"]) @knife.run expect(@knife.config[:attribute]).to eq("ec2.public_hostname") end end end describe "gateway" do context "when knife[:ssh_gateway] is set" do before do setup_knife(["*:*", "uptime"]) Chef::Config[:knife][:ssh_gateway] = "user@ec2.public_hostname" end it "uses the ssh_gateway" do expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", {}) @knife.run expect(@knife.config[:ssh_gateway]).to eq("user@ec2.public_hostname") end end context "when -G user@ec2.public_hostname is provided" do before do setup_knife(["-G user@ec2.public_hostname", "*:*", "uptime"]) Chef::Config[:knife][:ssh_gateway] = nil end it "uses the ssh_gateway" do expect(@knife.session).to receive(:via).with("ec2.public_hostname", "user", {}) @knife.run expect(@knife.config[:ssh_gateway]).to eq("user@ec2.public_hostname") end end context "when the gateway requires a password" do before do setup_knife(["-G user@ec2.public_hostname", "*:*", "uptime"]) Chef::Config[:knife][:ssh_gateway] = nil allow(@knife.session).to receive(:via) do |host, user, options| raise Net::SSH::AuthenticationFailed unless options[:password] end end it "should prompt the user for a password" do expect(@knife.ui).to receive(:ask).with("Enter the password for user@ec2.public_hostname: ").and_return("password") @knife.run end end end def setup_knife(params = []) @knife = Chef::Knife::Ssh.new(params) # We explicitly avoid running #configure_chef, which would read a knife.rb # if available, but #merge_configs (which is called by #configure_chef) is # necessary to have default options merged in. @knife.merge_configs allow(@knife).to receive(:ssh_command) { 0 } @api = TinyServer::API.instance @api.clear Chef::Config[:node_name] = nil Chef::Config[:client_key] = nil Chef::Config[:chef_server_url] = "http://localhost:9000" @api.get("/search/node?q=*:*&sort=X_CHEF_id_CHEF_X%20asc&start=0", 200) do %({"total":1, "start":0, "rows":[{"name":"i-xxxxxxxx", "json_class":"Chef::Node", "automatic":{"fqdn":"the.fqdn", "ec2":{"public_hostname":"the_public_hostname"}},"recipes":[]}]}) end end end chef-12.14.60/spec/functional/mixin/000077500000000000000000000000001276456504500171105ustar00rootroot00000000000000chef-12.14.60/spec/functional/mixin/powershell_out_spec.rb000066400000000000000000000031501276456504500235210ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mixin/powershell_out" describe Chef::Mixin::PowershellOut, windows_only: true do include Chef::Mixin::PowershellOut describe "#powershell_out" do it "runs a powershell command and collects stdout" do expect(powershell_out("get-process").run_command.stdout).to match /Handles\s+NPM\(K\)\s+PM\(K\)\s+WS\(K\)\s+VM\(M\)\s+CPU\(s\)\s+Id\s+/ end it "does not raise exceptions when the command is invalid" do powershell_out("this-is-not-a-valid-command").run_command end end describe "#powershell_out!" do it "runs a powershell command and collects stdout" do expect(powershell_out!("get-process").run_command.stdout).to match /Handles\s+NPM\(K\)\s+PM\(K\)\s+WS\(K\)\s+VM\(M\)\s+CPU\(s\)\s+Id\s+/ end it "raises exceptions when the command is invalid" do expect { powershell_out!("this-is-not-a-valid-command").run_command }.to raise_exception(Mixlib::ShellOut::ShellCommandFailed) end end end chef-12.14.60/spec/functional/mixin/shell_out_spec.rb000066400000000000000000000031221276456504500224430ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Mixin::ShellOut do include Chef::Mixin::ShellOut describe "shell_out_with_systems_locale" do describe "when environment['LC_ALL'] is not set" do it "should use the default shell_out setting" do cmd = if windows? shell_out_with_systems_locale("echo %LC_ALL%") else shell_out_with_systems_locale("echo $LC_ALL") end expect(cmd.stdout.chomp).to match_environment_variable("LC_ALL") end end describe "when environment['LC_ALL'] is set" do it "should use the option's setting" do cmd = if windows? shell_out_with_systems_locale("echo %LC_ALL%", :environment => { "LC_ALL" => "POSIX" }) else shell_out_with_systems_locale("echo $LC_ALL", :environment => { "LC_ALL" => "POSIX" }) end expect(cmd.stdout.chomp).to eq "POSIX" end end end end chef-12.14.60/spec/functional/notifications_spec.rb000066400000000000000000000216671276456504500222100ustar00rootroot00000000000000require "spec_helper" require "chef/recipe" # The goal of these tests is to make sure that loading resources from a file creates the necessary notifications. # Then once converge has started, both immediate and delayed notifications are called as the resources are converged. # We want to do this WITHOUT actually converging any resources - we don't want to take time changing the system, # we just want to make sure the run_context, the notification DSL and the converge hooks are working together # to perform notifications. # This test is extremely fragile since it mocks MANY different systems at once - any of them changes, this test # breaks describe "Notifications" do # We always pretend we are on OSx because that has a specific provider (HomebrewProvider) so it # tests the translation from Provider => HomebrewProvider let(:node) do n = Chef::Node.new n.override[:os] = "darwin" n end let(:cookbook_collection) { double("Chef::CookbookCollection").as_null_object } let(:events) { double("Chef::EventDispatch::Dispatcher").as_null_object } let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) } let(:recipe) { Chef::Recipe.new("notif", "test", run_context) } let(:runner) { Chef::Runner.new(run_context) } before do # By default, every provider will do nothing p = Chef::Provider.new(nil, run_context) allow_any_instance_of(Chef::Resource).to receive(:provider_for_action).and_return(p) allow(p).to receive(:run_action) end it "should subscribe from one resource to another" do log_resource = recipe.declare_resource(:log, "subscribed-log") do message "This is a log message" action :nothing subscribes :write, "package[vim]", :immediately end package_resource = recipe.declare_resource(:package, "vim") do action :install end expect(log_resource).to receive(:run_action).with(:nothing, nil, nil).and_call_original expect(package_resource).to receive(:run_action).with(:install, nil, nil).and_call_original update_action(package_resource) expect(log_resource).to receive(:run_action).with(:write, :immediate, package_resource).and_call_original runner.converge end it "should notify from one resource to another immediately" do log_resource = recipe.declare_resource(:log, "log") do message "This is a log message" action :write notifies :install, "package[vim]", :immediately end package_resource = recipe.declare_resource(:package, "vim") do action :nothing end expect(log_resource).to receive(:run_action).with(:write, nil, nil).and_call_original update_action(log_resource) expect(package_resource).to receive(:run_action).with(:install, :immediate, log_resource).ordered.and_call_original expect(package_resource).to receive(:run_action).with(:nothing, nil, nil).ordered.and_call_original runner.converge end it "should notify from one resource to another before" do log_resource = recipe.declare_resource(:log, "log") do message "This is a log message" action :write notifies :install, "package[vim]", :before end update_action(log_resource, 2) package_resource = recipe.declare_resource(:package, "vim") do action :nothing end actions = [] [ log_resource, package_resource ].each do |resource| allow(resource).to receive(:run_action).and_wrap_original do |m, action, notification_type, notifying_resource| actions << { resource: resource.to_s, action: action } actions[-1][:why_run] = Chef::Config[:why_run] if Chef::Config[:why_run] actions[-1][:notification_type] = notification_type if notification_type actions[-1][:notifying_resource] = notifying_resource.to_s if notifying_resource m.call(action, notification_type, notifying_resource) end end runner.converge expect(actions).to eq [ # First it runs why-run to check if the resource would update { resource: log_resource.to_s, action: :write, why_run: true }, # Then it runs the before action { resource: package_resource.to_s, action: :install, notification_type: :before, notifying_resource: log_resource.to_s }, # Then it runs the actual action { resource: log_resource.to_s, action: :write }, { resource: package_resource.to_s, action: :nothing }, ] end it "should not notify from one resource to another before if the resource is not updated" do log_resource = recipe.declare_resource(:log, "log") do message "This is a log message" action :write notifies :install, "package[vim]", :before end package_resource = recipe.declare_resource(:package, "vim") do action :nothing end actions = [] [ log_resource, package_resource ].each do |resource| allow(resource).to receive(:run_action).and_wrap_original do |m, action, notification_type, notifying_resource| actions << { resource: resource.to_s, action: action } actions[-1][:why_run] = Chef::Config[:why_run] if Chef::Config[:why_run] actions[-1][:notification_type] = notification_type if notification_type actions[-1][:notifying_resource] = notifying_resource.to_s if notifying_resource m.call(action, notification_type, notifying_resource) end end runner.converge expect(actions).to eq [ # First it runs why-run to check if the resource would update { resource: log_resource.to_s, action: :write, why_run: true }, # Then it does NOT run the before action # Then it runs the actual action { resource: log_resource.to_s, action: :write }, { resource: package_resource.to_s, action: :nothing }, ] end it "should notify from one resource to another delayed" do log_resource = recipe.declare_resource(:log, "log") do message "This is a log message" action :write notifies :install, "package[vim]", :delayed end package_resource = recipe.declare_resource(:package, "vim") do action :nothing end expect(log_resource).to receive(:run_action).with(:write, nil, nil).and_call_original update_action(log_resource) expect(package_resource).to receive(:run_action).with(:nothing, nil, nil).ordered.and_call_original expect(package_resource).to receive(:run_action).with(:install, :delayed, nil).ordered.and_call_original runner.converge end describe "when one resource is defined lazily" do it "subscribes to a resource defined in a ruby block" do r = recipe t = self ruby_block = recipe.declare_resource(:ruby_block, "rblock") do block do log_resource = r.declare_resource(:log, "log") do message "This is a log message" action :write end t.expect(log_resource).to t.receive(:run_action).with(:write, nil, nil).and_call_original t.update_action(log_resource) end end package_resource = recipe.declare_resource(:package, "vim") do action :nothing subscribes :install, "log[log]", :delayed end # RubyBlock needs to be able to run for our lazy examples to work - and it alone cannot affect the system expect(ruby_block).to receive(:provider_for_action).and_call_original expect(package_resource).to receive(:run_action).with(:nothing, nil, nil).ordered.and_call_original expect(package_resource).to receive(:run_action).with(:install, :delayed, nil).ordered.and_call_original runner.converge end it "notifies from inside a ruby_block to a resource defined outside" do r = recipe t = self ruby_block = recipe.declare_resource(:ruby_block, "rblock") do block do log_resource = r.declare_resource(:log, "log") do message "This is a log message" action :write notifies :install, "package[vim]", :immediately end t.expect(log_resource).to t.receive(:run_action).with(:write, nil, nil).and_call_original t.update_action(log_resource) end end package_resource = recipe.declare_resource(:package, "vim") do action :nothing end # RubyBlock needs to be able to run for our lazy examples to work - and it alone cannot affect the system expect(ruby_block).to receive(:provider_for_action).and_call_original expect(package_resource).to receive(:run_action).with(:install, :immediate, instance_of(Chef::Resource::Log)).ordered.and_call_original expect(package_resource).to receive(:run_action).with(:nothing, nil, nil).ordered.and_call_original runner.converge end end # Mocks having the provider run successfully and update the resource def update_action(resource, times = 1) p = Chef::Provider.new(resource, run_context) expect(resource).to receive(:provider_for_action).exactly(times).times.and_return(p) expect(p).to receive(:run_action).exactly(times).times { resource.updated_by_last_action(true) } end end chef-12.14.60/spec/functional/provider/000077500000000000000000000000001276456504500176165ustar00rootroot00000000000000chef-12.14.60/spec/functional/provider/remote_file/000077500000000000000000000000001276456504500221105ustar00rootroot00000000000000chef-12.14.60/spec/functional/provider/remote_file/cache_control_data_spec.rb000077500000000000000000000065641276456504500272610ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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 "uri" describe Chef::Provider::RemoteFile::CacheControlData do before do @original_config = Chef::Config.hash_dup end after do Chef::Config.configuration = @original_config if @original_config end before(:each) do Chef::Config[:file_cache_path] = Dir.mktmpdir end after(:each) do FileUtils.rm_rf(Chef::Config[:file_cache_path]) end let(:uri) { URI.parse("http://www.bing.com/robots.txt") } describe "when the cache control data save method is invoked" do subject(:cache_control_data) do Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, file_checksum) end # the checksum of the file last we fetched it. let(:file_checksum) { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } let(:etag) { "\"a-strong-identifier\"" } let(:mtime) { "Thu, 01 Aug 2013 08:16:32 GMT" } before do cache_control_data.etag = etag cache_control_data.mtime = mtime cache_control_data.checksum = file_checksum end it "writes data to the cache" do cache_control_data.save end it "writes the data to the cache and the same data can be read back" do cache_control_data.save saved_cache_control_data = Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, file_checksum) expect(saved_cache_control_data.etag).to eq(cache_control_data.etag) expect(saved_cache_control_data.mtime).to eq(cache_control_data.mtime) expect(saved_cache_control_data.checksum).to eq(cache_control_data.checksum) end # Cover the very long remote file path case -- see CHEF-4422 where # local cache file names generated from the long uri exceeded # local file system path limits resulting in exceptions from # file system API's on both Windows and Unix systems. context "when the length of the uri exceeds the path length limits for the local file system" do let(:uri_exceeds_file_system_limit) do URI.parse("http://www.bing.com/" + ("0" * 1024)) end let(:uri) { uri_exceeds_file_system_limit } it "writes data to the cache" do expect do cache_control_data.save end.not_to raise_error end it "writes the data to the cache and the same data can be read back" do cache_control_data.save saved_cache_control_data = Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, file_checksum) expect(saved_cache_control_data.etag).to eq(cache_control_data.etag) expect(saved_cache_control_data.mtime).to eq(cache_control_data.mtime) expect(saved_cache_control_data.checksum).to eq(cache_control_data.checksum) end end end end chef-12.14.60/spec/functional/provider/whyrun_safe_ruby_block_spec.rb000066400000000000000000000027721276456504500257320ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::WhyrunSafeRubyBlock do let(:node) { Chef::Node.new } let(:run_context) do events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, events) end before do $evil_global_evil_laugh = :wahwah Chef::Config[:why_run] = true end after do Chef::Config[:why_run] = false end describe "when testing the resource" do let(:new_resource) do r = Chef::Resource::WhyrunSafeRubyBlock.new("reload all", run_context) r.block { $evil_global_evil_laugh = :mwahahaha } r end it "updates the evil laugh, even in why-run mode" do Array(new_resource.action).each { |action| new_resource.run_action(action) } expect($evil_global_evil_laugh).to eq(:mwahahaha) expect(new_resource).to be_updated end end end chef-12.14.60/spec/functional/rebooter_spec.rb000066400000000000000000000062661276456504500211560ustar00rootroot00000000000000# # Author:: Chris Doherty ) # Copyright:: Copyright 2014-2016, Chef, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Platform::Rebooter do let(:reboot_info) do { :delay_mins => 5, :requested_by => "reboot resource functional test", :reason => "rebooter spec test", } end def create_resource resource = Chef::Resource::Reboot.new(expected[:requested_by], run_context) resource.delay_mins(expected[:delay_mins]) resource.reason(expected[:reason]) resource end let(:run_context) do node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, events) end let(:expected) do { :windows => 'shutdown /r /t 300 /c "rebooter spec test"', :linux => 'shutdown -r +5 "rebooter spec test"', } end let(:rebooter) { Chef::Platform::Rebooter } describe "#reboot_if_needed!" do it "should not call #shell_out! when reboot has not been requested" do expect(rebooter).to receive(:shell_out!).exactly(0).times expect(rebooter).to receive(:reboot_if_needed!).once.and_call_original rebooter.reboot_if_needed!(run_context.node) end describe "calling #shell_out! to reboot" do before(:each) do run_context.request_reboot(reboot_info) end after(:each) do run_context.cancel_reboot end shared_context "test a reboot method" do def test_rebooter_method(method_sym, is_windows, expected_reboot_str) allow(ChefConfig).to receive(:windows?).and_return(is_windows) expect(rebooter).to receive(:shell_out!).once.with(expected_reboot_str) expect(rebooter).to receive(method_sym).once.and_call_original rebooter.send(method_sym, run_context.node) end end describe "when using #reboot_if_needed!" do include_context "test a reboot method" it "should produce the correct string on Windows" do test_rebooter_method(:reboot_if_needed!, true, expected[:windows]) end it "should produce the correct (Linux-specific) string on non-Windows" do test_rebooter_method(:reboot_if_needed!, false, expected[:linux]) end end describe "when using #reboot!" do include_context "test a reboot method" it "should produce the correct string on Windows" do test_rebooter_method(:reboot!, true, expected[:windows]) end it "should produce the correct (Linux-specific) string on non-Windows" do test_rebooter_method(:reboot!, false, expected[:linux]) end end end end end chef-12.14.60/spec/functional/resource/000077500000000000000000000000001276456504500176135ustar00rootroot00000000000000chef-12.14.60/spec/functional/resource/aix_service_spec.rb000077500000000000000000000067521276456504500234700ustar00rootroot00000000000000# encoding: UTF-8 # # Author:: Kaustubh Deorukhkar () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "functional/resource/base" require "chef/mixin/shell_out" shared_examples "src service" do include Chef::Mixin::ShellOut def service_should_be_started expect(shell_out!("lssrc -a | grep #{new_resource.service_name}").stdout.split(" ").last).to eq("active") end def service_should_be_stopped expect(shell_out!("lssrc -a | grep #{new_resource.service_name}").stdout.split(" ").last).to eq("inoperative") end def get_service_pid args = shell_out!("lssrc -a | grep #{new_resource.service_name}").stdout.split(" ") if args.length == 3 args[1] else args[2] end end describe "start service" do it "should start the service" do new_resource.run_action(:start) service_should_be_started end end describe "stop service" do before do new_resource.run_action(:start) end it "should stop the service" do new_resource.run_action(:stop) service_should_be_stopped end end describe "restart service" do before do new_resource.run_action(:start) end it "should restart the service" do new_resource.run_action(:restart) service_should_be_started end end end describe Chef::Resource::Service, :requires_root, :aix_only do include Chef::Mixin::ShellOut def get_user_id shell_out("id -u #{ENV['USER']}").stdout.chomp end describe "When service is a subsystem" do before(:all) do script_dir = File.join(File.dirname(__FILE__), "/../assets/") shell_out!("mkssys -s ctestsys -p #{script_dir}/testchefsubsys -u #{get_user_id} -S -n 15 -f 9 -R -Q") end after(:each) do shell_out("stopsrc -s ctestsys") end after(:all) do shell_out!("rmssys -s ctestsys") end let(:new_resource) do new_resource = Chef::Resource::Service.new("ctestsys", run_context) new_resource end let(:provider) do provider = new_resource.provider_for_action(new_resource.action) provider end it_behaves_like "src service" end # Cannot run this test on a WPAR describe "When service is a group", :not_wpar do before(:all) do script_dir = File.join(File.dirname(__FILE__), "/../assets/") shell_out!("mkssys -s ctestsys -p #{script_dir}/testchefsubsys -u #{get_user_id} -S -n 15 -f 9 -R -Q -G ctestgrp") end after(:each) do shell_out("stopsrc -g ctestgrp") end after(:all) do # rmssys supports only -s option. shell_out!("rmssys -s ctestsys") end let(:new_resource) do new_resource = Chef::Resource::Service.new("ctestgrp", run_context) new_resource end let(:provider) do provider = new_resource.provider_for_action(new_resource.action) provider end it_behaves_like "src service" end end chef-12.14.60/spec/functional/resource/aixinit_service_spec.rb000077500000000000000000000143051276456504500243450ustar00rootroot00000000000000# encoding: UTF-8 # # Author:: Kaustubh Deorukhkar () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "functional/resource/base" require "chef/mixin/shell_out" require "fileutils" describe Chef::Resource::Service, :requires_root, :aix_only do include Chef::Mixin::ShellOut # Platform specific validation routines. def service_should_be_started(file_name) # The existence of this file indicates that the service was started. expect(File.exists?("#{Dir.tmpdir}/#{file_name}")).to be_truthy end def service_should_be_stopped(file_name) expect(File.exists?("#{Dir.tmpdir}/#{file_name}")).to be_falsey end def valide_symlinks(expected_output, run_level = nil, status = nil, priority = nil) directory = [] if priority.is_a? Hash priority.each do |level, o| directory << "/etc/rc.d/rc#{level}.d/#{(o[0] == :start ? 'S' : 'K')}#{o[1]}#{new_resource.service_name}" end directory else directory << "/etc/rc.d/rc#{run_level}.d/#{status}#{priority}#{new_resource.service_name}" end expect(Dir.glob(directory)).to eq(expected_output) File.delete(*directory) end def delete_test_files files = Dir.glob("#{Dir.tmpdir}/chefinit[a-z_]*.txt") File.delete(*files) end # Actual tests let(:new_resource) do new_resource = Chef::Resource::Service.new("chefinittest", run_context) new_resource.provider Chef::Provider::Service::AixInit new_resource.supports({ :status => true, :restart => true, :reload => true }) new_resource end let(:provider) do provider = new_resource.provider_for_action(new_resource.action) provider end before(:all) do File.delete("/etc/rc.d/init.d/chefinittest") if File.exists?("/etc/rc.d/init.d/chefinittest") FileUtils.cp("#{File.join(File.dirname(__FILE__), "/../assets/chefinittest")}", "/etc/rc.d/init.d/chefinittest") end after(:all) do File.delete("/etc/rc.d/init.d/chefinittest") if File.exists?("/etc/rc.d/init.d/chefinittest") end before(:each) do delete_test_files end after(:each) do delete_test_files end describe "start service" do it "should start the service" do new_resource.run_action(:start) service_should_be_started("chefinittest.txt") end end describe "stop service" do before do new_resource.run_action(:start) end it "should stop the service" do new_resource.run_action(:stop) service_should_be_stopped("chefinittest.txt") end end describe "restart service" do before do new_resource.run_action(:start) end it "should restart the service" do new_resource.run_action(:restart) service_should_be_started("chefinittest_restart.txt") end end describe "reload service" do before do new_resource.run_action(:start) end it "should reload the service" do new_resource.run_action(:reload) service_should_be_started("chefinittest_reload.txt") end end describe "enable service" do context "when the service doesn't set a priority" do it "creates symlink with status S" do new_resource.run_action(:enable) valide_symlinks(["/etc/rc.d/rc2.d/Schefinittest"], 2, "S") end end context "when the service sets a simple priority (integer)" do before do new_resource.priority(75) end it "creates a symlink with status S and a priority" do new_resource.run_action(:enable) valide_symlinks(["/etc/rc.d/rc2.d/S75chefinittest"], 2, "S", 75) end end context "when the service sets complex priorities (hash)" do before do priority = { 2 => [:start, 20], 3 => [:stop, 10] } new_resource.priority(priority) end it "create symlink with status start (S) or stop (K) and a priority " do new_resource.run_action(:enable) valide_symlinks(["/etc/rc.d/rc2.d/S20chefinittest", "/etc/rc.d/rc3.d/K10chefinittest"], 2, "S", new_resource.priority) end end end describe "disable_service" do context "when the service doesn't set a priority" do before do File.symlink("/etc/rc.d/init.d/chefinittest", "/etc/rc.d/rc2.d/Schefinittest") end after do File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exists?("/etc/rc.d/rc2.d/chefinittest") end it "creates symlink with status K" do new_resource.run_action(:disable) valide_symlinks(["/etc/rc.d/rc2.d/Kchefinittest"], 2, "K") end end context "when the service sets a simple priority (integer)" do before do new_resource.priority(75) File.symlink("/etc/rc.d/init.d/chefinittest", "/etc/rc.d/rc2.d/Schefinittest") end after do File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exists?("/etc/rc.d/rc2.d/chefinittest") end it "creates a symlink with status K and a priority" do new_resource.run_action(:disable) valide_symlinks(["/etc/rc.d/rc2.d/K25chefinittest"], 2, "K", 25) end end context "when the service sets complex priorities (hash)" do before do @priority = { 2 => [:stop, 20], 3 => [:start, 10] } new_resource.priority(@priority) File.symlink("/etc/rc.d/init.d/chefinittest", "/etc/rc.d/rc2.d/Schefinittest") end after do File.delete("/etc/rc.d/rc2.d/Schefinittest") if File.exists?("/etc/rc.d/rc2.d/chefinittest") end it "create symlink with status stop (K) and a priority " do new_resource.run_action(:disable) valide_symlinks(["/etc/rc.d/rc2.d/K80chefinittest"], 2, "K", 80) end end end end chef-12.14.60/spec/functional/resource/base.rb000066400000000000000000000017601276456504500210560ustar00rootroot00000000000000# # Author:: Kaustubh Deorukhkar () # Copyright:: Copyright 2013-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. # def run_context @run_context ||= begin node = Chef::Node.new node.default[:platform] = ohai[:platform] node.default[:platform_version] = ohai[:platform_version] node.default[:os] = ohai[:os] events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, events) end end chef-12.14.60/spec/functional/resource/bash_spec.rb000066400000000000000000000061371276456504500220760ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "functional/resource/base" describe Chef::Resource::Bash, :unix_only do let(:code) { "echo hello" } let(:resource) do resource = Chef::Resource::Bash.new("foo_resource", run_context) resource.code(code) resource end describe "when setting the command attribute" do let (:command) { "wizard racket" } # in Chef-12 the `command` attribute is largely useless, but does set the identity attribute # so that notifications need to target the value of the command. it will not run the `command` # and if it is given without a code block then it does nothing and always succeeds. describe "in Chef-12", chef: "< 13" do it "gets the commmand attribute from the name" do expect(resource.command).to eql("foo_resource") end it "sets the resource identity to the command name" do resource.command command expect(resource.identity).to eql(command) end it "warns when the code is not present and a useless `command` is present" do expect(Chef::Log).to receive(:warn).with(/coding error/) expect(Chef::Log).to receive(:warn).with(/deprecated/) resource.code nil resource.command command expect { resource.run_action(:run) }.not_to raise_error end describe "when the code is not present" do let(:code) { nil } it "warns" do expect(Chef::Log).to receive(:warn) expect { resource.run_action(:run) }.not_to raise_error end end end # in Chef-13 the `command` attribute needs to be for internal use only describe "in Chef-13", chef: ">= 13" do it "should raise an exception when trying to set the command" do expect { resource.command command }.to raise_error # FIXME: add a real error in Chef-13 end it "should initialize the command to nil" do expect(resource.command).to be_nil end describe "when the code is not present" do let(:code) { nil } it "raises an exception" do expect { resource.run_action(:run) }.to raise_error # FIXME: add a real error in Chef-13 expect { resource.run_action(:run) }.not_to raise_error end end end end it "times out when a timeout is set on the resource" do resource.code "sleep 600" resource.timeout 0.1 expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::CommandTimeout) end end chef-12.14.60/spec/functional/resource/batch_spec.rb000066400000000000000000000017331276456504500222370ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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::Resource::WindowsScript::Batch, :windows_only do include_context Chef::Resource::WindowsScript let(:output_command) { " > " } let (:architecture_command) { "@echo %PROCESSOR_ARCHITECTURE%" } it_behaves_like "a Windows script running on Windows" end chef-12.14.60/spec/functional/resource/bff_spec.rb000066400000000000000000000065601276456504500217160ustar00rootroot00000000000000# # Author:: Prabhu Das () # Copyright:: Copyright 2013-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 "functional/resource/base" require "chef/mixin/shell_out" # Run the test only for AIX platform. describe Chef::Resource::BffPackage, :requires_root, :external => ohai[:platform] != "aix" do include Chef::Mixin::ShellOut let(:new_resource) do new_resource = Chef::Resource::BffPackage.new(@pkg_name, run_context) new_resource.source @pkg_path new_resource end def bff_pkg_should_be_installed(resource) expect(shell_out("lslpp -L #{resource.name}").exitstatus).to eq(0) ::File.exists?("/usr/PkgA/bin/acommand") end def bff_pkg_should_be_removed(resource) expect(shell_out("lslpp -L #{resource.name}").exitstatus).to eq(1) !::File.exists?("/usr/PkgA/bin/acommand") end before(:all) do @pkg_name = "PkgA.rte" @pkg_path = "#{Dir.tmpdir}/PkgA.1.0.0.0.bff" FileUtils.cp "spec/functional/assets/PkgA.1.0.0.0.bff" , @pkg_path end after(:all) do FileUtils.rm @pkg_path end context "package install action" do it "should install a package" do new_resource.run_action(:install) bff_pkg_should_be_installed(new_resource) end after(:each) do shell_out("installp -u #{@pkg_name}") end end context "package install action with options" do it "should install a package" do new_resource.options("-e/tmp/installp.log") new_resource.run_action(:install) bff_pkg_should_be_installed(new_resource) end after(:each) do shell_out("installp -u #{@pkg_name}") FileUtils.rm "#{Dir.tmpdir}/installp.log" end end context "package upgrade action" do before(:each) do shell_out("installp -aYF -d #{@pkg_path} #{@pkg_name}") @pkg_path = "#{Dir.tmpdir}/PkgA.2.0.0.0.bff" FileUtils.cp "spec/functional/assets/PkgA.2.0.0.0.bff" , @pkg_path end it "should upgrade package" do new_resource.run_action(:install) bff_pkg_should_be_installed(new_resource) end after(:each) do shell_out("installp -u #{@pkg_name}") FileUtils.rm @pkg_path end end context "package remove action" do before(:each) do shell_out("installp -aYF -d #{@pkg_path} #{@pkg_name}") end it "should remove an installed package" do new_resource.run_action(:remove) bff_pkg_should_be_removed(new_resource) end end context "package remove action with options" do before(:each) do shell_out("installp -aYF -d #{@pkg_path} #{@pkg_name}") end it "should remove an installed package" do new_resource.options("-e/tmp/installp.log") new_resource.run_action(:remove) bff_pkg_should_be_removed(new_resource) end after(:each) do FileUtils.rm "#{Dir.tmpdir}/installp.log" end end end chef-12.14.60/spec/functional/resource/chocolatey_package_spec.rb000066400000000000000000000076711276456504500247720ustar00rootroot00000000000000# # Author:: Matt Wrock () # Copyright:: Copyright (c) 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/mixin/powershell_out" describe Chef::Resource::ChocolateyPackage, :windows_only do include Chef::Mixin::PowershellOut before(:all) do powershell_out!("iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))") unless ENV["PATH"] =~ /chocolatey\\bin/ ENV["PATH"] = "C:\\ProgramData\\chocolatey\\bin;#{ENV["PATH"]}" end end let(:package_name) { "test-A" } let(:package_list) { proc { powershell_out!("choco list -lo -r #{Array(package_name).join(' ')}").stdout.chomp } } let(:package_source) { File.join(CHEF_SPEC_ASSETS, "chocolatey_feed") } subject do new_resource = Chef::Resource::ChocolateyPackage.new("test choco package", run_context) new_resource.package_name package_name new_resource.source package_source new_resource end context "installing a package" do after { remove_package } it "installs the latest version" do subject.run_action(:install) expect(package_list.call).to eq("#{package_name}|2.0") end it "does not install if already installed" do subject.run_action(:install) subject.run_action(:install) expect(subject).not_to be_updated_by_last_action end it "installs version given" do subject.version "1.0" subject.run_action(:install) expect(package_list.call).to eq("#{package_name}|1.0") end it "installs new version if one is already installed" do subject.version "1.0" subject.run_action(:install) expect(package_list.call).to eq("#{package_name}|1.0") subject.version "2.0" subject.run_action(:install) expect(package_list.call).to eq("#{package_name}|2.0") end context "installing multiple packages" do let(:package_name) { [ "test-A", "test-B" ] } it "installs both packages" do subject.run_action(:install) expect(package_list.call).to eq("test-A|2.0\r\ntest-B|1.0") end end it "raises if package is not found" do subject.package_name "blah" expect { subject.run_action(:install) }.to raise_error Chef::Exceptions::Package end end context "upgrading a package" do after { remove_package } it "upgrades to a specific version" do subject.version "1.0" subject.run_action(:install) expect(package_list.call).to eq("#{package_name}|1.0") subject.version "1.5" subject.run_action(:upgrade) expect(package_list.call).to eq("#{package_name}|1.5") end it "upgrades to the latest version if no version given" do subject.version "1.0" subject.run_action(:install) expect(package_list.call).to eq("#{package_name}|1.0") subject2 = Chef::Resource::ChocolateyPackage.new("test-A", run_context) subject2.source package_source subject2.run_action(:upgrade) expect(package_list.call).to eq("#{package_name}|2.0") end end context "removing a package" do it "removes an installed package" do subject.run_action(:install) remove_package expect(package_list.call).to eq("") end end def remove_package pkg_to_remove = Chef::Resource::ChocolateyPackage.new(package_name, run_context) pkg_to_remove.source = package_source pkg_to_remove.run_action(:remove) end end chef-12.14.60/spec/functional/resource/cookbook_file_spec.rb000066400000000000000000000052361276456504500237650ustar00rootroot00000000000000# # Author:: Tim Hinderliter () # 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" describe Chef::Resource::CookbookFile do include_context Chef::Resource::File let(:file_base) { "cookbook_file_spec" } let(:source) { "java.response" } let(:cookbook_name) { "java" } let(:expected_content) do content = File.open(File.join(CHEF_SPEC_DATA, "cookbooks", "java", "files", "default", "java.response"), "rb") do |f| f.read end content.force_encoding(Encoding::BINARY) if content.respond_to?(:force_encoding) content end let(:default_mode) { (0666 & ~File.umask).to_s(8) } it_behaves_like "a securable resource with reporting" def create_resource # set up cookbook collection for this run to use, based on our # spec data. cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) Chef::Cookbook::FileVendor.fetch_from_disk(cookbook_repo) loader = Chef::CookbookLoader.new(cookbook_repo) loader.load_cookbooks cookbook_collection = Chef::CookbookCollection.new(loader) node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, cookbook_collection, events) resource = Chef::Resource::CookbookFile.new(path, run_context) resource.cookbook(cookbook_name) resource.source(source) resource end let(:resource) do create_resource end it_behaves_like "a file resource" # These examples cover CHEF-3467 where unexpected and incorrect # permissions can result on Windows because CookbookFile's # implementation # stages files in temp. context "targets a file outside of the system temp directory" do let(:windows_non_temp_dir) { File.join(ENV["systemdrive"], make_tmpname(file_base, "non-temp")) } let(:path) { File.join(windows_non_temp_dir, make_tmpname(file_base)) } before do FileUtils.mkdir_p(windows_non_temp_dir) if Chef::Platform.windows? end after do FileUtils.rm_r(windows_non_temp_dir) if Chef::Platform.windows? && File.exists?(windows_non_temp_dir) end end end chef-12.14.60/spec/functional/resource/cron_spec.rb000066400000000000000000000151301276456504500221130ustar00rootroot00000000000000# encoding: UTF-8 # # Author:: Kaustubh Deorukhkar () # Copyright:: Copyright 2013-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 "functional/resource/base" require "chef/mixin/shell_out" describe Chef::Resource::Cron, :requires_root, :unix_only do include Chef::Mixin::ShellOut # Platform specific validation routines. def cron_should_exists(cron_name, command) case ohai[:platform] when "aix", "solaris", "opensolaris", "solaris2", "omnios" expect(shell_out("crontab -l #{new_resource.user} | grep \"#{cron_name}\"").exitstatus).to eq(0) expect(shell_out("crontab -l #{new_resource.user} | grep \"#{cron_name}\"").stdout.lines.to_a.size).to eq(1) expect(shell_out("crontab -l #{new_resource.user} | grep \"#{command}\"").exitstatus).to eq(0) expect(shell_out("crontab -l #{new_resource.user} | grep \"#{command}\"").stdout.lines.to_a.size).to eq(1) else expect(shell_out("crontab -l -u #{new_resource.user} | grep \"#{cron_name}\"").exitstatus).to eq(0) expect(shell_out("crontab -l #{new_resource.user} | grep \"#{cron_name}\"").stdout.lines.to_a.size).to eq(0) expect(shell_out("crontab -l -u #{new_resource.user} | grep \"#{command}\"").exitstatus).to eq(0) expect(shell_out("crontab -l #{new_resource.user} | grep \"#{command}\"").stdout.lines.to_a.size).to eq(0) end end def cron_should_not_exists(cron_name) case ohai[:platform] when "aix", "solaris", "opensolaris", "solaris2", "omnios" expect(shell_out("crontab -l #{new_resource.user} | grep \"#{cron_name}\"").exitstatus).to eq(1) expect(shell_out("crontab -l #{new_resource.user} | grep \"#{new_resource.command}\"").stdout.lines.to_a.size).to eq(0) else expect(shell_out("crontab -l -u #{new_resource.user} | grep \"#{cron_name}\"").exitstatus).to eq(1) expect(shell_out("crontab -l -u #{new_resource.user} | grep \"#{new_resource.command}\"").stdout.lines.to_a.size).to eq(0) end end # Actual tests let(:new_resource) do new_resource = Chef::Resource::Cron.new("Chef functional test cron", run_context) new_resource.user "root" # @hourly is not supported on solaris, aix if ohai[:platform] == "solaris" || ohai[:platform] == "solaris2" || ohai[:platform] == "aix" new_resource.minute "0 * * * *" else new_resource.minute "@hourly" end new_resource.hour "" new_resource.day "" new_resource.month "" new_resource.weekday "" new_resource.command "/bin/true" new_resource end let(:provider) do provider = new_resource.provider_for_action(new_resource.action) provider end describe "create action" do after do new_resource.run_action(:delete) end it "should create a crontab entry" do new_resource.run_action(:create) cron_should_exists(new_resource.name, new_resource.command) end it "should create exactly one crontab entry" do 5.times { new_resource.run_action(:create) } cron_should_exists(new_resource.name, new_resource.command) end end describe "delete action" do before do new_resource.run_action(:create) end it "should delete a crontab entry" do # Note that test cron is created by previous test new_resource.run_action(:delete) cron_should_not_exists(new_resource.name) end end exclude_solaris = %w{solaris opensolaris solaris2 omnios}.include?(ohai[:platform]) describe "create action with various attributes", :external => exclude_solaris do def create_and_validate_with_attribute(resource, attribute, value) if ohai[:platform] == "aix" expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::Cron, /Aix cron entry does not support environment variables. Please set them in script and use script in cron./) else resource.run_action(:create) # Verify if the cron is created successfully cron_attribute_should_exists(resource.name, attribute, value) end end def cron_attribute_should_exists(cron_name, attribute, value) return if %w{aix solaris}.include?(ohai[:platform]) # Test if the attribute exists on newly created cron cron_should_exists(cron_name, "") expect(shell_out("crontab -l -u #{new_resource.user} | grep '#{attribute.upcase}=\"#{value}\"'").exitstatus).to eq(0) end after do new_resource.run_action(:delete) end it "should create a crontab entry for mailto attribute" do new_resource.mailto "cheftest@example.com" create_and_validate_with_attribute(new_resource, "mailto", "cheftest@example.com") end it "should create a crontab entry for path attribute" do new_resource.path "/usr/local/bin" create_and_validate_with_attribute(new_resource, "path", "/usr/local/bin") end it "should create a crontab entry for shell attribute" do new_resource.shell "/bin/bash" create_and_validate_with_attribute(new_resource, "shell", "/bin/bash") end it "should create a crontab entry for home attribute" do new_resource.home "/home/opscode" create_and_validate_with_attribute(new_resource, "home", "/home/opscode") end %i{ home mailto path shell }.each do |attr| it "supports an empty string for #{attr} attribute" do new_resource.send(attr, "") create_and_validate_with_attribute(new_resource, attr.to_s, "") end end end describe "negative tests for create action" do after do new_resource.run_action(:delete) end def cron_create_should_raise_exception expect { new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::Cron, /Error updating state of #{new_resource.name}, exit: 1/) cron_should_not_exists(new_resource.name) end it "should not create cron with invalid minute" do new_resource.minute "invalid" cron_create_should_raise_exception end it "should not create cron with invalid user" do new_resource.user "1-really-really-invalid-user-name" cron_create_should_raise_exception end end end chef-12.14.60/spec/functional/resource/deploy_revision_spec.rb000066400000000000000000000740401276456504500243710ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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 "tmpdir" # Deploy relies heavily on symlinks, so it doesn't work on windows. describe Chef::Resource::DeployRevision, :unix_only => true, :requires_git => true do let(:file_cache_path) { Dir.mktmpdir } let(:deploy_directory) { Dir.mktmpdir } # By making restart or other operations write to this file, we can externally # track the order in which those operations happened. let(:observe_order_file) { Tempfile.new("deploy-resource-observe-operations") } before do Chef::Log.level = :info @old_file_cache_path = Chef::Config[:file_cache_path] Chef::Config[:file_cache_path] = file_cache_path end after do Chef::Config[:file_cache_path] = @old_file_cache_path FileUtils.remove_entry_secure deploy_directory if File.exist?(deploy_directory) FileUtils.remove_entry_secure file_cache_path observe_order_file.close FileUtils.remove_entry_secure observe_order_file.path end before(:all) do @ohai = Ohai::System.new @ohai.all_plugins(%w{platform os}) end let(:node) do Chef::Node.new.tap do |n| n.name "rspec-test" n.consume_external_attrs(@ohai.data, {}) end end let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) } # These tests use git's bundle feature, which is a way to export an entire # git repo (or subset of commits) as a single file. # # Generally you can treat a git bundle as a regular git remote. # # See also: http://git-scm.com/2010/03/10/bundles.html let(:git_bundle_repo) { File.expand_path("git_bundles/sinatra-test-app.gitbundle", CHEF_SPEC_DATA) } let(:git_bundle_with_in_repo_callbacks) { File.expand_path("git_bundles/sinatra-test-app-with-callback-files.gitbundle", CHEF_SPEC_DATA) } let(:git_bundle_with_in_repo_symlinks) { File.expand_path("git_bundles/sinatra-test-app-with-symlinks.gitbundle", CHEF_SPEC_DATA) } # This is the fourth version let(:latest_rev) { "3eb5ca6c353c83d9179dd3b29347539829b401f3" } # This is the third version let(:previous_rev) { "6d19a6dbecc8e37f5b2277345885c0c783eb8fb1" } # This is the second version let(:second_rev) { "0827e1b0e5043608ac0a824da5c558e252154ad0" } # This is the sixth version, it is on the "with-deploy-scripts" branch let(:rev_with_in_repo_callbacks) { "2404d015882659754bdb93ad6e4b4d3d02691a82" } # This is the fifth version in the "with-symlinks" branch let(:rev_with_in_repo_symlinks) { "5a4748c52aaea8250b4346a9b8ede95ee3755e28" } # Read values from the +observe_order_file+ and split each line. This way you # can see in which order things really happened. def actual_operations_order IO.read(observe_order_file.path).split("\n").map(&:strip) end # 1. touch `restart.txt` in cwd so we know that the command is run with the # right cwd. # 2. Append +tag+ to the `observe_order_file` so we can check the order in # which operations happen later in the test. def shell_restart_command(tag) "touch restart.txt && echo '#{tag}' >> #{observe_order_file.path}" end let(:basic_deploy_resource) do Chef::Resource::DeployRevision.new(deploy_directory, run_context).tap do |r| r.name "deploy-revision-unit-test" r.repo git_bundle_repo r.symlink_before_migrate({}) r.symlinks({}) end end let(:deploy_to_latest_rev) do basic_deploy_resource.dup.tap do |r| r.revision(latest_rev) r.restart_command shell_restart_command(:deploy_to_latest_rev) end end let(:deploy_to_previous_rev) do basic_deploy_resource.dup.tap do |r| r.revision(previous_rev) r.restart_command shell_restart_command(:deploy_to_previous_rev) end end let(:deploy_to_latest_rev_again) do basic_deploy_resource.dup.tap do |r| r.revision(latest_rev) r.restart_command shell_restart_command(:deploy_to_latest_rev_again) end end let(:deploy_to_previous_rev_again) do basic_deploy_resource.dup.tap do |r| r.revision(previous_rev) r.restart_command shell_restart_command(:deploy_to_previous_rev_again) end end let(:deploy_to_second_rev) do basic_deploy_resource.dup.tap do |r| r.revision(second_rev) r.restart_command shell_restart_command(:deploy_to_second_rev) end end let(:deploy_to_second_rev_again) do basic_deploy_resource.dup.tap do |r| r.revision(second_rev) r.restart_command shell_restart_command(:deploy_to_second_rev_again) end end let(:deploy_to_second_rev_again_again) do basic_deploy_resource.dup.tap do |r| r.revision(second_rev) r.restart_command shell_restart_command(:deploy_to_second_rev_again_again) end end # Computes the full path for +path+ relative to the deploy directory def rel_path(path) File.expand_path(path, deploy_directory) end def actual_current_rev Dir.chdir(rel_path("current")) do `git rev-parse HEAD`.strip end end def self.the_app_is_deployed_at_revision(target_rev_spec) it "deploys the app to the target revision (#{target_rev_spec})" do target_rev = send(target_rev_spec) expect(File).to exist(rel_path("current")) expect(actual_current_rev).to eq(target_rev) # Is the app code actually there? expect(File).to exist(rel_path("current/app/app.rb")) end end context "when deploying a simple app" do describe "for the first time, with the required directory layout precreated" do before do FileUtils.mkdir_p(rel_path("releases")) FileUtils.mkdir_p(rel_path("shared")) deploy_to_latest_rev.run_action(:deploy) end the_app_is_deployed_at_revision(:latest_rev) it "restarts the application" do expect(File).to exist(rel_path("current/restart.txt")) expect(actual_operations_order).to eq(%w{deploy_to_latest_rev}) end it "is marked as updated" do expect(deploy_to_latest_rev).to be_updated_by_last_action end end describe "back to a previously deployed revision, with the directory structure precreated" do before do FileUtils.mkdir_p(rel_path("releases")) FileUtils.mkdir_p(rel_path("shared")) deploy_to_latest_rev.run_action(:deploy) deploy_to_previous_rev.run_action(:deploy) deploy_to_latest_rev_again.run_action(:deploy) end the_app_is_deployed_at_revision(:latest_rev) it "restarts the application after rolling back" do expect(actual_operations_order).to eq(%w{deploy_to_latest_rev deploy_to_previous_rev deploy_to_latest_rev_again}) end it "is marked updated" do expect(deploy_to_latest_rev_again).to be_updated_by_last_action end it "deploys the right code" do expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the fourth version of the app") end end describe "for the first time, with no existing directory layout" do before do deploy_to_latest_rev.run_action(:deploy) end it "creates the required directory tree" do expect(File).to be_directory(rel_path("releases")) expect(File).to be_directory(rel_path("shared")) expect(File).to be_directory(rel_path("releases/#{latest_rev}")) expect(File).to be_directory(rel_path("current/tmp")) expect(File).to be_directory(rel_path("current/config")) expect(File).to be_directory(rel_path("current/public")) expect(File).to be_symlink(rel_path("current")) expect(File.readlink(rel_path("current"))).to eq(rel_path("releases/#{latest_rev}")) end the_app_is_deployed_at_revision(:latest_rev) it "restarts the application" do expect(File).to exist(rel_path("current/restart.txt")) expect(actual_operations_order).to eq(%w{deploy_to_latest_rev}) end it "is marked as updated" do expect(deploy_to_latest_rev).to be_updated_by_last_action end end describe "again to the current revision" do before do deploy_to_latest_rev.run_action(:deploy) deploy_to_latest_rev.run_action(:deploy) end the_app_is_deployed_at_revision(:latest_rev) it "does not restart the app" do expect(actual_operations_order).to eq(%w{deploy_to_latest_rev}) end it "is not marked updated" do expect(deploy_to_latest_rev).not_to be_updated_by_last_action end end describe "again with force_deploy" do before do deploy_to_latest_rev.run_action(:force_deploy) deploy_to_latest_rev_again.run_action(:force_deploy) end the_app_is_deployed_at_revision(:latest_rev) it "restarts the app" do expect(actual_operations_order).to eq(%w{deploy_to_latest_rev deploy_to_latest_rev_again}) end it "is marked updated" do expect(deploy_to_latest_rev).to be_updated_by_last_action end end describe "again to a new revision" do before do deploy_to_previous_rev.run_action(:deploy) deploy_to_latest_rev.run_action(:deploy) end the_app_is_deployed_at_revision(:latest_rev) it "restarts the application after the new deploy" do expect(actual_operations_order).to eq(%w{deploy_to_previous_rev deploy_to_latest_rev}) end it "is marked updated" do expect(deploy_to_previous_rev).to be_updated_by_last_action end it "leaves the old copy of the app around for rollback" do expect(File).to exist(File.join(deploy_directory, "releases", previous_rev)) end end describe "back to a previously deployed revision (implicit rollback)" do before do deploy_to_latest_rev.run_action(:deploy) deploy_to_previous_rev.run_action(:deploy) deploy_to_latest_rev_again.run_action(:deploy) end the_app_is_deployed_at_revision(:latest_rev) it "restarts the application after rolling back" do expect(actual_operations_order).to eq(%w{deploy_to_latest_rev deploy_to_previous_rev deploy_to_latest_rev_again}) end it "is marked updated" do expect(deploy_to_latest_rev_again).to be_updated_by_last_action end it "deploys the right code" do expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the fourth version of the app") end end describe "back to a previously deployed revision where resource rev == latest revision (explicit rollback)" do before do deploy_to_previous_rev.run_action(:deploy) @previous_rev_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases deploy_to_latest_rev.run_action(:deploy) @latest_rev_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases deploy_to_latest_rev_again.run_action(:rollback) @previous_rev_again_all_releases = deploy_to_latest_rev_again.provider_for_action(:deploy).all_releases end the_app_is_deployed_at_revision(:previous_rev) it "restarts the application after rolling back" do expect(actual_operations_order).to eq(%w{deploy_to_previous_rev deploy_to_latest_rev deploy_to_latest_rev_again}) end it "is marked updated" do expect(deploy_to_latest_rev_again).to be_updated_by_last_action end it "deploys the right code" do expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the third version of the app") end it "all_releases after first deploy should have one entry" do expect(@previous_rev_all_releases.length).to eq(1) end it "all_releases after second deploy should have two entries" do expect(@latest_rev_all_releases.length).to eq(2) end it "all_releases after rollback should have one entry" do expect(@previous_rev_again_all_releases.length).to eq(1) end it "all_releases after rollback should be the same as after the first deploy" do expect(@previous_rev_again_all_releases).to eq(@previous_rev_all_releases) end end describe "back to a previously deployed revision where resource rev == previous revision (explicit rollback)" do before do deploy_to_previous_rev.run_action(:deploy) @previous_rev_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases deploy_to_latest_rev.run_action(:deploy) @latest_rev_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases deploy_to_previous_rev_again.run_action(:rollback) # FIXME: only difference with previous test is using latest_rev_again insetad of previous_rev_again @previous_rev_again_all_releases = deploy_to_latest_rev_again.provider_for_action(:deploy).all_releases end the_app_is_deployed_at_revision(:previous_rev) it "restarts the application after rolling back" do expect(actual_operations_order).to eq(%w{deploy_to_previous_rev deploy_to_latest_rev deploy_to_previous_rev_again}) end it "is marked updated" do expect(deploy_to_previous_rev_again).to be_updated_by_last_action end it "deploys the right code" do expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the third version of the app") end it "all_releases after first deploy should have one entry" do expect(@previous_rev_all_releases.length).to eq(1) end it "all_releases after second deploy should have two entries" do expect(@latest_rev_all_releases.length).to eq(2) end it "all_releases after rollback should have one entry" do expect(@previous_rev_again_all_releases.length).to eq(1) end it "all_releases after rollback should be the same as after the first deploy" do expect(@previous_rev_again_all_releases).to eq(@previous_rev_all_releases) end end describe "back to a previously deployed revision where resource rev == latest revision (explicit rollback)" do before do deploy_to_second_rev.run_action(:deploy) @first_deploy_all_releases = deploy_to_second_rev.provider_for_action(:deploy).all_releases deploy_to_previous_rev.run_action(:deploy) @second_deploy_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases deploy_to_previous_rev_again.run_action(:rollback) @third_deploy_all_releases = deploy_to_previous_rev_again.provider_for_action(:deploy).all_releases deploy_to_latest_rev.run_action(:deploy) @fourth_deploy_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases deploy_to_latest_rev_again.run_action(:rollback) @fifth_deploy_all_releases = deploy_to_latest_rev_again.provider_for_action(:deploy).all_releases end the_app_is_deployed_at_revision(:second_rev) it "restarts the application after rolling back" do expect(actual_operations_order).to eq(%w{deploy_to_second_rev deploy_to_previous_rev deploy_to_previous_rev_again deploy_to_latest_rev deploy_to_latest_rev_again}) end it "is marked updated" do expect(deploy_to_latest_rev_again).to be_updated_by_last_action end it "deploys the right code" do expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the second version of the app") end it "all_releases after rollback should have one entry" do expect(@fifth_deploy_all_releases.length).to eq(1) end it "all_releases after rollback should be the same as after the first deploy" do expect(@fifth_deploy_all_releases).to eq(@first_deploy_all_releases) end end describe "back to a previously deployed revision where resource rev == latest revision (explicit rollback)" do before do deploy_to_second_rev.run_action(:deploy) @first_deploy_all_releases = deploy_to_second_rev.provider_for_action(:deploy).all_releases deploy_to_previous_rev.run_action(:deploy) @second_deploy_all_releases = deploy_to_previous_rev.provider_for_action(:deploy).all_releases deploy_to_second_rev_again.run_action(:rollback) @third_deploy_all_releases = deploy_to_second_rev_again.provider_for_action(:deploy).all_releases deploy_to_latest_rev.run_action(:deploy) @fourth_deploy_all_releases = deploy_to_latest_rev.provider_for_action(:deploy).all_releases deploy_to_second_rev_again_again.run_action(:rollback) @fifth_deploy_all_releases = deploy_to_second_rev_again_again.provider_for_action(:deploy).all_releases end the_app_is_deployed_at_revision(:second_rev) it "restarts the application after rolling back" do expect(actual_operations_order).to eq(%w{deploy_to_second_rev deploy_to_previous_rev deploy_to_second_rev_again deploy_to_latest_rev deploy_to_second_rev_again_again}) end it "is marked updated" do expect(deploy_to_second_rev_again_again).to be_updated_by_last_action end it "deploys the right code" do expect(IO.read(rel_path("current/app/app.rb"))).to include("this is the second version of the app") end it "all_releases after rollback should have one entry" do expect(@fifth_deploy_all_releases.length).to eq(1) end it "all_releases after rollback should be the same as after the first deploy" do expect(@fifth_deploy_all_releases).to eq(@first_deploy_all_releases) end end # CHEF-3435 describe "to a deploy_to path that does not yet exist" do let(:top_level_tmpdir) { Dir.mktmpdir } # override top level deploy_directory let block with one that is two # directories deeper let(:deploy_directory) { File.expand_path("nested/deeper", top_level_tmpdir) } after do FileUtils.remove_entry_secure top_level_tmpdir end before do expect(File).not_to exist(deploy_directory) deploy_to_latest_rev.run_action(:deploy) end it "creates the required directory tree" do expect(File).to be_directory(rel_path("releases")) expect(File).to be_directory(rel_path("shared")) expect(File).to be_directory(rel_path("releases/#{latest_rev}")) expect(File).to be_directory(rel_path("current/tmp")) expect(File).to be_directory(rel_path("current/config")) expect(File).to be_directory(rel_path("current/public")) expect(File).to be_symlink(rel_path("current")) expect(File.readlink(rel_path("current"))).to eq(rel_path("releases/#{latest_rev}")) end the_app_is_deployed_at_revision(:latest_rev) end end context "when deploying an app with inline recipe callbacks" do # Use closures to capture and mutate this variable. This allows us to track # ordering of operations. callback_order = [] let(:deploy_to_latest_with_inline_recipes) do deploy_to_latest_rev.dup.tap do |r| r.symlink_before_migrate "config/config.ru" => "config.ru" r.before_migrate do callback_order << :before_migrate file "#{release_path}/before_migrate.txt" do # The content here isn't relevant, but it gets printed when running # the tests. Could be handy for debugging. content callback_order.inspect end end r.before_symlink do callback_order << :before_symlink current_release_path = release_path ruby_block "ensure before symlink" do block do if ::File.exist?(::File.join(current_release_path, "/tmp")) raise "Ordering issue with provider, expected symlinks to not have been created" end end end file "#{release_path}/before_symlink.txt" do content callback_order.inspect end end r.before_restart do callback_order << :before_restart current_release_path = release_path ruby_block "ensure after symlink" do block do unless ::File.exist?(::File.join(current_release_path, "/tmp")) raise "Ordering issue with provider, expected symlinks to have been created" end end end file "#{release_path}/tmp/before_restart.txt" do content callback_order.inspect end end r.after_restart do callback_order << :after_restart file "#{release_path}/tmp/after_restart.txt" do content callback_order.inspect end end end end before do callback_order.clear # callback_order variable is global for this context group deploy_to_latest_with_inline_recipes.run_action(:deploy) end the_app_is_deployed_at_revision(:latest_rev) it "is marked updated" do expect(deploy_to_latest_with_inline_recipes).to be_updated_by_last_action end it "calls the callbacks in order" do expect(callback_order).to eq([:before_migrate, :before_symlink, :before_restart, :after_restart]) end it "runs chef resources in the callbacks" do expect(File).to exist(rel_path("current/before_migrate.txt")) expect(File).to exist(rel_path("current/before_symlink.txt")) expect(File).to exist(rel_path("current/tmp/before_restart.txt")) expect(File).to exist(rel_path("current/tmp/after_restart.txt")) end end context "when deploying an app with in-repo callback scripts" do let(:deploy_with_in_repo_callbacks) do basic_deploy_resource.dup.tap do |r| r.repo git_bundle_with_in_repo_callbacks r.revision rev_with_in_repo_callbacks end end before do deploy_with_in_repo_callbacks.run_action(:deploy) end the_app_is_deployed_at_revision(:rev_with_in_repo_callbacks) it "runs chef resources in the callbacks" do expect(File).to exist(rel_path("current/before_migrate.txt")) expect(File).to exist(rel_path("current/before_symlink.txt")) expect(File).to exist(rel_path("current/tmp/before_restart.txt")) expect(File).to exist(rel_path("current/tmp/after_restart.txt")) end end context "when deploying an app with migrations" do let(:deploy_with_migration) do basic_deploy_resource.dup.tap do |r| # Need this so we can call methods from this test inside the inline # recipe callbacks spec_context = self r.revision latest_rev # enable migrations r.migrate true # abuse `shell_restart_command` so we can observe order of when the # miration command gets run r.migration_command shell_restart_command("migration") r.before_migrate do # inline recipe callbacks don't cwd, so you have to get the release # directory as a local and "capture" it in the closure. current_release = release_path execute spec_context.shell_restart_command("before_migrate") do cwd current_release end end r.before_symlink do current_release = release_path execute spec_context.shell_restart_command("before_symlink") do cwd current_release end end r.before_restart do current_release = release_path execute spec_context.shell_restart_command("before_restart") do cwd current_release end end r.after_restart do current_release = release_path execute spec_context.shell_restart_command("after_restart") do cwd current_release end end end end before do deploy_with_migration.run_action(:deploy) end it "runs migrations in between the before_migrate and before_symlink steps" do expect(actual_operations_order).to eq(%w{before_migrate migration before_symlink before_restart after_restart}) end end context "when deploying an app with in-repo symlinks" do let(:deploy_with_in_repo_symlinks) do basic_deploy_resource.dup.tap do |r| r.repo git_bundle_with_in_repo_symlinks r.revision rev_with_in_repo_symlinks end end it "should not raise an exception calling File.utime on symlinks" do expect { deploy_with_in_repo_symlinks.run_action(:deploy) }.not_to raise_error end end context "when a previously deployed application has been nuked" do shared_examples_for "a redeployed application" do it "should redeploy the application" do expect(File).to be_directory(rel_path("releases")) expect(File).to be_directory(rel_path("shared")) expect(File).to be_directory(rel_path("releases/#{latest_rev}")) expect(File).to be_directory(rel_path("current/tmp")) expect(File).to be_directory(rel_path("current/config")) expect(File).to be_directory(rel_path("current/public")) expect(File).to be_symlink(rel_path("current")) expect(File.readlink(rel_path("current"))).to eq(rel_path("releases/#{latest_rev}")) end end # background: If a deployment is hosed and the user decides to rm -rf the # deployment dir, deploy resource should detect that and nullify its cache. context "by removing the entire deploy directory" do before do deploy_to_latest_rev.dup.run_action(:deploy) FileUtils.rm_rf(deploy_directory) deploy_to_latest_rev.dup.run_action(:deploy) end include_examples "a redeployed application" end context "by removing the current/ directory" do before do deploy_to_latest_rev.dup.run_action(:deploy) FileUtils.rm(rel_path("current")) deploy_to_latest_rev.dup.run_action(:deploy) end include_examples "a redeployed application" end end context "when a deployment fails" do shared_examples_for "a recovered deployment" do it "should redeploy the application" do expect(File).to be_directory(rel_path("releases")) expect(File).to be_directory(rel_path("shared")) expect(File).to be_directory(rel_path("releases/#{latest_rev}")) expect(File).to be_directory(rel_path("current/tmp")) expect(File).to be_directory(rel_path("current/config")) expect(File).to be_directory(rel_path("current/public")) expect(File).to be_symlink(rel_path("current")) expect(File.readlink(rel_path("current"))).to eq(rel_path("releases/#{latest_rev}")) # if callbacks ran, we know the app was deployed and not merely rolled # back to a (busted) prior deployment. expect(callback_order).to eq([:before_migrate, :before_symlink, :before_restart, :after_restart ]) end end let!(:callback_order) { [] } let(:deploy_to_latest_with_callback_tracking) do resource = deploy_to_latest_rev.dup tracker = callback_order resource.before_migrate { tracker << :before_migrate } resource.before_symlink { tracker << :before_symlink } resource.before_restart { tracker << :before_restart } resource.after_restart { tracker << :after_restart } resource end [:before_migrate, :before_symlink, :before_restart, :after_restart].each do |callback| context "in the `#{callback}' callback" do before do expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Exception, %r{I am a failed deploy}) deploy_to_latest_with_callback_tracking.run_action(:deploy) end let(:deploy_that_fails) do resource = deploy_to_latest_rev.dup errant_callback = lambda { |x| raise Exception, "I am a failed deploy" } resource.send(callback, &errant_callback) resource end include_examples "a recovered deployment" end end context "in the service restart step" do let(:deploy_that_fails) do resource = deploy_to_latest_rev.dup resource.restart_command("RUBYOPT=\"\" ruby -e 'exit 1'") resource end before do expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) deploy_to_latest_with_callback_tracking.run_action(:deploy) end include_examples "a recovered deployment" end context "when cloning the app code" do class BadTimeScmProvider def initialize(new_resource, run_context) end def load_current_resource end def revision_slug "5" end def run_action(action) raise "network error" end end let(:deploy_that_fails) do resource = deploy_to_latest_rev.dup resource.scm_provider(BadTimeScmProvider) resource end before do expect { deploy_that_fails.run_action(:deploy) }.to raise_error(RuntimeError, /network error/) deploy_to_latest_with_callback_tracking.run_action(:deploy) end include_examples "a recovered deployment" end context "and then is deployed to a different revision" do let(:deploy_that_fails) do resource = deploy_to_previous_rev.dup resource.after_restart { |x| raise Exception, "I am a failed deploy" } resource end before do expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Exception, %r{I am a failed deploy}) deploy_to_latest_rev.run_action(:deploy) end it "removes the unsuccessful deploy after a later successful deploy" do expect(::File).not_to exist(File.join(deploy_directory, "releases", previous_rev)) end end end end chef-12.14.60/spec/functional/resource/directory_spec.rb000066400000000000000000000023561276456504500231640ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Directory do include_context Chef::Resource::Directory let(:directory_base) { "directory_spec" } let(:default_mode) { (0777 & ~File.umask).to_s(8) } def create_resource events = Chef::EventDispatch::Dispatcher.new node = Chef::Node.new run_context = Chef::RunContext.new(node, {}, events) Chef::Resource::Directory.new(path, run_context) end let(:resource) do create_resource end it_behaves_like "a directory resource" it_behaves_like "a securable resource with reporting" end chef-12.14.60/spec/functional/resource/dpkg_package_spec.rb000066400000000000000000000324171276456504500235610ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mixin/shell_out" describe Chef::Resource::DpkgPackage, :requires_root, :debian_family_only, arch: "x86_64" do include Chef::Mixin::ShellOut let(:apt_data) { File.join(CHEF_SPEC_DATA, "apt") } let(:test1_0) { File.join(apt_data, "chef-integration-test_1.0-1_amd64.deb") } let(:test1_1) { File.join(apt_data, "chef-integration-test_1.1-1_amd64.deb") } let(:test2_0) { File.join(apt_data, "chef-integration-test2_1.0-1_amd64.deb") } let(:run_context) do node = TEST_NODE.dup events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, events) end let(:dpkg_package) { Chef::Resource::DpkgPackage.new(test1_0, run_context) } before(:each) do shell_out("dpkg -P chef-integration-test chef-integration-test2") end # handles setting the name property after the initializer runs def set_dpkg_package_name(name) dpkg_package.name name dpkg_package.package_name name end def should_be_purged_or_removed(package, action = nil) status = shell_out("dpkg -s #{package}") output = status.stdout + status.stderr if action.nil? || action == :purge expect(output).to match(/no info|not-installed|not installed/) elsif action == :remove expect(output).to match(/deinstall ok config-files/) else raise "Unknown action" end end shared_examples_for "common behavior for upgrade or install" do it "installs a package when given only the filename as a name argument (no source)" do dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action shell_out!("dpkg -s chef-integration-test") end it "installs a package when given the name and a source argument" do set_dpkg_package_name "chef-integration-test" dpkg_package.source test1_0 dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action shell_out!("dpkg -s chef-integration-test") end it "installs a package when given a different name and a source argument" do set_dpkg_package_name "some other name" dpkg_package.source test1_0 dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action shell_out!("dpkg -s chef-integration-test") end it "installs a package when given a path as a package_name and no source" do set_dpkg_package_name "chef-integration-test" dpkg_package.package_name test1_0 dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action shell_out!("dpkg -s chef-integration-test") end it "raises an error when the name is not a path and the source is not given" do set_dpkg_package_name "chef-integration-test" dpkg_package.package_name "chef-integration-test" expect { dpkg_package.run_action(action) }.to raise_error(Chef::Exceptions::Package) end it "raises an error when passed a package_name that does not exist" do set_dpkg_package_name File.join(test1_0, "make.it.fail") expect { dpkg_package.run_action(action) }.to raise_error(Chef::Exceptions::Package) end it "raises an error when passed a source that does not exist" do set_dpkg_package_name "chef-integration-test" dpkg_package.source File.join(test1_0, "make.it.fail") expect { dpkg_package.run_action(action) }.to raise_error(Chef::Exceptions::Package) end it "should not install an already installed package" do shell_out!("dpkg -i #{test1_0}") dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action shell_out!("dpkg -s chef-integration-test") end it "should handle a multipackage install" do set_dpkg_package_name [ test1_0, test2_0 ] dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action shell_out!("dpkg -s chef-integration-test") shell_out!("dpkg -s chef-integration-test2") end it "should not update multipackages that are up-to-date" do shell_out!("dpkg -i #{test1_0} #{test2_0}") set_dpkg_package_name [ test1_0, test2_0 ] dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action shell_out!("dpkg -s chef-integration-test") shell_out!("dpkg -s chef-integration-test2") end it "should install the second if the first is installed" do shell_out!("dpkg -i #{test1_0}") set_dpkg_package_name [ test1_0, test2_0 ] dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action shell_out!("dpkg -s chef-integration-test") shell_out!("dpkg -s chef-integration-test2") end it "should install the first if the second is installed" do shell_out!("dpkg -i #{test2_0}") set_dpkg_package_name [ test1_0, test2_0 ] dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action shell_out!("dpkg -s chef-integration-test") shell_out!("dpkg -s chef-integration-test2") end end context "action :install" do let(:action) { :install } it_behaves_like "common behavior for upgrade or install" it "should not upgrade a package" do shell_out!("dpkg -i #{test1_0}") set_dpkg_package_name test1_1 dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action end it "should not upgrade on a multipackage install" do shell_out!("dpkg -i #{test1_0} #{test2_0}") set_dpkg_package_name [ test1_1, test2_0 ] dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action end end context "action :upgrade" do let(:action) { :upgrade } it_behaves_like "common behavior for upgrade or install" it "should upgrade a package" do shell_out!("dpkg -i #{test1_0}") set_dpkg_package_name test1_1 dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action end it "should upgrade on a multipackage install" do shell_out!("dpkg -i #{test1_0} #{test2_0}") set_dpkg_package_name [ test1_1, test2_0 ] dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action end end shared_examples_for "common behavior for remove or purge" do it "should remove a package that is installed when the name is a source" do shell_out!("dpkg -i #{test1_0}") dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") end it "should do nothing if the package is not installed when the name is a source" do dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") end it "should remove a package that is installed when the name is the package name and source is nil" do shell_out!("dpkg -i #{test1_0}") set_dpkg_package_name "chef-integration-test" dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") end it "should do nothing if the package is not installed when the name is the package name and the source is nil" do set_dpkg_package_name "chef-integration-test" dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") end it "should remove a package that is installed when the name is changed but the source is a package" do shell_out!("dpkg -i #{test1_0}") set_dpkg_package_name "some other name" dpkg_package.source test1_0 dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") end it "should do nothing if the package is not installed when the name is changed but the source is a package" do set_dpkg_package_name "some other name" dpkg_package.source test1_0 dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") end it "should remove a package if the name is a file that does not exist, but the source exists" do shell_out!("dpkg -i #{test1_0}") dpkg_package.name "whatever" dpkg_package.package_name File.join(test1_0, "make.it.fail") dpkg_package.source test1_0 dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") end it "should do nothing if the package is not installed when the name is a file that does not exist, but the source exists" do set_dpkg_package_name "some other name" dpkg_package.name "whatever" dpkg_package.package_name File.join(test1_0, "make.it.fail") dpkg_package.source test1_0 dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") end it "should remove a package if the package_name is correct, but the source does not exist" do shell_out!("dpkg -i #{test1_0}") dpkg_package.name "whatever" dpkg_package.package_name "chef-integration-test" dpkg_package.source File.join(test1_0, "make.it.fail") dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") end it "should do nothing if the package_name is correct, but the source does not exist, and the package is not installed" do dpkg_package.name "whatever" dpkg_package.package_name "chef-integration-test" dpkg_package.source File.join(test1_0, "make.it.fail") dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") end it "should remove both packages when called with two" do shell_out!("dpkg -i #{test1_0} #{test2_0}") set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ] dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") should_be_purged_or_removed("chef-integration-test2", action) end it "should remove a package when only the first one is installed" do shell_out!("dpkg -i #{test1_0}") set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ] dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") should_be_purged_or_removed("chef-integration-test2") end it "should remove a package when only the second one is installed" do shell_out!("dpkg -i #{test2_0}") set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ] dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") should_be_purged_or_removed("chef-integration-test2", action) end it "should do nothing when both packages are not installed" do set_dpkg_package_name [ "chef-integration-test", "chef-integration-test2" ] dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test") should_be_purged_or_removed("chef-integration-test2") end end context "action :remove" do let(:action) { :remove } it_behaves_like "common behavior for remove or purge" it "should not remove a removed package when the name is a source" do # the "test2" file has a conffile declared in it shell_out!("dpkg -i #{test2_0}") shell_out!("dpkg -r chef-integration-test2") set_dpkg_package_name "chef-integration-test2" dpkg_package.run_action(action) expect(dpkg_package).not_to be_updated_by_last_action shell_out!("dpkg -s chef-integration-test2") # its still 'installed' end end context "action :purge" do let(:action) { :purge } it_behaves_like "common behavior for remove or purge" it "should purge a removed package when the name is a source" do # the "test2" file has a conffile declared in it shell_out!("dpkg -i #{test2_0}") shell_out!("dpkg -r chef-integration-test2") set_dpkg_package_name "chef-integration-test2" dpkg_package.run_action(action) expect(dpkg_package).to be_updated_by_last_action should_be_purged_or_removed("chef-integration-test2", action) end end end chef-12.14.60/spec/functional/resource/dsc_resource_spec.rb000066400000000000000000000060741276456504500236410ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::DscResource, :windows_powershell_dsc_only do let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new } let(:node) do Chef::Node.new.tap do |n| n.consume_external_attrs(OHAI_SYSTEM.data, {}) end end let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) } let(:new_resource) do Chef::Resource::DscResource.new("dsc_resource_test", run_context) end context "when Powershell does not support Invoke-DscResource" context "when Powershell supports Invoke-DscResource" do before do if !Chef::Platform.supports_dsc_invoke_resource?(node) skip "Requires Powershell >= 5.0.10018.0" elsif !Chef::Platform.supports_refresh_mode_enabled?(node) && !Chef::Platform.dsc_refresh_mode_disabled?(node) skip "Requires LCM RefreshMode is Disabled" end end context "with an invalid dsc resource" do it "raises an exception if the resource is not found" do new_resource.resource "thisdoesnotexist" expect { new_resource.run_action(:run) }.to raise_error( Chef::Exceptions::ResourceNotFound) end end context "with a valid dsc resource" do let(:tmp_file_name) { Dir::Tmpname.create("tmpfile") {} } let(:test_text) { "'\"!@#$%^&*)(}{][\u2713~n" } before do new_resource.resource :File new_resource.property :Contents, test_text new_resource.property :DestinationPath, tmp_file_name end after do File.delete(tmp_file_name) if File.exists? tmp_file_name end it "converges the resource if it is not converged" do new_resource.run_action(:run) contents = File.open(tmp_file_name, "rb:bom|UTF-16LE") do |f| f.read.encode("UTF-8") end expect(contents).to eq(test_text) expect(new_resource).to be_updated end it "does not converge the resource if it is already converged" do new_resource.run_action(:run) expect(new_resource).to be_updated reresource = Chef::Resource::DscResource.new("dsc_resource_retest", run_context) reresource.resource :File reresource.property :Contents, test_text reresource.property :DestinationPath, tmp_file_name reresource.run_action(:run) expect(reresource).not_to be_updated end end end end chef-12.14.60/spec/functional/resource/dsc_script_spec.rb000066400000000000000000000424201276456504500233110ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mixin/powershell_out" require "chef/mixin/shell_out" require "chef/mixin/windows_architecture_helper" require "support/shared/integration/integration_helper" describe Chef::Resource::DscScript, :windows_powershell_dsc_only do include Chef::Mixin::WindowsArchitectureHelper include Chef::Mixin::PowershellOut before(:all) do @temp_dir = ::Dir.mktmpdir("dsc-functional-test") # enable the HTTP listener if it is not already enabled needed by underlying DSC engine winrm_code = <<-CODE winrm get winrm/config/listener?Address=*+Transport=HTTP if($LASTEXITCODE -ne 0) { winrm create winrm/config/Listener?Address=*+Transport=HTTP } CODE powershell_out!(winrm_code) end after(:all) do ::FileUtils.rm_rf(@temp_dir) if ::Dir.exist?(@temp_dir) end include Chef::Mixin::ShellOut def create_config_script_from_code(code, configuration_name, data = false) script_code = data ? code : "Configuration '#{configuration_name}'\n{\n\t#{code}\n}\n" data_suffix = data ? "_config_data" : "" extension = data ? "psd1" : "ps1" script_path = "#{@temp_dir}/dsc_functional_test#{data_suffix}.#{extension}" ::File.open(script_path, "wt") do |script| script.write(script_code) end script_path end def user_exists?(target_user) result = false begin shell_out!("net user #{target_user}") result = true rescue Mixlib::ShellOut::ShellCommandFailed end result end def delete_user(target_user) begin shell_out!("net user #{target_user} /delete") rescue Mixlib::ShellOut::ShellCommandFailed end end let(:dsc_env_variable) { "chefenvtest" } let(:dsc_env_value1) { "value1" } let(:env_value2) { "value2" } let(:dsc_test_run_context) do node = Chef::Node.new node.automatic["platform"] = "windows" node.automatic["platform_version"] = "6.1" node.automatic["kernel"][:machine] = :x86_64 # Only 64-bit architecture is supported node.automatic[:languages][:powershell][:version] = "4.0" empty_events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, empty_events) end let(:dsc_test_resource_name) { "DSCTest" } let(:dsc_test_resource_base) do Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) end let(:test_registry_key) { 'HKEY_LOCAL_MACHINE\Software\Chef\Spec\Functional\Resource\dsc_script_spec' } let(:test_registry_value) { "Registration" } let(:test_registry_data1) { "LL927" } let(:test_registry_data2) { "LL928" } let(:reg_key_name_param_name) { "testregkeyname" } let(:reg_key_value_param_name) { "testregvaluename" } let(:registry_embedded_parameters) { "$#{reg_key_name_param_name} = '#{test_registry_key}';$#{reg_key_value_param_name} = '#{test_registry_value}'" } let(:dsc_reg_code) do <<-EOH #{registry_embedded_parameters} Registry "ChefRegKey" { Key = $#{reg_key_name_param_name} ValueName = $#{reg_key_value_param_name} ValueData = '#{test_registry_data}' Ensure = 'Present' } EOH end let(:dsc_code) { dsc_reg_code } let(:dsc_reg_script) do <<-EOH param($testregkeyname, $testregvaluename) #{dsc_reg_code} EOH end let(:dsc_user_prefix) { "dsc" } let(:dsc_user_suffix) { "chefx" } let(:dsc_user) { "#{dsc_user_prefix}_usr_#{dsc_user_suffix}" } let(:dsc_user_prefix_env_var_name) { "dsc_user_env_prefix" } let(:dsc_user_suffix_env_var_name) { "dsc_user_env_suffix" } let(:dsc_user_prefix_env_code) { "$env:#{dsc_user_prefix_env_var_name}" } let(:dsc_user_suffix_env_code) { "$env:#{dsc_user_suffix_env_var_name}" } let(:dsc_user_prefix_param_name) { "dsc_user_prefix_param" } let(:dsc_user_suffix_param_name) { "dsc_user_suffix_param" } let(:dsc_user_prefix_param_code) { "$#{dsc_user_prefix_param_name}" } let(:dsc_user_suffix_param_code) { "$#{dsc_user_suffix_param_name}" } let(:dsc_user_env_code) { "\"$(#{dsc_user_prefix_env_code})_usr_$(#{dsc_user_suffix_env_code})\"" } let(:dsc_user_param_code) { "\"$(#{dsc_user_prefix_param_code})_usr_$(#{dsc_user_suffix_param_code})\"" } let(:config_flags) { nil } let(:config_params) do <<-EOH [CmdletBinding()] param ( $#{dsc_user_prefix_param_name}, $#{dsc_user_suffix_param_name} ) EOH end let(:config_param_section) { "" } let(:dsc_user_code) { "'#{dsc_user}'" } let(:dsc_user_prefix_code) { dsc_user_prefix } let(:dsc_user_suffix_code) { dsc_user_suffix } let(:dsc_script_environment_attribute) { nil } let(:dsc_user_resources_code) do <<-EOH #{config_param_section} node localhost { $testuser = #{dsc_user_code} $testpassword = ConvertTo-SecureString -String "jf9a8m49jrajf4#" -AsPlainText -Force $testcred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $testuser, $testpassword User dsctestusercreate { UserName = $testuser Password = $testcred Description = "DSC test user" Ensure = "Present" Disabled = $false PasswordNeverExpires = $true PasswordChangeRequired = $false } } EOH end let(:dsc_user_config_data) do <<-EOH @{ AllNodes = @( @{ NodeName = "localhost"; PSDscAllowPlainTextPassword = $true } ) } EOH end let(:dsc_environment_env_var_name) { "dsc_test_cwd" } let(:dsc_environment_no_fail_not_etc_directory) { "#{ENV['systemroot']}\\system32" } let(:dsc_environment_fail_etc_directory) { "#{ENV['systemroot']}\\system32\\drivers\\etc" } let(:exception_message_signature) { "LL927-LL928" } let(:dsc_environment_config) do <<-EOH if (($pwd.path -eq '#{dsc_environment_fail_etc_directory}') -and (test-path('#{dsc_environment_fail_etc_directory}'))) { throw 'Signature #{exception_message_signature}: Purposefully failing because cwd == #{dsc_environment_fail_etc_directory}' } environment "whatsmydir" { Name = '#{dsc_environment_env_var_name}' Value = $pwd.path Ensure = 'Present' } EOH end let(:dsc_config_name) do dsc_test_resource_base.name end let(:dsc_resource_from_code) do dsc_test_resource_base.code(dsc_code) dsc_test_resource_base end let(:config_name_value) { dsc_test_resource_base.name } let(:dsc_resource_from_path) do dsc_test_resource_base.command(create_config_script_from_code(dsc_code, config_name_value)) dsc_test_resource_base end before(:each) do test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context) test_key_resource.recursive(true) test_key_resource.run_action(:delete_key) end after(:each) do test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context) test_key_resource.recursive(true) test_key_resource.run_action(:delete_key) end shared_examples_for "a dsc_script resource with specified PowerShell configuration code" do let(:test_registry_data) { test_registry_data1 } it "should create a registry key with a specific registry value and data" do expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(false) dsc_test_resource.run_action(:run) expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(true) expect(dsc_test_resource.registry_value_exists?(test_registry_key, { :name => test_registry_value, :type => :string, :data => test_registry_data })).to eq(true) end it_should_behave_like "a dsc_script resource with configuration affected by cwd" end shared_examples_for "a dsc_script resource with configuration affected by cwd" do after(:each) do removal_resource = Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) removal_resource.code <<-EOH environment 'removethis' { Name = '#{dsc_environment_env_var_name}' Ensure = 'Absent' } EOH removal_resource.run_action(:run) end describe "when the DSC configuration contains code that raises an exception if cwd has a specific value" do let(:dsc_code) { dsc_environment_config } it "should not raise an exception if the cwd is not etc" do dsc_test_resource.cwd(dsc_environment_no_fail_not_etc_directory) expect { dsc_test_resource.run_action(:run) }.not_to raise_error end it "should raise an exception if the cwd is etc" do dsc_test_resource.cwd(dsc_environment_fail_etc_directory) expect { dsc_test_resource.run_action(:run) }.to raise_error(Chef::Exceptions::PowershellCmdletException) begin dsc_test_resource.run_action(:run) rescue Chef::Exceptions::PowershellCmdletException => e expect(e.message).to match(exception_message_signature) end end end end shared_examples_for "a parameterized DSC configuration script" do let(:dsc_user_prefix_code) { dsc_user_prefix_env_code } let(:dsc_user_suffix_code) { dsc_user_suffix_env_code } it_behaves_like "a dsc_script with configuration that uses environment variables" end shared_examples_for "a dsc_script without configuration data that takes parameters" do context "when configuration data is not specified" do before(:each) do test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context) test_key_resource.recursive(true) test_key_resource.run_action(:delete_key) end after(:each) do test_key_resource = Chef::Resource::RegistryKey.new(test_registry_key, dsc_test_run_context) test_key_resource.recursive(true) test_key_resource.run_action(:delete_key) end let(:test_registry_data) { test_registry_data1 } let(:dsc_parameterized_env_param_value) { "val" + Random.rand.to_s } it "should have a default value of nil for the configuration_data attribute" do expect(dsc_test_resource.configuration_data).to eql(nil) end it "should have a default value of nil for the configuration_data_path attribute" do expect(dsc_test_resource.configuration_data_script).to eql(nil) end let(:dsc_test_resource) { dsc_resource_from_path } let(:registry_embedded_parameters) { "" } let(:dsc_code) { dsc_reg_script } it "should set a registry key according to parameters passed to the configuration" do dsc_test_resource.configuration_name(config_name_value) dsc_test_resource.flags({ :"#{reg_key_name_param_name}" => test_registry_key, :"#{reg_key_value_param_name}" => test_registry_value }) expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(false) dsc_test_resource.run_action(:run) expect(dsc_test_resource.registry_key_exists?(test_registry_key)).to eq(true) expect(dsc_test_resource.registry_value_exists?(test_registry_key, { :name => test_registry_value, :type => :string, :data => test_registry_data })).to eq(true) end end end shared_examples_for "a dsc_script with configuration data" do let(:configuration_data_attribute) { "configuration_data" } it_behaves_like "a dsc_script with configuration data set via an attribute" let(:configuration_data_attribute) { "configuration_data_script" } it_behaves_like "a dsc_script with configuration data set via an attribute" end shared_examples_for "a dsc_script with configuration data set via an attribute" do it "should run a configuration script that creates a user" do config_data_value = dsc_user_config_data dsc_test_resource.configuration_name(config_name_value) if configuration_data_attribute == "configuration_data_script" config_data_value = create_config_script_from_code(dsc_user_config_data, "", true) end dsc_test_resource.environment({ dsc_user_prefix_env_var_name => dsc_user_prefix, dsc_user_suffix_env_var_name => dsc_user_suffix }) dsc_test_resource.send(configuration_data_attribute, config_data_value) dsc_test_resource.flags(config_flags) expect(user_exists?(dsc_user)).to eq(false) expect { dsc_test_resource.run_action(:run) }.not_to raise_error expect(user_exists?(dsc_user)).to eq(true) end end shared_examples_for "a dsc_script with configuration data that takes parameters" do let(:dsc_user_code) { dsc_user_param_code } let(:config_param_section) { config_params } let(:config_flags) { { :"#{dsc_user_prefix_param_name}" => "#{dsc_user_prefix}", :"#{dsc_user_suffix_param_name}" => "#{dsc_user_suffix}" } } it "does not directly contain the user name" do configuration_script_content = ::File.open(dsc_test_resource.command) do |file| file.read end expect(configuration_script_content.include?(dsc_user)).to be(false) end it_behaves_like "a dsc_script with configuration data" end shared_examples_for "a dsc_script with configuration data that uses environment variables" do let(:dsc_user_code) { dsc_user_env_code } it "does not directly contain the user name" do configuration_script_content = ::File.open(dsc_test_resource.command) do |file| file.read end expect(configuration_script_content.include?(dsc_user)).to be(false) end it_behaves_like "a dsc_script with configuration data" end context "when supplying configuration through the configuration attribute" do let(:dsc_test_resource) { dsc_resource_from_code } it_behaves_like "a dsc_script resource with specified PowerShell configuration code" end context "when supplying configuration using the path attribute" do let(:dsc_test_resource) { dsc_resource_from_path } it_behaves_like "a dsc_script resource with specified PowerShell configuration code" end context "when running a configuration that manages users" do before(:each) do delete_user(dsc_user) end let(:dsc_code) { dsc_user_resources_code } let(:config_name_value) { "DSCTestConfig" } let(:dsc_test_resource) { dsc_resource_from_path } it_behaves_like "a dsc_script with configuration data" it_behaves_like "a dsc_script with configuration data that uses environment variables" it_behaves_like "a dsc_script with configuration data that takes parameters" it_behaves_like "a dsc_script without configuration data that takes parameters" end context "when using ps_credential" do include IntegrationSupport before(:each) do delete_user(dsc_user) dsc_test_run_context.node.consume_external_attrs(OHAI_SYSTEM.data, {}) end let(:configuration_data_path) { 'C:\\configurationdata.psd1' } let(:self_signed_cert_path) do File.join(CHEF_SPEC_DATA, "dsc_lcm.pfx") end let(:dsc_configuration_script) do <<-MYCODE cd c:\\ configuration LCM { param ($thumbprint) localconfigurationmanager { RebootNodeIfNeeded = $false ConfigurationMode = 'ApplyOnly' CertificateID = $thumbprint } } $cert = ls Cert:\\LocalMachine\\My\\ | Where-Object {$_.Subject -match "ChefTest"} | Select -first 1 if($cert -eq $null) { $pfxpath = '#{self_signed_cert_path}' $password = '' $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxpath, $password, ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeyset)) $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "My", ([System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $store.Add($cert) $store.Close() } lcm -thumbprint $cert.thumbprint set-dsclocalconfigurationmanager -path ./LCM $ConfigurationData = @" @{ AllNodes = @( @{ NodeName = "localhost"; CertificateID = '$($cert.thumbprint)'; }; ); } "@ $ConfigurationData | out-file '#{configuration_data_path}' -force MYCODE end let(:powershell_script_resource) do Chef::Resource::PowershellScript.new("configure-lcm", dsc_test_run_context).tap do |r| r.code(dsc_configuration_script) r.architecture(:x86_64) end end let(:dsc_script_resource) do dsc_test_resource_base.tap do |r| r.code <<-EOF User dsctestusercreate { UserName = '#{dsc_user}' Password = #{r.ps_credential('jf9a8m49jrajf4#')} Ensure = "Present" } EOF r.configuration_data_script(configuration_data_path) end end it "allows the use of ps_credential" do skip("Skipped 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) dsc_script_resource.run_action(:run) expect(user_exists?(dsc_user)).to eq(true) end end end chef-12.14.60/spec/functional/resource/env_spec.rb000077500000000000000000000175361276456504500217610ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Env, :windows_only do context "when running on Windows" do let(:chef_env_test_lower_case) { "chefenvtest" } let(:chef_env_test_mixed_case) { "chefENVtest" } let(:env_dne_key) { "env_dne_key" } let(:env_value1) { "value1" } let(:env_value2) { "value2" } let(:env_value_expandable) { "%SystemRoot%" } let(:test_run_context) do node = Chef::Node.new node.default["os"] = "windows" node.default["platform"] = "windows" node.default["platform_version"] = "6.1" empty_events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, empty_events) end let(:test_resource) do Chef::Resource::Env.new("unknown", test_run_context) end before(:each) do resource_lower = Chef::Resource::Env.new(chef_env_test_lower_case, test_run_context) resource_lower.run_action(:delete) resource_mixed = Chef::Resource::Env.new(chef_env_test_mixed_case, test_run_context) resource_mixed.run_action(:delete) end context "when the create action is invoked" do it "should create an environment variable for action create" do expect(ENV[chef_env_test_lower_case]).to eq(nil) test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value1) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value1) end it "should modify an existing variable's value to a new value" do test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value1) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value1) test_resource.value(env_value2) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value2) end it "should modify an existing variable's value to a new value if the variable name case differs from the existing variable" do test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value1) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value1) test_resource.key_name(chef_env_test_mixed_case) test_resource.value(env_value2) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value2) end it "should not expand environment variables if the variable is not PATH" do expect(ENV[chef_env_test_lower_case]).to eq(nil) test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value_expandable) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value_expandable) end end context "when the modify action is invoked" do it "should raise an exception for modify if the variable doesn't exist" do expect(ENV[chef_env_test_lower_case]).to eq(nil) test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value1) expect { test_resource.run_action(:modify) }.to raise_error(Chef::Exceptions::Env) end it "should modify an existing variable's value to a new value" do test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value1) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value1) test_resource.value(env_value2) test_resource.run_action(:modify) expect(ENV[chef_env_test_lower_case]).to eq(env_value2) end # This examlpe covers Chef Issue #1754 it "should modify an existing variable's value to a new value if the variable name case differs from the existing variable" do test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value1) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value1) test_resource.key_name(chef_env_test_mixed_case) test_resource.value(env_value2) test_resource.run_action(:modify) expect(ENV[chef_env_test_lower_case]).to eq(env_value2) end it "should not expand environment variables if the variable is not PATH" do test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value1) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value1) test_resource.value(env_value_expandable) test_resource.run_action(:modify) expect(ENV[chef_env_test_lower_case]).to eq(env_value_expandable) end context "when using PATH" do let(:random_name) { Time.now.to_i } let(:env_val) { "#{env_value_expandable}_#{random_name}" } let!(:path_before) { test_resource.provider_for_action(test_resource.action).env_value("PATH") || "" } let!(:env_path_before) { ENV["PATH"] } it "should expand PATH" do expect(path_before).not_to include(env_val) test_resource.key_name("PATH") test_resource.value("#{path_before};#{env_val}") test_resource.run_action(:create) expect(ENV["PATH"]).not_to include(env_val) expect(ENV["PATH"]).to include("#{random_name}") end after(:each) do # cleanup so we don't flood the path test_resource.key_name("PATH") test_resource.value(path_before) test_resource.run_action(:create) ENV["PATH"] = env_path_before end end end context "when the delete action is invoked" do it "should delete an environment variable" do test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value1) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value1) test_resource.run_action(:delete) expect(ENV[chef_env_test_lower_case]).to eq(nil) end it "should not raise an exception when a non-existent environment variable is deleted" do expect(ENV[chef_env_test_lower_case]).to eq(nil) test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value1) expect { test_resource.run_action(:delete) }.not_to raise_error expect(ENV[chef_env_test_lower_case]).to eq(nil) end it "should delete an existing variable's value to a new value if the specified variable name case differs from the existing variable" do test_resource.key_name(chef_env_test_lower_case) test_resource.value(env_value1) test_resource.run_action(:create) expect(ENV[chef_env_test_lower_case]).to eq(env_value1) test_resource.key_name(chef_env_test_mixed_case) test_resource.run_action(:delete) expect(ENV[chef_env_test_lower_case]).to eq(nil) expect(ENV[chef_env_test_mixed_case]).to eq(nil) end it "should delete a value from the current process even if it is not in the registry" do expect(ENV[env_dne_key]).to eq(nil) ENV[env_dne_key] = env_value1 test_resource.key_name(env_dne_key) test_resource.run_action(:delete) expect(ENV[env_dne_key]).to eq(nil) end end end end chef-12.14.60/spec/functional/resource/execute_spec.rb000066400000000000000000000133301276456504500226140ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "functional/resource/base" require "timeout" describe Chef::Resource::Execute do let(:resource) do resource = Chef::Resource::Execute.new("foo_resource", run_context) resource.command("echo hello") resource end describe "when guard is ruby block" do it "guard can still run" do resource.only_if { true } resource.run_action(:run) expect(resource).to be_updated_by_last_action end end describe "when why_run is enabled" do before do Chef::Config[:why_run] = true end let(:guard) { "ruby -e 'exit 0'" } let!(:guard_resource) do interpreter = Chef::GuardInterpreter::ResourceGuardInterpreter.new(resource, guard, nil) interpreter.send(:get_interpreter_resource, resource) end it "executes the guard and not the regular resource" do expect_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:get_interpreter_resource).and_return(guard_resource) # why_run mode doesn't disable the updated_by_last_action logic, so we really have to look at the provider action # to see if why_run correctly disabled the resource. It should shell_out! for the guard but not the resource. expect_any_instance_of(Chef::Provider::Execute).to receive(:shell_out!).once resource.only_if guard resource.run_action(:run) expect(resource).to be_updated_by_last_action expect(guard_resource).to be_updated_by_last_action end end describe "when parent resource sets :cwd" do let(:guard) { %{ruby -e 'exit 1 unless File.exists?("./nested.json")'} } it "guard inherits :cwd from resource and runs" do resource.cwd CHEF_SPEC_DATA resource.only_if guard resource.run_action(:run) expect(resource).to be_updated_by_last_action end it "guard inherits :cwd from resource and does not run" do resource.cwd CHEF_SPEC_DATA resource.not_if guard resource.run_action(:run) expect(resource).not_to be_updated_by_last_action end end # We use ruby command so that we don't need to deal with platform specific # commands while testing execute resource. We set it so that the resource # will be updated if the ENV variable is set to what we are intending # # FIXME: yeah, but invoking ruby is slow... describe "when parent resource sets :environment" do before do resource.environment({ "SAWS_SECRET" => "supersecret", "SAWS_KEY" => "qwerty", }) end it "guard inherits :environment value from resource and runs" do resource.only_if %{ruby -e 'exit 1 if ENV["SAWS_SECRET"] != "supersecret"'} resource.run_action(:run) expect(resource).to be_updated_by_last_action end it "guard inherits :environment value from resource and does not run" do resource.only_if %{ruby -e 'exit 1 if ENV["SAWS_SECRET"] == "supersecret"'} resource.run_action(:run) expect(resource).not_to be_updated_by_last_action end it "guard adds additional values in its :environment and runs" do resource.only_if %{ruby -e 'exit 1 if ENV["SGCE_SECRET"] != "regularsecret"'}, { :environment => { "SGCE_SECRET" => "regularsecret" }, } resource.run_action(:run) expect(resource).to be_updated_by_last_action end it "guard adds additional values in its :environment and does not run" do resource.only_if %{ruby -e 'exit 1 if ENV["SGCE_SECRET"] == "regularsecret"'}, { :environment => { "SGCE_SECRET" => "regularsecret" }, } resource.run_action(:run) expect(resource).not_to be_updated_by_last_action end it "guard overwrites value with its :environment and runs" do resource.only_if %{ruby -e 'exit 1 if ENV["SAWS_SECRET"] != "regularsecret"'}, { :environment => { "SAWS_SECRET" => "regularsecret" }, } resource.run_action(:run) expect(resource).to be_updated_by_last_action end it "guard overwrites value with its :environment and does not runs" do resource.only_if %{ruby -e 'exit 1 if ENV["SAWS_SECRET"] == "regularsecret"'}, { :environment => { "SAWS_SECRET" => "regularsecret" }, } resource.run_action(:run) expect(resource).not_to be_updated_by_last_action end end # Ensure that CommandTimeout is raised, and is caused by resource.timeout really expiring. # https://github.com/chef/chef/issues/2985 # # resource.timeout should be short, this is what we're testing # resource.command ruby sleep timer should be longer than resource.timeout to give us something to timeout # Timeout::timeout should be longer than resource.timeout, but less than the resource.command ruby sleep timer, # so we fail if we finish on resource.command instead of resource.timeout, but raise CommandTimeout anyway (#2175). it "times out when a timeout is set on the resource" do Timeout.timeout(30) do resource.command %{ruby -e 'sleep 300'} resource.timeout 0.1 expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::CommandTimeout) end end end chef-12.14.60/spec/functional/resource/file_spec.rb000066400000000000000000000107601276456504500220750ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "tmpdir" describe Chef::Resource::File do include_context Chef::Resource::File let(:file_base) { "file_spec" } let(:expected_content) { "Don't fear the ruby." } def create_resource(opts = {}) events = Chef::EventDispatch::Dispatcher.new node = Chef::Node.new run_context = Chef::RunContext.new(node, {}, events) use_path = if opts[:use_relative_path] Dir.chdir(Dir.tmpdir) File.basename(path) else path end Chef::Resource::File.new(use_path, run_context) end let(:resource) do r = create_resource r.content(expected_content) r end let(:resource_without_content) do create_resource end let(:resource_with_relative_path) do create_resource(:use_relative_path => true) end let(:unmanaged_content) do "This is file content that is not managed by chef" end let(:current_resource) do provider = resource.provider_for_action(resource.action) provider.load_current_resource provider.current_resource end let(:default_mode) { (0666 & ~File.umask).to_s(8) } it_behaves_like "a file resource" it_behaves_like "a securable resource with reporting" describe "when running action :create without content" do before do resource_without_content.run_action(:create) end context "and the target file does not exist" do it "creates the file" do expect(File).to exist(path) end it "is marked updated by last action" do expect(resource_without_content).to be_updated_by_last_action end end end describe "when using backup" do before do Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH resource_without_content.backup(1) resource_without_content.run_action(:create) end let(:backup_glob) { File.join(CHEF_SPEC_BACKUP_PATH, test_file_dir.sub(/^([A-Za-z]:)/, ""), "#{file_base}*") } let(:path) do # Use native system path ChefConfig::PathHelper.canonical_path(File.join(test_file_dir, make_tmpname(file_base)), false) end it "only stores the number of requested backups" do resource_without_content.content("foo") resource_without_content.run_action(:create) resource_without_content.content("bar") resource_without_content.run_action(:create) expect(Dir.glob(backup_glob).length).to eq(1) end end # github issue 1842. describe "when running action :create on a relative path" do before do resource_with_relative_path.run_action(:create) end context "and the file exists" do it "should run without an exception" do resource_with_relative_path.run_action(:create) end end end describe "when running action :touch" do context "and the target file does not exist" do before do resource.run_action(:touch) end it "it creates the file" do expect(File).to exist(path) end it "is marked updated by last action" do expect(resource).to be_updated_by_last_action end end context "and the target file exists and has the correct content" do before(:each) do File.open(path, "w") { |f| f.print expected_content } @expected_checksum = sha256_checksum(path) now = Time.now.to_i File.utime(now - 9000, now - 9000, path) @expected_mtime = File.stat(path).mtime resource.run_action(:touch) end it "updates the mtime of the file" do expect(File.stat(path).mtime).to be > @expected_mtime end it "does not change the content" do expect(sha256_checksum(path)).to eq(@expected_checksum) end it "is marked as updated by last action" do expect(resource).to be_updated_by_last_action end end end end chef-12.14.60/spec/functional/resource/git_spec.rb000066400000000000000000000220511276456504500217350ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2013-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/mixin/shell_out" require "tmpdir" require "shellwords" # Deploy relies heavily on symlinks, so it doesn't work on windows. describe Chef::Resource::Git, :requires_git => true do include Chef::Mixin::ShellOut let(:file_cache_path) { Dir.mktmpdir } # Some versions of git complains when the deploy directory is # already created. Here we intentionally don't create the deploy # directory beforehand. let(:base_dir_path) { Dir.mktmpdir } let(:deploy_directory) { File.join(base_dir_path, make_tmpname("git_base")) } let(:node) do Chef::Node.new.tap do |n| n.name "rspec-test" n.consume_external_attrs(@ohai.data, {}) end end let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) } # These tests use git's bundle feature, which is a way to export an entire # git repo (or subset of commits) as a single file. # # Generally you can treat a git bundle as a regular git remote. # # See also: http://git-scm.com/2010/03/10/bundles.html # # Beware that git bundles don't behave exactly the same as real # remotes. To get closer to real remotes, we'll create a local clone # of the bundle to use as a remote for the tests. This at least # gives the expected responses for ls-remote using git version # 1.7.12.4 let(:git_bundle_repo) { File.expand_path("git_bundles/example-repo.gitbundle", CHEF_SPEC_DATA) } let(:origin_repo_dir) { Dir.mktmpdir } let(:origin_repo) { "#{origin_repo_dir}/example" } # This is the fourth version let(:v1_commit) { "bc5ec79931ae74089aeadca6edc173527613e6d9" } let(:v1_tag) { "9b73fb5e316bfaff7b822b0ccb3e1e08f9885085" } let(:rev_foo) { "ed181b3419b6f489bedab282348162a110d6d3a1" } let(:rev_testing) { "972d153654503bccec29f630c5dd369854a561e8" } let(:rev_head) { "d294fbfd05aa7709ad9a9b8ef6343b17d355bf5f" } let(:git_user_config) do <<-E [user] name = frodoTbaggins email = frodo@shire.org E end before(:each) do Chef::Log.level = :warn # silence git command live streams @old_file_cache_path = Chef::Config[:file_cache_path] shell_out!("git clone \"#{git_bundle_repo}\" example", :cwd => origin_repo_dir) File.open("#{origin_repo}/.git/config", "a+") { |f| f.print(git_user_config) } Chef::Config[:file_cache_path] = file_cache_path end after(:each) do Chef::Config[:file_cache_path] = @old_file_cache_path FileUtils.remove_entry_secure deploy_directory if File.exist?(deploy_directory) FileUtils.remove_entry_secure base_dir_path FileUtils.remove_entry_secure file_cache_path FileUtils.remove_entry_secure origin_repo_dir end before(:all) do @ohai = Ohai::System.new @ohai.all_plugins(%w{platform os}) end context "working with pathes with special characters" do let(:path_with_spaces) { "#{origin_repo_dir}/path with spaces" } before(:each) do FileUtils.mkdir(path_with_spaces) FileUtils.cp(git_bundle_repo, path_with_spaces) end it "clones a repository with a space in the path" do Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| r.repository "#{path_with_spaces}/example-repo.gitbundle" end.run_action(:sync) end end context "when deploying from an annotated tag" do let(:basic_git_resource) do Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| r.repository origin_repo r.revision "v1.0.0" end end # We create a copy of the basic_git_resource so that we can run # the resource again and verify that it doesn't update. let(:copy_git_resource) do Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| r.repository origin_repo r.revision "v1.0.0" end end it "checks out the revision pointed to by the tag commit, not the tag commit itself" do basic_git_resource.run_action(:sync) head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip expect(head_rev).to eq(v1_commit) # also verify the tag commit itself is what we expect as an extra sanity check rev = shell_out!("git rev-parse v1.0.0", :cwd => deploy_directory, :returns => [0]).stdout.strip expect(rev).to eq(v1_tag) end it "doesn't update if up-to-date" do # this used to fail because we didn't resolve the annotated tag # properly to the pointed to commit. basic_git_resource.run_action(:sync) head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip expect(head_rev).to eq(v1_commit) copy_git_resource.run_action(:sync) expect(copy_git_resource).not_to be_updated end end context "when deploying from a SHA revision" do let(:basic_git_resource) do Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| r.repository git_bundle_repo end end # We create a copy of the basic_git_resource so that we can run # the resource again and verify that it doesn't update. let(:copy_git_resource) do Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| r.repository origin_repo end end it "checks out the expected revision ed18" do basic_git_resource.revision rev_foo basic_git_resource.run_action(:sync) head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip expect(head_rev).to eq(rev_foo) end it "doesn't update if up-to-date" do basic_git_resource.revision rev_foo basic_git_resource.run_action(:sync) head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip expect(head_rev).to eq(rev_foo) copy_git_resource.revision rev_foo copy_git_resource.run_action(:sync) expect(copy_git_resource).not_to be_updated end it "checks out the expected revision 972d" do basic_git_resource.revision rev_testing basic_git_resource.run_action(:sync) head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip expect(head_rev).to eq(rev_testing) end end context "when deploying from a revision named 'HEAD'" do let(:basic_git_resource) do Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| r.repository origin_repo r.revision "HEAD" end end it "checks out the expected revision" do basic_git_resource.run_action(:sync) head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip expect(head_rev).to eq(rev_head) end end context "when deploying from the default revision" do let(:basic_git_resource) do Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| r.repository origin_repo # use default end end it "checks out HEAD as the default revision" do basic_git_resource.run_action(:sync) head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip expect(head_rev).to eq(rev_head) end end context "when dealing with a repo with a degenerate tag named 'HEAD'" do before do shell_out!("git tag -m\"degenerate tag\" HEAD ed181b3419b6f489bedab282348162a110d6d3a1", :cwd => origin_repo) end let(:basic_git_resource) do Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| r.repository origin_repo r.revision "HEAD" end end let(:git_resource_default_rev) do Chef::Resource::Git.new(deploy_directory, run_context).tap do |r| r.repository origin_repo # use default of revision end end it "checks out the (master) HEAD revision and ignores the tag" do basic_git_resource.run_action(:sync) head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip expect(head_rev).to eq(rev_head) end it "checks out the (master) HEAD revision when no revision is specified (ignores tag)" do git_resource_default_rev.run_action(:sync) head_rev = shell_out!("git rev-parse HEAD", :cwd => deploy_directory, :returns => [0]).stdout.strip expect(head_rev).to eq(rev_head) end end end chef-12.14.60/spec/functional/resource/group_spec.rb000066400000000000000000000365671276456504500223270ustar00rootroot00000000000000# # Author:: Chirag Jog () # Author:: Siddheshwar More () # Copyright:: Copyright 2013-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 "functional/resource/base" require "chef/mixin/shell_out" # Chef::Resource::Group are turned off on Mac OS X 10.6 due to caching # issues around Etc.getgrnam() not picking up the group membership # changes that are done on the system. Etc.endgrent is not functioning # correctly on certain 10.6 boxes. describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supported_on_mac_osx_106 do include Chef::Mixin::ShellOut def group_should_exist(group) case ohai[:platform_family] when "debian", "fedora", "rhel", "suse", "gentoo", "slackware", "arch" expect { Etc.getgrnam(group) }.not_to raise_error expect(group).to eq(Etc.getgrnam(group).name) when "windows" expect { Chef::Util::Windows::NetGroup.new(group).local_get_members }.not_to raise_error end end def user_exist_in_group?(user) case ohai[:platform_family] when "windows" user_sid = sid_string_from_user(user) user_sid.nil? ? false : Chef::Util::Windows::NetGroup.new(group_name).local_get_members.include?(user_sid) when "mac_os_x" membership_info = shell_out("dscl . -read /Groups/#{group_name}").stdout members = membership_info.split(" ") members.shift # Get rid of GroupMembership: string members.include?(user) else Etc.getgrnam(group_name).mem.include?(user) end end def group_should_not_exist(group) case ohai[:platform_family] when "debian", "fedora", "rhel", "suse", "gentoo", "slackware", "arch" expect { Etc.getgrnam(group) }.to raise_error(ArgumentError, "can't find group for #{group}") when "windows" expect { Chef::Util::Windows::NetGroup.new(group).local_get_members }.to raise_error(ArgumentError, /The group name could not be found./) end end def compare_gid(resource, gid) return resource.gid == Etc.getgrnam(resource.name).gid if unix? end def sid_string_from_user(user) begin sid = Chef::ReservedNames::Win32::Security.lookup_account_name(user) rescue Chef::Exceptions::Win32APIError sid = nil end sid.nil? ? nil : sid[1].to_s end def windows_domain_user?(user_name) domain, user = user_name.split('\\') if user && domain != "." computer_name = ENV["computername"] !domain.casecmp(computer_name.downcase).zero? end end def node node = Chef::Node.new node.consume_external_attrs(ohai.data, {}) node end def user(username) usr = Chef::Resource.resource_for_node(:user, node).new(username, run_context) if ohai[:platform_family] == "windows" usr.password("ComplexPass11!") end usr end def create_user(username) user(username).run_action(:create) if ! windows_domain_user?(username) # TODO: User should exist end def remove_user(username) if ! windows_domain_user?(username) u = user(username) u.manage_home false # jekins hosts throw mail spool file not owned by user if we use manage_home true u.run_action(:remove) end # TODO: User shouldn't exist end shared_examples_for "correct group management" do def add_members_to_group(members) temp_resource = group_resource.dup temp_resource.members(members) temp_resource.excluded_members([ ]) temp_resource.append(true) temp_resource.run_action(:modify) members.each do |member| expect(user_exist_in_group?(member)).to eq(true) end end def create_group temp_resource = group_resource.dup temp_resource.members([ ]) temp_resource.excluded_members([ ]) temp_resource.run_action(:create) group_should_exist(group_name) included_members.each do |member| expect(user_exist_in_group?(member)).to eq(false) end end before(:each) do create_group end after(:each) do group_resource.run_action(:remove) group_should_not_exist(group_name) end # dscl doesn't perform any error checking and will let you add users that don't exist. describe "when no users exist", :not_supported_on_mac_osx do describe "when append is not set" do # excluded_members can only be used when append is set. It is ignored otherwise. let(:excluded_members) { [] } it "should raise an error" do expect { group_resource.run_action(tested_action) }.to raise_error() end end describe "when append is set" do before do group_resource.append(true) end it "should raise an error" do expect { group_resource.run_action(tested_action) }.to raise_error() end end end describe "when the users exist" do before do (spec_members).each do |member| create_user(member) end end after do (spec_members).each do |member| remove_user(member) end end describe "when append is not set" do it "should set the group to to contain given members" do group_resource.run_action(tested_action) included_members.each do |member| expect(user_exist_in_group?(member)).to eq(true) end (spec_members - included_members).each do |member| expect(user_exist_in_group?(member)).to eq(false) end end describe "when group already contains some users" do before do add_members_to_group([included_members[0]]) add_members_to_group(spec_members - included_members) end it "should remove all existing users and only add the new users to the group" do group_resource.run_action(tested_action) included_members.each do |member| expect(user_exist_in_group?(member)).to eq(true) end (spec_members - included_members).each do |member| expect(user_exist_in_group?(member)).to eq(false) end end end end describe "when append is set" do before(:each) do group_resource.append(true) end it "should add included members to the group" do group_resource.run_action(tested_action) included_members.each do |member| expect(user_exist_in_group?(member)).to eq(true) end excluded_members.each do |member| expect(user_exist_in_group?(member)).to eq(false) end end describe "when group already contains some users" do before(:each) do add_members_to_group([included_members[0], excluded_members[0]]) end it "should add the included users and remove excluded users" do group_resource.run_action(tested_action) included_members.each do |member| expect(user_exist_in_group?(member)).to eq(true) end excluded_members.each do |member| expect(user_exist_in_group?(member)).to eq(false) end end end end end end shared_examples_for "an expected invalid domain error case" do let(:invalid_domain_user_name) { "no space\\administrator" } let(:nonexistent_domain_user_name) { "xxfakedom\\administrator" } before(:each) do group_resource.members [] group_resource.excluded_members [] group_resource.append(true) group_resource.run_action(:create) group_should_exist(group_name) end after(:each) do group_resource.run_action(:remove) end # TODO: The ones below might actually return ArgumentError now - but I don't have # a way to verify that. Change it and delete this comment if that's the case. describe "when updating membership" do it "raises an error for a non well-formed domain name" do group_resource.members [invalid_domain_user_name] expect { group_resource.run_action(tested_action) }.to raise_error Chef::Exceptions::Win32APIError end it "raises an error for a nonexistent domain" do group_resource.members [nonexistent_domain_user_name] expect { group_resource.run_action(tested_action) }.to raise_error Chef::Exceptions::Win32APIError end end describe "when removing members" do it "does not raise an error for a non well-formed domain name" do group_resource.excluded_members [invalid_domain_user_name] expect { group_resource.run_action(tested_action) }.to_not raise_error end it "does not raise an error for a nonexistent domain" do group_resource.excluded_members [nonexistent_domain_user_name] expect { group_resource.run_action(tested_action) }.to_not raise_error end end end let(:group_name) { "group#{SecureRandom.random_number(9999)}" } let(:included_members) { nil } let(:excluded_members) { nil } let(:group_resource) do group = Chef::Resource::Group.new(group_name, run_context) group.members(included_members) group.excluded_members(excluded_members) group end it "append should be false by default" do expect(group_resource.append).to eq(false) end describe "group create action" do after(:each) do group_resource.run_action(:remove) group_should_not_exist(group_name) end it "should create a group" do group_resource.run_action(:create) group_should_exist(group_name) end describe "when group name is length 256", :windows_only do let!(:group_name) do "theoldmanwalkingdownthestreetalwayshadagood\ smileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisface\ theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking\ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestree" end it "should create a group" do group_resource.run_action(:create) group_should_exist(group_name) end end describe "when group name length is more than 256", :windows_only do let!(:group_name) do "theoldmanwalkingdownthestreetalwayshadagood\ smileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisface\ theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking\ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" end it "should not create a group" do expect { group_resource.run_action(:create) }.to raise_error(ArgumentError) group_should_not_exist(group_name) end end # not_supported_on_solaris because of the use of excluded_members describe "should raise an error when same member is included in the members and excluded_members", :not_supported_on_solaris do it "should raise an error" do invalid_resource = group_resource.dup invalid_resource.members(["Jack"]) invalid_resource.excluded_members(["Jack"]) expect { invalid_resource.run_action(:create) }.to raise_error(Chef::Exceptions::ConflictingMembersInGroup) end end end describe "group remove action" do describe "when there is a group" do before do group_resource.run_action(:create) group_should_exist(group_name) end it "should remove a group" do group_resource.run_action(:remove) group_should_not_exist(group_name) end end describe "when there is no group" do it "should be no-op" do group_resource.run_action(:remove) group_should_not_exist(group_name) end end end describe "group modify action", :not_supported_on_solaris do let(:spec_members) { %w{mnou5sdz htulrvwq x4c3g1lu} } let(:included_members) { [spec_members[0], spec_members[1]] } let(:excluded_members) { [spec_members[2]] } let(:tested_action) { :modify } describe "when there is no group" do it "should raise an error" do expect { group_resource.run_action(:modify) }.to raise_error(Chef::Exceptions::Group) end end describe "when there is a group" do it_behaves_like "correct group management" end describe "when running on Windows", :windows_only do describe "when members are Active Directory domain identities", :windows_domain_joined_only do let(:computer_domain) { ohai[:kernel]["cs_info"]["domain"].split(".")[0] } let(:spec_members) { ["#{computer_domain}\\Domain Admins", "#{computer_domain}\\Domain Users", "#{computer_domain}\\Domain Computers"] } include_examples "correct group management" end it_behaves_like "an expected invalid domain error case" end end describe "group manage action", :not_supported_on_solaris do let(:spec_members) { %w{mnou5sdz htulrvwq x4c3g1lu} } let(:included_members) { [spec_members[0], spec_members[1]] } let(:excluded_members) { [spec_members[2]] } let(:tested_action) { :manage } describe "when there is no group" do before(:each) do group_resource.run_action(:remove) group_should_not_exist(group_name) end it "raises an error on modify" do expect { group_resource.run_action(:modify) }.to raise_error(Chef::Exceptions::Group) end it "does not raise an error on manage" do expect { group_resource.run_action(:manage) }.not_to raise_error end end describe "when there is a group" do it_behaves_like "correct group management" end describe "running on windows", :windows_only do describe "when members are Windows domain identities", :windows_domain_joined_only do let(:computer_domain) { ohai[:kernel]["cs_info"]["domain"].split(".")[0] } let(:spec_members) { ["#{computer_domain}\\Domain Admins", "#{computer_domain}\\Domain Users", "#{computer_domain}\\Domain Computers"] } include_examples "correct group management" end it_behaves_like "an expected invalid domain error case" end end describe "group resource with Usermod provider", :solaris_only do describe "when excluded_members is set" do let(:excluded_members) { ["x4c3g1lu"] } it ":manage should raise an error" do expect { group_resource.run_action(:manage) }.to raise_error end it ":modify should raise an error" do expect { group_resource.run_action(:modify) }.to raise_error end it ":create should raise an error" do expect { group_resource.run_action(:create) }.to raise_error end end describe "when append is not set" do let(:included_members) { %w{gordon eric} } before(:each) do group_resource.append(false) end it ":manage should raise an error" do expect { group_resource.run_action(:manage) }.to raise_error end it ":modify should raise an error" do expect { group_resource.run_action(:modify) }.to raise_error end end end end chef-12.14.60/spec/functional/resource/ifconfig_spec.rb000066400000000000000000000113331276456504500227370ustar00rootroot00000000000000# # Author:: Kaustubh Deorukhkar () # Copyright:: Copyright 2013-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 "functional/resource/base" require "chef/mixin/shell_out" # run this test only for following platforms. include_flag = !(%w{ubuntu centos aix}.include?(ohai[:platform])) describe Chef::Resource::Ifconfig, :requires_root, :skip_travis, :external => include_flag do # This test does not work in travis because there is no eth0 include Chef::Mixin::ShellOut let(:new_resource) do new_resource = Chef::Resource::Ifconfig.new("10.10.0.1", run_context) new_resource end let(:provider) do provider = new_resource.provider_for_action(new_resource.action) provider end let(:current_resource) do provider.load_current_resource end def lo_interface_for_test # use loopback interface for tests case ohai[:platform] when "aix" "lo0" else "lo" end end # **Caution: any updates to core interfaces can be risky. def en0_interface_for_test case ohai[:platform] when "aix" "en0" else "eth0" end end def network_interface_alias(interface) case ohai[:platform] when "aix" interface else interface + ":10" end end # platform specific test setup and validation routines def setup_add_interface(resource) resource.device network_interface_alias(en0_interface_for_test) end def setup_enable_interface(resource) resource.device network_interface_alias(en0_interface_for_test) end def interface_should_exists(interface) expect(shell_out("ifconfig #{@interface} | grep 10.10.0.1").exitstatus).to eq(0) end def interface_should_not_exists(interface) expect(shell_out("ifconfig #{@interface} | grep 10.10.0.1").exitstatus).to eq(1) end def interface_persistence_should_exists(interface) case ohai[:platform] when "aix" expect(shell_out("lsattr -E -l #{@interface} | grep 10.10.0.1").exitstatus).to eq(0) else end end def interface_persistence_should_not_exists(interface) case ohai[:platform] when "aix" expect(shell_out("lsattr -E -l #{@interface} | grep 10.10.0.1").exitstatus).to eq(1) else end end # Actual tests describe "#load_current_resource" do it "should load given interface" do new_resource.device lo_interface_for_test expect(current_resource.device).to eql(lo_interface_for_test) expect(current_resource.inet_addr).to match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) end end exclude_test = ohai[:platform] != "ubuntu" describe "#action_add", :external => exclude_test do after do new_resource.run_action(:delete) end it "should add interface (vip)" do setup_add_interface(new_resource) new_resource.run_action(:add) interface_should_exists(network_interface_alias(en0_interface_for_test)) interface_persistence_should_exists(network_interface_alias(en0_interface_for_test)) end end describe "#action_enable", :external => exclude_test do after do new_resource.run_action(:disable) end it "should enable interface (vip)" do setup_enable_interface(new_resource) new_resource.run_action(:enable) interface_should_exists(network_interface_alias(en0_interface_for_test)) end end describe "#action_disable", :external => exclude_test do before do setup_enable_interface(new_resource) new_resource.run_action(:enable) end it "should disable interface (vip)" do new_resource.run_action(:disable) expect(new_resource).to be_updated_by_last_action interface_should_not_exists(network_interface_alias(en0_interface_for_test)) end end describe "#action_delete", :external => exclude_test do before do setup_add_interface(new_resource) new_resource.run_action(:add) end it "should delete interface (vip)" do new_resource.run_action(:delete) expect(new_resource).to be_updated_by_last_action interface_should_not_exists(network_interface_alias(en0_interface_for_test)) interface_persistence_should_not_exists(network_interface_alias(en0_interface_for_test)) end end end chef-12.14.60/spec/functional/resource/link_spec.rb000066400000000000000000000551251276456504500221170ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" if windows? require "chef/win32/file" #probably need this in spec_helper end describe Chef::Resource::Link do let(:file_base) { "file_spec" } let(:expect_updated?) { true } # We create the files in a different directory than tmp to exercise # different file deployment strategies more completely. let(:test_file_dir) do if windows? File.join(ENV["systemdrive"], "test-dir") else File.join(CHEF_SPEC_DATA, "test-dir") end end before do FileUtils.mkdir_p(test_file_dir) end after do FileUtils.rm_rf(test_file_dir) end let(:to) do File.join(test_file_dir, make_tmpname("to_spec")) end let(:target_file) do File.join(test_file_dir, make_tmpname("from_spec")) end after(:each) do begin cleanup_link(to) if File.exists?(to) cleanup_link(target_file) if File.exists?(target_file) cleanup_link(CHEF_SPEC_BACKUP_PATH) if File.exists?(CHEF_SPEC_BACKUP_PATH) rescue puts "Could not remove a file: #{$!}" end end def cleanup_link(path) if windows? && File.directory?(path) # If the link target is a directory rm_rf doesn't work all the # time on windows. system "rmdir '#{path}'" else FileUtils.rm_rf(path) end end def canonicalize(path) windows? ? path.tr("/", '\\') : path end def symlink(a, b) if windows? Chef::ReservedNames::Win32::File.symlink(a, b) else File.symlink(a, b) end end def symlink?(file) if windows? Chef::ReservedNames::Win32::File.symlink?(file) else File.symlink?(file) end end def readlink(file) if windows? Chef::ReservedNames::Win32::File.readlink(file) else File.readlink(file) end end def link(a, b) if windows? Chef::ReservedNames::Win32::File.link(a, b) else File.link(a, b) end end def create_resource node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) cookbook_collection = Chef::CookbookCollection.new(Chef::CookbookLoader.new(cookbook_repo)) run_context = Chef::RunContext.new(node, cookbook_collection, events) resource = Chef::Resource::Link.new(target_file, run_context) resource.to(to) resource end let(:resource) do create_resource end describe "when supported on platform", :not_supported_on_win2k3 do shared_examples_for "delete errors out" do it "delete errors out" do expect { resource.run_action(:delete) }.to raise_error(Chef::Exceptions::Link) expect(File.exist?(target_file) || symlink?(target_file)).to be_truthy end end shared_context "delete is noop" do describe "the :delete action" do before(:each) do @info = [] allow(Chef::Log).to receive(:info) { |msg| @info << msg } resource.run_action(:delete) end it "leaves the file deleted" do expect(File.exist?(target_file)).to be_falsey expect(symlink?(target_file)).to be_falsey end it "does not mark the resource updated" do expect(resource).not_to be_updated end it "does not log that it deleted" do expect(@info.include?("link[#{target_file}] deleted")).to be_falsey end end end shared_context "delete succeeds" do describe "the :delete action" do before(:each) do @info = [] allow(Chef::Log).to receive(:info) { |msg| @info << msg } resource.run_action(:delete) end it "deletes the file" do expect(File.exist?(target_file)).to be_falsey expect(symlink?(target_file)).to be_falsey end it "marks the resource updated" do expect(resource).to be_updated end it "logs that it deleted" do expect(@info.include?("link[#{target_file}] deleted")).to be_truthy end end end shared_context "create symbolic link succeeds" do describe "the :create action" do before(:each) do @info = [] allow(Chef::Log).to receive(:info) { |msg| @info << msg } resource.run_action(:create) end it "links to the target file" do expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(to)) end it "marks the resource updated" do expect(resource).to be_updated end it "logs that it created" do expect(@info.include?("link[#{target_file}] created")).to be_truthy end end end shared_context "create symbolic link is noop" do describe "the :create action" do before(:each) do @info = [] allow(Chef::Log).to receive(:info) { |msg| @info << msg } resource.run_action(:create) end it "leaves the file linked" do expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(to)) end it "does not mark the resource updated" do expect(resource).not_to be_updated end it "does not log that it created" do expect(@info.include?("link[#{target_file}] created")).to be_falsey end end end shared_context "create hard link succeeds" do describe "the :create action" do before(:each) do @info = [] allow(Chef::Log).to receive(:info) { |msg| @info << msg } resource.run_action(:create) end it "preserves the hard link" do expect(File.exists?(target_file)).to be_truthy expect(symlink?(target_file)).to be_falsey # Writing to one hardlinked file should cause both # to have the new value. expect(IO.read(to)).to eq(IO.read(target_file)) File.open(to, "w") { |file| file.write("wowzers") } expect(IO.read(target_file)).to eq("wowzers") end it "marks the resource updated" do expect(resource).to be_updated end it "logs that it created" do expect(@info.include?("link[#{target_file}] created")).to be_truthy end end end shared_context "create hard link is noop" do describe "the :create action" do before(:each) do @info = [] allow(Chef::Log).to receive(:info) { |msg| @info << msg } resource.run_action(:create) end it "links to the target file" do expect(File.exists?(target_file)).to be_truthy expect(symlink?(target_file)).to be_falsey # Writing to one hardlinked file should cause both # to have the new value. expect(IO.read(to)).to eq(IO.read(target_file)) File.open(to, "w") { |file| file.write("wowzers") } expect(IO.read(target_file)).to eq("wowzers") end it "does not mark the resource updated" do expect(resource).not_to be_updated end it "does not log that it created" do expect(@info.include?("link[#{target_file}] created")).to be_falsey end end end context "is symbolic" do context "when the link destination is a file" do before(:each) do File.open(to, "w") do |file| file.write("woohoo") end end context "and the link does not yet exist" do include_context "create symbolic link succeeds" include_context "delete is noop" end context "and the link already exists and is a symbolic link" do context "pointing at the target" do before(:each) do symlink(to, target_file) expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(to)) end include_context "create symbolic link is noop" include_context "delete succeeds" it "the :delete action does not delete the target file" do resource.run_action(:delete) expect(File.exists?(to)).to be_truthy end end context "pointing somewhere else" do before(:each) do @other_target = File.join(test_file_dir, make_tmpname("other_spec")) File.open(@other_target, "w") { |file| file.write("eek") } symlink(@other_target, target_file) expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(@other_target)) end after(:each) do File.delete(@other_target) end include_context "create symbolic link succeeds" include_context "delete succeeds" it "the :delete action does not delete the target file" do resource.run_action(:delete) expect(File.exists?(to)).to be_truthy end end context "pointing nowhere" do before(:each) do nonexistent = File.join(test_file_dir, make_tmpname("nonexistent_spec")) symlink(nonexistent, target_file) expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(nonexistent)) end include_context "create symbolic link succeeds" include_context "delete succeeds" end end context "and the link already exists and is a hard link to the file" do before(:each) do link(to, target_file) expect(File.exists?(target_file)).to be_truthy expect(symlink?(target_file)).to be_falsey end include_context "create symbolic link succeeds" it_behaves_like "delete errors out" end context "and the link already exists and is a file" do before(:each) do File.open(target_file, "w") { |file| file.write("eek") } end include_context "create symbolic link succeeds" it_behaves_like "delete errors out" end context "and the link already exists and is a directory" do before(:each) do Dir.mkdir(target_file) end it "create errors out" do if windows? expect { resource.run_action(:create) }.to raise_error(Errno::EACCES) elsif os_x? || solaris? || freebsd? || aix? expect { resource.run_action(:create) }.to raise_error(Errno::EPERM) else expect { resource.run_action(:create) }.to raise_error(Errno::EISDIR) end end it_behaves_like "delete errors out" end it_behaves_like "a securable resource without existing target" do let(:path) { target_file } def allowed_acl(sid, expected_perms) [ ACE.access_allowed(sid, expected_perms[:specific]) ] end def denied_acl(sid, expected_perms) [ ACE.access_denied(sid, expected_perms[:specific]) ] end def parent_inheritable_acls dummy_file_path = File.join(test_file_dir, "dummy_file") FileUtils.touch(dummy_file_path) dummy_desc = get_security_descriptor(dummy_file_path) FileUtils.rm_rf(dummy_file_path) dummy_desc end end end context "when the link destination is a directory" do before(:each) do Dir.mkdir(to) end # On Windows, readlink fails to open the link. FILE_FLAG_OPEN_REPARSE_POINT # might help, from http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx context "and the link does not yet exist" do include_context "create symbolic link succeeds" include_context "delete is noop" end context "and the link already exists and points to a different directory" do before(:each) do other_dir = File.join(test_file_dir, make_tmpname("other_dir")) Dir.mkdir(other_dir) symlink(other_dir, target_file) end include_context "create symbolic link succeeds" include_context "delete succeeds" end context "and the link already exists and points at the target" do before do symlink(to, target_file) end include_context "create symbolic link is noop" include_context "delete succeeds" end end context "when the link destination is a symbolic link" do context "to a file that exists" do before(:each) do @other_target = File.join(test_file_dir, make_tmpname("other_spec")) File.open(@other_target, "w") { |file| file.write("eek") } symlink(@other_target, to) expect(symlink?(to)).to be_truthy expect(readlink(to)).to eq(canonicalize(@other_target)) end after(:each) do File.delete(@other_target) end context "and the link does not yet exist" do include_context "create symbolic link succeeds" include_context "delete is noop" end context "and the destination itself has another symbolic link" do context "to a link that exist" do before do symlink(to, target_file) end include_context "create symbolic link is noop" include_context "delete succeeds" end context "to a link that does not exist" do include_context "create symbolic link succeeds" include_context "delete is noop" end end end context "to a file that does not exist" do before(:each) do @other_target = File.join(test_file_dir, make_tmpname("other_spec")) symlink(@other_target, to) expect(symlink?(to)).to be_truthy expect(readlink(to)).to eq(canonicalize(@other_target)) end context "and the link does not yet exist" do include_context "create symbolic link succeeds" include_context "delete is noop" end context "and the destination itself has another symbolic link" do context "to a link that exist" do before do symlink(to, target_file) end include_context "create symbolic link is noop" include_context "delete succeeds" end context "to a link that does not exist" do include_context "create symbolic link succeeds" include_context "delete is noop" end end end end context "when the link destination does not exist" do include_context "create symbolic link succeeds" include_context "delete is noop" end { "../" => "with a relative link destination", "" => "with a bare filename for the link destination", }.each do |prefix, desc| context desc do let(:to) { "#{prefix}#{File.basename(absolute_to)}" } let(:absolute_to) { File.join(test_file_dir, make_tmpname("to_spec")) } before(:each) do resource.to(to) end context "when the link does not yet exist" do include_context "create symbolic link succeeds" include_context "delete is noop" end context "when the link already exists and points at the target" do before(:each) do symlink(to, target_file) expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(to)) end include_context "create symbolic link is noop" include_context "delete succeeds" end context "when the link already exists and points at the target with an absolute path" do before(:each) do symlink(absolute_to, target_file) expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(absolute_to)) end include_context "create symbolic link succeeds" include_context "delete succeeds" end end end end context "is a hard link" do before(:each) do resource.link_type(:hard) end context "when the link destination is a file" do before(:each) do File.open(to, "w") do |file| file.write("woohoo") end end context "and the link does not yet exist" do include_context "create hard link succeeds" include_context "delete is noop" end context "and the link already exists and is a symbolic link pointing at the same file" do before(:each) do symlink(to, target_file) expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(to)) end include_context "create hard link succeeds" it_behaves_like "delete errors out" end context "and the link already exists and is a hard link to the file" do before(:each) do link(to, target_file) expect(File.exists?(target_file)).to be_truthy expect(symlink?(target_file)).to be_falsey end include_context "create hard link is noop" include_context "delete succeeds" it "the :delete action does not delete the target file" do resource.run_action(:delete) expect(File.exists?(to)).to be_truthy end end context "and the link already exists and is a file" do before(:each) do File.open(target_file, "w") { |file| file.write("tomfoolery") } end include_context "create hard link succeeds" it_behaves_like "delete errors out" end context "and the link already exists and is a directory" do before(:each) do Dir.mkdir(target_file) end it "errors out" do if windows? expect { resource.run_action(:create) }.to raise_error(Errno::EACCES) elsif os_x? || solaris? || freebsd? || aix? expect { resource.run_action(:create) }.to raise_error(Errno::EPERM) else expect { resource.run_action(:create) }.to raise_error(Errno::EISDIR) end end it_behaves_like "delete errors out" end context "and specifies security attributes" do before(:each) do resource.owner(windows? ? "Guest" : "nobody") end it "ignores them" do resource.run_action(:create) if windows? expect(Chef::ReservedNames::Win32::Security.get_named_security_info(target_file).owner).not_to eq(SID.Guest) else expect(File.lstat(target_file).uid).not_to eq(Etc.getpwnam("nobody").uid) end end end end context "when the link destination is a directory" do before(:each) do Dir.mkdir(to) end context "and the link does not yet exist" do it "create errors out" do expect { resource.run_action(:create) }.to raise_error(windows? ? Chef::Exceptions::Win32APIError : Errno::EPERM) end include_context "delete is noop" end end context "when the link destination is a symbolic link" do context "to a real file" do before(:each) do @other_target = File.join(test_file_dir, make_tmpname("other_spec")) File.open(@other_target, "w") { |file| file.write("eek") } symlink(@other_target, to) expect(symlink?(to)).to be_truthy expect(readlink(to)).to eq(canonicalize(@other_target)) end after(:each) do File.delete(@other_target) end context "and the link does not yet exist" do it "links to the target file" do skip("OS X/FreeBSD/AIX symlink? and readlink working on hard links to symlinks") if os_x? || freebsd? || aix? resource.run_action(:create) expect(File.exists?(target_file)).to be_truthy # OS X gets angry about this sort of link. Bug in OS X, IMO. expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(@other_target)) end include_context "delete is noop" end end context "to a nonexistent file" do before(:each) do @other_target = File.join(test_file_dir, make_tmpname("other_spec")) symlink(@other_target, to) expect(symlink?(to)).to be_truthy expect(readlink(to)).to eq(canonicalize(@other_target)) end context "and the link does not yet exist" do it "links to the target file" do skip("OS X/FreeBSD/AIX fails to create hardlinks to broken symlinks") if os_x? || freebsd? || aix? resource.run_action(:create) expect(File.exists?(target_file) || File.symlink?(target_file)).to be_truthy expect(symlink?(target_file)).to be_truthy expect(readlink(target_file)).to eq(canonicalize(@other_target)) end include_context "delete is noop" end end end context "when the link destination does not exist" do context "and the link does not yet exist" do it "create errors out" do expect { resource.run_action(:create) }.to raise_error(Errno::ENOENT) end include_context "delete is noop" end end end end describe "when not supported on platform", :win2k3_only do it "raises error" do expect { resource }.to raise_error(Chef::Exceptions::Win32APIFunctionNotImplemented) end end end chef-12.14.60/spec/functional/resource/mount_spec.rb000066400000000000000000000151161276456504500223200ustar00rootroot00000000000000# # Author:: Kaustubh Deorukhkar () # Copyright:: Copyright 2013-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 "functional/resource/base" require "chef/mixin/shell_out" require "tmpdir" # run this test only for following platforms. include_flag = !(%w{ubuntu centos aix solaris2}.include?(ohai[:platform])) describe Chef::Resource::Mount, :requires_root, :skip_travis, :external => include_flag do # Disabled in travis because it refuses to let us mount a ramdisk. /dev/ramX does not # exist even after loading the kernel module include Chef::Mixin::ShellOut # Platform specific setup, cleanup and validation helpers. def setup_device_for_mount # use ramdisk for creating a test device for mount. # This can cleaner if we have chef resource/provider for ramdisk. case ohai[:platform] when "aix" # On AIX, we can't create a ramdisk inside a WPAR, so we use # a "namefs" mount against / to test # https://www-304.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.performance/namefs_file_sys.htm device = "/" fstype = "namefs" when "ubuntu", "centos" device = "/dev/ram1" shell_out("ls -1 /dev/ram*").stdout.each_line do |d| if shell_out("mount | grep #{d}").exitstatus == "1" # this device is not mounted, so use it. device = d break end end fstype = "tmpfs" shell_out!("mkfs -q #{device} 512") when "solaris2" device = "swap" fstype = "tmpfs" else end [device, fstype] end def cleanup_mount(mount_point) shell_out("umount #{mount_point}") end # platform specific validations. def mount_should_exist(mount_point, device, fstype = nil, options = nil) validation_cmd = "mount | grep #{mount_point} | grep #{device} " validation_cmd << " | grep #{fstype} " unless fstype.nil? validation_cmd << " | grep #{options.join(',')} " unless options.nil? || options.empty? expect(shell_out(validation_cmd).exitstatus).to eq(0) end def mount_should_not_exists(mount_point) expect(shell_out("mount").stdout).not_to include(mount_point) end def unix_mount_config_file case ohai[:platform] when "aix" mount_config = "/etc/filesystems" when "solaris2" mount_config = "/etc/vfstab" else mount_config = "/etc/fstab" end end def mount_should_be_enabled(mount_point, device) case ohai[:platform] when "aix" expect(shell_out("cat #{unix_mount_config_file} | grep \"#{mount_point}:\" ").exitstatus).to eq(0) else expect(shell_out("cat #{unix_mount_config_file} | grep \"#{mount_point}\" | grep \"#{device}\" ").exitstatus).to eq(0) end end def mount_should_be_disabled(mount_point) expect(shell_out("cat #{unix_mount_config_file}").stdout).not_to include("#{mount_point}:") end let(:new_resource) do new_resource = Chef::Resource::Mount.new(@mount_point, run_context) new_resource.device @device new_resource.name @mount_point new_resource.fstype @fstype new_resource.options "log=NULL" if ohai[:platform] == "aix" new_resource end let(:provider) do provider = new_resource.provider_for_action(new_resource.action) provider end let(:current_resource) do provider.load_current_resource provider.current_resource end # Actual tests begin here. before(:all) do @device, @fstype = setup_device_for_mount @mount_point = Dir.mktmpdir("testmount") # Make sure all the potentially leaked mounts are cleared up shell_out("mount").stdout.each_line do |line| if line.include? "testmount" line.split(" ").each do |section| cleanup_mount(section) if section.include? "testmount" end end end end after(:all) do Dir.rmdir(@mount_point) end after(:each) do cleanup_mount(new_resource.mount_point) end describe "when the target state is a mounted filesystem" do it "should mount the filesystem if it isn't mounted" do expect(current_resource.enabled).to be_falsey expect(current_resource.mounted).to be_falsey new_resource.run_action(:mount) expect(new_resource).to be_updated mount_should_exist(new_resource.mount_point, new_resource.device) end end # don't run the remount tests on solaris2 (tmpfs does not support remount) # Need to make sure the platforms we've already excluded are considered: skip_remount = include_flag || (ohai[:platform] == "solaris2") describe "when the filesystem should be remounted and the resource supports remounting", :external => skip_remount do it "should remount the filesystem if it is mounted" do new_resource.run_action(:mount) mount_should_exist(new_resource.mount_point, new_resource.device) new_resource.supports[:remount] = true new_resource.options "rw" if ohai[:platform] == "aix" new_resource.run_action(:remount) mount_should_exist(new_resource.mount_point, new_resource.device, nil, (ohai[:platform] == "aix") ? new_resource.options : nil) end end describe "when the target state is a unmounted filesystem" do it "should umount the filesystem if it is mounted" do new_resource.run_action(:mount) mount_should_exist(new_resource.mount_point, new_resource.device) new_resource.run_action(:umount) mount_should_not_exists(new_resource.mount_point) end end describe "when enabling the filesystem to be mounted" do after do new_resource.run_action(:disable) end it "should enable the mount if it isn't enable" do new_resource.run_action(:mount) new_resource.run_action(:enable) mount_should_be_enabled(new_resource.mount_point, new_resource.device) end end describe "when the target state is to disable the mount" do it "should disable the mount if it is enabled" do new_resource.run_action(:mount) new_resource.run_action(:enable) new_resource.run_action(:disable) mount_should_be_disabled(new_resource.mount_point) end end end chef-12.14.60/spec/functional/resource/ohai_spec.rb000066400000000000000000000033061276456504500220740ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Ohai do let(:ohai) do OHAI_SYSTEM end let(:node) { Chef::Node.new } let(:run_context) do node.default[:platform] = ohai[:platform] node.default[:platform_version] = ohai[:platform_version] events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, events) end shared_examples_for "reloaded :uptime" do it "should reload :uptime" do initial_uptime = ohai[:uptime] # Sleep for a second so the uptime gets updated. sleep 1 ohai_resource.run_action(:reload) expect(node[:uptime]).not_to eq(initial_uptime) end end describe "when reloading all plugins" do let(:ohai_resource) { Chef::Resource::Ohai.new("reload all", run_context) } it_behaves_like "reloaded :uptime" end describe "when reloading only uptime" do let(:ohai_resource) do r = Chef::Resource::Ohai.new("reload all", run_context) r.plugin("uptime") r end it_behaves_like "reloaded :uptime" end end chef-12.14.60/spec/functional/resource/package_spec.rb000066400000000000000000000311131276456504500225440ustar00rootroot00000000000000# encoding: UTF-8 # # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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 "webrick" module AptServer def enable_testing_apt_source File.open("/etc/apt/sources.list.d/chef-integration-test.list", "w+") do |f| f.puts "deb http://localhost:9000/ sid main" end # Magic to update apt cache for only our repo shell_out!("apt-get update " + '-o Dir::Etc::sourcelist="sources.list.d/chef-integration-test.list" ' + '-o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"') end def disable_testing_apt_source FileUtils.rm("/etc/apt/sources.list.d/chef-integration-test.list") rescue Errno::ENOENT puts("Attempted to remove integration test from /etc/apt/sources.list.d but it didn't exist") end def tcp_test_port(hostname, port) tcp_socket = TCPSocket.new(hostname, port) true rescue Errno::ETIMEDOUT false rescue Errno::ECONNREFUSED false ensure tcp_socket && tcp_socket.close end def apt_server @apt_server ||= WEBrick::HTTPServer.new( :Port => 9000, :DocumentRoot => apt_data_dir + "/var/www/apt", # Make WEBrick quiet, comment out for debug. :Logger => Logger.new(StringIO.new), :AccessLog => [ StringIO.new, WEBrick::AccessLog::COMMON_LOG_FORMAT ] ) end def run_apt_server apt_server.start end def start_apt_server @apt_server_thread = Thread.new do run_apt_server end until tcp_test_port("localhost", 9000) if @apt_server_thread.alive? sleep 1 else @apt_server_thread.join raise "apt server failed to start" end end end def stop_apt_server apt_server.shutdown @apt_server_thread.join end def apt_data_dir File.join(CHEF_SPEC_DATA, "apt") end end metadata = { :unix_only => true, :requires_root => true, :provider => { :package => Chef::Provider::Package::Apt }, :arch => "x86_64" # test packages are 64bit } describe Chef::Resource::Package, metadata do include Chef::Mixin::ShellOut context "with a remote package source" do include AptServer before(:all) do # Disable mixlib-shellout live streams Chef::Log.level = :warn start_apt_server enable_testing_apt_source end after(:all) do stop_apt_server disable_testing_apt_source shell_out!("apt-get clean") end after do shell_out!("dpkg -r chef-integration-test") shell_out("dpkg --clear-avail") shell_out!("apt-get clean") end let(:node) do n = Chef::Node.new n.consume_external_attrs(OHAI_SYSTEM.data.dup, {}) n end let(:events) do Chef::EventDispatch::Dispatcher.new end # TODO: lots of duplication from client.rb; # All of this must be setup for preseed files to get found let(:cookbook_collection) do cookbook_path = File.join(CHEF_SPEC_DATA, "cookbooks") cl = Chef::CookbookLoader.new(cookbook_path) cl.load_cookbooks Chef::Cookbook::FileVendor.fetch_from_disk(cookbook_path) Chef::CookbookCollection.new(cl) end let(:run_context) do Chef::RunContext.new(node, cookbook_collection, events) end def base_resource r = Chef::Resource::Package.new("chef-integration-test", run_context) # The apt repository in the spec data is not gpg signed, so we need to # force apt to accept the package: r.options("--force-yes") r end let(:package_resource) do base_resource end context "when the package is not yet installed" do it "installs the package with action :install" do package_resource.run_action(:install) shell_out!("dpkg -l chef-integration-test") expect(package_resource).to be_updated_by_last_action end it "installs the package for action :upgrade" do package_resource.run_action(:upgrade) shell_out!("dpkg -l chef-integration-test") expect(package_resource).to be_updated_by_last_action end it "does nothing for action :remove" do package_resource.run_action(:remove) shell_out!("dpkg -l chef-integration-test", :returns => [1]) expect(package_resource).not_to be_updated_by_last_action end it "does nothing for action :purge" do package_resource.run_action(:purge) shell_out!("dpkg -l chef-integration-test", :returns => [1]) expect(package_resource).not_to be_updated_by_last_action end context "and a not-available package version is specified" do let(:package_resource) do r = base_resource r.version("2.0") r end it "raises a reasonable error for action :install" do expect do package_resource.run_action(:install) end.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end end describe "when preseeding the install" do let(:file_cache_path) { Dir.mktmpdir } before do Chef::Config[:file_cache_path] = file_cache_path debconf_reset = 'chef-integration-test chef-integration-test/sample-var string "INVALID"' shell_out!("echo #{debconf_reset} |debconf-set-selections") end after do FileUtils.rm_rf(file_cache_path) end context "with a preseed file" do let(:package_resource) do r = base_resource r.cookbook_name = "preseed" r.response_file("preseed-file.seed") r end it "preseeds the package, then installs it" do package_resource.run_action(:install) cmd = shell_out!("debconf-show chef-integration-test") expect(cmd.stdout).to include('chef-integration-test/sample-var: "hello world"') expect(package_resource).to be_updated_by_last_action end context "and the preseed file exists and is up-to-date" do before do # Code here is duplicated from the implementation. Not great, but # it should at least fail if the code gets out of sync. source = File.join(CHEF_SPEC_DATA, "cookbooks/preseed/files/default/preseed-file.seed") file_cache_dir = Chef::FileCache.create_cache_path("preseed/preseed") dest = "#{file_cache_dir}/chef-integration-test-1.1-1.seed" FileUtils.cp(source, dest) end it "does not update the package configuration" do package_resource.run_action(:install) cmd = shell_out!("debconf-show chef-integration-test") expect(cmd.stdout).to include("chef-integration-test/sample-var: INVALID") expect(package_resource).to be_updated_by_last_action end end end context "with a preseed template" do # NOTE: in the fixtures, there is also a cookbook_file named # "preseed-template.seed". This implicitly tests that templates are # preferred over cookbook_files when both are present. let(:package_resource) do r = base_resource r.cookbook_name = "preseed" r.response_file("preseed-template.seed") r end before do node.normal[:preseed_value] = "FROM TEMPLATE" end it "preseeds the package, then installs it" do package_resource.run_action(:install) cmd = shell_out!("debconf-show chef-integration-test") expect(cmd.stdout).to include('chef-integration-test/sample-var: "FROM TEMPLATE"') expect(package_resource).to be_updated_by_last_action end context "with variables" do let(:package_resource) do r = base_resource r.cookbook_name = "preseed" r.response_file("preseed-template-variables.seed") r.response_file_variables({ :template_variable => "SUPPORTS VARIABLES" }) r end it "preseeds the package, then installs it" do package_resource.run_action(:install) cmd = shell_out!("debconf-show chef-integration-test") expect(cmd.stdout).to include('chef-integration-test/sample-var: "SUPPORTS VARIABLES"') expect(package_resource).to be_updated_by_last_action end end end end # installing w/ preseed end # when package not installed context "and the desired version of the package is installed" do before do v_1_1_package = File.expand_path("apt/chef-integration-test_1.1-1_amd64.deb", CHEF_SPEC_DATA) shell_out!("dpkg -i #{v_1_1_package}") end it "does nothing for action :install" do package_resource.run_action(:install) shell_out!("dpkg -l chef-integration-test", :returns => [0]) expect(package_resource).not_to be_updated_by_last_action end it "does nothing for action :upgrade" do package_resource.run_action(:upgrade) shell_out!("dpkg -l chef-integration-test", :returns => [0]) expect(package_resource).not_to be_updated_by_last_action end # Verify that the package is removed by running `dpkg -l PACKAGE` # On Ubuntu 12.10 and newer, the command exits 1. # # On Ubuntu 12.04 and older, the `dpkg -l` command will exit 0 and # display a package status message like this: # # Desired=Unknown/Install/Remove/Purge/Hold # | Status=Not/Inst/Cfg-files/Unpacked/Failed-cfg/Half-inst/trig-aWait/Trig-pend # |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) # ||/ Name Version Description # +++-=================================-=========================================-============================================ # un chef-integration-test (no description available) def pkg_should_be_removed # will raise if exit code != 0,1 pkg_check = shell_out!("dpkg -l chef-integration-test", :returns => [0, 1]) if pkg_check.exitstatus == 0 expect(pkg_check.stdout).to match(/un[\s]+chef-integration-test/) end end it "removes the package for action :remove" do package_resource.run_action(:remove) pkg_should_be_removed expect(package_resource).to be_updated_by_last_action end it "removes the package for action :purge" do package_resource.run_action(:purge) pkg_should_be_removed expect(package_resource).to be_updated_by_last_action end end context "and an older version of the package is installed" do before do v_1_0_package = File.expand_path("apt/chef-integration-test_1.0-1_amd64.deb", CHEF_SPEC_DATA) shell_out!("dpkg -i #{v_1_0_package}") end it "does nothing for action :install" do package_resource.run_action(:install) shell_out!("dpkg -l chef-integration-test", :returns => [0]) expect(package_resource).not_to be_updated_by_last_action end it "upgrades the package for action :upgrade" do package_resource.run_action(:upgrade) dpkg_l = shell_out!("dpkg -l chef-integration-test", :returns => [0]) expect(dpkg_l.stdout).to match(/chef\-integration\-test[\s]+1\.1\-1/) expect(package_resource).to be_updated_by_last_action end context "and the resource specifies the new version" do let(:package_resource) do r = base_resource r.version("1.1-1") r end it "upgrades the package for action :install" do package_resource.run_action(:install) dpkg_l = shell_out!("dpkg -l chef-integration-test", :returns => [0]) expect(dpkg_l.stdout).to match(/chef\-integration\-test[\s]+1\.1\-1/) expect(package_resource).to be_updated_by_last_action end end end end end chef-12.14.60/spec/functional/resource/powershell_script_spec.rb000066400000000000000000000630241276456504500247270ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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/platform/query_helpers" require "spec_helper" describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do include_context Chef::Resource::WindowsScript let (:architecture_command) { "echo $env:PROCESSOR_ARCHITECTURE" } let (:output_command) { " | out-file -encoding ASCII " } it_behaves_like "a Windows script running on Windows" let(:successful_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe $env:systemroot" } let(:failed_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe /badargument" } let(:processor_architecture_script_content) { "echo $env:PROCESSOR_ARCHITECTURE" } let(:native_architecture_script_content) { "echo $env:PROCESSOR_ARCHITECTUREW6432" } let(:cmdlet_exit_code_not_found_content) { "get-item '.\\thisdoesnotexist'" } let(:cmdlet_exit_code_success_content) { "get-item ." } let(:windows_process_exit_code_success_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe $env:systemroot" } let(:windows_process_exit_code_not_found_content) { "findstr /notavalidswitch" } # Note that process exit codes on 32-bit Win2k3 cannot # exceed maximum value of signed integer let(:arbitrary_nonzero_process_exit_code) { 4193 } let(:arbitrary_nonzero_process_exit_code_content) { "exit #{arbitrary_nonzero_process_exit_code}" } let(:invalid_powershell_interpreter_flag) { "/thisflagisinvalid" } let(:valid_powershell_interpreter_flag) { "-Sta" } let!(:resource) do r = Chef::Resource::WindowsScript::PowershellScript.new("Powershell resource functional test", @run_context) r.code(successful_executable_script_content) r end describe "when the run action is invoked on Windows" do it "successfully executes a non-cmdlet Windows binary as the last command of the script" do resource.code(successful_executable_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.returns(0) resource.run_action(:run) end it "returns the exit status 27 for a powershell script that exits with 27" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? file = Tempfile.new(["foo", ".ps1"]) begin file.write "exit 27" file.close resource.code(". \"#{file.path}\"") resource.returns(27) resource.run_action(:run) ensure file.close file.unlink end end let (:negative_exit_status) { -27 } let (:unsigned_exit_status) { (-negative_exit_status ^ 65535) + 1 } it "returns the exit status -27 as a signed integer or an unsigned 16-bit 2's complement value of 65509 for a powershell script that exits with -27" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? # Versions of PowerShell prior to 4.0 return a 16-bit unsigned value -- # PowerShell 4.0 and later versions return a 32-bit signed value. file = Tempfile.new(["foo", ".ps1"]) begin file.write "exit #{negative_exit_status}" file.close resource.code(". \"#{file.path}\"") # PowerShell earlier than 4.0 takes negative exit codes # and returns them as the underlying unsigned 16-bit # 2's complement representation. We cover multiple versions # of PowerShell in this example by including both the signed # exit code and its converted counterpart as permitted return values. # See http://support.microsoft.com/en-us/kb/2646183/zh-cn resource.returns([negative_exit_status, unsigned_exit_status]) expect { resource.run_action(:run) }.not_to raise_error ensure file.close file.unlink end end it "returns the process exit code" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.code(arbitrary_nonzero_process_exit_code_content) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns 0 if the last command was a cmdlet that succeeded" do resource.code(cmdlet_exit_code_success_content) resource.returns(0) resource.run_action(:run) end it "returns 0 if the last command was a cmdlet that succeeded and was preceded by a non-cmdlet Windows binary that failed" do resource.code([windows_process_exit_code_not_found_content, cmdlet_exit_code_success_content].join(";")) resource.returns(0) resource.run_action(:run) end it "returns 1 if the last command was a cmdlet that failed" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.code(cmdlet_exit_code_not_found_content) resource.returns(1) resource.run_action(:run) end it "returns 1 if the last command was a cmdlet that failed and was preceded by a successfully executed non-cmdlet Windows binary" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.code([windows_process_exit_code_success_content, cmdlet_exit_code_not_found_content].join(";")) resource.returns(1) expect { resource.run_action(:run) }.not_to raise_error end it "raises a Mixlib::ShellOut::ShellCommandFailed error if the script is not syntactically correct" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.code("if({)") resource.returns(0) expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end it "raises an error if the script is not syntactically correct even if returns is set to 1 which is what powershell.exe returns for syntactically invalid scripts" do # This test fails because shell_out expects the exit status to be 1, but it is actually 0 # The error is a false-positive. skip "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.code("if({)") resource.returns(1) expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end # This somewhat ambiguous case, two failures of different types, # seems to violate the principle of returning the status of the # last line executed -- in this case, we return the status of the # second to last line. This happens because Powershell gives no # way for us to determine whether the last operation was a cmdlet # or Windows process. Because the latter gives more specific # errors than 0 or 1, we return that instead, which is acceptable # since callers can test for nonzero rather than testing for 1. it "returns 1 if the last command was a cmdlet that failed and was preceded by an unsuccessfully executed non-cmdlet Windows binary" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.code([arbitrary_nonzero_process_exit_code_content, cmdlet_exit_code_not_found_content].join(";")) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns 0 if the last command was a non-cmdlet Windows binary that succeeded and was preceded by a failed cmdlet" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.code([cmdlet_exit_code_success_content, arbitrary_nonzero_process_exit_code_content].join(";")) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns a specific error code if the last command was a non-cmdlet Windows binary that failed and was preceded by cmdlet that succeeded" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.code([cmdlet_exit_code_success_content, arbitrary_nonzero_process_exit_code_content].join(";")) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns a specific error code if the last command was a non-cmdlet Windows binary that failed and was preceded by cmdlet that failed" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.code([cmdlet_exit_code_not_found_content, arbitrary_nonzero_process_exit_code_content].join(";")) resource.returns(arbitrary_nonzero_process_exit_code) resource.run_action(:run) end it "returns 0 for $false as the last line of the script when convert_boolean_return is false" do resource.code "$false" resource.returns(0) resource.run_action(:run) end it "returns 0 for $true as the last line of the script when convert_boolean_return is false" do resource.code "$true" resource.returns(0) resource.run_action(:run) end it "returns 1 for $false as the last line of the script when convert_boolean_return is true" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.convert_boolean_return true resource.code "$false" resource.returns(1) resource.run_action(:run) end it "returns 0 for $true as the last line of the script when convert_boolean_return is true" do resource.convert_boolean_return true resource.code "$true" resource.returns(0) resource.run_action(:run) end it "executes a script with a 64-bit process on a 64-bit OS, otherwise a 32-bit process" do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.returns(0) resource.run_action(:run) is_64_bit = (ENV["PROCESSOR_ARCHITECTURE"] == "AMD64") || (ENV["PROCESSOR_ARCHITEW6432"] == "AMD64") detected_64_bit = source_contains_case_insensitive_content?( get_script_output, "AMD64" ) expect(is_64_bit).to eq(detected_64_bit) end it "returns 1 if an invalid flag is passed to the interpreter" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.code(cmdlet_exit_code_success_content) resource.flags(invalid_powershell_interpreter_flag) resource.returns(1) resource.run_action(:run) end it "returns 0 if a valid flag is passed to the interpreter" do resource.code(cmdlet_exit_code_success_content) resource.flags(valid_powershell_interpreter_flag) resource.returns(0) resource.run_action(:run) end it "raises an error when given a block and a guard_interpreter" do resource.guard_interpreter :sh resource.only_if { true } expect { resource.should_skip?(:run) }.to raise_error(ArgumentError, /guard_interpreter does not support blocks/) end context "when dsc is supported", :windows_powershell_dsc_only do it "can execute LCM configuration code" do resource.code <<-EOF configuration LCM { param ($thumbprint) localconfigurationmanager { RebootNodeIfNeeded = $false ConfigurationMode = 'ApplyOnly' } } EOF expect { resource.run_action(:run) }.not_to raise_error end end end context "when running on a 32-bit version of Ruby", :ruby32_only do it "executes a script with a 32-bit process if process architecture :i386 is specified" do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.architecture(:i386) resource.returns(0) resource.run_action(:run) expect(source_contains_case_insensitive_content?( get_script_output, "x86" )).to eq(true) end context "when running on a 64-bit version of Windows", :windows64_only do it "executes a script with a 64-bit process if :x86_64 arch is specified" do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.architecture(:x86_64) resource.returns(0) resource.run_action(:run) expect(source_contains_case_insensitive_content?( get_script_output, "AMD64" )).to eq(true) end end context "when running on a 32-bit version of Windows", :windows32_only do it "raises an exception if :x86_64 process architecture is specified" do begin expect(resource.architecture(:x86_64)).to raise_error Chef::Exceptions::Win32ArchitectureIncorrect rescue Chef::Exceptions::Win32ArchitectureIncorrect end end end end context "when running on a 64-bit version of Ruby", :ruby64_only do it "executes a script with a 64-bit process if :x86_64 arch is specified" do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.architecture(:x86_64) resource.returns(0) resource.run_action(:run) expect(source_contains_case_insensitive_content?( get_script_output, "AMD64" )).to eq(true) end it "executes a script with a 32-bit process if :i386 arch is specified", :not_supported_on_nano do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.architecture(:i386) resource.returns(0) resource.run_action(:run) expect(source_contains_case_insensitive_content?( get_script_output, "x86" )).to eq(true) end it "raises an error when executing a script with a 32-bit process on Windows Nano Server", :windows_nano_only do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") expect { resource.architecture(:i386) }.to raise_error(Chef::Exceptions::Win32ArchitectureIncorrect, "cannot execute script with requested architecture 'i386' on Windows Nano Server") end end describe "when executing guards" do before(:each) do resource.not_if.clear resource.only_if.clear end context "when the guard_interpreter's default value of :powershell_script is overridden to :default" do before(:each) do resource.guard_interpreter :default end it "evaluates a succeeding not_if block using cmd.exe as false by default" do resource.not_if "exit /b 0" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a failing not_if block using cmd.exe as true by default" do resource.not_if "exit /b 2" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates an succeeding only_if block using cmd.exe as true by default" do resource.only_if "exit /b 0" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a failing only_if block using cmd.exe as false by default" do resource.only_if "exit /b 2" expect(resource.should_skip?(:run)).to be_truthy end end context "the only_if is specified before the guard" do before do resource.guard_interpreter :default end it "evaluates a powershell $true for a only_if block as true" do resource.only_if "$true" resource.guard_interpreter :powershell_script expect(resource.should_skip?(:run)).to be_falsey end end context "with powershell_script as the guard_interpreter" do it "has a guard_interpreter attribute set to :powershell_script" do expect(resource.guard_interpreter).to eq(:powershell_script) end it "evaluates a powershell $false for a not_if block as true" do pending "powershell.exe always exits with $true on nano" if Chef::Platform.windows_nano_server? resource.not_if "$false" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a powershell $true for a not_if block as false" do resource.not_if "$true" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a powershell $false for an only_if block as false" do pending "powershell.exe always exits with $true on nano" if Chef::Platform.windows_nano_server? resource.only_if "$false" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a powershell $true for a only_if block as true" do resource.only_if "$true" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a not_if block using powershell.exe" do resource.not_if "exit([int32](![System.Environment]::CommandLine.Contains('powershell.exe')))" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates an only_if block using powershell.exe" do resource.only_if "exit([int32](![System.Environment]::CommandLine.Contains('powershell.exe')))" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a non-zero powershell exit status for not_if as true" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.not_if "exit 37" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a zero powershell exit status for not_if as false" do resource.not_if "exit 0" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a failed executable exit status for not_if as false" do pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? resource.not_if windows_process_exit_code_not_found_content expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a successful executable exit status for not_if as true" do resource.not_if windows_process_exit_code_success_content expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a failed executable exit status for only_if as false" do pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? resource.only_if windows_process_exit_code_not_found_content expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a successful executable exit status for only_if as true" do resource.only_if windows_process_exit_code_success_content expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a failed cmdlet exit status for not_if as true" do pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? resource.not_if "throw 'up'" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a successful cmdlet exit status for not_if as true" do resource.not_if "cd ." expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a failed cmdlet exit status for only_if as false" do pending "powershell.exe always exits with success on nano" if Chef::Platform.windows_nano_server? resource.only_if "throw 'up'" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a successful cmdlet exit status for only_if as true" do resource.only_if "cd ." expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a not_if block using the cwd guard parameter" do custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" resource.not_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')", :cwd => custom_cwd expect(resource.should_skip?(:run)).to be_truthy end it "evaluates an only_if block using the cwd guard parameter" do custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" resource.only_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')", :cwd => custom_cwd expect(resource.should_skip?(:run)).to be_falsey end it "inherits cwd from the parent resource for only_if" do custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" resource.cwd custom_cwd resource.only_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')" expect(resource.should_skip?(:run)).to be_falsey end it "inherits cwd from the parent resource for not_if" do custom_cwd = "#{ENV['SystemRoot']}\\system32\\drivers\\etc" resource.cwd custom_cwd resource.not_if "exit ! [int32]($pwd.path -eq '#{custom_cwd}')" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a 64-bit resource with a 64-bit guard and interprets boolean false as zero status code", :windows64_only do resource.architecture :x86_64 resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -ne 'AMD64')" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a 64-bit resource with a 64-bit guard and interprets boolean true as nonzero status code", :windows64_only do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.architecture :x86_64 resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'AMD64')" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code", :not_supported_on_nano do resource.architecture :i386 resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -ne 'X86')" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code", :not_supported_on_nano do resource.architecture :i386 resource.only_if "exit [int32]($env:PROCESSOR_ARCHITECTURE -eq 'X86')" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for only_if" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.convert_boolean_return true resource.only_if "$false" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a simple boolean false as nonzero status code when convert_boolean_return is true for not_if" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? resource.convert_boolean_return true resource.not_if "$false" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a simple boolean true as 0 status code when convert_boolean_return is true for only_if" do resource.convert_boolean_return true resource.only_if "$true" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a simple boolean true as 0 status code when convert_boolean_return is true for not_if" do resource.convert_boolean_return true resource.not_if "$true" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for only_if", :not_supported_on_nano do resource.convert_boolean_return true resource.architecture :i386 resource.only_if "$env:PROCESSOR_ARCHITECTURE -eq 'X86'" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean false as zero status code using convert_boolean_return for not_if", :not_supported_on_nano do resource.convert_boolean_return true resource.architecture :i386 resource.not_if "$env:PROCESSOR_ARCHITECTURE -ne 'X86'" expect(resource.should_skip?(:run)).to be_falsey end it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for only_if", :not_supported_on_nano do resource.convert_boolean_return true resource.architecture :i386 resource.only_if "$env:PROCESSOR_ARCHITECTURE -ne 'X86'" expect(resource.should_skip?(:run)).to be_truthy end it "evaluates a 32-bit resource with a 32-bit guard and interprets boolean true as nonzero status code using convert_boolean_return for not_if", :not_supported_on_nano do resource.convert_boolean_return true resource.architecture :i386 resource.not_if "$env:PROCESSOR_ARCHITECTURE -eq 'X86'" expect(resource.should_skip?(:run)).to be_truthy end it "raises an error when a 32-bit guard is used on Windows Nano Server", :windows_nano_only do resource.only_if "$true", :architecture => :i386 expect { resource.run_action(:run) }.to raise_error( Chef::Exceptions::Win32ArchitectureIncorrect, /cannot execute script with requested architecture 'i386' on Windows Nano Server/) end end end def get_script_output script_output = File.read(script_output_path) end def source_contains_case_insensitive_content?( source, content ) source.downcase.include?(content.downcase) end end chef-12.14.60/spec/functional/resource/reboot_spec.rb000066400000000000000000000061051276456504500224460ustar00rootroot00000000000000# # Author:: Chris Doherty ) # Copyright:: Copyright 2014-2016, Chef, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Reboot do let(:expected) do { :delay_mins => 5, :requested_by => "reboot resource functional test", :reason => "reboot resource spec test", } end def create_resource node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) resource = Chef::Resource::Reboot.new(expected[:requested_by], run_context) resource.delay_mins(expected[:delay_mins]) resource.reason(expected[:reason]) resource end let(:resource) do create_resource end shared_context "testing run context modification" do def test_reboot_action(resource) reboot_info = resource.run_context.reboot_info expect(reboot_info.keys.sort).to eq([:delay_mins, :reason, :requested_by, :timestamp]) expect(reboot_info[:delay_mins]).to eq(expected[:delay_mins]) expect(reboot_info[:reason]).to eq(expected[:reason]) expect(reboot_info[:requested_by]).to eq(expected[:requested_by]) expect(resource.run_context.reboot_requested?).to be_truthy end end # the currently defined behavior for multiple calls to this resource is "last one wins." describe "the request_reboot_on_successful_run action" do include_context "testing run context modification" before do resource.run_action(:request_reboot) end after do resource.run_context.cancel_reboot end it "should have modified the run context correctly" do test_reboot_action(resource) end end describe "the reboot_interrupt_run action" do include_context "testing run context modification" after do resource.run_context.cancel_reboot end it "should have modified the run context correctly" do # this doesn't actually test the flow of Chef::Client#do_run, unfortunately. expect do resource.run_action(:reboot_now) end.to throw_symbol(:end_client_run_early) test_reboot_action(resource) end end describe "the cancel action" do before do resource.run_context.request_reboot(expected) resource.run_action(:cancel) end it "should have cleared the reboot request" do # arguably we shouldn't be querying RunContext's internal data directly. expect(resource.run_context.reboot_info).to eq({}) expect(resource.run_context.reboot_requested?).to be_falsey end end end chef-12.14.60/spec/functional/resource/registry_spec.rb000066400000000000000000000615731276456504500230360ustar00rootroot00000000000000# # Author:: Prajakta Purohit () # Author:: Lamont Granquist () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/registry" require "chef/resource_reporter" require "spec_helper" describe Chef::Resource::RegistryKey, :unix_only do before(:all) do events = Chef::EventDispatch::Dispatcher.new node = Chef::Node.new ohai = Ohai::System.new ohai.all_plugins node.consume_external_attrs(ohai.data, {}) run_context = Chef::RunContext.new(node, {}, events) @resource = Chef::Resource::RegistryKey.new("HKCU\\Software", run_context) end context "when load_current_resource is run on a non-windows node" do it "throws an exception because you don't have a windows registry (derp)" do @resource.key("HKCU\\Software\\Opscode") @resource.values([{ :name => "Color", :type => :string, :data => "Orange" }]) expect { @resource.run_action(:create) }.to raise_error(Chef::Exceptions::Win32NotWindows) end end end describe Chef::Resource::RegistryKey, :windows_only, :broken => true do # parent and key must be single keys, not paths let(:parent) { "Opscode" } let(:child) { "Whatever" } let(:key_parent) { "SOFTWARE\\" + parent } let(:key_child) { "SOFTWARE\\" + parent + "\\" + child } # must be under HKLM\SOFTWARE for WOW64 redirection to work let(:reg_parent) { "HKLM\\" + key_parent } let(:reg_child) { "HKLM\\" + key_child } let(:hive_class) { ::Win32::Registry::HKEY_LOCAL_MACHINE } let(:resource_name) { "This is the name of my Resource" } def clean_registry if windows64? # clean 64-bit space on WOW64 @registry.architecture = :x86_64 @registry.delete_key(reg_parent, true) @registry.architecture = :machine end # clean 32-bit space on WOW64 @registry.architecture = :i386 @registry.delete_key(reg_parent, true) @registry.architecture = :machine end def reset_registry clean_registry hive_class.create(key_parent, Win32::Registry::KEY_WRITE | 0x0100) hive_class.create(key_parent, Win32::Registry::KEY_WRITE | 0x0200) end def create_deletable_keys # create them both 32-bit and 64-bit [ 0x0100, 0x0200 ].each do |flag| hive_class.create(key_parent + '\Opscode', Win32::Registry::KEY_WRITE | flag) hive_class.open(key_parent + '\Opscode', Win32::Registry::KEY_ALL_ACCESS | flag) do |reg| reg["Color", Win32::Registry::REG_SZ] = "Orange" reg.write("Opscode", Win32::Registry::REG_MULTI_SZ, %w{Seattle Washington}) reg["AKA", Win32::Registry::REG_SZ] = "OC" end hive_class.create(key_parent + '\ReportKey', Win32::Registry::KEY_WRITE | flag) hive_class.open(key_parent + '\ReportKey', Win32::Registry::KEY_ALL_ACCESS | flag) do |reg| reg["ReportVal4", Win32::Registry::REG_SZ] = "report4" reg["ReportVal5", Win32::Registry::REG_SZ] = "report5" end hive_class.create(key_parent + '\OpscodeWhyRun', Win32::Registry::KEY_WRITE | flag) hive_class.open(key_parent + '\OpscodeWhyRun', Win32::Registry::KEY_ALL_ACCESS | flag) do |reg| reg["BriskWalk", Win32::Registry::REG_SZ] = "is good for health" end end end before(:all) do @events = Chef::EventDispatch::Dispatcher.new @node = Chef::Node.new ohai = Ohai::System.new ohai.all_plugins @node.consume_external_attrs(ohai.data, {}) @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::RegistryKey.new(resource_name, @run_context) @registry = Chef::Win32::Registry.new(@run_context) reset_registry end #Reporting setup before do @node.name("windowsbox") @rest_client = double("Chef::ServerAPI (mock)") allow(@rest_client).to receive(:create_url).and_return("reports/nodes/windowsbox/runs/#{@run_id}"); allow(@rest_client).to receive(:raw_http_request).and_return({ "result" => "ok" }); allow(@rest_client).to receive(:post_rest).and_return({ "uri" => "https://example.com/reports/nodes/windowsbox/runs/#{@run_id}" }); @resource_reporter = Chef::ResourceReporter.new(@rest_client) @events.register(@resource_reporter) @run_status = Chef::RunStatus.new(@node, @events) @resource_reporter.run_started(@run_status) @run_id = @resource_reporter.run_id @new_resource.cookbook_name = "monkey" @cookbook_version = double("Cookbook::Version", :version => "1.2.3") allow(@new_resource).to receive(:cookbook_version).and_return(@cookbook_version) end after (:all) do clean_registry end context "when action is create" do before (:all) do reset_registry end it "creates registry key, value if the key is missing" do @new_resource.key(reg_child) @new_resource.values([{ :name => "Color", :type => :string, :data => "Orange" }]) @new_resource.run_action(:create) expect(@registry.key_exists?(reg_child)).to eq(true) expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) end it "does not create the key if it already exists with same value, type and data" do @new_resource.key(reg_child) @new_resource.values([{ :name => "Color", :type => :string, :data => "Orange" }]) @new_resource.run_action(:create) expect(@registry.key_exists?(reg_child)).to eq(true) expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) end it "creates a value if it does not exist" do @new_resource.key(reg_child) @new_resource.values([{ :name => "Mango", :type => :string, :data => "Yellow" }]) @new_resource.run_action(:create) expect(@registry.data_exists?(reg_child, { :name => "Mango", :type => :string, :data => "Yellow" })).to eq(true) end it "modifies the data if the key and value exist and type matches" do @new_resource.key(reg_child) @new_resource.values([{ :name => "Color", :type => :string, :data => "Not just Orange - OpscodeOrange!" }]) @new_resource.run_action(:create) expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :string, :data => "Not just Orange - OpscodeOrange!" })).to eq(true) end it "modifys the type if the key and value exist and the type does not match" do @new_resource.key(reg_child) @new_resource.values([{ :name => "Color", :type => :multi_string, :data => ["Not just Orange - OpscodeOrange!"] }]) @new_resource.run_action(:create) expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :multi_string, :data => ["Not just Orange - OpscodeOrange!"] })).to eq(true) end it "creates subkey if parent exists" do @new_resource.key(reg_child + '\OpscodeTest') @new_resource.values([{ :name => "Chef", :type => :multi_string, :data => %w{OpscodeOrange Rules} }]) @new_resource.recursive(false) @new_resource.run_action(:create) expect(@registry.key_exists?(reg_child + '\OpscodeTest')).to eq(true) expect(@registry.value_exists?(reg_child + '\OpscodeTest', { :name => "Chef", :type => :multi_string, :data => %w{OpscodeOrange Rules} })).to eq(true) end it "gives error if action create and parent does not exist and recursive is set to false" do @new_resource.key(reg_child + '\Missing1\Missing2') @new_resource.values([{ :name => "OC", :type => :string, :data => "MissingData" }]) @new_resource.recursive(false) expect { @new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) end it "creates missing keys if action create and parent does not exist and recursive is set to true" do @new_resource.key(reg_child + '\Missing1\Missing2') @new_resource.values([{ :name => "OC", :type => :string, :data => "MissingData" }]) @new_resource.recursive(true) @new_resource.run_action(:create) expect(@registry.key_exists?(reg_child + '\Missing1\Missing2')).to eq(true) expect(@registry.value_exists?(reg_child + '\Missing1\Missing2', { :name => "OC", :type => :string, :data => "MissingData" })).to eq(true) end it "creates key with multiple value as specified" do @new_resource.key(reg_child) @new_resource.values([{ :name => "one", :type => :string, :data => "1" }, { :name => "two", :type => :string, :data => "2" }, { :name => "three", :type => :string, :data => "3" }]) @new_resource.recursive(true) @new_resource.run_action(:create) @new_resource.values.each do |value| expect(@registry.value_exists?(reg_child, value)).to eq(true) end end context "when running on 64-bit server", :windows64_only do before(:all) do reset_registry end after(:all) do @new_resource.architecture(:machine) @registry.architecture = :machine end it "creates a key in a 32-bit registry that is not viewable in 64-bit" do @new_resource.key(reg_child + '\Atraxi' ) @new_resource.values([{ :name => "OC", :type => :string, :data => "Data" }]) @new_resource.recursive(true) @new_resource.architecture(:i386) @new_resource.run_action(:create) @registry.architecture = :i386 expect(@registry.data_exists?(reg_child + '\Atraxi', { :name => "OC", :type => :string, :data => "Data" })).to eq(true) @registry.architecture = :x86_64 expect(@registry.key_exists?(reg_child + '\Atraxi')).to eq(false) end end it "prepares the reporting data for action :create" do @new_resource.key(reg_child + '\Ood') @new_resource.values([{ :name => "ReportingVal1", :type => :string, :data => "report1" }, { :name => "ReportingVal2", :type => :string, :data => "report2" }]) @new_resource.recursive(true) @new_resource.run_action(:create) @report = @resource_reporter.prepare_run_data expect(@report["action"]).to eq("end") expect(@report["resources"][0]["type"]).to eq("registry_key") expect(@report["resources"][0]["name"]).to eq(resource_name) expect(@report["resources"][0]["id"]).to eq(reg_child + '\Ood') expect(@report["resources"][0]["after"][:values]).to eq([{ :name => "ReportingVal1", :type => :string, :data => "report1" }, { :name => "ReportingVal2", :type => :string, :data => "report2" }]) expect(@report["resources"][0]["before"][:values]).to eq([]) expect(@report["resources"][0]["result"]).to eq("create") expect(@report["status"]).to eq("success") expect(@report["total_res_count"]).to eq("1") end context "while running in whyrun mode" do before (:each) do Chef::Config[:why_run] = true end it "does not throw an exception if the keys do not exist but recursive is set to false" do @new_resource.key(reg_child + '\Slitheen\Raxicoricofallapatorius') @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:create) # should not raise_error expect(@registry.key_exists?(reg_child + '\Slitheen')).to eq(false) expect(@registry.key_exists?(reg_child + '\Slitheen\Raxicoricofallapatorius')).to eq(false) end it "does not create key if the action is create" do @new_resource.key(reg_child + '\Slitheen') @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:create) expect(@registry.key_exists?(reg_child + '\Slitheen')).to eq(false) end end end context "when action is create_if_missing" do before (:all) do reset_registry end it "creates registry key, value if the key is missing" do @new_resource.key(reg_child) @new_resource.values([{ :name => "Color", :type => :string, :data => "Orange" }]) @new_resource.run_action(:create_if_missing) expect(@registry.key_exists?(reg_parent)).to eq(true) expect(@registry.key_exists?(reg_child)).to eq(true) expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) end it "does not create the key if it already exists with same value, type and data" do @new_resource.key(reg_child) @new_resource.values([{ :name => "Color", :type => :string, :data => "Orange" }]) @new_resource.run_action(:create_if_missing) expect(@registry.key_exists?(reg_child)).to eq(true) expect(@registry.data_exists?(reg_child, { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) end it "creates a value if it does not exist" do @new_resource.key(reg_child) @new_resource.values([{ :name => "Mango", :type => :string, :data => "Yellow" }]) @new_resource.run_action(:create_if_missing) expect(@registry.data_exists?(reg_child, { :name => "Mango", :type => :string, :data => "Yellow" })).to eq(true) end it "creates subkey if parent exists" do @new_resource.key(reg_child + '\Pyrovile') @new_resource.values([{ :name => "Chef", :type => :multi_string, :data => %w{OpscodeOrange Rules} }]) @new_resource.recursive(false) @new_resource.run_action(:create_if_missing) expect(@registry.key_exists?(reg_child + '\Pyrovile')).to eq(true) expect(@registry.value_exists?(reg_child + '\Pyrovile', { :name => "Chef", :type => :multi_string, :data => %w{OpscodeOrange Rules} })).to eq(true) end it "gives error if action create and parent does not exist and recursive is set to false" do @new_resource.key(reg_child + '\Sontaran\Sontar') @new_resource.values([{ :name => "OC", :type => :string, :data => "MissingData" }]) @new_resource.recursive(false) expect { @new_resource.run_action(:create_if_missing) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) end it "creates missing keys if action create and parent does not exist and recursive is set to true" do @new_resource.key(reg_child + '\Sontaran\Sontar') @new_resource.values([{ :name => "OC", :type => :string, :data => "MissingData" }]) @new_resource.recursive(true) @new_resource.run_action(:create_if_missing) expect(@registry.key_exists?(reg_child + '\Sontaran\Sontar')).to eq(true) expect(@registry.value_exists?(reg_child + '\Sontaran\Sontar', { :name => "OC", :type => :string, :data => "MissingData" })).to eq(true) end it "creates key with multiple value as specified" do @new_resource.key(reg_child + '\Adipose') @new_resource.values([{ :name => "one", :type => :string, :data => "1" }, { :name => "two", :type => :string, :data => "2" }, { :name => "three", :type => :string, :data => "3" }]) @new_resource.recursive(true) @new_resource.run_action(:create_if_missing) @new_resource.values.each do |value| expect(@registry.value_exists?(reg_child + '\Adipose', value)).to eq(true) end end it "prepares the reporting data for :create_if_missing" do @new_resource.key(reg_child + '\Judoon') @new_resource.values([{ :name => "ReportingVal3", :type => :string, :data => "report3" }]) @new_resource.recursive(true) @new_resource.run_action(:create_if_missing) @report = @resource_reporter.prepare_run_data expect(@report["action"]).to eq("end") expect(@report["resources"][0]["type"]).to eq("registry_key") expect(@report["resources"][0]["name"]).to eq(resource_name) expect(@report["resources"][0]["id"]).to eq(reg_child + '\Judoon') expect(@report["resources"][0]["after"][:values]).to eq([{ :name => "ReportingVal3", :type => :string, :data => "report3" }]) expect(@report["resources"][0]["before"][:values]).to eq([]) expect(@report["resources"][0]["result"]).to eq("create_if_missing") expect(@report["status"]).to eq("success") expect(@report["total_res_count"]).to eq("1") end context "while running in whyrun mode" do before (:each) do Chef::Config[:why_run] = true end it "does not throw an exception if the keys do not exist but recursive is set to false" do @new_resource.key(reg_child + '\Zygons\Zygor') @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:create_if_missing) # should not raise_error expect(@registry.key_exists?(reg_child + '\Zygons')).to eq(false) expect(@registry.key_exists?(reg_child + '\Zygons\Zygor')).to eq(false) end it "does nothing if the action is create_if_missing" do @new_resource.key(reg_child + '\Zygons') @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:create_if_missing) expect(@registry.key_exists?(reg_child + '\Zygons')).to eq(false) end end end context "when the action is delete" do before(:all) do reset_registry create_deletable_keys end it "takes no action if the specified key path does not exist in the system" do expect(@registry.key_exists?(reg_parent + '\Osirian')).to eq(false) @new_resource.key(reg_parent + '\Osirian') @new_resource.recursive(false) @new_resource.run_action(:delete) expect(@registry.key_exists?(reg_parent + '\Osirian')).to eq(false) end it "takes no action if the key exists but the value does not" do expect(@registry.data_exists?(reg_parent + '\Opscode', { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) @new_resource.key(reg_parent + '\Opscode') @new_resource.values([{ :name => "LooksLike", :type => :multi_string, :data => %w{SeattleGrey OCOrange} }]) @new_resource.recursive(false) @new_resource.run_action(:delete) expect(@registry.data_exists?(reg_parent + '\Opscode', { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) end it "deletes only specified values under a key path" do @new_resource.key(reg_parent + '\Opscode') @new_resource.values([{ :name => "Opscode", :type => :multi_string, :data => %w{Seattle Washington} }, { :name => "AKA", :type => :string, :data => "OC" }]) @new_resource.recursive(false) @new_resource.run_action(:delete) expect(@registry.data_exists?(reg_parent + '\Opscode', { :name => "Color", :type => :string, :data => "Orange" })).to eq(true) expect(@registry.value_exists?(reg_parent + '\Opscode', { :name => "AKA", :type => :string, :data => "OC" })).to eq(false) expect(@registry.value_exists?(reg_parent + '\Opscode', { :name => "Opscode", :type => :multi_string, :data => %w{Seattle Washington} })).to eq(false) end it "it deletes the values with the same name irrespective of it type and data" do @new_resource.key(reg_parent + '\Opscode') @new_resource.values([{ :name => "Color", :type => :multi_string, :data => %w{Black Orange} }]) @new_resource.recursive(false) @new_resource.run_action(:delete) expect(@registry.value_exists?(reg_parent + '\Opscode', { :name => "Color", :type => :string, :data => "Orange" })).to eq(false) end it "prepares the reporting data for action :delete" do @new_resource.key(reg_parent + '\ReportKey') @new_resource.values([{ :name => "ReportVal4", :type => :string, :data => "report4" }, { :name => "ReportVal5", :type => :string, :data => "report5" }]) @new_resource.recursive(true) @new_resource.run_action(:delete) @report = @resource_reporter.prepare_run_data expect(@registry.value_exists?(reg_parent + '\ReportKey', [{ :name => "ReportVal4", :type => :string, :data => "report4" }, { :name => "ReportVal5", :type => :string, :data => "report5" }])).to eq(false) expect(@report["action"]).to eq("end") expect(@report["resources"].count).to eq(1) expect(@report["resources"][0]["type"]).to eq("registry_key") expect(@report["resources"][0]["name"]).to eq(resource_name) expect(@report["resources"][0]["id"]).to eq(reg_parent + '\ReportKey') expect(@report["resources"][0]["before"][:values]).to eq([{ :name => "ReportVal4", :type => :string, :data => "report4" }, { :name => "ReportVal5", :type => :string, :data => "report5" }]) #Not testing for after values to match since after -> new_resource values. expect(@report["resources"][0]["result"]).to eq("delete") expect(@report["status"]).to eq("success") expect(@report["total_res_count"]).to eq("1") end context "while running in whyrun mode" do before (:each) do Chef::Config[:why_run] = true end it "does nothing if the action is delete" do @new_resource.key(reg_parent + '\OpscodeWhyRun') @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:delete) expect(@registry.key_exists?(reg_parent + '\OpscodeWhyRun')).to eq(true) end end end context "when the action is delete_key" do before (:all) do reset_registry create_deletable_keys end it "takes no action if the specified key path does not exist in the system" do expect(@registry.key_exists?(reg_parent + '\Osirian')).to eq(false) @new_resource.key(reg_parent + '\Osirian') @new_resource.recursive(false) @new_resource.run_action(:delete_key) expect(@registry.key_exists?(reg_parent + '\Osirian')).to eq(false) end it "deletes key if it has no subkeys and recursive == false" do @new_resource.key(reg_parent + '\OpscodeTest') @new_resource.recursive(false) @new_resource.run_action(:delete_key) expect(@registry.key_exists?(reg_parent + '\OpscodeTest')).to eq(false) end it "raises an exception if the key has subkeys and recursive == false" do @new_resource.key(reg_parent) @new_resource.recursive(false) expect { @new_resource.run_action(:delete_key) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) end it "ignores the values under a key" do @new_resource.key(reg_parent + '\OpscodeIgnoredValues') #@new_resource.values([{:name=>"DontExist", :type=>:string, :data=>"These will be ignored anyways"}]) @new_resource.recursive(true) @new_resource.run_action(:delete_key) end it "deletes the key if it has subkeys and recursive == true" do @new_resource.key(reg_parent + '\Opscode') @new_resource.recursive(true) @new_resource.run_action(:delete_key) expect(@registry.key_exists?(reg_parent + '\Opscode')).to eq(false) end it "prepares the reporting data for action :delete_key" do @new_resource.key(reg_parent + '\ReportKey') @new_resource.recursive(true) @new_resource.run_action(:delete_key) @report = @resource_reporter.prepare_run_data expect(@report["action"]).to eq("end") expect(@report["resources"][0]["type"]).to eq("registry_key") expect(@report["resources"][0]["name"]).to eq(resource_name) expect(@report["resources"][0]["id"]).to eq(reg_parent + '\ReportKey') #Not testing for before or after values to match since #after -> new_resource.values and #before -> current_resource.values expect(@report["resources"][0]["result"]).to eq("delete_key") expect(@report["status"]).to eq("success") expect(@report["total_res_count"]).to eq("1") end context "while running in whyrun mode" do before (:each) do Chef::Config[:why_run] = true end it "does not throw an exception if the key has subkeys but recursive is set to false" do @new_resource.key(reg_parent + '\OpscodeWhyRun') @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:delete_key) end it "does nothing if the action is delete_key" do @new_resource.key(reg_parent + '\OpscodeWhyRun') @new_resource.values([{ :name => "BriskWalk", :type => :string, :data => "is good for health" }]) @new_resource.recursive(false) @new_resource.run_action(:delete_key) expect(@registry.key_exists?(reg_parent + '\OpscodeWhyRun')).to eq(true) end end end end chef-12.14.60/spec/functional/resource/remote_directory_spec.rb000066400000000000000000000153031276456504500245330ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::RemoteDirectory do include_context Chef::Resource::Directory let(:directory_base) { "directory_spec" } let(:default_mode) { (0777 & ~File.umask).to_s(8) } def create_resource cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) Chef::Cookbook::FileVendor.fetch_from_disk(cookbook_repo) node = Chef::Node.new cl = Chef::CookbookLoader.new(cookbook_repo) cl.load_cookbooks cookbook_collection = Chef::CookbookCollection.new(cl) events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, cookbook_collection, events) resource = Chef::Resource::RemoteDirectory.new(path, run_context) resource.source "remotedir" resource.cookbook("openldap") resource end def create_extraneous_files FileUtils.mkdir_p(File.join(path, "remotesubdir")) @existing1 = File.join(path, "marked_for_death.txt") @existing2 = File.join(path, "remotesubdir", "marked_for_death_again.txt") FileUtils.touch(@existing1) FileUtils.touch(@existing2) end let(:resource) do create_resource end let(:resource_second_pass) do create_resource end # See spec/data/cookbooks/openldap/files/default let(:expected_files) do [ File.join(path, "remote_dir_file1.txt"), File.join(path, "remote_dir_file2.txt"), File.join(path, "remotesubdir", "remote_subdir_file1.txt"), File.join(path, "remotesubdir", "remote_subdir_file2.txt"), File.join(path, "remotesubdir", ".a_dotfile"), File.join(path, ".a_dotdir", ".a_dotfile_in_a_dotdir"), ] end it_behaves_like "a directory resource" it_behaves_like "a securable resource with reporting" context "when creating the remote directory with purging disabled" do context "and the directory does not yet exist" do before do resource.run_action(:create) end it "transfers the directory with all contents" do expected_files.each do |file_path| expect(File).to exist(file_path) end end it "is marked as updated by last action" do expect(resource).to be_updated_by_last_action end end context "and there are extraneous files in the directory" do before do create_extraneous_files resource.run_action(:create) end it "does not modify the expected state of the directory" do expected_files.each do |file_path| expect(File).to exist(file_path) end end it "does not remove unmanaged files" do expect(File).to exist(@existing1) expect(File).to exist(@existing2) end end context "and the directory is in the desired state" do before do resource.run_action(:create) resource_second_pass.run_action(:create) end it "does not modify the expected state of the directory" do expected_files.each do |file_path| expect(File).to exist(file_path) end end it "is not marked as updated by last action" do expect(resource_second_pass).not_to be_updated_by_last_action end end describe "with overwrite disabled" do before(:each) do resource.purge(false) resource.overwrite(false) end it "leaves modifications alone" do FileUtils.mkdir_p(File.join(path, "remotesubdir")) modified_file = File.join(path, "remote_dir_file1.txt") modified_subdir_file = File.join(path, "remotesubdir", "remote_subdir_file1.txt") File.open(modified_file, "a") { |f| f.puts "santa is real" } File.open(modified_subdir_file, "a") { |f| f.puts "so is rudolph" } modified_file_checksum = sha256_checksum(modified_file) modified_subdir_file_checksum = sha256_checksum(modified_subdir_file) resource.run_action(:create) expect(sha256_checksum(modified_file)).to eq(modified_file_checksum) expect(sha256_checksum(modified_subdir_file)).to eq(modified_subdir_file_checksum) end end end context "when creating the directory with purging enabled" do before(:each) do resource.purge(true) end context "and there are no extraneous files in the directory" do before do resource.run_action(:create) end it "creates the directory contents as normal" do expected_files.each do |file_path| expect(File).to exist(file_path) end end end context "and there are extraneous files in the directory" do before do create_extraneous_files resource.run_action(:create) end it "removes unmanaged files" do expect(File).not_to exist(@existing1) expect(File).not_to exist(@existing2) end it "does not modify managed files" do expected_files.each do |file_path| expect(File).to exist(file_path) end end it "is marked as updated by last action" do expect(resource).to be_updated_by_last_action end end context "and there are deeply nested extraneous files in the directory" do before do FileUtils.mkdir_p(File.join(path, "a", "multiply", "nested", "directory")) @existing1 = File.join(path, "a", "foo.txt") @existing2 = File.join(path, "a", "multiply", "bar.txt") @existing3 = File.join(path, "a", "multiply", "nested", "baz.txt") @existing4 = File.join(path, "a", "multiply", "nested", "directory", "qux.txt") FileUtils.touch(@existing1) FileUtils.touch(@existing2) FileUtils.touch(@existing3) FileUtils.touch(@existing4) resource.run_action(:create) end it "removes files in subdirectories before files above" do expect(File).not_to exist(@existing1) expect(File).not_to exist(@existing2) expect(File).not_to exist(@existing3) expect(File).not_to exist(@existing4) end it "is marked as updated by last action" do expect(resource).to be_updated_by_last_action end end end end chef-12.14.60/spec/functional/resource/remote_file_spec.rb000066400000000000000000000157011276456504500234500ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "tiny_server" require "support/shared/functional/http" describe Chef::Resource::RemoteFile do include ChefHTTPShared let(:file_cache_path) { Dir.mktmpdir } before(:each) do @old_file_cache = Chef::Config[:file_cache_path] Chef::Config[:file_cache_path] = file_cache_path end after(:each) do Chef::Config[:file_cache_path] = @old_file_cache FileUtils.rm_rf(file_cache_path) end include_context Chef::Resource::File let(:file_base) { "remote_file_spec" } def create_resource node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) resource = Chef::Resource::RemoteFile.new(path, run_context) resource.source(source) resource end let(:resource) do create_resource end let(:default_mode) { (0666 & ~File.umask).to_s(8) } context "when fetching files over HTTP" do before(:each) do start_tiny_server end after(:each) do stop_tiny_server end describe "when redownload isn't necessary" do let(:source) { "http://localhost:9000/seattle_capo.png" } before do @api.get("/seattle_capo.png", 304, "", { "Etag" => "abcdef" } ) end it "does not fetch the file" do resource.run_action(:create) end end context "when using normal encoding" do let(:source) { "http://localhost:9000/nyan_cat.png" } let(:expected_content) { binread(nyan_uncompressed_filename) } it_behaves_like "a file resource" it_behaves_like "a securable resource with reporting" end context "when using gzip encoding" do let(:source) { "http://localhost:9000/nyan_cat.png.gz" } let(:expected_content) { binread(nyan_compressed_filename) } it_behaves_like "a file resource" it_behaves_like "a securable resource with reporting" end end context "when fetching files over HTTPS" do before(:each) do cert_text = File.read(File.expand_path("ssl/chef-rspec.cert", CHEF_SPEC_DATA)) cert = OpenSSL::X509::Certificate.new(cert_text) key_text = File.read(File.expand_path("ssl/chef-rspec.key", CHEF_SPEC_DATA)) key = OpenSSL::PKey::RSA.new(key_text) server_opts = { :SSLEnable => true, :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE, :SSLCertificate => cert, :SSLPrivateKey => key } start_tiny_server(server_opts) end after(:each) do stop_tiny_server end let(:source) { "https://localhost:9000/nyan_cat.png" } let(:expected_content) { binread(nyan_uncompressed_filename) } it_behaves_like "a file resource" end context "when dealing with content length checking" do before(:each) do start_tiny_server end after(:each) do stop_tiny_server end context "when downloading compressed data" do let(:expected_content) { binread(nyan_uncompressed_filename) } let(:source) { "http://localhost:9000/nyan_cat_content_length_compressed.png" } before do expect(File).not_to exist(path) resource.run_action(:create) end it "should create the file" do expect(File).to exist(path) end it "should mark the resource as updated" do expect(resource).to be_updated_by_last_action end it "has the correct content" do expect(binread(path)).to eq(expected_content) end end context "when downloding uncompressed data" do let(:expected_content) { binread(nyan_uncompressed_filename) } let(:source) { "http://localhost:9000/nyan_cat_content_length.png" } before do expect(File).not_to exist(path) resource.run_action(:create) end it "should create the file" do expect(File).to exist(path) end it "should mark the resource as updated" do expect(resource).to be_updated_by_last_action end it "has the correct content" do expect(binread(path)).to eq(expected_content) end end context "when downloading truncated compressed data" do let(:source) { "http://localhost:9000/nyan_cat_truncated_compressed.png" } before do expect(File).not_to exist(path) end it "should raise ContentLengthMismatch" do expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) #File.should_not exist(path) # XXX: CHEF-5081 end end context "when downloding truncated uncompressed data" do let(:source) { "http://localhost:9000/nyan_cat_truncated.png" } before do expect(File).not_to exist(path) end it "should raise ContentLengthMismatch" do expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) #File.should_not exist(path) # XXX: CHEF-5081 end end context "when downloding data with transfer-encoding set" do let(:expected_content) { binread(nyan_uncompressed_filename) } let(:source) { "http://localhost:9000/nyan_cat_transfer_encoding.png" } before do expect(File).not_to exist(path) resource.run_action(:create) end it "should create the file" do expect(File).to exist(path) end it "should mark the resource as updated" do expect(resource).to be_updated_by_last_action end it "has the correct content" do expect(binread(path)).to eq(expected_content) end end describe "when the download of the source raises an exception" do let(:source) { "http://localhost:0000/seattle_capo.png" } before do expect(File).not_to exist(path) end it "should not create the file" do # This can legitimately raise either Errno::EADDRNOTAVAIL or Errno::ECONNREFUSED # in different Ruby versions. old_value = RSpec::Expectations.configuration.on_potential_false_positives RSpec::Expectations.configuration.on_potential_false_positives = :nothing begin expect { resource.run_action(:create) }.to raise_error ensure RSpec::Expectations.configuration.on_potential_false_positives = old_value end expect(File).not_to exist(path) end end end end chef-12.14.60/spec/functional/resource/rpm_spec.rb000066400000000000000000000100161276456504500217460ustar00rootroot00000000000000# # Author:: Prabhu Das () # Copyright:: Copyright 2013-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 "functional/resource/base" require "chef/mixin/shell_out" # run this test only for following platforms. exclude_test = !%w{aix centos redhat suse}.include?(ohai[:platform]) describe Chef::Resource::RpmPackage, :requires_root, :external => exclude_test do include Chef::Mixin::ShellOut let(:new_resource) do new_resource = Chef::Resource::RpmPackage.new(@pkg_name, run_context) new_resource.source @pkg_path new_resource end def rpm_pkg_should_be_installed(resource) case ohai[:platform] # Due to dependency issues , different rpm pkgs are used in different platforms. # dummy rpm package works in aix, without any dependency issues. when "aix" expect(shell_out("rpm -qa | grep dummy").exitstatus).to eq(0) # mytest rpm package works in centos, redhat and in suse without any dependency issues. when "centos", "redhat", "suse" expect(shell_out("rpm -qa | grep mytest").exitstatus).to eq(0) ::File.exists?("/opt/mytest/mytest.sh") # The mytest rpm package contains the mytest.sh file end end def rpm_pkg_should_not_be_installed(resource) case ohai[:platform] when "aix" expect(shell_out("rpm -qa | grep dummy").exitstatus).to eq(1) when "centos", "redhat", "suse" expect(shell_out("rpm -qa | grep mytest").exitstatus).to eq(1) !::File.exists?("/opt/mytest/mytest.sh") end end before(:all) do case ohai[:platform] # Due to dependency issues , different rpm pkgs are used in different platforms. when "aix" @pkg_name = "dummy" @pkg_version = "1-0" @pkg_path = "#{Dir.tmpdir}/dummy-1-0.aix6.1.noarch.rpm" FileUtils.cp(File.join(CHEF_SPEC_ASSETS, "dummy-1-0.aix6.1.noarch.rpm") , @pkg_path) when "centos", "redhat", "suse" @pkg_name = "mytest" @pkg_version = "1.0-1" @pkg_path = "#{Dir.tmpdir}/mytest-1.0-1.noarch.rpm" FileUtils.cp(File.join(CHEF_SPEC_ASSETS, "mytest-1.0-1.noarch.rpm") , @pkg_path) end end after(:all) do FileUtils.rm @pkg_path end context "package install action" do it "should create a package" do new_resource.run_action(:install) rpm_pkg_should_be_installed(new_resource) end after(:each) do shell_out("rpm -qa | grep #{@pkg_name}-#{@pkg_version} | xargs rpm -e") end end context "package remove action" do before(:each) do shell_out("rpm -i #{@pkg_path}") end it "should remove an existing package" do new_resource.run_action(:remove) rpm_pkg_should_not_be_installed(new_resource) end end context "package upgrade action" do before(:each) do shell_out("rpm -i #{@pkg_path}") if ohai[:platform] == "aix" @pkg_version = "2-0" @pkg_path = "#{Dir.tmpdir}/dummy-2-0.aix6.1.noarch.rpm" FileUtils.cp(File.join(CHEF_SPEC_ASSETS, "dummy-2-0.aix6.1.noarch.rpm") , @pkg_path) else @pkg_version = "2.0-1" @pkg_path = "#{Dir.tmpdir}/mytest-2.0-1.noarch.rpm" FileUtils.cp(File.join(CHEF_SPEC_ASSETS, "mytest-2.0-1.noarch.rpm") , @pkg_path) end end it "should upgrade a package" do new_resource.run_action(:install) rpm_pkg_should_be_installed(new_resource) end after(:each) do shell_out("rpm -qa | grep #{@pkg_name}-#{@pkg_version} | xargs rpm -e") FileUtils.rm @pkg_path end end end chef-12.14.60/spec/functional/resource/template_spec.rb000066400000000000000000000134631276456504500227740ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Template do def binread(file) File.open(file, "rb") { |f| f.read } end include_context Chef::Resource::File let(:file_base) { "template_spec" } let(:expected_content) { "slappiness is a warm gun" } let(:node) do node = Chef::Node.new node.normal[:slappiness] = "a warm gun" node end def create_resource cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) Chef::Cookbook::FileVendor.fetch_from_disk(cookbook_repo) cl = Chef::CookbookLoader.new(cookbook_repo) cl.load_cookbooks cookbook_collection = Chef::CookbookCollection.new(cl) events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, cookbook_collection, events) resource = Chef::Resource::Template.new(path, run_context) resource.source("openldap_stuff.conf.erb") resource.cookbook("openldap") # NOTE: partials rely on `cookbook_name` getting set by chef internals and # ignore the user-set `cookbook` attribute. resource.cookbook_name = "openldap" resource end let(:resource) do create_resource end let(:default_mode) { (0666 & ~File.umask).to_s(8) } it_behaves_like "a file resource" it_behaves_like "a securable resource with reporting" context "when the target file does not exist" do it "creates the template with the rendered content using the variable attribute when the :create action is run" do resource.source("openldap_variable_stuff.conf.erb") resource.variables(:secret => "nutella") resource.run_action(:create) expect(IO.read(path)).to eq("super secret is nutella") end it "creates the template with the rendered content using a local erb file when the :create action is run" do resource.source(File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks", "openldap", "templates", "default", "openldap_stuff.conf.erb"))) resource.cookbook(nil) resource.local(true) resource.run_action(:create) expect(IO.read(path)).to eq(expected_content) end end describe "when the template resource defines helper methods" do include_context "diff disabled" let(:resource) do r = create_resource r.source "helper_test.erb" r end let(:expected_content) { "value from helper method" } shared_examples "a template with helpers" do it "generates expected content by calling helper methods" do resource.run_action(:create) expect(binread(path).strip).to eq(expected_content) end end context "using single helper syntax" do before do resource.helper(:helper_method) { "value from helper method" } end it_behaves_like "a template with helpers" end context "using single helper syntax referencing @node" do before do node.normal[:helper_test_attr] = "value from helper method" resource.helper(:helper_method) { "#{@node[:helper_test_attr]}" } end it_behaves_like "a template with helpers" end context "using an inline block to define helpers" do before do resource.helpers do def helper_method "value from helper method" end end end it_behaves_like "a template with helpers" end context "using an inline block referencing @node" do before do node.normal[:helper_test_attr] = "value from helper method" resource.helpers do def helper_method @node[:helper_test_attr] end end end it_behaves_like "a template with helpers" end context "using a module from a library" do module ExampleModule def helper_method "value from helper method" end end before do resource.helpers(ExampleModule) end it_behaves_like "a template with helpers" end context "using a module from a library referencing @node" do module ExampleModuleReferencingATNode def helper_method @node[:helper_test_attr] end end before do node.normal[:helper_test_attr] = "value from helper method" resource.helpers(ExampleModuleReferencingATNode) end it_behaves_like "a template with helpers" end context "using helpers with partial templates" do before do resource.source("helpers_via_partial_test.erb") resource.helper(:helper_method) { "value from helper method" } end it_behaves_like "a template with helpers" end end describe "when template source contains windows style line endings" do include_context "diff disabled" %w{all some no}.each do |test_case| context "for #{test_case} lines" do let(:resource) do r = create_resource r.source "#{test_case}_windows_line_endings.erb" r end it "output should contain platform's line endings" do resource.run_action(:create) binread(path).each_line do |line| expect(line).to end_with(Chef::Platform.windows? ? "\r\n" : "\n") end end end end end end chef-12.14.60/spec/functional/resource/user/000077500000000000000000000000001276456504500205715ustar00rootroot00000000000000chef-12.14.60/spec/functional/resource/user/dscl_spec.rb000066400000000000000000000124461276456504500230640ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mixin/shell_out" metadata = { :mac_osx_only => true, :requires_root => true, :not_supported_on_mac_osx_106 => true, } describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metadata do include Chef::Mixin::ShellOut def clean_user begin shell_out!("/usr/bin/dscl . -delete '/Users/#{username}'") rescue Mixlib::ShellOut::ShellCommandFailed # Raised when the user is already cleaned end end def user_should_exist expect(shell_out("/usr/bin/dscl . -ls /Users").stdout).to include username end def check_password(pass) # In order to test the password we use dscl passwd command since # that's the only command that gets the user password from CLI. expect(shell_out("dscl . -passwd /Users/greatchef #{pass} new_password").exitstatus).to eq(0) # Now reset the password back expect(shell_out("dscl . -passwd /Users/greatchef new_password #{pass}").exitstatus).to eq(0) end let(:node) do n = Chef::Node.new n.consume_external_attrs(OHAI_SYSTEM.data.dup, {}) n end let(:events) do Chef::EventDispatch::Dispatcher.new end let(:run_context) do Chef::RunContext.new(node, {}, events) end let(:username) do "greatchef" end let(:uid) { nil } let(:gid) { 20 } let(:home) { nil } let(:manage_home) { false } let(:password) { "XXXYYYZZZ" } let(:comment) { "Great Chef" } let(:shell) { "/bin/bash" } let(:salt) { nil } let(:iterations) { nil } let(:user_resource) do r = Chef::Resource::User::DsclUser.new("TEST USER RESOURCE", run_context) r.username(username) r.uid(uid) r.gid(gid) r.home(home) r.shell(shell) r.comment(comment) r.manage_home(manage_home) r.password(password) r.salt(salt) r.iterations(iterations) r end before do clean_user end after(:each) do clean_user end describe "action :create" do it "should create the user" do user_resource.run_action(:create) user_should_exist check_password(password) end end describe "when user exists" do before do existing_resource = user_resource.dup existing_resource.run_action(:create) user_should_exist end describe "when password is updated" do it "should update the password of the user" do user_resource.password("mykitchen") user_resource.run_action(:create) check_password("mykitchen") end end end describe "when password is being set via shadow hash" do let(:password) do if node[:platform_version].start_with?("10.7.") # On Mac 10.7 we only need to set the password "c9b3bd1a0cde797eef0eff16c580dab996ba3a21961cccc\ d0f5e65c61558243e50b1a490088bd4824e3b35562d383ca02260398\ ef1979b302212ec1c5383d1d05fc8d843" else "c734b6e4787c3727bb35e29fdd92b97c\ 1de12df509577a045728255ec7c6c5f5\ c18efa05ed02b682ffa7ebc05119900e\ b1d4880833aa7a190afc13e2bf0936b8\ 20123e8c98f0f9bcac2a629d9163caac\ 9464a8c234f3919082400b4f939bb77b\ c5adbbac718b7eb99463a7b679571e0f\ 1c9fef2ef08d0b9e9c2bcf644eed2ffc" end end let(:iterations) { 25000 } let(:salt) { "9e2e7d5ee473b496fd24cf0bbfcaedfcb291ee21740e570d1e917e874f8788ca" } it "action :create should create the user" do user_resource.run_action(:create) user_should_exist check_password("soawesome") end describe "when user exists" do before do existing_resource = user_resource.dup existing_resource.run_action(:create) user_should_exist end describe "when password is updated" do it "should update the password of the user" do user_resource.password("mykitchen") user_resource.run_action(:create) check_password("mykitchen") end end end end describe "when a user is member of some groups" do let(:groups) { %w{staff operator} } before do existing_resource = user_resource.dup existing_resource.run_action(:create) groups.each do |group| shell_out!("/usr/bin/dscl . -append '/Groups/#{group}' GroupMembership #{username}") end end after do groups.each do |group| # Do not raise an error when user is correctly removed shell_out("/usr/bin/dscl . -delete '/Groups/#{group}' GroupMembership #{username}") end end it ":remove action removes the user from the groups and deletes the user" do user_resource.run_action(:remove) groups.each do |group| # Do not raise an error when group is empty expect(shell_out("dscl . read /Groups/staff GroupMembership").stdout).not_to include(group) end end end end chef-12.14.60/spec/functional/resource/user/useradd_spec.rb000066400000000000000000000476641276456504500236000ustar00rootroot00000000000000# encoding: UTF-8 # # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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 "functional/resource/base" require "chef/mixin/shell_out" def resource_for_platform(username, run_context) Chef::Resource.resource_for_node(:user, node).new(username, run_context) end # ideally we could somehow pass an array of [ ...::Aix, ...::Linux ] to the # filter, but we have to pick the right one for the O/S. def user_provider_filter case ohai[:os] when "aix" Chef::Provider::User::Aix when "linux" Chef::Provider::User::Linux end end metadata = { :unix_only => true, :requires_root => true, :not_supported_on_mac_osx => true, :provider => { :user => user_provider_filter }, } describe Chef::Provider::User::Useradd, metadata do include Chef::Mixin::ShellOut # Utility code for /etc/passwd interaction, avoid any caching of user records: PwEntry = Struct.new(:name, :passwd, :uid, :gid, :gecos, :home, :shell) class UserNotFound < StandardError; end def pw_entry passwd_file = File.open("/etc/passwd", "rb") { |f| f.read } matcher = /^#{Regexp.escape(username)}.+$/ if passwd_entry = passwd_file.scan(matcher).first PwEntry.new(*passwd_entry.split(":")) else raise UserNotFound, "no entry matching #{matcher.inspect} found in /etc/passwd" end end def etc_shadow case ohai[:platform] when "aix" File.open("/etc/security/passwd") { |f| f.read } else File.open("/etc/shadow") { |f| f.read } end end def self.quote_in_username_unsupported? if OHAI_SYSTEM["platform_family"] == "debian" false else "Only debian family systems support quotes in username" end end def password_should_be_set if ohai[:platform] == "aix" expect(pw_entry.passwd).to eq("!") else expect(pw_entry.passwd).to eq("x") end end def try_cleanup ["/home/cheftestfoo", "/home/cheftestbar", "/home/cf-test"].each do |f| FileUtils.rm_rf(f) if File.exists? f end ["cf-test"].each do |u| r = resource_for_platform("DELETE USER", run_context) r.manage_home true r.username("cf-test") r.run_action(:remove) end end before do # Silence shell_out live stream Chef::Log.level = :warn try_cleanup end after do max_retries = 3 while max_retries > 0 begin pw_entry # will raise if the user doesn't exist status = shell_out!("userdel", "-r", username, :returns => [0, 8, 12]) # Error code 8 during userdel indicates that the user is logged in. # This occurs randomly because the accounts daemon holds a lock due to which userdel fails. # The work around is to retry userdel for 3 times. break if status.exitstatus != 8 sleep 1 max_retries = max_retries - 1 rescue UserNotFound break end end status.error! if max_retries == 0 end let(:node) do n = Chef::Node.new n.consume_external_attrs(OHAI_SYSTEM.data.dup, {}) n end let(:events) do Chef::EventDispatch::Dispatcher.new end let(:run_context) do Chef::RunContext.new(node, {}, events) end let(:username) { "cf-test" } let(:uid) { nil } let(:home) { nil } let(:manage_home) { false } let(:password) { nil } let(:system) { false } let(:comment) { nil } let(:user_resource) do r = resource_for_platform("TEST USER RESOURCE", run_context) r.username(username) r.uid(uid) r.home(home) r.comment(comment) r.manage_home(manage_home) r.password(password) r.system(system) r end let(:expected_shadow) do if ohai[:platform] == "aix" expected_shadow = "cf-test" # For aix just check user entry in shadow file else expected_shadow = "cf-test:$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" end end describe "action :create" do context "when the user does not exist beforehand" do before do user_resource.run_action(:create) expect(user_resource).to be_updated_by_last_action end it "ensures the user exists" do expect(pw_entry.name).to eq(username) end # On Debian, the only constraints are that usernames must neither start # with a dash ('-') nor plus ('+') nor tilde ('~') nor contain a colon # (':'), a comma (','), or a whitespace (space: ' ', end of line: '\n', # tabulation: '\t', etc.). Note that using a slash ('/') may break the # default algorithm for the definition of the user's home directory. context "and the username contains a single quote", skip: quote_in_username_unsupported? do let(:username) { "t'bilisi" } it "ensures the user exists" do expect(pw_entry.name).to eq(username) end end context "when uid is set" do # Should verify uid not in use... let(:uid) { 1999 } it "ensures the user has the given uid" do expect(pw_entry.uid).to eq("1999") end end context "when comment is set" do let(:comment) { "hello this is dog" } it "ensures the comment is set" do expect(pw_entry.gecos).to eq("hello this is dog") end context "in standard gecos format" do let(:comment) { "Bobo T. Clown,some building,555-555-5555,@boboclown" } it "ensures the comment is set" do expect(pw_entry.gecos).to eq(comment) end end context "to a string containing multibyte characters" do let(:comment) { "(╯°□°)╯︵ â”»â”â”»" } it "ensures the comment is set" do actual = pw_entry.gecos actual.force_encoding(Encoding::UTF_8) if "".respond_to?(:force_encoding) expect(actual).to eq(comment) end end context "to a string containing an apostrophe `'`" do let(:comment) { "don't go" } it "ensures the comment is set" do expect(pw_entry.gecos).to eq(comment) end end end context "when home is set" do let(:home) { "/home/#{username}" } it "ensures the user's home is set to the given path" do expect(pw_entry.home).to eq(home) end it "does not create the home dir without `manage_home'" do expect(File).not_to exist(home) end context "and manage_home is enabled" do let(:manage_home) { true } it "ensures the user's home directory exists" do expect(File).to exist(home) end end context "and manage_home is the default" do let(:manage_home) { nil } it "does not create the home dir without `manage_home'" do expect(File).not_to exist(home) end end end context "when a password is specified" do # openssl passwd -1 "secretpassword" let(:password) do case ohai[:platform] when "aix" "eL5qfEVznSNss" else "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" end end it "sets the user's shadow password" do password_should_be_set expect(etc_shadow).to include(expected_shadow) end end context "when a system user is specified", skip: aix? do let(:system) { true } let(:uid_min) do # from `man useradd`, login user means uid will be between # UID_SYS_MIN and UID_SYS_MAX defined in /etc/login.defs. On my # Ubuntu 13.04 system, these are commented out, so we'll look at # UID_MIN to find the lower limit of the non-system-user range, and # use that value in our assertions. login_defs = File.open("/etc/login.defs", "rb") { |f| f.read } uid_min_scan = /^UID_MIN\s+(\d+)/ login_defs.match(uid_min_scan)[1] end it "ensures the user has the properties of a system user" do expect(pw_entry.uid.to_i).to be < uid_min.to_i end end end # when the user does not exist beforehand context "when the user already exists" do let(:expect_updated?) { true } let(:existing_uid) { nil } let(:existing_home) { nil } let(:existing_manage_home) { false } let(:existing_password) { nil } let(:existing_system) { false } let(:existing_comment) { nil } let(:existing_user) do r = resource_for_platform("TEST USER RESOURCE", run_context) # username is identity attr, must match. r.username(username) r.uid(existing_uid) r.home(existing_home) r.comment(existing_comment) r.manage_home(existing_manage_home) r.password(existing_password) r.system(existing_system) r end before do if reason = skip skip(reason) end existing_user.run_action(:create) expect(existing_user).to be_updated_by_last_action user_resource.run_action(:create) expect(user_resource.updated_by_last_action?).to eq(expect_updated?) end context "and all properties are in the desired state" do let(:uid) { 1999 } let(:home) { "/home/bobo" } let(:manage_home) { true } # openssl passwd -1 "secretpassword" let(:password) do case ohai[:platform] when "aix" "eL5qfEVznSNss" else "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" end end let(:system) { false } let(:comment) { "hello this is dog" } let(:existing_uid) { uid } let(:existing_home) { home } let(:existing_manage_home) { manage_home } let(:existing_password) { password } let(:existing_system) { false } let(:existing_comment) { comment } let(:expect_updated?) { false } it "does not update the user" do expect(user_resource).not_to be_updated end end context "and the uid is updated" do let(:uid) { 1999 } let(:existing_uid) { 1998 } it "ensures the uid is set to the desired value" do expect(pw_entry.uid).to eq("1999") end end context "and the comment is updated" do let(:comment) { "hello this is dog" } let(:existing_comment) { "woof" } it "ensures the comment field is set to the desired value" do expect(pw_entry.gecos).to eq("hello this is dog") end end context "and home directory is updated" do let(:existing_home) { "/home/cheftestfoo" } let(:home) { "/home/cheftestbar" } it "ensures the home directory is set to the desired value" do expect(pw_entry.home).to eq("/home/cheftestbar") end context "and manage_home is enabled" do let(:existing_manage_home) { true } let(:manage_home) { true } it "moves the home directory to the new location" do expect(File).not_to exist("/home/cheftestfoo") expect(File).to exist("/home/cheftestbar") end end context "and manage_home wasn't enabled but is now" do let(:existing_manage_home) { false } let(:manage_home) { true } if %w{rhel fedora}.include?(OHAI_SYSTEM["platform_family"]) # Inconsistent behavior. See: CHEF-2205 it "created the home dir b/c of CHEF-2205 so it still exists" do # This behavior seems contrary to expectation and non-convergent. expect(File).not_to exist("/home/cheftestfoo") expect(File).to exist("/home/cheftestbar") end elsif ohai[:platform] == "aix" it "creates the home dir in the desired location" do expect(File).not_to exist("/home/cheftestfoo") expect(File).to exist("/home/cheftestbar") end else it "does not create the home dir in the desired location (XXX)" do # This behavior seems contrary to expectation and non-convergent. expect(File).not_to exist("/home/cheftestfoo") expect(File).not_to exist("/home/cheftestbar") end end end context "and manage_home was enabled but is not now" do let(:existing_manage_home) { true } let(:manage_home) { false } it "leaves the old home directory around (XXX)" do # Would it be better to remove the old home? expect(File).to exist("/home/cheftestfoo") expect(File).not_to exist("/home/cheftestbar") end end end context "and a password is added" do # openssl passwd -1 "secretpassword" let(:password) do case ohai[:platform] when "aix" "eL5qfEVznSNss" else "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" end end it "ensures the password is set" do password_should_be_set expect(etc_shadow).to include(expected_shadow) end end context "and the password is updated" do # openssl passwd -1 "OLDpassword" let(:existing_password) do case ohai[:platform] when "aix" "jkzG6MvUxjk2g" else "$1$1dVmwm4z$CftsFn8eBDjDRUytYKkXB." end end # openssl passwd -1 "secretpassword" let(:password) do case ohai[:platform] when "aix" "eL5qfEVznSNss" else "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" end end it "ensures the password is set to the desired value" do password_should_be_set expect(etc_shadow).to include(expected_shadow) end end context "and the user is changed from not-system to system" do let(:existing_system) { false } let(:system) { true } let(:expect_updated?) { false } it "does not modify the user at all" do end end context "and the user is changed from system to not-system" do let(:existing_system) { true } let(:system) { false } let(:expect_updated?) { false } it "does not modify the user at all" do end end end # when the user already exists end # action :create shared_context "user exists for lock/unlock" do let(:user_locked_context?) { false } def shadow_entry etc_shadow.lines.find { |l| l.include?(username) } end def shadow_password shadow_entry.split(":")[1] end def aix_user_lock_status lock_info = shell_out!("lsuser -a account_locked #{username}") /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)[1] end def user_account_should_be_locked case ohai[:platform] when "aix" expect(aix_user_lock_status).to eq("true") else expect(shadow_password).to include("!") end end def user_account_should_be_unlocked case ohai[:platform] when "aix" expect(aix_user_lock_status).to eq("false") else expect(shadow_password).not_to include("!") end end def lock_user_account case ohai[:platform] when "aix" shell_out!("chuser account_locked=true #{username}") else shell_out!("usermod -L #{username}") end end before do # create user and setup locked/unlocked state user_resource.dup.run_action(:create) if user_locked_context? lock_user_account user_account_should_be_locked elsif password user_account_should_be_unlocked end end end describe "action :lock" do context "when the user does not exist" do it "raises a sensible error" do expect { user_resource.run_action(:lock) }.to raise_error(Chef::Exceptions::User) end end context "when the user exists" do include_context "user exists for lock/unlock" before do user_resource.run_action(:lock) end context "and the user is not locked" do # user will be locked if it has no password let(:password) do case ohai[:platform] when "aix" "eL5qfEVznSNss" else "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" end end it "locks the user's password" do user_account_should_be_locked end end context "and the user is locked" do # user will be locked if it has no password let(:password) do case ohai[:platform] when "aix" "eL5qfEVznSNss" else "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" end end let(:user_locked_context?) { true } it "does not update the user" do expect(user_resource).not_to be_updated_by_last_action end end end end # action :lock describe "action :unlock" do context "when the user does not exist" do it "raises a sensible error" do expect { user_resource.run_action(:unlock) }.to raise_error(Chef::Exceptions::User) end end context "when the user exists" do include_context "user exists for lock/unlock" before do begin user_resource.run_action(:unlock) @error = nil rescue Exception => e @error = e end end context "and has no password" do # TODO: platform_family should be setup in spec_helper w/ tags if %w{suse opensuse}.include?(OHAI_SYSTEM["platform_family"]) # suse gets this right: it "errors out trying to unlock the user" do expect(@error).to be_a(Mixlib::ShellOut::ShellCommandFailed) expect(@error.message).to include("Cannot unlock the password") end else # borked on all other platforms: it "is marked as updated but doesn't modify the user (XXX)" do # This should be an error instead; note that usermod still exits 0 # (which is probably why this case silently fails): # # DEBUG: ---- Begin output of usermod -U chef-functional-test ---- # DEBUG: STDOUT: # DEBUG: STDERR: usermod: unlocking the user's password would result in a passwordless account. # You should set a password with usermod -p to unlock this user's password. # DEBUG: ---- End output of usermod -U chef-functional-test ---- # DEBUG: Ran usermod -U chef-functional-test returned 0 expect(@error).to be_nil if ohai[:platform] == "aix" expect(pw_entry.passwd).to eq("*") user_account_should_be_unlocked else expect(pw_entry.passwd).to eq("x") expect(shadow_password).to include("!") end end end end context "and has a password" do let(:password) do case ohai[:platform] when "aix" "eL5qfEVznSNss" else "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" end end context "and the user is not locked" do it "does not update the user" do expect(user_resource).not_to be_updated_by_last_action end end context "and the user is locked" do let(:user_locked_context?) { true } it "unlocks the user's password" do user_account_should_be_unlocked end end end end end # action :unlock end chef-12.14.60/spec/functional/resource/user/windows_spec.rb000066400000000000000000000074371276456504500236350ustar00rootroot00000000000000# Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software # 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/mixin/shell_out" describe Chef::Provider::User::Windows, :windows_only do include Chef::Mixin::ShellOut let(:username) { "ChefFunctionalTest" } let(:password) { SecureRandom.uuid } let(:node) do n = Chef::Node.new n.consume_external_attrs(OHAI_SYSTEM.data.dup, {}) n end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) do Chef::Resource::User.new(username, run_context).tap do |r| r.provider(Chef::Provider::User::Windows) r.password(password) end end def delete_user(u) shell_out("net user #{u} /delete") end before do delete_user(username) end describe "action :create" do it "creates a user when a username and password are given" do new_resource.run_action(:create) expect(new_resource).to be_updated_by_last_action expect(shell_out("net user #{username}").exitstatus).to eq(0) end it "reports no changes if there are no changes needed" do new_resource.run_action(:create) new_resource.run_action(:create) expect(new_resource).not_to be_updated_by_last_action end it "allows chaning the password" do new_resource.run_action(:create) new_resource.password(SecureRandom.uuid) new_resource.run_action(:create) expect(new_resource).to be_updated_by_last_action end context "with a gid specified" do it "warns unsupported" do expect(Chef::Log).to receive(:warn).with(/not implemented/) new_resource.gid("agroup") new_resource.run_action(:create) end end end describe "action :remove" do before do new_resource.run_action(:create) end it "deletes the user" do new_resource.run_action(:remove) expect(new_resource).to be_updated_by_last_action expect(shell_out("net user #{username}").exitstatus).to eq(2) end it "is idempotent" do new_resource.run_action(:remove) new_resource.run_action(:remove) expect(new_resource).not_to be_updated_by_last_action end end describe "action :lock" do before do new_resource.run_action(:create) end it "locks the user account" do new_resource.run_action(:lock) expect(new_resource).to be_updated_by_last_action expect(shell_out("net user #{username}").stdout).to match(/Account active\s*No/) end it "is idempotent" do new_resource.run_action(:lock) new_resource.run_action(:lock) expect(new_resource).not_to be_updated_by_last_action end end describe "action :unlock" do before do new_resource.run_action(:create) new_resource.run_action(:lock) end it "unlocks the user account" do new_resource.run_action(:unlock) expect(new_resource).to be_updated_by_last_action expect(shell_out("net user #{username}").stdout).to match(/Account active\s*Yes/) end it "is idempotent" do new_resource.run_action(:unlock) new_resource.run_action(:unlock) expect(new_resource).not_to be_updated_by_last_action end end end chef-12.14.60/spec/functional/resource/windows_package_spec.rb000066400000000000000000000140211276456504500243150ustar00rootroot00000000000000# # Author:: Matt Wrock () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "functional/resource/base" describe Chef::Resource::WindowsPackage, :windows_only, :volatile do let(:pkg_name) { nil } let(:pkg_path) { nil } let(:pkg_checksum) { nil } let(:pkg_version) { nil } let(:pkg_type) { nil } let(:pkg_options) { nil } subject do new_resource = Chef::Resource::WindowsPackage.new(pkg_name, run_context) new_resource.source pkg_path if pkg_path new_resource.version pkg_version new_resource.installer_type pkg_type new_resource.options pkg_options new_resource.checksum pkg_checksum new_resource end describe "install package" do let(:pkg_name) { "Microsoft Visual C++ 2005 Redistributable" } let(:pkg_checksum) { "d6832398e3bc9156a660745f427dc1c2392ce4e9a872e04f41f62d0c6bae07a8" } let(:pkg_path) { "https://download.microsoft.com/download/6/B/B/6BB661D6-A8AE-4819-B79F-236472F6070C/vcredist_x86.exe" } let(:pkg_checksum) { nil } let(:pkg_type) { :custom } let(:pkg_options) { "/Q" } it "updates resource on first install" do subject.run_action(:install) expect(subject).to be_updated_by_last_action end it "does not update resource when already installed" do subject.run_action(:install) expect(subject).not_to be_updated_by_last_action end context "installing additional version" do let(:pkg_path) { "https://download.microsoft.com/download/e/1/c/e1c773de-73ba-494a-a5ba-f24906ecf088/vcredist_x86.exe" } let(:pkg_checksum) { "eb00f891919d4f894ab725b158459db8834470c382dc60cd3c3ee2c6de6da92c" } let(:pkg_version) { "8.0.56336" } it "installs older version" do subject.run_action(:install) expect(subject).to be_updated_by_last_action end end describe "removing package" do subject { Chef::Resource::WindowsPackage.new(pkg_name, run_context) } context "multiple versions and a version given to remove" do before { subject.version("8.0.56336") } it "removes specified version" do subject.run_action(:remove) expect(subject).to be_updated_by_last_action prov = subject.provider_for_action(:remove) prov.load_current_resource expect(prov.current_version_array).to eq([["8.0.59193"]]) end end context "single version installed and no version given to remove" do it "removes last remaining version" do subject.run_action(:remove) expect(subject).to be_updated_by_last_action prov = subject.provider_for_action(:remove) prov.load_current_resource expect(prov.current_version_array).to eq([nil]) end end describe "removing multiple versions at once" do let(:pkg_version) { nil } before do install1 = Chef::Resource::WindowsPackage.new(pkg_name, run_context) install1.source pkg_path install1.version pkg_version install1.installer_type pkg_type install1.options pkg_options install1.run_action(:install) install2 = Chef::Resource::WindowsPackage.new(pkg_name, run_context) install2.source "https://download.microsoft.com/download/e/1/c/e1c773de-73ba-494a-a5ba-f24906ecf088/vcredist_x86.exe" install2.version "8.0.56336" install2.installer_type pkg_type install2.options pkg_options install2.run_action(:install) end it "removes all versions" do subject.run_action(:remove) expect(subject).to be_updated_by_last_action prov = subject.provider_for_action(:remove) prov.load_current_resource expect(prov.current_version_array).to eq([nil]) end end end end describe "package version and installer type" do after { subject.run_action(:remove) } context "null soft" do let(:pkg_name) { "Ultra Defragmenter" } let(:pkg_path) { "http://iweb.dl.sourceforge.net/project/ultradefrag/stable-release/6.1.1/ultradefrag-6.1.1.bin.amd64.exe" } let(:pkg_checksum) { "11d53ed4c426c8c867ad43f142b7904226ffd9938c02e37086913620d79e3c09" } it "finds the correct installer type" do subject.run_action(:install) expect(subject.provider_for_action(:install).installer_type).to eq(:nsis) end end context "inno" do let(:pkg_name) { "Mercurial 3.6.1 (64-bit)" } let(:pkg_path) { "http://mercurial.selenic.com/release/windows/Mercurial-3.6.1-x64.exe" } let(:pkg_checksum) { "febd29578cb6736163d232708b834a2ddd119aa40abc536b2c313fc5e1b5831d" } it "finds the correct installer type" do subject.run_action(:install) expect(subject.provider_for_action(:install).installer_type).to eq(:inno) end end end describe "install from local file" do let(:pkg_name) { "Mercurial 3.6.1 (64-bit)" } let(:pkg_path) { ::File.join(Chef::Config[:file_cache_path], "package", "Mercurial-3.6.1-x64.exe") } let(:pkg_checksum) { "febd29578cb6736163d232708b834a2ddd119aa40abc536b2c313fc5e1b5831d" } it "installs the app" do subject.run_action(:install) expect(subject).to be_updated_by_last_action end end describe "uninstall exe without source" do let(:pkg_name) { "Mercurial 3.6.1 (64-bit)" } it "uninstalls the app" do subject.run_action(:remove) expect(subject).to be_updated_by_last_action end end end chef-12.14.60/spec/functional/resource/windows_service_spec.rb000066400000000000000000000064651276456504500243770ustar00rootroot00000000000000# # Author:: Chris Doherty () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_gem_only, :appveyor_only, :broken => true do # Marking as broken. This test is causing appveyor tests to exit with 116. include_context "using Win32::Service" let(:username) { "service_spec_user" } let(:qualified_username) { "#{ENV['COMPUTERNAME']}\\#{username}" } let(:password) { "1a2b3c4X!&narf" } let(:user_resource) do r = Chef::Resource::User::WindowsUser.new(username, run_context) r.username(username) r.password(password) r.comment("temp spec user") r end let(:global_service_file_path) do "#{ENV['WINDIR']}\\temp\\#{File.basename(test_service[:service_file_path])}" end let(:service_params) do id = "#{$$}_#{rand(1000)}" test_service.merge( { run_as_user: qualified_username, run_as_password: password, service_name: "spec_service_#{id}", service_display_name: "windows_service spec #{id}}", service_description: "Test service for running the windows_service functional spec.", service_file_path: global_service_file_path, } ) end let(:manager) do Chef::Application::WindowsServiceManager.new(service_params) end let(:service_resource) do r = Chef::Resource::WindowsService.new(service_params[:service_name], run_context) [:run_as_user, :run_as_password].each { |prop| r.send(prop, service_params[prop]) } r end before do user_resource.run_action(:create) # the service executable has to be outside the current user's home # directory in order for the logon user to execute it. FileUtils.copy_file(test_service[:service_file_path], global_service_file_path) # if you don't make the file executable by the service user, you'll get # the not-very-helpful "service did not respond fast enough" error. # #mode may break in a post-Windows 8.1 release, and have to be replaced # with the rights stuff in the file resource. file = Chef::Resource::File.new(global_service_file_path, run_context) file.mode("0777") file.run_action(:create) manager.run(%w{--action install}) end after do user_resource.run_action(:remove) manager.run(%w{--action uninstall}) File.delete(global_service_file_path) end describe "logon as a service" do it "successfully runs a service as another user" do service_resource.run_action(:start) end it "grants the user the log on as service right" do service_resource.run_action(:start) expect(Chef::ReservedNames::Win32::Security.get_account_right(qualified_username)).to include("SeServiceLogonRight") end end end chef-12.14.60/spec/functional/rest_spec.rb000066400000000000000000000067551276456504500203150ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "tiny_server" require "support/shared/functional/http" describe Chef::REST do include ChefHTTPShared let(:http_client) { described_class.new(source) } let(:http_client_disable_gzip) { described_class.new(source, Chef::Config[:node_name], Chef::Config[:client_key], { :disable_gzip => true } ) } shared_examples_for "downloads requests correctly" do it "successfully downloads a streaming request" do tempfile = http_client.streaming_request(source, {}) tempfile.close expect(Digest::MD5.hexdigest(binread(tempfile.path))).to eq(Digest::MD5.hexdigest(expected_content)) end it "successfully downloads a GET request" do tempfile = http_client.get(source, {}) tempfile.close expect(Digest::MD5.hexdigest(binread(tempfile.path))).to eq(Digest::MD5.hexdigest(expected_content)) end end shared_examples_for "validates content length and throws an exception" do it "fails validation on a streaming download" do expect { http_client.streaming_request(source, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end it "fails validation on a GET request" do expect { http_client.get(source, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end end shared_examples_for "an endpoint that 403s" do it "fails with a Net::HTTPServerException on a streaming download" do expect { http_client.streaming_request(source, {}) }.to raise_error(Net::HTTPServerException) end it "fails with a Net::HTTPServerException on a GET request" do expect { http_client.get(source, {}) }.to raise_error(Net::HTTPServerException) end end # see CHEF-5100 shared_examples_for "a 403 after a successful request when reusing the request object" do it "fails with a Net::HTTPServerException on a streaming download" do tempfile = http_client.streaming_request(source, {}) tempfile.close expect(Digest::MD5.hexdigest(binread(tempfile.path))).to eq(Digest::MD5.hexdigest(expected_content)) expect { http_client.streaming_request(source2, {}) }.to raise_error(Net::HTTPServerException) end it "fails with a Net::HTTPServerException on a GET request" do tempfile = http_client.get(source, {}) tempfile.close expect(Digest::MD5.hexdigest(binread(tempfile.path))).to eq(Digest::MD5.hexdigest(expected_content)) expect { http_client.get(source2, {}) }.to raise_error(Net::HTTPServerException) end end before do Chef::Config[:node_name] = "webmonkey.example.com" Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem" Chef::Config[:treat_deprecation_warnings_as_errors] = false end before(:each) do start_tiny_server end after(:each) do stop_tiny_server end it_behaves_like "downloading all the things" end chef-12.14.60/spec/functional/run_lock_spec.rb000066400000000000000000000355161276456504500211510ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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 File.expand_path("../../spec_helper", __FILE__) require "chef/client" describe Chef::RunLock do # This behavior works on windows, but the tests use fork :( describe "when locking the chef-client run", :unix_only => true do ## # Lockfile location and helpers let(:random_temp_root) do Kernel.srand(Time.now.to_i + Process.pid) "#{Dir.tmpdir}/#{Kernel.rand(Time.now.to_i + Process.pid)}" end let(:lockfile) { "#{random_temp_root}/this/long/path/does/not/exist/chef-client-running.pid" } # make sure to start with a clean slate. before(:each) { log_event("rm -rf before"); FileUtils.rm_r(random_temp_root) if File.exist?(random_temp_root) } after(:each) { log_event("rm -rf after"); FileUtils.rm_r(random_temp_root) if File.exist?(random_temp_root) } def log_event(message, time = Time.now.strftime("%H:%M:%S.%L")) events << [ message, time ] end def events @events ||= [] end WAIT_ON_LOCK_TIME = 1.0 def wait_on_lock(from_fork) Timeout.timeout(WAIT_ON_LOCK_TIME) do from_fork.readline end rescue Timeout::Error raise "Lockfile never created, abandoning test" end CLIENT_PROCESS_TIMEOUT = 10 BREATHING_ROOM = 1 # ClientProcess is defined below let!(:p1) { ClientProcess.new(self, "p1") } let!(:p2) { ClientProcess.new(self, "p2") } after(:each) do |example| begin p1.stop p2.stop rescue example.exception = $! raise ensure if example.exception print_events end end end def print_events # Consume any remaining events that went on the channel and print them all p1.last_event p2.last_event events.each_with_index.sort_by { |(message, time), index| [ time, index ] }.each do |(message, time), index| print "#{time} #{message}\n" end end context "when the lockfile does not already exist" do context "when a client creates the lockfile but has not yet acquired the lock" do before { p1.run_to("created lock") } shared_context "second client gets the lock" do it "the lockfile is created" do log_event("lockfile exists? #{File.exist?(lockfile)}") expect(File.exist?(lockfile)).to be_truthy end it "the lockfile is not locked" do run_lock = Chef::RunLock.new(lockfile) begin expect(run_lock.test).to be_truthy ensure run_lock.release end end it "the lockfile is empty" do expect(IO.read(lockfile)).to eq("") end context "and a second client gets the lock" do before { p2.run_to("acquired lock") } it "the first client does not get the lock until the second finishes" do p1.run_to("acquired lock") do p2.run_to_completion end end it "and the first client tries to get the lock and the second is killed, the first client gets the lock immediately" do p1.run_to("acquired lock") do sleep BREATHING_ROOM expect(p1.last_event).to match(/after (started|created lock)/) p2.stop end p1.run_to_completion end end end context "and the second client has done nothing" do include_context "second client gets the lock" end context "and the second client has created the lockfile but not yet acquired the lock" do before { p2.run_to("created lock") } include_context "second client gets the lock" end end context "when a client acquires the lock but has not yet saved the pid" do before { p1.run_to("acquired lock") } it "the lockfile is created" do log_event("lockfile exists? #{File.exist?(lockfile)}") expect(File.exist?(lockfile)).to be_truthy end it "the lockfile is locked" do run_lock = Chef::RunLock.new(lockfile) begin expect(run_lock.test).to be_falsey ensure run_lock.release end end it "sets FD_CLOEXEC on the lockfile", :supports_cloexec => true do run_lock = File.open(lockfile) expect(run_lock.fcntl(Fcntl::F_GETFD, 0) & Fcntl::FD_CLOEXEC).to eq(Fcntl::FD_CLOEXEC) end it "the lockfile is empty" do expect(IO.read(lockfile)).to eq("") end it "and a second client tries to acquire the lock, it doesn't get the lock until *after* the first client exits" do # Start p2 and tell it to move forward in the background p2.run_to("acquired lock") do # While p2 is trying to acquire, wait a bit and then let p1 complete sleep(BREATHING_ROOM) expect(p2.last_event).to match(/after (started|created lock)/) p1.run_to_completion end p2.run_to_completion end it "and a second client tries to get the lock and the first is killed, the second client gets the lock immediately" do p2.run_to("acquired lock") do sleep BREATHING_ROOM expect(p2.last_event).to match(/after (started|created lock)/) p1.stop end p2.run_to_completion end end context "when a client acquires the lock and saves the pid" do before { p1.run_to("saved pid") } it "the lockfile is created" do expect(File.exist?(lockfile)).to be_truthy end it "the lockfile is locked" do run_lock = Chef::RunLock.new(lockfile) begin expect(run_lock.test).to be_falsey ensure run_lock.release end end it "sets FD_CLOEXEC on the lockfile", :supports_cloexec => true do run_lock = File.open(lockfile) expect(run_lock.fcntl(Fcntl::F_GETFD, 0) & Fcntl::FD_CLOEXEC).to eq(Fcntl::FD_CLOEXEC) end it "the PID is in the lockfile" do expect(IO.read(lockfile)).to eq p1.pid.to_s end it "and a second client tries to acquire the lock, it doesn't get the lock until *after* the first client exits" do # Start p2 and tell it to move forward in the background p2.run_to("acquired lock") do # While p2 is trying to acquire, wait a bit and then let p1 complete sleep(BREATHING_ROOM) expect(p2.last_event).to match(/after (started|created lock)/) p1.run_to_completion end p2.run_to_completion end it "when a second client tries to get the lock and the first is killed, the second client gets the lock immediately" do p2.run_to("acquired lock") do sleep BREATHING_ROOM expect(p2.last_event).to match(/after (started|created lock)/) p1.stop end p2.run_to_completion end end context "when a client acquires a lock and exits normally" do before { p1.run_to_completion } it "the lockfile remains" do expect(File.exist?(lockfile)).to be_truthy end it "the lockfile is not locked" do run_lock = Chef::RunLock.new(lockfile) begin expect(run_lock.test).to be_truthy ensure run_lock.release end end it "the PID is in the lockfile" do expect(IO.read(lockfile)).to eq p1.pid.to_s end it "and a second client tries to acquire the lock, it gets the lock immediately" do p2.run_to_completion end end end it "test returns true and acquires the lock" do run_lock = Chef::RunLock.new(lockfile) from_tests, to_fork = IO.pipe from_fork, to_tests = IO.pipe p1 = fork do expect(run_lock.test).to eq(true) to_tests.puts "lock acquired" # Wait for the test to tell us we can exit before exiting from_tests.readline exit! 0 end wait_on_lock(from_fork) p2 = fork do expect(run_lock.test).to eq(false) exit! 0 end pid, exit_status = Process.waitpid2(p2) expect(exit_status).to eq(0) to_fork.puts "you can exit now" pid, exit_status = Process.waitpid2(p1) expect(exit_status).to eq(0) end it "test returns without waiting when the lock is acquired" do run_lock = Chef::RunLock.new(lockfile) from_tests, to_fork = IO.pipe from_fork, to_tests = IO.pipe p1 = fork do run_lock.acquire to_tests.puts "lock acquired" # Wait for the test to tell us we can exit before exiting from_tests.readline exit! 0 end wait_on_lock(from_fork) expect(run_lock.test).to eq(false) to_fork.puts "you can exit now" pid, exit_status = Process.waitpid2(p1) expect(exit_status).to eq(0) end end # # Runs a process in the background that will: # # 1. start up (`started` event) # 2. acquire the runlock file (`acquired lock` event) # 3. save the pid to the lockfile (`saved pid` event) # 4. exit # # You control exactly how far the client process goes with the `run_to` # method: it will stop at any given spot so you can test for race conditions. # # It uses a pair of pipes to communicate with the process. The tests will # send an event name over to the process, which gives the process permission # to run until it reaches that event (at which point it waits for another event # name). The process sends the name of each event it reaches back to the tests. # class ClientProcess def initialize(example, name) @example = example @name = name @read_from_process, @write_to_tests = IO.pipe @read_from_tests, @write_to_process = IO.pipe end attr_reader :example attr_reader :name attr_reader :pid def last_event loop do line = readline_nonblock(read_from_process) break if line.nil? event, time = line.split("@") example.log_event("#{name}.last_event got #{event}") example.log_event("[#{name}] #{event}", time.strip) @last_event = event end @last_event end def run_to(to_event, &background_block) example.log_event("#{name}.run_to(#{to_event.inspect})") # Start the process if it's not started start if !pid # Tell the process what to stop at (also means it can go) write_to_process.print "#{to_event}\n" # Run the background block yield if background_block # Wait until it gets there Timeout.timeout(CLIENT_PROCESS_TIMEOUT) do until @last_event == "after #{to_event}" got_event, time = read_from_process.gets.split("@") example.log_event("#{name}.last_event got #{got_event}") example.log_event("[#{name}] #{got_event}", time.strip) @last_event = got_event end end example.log_event("#{name}.run_to(#{to_event.inspect}) finished") end def run_to_completion example.log_event("#{name}.run_to_completion") # Start the process if it's not started start if !pid # Tell the process to stop at nothing (no blocking) @write_to_process.print "nothing\n" # Wait for the process to exit wait_for_exit example.log_event("#{name}.run_to_completion finished") end def wait_for_exit example.log_event("#{name}.wait_for_exit (pid #{pid})") Timeout.timeout(CLIENT_PROCESS_TIMEOUT) do Process.wait(pid) if pid end example.log_event("#{name}.wait_for_exit finished (pid #{pid})") end def stop if pid example.log_event("#{name}.stop (pid #{pid})") begin # Send it the kill signal over and over until it dies Timeout.timeout(CLIENT_PROCESS_TIMEOUT) do Process.kill(:KILL, pid) sleep(0.05) until Process.waitpid2(pid, Process::WNOHANG) end example.log_event("#{name}.stop finished (stopped pid #{pid})") # Process not found is perfectly fine when we're trying to kill a process :) rescue Errno::ESRCH example.log_event("#{name}.stop finished (pid #{pid} wasn't running)") end end end def fire_event(event) # Let the caller know what event we've reached write_to_tests.print("after #{event}@#{Time.now.strftime("%H:%M:%S.%L")}\n") # Block until the client tells us where to stop if !@run_to_event || event == @run_to_event write_to_tests.print("waiting for instructions after #{event}@#{Time.now.strftime("%H:%M:%S.%L")}\n") @run_to_event = read_from_tests.gets.strip write_to_tests.print("told to run to #{@run_to_event} after #{event}@#{Time.now.strftime("%H:%M:%S.%L")}\n") elsif @run_to_event write_to_tests.print("continuing until #{@run_to_event} after #{event}@#{Time.now.strftime("%H:%M:%S.%L")}\n") end end private attr_reader :read_from_process attr_reader :write_to_tests attr_reader :read_from_tests attr_reader :write_to_process class TestRunLock < Chef::RunLock attr_accessor :client_process def create_lock super client_process.fire_event("created lock") end end def start example.log_event("#{name}.start") @pid = fork do begin Timeout.timeout(CLIENT_PROCESS_TIMEOUT) do run_lock = TestRunLock.new(example.lockfile) run_lock.client_process = self fire_event("started") run_lock.acquire fire_event("acquired lock") run_lock.save_pid fire_event("saved pid") exit!(0) end rescue fire_event($!.message.lines.join(" // ")) raise end end example.log_event("#{name}.start forked (pid #{pid})") end def readline_nonblock(fd) buffer = "" buffer << fd.read_nonblock(1) while buffer[-1] != "\n" buffer #rescue IO::EAGAINUnreadable rescue IO::WaitReadable unless buffer == "" sleep 0.1 retry end nil end end end chef-12.14.60/spec/functional/shell_spec.rb000066400000000000000000000113451276456504500204360ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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 "functional/resource/base" require "chef/version" require "chef/shell" require "chef/mixin/command/unix" describe Shell do # chef-shell's unit tests are by necessity very mock-heavy, and frequently do # not catch cases where chef-shell fails to boot because of changes in # chef/client.rb describe "smoke tests", :unix_only => true do include Chef::Mixin::Command::Unix TIMEOUT = 300 def read_until(io, expected_value) start = Time.new buffer = "" until buffer.include?(expected_value) begin buffer << io.read_nonblock(1) rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EIO, EOFError sleep 0.01 end if Time.new - start > TIMEOUT raise "did not read expected value `#{expected_value}' within #{TIMEOUT}s\n" + "Buffer so far: `#{buffer}'" end end buffer end def flush_output(io) start = Time.new loop do begin io.read_nonblock(1) rescue Errno::EWOULDBLOCK, Errno::EAGAIN sleep 0.01 rescue EOFError, Errno::EIO break end if Time.new - start > TIMEOUT raise "timed out after #{TIMEOUT}s waiting for output to end" end end end def wait_or_die(pid) start = Time.new until exitstatus = Process.waitpid2(pid, Process::WNOHANG) if Time.new - start > 5 STDERR.puts("chef-shell tty did not exit cleanly, killing it") Process.kill(:KILL, pid) end sleep 0.01 end exitstatus[1] end def run_chef_shell_with(options) case ohai[:platform] when "aix" config = File.expand_path("shef-config.rb", CHEF_SPEC_DATA) path_to_chef_shell = File.expand_path("../../../bin/chef-shell", __FILE__) output = "" status = popen4("#{path_to_chef_shell} -c #{config} #{options}", :waitlast => true) do |pid, stdin, stdout, stderr| read_until(stdout, "chef (#{Chef::VERSION})>") yield stdout, stdin if block_given? stdin.write("'done'\n") output = read_until(stdout, '=> "done"') stdin.print("exit\n") flush_output(stdout) end [output, status.exitstatus] else # Windows ruby installs don't (always?) have PTY, # so hide the require here begin require "pty" config = File.expand_path("shef-config.rb", CHEF_SPEC_DATA) path_to_chef_shell = File.expand_path("../../../bin/chef-shell", __FILE__) reader, writer, pid = PTY.spawn("#{path_to_chef_shell} -c #{config} #{options}") read_until(reader, "chef (#{Chef::VERSION})>") yield reader, writer if block_given? writer.puts('"done"') output = read_until(reader, '=> "done"') writer.print("exit\n") flush_output(reader) writer.close exitstatus = wait_or_die(pid) [output, exitstatus] rescue PTY::ChildExited => e [output, e.status] end end end it "boots correctly with -lauto" do output, exitstatus = run_chef_shell_with("-lauto") expect(output).to include("done") expect(exitstatus).to eq(0) end it "sets the log_level from the command line" do output, exitstatus = run_chef_shell_with("-lfatal") do |out, keyboard| show_log_level_code = %q[puts "===#{Chef::Log.level}==="] keyboard.puts(show_log_level_code) read_until(out, show_log_level_code) end expect(output).to include("===fatal===") expect(exitstatus).to eq(0) end it "sets the override_runlist from the command line" do output, exitstatus = run_chef_shell_with("-o 'override::foo,override::bar'") do |out, keyboard| show_recipes_code = %q[puts "#{node["recipes"].inspect}"] keyboard.puts(show_recipes_code) read_until(out, show_recipes_code) end expect(output).to include(%q{["override::foo", "override::bar"]}) expect(exitstatus).to eq(0) end end end chef-12.14.60/spec/functional/tiny_server_spec.rb000066400000000000000000000054411276456504500217000ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 "tiny_server" describe TinyServer::API do before do @api = TinyServer::API.instance @api.clear end it "is a Singleton" do expect { TinyServer::API.new }.to raise_error NoMethodError end it "clears the router" do @api.get("/blargh", 200, "blargh") @api.clear expect(@api.routes["GET"]).to be_empty end it "creates a route for a GET request" do @api.get("/foo/bar", 200, "hello foobar") # WEBrick gives you the full URI with host, Thin only gave the part after scheme+host+port response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => "http://localhost:1974/foo/bar") expect(response).to eq([200, { "Content-Type" => "application/json" }, [ "hello foobar" ]]) end it "creates a route for a request with a block" do block_called = false @api.get("/bar/baz", 200) { block_called = true; "hello barbaz" } response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => "http://localhost:1974/bar/baz") expect(response).to eq([200, { "Content-Type" => "application/json" }, [ "hello barbaz" ]]) expect(block_called).to be_truthy end it "returns debugging info for 404s" do response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => "/no_such_thing") expect(response[0]).to eq(404) expect(response[1]).to eq({ "Content-Type" => "application/json" }) expect(response[2]).to be_a_kind_of(Array) response_obj = Chef::JSONCompat.from_json(response[2].first) expect(response_obj["message"]).to eq("no data matches the request for /no_such_thing") expect(response_obj["available_routes"]).to eq({ "GET" => [], "PUT" => [], "POST" => [], "DELETE" => [] }) expect(response_obj["request"]).to eq({ "REQUEST_METHOD" => "GET", "REQUEST_URI" => "/no_such_thing" }) end end describe TinyServer::Manager do it "runs the server" do server = TinyServer::Manager.new server.start begin TinyServer::API.instance.get("/index", 200, "[\"hello\"]") rest = Chef::HTTP.new("http://localhost:9000") expect(rest.get("index")).to eq("[\"hello\"]") ensure server.stop end end end chef-12.14.60/spec/functional/util/000077500000000000000000000000001276456504500167415ustar00rootroot00000000000000chef-12.14.60/spec/functional/util/path_helper_spec.rb000066400000000000000000000023461276456504500226000ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "tmpdir" require "chef/util/path_helper" require "spec_helper" describe Chef::Util::PathHelper, "escape_glob" do PathHelper = Chef::Util::PathHelper it "escapes the glob metacharacters so globbing succeeds" do # make a dir Dir.mktmpdir("\\silly[dir]") do |dir| # add some files files = ["some.rb", "file.txt", "names.csv"] files.each do |file| File.new(File.join(dir, file), "w").close end pattern = File.join(PathHelper.escape_glob_dir(dir), "*") expect(Dir.glob(pattern).map { |x| File.basename(x) }).to match_array(files) end end end chef-12.14.60/spec/functional/util/powershell/000077500000000000000000000000001276456504500211255ustar00rootroot00000000000000chef-12.14.60/spec/functional/util/powershell/cmdlet_spec.rb000066400000000000000000000107051276456504500237370ustar00rootroot00000000000000# # Author:: Adam Edwards () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/json_compat" require File.expand_path("../../../../spec_helper", __FILE__) describe Chef::Util::Powershell::Cmdlet, :windows_powershell_dsc_only do before(:all) do @node = Chef::Node.new @node.consume_external_attrs(OHAI_SYSTEM.data, {}) end let(:cmd_output_format) { :text } let(:simple_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, "get-childitem", cmd_output_format, { :depth => 2 }) } let(:invalid_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, "get-idontexist", cmd_output_format) } let(:cmdlet_get_item_requires_switch_or_argument) { Chef::Util::Powershell::Cmdlet.new(@node, "get-item", cmd_output_format, { :depth => 2 }) } let(:cmdlet_alias_requires_switch_or_argument) { Chef::Util::Powershell::Cmdlet.new(@node, "alias", cmd_output_format, { :depth => 2 }) } let(:etc_directory) { "#{ENV['systemroot']}\\system32\\drivers\\etc" } let(:architecture_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, "$env:PROCESSOR_ARCHITECTURE") } it "executes a simple process" do result = simple_cmdlet.run expect(result.succeeded?).to eq(true) end it "#run does not raise a PowershellCmdletException exception if the command cannot be executed" do expect { invalid_cmdlet.run }.not_to raise_error end it "#run! raises a PowershellCmdletException exception if the command cannot be executed" do expect { invalid_cmdlet.run! }.to raise_error(Chef::Exceptions::PowershellCmdletException) end it "executes a 64-bit command on a 64-bit OS, 32-bit otherwise" do os_arch = ENV["PROCESSOR_ARCHITEW6432"] if os_arch.nil? os_arch = ENV["PROCESSOR_ARCHITECTURE"] end result = architecture_cmdlet.run execution_arch = result.return_value execution_arch.strip! expect(execution_arch).to eq(os_arch) end it "passes command line switches to the command" do result = cmdlet_alias_requires_switch_or_argument.run({ :name => "ls" }) expect(result.succeeded?).to eq(true) end it "passes command line arguments to the command" do result = cmdlet_alias_requires_switch_or_argument.run({}, {}, "ls") expect(result.succeeded?).to eq(true) end it "passes command line arguments and switches to the command" do result = cmdlet_get_item_requires_switch_or_argument.run({ :path => etc_directory }, {}, " | select-object -property fullname | format-table -hidetableheaders") expect(result.succeeded?).to eq(true) returned_directory = result.return_value returned_directory.strip! expect(returned_directory).to eq(etc_directory) end it "passes execution options to the command" do result = cmdlet_get_item_requires_switch_or_argument.run({}, { :cwd => etc_directory }, ". | select-object -property fullname | format-table -hidetableheaders") expect(result.succeeded?).to eq(true) returned_directory = result.return_value returned_directory.strip! expect(returned_directory).to eq(etc_directory) end context "when returning json" do let(:cmd_output_format) { :json } it "returns json format data" do result = cmdlet_alias_requires_switch_or_argument.run({}, {}, "ls") expect(result.succeeded?).to eq(true) expect(lambda { Chef::JSONCompat.parse(result.return_value) }).not_to raise_error end end context "when returning Ruby objects" do let(:cmd_output_format) { :object } it "returns object format data" do result = simple_cmdlet.run({}, { :cwd => etc_directory }, "hosts") expect(result.succeeded?).to eq(true) data = result.return_value expect(data["Name"]).to eq("hosts") end end context "when constructor is given invalid arguments" do let(:cmd_output_format) { :invalid } it "throws an exception if an invalid format is passed to the constructor" do expect(lambda { simple_cmdlet }).to raise_error(ArgumentError) end end end chef-12.14.60/spec/functional/version_spec.rb000066400000000000000000000023431276456504500210120ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-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 File.expand_path("../../spec_helper", __FILE__) require "chef/mixin/shell_out" require "chef/version" require "ohai/version" describe "Chef Versions" do include Chef::Mixin::ShellOut let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..") } binaries = [ "chef-client", "chef-shell", "chef-apply", "knife", "chef-solo" ] binaries.each do |binary| it "#{binary} version should be sane" do expect(shell_out!("ruby #{File.join("bin", binary)} -v", :cwd => chef_dir).stdout.chomp).to include("Chef: #{Chef::VERSION}") end end end chef-12.14.60/spec/functional/win32/000077500000000000000000000000001276456504500167265ustar00rootroot00000000000000chef-12.14.60/spec/functional/win32/crypto_spec.rb000066400000000000000000000033721276456504500216120ustar00rootroot00000000000000# # Author:: Jay Mundrawala() # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" if Chef::Platform.windows? require "chef/win32/crypto" end describe "Chef::ReservedNames::Win32::Crypto", :windows_only do describe "#encrypt" do before(:all) do new_node = Chef::Node.new new_node.consume_external_attrs(OHAI_SYSTEM.data, {}) events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(new_node, {}, events) end let (:plaintext) { "p@assword" } it "can be decrypted by powershell" do encrypted = Chef::ReservedNames::Win32::Crypto.encrypt(plaintext) resource = Chef::Resource::WindowsScript::PowershellScript.new("Powershell resource functional test", @run_context) resource.code <<-EOF $encrypted = '#{encrypted}' | ConvertTo-SecureString $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($encrypted) $plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR) if ($plaintext -ne '#{plaintext}') { Write-Error 'Got: ' $plaintext exit 1 } exit 0 EOF resource.returns(0) resource.run_action(:run) end end end chef-12.14.60/spec/functional/win32/registry_spec.rb000066400000000000000000000755341276456504500221530ustar00rootroot00000000000000# # Author:: Prajakta Purohit () # Author:: Lamont Granquist () # 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/win32/registry" describe "Chef::Win32::Registry", :windows_only do before(:all) do #Create a registry item ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root" ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch" ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\B®anch" ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch\\Flower" ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root', Win32::Registry::KEY_ALL_ACCESS) do |reg| reg["RootType1", Win32::Registry::REG_SZ] = "fibrous" reg.write("Roots", Win32::Registry::REG_MULTI_SZ, ["strong roots", "healthy tree"]) end ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch', Win32::Registry::KEY_ALL_ACCESS) do |reg| reg["Strong", Win32::Registry::REG_SZ] = "bird nest" end ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch\\Flower', Win32::Registry::KEY_ALL_ACCESS) do |reg| reg["Petals", Win32::Registry::REG_MULTI_SZ] = %w{Pink Delicate} end #Create the node with ohai data events = Chef::EventDispatch::Dispatcher.new @node = Chef::Node.new @node.consume_external_attrs(OHAI_SYSTEM.data, {}) @run_context = Chef::RunContext.new(@node, {}, events) #Create a registry object that has access ot the node previously created @registry = Chef::Win32::Registry.new(@run_context) end #Delete what is left of the registry key-values previously created after(:all) do ::Win32::Registry::HKEY_CURRENT_USER.open("Software") do |reg| reg.delete_key("Root", true) end end # Server Versions # it "succeeds if server versiion is 2003R2, 2008, 2008R2, 2012" do # end # it "falis if the server versions are anything else" do # end describe "hive_exists?" do it "returns true if the hive exists" do expect(@registry.hive_exists?("HKCU\\Software\\Root")).to eq(true) end it "returns false if the hive does not exist" do hive = expect(@registry.hive_exists?("LYRU\\Software\\Root")).to eq(false) end end describe "key_exists?" do it "returns true if the key path exists" do expect(@registry.key_exists?("HKCU\\Software\\Root\\Branch\\Flower")).to eq(true) end it "returns false if the key path does not exist" do expect(@registry.key_exists?("HKCU\\Software\\Branch\\Flower")).to eq(false) end it "throws an exception if the hive does not exist" do expect { @registry.key_exists?("JKLM\\Software\\Branch\\Flower") }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end end describe "key_exists!" do it "returns true if the key path exists" do expect(@registry.key_exists!("HKCU\\Software\\Root\\Branch\\Flower")).to eq(true) end it "throws an exception if the key path does not exist" do expect { @registry.key_exists!("HKCU\\Software\\Branch\\Flower") }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "throws an exception if the hive does not exist" do expect { @registry.key_exists!("JKLM\\Software\\Branch\\Flower") }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end end describe "value_exists?" do it "throws an exception if the hive does not exist" do expect { @registry.value_exists?("JKLM\\Software\\Branch\\Flower", { :name => "Petals" }) }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end it "throws an exception if the key does not exist" do expect { @registry.value_exists?("HKCU\\Software\\Branch\\Flower", { :name => "Petals" }) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "returns true if the value exists" do expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals" })).to eq(true) end it "returns true if the value exists with a case mismatch on the value name" do expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "petals" })).to eq(true) end it "returns false if the value does not exist" do expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "FOOBAR" })).to eq(false) end end describe "value_exists!" do it "throws an exception if the hive does not exist" do expect { @registry.value_exists!("JKLM\\Software\\Branch\\Flower", { :name => "Petals" }) }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end it "throws an exception if the key does not exist" do expect { @registry.value_exists!("HKCU\\Software\\Branch\\Flower", { :name => "Petals" }) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "returns true if the value exists" do expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals" })).to eq(true) end it "returns true if the value exists with a case mismatch on the value name" do expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", { :name => "petals" })).to eq(true) end it "throws an exception if the value does not exist" do expect { @registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", { :name => "FOOBAR" }) }.to raise_error(Chef::Exceptions::Win32RegValueMissing) end end describe "data_exists?" do it "throws an exception if the hive does not exist" do expect { @registry.data_exists?("JKLM\\Software\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => %w{Pink Delicate} }) }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end it "throws an exception if the key does not exist" do expect { @registry.data_exists?("HKCU\\Software\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => %w{Pink Delicate} }) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "returns true if all the data matches" do expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => %w{Pink Delicate} })).to eq(true) end it "returns true if all the data matches with a case mismatch on the data name" do expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "petals", :type => :multi_string, :data => %w{Pink Delicate} })).to eq(true) end it "returns false if the name does not exist" do expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "slateP", :type => :multi_string, :data => %w{Pink Delicate} })).to eq(false) end it "returns false if the types do not match" do expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :string, :data => "Pink" })).to eq(false) end it "returns false if the data does not match" do expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => %w{Mauve Delicate} })).to eq(false) end end describe "data_exists!" do it "throws an exception if the hive does not exist" do expect { @registry.data_exists!("JKLM\\Software\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => %w{Pink Delicate} }) }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end it "throws an exception if the key does not exist" do expect { @registry.data_exists!("HKCU\\Software\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => %w{Pink Delicate} }) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "returns true if all the data matches" do expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => %w{Pink Delicate} })).to eq(true) end it "returns true if all the data matches with a case mismatch on the data name" do expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", { :name => "petals", :type => :multi_string, :data => %w{Pink Delicate} })).to eq(true) end it "throws an exception if the name does not exist" do expect { @registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", { :name => "slateP", :type => :multi_string, :data => %w{Pink Delicate} }) }.to raise_error(Chef::Exceptions::Win32RegDataMissing) end it "throws an exception if the types do not match" do expect { @registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :string, :data => "Pink" }) }.to raise_error(Chef::Exceptions::Win32RegDataMissing) end it "throws an exception if the data does not match" do expect { @registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => %w{Mauve Delicate} }) }.to raise_error(Chef::Exceptions::Win32RegDataMissing) end end describe "get_values" do it "returns all values for a key if it exists" do values = @registry.get_values("HKCU\\Software\\Root") expect(values).to be_an_instance_of Array expect(values).to eq([{ :name => "RootType1", :type => :string, :data => "fibrous" }, { :name => "Roots", :type => :multi_string, :data => ["strong roots", "healthy tree"] }]) end it "throws an exception if the key does not exist" do expect { @registry.get_values("HKCU\\Software\\Branch\\Flower") }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "throws an exception if the hive does not exist" do expect { @registry.get_values("JKLM\\Software\\Branch\\Flower") }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end end describe "set_value" do it "updates a value if the key, value exist and type matches and value different" do expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => ["Yellow", "Changed Color"] })).to eq(true) expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => ["Yellow", "Changed Color"] })).to eq(true) end it "updates a value if the type does match and the values are different" do expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :string, :data => "Yellow" })).to eq(true) expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :string, :data => "Yellow" })).to eq(true) expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => ["Yellow", "Changed Color"] })).to eq(false) end it "creates a value if key exists and value does not" do expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Stamen", :type => :multi_string, :data => ["Yellow", "Changed Color"] })).to eq(true) expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Stamen", :type => :multi_string, :data => ["Yellow", "Changed Color"] })).to eq(true) end it "does nothing if data,type and name parameters for the value are same" do expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Stamen", :type => :multi_string, :data => ["Yellow", "Changed Color"] })).to eq(false) expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "Stamen", :type => :multi_string, :data => ["Yellow", "Changed Color"] })).to eq(true) end it "throws an exception if the key does not exist" do expect { @registry.set_value("HKCU\\Software\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => ["Yellow", "Changed Color"] }) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "throws an exception if the hive does not exist" do expect { @registry.set_value("JKLM\\Software\\Root\\Branch\\Flower", { :name => "Petals", :type => :multi_string, :data => ["Yellow", "Changed Color"] }) }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end # we are validating that the data gets .to_i called on it when type is a :dword it "casts an integer string given as a dword into an integer" do expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldBe32767", :type => :dword, :data => "32767" })).to eq(true) expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldBe32767", :type => :dword, :data => 32767 })).to eq(true) end it "casts a nonsense string given as a dword into zero" do expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldBeZero", :type => :dword, :data => "whatdoesthisdo" })).to eq(true) expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldBeZero", :type => :dword, :data => 0 })).to eq(true) end it "throws an exception when trying to cast an array to an int for a dword" do expect { @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldThrow", :type => :dword, :data => %w{one two} }) }.to raise_error NoMethodError end # we are validating that the data gets .to_s called on it when type is a :string it "stores the string representation of an array into a string if you pass it an array" do expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldBePainful", :type => :string, :data => %w{one two} })).to eq(true) expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldBePainful", :type => :string, :data => '["one", "two"]' })).to eq(true) end it "stores the string representation of a number into a string if you pass it an number" do expect(@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldBe65535", :type => :string, :data => 65535 })).to eq(true) expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldBe65535", :type => :string, :data => "65535" })).to eq(true) end # we are validating that the data gets .to_a called on it when type is a :multi_string it "throws an exception when a multi-string is passed a number" do expect { @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldThrow", :type => :multi_string, :data => 65535 }) }.to raise_error NoMethodError end it "throws an exception when a multi-string is passed a string" do expect { @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", { :name => "ShouldBeWat", :type => :multi_string, :data => "foo" }) }.to raise_error NoMethodError end end describe "create_key" do before(:all) do ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root") do |reg| begin reg.delete_key("Trunk", true) rescue end end end it "throws an exception if the path has missing keys but recursive set to false" do expect { @registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", false) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(false) end it "creates the key_path if the keys were missing but recursive was set to true" do expect(@registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", true)).to eq(true) expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(true) end it "does nothing if the key already exists" do expect(@registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", false)).to eq(true) expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(true) end it "throws an exception of the hive does not exist" do expect { @registry.create_key("JKLM\\Software\\Root\\Trunk\\Peck\\Woodpecker", false) }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end end describe "delete_value" do before(:all) do ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Peck\\Woodpecker" ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Trunk\\Peck\\Woodpecker', Win32::Registry::KEY_ALL_ACCESS) do |reg| reg["Peter", Win32::Registry::REG_SZ] = "Tiny" end end it "deletes values if the value exists" do expect(@registry.delete_value("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", { :name => "Peter", :type => :string, :data => "Tiny" })).to eq(true) expect(@registry.value_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", { :name => "Peter", :type => :string, :data => "Tiny" })).to eq(false) end it "does nothing if value does not exist" do expect(@registry.delete_value("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", { :name => "Peter", :type => :string, :data => "Tiny" })).to eq(true) expect(@registry.value_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", { :name => "Peter", :type => :string, :data => "Tiny" })).to eq(false) end it "throws an exception if the key does not exist?" do expect { @registry.delete_value("HKCU\\Software\\Trunk\\Peck\\Woodpecker", { :name => "Peter", :type => :string, :data => "Tiny" }) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "throws an exception if the hive does not exist" do expect { @registry.delete_value("JKLM\\Software\\Root\\Trunk\\Peck\\Woodpecker", { :name => "Peter", :type => :string, :data => "Tiny" }) }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end end describe "delete_key" do before (:all) do ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch\\Fruit" ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch\\Fruit', Win32::Registry::KEY_ALL_ACCESS) do |reg| reg["Apple", Win32::Registry::REG_MULTI_SZ] = %w{Red Juicy} end ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Peck\\Woodpecker" ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Trunk\\Peck\\Woodpecker', Win32::Registry::KEY_ALL_ACCESS) do |reg| reg["Peter", Win32::Registry::REG_SZ] = "Tiny" end end it "deletes a key if it has no subkeys" do expect(@registry.delete_key("HKCU\\Software\\Root\\Branch\\Fruit", false)).to eq(true) expect(@registry.key_exists?("HKCU\\Software\\Root\\Branch\\Fruit")).to eq(false) end it "throws an exception if key to delete has subkeys and recursive is false" do expect { @registry.delete_key("HKCU\\Software\\Root\\Trunk", false) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker")).to eq(true) end it "deletes a key if it has subkeys and recursive true" do expect(@registry.delete_key("HKCU\\Software\\Root\\Trunk", true)).to eq(true) expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk")).to eq(false) end it "does nothing if the key does not exist" do expect(@registry.delete_key("HKCU\\Software\\Root\\Trunk", true)).to eq(true) expect(@registry.key_exists?("HKCU\\Software\\Root\\Trunk")).to eq(false) end it "throws an exception if the hive does not exist" do expect { @registry.delete_key("JKLM\\Software\\Root\\Branch\\Flower", false) }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end end describe "has_subkeys?" do before(:all) do ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk" ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root\\Trunk") do |reg| begin reg.delete_key("Red", true) rescue end end end it "throws an exception if the hive was missing" do expect { @registry.has_subkeys?("LMNO\\Software\\Root") }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end it "throws an exception if the key is missing" do expect { @registry.has_subkeys?("HKCU\\Software\\Root\\Trunk\\Red") }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "returns true if the key has subkeys" do expect(@registry.has_subkeys?("HKCU\\Software\\Root")).to eq(true) end it "returns false if the key has no subkeys" do ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Red" expect(@registry.has_subkeys?("HKCU\\Software\\Root\\Trunk\\Red")).to eq(false) end end describe "get_subkeys" do it "throws an exception if the key is missing" do expect { @registry.get_subkeys("HKCU\\Software\\Trunk\\Red") }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "throws an exception if the hive does not exist" do expect { @registry.get_subkeys("JKLM\\Software\\Root") }.to raise_error(Chef::Exceptions::Win32RegHiveMissing) end it "returns the array of subkeys for a given key" do subkeys = @registry.get_subkeys("HKCU\\Software\\Root") reg_subkeys = [] ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root", Win32::Registry::KEY_ALL_ACCESS) do |reg| reg.each_key { |name| reg_subkeys << name } end expect(reg_subkeys).to eq(subkeys) end end describe "architecture" do describe "on 32-bit" do before(:all) do @saved_kernel_machine = @node.automatic_attrs[:kernel][:machine] @node.automatic_attrs[:kernel][:machine] = :i386 end after(:all) do @node.automatic_attrs[:kernel][:machine] = @saved_kernel_machine end context "registry constructor" do it "throws an exception if requested architecture is 64bit but running on 32bit" do expect { Chef::Win32::Registry.new(@run_context, :x86_64) }.to raise_error(Chef::Exceptions::Win32RegArchitectureIncorrect) end it "can correctly set the requested architecture to 32-bit" do @r = Chef::Win32::Registry.new(@run_context, :i386) expect(@r.architecture).to eq(:i386) expect(@r.registry_system_architecture).to eq(0x0200) end it "can correctly set the requested architecture to :machine" do @r = Chef::Win32::Registry.new(@run_context, :machine) expect(@r.architecture).to eq(:machine) expect(@r.registry_system_architecture).to eq(0x0200) end end context "architecture setter" do it "throws an exception if requested architecture is 64bit but running on 32bit" do expect { @registry.architecture = :x86_64 }.to raise_error(Chef::Exceptions::Win32RegArchitectureIncorrect) end it "sets the requested architecture to :machine if passed :machine" do @registry.architecture = :machine expect(@registry.architecture).to eq(:machine) expect(@registry.registry_system_architecture).to eq(0x0200) end it "sets the requested architecture to 32-bit if passed i386 as a string" do @registry.architecture = :i386 expect(@registry.architecture).to eq(:i386) expect(@registry.registry_system_architecture).to eq(0x0200) end end end describe "on 64-bit" do before(:all) do @saved_kernel_machine = @node.automatic_attrs[:kernel][:machine] @node.automatic_attrs[:kernel][:machine] = :x86_64 end after(:all) do @node.automatic_attrs[:kernel][:machine] = @saved_kernel_machine end context "registry constructor" do it "can correctly set the requested architecture to 32-bit" do @r = Chef::Win32::Registry.new(@run_context, :i386) expect(@r.architecture).to eq(:i386) expect(@r.registry_system_architecture).to eq(0x0200) end it "can correctly set the requested architecture to 64-bit" do @r = Chef::Win32::Registry.new(@run_context, :x86_64) expect(@r.architecture).to eq(:x86_64) expect(@r.registry_system_architecture).to eq(0x0100) end it "can correctly set the requested architecture to :machine" do @r = Chef::Win32::Registry.new(@run_context, :machine) expect(@r.architecture).to eq(:machine) expect(@r.registry_system_architecture).to eq(0x0100) end end context "architecture setter" do it "sets the requested architecture to 64-bit if passed 64-bit" do @registry.architecture = :x86_64 expect(@registry.architecture).to eq(:x86_64) expect(@registry.registry_system_architecture).to eq(0x0100) end it "sets the requested architecture to :machine if passed :machine" do @registry.architecture = :machine expect(@registry.architecture).to eq(:machine) expect(@registry.registry_system_architecture).to eq(0x0100) end it "sets the requested architecture to 32-bit if passed 32-bit" do @registry.architecture = :i386 expect(@registry.architecture).to eq(:i386) expect(@registry.registry_system_architecture).to eq(0x0200) end end end describe "when running on an actual 64-bit server", :windows64_only do before(:all) do begin ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| reg.delete_key("Trunk", true) end rescue end begin ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| reg.delete_key("Trunk", true) end rescue end # 64-bit ::Win32::Registry::HKEY_LOCAL_MACHINE.create("Software\\Root\\Mauve", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) ::Win32::Registry::HKEY_LOCAL_MACHINE.open('Software\\Root\\Mauve', Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| reg["Alert", Win32::Registry::REG_SZ] = "Universal" end # 32-bit ::Win32::Registry::HKEY_LOCAL_MACHINE.create("Software\\Root\\Poosh", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) ::Win32::Registry::HKEY_LOCAL_MACHINE.open('Software\\Root\\Poosh', Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| reg["Status", Win32::Registry::REG_SZ] = "Lost" end end after(:all) do ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg| reg.delete_key("Root", true) end ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg| reg.delete_key("Root", true) end end describe "key_exists?" do it "does not find 64-bit keys in the 32-bit registry" do @registry.architecture = :i386 expect(@registry.key_exists?("HKLM\\Software\\Root\\Mauve")).to eq(false) end it "finds 32-bit keys in the 32-bit registry" do @registry.architecture = :i386 expect(@registry.key_exists?("HKLM\\Software\\Root\\Poosh")).to eq(true) end it "does not find 32-bit keys in the 64-bit registry" do @registry.architecture = :x86_64 expect(@registry.key_exists?("HKLM\\Software\\Root\\Mauve")).to eq(true) end it "finds 64-bit keys in the 64-bit registry" do @registry.architecture = :x86_64 expect(@registry.key_exists?("HKLM\\Software\\Root\\Poosh")).to eq(false) end end describe "value_exists?" do it "does not find 64-bit values in the 32-bit registry" do @registry.architecture = :i386 expect { @registry.value_exists?("HKLM\\Software\\Root\\Mauve", { :name => "Alert" }) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "finds 32-bit values in the 32-bit registry" do @registry.architecture = :i386 expect(@registry.value_exists?("HKLM\\Software\\Root\\Poosh", { :name => "Status" })).to eq(true) end it "does not find 32-bit values in the 64-bit registry" do @registry.architecture = :x86_64 expect(@registry.value_exists?("HKLM\\Software\\Root\\Mauve", { :name => "Alert" })).to eq(true) end it "finds 64-bit values in the 64-bit registry" do @registry.architecture = :x86_64 expect { @registry.value_exists?("HKLM\\Software\\Root\\Poosh", { :name => "Status" }) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end end describe "data_exists?" do it "does not find 64-bit keys in the 32-bit registry" do @registry.architecture = :i386 expect { @registry.data_exists?("HKLM\\Software\\Root\\Mauve", { :name => "Alert", :type => :string, :data => "Universal" }) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "finds 32-bit keys in the 32-bit registry" do @registry.architecture = :i386 expect(@registry.data_exists?("HKLM\\Software\\Root\\Poosh", { :name => "Status", :type => :string, :data => "Lost" })).to eq(true) end it "does not find 32-bit keys in the 64-bit registry" do @registry.architecture = :x86_64 expect(@registry.data_exists?("HKLM\\Software\\Root\\Mauve", { :name => "Alert", :type => :string, :data => "Universal" })).to eq(true) end it "finds 64-bit keys in the 64-bit registry" do @registry.architecture = :x86_64 expect { @registry.data_exists?("HKLM\\Software\\Root\\Poosh", { :name => "Status", :type => :string, :data => "Lost" }) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end end describe "create_key" do it "can create a 32-bit only registry key" do @registry.architecture = :i386 expect(@registry.create_key("HKLM\\Software\\Root\\Trunk\\Red", true)).to eq(true) expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Red")).to eq(true) @registry.architecture = :x86_64 expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Red")).to eq(false) end it "can create a 64-bit only registry key" do @registry.architecture = :x86_64 expect(@registry.create_key("HKLM\\Software\\Root\\Trunk\\Blue", true)).to eq(true) expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Blue")).to eq(true) @registry.architecture = :i386 expect(@registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Blue")).to eq(false) end end end end end chef-12.14.60/spec/functional/win32/security_spec.rb000066400000000000000000000072451276456504500221440ustar00rootroot00000000000000# # Author:: Serdar Sutay () # 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" if Chef::Platform.windows? require "chef/win32/security" end describe "Chef::Win32::Security", :windows_only do it "has_admin_privileges? returns true when running as admin" do expect(Chef::ReservedNames::Win32::Security.has_admin_privileges?).to eq(true) end # We've done some investigation adding a negative test and it turned # out to be a lot of work since mixlib-shellout doesn't have user # support for windows. # # TODO - Add negative tests once mixlib-shellout has user support it "has_admin_privileges? returns false when running as non-admin" do skip "requires user support in mixlib-shellout" end describe "get_file_security" do it "should return a security descriptor when called with a path that exists" do security_descriptor = Chef::ReservedNames::Win32::Security.get_file_security( "C:\\Program Files") # Make sure the security descriptor works expect(security_descriptor.dacl_present?).to be true end end describe "access_check" do let(:security_descriptor) do Chef::ReservedNames::Win32::Security.get_file_security( "C:\\Program Files") end let(:token_rights) { Chef::ReservedNames::Win32::Security::TOKEN_ALL_ACCESS } let(:token) do Chef::ReservedNames::Win32::Security.open_process_token( Chef::ReservedNames::Win32::Process.get_current_process, token_rights).duplicate_token(:SecurityImpersonation) end let(:mapping) do mapping = Chef::ReservedNames::Win32::Security::GENERIC_MAPPING.new mapping[:GenericRead] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_READ mapping[:GenericWrite] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_WRITE mapping[:GenericExecute] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_EXECUTE mapping[:GenericAll] = Chef::ReservedNames::Win32::Security::FILE_ALL_ACCESS mapping end let(:desired_access) { Chef::ReservedNames::Win32::Security::FILE_GENERIC_READ } it "should check if the provided token has the desired access" do expect(Chef::ReservedNames::Win32::Security.access_check(security_descriptor, token, desired_access, mapping)).to be true end end describe "Chef::Win32::Security::Token" do let(:token) do Chef::ReservedNames::Win32::Security.open_process_token( Chef::ReservedNames::Win32::Process.get_current_process, token_rights) end context "with all rights" do let(:token_rights) { Chef::ReservedNames::Win32::Security::TOKEN_ALL_ACCESS } it "can duplicate a token" do expect { token.duplicate_token(:SecurityImpersonation) }.not_to raise_error end end context "with read only rights" do let(:token_rights) { Chef::ReservedNames::Win32::Security::TOKEN_READ } it "raises an exception when trying to duplicate" do expect { token.duplicate_token(:SecurityImpersonation) }.to raise_error(Chef::Exceptions::Win32APIError) end end end end chef-12.14.60/spec/functional/win32/service_manager_spec.rb000066400000000000000000000166651276456504500234350ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-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" if Chef::Platform.windows? require "chef/application/windows_service_manager" end # # ATTENTION: # This test creates a windows service for testing purposes and runs it # as Local System (or an otherwise specified user) on windows boxes. # This test will fail if you run the tests inside a Windows VM by # sharing the code from your host since Local System account by # default can't see the mounted partitions. # Run this test by copying the code to a local VM directory or setup # Local System account to see the maunted partitions for the shared # directories. # describe "Chef::Application::WindowsServiceManager", :windows_only, :system_windows_service_gem_only, :appveyor_only do include_context "using Win32::Service" context "with invalid service definition" do it "throws an error when initialized with no service definition" do expect { Chef::Application::WindowsServiceManager.new(nil) }.to raise_error(ArgumentError) end it "throws an error with required missing options" do [:service_name, :service_display_name, :service_description, :service_file_path].each do |key| service_def = test_service.dup service_def.delete(key) expect { Chef::Application::WindowsServiceManager.new(service_def) }.to raise_error(ArgumentError) end end end context "with valid definition" do before(:each) do @service_manager_output = [ ] # Uncomment below lines to debug this test # original_puts = $stdout.method(:puts) allow($stdout).to receive(:puts) do |message| @service_manager_output << message # original_puts.call(message) end end after(:each) do cleanup end context "when service doesn't exist" do it "default => should say service don't exist" do service_manager.run expect(@service_manager_output.grep(/doesn't exist on the system/).length).to be > 0 end it "install => should install the service" do service_manager.run(["-a", "install"]) expect(test_service_exists?).to be_truthy end it "other actions => should say service doesn't exist" do %w{delete start stop pause resume uninstall}.each do |action| service_manager.run(["-a", action]) expect(@service_manager_output.grep(/doesn't exist on the system/).length).to be > 0 @service_manager_output = [ ] end end end context "when service exists" do before(:each) do service_manager.run(["-a", "install"]) end it "should have an own-process, non-interactive type" do status = ::Win32::Service.status("spec-service") expect(status[:service_type]).to eq("own process") expect(status[:interactive]).to be_falsey end it "install => should say service already exists" do service_manager.run(["-a", "install"]) expect(@service_manager_output.grep(/already exists/).length).to be > 0 end context "and service is stopped" do %w{delete uninstall}.each do |action| it "#{action} => should remove the service", :volatile do service_manager.run(["-a", action]) expect(test_service_exists?).to be_falsey end end it "default, status => should say service is stopped" do service_manager.run([ ]) expect(@service_manager_output.grep(/stopped/).length).to be > 0 @service_manager_output = [ ] service_manager.run(["-a", "status"]) expect(@service_manager_output.grep(/stopped/).length).to be > 0 end it "start should start the service", :volatile do service_manager.run(["-a", "start"]) expect(test_service_state).to eq("running") expect(File.exists?(test_service_file)).to be_truthy end it "stop should not affect the service" do service_manager.run(["-a", "stop"]) expect(test_service_state).to eq("stopped") end %w{pause resume}.each do |action| it "#{action} => should raise error" do expect { service_manager.run(["-a", action]) }.to raise_error(SystemCallError) end end context "and service is started", :volatile do before(:each) do service_manager.run(["-a", "start"]) end %w{delete uninstall}.each do |action| it "#{action} => should remove the service", :volatile do service_manager.run(["-a", action]) expect(test_service_exists?).to be_falsey end end it "default, status => should say service is running" do service_manager.run([ ]) expect(@service_manager_output.grep(/running/).length).to be > 0 @service_manager_output = [ ] service_manager.run(["-a", "status"]) expect(@service_manager_output.grep(/running/).length).to be > 0 end it "stop should stop the service" do service_manager.run(["-a", "stop"]) expect(test_service_state).to eq("stopped") end it "pause should pause the service" do service_manager.run(["-a", "pause"]) expect(test_service_state).to eq("paused") end it "resume should have no affect" do service_manager.run(["-a", "resume"]) expect(test_service_state).to eq("running") end end context "and service is paused", :volatile do before(:each) do service_manager.run(["-a", "start"]) service_manager.run(["-a", "pause"]) end actions = %w{delete uninstall} actions.each do |action| it "#{action} => should remove the service" do service_manager.run(["-a", action]) expect(test_service_exists?).to be_falsey end end it "default, status => should say service is paused" do service_manager.run([ ]) expect(@service_manager_output.grep(/paused/).length).to be > 0 @service_manager_output = [ ] service_manager.run(["-a", "status"]) expect(@service_manager_output.grep(/paused/).length).to be > 0 end it "stop should stop the service" do service_manager.run(["-a", "stop"]) expect(test_service_state).to eq("stopped") end it "pause should not affect the service" do service_manager.run(["-a", "pause"]) expect(test_service_state).to eq("paused") end it "start should raise an error" do expect { service_manager.run(["-a", "start"]) }.to raise_error(::Win32::Service::Error) end end end end end end chef-12.14.60/spec/functional/win32/sid_spec.rb000066400000000000000000000040411276456504500210430ustar00rootroot00000000000000# # Author:: Dan Bjorge () # Copyright:: Copyright 2015-2016, Dan Bjorge # 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" if Chef::Platform.windows? require "chef/win32/security" end describe "Chef::ReservedNames::Win32::SID", :windows_only do if Chef::Platform.windows? SID ||= Chef::ReservedNames::Win32::Security::SID end it "should resolve default_security_object_group as a sane user group", :windows_not_domain_joined_only do # Domain accounts: domain-specific Domain Users SID # Microsoft Accounts: SID.current_user # Else: SID.None expect(SID.default_security_object_group).to eq(SID.None).or eq(SID.current_user) end context "running as an elevated administrator user" do it "should resolve default_security_object_owner as the Administrators group" do expect(SID.default_security_object_owner).to eq(SID.Administrators) end end context "running as a non-elevated administrator user" do it "should resolve default_security_object_owner as the current user" do skip "requires user support in mixlib-shellout, see security_spec.rb" expect(SID.default_security_object_owner).to eq(SID.Administrators) end end context "running as a non-elevated, non-administrator user" do it "should resolve default_security_object_owner as the current user" do skip "requires user support in mixlib-shellout, see security_spec.rb" expect(SID.default_security_object_owner).to eq(SID.current_user) end end end chef-12.14.60/spec/functional/win32/version_info_spec.rb000066400000000000000000000031061276456504500227650ustar00rootroot00000000000000# # Author:: Matt Wrock () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" if Chef::Platform.windows? require "chef/win32/file/version_info" end describe "Chef::ReservedNames::Win32::File::VersionInfo", :windows_only do require "wmi-lite/wmi" let(:file_path) { ENV["ComSpec"] } let(:os_version) do wmi = WmiLite::Wmi.new os_info = wmi.first_of("Win32_OperatingSystem") os_info["version"] end subject { Chef::ReservedNames::Win32::File::VersionInfo.new(file_path) } it "file version has the same version as windows" do expect(subject.FileVersion).to start_with(os_version) end it "product version has the same version as windows" do expect(subject.ProductVersion).to start_with(os_version) end it "company is microsoft" do expect(subject.CompanyName).to eq("Microsoft Corporation") end it "file description is command processor" do expect(subject.FileDescription).to eq("Windows Command Processor") end end chef-12.14.60/spec/functional/win32/versions_spec.rb000066400000000000000000000103121276456504500221320ustar00rootroot00000000000000# # Author:: Chirag Jog () # Copyright:: Copyright 2013-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" if Chef::Platform.windows? require "chef/win32/version" end describe "Chef::ReservedNames::Win32::Version", :windows_only, :not_supported_on_win2k3 do before do wmi = WmiLite::Wmi.new host = wmi.first_of("Win32_OperatingSystem") # Use WMI to determine current OS version. # On Win2k8R2 and later, we can dynamically obtain marketing # names for comparison from WMI so the test should not # need to be modified when new Windows releases arise. # For Win2k3 and Win2k8, we use static names in this test # based on the version number information from WMI. The names # from WMI contain extended characters such as registered # trademark on Win2k8 and Win2k3 that we're not using in our # library, so we have to set the expectation statically. if Chef::Platform.windows_server_2003? @current_os_version = "Windows Server 2003 R2" elsif is_windows_server_2008?(host) @current_os_version = "Windows Server 2008" else # The name from WMI is actually what we want in Win2k8R2+. # So this expectation sould continue to hold without modification # as new versions of Windows are released. @current_os_version = host["caption"] end @version = Chef::ReservedNames::Win32::Version.new end def for_each_windows_version @version.methods.each do |method_name| if Chef::ReservedNames::Win32::Version::WIN_VERSIONS.keys.find { |key| method_name.to_s == Chef::ReservedNames::Win32::Version.send(:method_name_from_marketing_name, key) } yield method_name end end end context "Win32 version object" do it "should have have one method for each marketing version" do versions = 0 for_each_windows_version { versions += 1 } expect(versions).to be > 0 expect(versions).to eq(Chef::ReservedNames::Win32::Version::WIN_VERSIONS.length) end it "should only contain version methods with legal method names" do method_name_pattern = /[a-z]+([a-z]|[0-9]|_)*\?{0,1}/ for_each_windows_version do |method_name| method_match = method_name_pattern.match(method_name.to_s) expect(method_match).not_to be_nil expect(method_name.to_s).to eq(method_match[0]) end end it "should have exactly one method that returns true" do true_versions = 0 for_each_windows_version do |method_name| true_versions += 1 if @version.send(method_name) end expect(true_versions).to eq(1) end it "should successfully execute all version methods" do for_each_windows_version { |method_name| @version.send(method_name.to_sym) } end end context "Windows Operating System version" do it "should match the version from WMI" do expect(@current_os_version).to include(@version.marketing_name) end end def is_windows_server_2008?(wmi_host) is_win2k8 = false os_version = wmi_host["version"] # The operating system version is a string in the following form # that can be split into components based on the '.' delimiter: # MajorVersionNumber.MinorVersionNumber.BuildNumber os_version_components = os_version.split(".") if os_version_components.length < 2 raise "WMI returned a Windows version from Win32_OperatingSystem.Version " + "with an unexpected format. The Windows version could not be determined." end # Windows 6.0 is Windows Server 2008, so test the major and # minor version components is_win2k8 = os_version_components[0] == "6" && os_version_components[1] == "0" end end chef-12.14.60/spec/integration/000077500000000000000000000000001276456504500161455ustar00rootroot00000000000000chef-12.14.60/spec/integration/client/000077500000000000000000000000001276456504500174235ustar00rootroot00000000000000chef-12.14.60/spec/integration/client/client_spec.rb000066400000000000000000000434111276456504500222430ustar00rootroot00000000000000require "support/shared/integration/integration_helper" require "chef/mixin/shell_out" require "tiny_server" require "tmpdir" describe "chef-client" do def recipes_filename File.join(CHEF_SPEC_DATA, "recipes.tgz") end def start_tiny_server(server_opts = {}) @server = TinyServer::Manager.new(server_opts) @server.start @api = TinyServer::API.instance @api.clear # # trivial endpoints # # just a normal file # (expected_content should be uncompressed) @api.get("/recipes.tgz", 200) do File.open(recipes_filename, "rb") do |f| f.read end end end def stop_tiny_server @server.stop @server = @api = nil end 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' --minimal-ohai" } let(:critical_env_vars) { %w{_ORIGINAL_GEM_PATH GEM_PATH GEM_HOME GEM_ROOT BUNDLE_BIN_PATH BUNDLE_GEMFILE RUBYLIB RUBYOPT RUBY_ENGINE RUBY_ROOT RUBY_VERSION PATH}.map { |o| "#{o}=#{ENV[o]}" } .join(" ") } when_the_repository "has a cookbook with a no-op recipe" do before { file "cookbooks/x/recipes/default.rb", "" } it "should complete with success" do file "config/client.rb", < chef_dir) end it "should complete successfully with no other environment variables", :skip => (Chef::Platform.windows?) do file "config/client.rb", < chef_dir) result.error! rescue Chef::Log.info "Bare invocation will have the following load-path." Chef::Log.info shell_out!("env -i #{critical_env_vars} ruby -e 'puts $:'").stdout raise end end it "should complete successfully with --no-listen" do file "config/client.rb", < chef_dir) result.error! end it "should be able to node.save with bad utf8 characters in the node data" do file "cookbooks/x/attributes/default.rb", 'default["badutf8"] = "Elan Ruusam\xE4e"' result = shell_out("#{chef_client} -z -r 'x::default' --disable-config", :cwd => path_to("")) result.error! end context "and no config file" do it "should complete with success when cwd is just above cookbooks and paths are not specified" do result = shell_out("#{chef_client} -z -o 'x::default' --disable-config", :cwd => path_to("")) result.error! end it "should complete with success when cwd is below cookbooks and paths are not specified" do result = shell_out("#{chef_client} -z -o 'x::default' --disable-config", :cwd => path_to("cookbooks/x")) result.error! end it "should fail when cwd is below high above and paths are not specified" do result = shell_out("#{chef_client} -z -o 'x::default' --disable-config", :cwd => File.expand_path("..", path_to(""))) expect(result.exitstatus).to eq(1) end end context "and a config file under .chef/knife.rb" do before { file ".chef/knife.rb", "xxx.xxx" } it "should load .chef/knife.rb when -z is specified" do result = shell_out("#{chef_client} -z -o 'x::default'", :cwd => path_to("")) # FATAL: Configuration error NoMethodError: undefined method `xxx' for nil:NilClass expect(result.stdout).to include("xxx") end end it "should complete with success" do file "config/client.rb", < chef_dir) result.error! end context "and a private key" do before do file "mykey.pem", < chef_dir) result.error! end it "should run recipes specified directly on the command line" do file "config/client.rb", < chef_dir) result.error! expect(IO.read(path_to("tempfile.txt"))).to eq("1") expect(IO.read(path_to("tempfile2.txt"))).to eq("2") end it "should run recipes specified as relative paths directly on the command line" do file "config/client.rb", < path_to("")) result.error! expect(IO.read(path_to("tempfile.txt"))).to eq("1") end it "should run recipes specified directly on the command line AFTER recipes in the run list" do file "config/client.rb", < path_to("")) result.error! expect(IO.read(path_to("tempfile.txt"))).to eq("1") end end it "should complete with success when passed the -z flag" do file "config/client.rb", < chef_dir) result.error! end it "should complete with success when passed the --local-mode flag" do file "config/client.rb", < chef_dir) result.error! end it "should not print SSL warnings when running in local-mode" do file "config/client.rb", < chef_dir) expect(result.stdout).not_to include("SSL validation of HTTPS requests is disabled.") result.error! end it "should complete with success when passed -z and --chef-zero-port" do file "config/client.rb", < chef_dir) result.error! end it "should complete with success when setting the run list with -r" do file "config/client.rb", < chef_dir) expect(result.stdout).not_to include("Overridden Run List") expect(result.stdout).to include("Run List is [recipe[x::default]]") #puts result.stdout result.error! end it "should complete with success when using --profile-ruby and output a profile file" do file "config/client.rb", < chef_dir) expect(File.exist?(path_to("config/local-mode-cache/cache/graph_profile.out"))).to be true end it "doesn't produce a profile when --profile-ruby is not present" do file "config/client.rb", < chef_dir) expect(File.exist?(path_to("config/local-mode-cache/cache/graph_profile.out"))).to be false end end when_the_repository "has a cookbook that should fail chef_version checks" do before do file "cookbooks/x/recipes/default.rb", "" file "cookbooks/x/metadata.rb", < 999.99' EOM file "config/client.rb", < chef_dir) expect(command.exitstatus).to eql(1) expect(command.stdout).to match(/Chef::Exceptions::CookbookChefVersionMismatch/) end end when_the_repository "has a cookbook that uses cheffish resources" do before do file "cookbooks/x/recipes/default.rb", <<-EOM raise "Cheffish was loaded before we used any cheffish things!" if defined?(Cheffish::VERSION) ran_block = false got_server = with_chef_server 'https://blah.com' do ran_block = true run_context.cheffish.current_chef_server end raise "with_chef_server block was not run!" if !ran_block raise "Cheffish was not loaded when we did cheffish things!" if !defined?(Cheffish::VERSION) raise "current_chef_server did not return its value!" if got_server[:chef_server_url] != 'https://blah.com' EOM file "config/client.rb", <<-EOM local_mode true cookbook_path "#{path_to('cookbooks')}" EOM end it "the cheffish DSL is loaded lazily" do command = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' --no-fork", :cwd => chef_dir) expect(command.exitstatus).to eql(0) end end when_the_repository "has a cookbook that uses chef-provisioning resources" do before do file "cookbooks/x/recipes/default.rb", <<-EOM with_driver 'blah' EOM file "config/client.rb", <<-EOM local_mode true cookbook_path "#{path_to('cookbooks')}" EOM end it "the cheffish DSL tries to load but fails (because chef-provisioning is not there)" do # we'd need to have a custom bundle to fix this that omitted chef-provisioning, but that would dig our crazy even deeper, so lets not skip "but if chef-provisioning is in our bundle or in our gemset then this test, very annoyingly, always fails" command = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' --no-fork", :cwd => chef_dir) expect(command.exitstatus).to eql(1) expect(command.stdout).to match(/cannot load such file -- chef\/provisioning/) end end when_the_repository "has a cookbook that generates deprecation warnings" do before do file "cookbooks/x/recipes/default.rb", <<-EOM class ::MyResource < Chef::Resource use_automatic_resource_name property :x, default: [] property :y, default: {} end my_resource 'blah' do 1.upto(10) do x nil end x nil end EOM end def match_indices(regex, str) result = [] pos = 0 while match = regex.match(str, pos) result << match.begin(0) pos = match.end(0) + 1 end result end it "should output each deprecation warning only once, at the end of the run" do file "config/client.rb", < chef_dir) expect(result.error?).to be_falsey # Search to the end of the client run in the output run_complete = result.stdout.index("Running handlers complete") expect(run_complete).to be >= 0 # Make sure there is exactly one result for each, and that it occurs *after* the complete message. expect(match_indices(/An attempt was made to change x from \[\] to nil by calling x\(nil\). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil./, result.stdout)).to match([ be > run_complete ]) end end when_the_repository "has a cookbook with only an audit recipe" do before do file "config/client.rb", < chef_dir) expect(result.error?).to be_falsey expect(result.stdout).to include("Successfully executed all `control_group` blocks and contained examples") end it "should exit with a non-zero code when there is an audit failure" do file "cookbooks/audit_test/recipes/fail.rb", <<-RECIPE control_group "control group without top level control" do it "should fail" do expect(2 - 2).to eq(1) end end RECIPE result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'audit_test::fail'", :cwd => chef_dir) expect(result.error?).to be_truthy expect(result.stdout).to include("Failure/Error: expect(2 - 2).to eq(1)") end end # Fails on appveyor, but works locally on windows and on windows hosts in Ci. context "when using recipe-url", :skip_appveyor do before(:each) do start_tiny_server end after(:each) do stop_tiny_server end let(:tmp_dir) { Dir.mktmpdir("recipe-url") } it "should complete with success when passed -z and --recipe-url" do file "config/client.rb", < tmp_dir) result.error! end it "should fail when passed --recipe-url and not passed -z" do result = shell_out("#{chef_client} --recipe-url=http://localhost:9000/recipes.tgz", :cwd => tmp_dir) expect(result.exitstatus).not_to eq(0) end end end chef-12.14.60/spec/integration/client/exit_code_spec.rb000066400000000000000000000162151276456504500227320ustar00rootroot00000000000000 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", < 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", < 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", <) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/mixin/shell_out" describe "chef-client" do include IntegrationSupport include Chef::Mixin::ShellOut let(:chef_zero_opts) { { :host => "::1" } } let(:validation_pem) do <<-END_VALIDATION_PEM -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEApubutqtYYQ5UiA9QhWP7UvSmsfHsAoPKEVVPdVW/e8Svwpyf 0Xef6OFWVmBE+W442ZjLOe2y6p2nSnaq4y7dg99NFz6X+16mcKiCbj0RCiGqCvCk NftHhTgO9/RFvCbmKZ1RKNob1YzLrFpxBHaSh9po+DGWhApcd+I+op+ZzvDgXhNn 0nauZu3rZmApI/r7EEAOjFedAXs7VPNXhhtZAiLSAVIrwU3ZajtSzgXOxbNzgj5O AAAMmThK+71qPdffAdO4J198H6/MY04qgtFo7vumzCq0UCaGZfmeI1UNE4+xQWwP HJ3pDAP61C6Ebx2snI2kAd9QMx9Y78nIedRHPwIDAQABAoIBAHssRtPM1GacWsom 8zfeN6ZbI4KDlbetZz0vhnqDk9NVrpijWlcOP5dwZXVNitnB/HaqCqFvyPDY9JNB zI/pEFW4QH59FVDP42mVEt0keCTP/1wfiDDGh1vLqVBYl/ZphscDcNgDTzNkuxMx k+LFVxKnn3w7rGc59lALSkpeGvbbIDjp3LUMlUeCF8CIFyYZh9ZvXe4OCxYdyjxb i8tnMLKvJ4Psbh5jMapsu3rHQkfPdqzztQUz8vs0NYwP5vWge46FUyk+WNm/IhbJ G3YM22nwUS8Eu2bmTtADSJolATbCSkOwQ1D+Fybz/4obfYeGaCdOqB05ttubhenV ShsAb7ECgYEA20ecRVxw2S7qA7sqJ4NuYOg9TpfGooptYNA1IP971eB6SaGAelEL awYkGNuu2URmm5ElZpwJFFTDLGA7t2zB2xI1FeySPPIVPvJGSiZoFQOVlIg9WQzK 7jTtFQ/tOMrF+bigEUJh5bP1/7HzqSpuOsPjEUb2aoCTp+tpiRGL7TUCgYEAwtns g3ysrSEcTzpSv7fQRJRk1lkBhatgNd0oc+ikzf74DaVLhBg1jvSThDhiDCdB59mr Jh41cnR1XqE8jmdQbCDRiFrI1Pq6TPaDZFcovDVE1gue9x86v3FOH2ukPG4d2/Xy HevXjThtpMMsWFi0JYXuzXuV5HOvLZiP8sN3lSMCgYANpdxdGM7RRbE9ADY0dWK2 V14ReTLcxP7fyrWz0xLzEeCqmomzkz3BsIUoouu0DCTSw+rvAwExqcDoDylIVlWO fAifz7SeZHbcDxo+3TsXK7zwnLYsx7YNs2+aIv6hzUUbMNmNmXMcZ+IEwx+mRMTN lYmZdrA5mr0V83oDFPt/jQKBgC74RVE03pMlZiObFZNtheDiPKSG9Bz6wMh7NWMr c37MtZLkg52mEFMTlfPLe6ceV37CM8WOhqe+dwSGrYhOU06dYqUR7VOZ1Qr0aZvo fsNPu/Y0+u7rMkgv0fs1AXQnvz7kvKaF0YITVirfeXMafuKEtJoH7owRbur42cpV YCAtAoGAP1rHOc+w0RUcBK3sY7aErrih0OPh9U5bvJsrw1C0FIZhCEoDVA+fNIQL syHLXYFNy0OxMtH/bBAXBGNHd9gf5uOnqh0pYcbe/uRAxumC7Rl0cL509eURiA2T +vFmf54y9YdnLXaqv+FhJT6B6V7WX7IpU9BMqJY1cJYXHuHG2KA= -----END RSA PRIVATE KEY----- END_VALIDATION_PEM end let(:cache_path) do Dir.mktmpdir end let(:basic_config_file) do <<-END_CLIENT_RB chef_server_url "http://[::1]:8900" validation_key '#{path_to('config/validator.pem')}' cache_path '#{cache_path}' client_key '#{cache_path}/client.pem' END_CLIENT_RB end let(:client_rb_content) do basic_config_file end let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") } let(:chef_client_cmd) { %Q{ruby '#{chef_dir}/chef-client' --minimal-ohai -c "#{path_to('config/client.rb')}" -lwarn} } after do FileUtils.rm_rf(cache_path) end # Some Solaris test platforms are too old for IPv6. These tests should not # otherwise be platform dependent, so exclude solaris when_the_chef_server "is running on IPv6", :not_supported_on_solaris, :not_supported_on_gce do when_the_repository "has a cookbook with a no-op recipe" do before do cookbook "noop", "1.0.0", {}, "recipes" => { "default.rb" => "#raise 'foo'" } file "config/client.rb", client_rb_content file "config/validator.pem", validation_pem end it "should complete with success" do result = shell_out("#{chef_client_cmd} -o 'noop::default'", :cwd => chef_dir) result.error! end end when_the_repository "has a cookbook that hits server APIs" do before do recipe = <<-END_RECIPE actual_item = data_bag_item("expect_bag", "expect_item") if actual_item.key?("expect_key") and actual_item["expect_key"] == "expect_value" Chef::Log.info "lookin good" else Chef::Log.error("!" * 80) raise "unexpected data bag item content \#{actual_item.inspect}" Chef::Log.error("!" * 80) end END_RECIPE data_bag("expect_bag", { "expect_item" => { "expect_key" => "expect_value" } }) cookbook "api-smoke-test", "1.0.0", {}, "recipes" => { "default.rb" => recipe } end before do file "config/client.rb", client_rb_content file "config/validator.pem", validation_pem end it "should complete with success" do result = shell_out("#{chef_client_cmd} -o 'api-smoke-test::default'", :cwd => chef_dir) result.error! end end end end chef-12.14.60/spec/integration/knife/000077500000000000000000000000001276456504500172415ustar00rootroot00000000000000chef-12.14.60/spec/integration/knife/chef_fs_data_store_spec.rb000066400000000000000000000467161276456504500244200ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/knife/list" require "chef/knife/delete" require "chef/knife/show" require "chef/knife/raw" require "chef/knife/cookbook_upload" describe "ChefFSDataStore tests", :workstation do include IntegrationSupport include KnifeSupport let(:cookbook_x_100_metadata_rb) { cb_metadata("x", "1.0.0") } let(:cookbook_z_100_metadata_rb) { cb_metadata("z", "1.0.0") } describe "with repo mode 'hosted_everything' (default)" do before do Chef::Config.chef_zero.osc_compat = false end when_the_repository "has one of each thing" do before do file "clients/x.json", {} file "cookbook_artifacts/x-111/metadata.rb", cookbook_x_100_metadata_rb file "cookbooks/x/metadata.rb", cookbook_x_100_metadata_rb file "data_bags/x/y.json", {} file "environments/x.json", {} file "nodes/x.json", {} file "roles/x.json", {} # file "users/x.json", {} file "containers/x.json", {} file "groups/x.json", {} file "containers/x.json", {} file "groups/x.json", {} file "policies/x-111.json", {} file "policy_groups/x.json", {} end context "GET /TYPE" do it "knife list -z -R returns everything" do knife("list -z -Rfp /").should_succeed < "x", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } } file "rolestuff.json", '{"description":"hi there","name":"x"}' file "cookbooks_to_upload/x/metadata.rb", cookbook_x_100_metadata_rb end it "knife raw -z -i empty.json -m PUT /clients/x" do knife("raw -z -i #{path_to('empty.json')} -m PUT /clients/x").should_succeed( /"x"/ ) knife("list --local /clients").should_succeed "/clients/x.json\n" end it "knife cookbook upload works" do knife("cookbook upload -z --cookbook-path #{path_to('cookbooks_to_upload')} x").should_succeed :stderr => < (RUBY_VERSION < "1.9") do knife("raw -z -i #{path_to('rolestuff.json')} -m PUT /roles/x").should_succeed( /"x"/ ) expect(IO.read(path_to("roles/x.json"))).to eq < "z" } file "dummynode.json", { "name" => "z", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } } file "empty_x.json", { "name" => "x" } file "empty_id.json", { "id" => "z" } file "rolestuff.json", '{"description":"hi there","name":"x"}' file "cookbooks_to_upload/z/metadata.rb", cookbook_z_100_metadata_rb end it "knife raw -z -i empty.json -m POST /clients" do knife("raw -z -i #{path_to('empty.json')} -m POST /clients").should_succeed( /uri/ ) knife("list --local /clients").should_succeed "/clients/z.json\n" end it "knife cookbook upload works" do knife("cookbook upload -z --cookbook-path #{path_to('cookbooks_to_upload')} z").should_succeed :stderr => < (RUBY_VERSION < "1.9") do knife("raw -z -i #{path_to('rolestuff.json')} -m POST /roles").should_succeed( /uri/ ) expect(IO.read(path_to("roles/x.json"))).to eq < "x", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } } file "rolestuff.json", '{"description":"hi there","name":"x"}' file "cookbooks_to_upload/x/metadata.rb", cookbook_x_100_metadata_rb end it "knife raw -z -i empty.json -m PUT /users/x" do knife("raw -z -i #{path_to('empty.json')} -m PUT /users/x").should_succeed( /"x"/ ) knife("list --local /users").should_succeed "/users/x.json\n" end it "After knife raw -z -i rolestuff.json -m PUT /roles/x, the output is pretty", :skip => (RUBY_VERSION < "1.9") do knife("raw -z -i #{path_to('rolestuff.json')} -m PUT /roles/x").should_succeed( /"x"/ ) expect(IO.read(path_to("roles/x.json"))).to eq < "z" } file "dummynode.json", { "name" => "z", "chef_environment" => "rspec" , "json_class" => "Chef::Node", "normal" => { "foo" => "bar" } } file "empty_x.json", { "name" => "x" } file "empty_id.json", { "id" => "z" } file "rolestuff.json", '{"description":"hi there","name":"x"}' file "cookbooks_to_upload/z/metadata.rb", cookbook_z_100_metadata_rb end it "knife raw -z -i empty.json -m POST /users" do knife("raw -z -i #{path_to('empty.json')} -m POST /users").should_succeed( /uri/ ) knife("list --local /users").should_succeed "/users/z.json\n" end end it "knife list -z -R returns nothing" do knife("list -z -Rfp /").should_succeed <) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" require "chef/knife/list" require "chef/knife/show" describe "chef_repo_path tests", :workstation do include IntegrationSupport include KnifeSupport let(:error_rel_path_outside_repo) { /^ERROR: Attempt to use relative path '' when current directory is outside the repository path/ } # TODO alternate repo_path / *_path context "alternate *_path" do when_the_repository "has clients and clients2, cookbooks and cookbooks2, etc." do before do file "clients/client1.json", {} file "cookbooks/cookbook1/metadata.rb", "" file "data_bags/bag/item.json", {} file "environments/env1.json", {} file "nodes/node1.json", {} file "roles/role1.json", {} file "users/user1.json", {} file "clients2/client2.json", {} file "cookbooks2/cookbook2/metadata.rb", "" file "data_bags2/bag2/item2.json", {} file "environments2/env2.json", {} file "nodes2/node2.json", {} file "roles2/role2.json", {} file "users2/user2.json", {} directory "chef_repo2" do file "clients/client3.json", {} file "cookbooks/cookbook3/metadata.rb", "name 'cookbook3'" file "data_bags/bag3/item3.json", {} file "environments/env3.json", {} file "nodes/node3.json", {} file "roles/role3.json", {} file "users/user3.json", {} end end it "knife list --local -Rfp --chef-repo-path chef_repo2 / grabs chef_repo2 stuff" do Chef::Config.delete(:chef_repo_path) knife("list --local -Rfp --chef-repo-path #{path_to('chef_repo2')} /").should_succeed < "WARN: Cookbook 'blah' is empty or entirely chefignored at #{Chef::Config.cookbook_path[0]}/blah\n") /cookbooks/blah/ /cookbooks/blah/metadata.rb /cookbooks/cookbook1/ /cookbooks/cookbook1/metadata.rb /cookbooks/cookbook2/ /cookbooks/cookbook2/metadata.rb EOM end end context "when there is a cookbook in cookbooks1 and a cookbook in cookbooks2 with the same name" do before do file "cookbooks/blah/metadata.json", {} file "cookbooks2/blah/metadata.rb", "" end it "knife list -Rfp cookbooks shows files in the first cookbook and not the second" do knife("list --local -Rfp /cookbooks").should_succeed(< "WARN: Child with name 'blah' found in multiple directories: #{Chef::Config.cookbook_path[0]}/blah and #{Chef::Config.cookbook_path[1]}/blah\n") /cookbooks/blah/ /cookbooks/blah/metadata.json /cookbooks/cookbook1/ /cookbooks/cookbook1/metadata.rb /cookbooks/cookbook2/ /cookbooks/cookbook2/metadata.rb EOM end end context "when there is a file in data_bags1 and a directory in data_bags2 with the same name" do before do file "data_bags/blah", "" file "data_bags2/blah/item.json", "" end it "knife list -Rfp data_bags shows files in blah" do knife("list --local -Rfp /data_bags").should_succeed < "WARN: Child with name 'blah' found in multiple directories: #{Chef::Config.data_bag_path[0]}/blah and #{Chef::Config.data_bag_path[1]}/blah\n") /data_bags/bag/ /data_bags/bag/item.json /data_bags/bag2/ /data_bags/bag2/item2.json /data_bags/blah/ /data_bags/blah/item1.json EOM end end context "when there is a directory in environments1 and file in environments2 with the same name" do before do directory "environments/blah.json" file "environments2/blah.json", {} end it "knife show /environments/blah.json succeeds" do knife("show --local /environments/blah.json").should_succeed <) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/knife/list" require "chef/knife/show" describe "General chef_repo file system checks", :workstation do include IntegrationSupport include KnifeSupport context "directories and files that should/should not be ignored" do when_the_repository "has empty roles, environments and data bag item directories" do before do directory "roles" directory "environments" directory "data_bags/bag1" end it "knife list --local -Rfp / returns them" do knife("list --local -Rfp /").should_succeed < "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n") /cookbooks/ EOM end end when_the_repository "has only empty cookbook subdirectories" do before { directory "cookbooks/cookbook1/recipes" } it "knife list --local -Rfp / does not return it" do knife("list --local -Rfp /").should_succeed(< "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n") /cookbooks/ EOM end end when_the_repository "has empty and non-empty cookbook subdirectories" do before do directory "cookbooks/cookbook1/recipes" file "cookbooks/cookbook1/templates/default/x.txt", "" end it "knife list --local -Rfp / does not return the empty ones" do knife("list --local -Rfp /").should_succeed < "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n") /cookbooks/ EOM end end when_the_repository "has empty cookbook sub-sub-directories alongside non-empty ones" do before do file "cookbooks/cookbook1/templates/default/x.txt", "" directory "cookbooks/cookbook1/templates/rhel" directory "cookbooks/cookbook1/files/default" end it "knife list --local -Rfp / does not return the empty ones" do knife("list --local -Rfp /").should_succeed <) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/knife/list" require "chef/knife/show" describe "chefignore tests", :workstation do include IntegrationSupport include KnifeSupport when_the_repository "has lots of stuff in it" do before do file "roles/x.json", {} file "environments/x.json", {} file "data_bags/bag1/x.json", {} file "cookbooks/cookbook1/x.json", {} end context "and has a chefignore everywhere except cookbooks" do before do chefignore = "x.json\nroles/x.json\nenvironments/x.json\ndata_bags/bag1/x.json\nbag1/x.json\ncookbooks/cookbook1/x.json\ncookbook1/x.json\n" file "chefignore", chefignore file "roles/chefignore", chefignore file "environments/chefignore", chefignore file "data_bags/chefignore", chefignore file "data_bags/bag1/chefignore", chefignore file "cookbooks/cookbook1/chefignore", chefignore end it "matching files and directories get ignored" do # NOTE: many of the "chefignore" files should probably not show up # themselves, but we have other tests that talk about that knife("list --local -Rfp /").should_succeed < "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n") /cookbooks/ EOM end end when_the_repository "has multiple cookbooks" do before do file "cookbooks/cookbook1/x.json", {} file "cookbooks/cookbook1/y.json", {} file "cookbooks/cookbook2/x.json", {} file "cookbooks/cookbook2/y.json", {} end context "and has a chefignore with filenames" do before { file "cookbooks/chefignore", "x.json\n" } it "matching files and directories get ignored in all cookbooks" do knife("list --local -Rfp /").should_succeed < "WARN: Child with name 'yourcookbook' found in multiple directories: #{Chef::Config.chef_repo_path}/cookbooks1/yourcookbook and #{Chef::Config.chef_repo_path}/cookbooks2/yourcookbook\n") /cookbooks/ /cookbooks/mycookbook/ /cookbooks/mycookbook/x.json /cookbooks/yourcookbook/ /cookbooks/yourcookbook/onlyincookbooks1.rb /cookbooks/yourcookbook/x.json EOM end end end end when_the_repository "has a cookbook named chefignore" do before do file "cookbooks/chefignore/metadata.rb", {} end it "knife list -Rfp /cookbooks shows it" do knife("list --local -Rfp /cookbooks").should_succeed <> 1).strftime("%FT%TZ") } when_the_chef_server "has a client" do before do client "cons", {} knife("client key create cons -k new") knife("client key create cons -k next_month -e #{next_month}") knife("client key create cons -k expired -e #{last_month}") end it "lists the keys for a client" do knife("client key list cons").should_succeed "expired\nnew\nnext_month\n" end it "shows detailed output" do knife("client key list -w cons").should_succeed <> 1).strftime("%FT%TZ") } when_the_chef_server "has a client" do before do client "cons", {} knife("client key create cons -k new") knife("client key create cons -k next_month -e #{next_month}") knife("client key create cons -k expired -e #{last_month}") end it "shows a key for a client" do knife("client key show cons new").should_succeed stdout: /.*name:.*new/ end end end chef-12.14.60/spec/integration/knife/client_list_spec.rb000066400000000000000000000023441276456504500231140ustar00rootroot00000000000000# # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" describe "knife client list", :workstation do include IntegrationSupport include KnifeSupport include_context "default config options" when_the_chef_server "has some clients" do before do client "cons", {} client "car", {} client "car-validator", { validator: true } client "cdr", {} client "cat", {} end it "lists the clients" do knife("client list").should_succeed <) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/knife/raw" describe "knife common options", :workstation do include IntegrationSupport include KnifeSupport when_the_repository "has a node" do before { file "nodes/x.json", {} } context "When chef_zero.enabled is true" do before(:each) do Chef::Config.chef_zero.enabled = true end it "knife raw /nodes/x should retrieve the node" do knife("raw /nodes/x").should_succeed( /"name": "x"/ ) end context "And chef_zero.port is 9999" do before(:each) { Chef::Config.chef_zero.port = 9999 } it "knife raw /nodes/x should retrieve the node" do knife("raw /nodes/x").should_succeed( /"name": "x"/ ) expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999") end end # 0.0.0.0 is not a valid address to bind to on windows. context "And chef_zero.host is 0.0.0.0", :unix_only do before(:each) { Chef::Config.chef_zero.host = "0.0.0.0" } it "knife raw /nodes/x should retrieve the role" do knife("raw /nodes/x").should_succeed( /"name": "x"/ ) end end context "and there is a private key" do before do file "mykey.pem", < "localhost", :port => 8889) @server.start_background rescue Errno::EADDRINUSE # OK. Don't care who has it in use, as long as *someone* does. end end after :each do @server.stop if @server end it "knife raw -z /nodes/x retrieves the node" do knife("raw -z /nodes/x").should_succeed( /"name": "x"/ ) expect(URI(Chef::Config.chef_server_url).port).to be > 8889 end end context "when port 9999 is already bound" do before :each do begin @server = ChefZero::Server.new(:host => "localhost", :port => 9999) @server.start_background rescue Errno::EADDRINUSE # OK. Don't care who has it in use, as long as *someone* does. end end after :each do @server.stop if @server end it "knife raw -z --chef-zero-port=9999-20000 /nodes/x" do knife("raw -z --chef-zero-port=9999-20000 /nodes/x").should_succeed( /"name": "x"/ ) expect(URI(Chef::Config.chef_server_url).port).to be > 9999 end it "knife raw -z --chef-zero-port=9999-9999,19423" do knife("raw -z --chef-zero-port=9999-9999,19423 /nodes/x").should_succeed( /"name": "x"/ ) expect(URI(Chef::Config.chef_server_url).port).to be == 19423 end end it "knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node" do knife("raw -z --chef-zero-port=9999 /nodes/x").should_succeed( /"name": "x"/ ) expect(Chef::Config.chef_server_url).to eq("chefzero://localhost:9999") end end end chef-12.14.60/spec/integration/knife/cookbook_api_ipv6_spec.rb000066400000000000000000000112221276456504500242010ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/mixin/shell_out" describe "Knife cookbook API integration with IPv6", :workstation, :not_supported_on_gce do include IntegrationSupport include Chef::Mixin::ShellOut when_the_chef_server "is bound to IPv6" do let(:chef_zero_opts) { { :host => "::1" } } let(:client_key) do <<-END_VALIDATION_PEM -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEApubutqtYYQ5UiA9QhWP7UvSmsfHsAoPKEVVPdVW/e8Svwpyf 0Xef6OFWVmBE+W442ZjLOe2y6p2nSnaq4y7dg99NFz6X+16mcKiCbj0RCiGqCvCk NftHhTgO9/RFvCbmKZ1RKNob1YzLrFpxBHaSh9po+DGWhApcd+I+op+ZzvDgXhNn 0nauZu3rZmApI/r7EEAOjFedAXs7VPNXhhtZAiLSAVIrwU3ZajtSzgXOxbNzgj5O AAAMmThK+71qPdffAdO4J198H6/MY04qgtFo7vumzCq0UCaGZfmeI1UNE4+xQWwP HJ3pDAP61C6Ebx2snI2kAd9QMx9Y78nIedRHPwIDAQABAoIBAHssRtPM1GacWsom 8zfeN6ZbI4KDlbetZz0vhnqDk9NVrpijWlcOP5dwZXVNitnB/HaqCqFvyPDY9JNB zI/pEFW4QH59FVDP42mVEt0keCTP/1wfiDDGh1vLqVBYl/ZphscDcNgDTzNkuxMx k+LFVxKnn3w7rGc59lALSkpeGvbbIDjp3LUMlUeCF8CIFyYZh9ZvXe4OCxYdyjxb i8tnMLKvJ4Psbh5jMapsu3rHQkfPdqzztQUz8vs0NYwP5vWge46FUyk+WNm/IhbJ G3YM22nwUS8Eu2bmTtADSJolATbCSkOwQ1D+Fybz/4obfYeGaCdOqB05ttubhenV ShsAb7ECgYEA20ecRVxw2S7qA7sqJ4NuYOg9TpfGooptYNA1IP971eB6SaGAelEL awYkGNuu2URmm5ElZpwJFFTDLGA7t2zB2xI1FeySPPIVPvJGSiZoFQOVlIg9WQzK 7jTtFQ/tOMrF+bigEUJh5bP1/7HzqSpuOsPjEUb2aoCTp+tpiRGL7TUCgYEAwtns g3ysrSEcTzpSv7fQRJRk1lkBhatgNd0oc+ikzf74DaVLhBg1jvSThDhiDCdB59mr Jh41cnR1XqE8jmdQbCDRiFrI1Pq6TPaDZFcovDVE1gue9x86v3FOH2ukPG4d2/Xy HevXjThtpMMsWFi0JYXuzXuV5HOvLZiP8sN3lSMCgYANpdxdGM7RRbE9ADY0dWK2 V14ReTLcxP7fyrWz0xLzEeCqmomzkz3BsIUoouu0DCTSw+rvAwExqcDoDylIVlWO fAifz7SeZHbcDxo+3TsXK7zwnLYsx7YNs2+aIv6hzUUbMNmNmXMcZ+IEwx+mRMTN lYmZdrA5mr0V83oDFPt/jQKBgC74RVE03pMlZiObFZNtheDiPKSG9Bz6wMh7NWMr c37MtZLkg52mEFMTlfPLe6ceV37CM8WOhqe+dwSGrYhOU06dYqUR7VOZ1Qr0aZvo fsNPu/Y0+u7rMkgv0fs1AXQnvz7kvKaF0YITVirfeXMafuKEtJoH7owRbur42cpV YCAtAoGAP1rHOc+w0RUcBK3sY7aErrih0OPh9U5bvJsrw1C0FIZhCEoDVA+fNIQL syHLXYFNy0OxMtH/bBAXBGNHd9gf5uOnqh0pYcbe/uRAxumC7Rl0cL509eURiA2T +vFmf54y9YdnLXaqv+FhJT6B6V7WX7IpU9BMqJY1cJYXHuHG2KA= -----END RSA PRIVATE KEY----- END_VALIDATION_PEM end let(:cache_path) do Dir.mktmpdir end let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") } let(:knife) { "ruby '#{chef_dir}/knife'" } let(:knife_config_flag) { "-c '#{path_to("config/knife.rb")}'" } # Some Solaris test platforms are too old for IPv6. These tests should not # otherwise be platform dependent, so exclude solaris context "and the chef_server_url contains an IPv6 literal", :not_supported_on_solaris do # This provides helper functions we need such as #path_to() when_the_repository "has the cookbook to be uploaded" do let(:knife_rb_content) do <<-END_CLIENT_RB chef_server_url "http://[::1]:8900" syntax_check_cache_path '#{cache_path}' client_key '#{path_to('config/knifeuser.pem')}' node_name 'whoisthisis' cookbook_path '#{CHEF_SPEC_DATA}/cookbooks' END_CLIENT_RB end before do file "config/knife.rb", knife_rb_content file "config/knifeuser.pem", client_key end it "successfully uploads a cookbook" do shell_out!("#{knife} cookbook upload apache2 #{knife_config_flag}", :cwd => chef_dir) versions_list_json = Chef::HTTP::Simple.new("http://[::1]:8900").get("/cookbooks/apache2", "accept" => "application/json") versions_list = Chef::JSONCompat.from_json(versions_list_json) expect(versions_list["apache2"]["versions"]).not_to be_empty end context "and the cookbook has been uploaded to the server" do before do shell_out!("#{knife} cookbook upload apache2 #{knife_config_flag}", :cwd => chef_dir) end it "downloads the cookbook" do shell_out!("knife cookbook download apache2 #{knife_config_flag} -d #{cache_path}", :cwd => chef_dir) expect(Dir["#{cache_path}/*"].map { |entry| File.basename(entry) }).to include("apache2-0.0.1") end end end end end end chef-12.14.60/spec/integration/knife/cookbook_bulk_delete_spec.rb000066400000000000000000000036061276456504500247520ustar00rootroot00000000000000# # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" require "chef/knife/cookbook_bulk_delete" describe "knife cookbook bulk delete", :workstation do include IntegrationSupport include KnifeSupport include_context "default config options" when_the_chef_server "has a cookbook" do before do cookbook "foo", "1.0.0" cookbook "foo", "0.6.5" cookbook "fox", "0.6.0" cookbook "fox", "0.6.5" cookbook "fax", "0.6.0" cookbook "zfa", "0.6.5" end # rubocop:disable Style/TrailingWhitespace it "knife cookbook bulk delete deletes all matching cookbooks" do stdout = < { "default.rb" => "file 'n'", "x.rb" => "" } } cookbook "x", "0.6.5" end it "knife cookbook show x shows all the versions" do knife("cookbook show x").should_succeed "x 1.0.0 0.6.5\n" end # rubocop:disable Style/TrailingWhitespace it "knife cookbook show x 1.0.0 shows the correct version" do knife("cookbook show x 1.0.0").should_succeed <= 0.0.0' EOM end it "knife cookbook upload -a uploads both cookbooks" do knife("cookbook upload -a -o #{cb_dir}").should_succeed stderr: < { heavy: "true" }, "atlas" => {}, "ariane" => {} } end it "with an empty data bag" do knife("data bag delete canteloupe", input: "y").should_succeed < "bar", "foo" => "bar " } file "data_bags/foo/bzr.json", { "id" => "bzr", "foo" => "bar " } file "data_bags/foo/cat.json", { "id" => "cat", "foo" => "bar " } file "data_bags/foo/dog.json", { "id" => "dog", "foo" => "bar " } file "data_bags/foo/encrypted.json", < { heavy: "true" }, "atlas" => {}, "ariane" => {} } end it "with an empty data bag" do knife("data bag show canteloupe").should_succeed "\n" end it "with a bag with some items" do knife("data bag show rocket").should_succeed <) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/knife/delete" require "chef/knife/list" require "chef/knife/raw" describe "knife delete", :workstation do include IntegrationSupport include KnifeSupport let :everything do < "{}" } environment "x", "{}" node "x", "{}" role "x", "{}" user "x", "{}" end when_the_repository "also has one of each thing" do before do file "clients/x.json", {} file "cookbooks/x/metadata.rb", "" file "data_bags/x/y.json", {} file "environments/_default.json", {} file "environments/x.json", {} file "nodes/x.json", {} file "roles/x.json", {} file "users/x.json", {} end it "knife delete --both /cookbooks/x fails" do knife("delete --both /cookbooks/x").should_fail < "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n", :stdout => "Deleted /environments/_default.json\n" knife("list -Rf /").should_succeed server_everything knife("list -Rf --local /").should_succeed < "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n" knife("list -Rf /").should_succeed server_everything knife("list -Rf --local /").should_succeed nothing end it "knife delete --both / fails" do knife("delete --both /").should_fail "ERROR: / (remote) cannot be deleted.\nERROR: / (local) cannot be deleted.\n" knife("list -Rf /").should_succeed server_everything knife("list -Rf --local /").should_succeed nothing end it "knife delete --both -r /* fails" do knife("delete --both -r /*").should_fail < /USAGE/ knife("list -Rf /").should_succeed < "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n", :stdout => "Deleted /environments/_default.json\n" knife("list -Rf /").should_succeed server_nothing knife("list -Rf --local /").should_succeed < /USAGE/ knife("list -Rf /").should_succeed < "" } cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } end # TODO this seems wrong it "knife delete --both -r /cookbooks/x deletes the latest version on the server and the local version" do knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" knife("raw /cookbooks/x").should_succeed(/1.0.0/) knife("list --local /cookbooks").should_succeed "" end end when_the_chef_server "has an earlier version for the cookbook" do before do cookbook "x", "1.0.0", { "onlyin1.0.0.rb" => "" } cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } end it "knife delete --both /cookbooks/x deletes the latest version on the server and the local version" do knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" knife("raw /cookbooks/x").should_succeed(/0.9.9/) knife("list --local /cookbooks").should_succeed "" end end when_the_chef_server "has a later version for the cookbook, and no current version" do before { cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } } it "knife delete --both /cookbooks/x deletes the server and client version of the cookbook" do knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" knife("raw /cookbooks/x").should_fail(/404/) knife("list --local /cookbooks").should_succeed "" end end when_the_chef_server "has an earlier version for the cookbook, and no current version" do before { cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } } it "knife delete --both /cookbooks/x deletes the server and client version of the cookbook" do knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" knife("raw /cookbooks/x").should_fail(/404/) knife("list --local /cookbooks").should_succeed "" end end end when_the_repository "is empty" do when_the_chef_server "has two versions of a cookbook" do before do cookbook "x", "2.0.11" cookbook "x", "11.0.0" end it "knife delete deletes the latest version" do knife("delete --both -r /cookbooks/x").should_succeed "Deleted /cookbooks/x\n" knife("raw /cookbooks/x").should_succeed( /2.0.11/ ) end end end when_the_chef_server "is in Enterprise mode", :osc_compat => false, :single_org => false do before do organization "foo" do container "x", {} group "x", {} policy "x", "1.2.3", {} policy_group "x", { "policies" => { "x" => { "revision_id" => "1.2.3" } } } end end before :each do Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo") end it "knife delete /acls/containers/environments.json fails with a reasonable error" do knife("delete /acls/containers/environments.json").should_fail "ERROR: /acls/containers/environments.json (remote) ACLs cannot be deleted.\n" end it "knife delete /containers/x.json succeeds" do knife("delete /containers/x.json").should_succeed "Deleted /containers/x.json\n" knife("raw /containers/x.json").should_fail(/404/) end it "knife delete /groups/x.json succeeds" do knife("delete /groups/x.json").should_succeed "Deleted /groups/x.json\n" knife("raw /groups/x.json").should_fail(/404/) end it "knife delete /policies/x-1.2.3.json succeeds" do knife("raw /policies/x/revisions/1.2.3").should_succeed "{\n \"name\": \"x\",\n \"revision_id\": \"1.2.3\",\n \"run_list\": [\n\n ],\n \"cookbook_locks\": {\n\n }\n}\n" knife("delete /policies/x-1.2.3.json").should_succeed "Deleted /policies/x-1.2.3.json\n" knife("raw /policies/x/revisions/1.2.3").should_fail(/404/) end it "knife delete /policy_groups/x.json succeeds" do knife("raw /policy_groups/x").should_succeed "{\n \"uri\": \"http://127.0.0.1:8900/organizations/foo/policy_groups/x\",\n \"policies\": {\n \"x\": {\n \"revision_id\": \"1.2.3\"\n }\n }\n}\n" knife("delete /policy_groups/x.json").should_succeed "Deleted /policy_groups/x.json\n" knife("raw /policy_groups/x").should_fail(/404/) end it "knife delete /org.json fails with a reasonable error" do knife("delete /org.json").should_fail "ERROR: /org.json (remote) cannot be deleted.\n" end it "knife delete /invitations.json fails with a reasonable error" do knife("delete /invitations.json").should_fail "ERROR: /invitations.json (remote) cannot be deleted.\n" end it "knife delete /members.json fails with a reasonable error" do knife("delete /members.json").should_fail "ERROR: /members.json (remote) cannot be deleted.\n" end end end chef-12.14.60/spec/integration/knife/deps_spec.rb000066400000000000000000000637761276456504500215560ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" require "chef/knife/deps" describe "knife deps", :workstation do include IntegrationSupport include KnifeSupport context "local" do when_the_repository "has a role with no run_list" do before { file "roles/starring.json", {} } it "knife deps reports no dependencies" do knife("deps /roles/starring.json").should_succeed "/roles/starring.json\n" end end when_the_repository "has a role with a default run_list" do before do file "roles/starring.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } file "roles/minor.json", {} file "cookbooks/quiche/metadata.rb", 'name "quiche"' file "cookbooks/quiche/recipes/default.rb", "" file "cookbooks/soup/metadata.rb", 'name "soup"' file "cookbooks/soup/recipes/chicken.rb", "" end it "knife deps reports all dependencies" do knife("deps /roles/starring.json").should_succeed < { "desert" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } } file "roles/minor.json", {} file "cookbooks/quiche/metadata.rb", 'name "quiche"' file "cookbooks/quiche/recipes/default.rb", "" file "cookbooks/soup/metadata.rb", 'name "soup"' file "cookbooks/soup/recipes/chicken.rb", "" end it "knife deps reports all dependencies" do knife("deps /roles/starring.json").should_succeed < "desert" } end it "knife deps reports just the node" do knife("deps /nodes/mort.json").should_succeed "/environments/desert.json\n/nodes/mort.json\n" end end when_the_repository "has a node with roles and recipes in its run_list" do before do file "roles/minor.json", {} file "cookbooks/quiche/metadata.rb", 'name "quiche"' file "cookbooks/quiche/recipes/default.rb", "" file "cookbooks/soup/metadata.rb", 'name "soup"' file "cookbooks/soup/recipes/chicken.rb", "" file "nodes/mort.json", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } end it "knife deps reports just the node" do knife("deps /nodes/mort.json").should_succeed < %w{role[minor] recipe[quiche] recipe[soup::chicken]} } file "roles/minor.json", {} file "cookbooks/quiche/metadata.rb", 'name "quiche"' file "cookbooks/quiche/recipes/default.rb", "" file "cookbooks/soup/metadata.rb", 'name "soup"' file "cookbooks/soup/recipes/chicken.rb", "" file "environments/desert.json", {} file "nodes/mort.json", { "chef_environment" => "desert", "run_list" => [ "role[starring]" ] } file "nodes/bart.json", { "run_list" => [ "role[minor]" ] } end it "knife deps reports all dependencies" do knife("deps /nodes/mort.json").should_succeed < [ "role[bar]" ] } file "roles/bar.json", { "run_list" => [ "role[baz]" ] } file "roles/baz.json", { "run_list" => [ "role[foo]" ] } file "roles/self.json", { "run_list" => [ "role[self]" ] } end it "knife deps prints each once" do knife("deps /roles/foo.json /roles/self.json").should_succeed < 2, :stdout => "/blah\n", :stderr => "ERROR: /blah: No such file or directory\n" ) end it "knife deps /roles/x.json reports an error" do knife("deps /roles/x.json").should_fail( :exit_code => 2, :stdout => "/roles/x.json\n", :stderr => "ERROR: /roles/x.json: No such file or directory\n" ) end it "knife deps /nodes/x.json reports an error" do knife("deps /nodes/x.json").should_fail( :exit_code => 2, :stdout => "/nodes/x.json\n", :stderr => "ERROR: /nodes/x.json: No such file or directory\n" ) end it "knife deps /environments/x.json reports an error" do knife("deps /environments/x.json").should_fail( :exit_code => 2, :stdout => "/environments/x.json\n", :stderr => "ERROR: /environments/x.json: No such file or directory\n" ) end it "knife deps /cookbooks/x reports an error" do knife("deps /cookbooks/x").should_fail( :exit_code => 2, :stdout => "/cookbooks/x\n", :stderr => "ERROR: /cookbooks/x: No such file or directory\n" ) end it "knife deps /data_bags/bag/item.json reports an error" do knife("deps /data_bags/bag/item.json").should_fail( :exit_code => 2, :stdout => "/data_bags/bag/item.json\n", :stderr => "ERROR: /data_bags/bag/item.json: No such file or directory\n" ) end end when_the_repository "is missing a dependent cookbook" do before do file "roles/starring.json", { "run_list" => [ "recipe[quiche]"] } end it "knife deps reports the cookbook, along with an error" do knife("deps /roles/starring.json").should_fail( :exit_code => 2, :stdout => "/cookbooks/quiche\n/roles/starring.json\n", :stderr => "ERROR: /cookbooks/quiche: No such file or directory\n" ) end end when_the_repository "is missing a dependent environment" do before do file "nodes/mort.json", { "chef_environment" => "desert" } end it "knife deps reports the environment, along with an error" do knife("deps /nodes/mort.json").should_fail( :exit_code => 2, :stdout => "/environments/desert.json\n/nodes/mort.json\n", :stderr => "ERROR: /environments/desert.json: No such file or directory\n" ) end end when_the_repository "is missing a dependent role" do before do file "roles/starring.json", { "run_list" => [ "role[minor]"] } end it "knife deps reports the role, along with an error" do knife("deps /roles/starring.json").should_fail( :exit_code => 2, :stdout => "/roles/minor.json\n/roles/starring.json\n", :stderr => "ERROR: /roles/minor.json: No such file or directory\n" ) end end end context "invalid objects" do when_the_repository "is empty" do it "knife deps / reports itself only" do knife("deps /").should_succeed("/\n") end it "knife deps /roles reports an error" do knife("deps /roles").should_fail( :exit_code => 2, :stderr => "ERROR: /roles: No such file or directory\n", :stdout => "/roles\n" ) end end when_the_repository "has a data bag" do before { file "data_bags/bag/item.json", "" } it "knife deps /data_bags/bag shows no dependencies" do knife("deps /data_bags/bag").should_succeed("/data_bags/bag\n") end end when_the_repository "has a cookbook" do before { file "cookbooks/blah/metadata.rb", 'name "blah"' } it "knife deps on a cookbook file shows no dependencies" do knife("deps /cookbooks/blah/metadata.rb").should_succeed( "/cookbooks/blah/metadata.rb\n" ) end end end end context "remote" do include_context "default config options" when_the_chef_server "has a role with no run_list" do before { role "starring", {} } it "knife deps reports no dependencies" do knife("deps --remote /roles/starring.json").should_succeed "/roles/starring.json\n" end end when_the_chef_server "has a role with a default run_list" do before do role "starring", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } role "minor", {} cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } } cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } } end it "knife deps reports all dependencies" do knife("deps --remote /roles/starring.json").should_succeed < { "desert" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } } role "minor", {} cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } } cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } } end it "knife deps reports all dependencies" do knife("deps --remote /roles/starring.json").should_succeed < "desert" } end it "knife deps reports just the node" do knife("deps --remote /nodes/mort.json").should_succeed "/environments/desert.json\n/nodes/mort.json\n" end end when_the_chef_server "has a node with roles and recipes in its run_list" do before do role "minor", {} cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } } cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } } node "mort", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } end it "knife deps reports just the node" do knife("deps --remote /nodes/mort.json").should_succeed < %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } } end it "knife deps reports just the cookbook" do knife("deps --remote /cookbooks/quiche").should_succeed "/cookbooks/quiche\n" end end when_the_chef_server "has a cookbook with dependencies" do before do cookbook "kettle", "1.0.0", { "metadata.rb" => %Q{name "kettle"\nversion "1.0.0"\n} } cookbook "quiche", "1.0.0", { "metadata.rb" => 'name "quiche" depends "kettle"', "recipes" => { "default.rb" => "" } } end it "knife deps reports the cookbook and its dependencies" do knife("deps --remote /cookbooks/quiche").should_succeed "/cookbooks/kettle\n/cookbooks/quiche\n" end end when_the_chef_server "has a data bag" do before { data_bag "bag", { "item" => {} } } it "knife deps reports just the data bag" do knife("deps --remote /data_bags/bag/item.json").should_succeed "/data_bags/bag/item.json\n" end end when_the_chef_server "has an environment" do before { environment "desert", {} } it "knife deps reports just the environment" do knife("deps --remote /environments/desert.json").should_succeed "/environments/desert.json\n" end end when_the_chef_server "has a deep dependency tree" do before do role "starring", { "run_list" => %w{role[minor] recipe[quiche] recipe[soup::chicken]} } role "minor", {} cookbook "quiche", "1.0.0", { "metadata.rb" => %Q{name "quiche"\nversion "1.0.0"\n}, "recipes" => { "default.rb" => "" } } cookbook "soup", "1.0.0", { "metadata.rb" => %Q{name "soup"\nversion "1.0.0"\n}, "recipes" => { "chicken.rb" => "" } } environment "desert", {} node "mort", { "chef_environment" => "desert", "run_list" => [ "role[starring]" ] } node "bart", { "run_list" => [ "role[minor]" ] } end it "knife deps reports all dependencies" do knife("deps --remote /nodes/mort.json").should_succeed < 'name "foo" depends "bar"' } cookbook "bar", "1.0.0", { "metadata.rb" => 'name "bar" depends "baz"' } cookbook "baz", "1.0.0", { "metadata.rb" => 'name "baz" depends "foo"' } cookbook "self", "1.0.0", { "metadata.rb" => 'name "self" depends "self"' } end it "knife deps prints each once" do knife("deps --remote /cookbooks/foo /cookbooks/self").should_succeed < [ "role[bar]" ] } role "bar", { "run_list" => [ "role[baz]" ] } role "baz", { "run_list" => [ "role[foo]" ] } role "self", { "run_list" => [ "role[self]" ] } end it "knife deps prints each once" do knife("deps --remote /roles/foo.json /roles/self.json").should_succeed < 2, :stdout => "/blah\n", :stderr => "ERROR: /blah: No such file or directory\n" ) end it "knife deps /roles/x.json reports an error" do knife("deps --remote /roles/x.json").should_fail( :exit_code => 2, :stdout => "/roles/x.json\n", :stderr => "ERROR: /roles/x.json: No such file or directory\n" ) end it "knife deps /nodes/x.json reports an error" do knife("deps --remote /nodes/x.json").should_fail( :exit_code => 2, :stdout => "/nodes/x.json\n", :stderr => "ERROR: /nodes/x.json: No such file or directory\n" ) end it "knife deps /environments/x.json reports an error" do knife("deps --remote /environments/x.json").should_fail( :exit_code => 2, :stdout => "/environments/x.json\n", :stderr => "ERROR: /environments/x.json: No such file or directory\n" ) end it "knife deps /cookbooks/x reports an error" do knife("deps --remote /cookbooks/x").should_fail( :exit_code => 2, :stdout => "/cookbooks/x\n", :stderr => "ERROR: /cookbooks/x: No such file or directory\n" ) end it "knife deps /data_bags/bag/item reports an error" do knife("deps --remote /data_bags/bag/item.json").should_fail( :exit_code => 2, :stdout => "/data_bags/bag/item.json\n", :stderr => "ERROR: /data_bags/bag/item.json: No such file or directory\n" ) end end when_the_chef_server "is missing a dependent cookbook" do before do role "starring", { "run_list" => [ "recipe[quiche]"] } end it "knife deps reports the cookbook, along with an error" do knife("deps --remote /roles/starring.json").should_fail( :exit_code => 2, :stdout => "/cookbooks/quiche\n/roles/starring.json\n", :stderr => "ERROR: /cookbooks/quiche: No such file or directory\n" ) end end when_the_chef_server "is missing a dependent environment" do before do node "mort", { "chef_environment" => "desert" } end it "knife deps reports the environment, along with an error" do knife("deps --remote /nodes/mort.json").should_fail( :exit_code => 2, :stdout => "/environments/desert.json\n/nodes/mort.json\n", :stderr => "ERROR: /environments/desert.json: No such file or directory\n" ) end end when_the_chef_server "is missing a dependent role" do before do role "starring", { "run_list" => [ "role[minor]"] } end it "knife deps reports the role, along with an error" do knife("deps --remote /roles/starring.json").should_fail( :exit_code => 2, :stdout => "/roles/minor.json\n/roles/starring.json\n", :stderr => "ERROR: /roles/minor.json: No such file or directory\n" ) end end end context "invalid objects" do when_the_chef_server "is empty" do it "knife deps / reports an error" do knife("deps --remote /").should_succeed("/\n") end it "knife deps /roles reports an error" do knife("deps --remote /roles").should_succeed("/roles\n") end end when_the_chef_server "has a data bag" do before { data_bag "bag", { "item" => {} } } it "knife deps /data_bags/bag shows no dependencies" do knife("deps --remote /data_bags/bag").should_succeed("/data_bags/bag\n") end end when_the_chef_server "has a cookbook" do before do cookbook "blah", "1.0.0", { "metadata.rb" => 'name "blah"' } end it "knife deps on a cookbook file shows no dependencies" do knife("deps --remote /cookbooks/blah/metadata.rb").should_succeed( "/cookbooks/blah/metadata.rb\n" ) end end end end it "knife deps --no-recurse reports an error" do knife("deps --no-recurse /").should_fail("ERROR: --no-recurse requires --tree\n") end end chef-12.14.60/spec/integration/knife/diff_spec.rb000066400000000000000000000452161276456504500215200ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/knife/diff" describe "knife diff", :workstation do include IntegrationSupport include KnifeSupport context "without versioned cookbooks" do when_the_chef_server "has one of each thing" do before do client "x", "{}" cookbook "x", "1.0.0" data_bag "x", { "y" => "{}" } environment "x", "{}" node "x", "{}" role "x", "{}" user "x", "{}" end when_the_repository "has only top-level directories" do before do directory "clients" directory "cookbooks" directory "data_bags" directory "environments" directory "nodes" directory "roles" directory "users" end it "knife diff reports everything as deleted" do knife("diff --name-status /").should_succeed < true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") file "data_bags/x/y.json", {} file "environments/_default.json", { "description" => "The default Chef environment" } file "environments/x.json", {} file "nodes/x.json", { "normal" => { "tags" => [] } } file "roles/x.json", {} file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife diff reports no differences" do knife("diff /").should_succeed "" end it "knife diff /environments/nonexistent.json reports an error" do knife("diff /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n" end it "knife diff /environments/*.txt reports an error" do knife("diff /environments/*.txt").should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n" end context "except the role file" do before do file "roles/x.json", < ChefZero::PUBLIC_KEY } file "cookbooks/x/blah.rb", "" file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0") file "data_bags/x/z.json", {} file "data_bags/y/zz.json", {} file "environments/y.json", {} file "nodes/y.json", {} file "roles/y.json", {} file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife diff reports the new files as added" do knife("diff --name-status /").should_succeed < "" } cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" } end it "knife diff /cookbooks/x shows differences" do knife("diff --name-status /cookbooks/x").should_succeed < "" } cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "" } end it "knife diff /cookbooks/x shows no differences" do knife("diff --name-status /cookbooks/x").should_succeed "" end end when_the_chef_server "has a later version for the cookbook, and no current version" do before do cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" } end it "knife diff /cookbooks/x shows the differences" do knife("diff --name-status /cookbooks/x").should_succeed < "" } end it "knife diff /cookbooks/x shows the differences" do knife("diff --name-status /cookbooks/x").should_succeed < "hi" } } it "knife diff reports the difference", :skip => (RUBY_VERSION < "1.9") do knife("diff /environments/x.json").should_succeed(/ { - "name": "x", - "description": "hi" \+ "name": "x" } /) end end end when_the_repository "has an environment file with a value in it" do before do file "environments/x.json", { "description" => "hi" } end when_the_chef_server "has an environment with the same value" do before do environment "x", { "description" => "hi" } end it "knife diff returns no differences" do knife("diff /environments/x.json").should_succeed "" end end when_the_chef_server "has an environment with no value" do before do environment "x", {} end it "knife diff reports the difference", :skip => (RUBY_VERSION < "1.9") do knife("diff /environments/x.json").should_succeed(/ { - "name": "x" \+ "name": "x", \+ "description": "hi" } /) end end when_the_chef_server "has an environment with a different value" do before do environment "x", { "description" => "lo" } end it "knife diff reports the difference", :skip => (RUBY_VERSION < "1.9") do knife("diff /environments/x.json").should_succeed(/ { "name": "x", - "description": "lo" \+ "description": "hi" } /) end end end end when_the_chef_server "has an environment" do before { environment "x", {} } when_the_repository "has an environment with bad JSON" do before { file "environments/x.json", "{" } it "knife diff reports an error and does a textual diff" do error_text = "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF" error_match = Regexp.new(Regexp.escape(error_text)) knife("diff /environments/x.json").should_succeed(/- "name": "x"/, :stderr => error_match) end end end end # without versioned cookbooks with_versioned_cookbooks do when_the_chef_server "has one of each thing" do before do client "x", "{}" cookbook "x", "1.0.0" data_bag "x", { "y" => "{}" } environment "x", "{}" node "x", "{}" role "x", "{}" user "x", "{}" end when_the_repository "has only top-level directories" do before do directory "clients" directory "cookbooks" directory "data_bags" directory "environments" directory "nodes" directory "roles" directory "users" end it "knife diff reports everything as deleted" do knife("diff --name-status /").should_succeed < true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") file "data_bags/x/y.json", {} file "environments/_default.json", { "description" => "The default Chef environment" } file "environments/x.json", {} file "nodes/x.json", { "normal" => { "tags" => [] } } file "roles/x.json", {} file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife diff reports no differences" do knife("diff /").should_succeed "" end it "knife diff /environments/nonexistent.json reports an error" do knife("diff /environments/nonexistent.json").should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n" end it "knife diff /environments/*.txt reports an error" do knife("diff /environments/*.txt").should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n" end context "except the role file" do before do file "roles/x.json", < "" } cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" } end it "knife diff /cookbooks shows differences" do knife("diff --name-status /cookbooks").should_succeed < "" } cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "" } end it "knife diff /cookbooks shows the differences" do knife("diff --name-status /cookbooks").should_succeed "D\t/cookbooks/x-0.9.9\n" end end when_the_chef_server "has a later version for the cookbook, and no current version" do before do cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "" } end it "knife diff /cookbooks shows the differences" do knife("diff --name-status /cookbooks").should_succeed < "" } end it "knife diff /cookbooks shows the differences" do knife("diff --name-status /cookbooks").should_succeed < "hi" } } it "knife diff reports the difference", :skip => (RUBY_VERSION < "1.9") do knife("diff /environments/x.json").should_succeed(/ { - "name": "x", - "description": "hi" \+ "name": "x" } /) end end end when_the_repository "has an environment file with a value in it" do before do file "environments/x.json", { "description" => "hi" } end when_the_chef_server "has an environment with the same value" do before do environment "x", { "description" => "hi" } end it "knife diff returns no differences" do knife("diff /environments/x.json").should_succeed "" end end when_the_chef_server "has an environment with no value" do before { environment "x", {} } it "knife diff reports the difference", :skip => (RUBY_VERSION < "1.9") do knife("diff /environments/x.json").should_succeed(/ { - "name": "x" \+ "name": "x", \+ "description": "hi" } /) end end when_the_chef_server "has an environment with a different value" do before do environment "x", { "description" => "lo" } end it "knife diff reports the difference", :skip => (RUBY_VERSION < "1.9") do knife("diff /environments/x.json").should_succeed(/ { "name": "x", - "description": "lo" \+ "description": "hi" } /) end end end end when_the_chef_server "has an environment" do before { environment "x", {} } when_the_repository "has an environment with bad JSON" do before { file "environments/x.json", "{" } it "knife diff reports an error and does a textual diff" do error_text = "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF" error_match = Regexp.new(Regexp.escape(error_text)) knife("diff /environments/x.json").should_succeed(/- "name": "x"/, :stderr => error_match) end end end end # without versioned cookbooks end chef-12.14.60/spec/integration/knife/download_spec.rb000066400000000000000000001267641276456504500224270ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/knife/download" require "chef/knife/diff" describe "knife download", :workstation do include IntegrationSupport include KnifeSupport context "without versioned cookbooks" do when_the_chef_server "has one of each thing" do before do client "x", {} cookbook "x", "1.0.0" data_bag "x", { "y" => {} } environment "x", {} node "x", {} role "x", {} user "x", {} end when_the_repository "has only top-level directories" do before do directory "clients" directory "cookbooks" directory "data_bags" directory "environments" directory "nodes" directory "roles" directory "users" end it "knife download downloads everything" do knife("download /").should_succeed < true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") file "data_bags/x/y.json", {} file "environments/_default.json", { "description" => "The default Chef environment" } file "environments/x.json", {} file "nodes/x.json", { "normal" => { "tags" => [] } } file "roles/x.json", {} file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife download makes no changes" do knife("download /").should_succeed "" knife("diff --name-status /").should_succeed "" end it "knife download --purge makes no changes" do knife("download --purge /").should_succeed "" knife("diff --name-status /").should_succeed "" end context "except the role file" do before do file "roles/x.json", < ChefZero::PUBLIC_KEY } file "cookbooks/x/blah.rb", "" file "cookbooks/y/metadata.rb", cb_metadata("x", "1.0.0") file "data_bags/x/z.json", {} file "data_bags/y/zz.json", {} file "environments/y.json", {} file "nodes/y.json", {} file "roles/y.json", {} file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife download does nothing" do knife("download /").should_succeed "" knife("diff --name-status /").should_succeed < /USAGE/ end end end end # Test download of an item when the other end doesn't even have the container when_the_repository "is empty" do when_the_chef_server "has two data bag items" do before do data_bag "x", { "y" => {}, "z" => {} } end it "knife download of one data bag item itself succeeds" do knife("download /data_bags/x/y.json").should_succeed < {}, "modified" => { "foo" => "bar" }, "unmodified" => {}, } end it "knife download of the modified file succeeds" do knife("download /data_bags/x/modified.json").should_succeed < /USAGE/ end it "knife download --purge . downloads everything" do knife("download --purge .").should_succeed < cb_metadata("x", "1.0.0", "#extra content"), "y.rb" => "hi" } end it "knife download of a modified file succeeds" do knife("download /cookbooks/x/metadata.rb").should_succeed "Updated /cookbooks/x/metadata.rb\n" knife("diff --name-status /cookbooks").should_succeed < "" } cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } end it "knife download /cookbooks/x downloads the latest version" do knife("download --purge /cookbooks/x").should_succeed < "" } cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } end it "knife download /cookbooks/x downloads the updated file" do knife("download --purge /cookbooks/x").should_succeed < "hi" } end it "knife download /cookbooks/x downloads the latest version" do knife("download --purge /cookbooks/x").should_succeed < "hi" } end it "knife download /cookbooks/x downloads the old version" do knife("download --purge /cookbooks/x").should_succeed < "WARNING: /roles/x.rb cannot be updated (can't safely update ruby files).\n" knife("diff --name-status /roles/x.json").should_succeed "M\t/roles/x.rb\n" end end end when_the_chef_server "has an environment" do before do environment "x", {} end when_the_repository "has an environment with bad JSON" do before do file "environments/x.json", "{" end it "knife download succeeds" do warning = <<-EOH WARN: Parse error reading #{path_to('environments/x.json')} as JSON: parse error: premature EOF { (right here) ------^ EOH knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n", :stderr => warning knife("diff --name-status /environments/x.json").should_succeed "" end end when_the_repository "has the same environment with the wrong name in the file" do before do file "environments/x.json", { "name" => "y" } end it "knife download succeeds" do knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n" knife("diff --name-status /environments/x.json").should_succeed "" end end when_the_repository "has the same environment with no name in the file" do before do file "environments/x.json", { "description" => "hi" } end it "knife download succeeds" do knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n" knife("diff --name-status /environments/x.json").should_succeed "" end end end end # without versioned cookbooks with_versioned_cookbooks do when_the_chef_server "has one of each thing" do before do client "x", {} cookbook "x", "1.0.0" data_bag "x", { "y" => {} } environment "x", {} node "x", {} role "x", {} user "x", {} end when_the_repository "has only top-level directories" do before do directory "clients" directory "cookbooks" directory "data_bags" directory "environments" directory "nodes" directory "roles" directory "users" end it "knife download downloads everything" do knife("download /").should_succeed < true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") file "data_bags/x/y.json", {} file "environments/_default.json", { "description" => "The default Chef environment" } file "environments/x.json", {} file "nodes/x.json", { "normal" => { "tags" => [] } } file "roles/x.json", {} file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife download makes no changes" do knife("download /").should_succeed "" knife("diff --name-status /").should_succeed "" end it "knife download --purge makes no changes" do knife("download --purge /").should_succeed "" knife("diff --name-status /").should_succeed "" end context "except the role file" do before do file "roles/x.json", { "description" => "blarghle" } end it "knife download changes the role" do knife("download /").should_succeed "Updated /roles/x.json\n" knife("diff --name-status /").should_succeed "" end end context "except the role file is textually different, but not ACTUALLY different" do before do file "roles/x.json", < ChefZero::PUBLIC_KEY } file "cookbooks/x-1.0.0/blah.rb", "" file "cookbooks/x-2.0.0/metadata.rb", 'version "2.0.0"' file "cookbooks/y-1.0.0/metadata.rb", 'version "1.0.0"' file "data_bags/x/z.json", {} file "data_bags/y/zz.json", {} file "environments/y.json", {} file "nodes/y.json", {} file "roles/y.json", {} file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife download does nothing" do knife("download /").should_succeed "" knife("diff --name-status /").should_succeed < /USAGE/ end end end end # Test download of an item when the other end doesn't even have the container when_the_repository "is empty" do when_the_chef_server "has two data bag items" do before do data_bag "x", { "y" => {}, "z" => {} } end it "knife download of one data bag item itself succeeds" do knife("download /data_bags/x/y.json").should_succeed < {}, "modified" => { "foo" => "bar" }, "unmodified" => {}, } end it "knife download of the modified file succeeds" do knife("download /data_bags/x/modified.json").should_succeed < /USAGE/ end it "knife download --purge . downloads everything" do knife("download --purge .").should_succeed < "hi" } end it "knife download of a modified file succeeds" do knife("download /cookbooks/x-1.0.0/metadata.rb").should_succeed "Updated /cookbooks/x-1.0.0/metadata.rb\n" knife("diff --name-status /cookbooks").should_succeed < "" } cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } end it "knife download /cookbooks/x downloads the latest version" do knife("download --purge /cookbooks").should_succeed < "" } cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } end it "knife download /cookbooks downloads the updated file" do knife("download --purge /cookbooks").should_succeed < "hi" } end it "knife download /cookbooks/x downloads the latest version" do knife("download --purge /cookbooks").should_succeed < "hi" } end it "knife download --purge /cookbooks downloads the old version and deletes the new version" do knife("download --purge /cookbooks").should_succeed < "y" } end it "knife download succeeds" do knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n" knife("diff --name-status /environments/x.json").should_succeed "" end end when_the_repository "has the same environment with no name in the file" do before do file "environments/x.json", { "description" => "hi" } end it "knife download succeeds" do knife("download /environments/x.json").should_succeed "Updated /environments/x.json\n" knife("diff --name-status /environments/x.json").should_succeed "" end end end end # with versioned cookbooks when_the_chef_server "has a cookbook" do before do cookbook "x", "1.0.0" end when_the_repository "is empty" do it "knife download /cookbooks/x signs all requests" do # Check that BasicClient.request() always gets called with X-OPS-USERID original_new = Chef::HTTP::BasicClient.method(:new) expect(Chef::HTTP::BasicClient).to receive(:new) { |args| new_result = original_new.call(*args) original_request = new_result.method(:request) expect(new_result).to receive(:request) { |method, url, body, headers, &response_handler| expect(headers["X-OPS-USERID"]).not_to be_nil original_request.call(method, url, body, headers, &response_handler) }.at_least(:once) new_result }.at_least(:once) knife("download /cookbooks/x").should_succeed < false, :single_org => false do before do user "foo", {} user "bar", {} user "foobar", {} organization "foo", { "full_name" => "Something" } end before :each do Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo") end when_the_repository "has all the default stuff" do before do knife("download /").should_succeed < cb_metadata("x", "1.0.0") } container "x", {} data_bag "x", { "y" => {} } environment "x", {} group "x", {} org_invite "foo" org_member "bar" node "x", {} policy "x", "1.0.0", {} policy "blah", "1.0.0", {} policy_group "x", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" }, }, } role "x", {} end before do knife("download /acls /groups/clients.json /groups/users.json").should_succeed <<-EOM Created /acls/clients/x.json Created /acls/containers/x.json Created /acls/cookbook_artifacts/x.json Created /acls/cookbooks/x.json Created /acls/data_bags/x.json Created /acls/environments/x.json Created /acls/groups/x.json Created /acls/nodes/x.json Created /acls/policies/blah.json Created /acls/policies/x.json Created /acls/policy_groups/x.json Created /acls/roles/x.json Updated /groups/clients.json Updated /groups/users.json EOM end it "knife download / downloads everything" do knife("download /").should_succeed < ChefZero::PUBLIC_KEY } file "containers/x.json", {} file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.0") file "data_bags/x/y.json", {} file "environments/x.json", {} file "groups/x.json", {} file "invitations.json", [ "foo" ] file "members.json", [ "bar" ] file "nodes/x.json", { "normal" => { "tags" => [] } } file "org.json", { "full_name" => "Something" } file "policies/x-1.0.0.json", {} file "policies/blah-1.0.0.json", {} file "policy_groups/x.json", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" } } } file "roles/x.json", {} end it "knife download makes no changes" do knife("download /").should_succeed "" end end context "and the repository has a slightly different copy of each thing" do before do # acl_for %w(organizations foo groups blah) file "clients/x.json", { "validator" => true } file "containers/x.json", {} file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.1") file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.1") file "data_bags/x/y.json", { "a" => "b" } file "environments/x.json", { "description" => "foo" } file "groups/x.json", { "description" => "foo" } file "groups/x.json", { "groups" => [ "admin" ] } file "nodes/x.json", { "normal" => { "tags" => [] }, "run_list" => [ "blah" ] } file "org.json", { "full_name" => "Something Else " } file "policies/x-1.0.0.json", { "run_list" => [ "blah" ] } file "policy_groups/x.json", { "policies" => { "x" => { "revision_id" => "1.0.1" }, "y" => { "revision_id" => "1.0.0" }, }, } file "roles/x.json", { "run_list" => [ "blah" ] } end it "knife download updates everything" do knife("download /").should_succeed < { "blah" => "= 1.0.0", "krad" => ">= 1.0.0", }, } environment "y", { "cookbook_versions" => { "blah" => "= 1.1.0", "krad" => ">= 1.0.0", }, } end # rubocop:disable Style/TrailingWhitespace it "displays the cookbooks for a single environment" do knife("environment compare x").should_succeed <= 1.0.0 EOM end it "compares the cookbooks for two environments" do knife("environment compare x y").should_succeed <= 1.0.0 >= 1.0.0 EOM end it "compares the cookbooks for all environments" do knife("environment compare --all").should_succeed <= 1.0.0 >= 1.0.0 EOM end # rubocop:enable Style/TrailingWhitespace end end chef-12.14.60/spec/integration/knife/environment_create_spec.rb000066400000000000000000000025351276456504500244740ustar00rootroot00000000000000# # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" describe "knife environment create", :workstation do include IntegrationSupport include KnifeSupport include_context "default config options" let(:out) { "Created bah\n" } when_the_chef_server "is empty" do it "creates a new environment" do knife("environment create bah").should_succeed out end it "refuses to add an existing environment" do pending "Knife environment create must not blindly overwrite an existing environment" knife("environment create bah").should_succeed out expect { knife("environment create bah") }.to raise_error(Net::HTTPServerException) end end end chef-12.14.60/spec/integration/knife/environment_delete_spec.rb000066400000000000000000000022031276456504500244630ustar00rootroot00000000000000# # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" describe "knife environment delete", :workstation do include IntegrationSupport include KnifeSupport include_context "default config options" when_the_chef_server "has an environment" do before do environment "y", {} end it "deletes an environment" do knife("environment delete y", input: "y").should_succeed "Do you really want to delete y? (Y/N) Deleted y\n" end end end chef-12.14.60/spec/integration/knife/environment_from_file_spec.rb000066400000000000000000000050571276456504500251750ustar00rootroot00000000000000# # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" describe "knife environment from file", :workstation do include IntegrationSupport include KnifeSupport # include_context "default config options" let (:env_dir) { "#{@repository_dir}/environments" } when_the_chef_server "is empty" do when_the_repository "has some environments" do before do file "environments/cons.json", < { "foo" => "bar" }, } end # rubocop:disable Style/TrailingWhitespace it "shows an environment" do knife("environment show b").should_succeed <) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" require "chef/knife/list" describe "knife list", :workstation do include IntegrationSupport include KnifeSupport include_context "default config options" when_the_chef_server "is empty" do it "knife list / returns all top level directories" do knife("list /").should_succeed <<-EOM /clients /cookbooks /data_bags /environments /nodes /roles /users EOM end it "knife list -R / returns everything" do knife("list -R /").should_succeed <<-EOM /: clients cookbooks data_bags environments nodes roles users /clients: chef-validator.json chef-webui.json /cookbooks: /data_bags: /environments: _default.json /nodes: /roles: /users: admin.json EOM end end when_the_chef_server "has plenty of stuff in it" do before do client "client1", {} client "client2", {} cookbook "cookbook1", "1.0.0" cookbook "cookbook2", "1.0.1", { "recipes" => { "default.rb" => "" } } data_bag "bag1", { "item1" => {}, "item2" => {} } data_bag "bag2", { "item1" => {}, "item2" => {} } environment "environment1", {} environment "environment2", {} node "node1", {} node "node2", {} policy "policy1", "1.2.3", {} policy "policy2", "1.2.3", {} policy "policy2", "1.3.5", {} role "role1", {} role "role2", {} user "user1", {} user "user2", {} end it "knife list / returns all top level directories" do knife("list /").should_succeed <<-EOM /clients /cookbooks /data_bags /environments /nodes /roles /users EOM end it "knife list -R / returns everything" do knife("list -R /").should_succeed <<-EOM /: clients cookbooks data_bags environments nodes roles users /clients: chef-validator.json chef-webui.json client1.json client2.json /cookbooks: cookbook1 cookbook2 /cookbooks/cookbook1: metadata.rb /cookbooks/cookbook2: metadata.rb recipes /cookbooks/cookbook2/recipes: default.rb /data_bags: bag1 bag2 /data_bags/bag1: item1.json item2.json /data_bags/bag2: item1.json item2.json /environments: _default.json environment1.json environment2.json /nodes: node1.json node2.json /roles: role1.json role2.json /users: admin.json user1.json user2.json EOM end it "knife list -R --flat / returns everything" do knife("list -R --flat /").should_succeed <<-EOM /clients /clients/chef-validator.json /clients/chef-webui.json /clients/client1.json /clients/client2.json /cookbooks /cookbooks/cookbook1 /cookbooks/cookbook1/metadata.rb /cookbooks/cookbook2 /cookbooks/cookbook2/metadata.rb /cookbooks/cookbook2/recipes /cookbooks/cookbook2/recipes/default.rb /data_bags /data_bags/bag1 /data_bags/bag1/item1.json /data_bags/bag1/item2.json /data_bags/bag2 /data_bags/bag2/item1.json /data_bags/bag2/item2.json /environments /environments/_default.json /environments/environment1.json /environments/environment2.json /nodes /nodes/node1.json /nodes/node2.json /roles /roles/role1.json /roles/role2.json /users /users/admin.json /users/user1.json /users/user2.json EOM end it "knife list -Rfp / returns everything" do knife("list -Rfp /").should_succeed <<-EOM /clients/ /clients/chef-validator.json /clients/chef-webui.json /clients/client1.json /clients/client2.json /cookbooks/ /cookbooks/cookbook1/ /cookbooks/cookbook1/metadata.rb /cookbooks/cookbook2/ /cookbooks/cookbook2/metadata.rb /cookbooks/cookbook2/recipes/ /cookbooks/cookbook2/recipes/default.rb /data_bags/ /data_bags/bag1/ /data_bags/bag1/item1.json /data_bags/bag1/item2.json /data_bags/bag2/ /data_bags/bag2/item1.json /data_bags/bag2/item2.json /environments/ /environments/_default.json /environments/environment1.json /environments/environment2.json /nodes/ /nodes/node1.json /nodes/node2.json /roles/ /roles/role1.json /roles/role2.json /users/ /users/admin.json /users/user1.json /users/user2.json EOM end it "knife list /cookbooks returns the list of cookbooks" do knife("list /cookbooks").should_succeed <<-EOM /cookbooks/cookbook1 /cookbooks/cookbook2 EOM end it "knife list /cookbooks/*2/*/*.rb returns the one file" do knife("list /cookbooks/*2/*/*.rb").should_succeed "/cookbooks/cookbook2/recipes/default.rb\n" end it "knife list /**.rb returns all ruby files" do knife("list /**.rb").should_succeed <<-EOM /cookbooks/cookbook1/metadata.rb /cookbooks/cookbook2/metadata.rb /cookbooks/cookbook2/recipes/default.rb EOM end it "knife list /cookbooks/**.rb returns all ruby files" do knife("list /cookbooks/**.rb").should_succeed <<-EOM /cookbooks/cookbook1/metadata.rb /cookbooks/cookbook2/metadata.rb /cookbooks/cookbook2/recipes/default.rb EOM end it "knife list /**.json returns all json files" do knife("list /**.json").should_succeed <<-EOM /clients/chef-validator.json /clients/chef-webui.json /clients/client1.json /clients/client2.json /data_bags/bag1/item1.json /data_bags/bag1/item2.json /data_bags/bag2/item1.json /data_bags/bag2/item2.json /environments/_default.json /environments/environment1.json /environments/environment2.json /nodes/node1.json /nodes/node2.json /roles/role1.json /roles/role2.json /users/admin.json /users/user1.json /users/user2.json EOM end it "knife list /data**.json returns all data bag json files" do knife("list /data**.json").should_succeed <<-EOM /data_bags/bag1/item1.json /data_bags/bag1/item2.json /data_bags/bag2/item1.json /data_bags/bag2/item2.json EOM end it "knife list /environments/missing_file.json reports missing file" do knife("list /environments/missing_file.json").should_fail "ERROR: /environments/missing_file.json: No such file or directory\n" end context "missing file/directory exact match tests" do it "knife list /blarghle reports missing directory" do knife("list /blarghle").should_fail "ERROR: /blarghle: No such file or directory\n" end end context "symlink tests" do when_the_repository "is empty" do context "when cwd is at the top of the repository" do before { cwd "." } it "knife list -Rfp returns everything" do knife("list -Rfp").should_succeed <<-EOM clients/ clients/chef-validator.json clients/chef-webui.json clients/client1.json clients/client2.json cookbooks/ cookbooks/cookbook1/ cookbooks/cookbook1/metadata.rb cookbooks/cookbook2/ cookbooks/cookbook2/metadata.rb cookbooks/cookbook2/recipes/ cookbooks/cookbook2/recipes/default.rb data_bags/ data_bags/bag1/ data_bags/bag1/item1.json data_bags/bag1/item2.json data_bags/bag2/ data_bags/bag2/item1.json data_bags/bag2/item2.json environments/ environments/_default.json environments/environment1.json environments/environment2.json nodes/ nodes/node1.json nodes/node2.json roles/ roles/role1.json roles/role2.json users/ users/admin.json users/user1.json users/user2.json EOM end end end when_the_repository "has a cookbooks directory" do before { directory "cookbooks" } context "when cwd is in cookbooks/" do before { cwd "cookbooks" } it "knife list -Rfp / returns everything" do knife("list -Rfp /").should_succeed <<-EOM /clients/ /clients/chef-validator.json /clients/chef-webui.json /clients/client1.json /clients/client2.json ./ cookbook1/ cookbook1/metadata.rb cookbook2/ cookbook2/metadata.rb cookbook2/recipes/ cookbook2/recipes/default.rb /data_bags/ /data_bags/bag1/ /data_bags/bag1/item1.json /data_bags/bag1/item2.json /data_bags/bag2/ /data_bags/bag2/item1.json /data_bags/bag2/item2.json /environments/ /environments/_default.json /environments/environment1.json /environments/environment2.json /nodes/ /nodes/node1.json /nodes/node2.json /roles/ /roles/role1.json /roles/role2.json /users/ /users/admin.json /users/user1.json /users/user2.json EOM end it "knife list -Rfp .. returns everything" do knife("list -Rfp ..").should_succeed <<-EOM /clients/ /clients/chef-validator.json /clients/chef-webui.json /clients/client1.json /clients/client2.json ./ cookbook1/ cookbook1/metadata.rb cookbook2/ cookbook2/metadata.rb cookbook2/recipes/ cookbook2/recipes/default.rb /data_bags/ /data_bags/bag1/ /data_bags/bag1/item1.json /data_bags/bag1/item2.json /data_bags/bag2/ /data_bags/bag2/item1.json /data_bags/bag2/item2.json /environments/ /environments/_default.json /environments/environment1.json /environments/environment2.json /nodes/ /nodes/node1.json /nodes/node2.json /roles/ /roles/role1.json /roles/role2.json /users/ /users/admin.json /users/user1.json /users/user2.json EOM end it "knife list -Rfp returns cookbooks" do knife("list -Rfp").should_succeed <<-EOM cookbook1/ cookbook1/metadata.rb cookbook2/ cookbook2/metadata.rb cookbook2/recipes/ cookbook2/recipes/default.rb EOM end end end when_the_repository "has a cookbooks/cookbook2 directory" do before { directory "cookbooks/cookbook2" } context "when cwd is in cookbooks/cookbook2" do before { cwd "cookbooks/cookbook2" } it "knife list -Rfp returns cookbooks" do knife("list -Rfp").should_succeed <<-EOM metadata.rb recipes/ recipes/default.rb EOM end end end when_the_repository "has a cookbooks directory and a symlinked cookbooks directory", :skip => (Chef::Platform.windows?) do before do directory "cookbooks" symlink "symlinked", "cookbooks" end context "when cwd is in cookbooks/" do before { cwd "cookbooks" } it "knife list -Rfp returns cookbooks" do knife("list -Rfp").should_succeed <<-EOM cookbook1/ cookbook1/metadata.rb cookbook2/ cookbook2/metadata.rb cookbook2/recipes/ cookbook2/recipes/default.rb EOM end end context "when cwd is in symlinked/" do before { cwd "symlinked" } it "knife list -Rfp returns cookbooks" do knife("list -Rfp").should_succeed <<-EOM cookbook1/ cookbook1/metadata.rb cookbook2/ cookbook2/metadata.rb cookbook2/recipes/ cookbook2/recipes/default.rb EOM end end end when_the_repository "has a real_cookbooks directory and a cookbooks symlink to it", :skip => (Chef::Platform.windows?) do before do directory "real_cookbooks" symlink "cookbooks", "real_cookbooks" end context "when cwd is in real_cookbooks/" do before { cwd "real_cookbooks" } it "knife list -Rfp returns cookbooks" do knife("list -Rfp").should_succeed <<-EOM cookbook1/ cookbook1/metadata.rb cookbook2/ cookbook2/metadata.rb cookbook2/recipes/ cookbook2/recipes/default.rb EOM end end context "when cwd is in cookbooks/" do before { cwd "cookbooks" } it "knife list -Rfp returns cookbooks" do knife("list -Rfp").should_succeed <<-EOM cookbook1/ cookbook1/metadata.rb cookbook2/ cookbook2/metadata.rb cookbook2/recipes/ cookbook2/recipes/default.rb EOM end end end end end context "--local" do when_the_repository "is empty" do it "knife list --local / returns nothing" do knife("list --local /").should_succeed "" end it "knife list /roles returns nothing" do knife("list --local /roles").should_fail "ERROR: /roles: No such file or directory\n" end end when_the_repository "has a bunch of stuff" do before do file "clients/client1.json", {} file "clients/client2.json", {} directory "cookbooks/cookbook1" do file "metadata.rb", cb_metadata("cookbook1", "1.0.0") end directory "cookbooks/cookbook2" do file "metadata.rb", cb_metadata("cookbook2", "2.0.0") file "recipes/default.rb", "" end directory "data_bags" do directory "bag1" do file "item1.json", {} file "item2.json", {} end directory "bag2" do file "item1.json", {} file "item2.json", {} end end file "environments/environment1.json", {} file "environments/environment2.json", {} file "nodes/node1.json", {} file "nodes/node2.json", {} file "roles/role1.json", {} file "roles/role2.json", {} file "users/user1.json", {} file "users/user2.json", {} end it "knife list -Rfp / returns everything" do knife("list -Rp --local --flat /").should_succeed <<-EOM /clients/ /clients/client1.json /clients/client2.json /cookbooks/ /cookbooks/cookbook1/ /cookbooks/cookbook1/metadata.rb /cookbooks/cookbook2/ /cookbooks/cookbook2/metadata.rb /cookbooks/cookbook2/recipes/ /cookbooks/cookbook2/recipes/default.rb /data_bags/ /data_bags/bag1/ /data_bags/bag1/item1.json /data_bags/bag1/item2.json /data_bags/bag2/ /data_bags/bag2/item1.json /data_bags/bag2/item2.json /environments/ /environments/environment1.json /environments/environment2.json /nodes/ /nodes/node1.json /nodes/node2.json /roles/ /roles/role1.json /roles/role2.json /users/ /users/user1.json /users/user2.json EOM end context "missing file/directory tests" do it "knife list --local /blarghle reports missing directory" do knife("list --local /blarghle").should_fail "ERROR: /blarghle: No such file or directory\n" end it "knife list /roles/blarghle reports missing directory" do knife("list --local /roles/blarghle").should_fail "ERROR: /roles/blarghle: No such file or directory\n" end it "knife list /roles/blarghle/blorghle reports missing directory" do knife("list --local /roles/blarghle/blorghle").should_fail "ERROR: /roles/blarghle/blorghle: No such file or directory\n" end end end end when_the_chef_server "is in Enterprise mode", :osc_compat => false, :single_org => false do before do organization "foo" end before :each do Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo") end context "and is empty" do it "knife list / returns all top level directories" do knife("list /").should_succeed <<-EOM /acls /clients /containers /cookbook_artifacts /cookbooks /data_bags /environments /groups /invitations.json /members.json /nodes /org.json /policies /policy_groups /roles EOM end it "knife list -R / returns everything" do knife("list -R /").should_succeed <<-EOM /: acls clients containers cookbook_artifacts cookbooks data_bags environments groups invitations.json members.json nodes org.json policies policy_groups roles /acls: clients containers cookbook_artifacts cookbooks data_bags environments groups nodes organization.json policies policy_groups roles /acls/clients: foo-validator.json /acls/containers: clients.json containers.json cookbook_artifacts.json cookbooks.json data.json environments.json groups.json nodes.json policies.json policy_groups.json roles.json sandboxes.json /acls/cookbook_artifacts: /acls/cookbooks: /acls/data_bags: /acls/environments: _default.json /acls/groups: admins.json billing-admins.json clients.json users.json /acls/nodes: /acls/policies: /acls/policy_groups: /acls/roles: /clients: foo-validator.json /containers: clients.json containers.json cookbook_artifacts.json cookbooks.json data.json environments.json groups.json nodes.json policies.json policy_groups.json roles.json sandboxes.json /cookbook_artifacts: /cookbooks: /data_bags: /environments: _default.json /groups: admins.json billing-admins.json clients.json users.json /nodes: /policies: /policy_groups: /roles: EOM end end it "knife list -R / returns everything" do knife("list -R /").should_succeed <<-EOM /: acls clients containers cookbook_artifacts cookbooks data_bags environments groups invitations.json members.json nodes org.json policies policy_groups roles /acls: clients containers cookbook_artifacts cookbooks data_bags environments groups nodes organization.json policies policy_groups roles /acls/clients: foo-validator.json /acls/containers: clients.json containers.json cookbook_artifacts.json cookbooks.json data.json environments.json groups.json nodes.json policies.json policy_groups.json roles.json sandboxes.json /acls/cookbook_artifacts: /acls/cookbooks: /acls/data_bags: /acls/environments: _default.json /acls/groups: admins.json billing-admins.json clients.json users.json /acls/nodes: /acls/policies: /acls/policy_groups: /acls/roles: /clients: foo-validator.json /containers: clients.json containers.json cookbook_artifacts.json cookbooks.json data.json environments.json groups.json nodes.json policies.json policy_groups.json roles.json sandboxes.json /cookbook_artifacts: /cookbooks: /data_bags: /environments: _default.json /groups: admins.json billing-admins.json clients.json users.json /nodes: /policies: /policy_groups: /roles: EOM end context "has plenty of stuff in it" do before do client "client1", {} client "client2", {} container "container1", {} container "container2", {} cookbook "cookbook1", "1.0.0" cookbook "cookbook2", "1.0.1", { "recipes" => { "default.rb" => "" } } cookbook_artifact "cookbook_artifact1", "1x1" cookbook_artifact "cookbook_artifact2", "2x2", { "recipes" => { "default.rb" => "" } } data_bag "bag1", { "item1" => {}, "item2" => {} } data_bag "bag2", { "item1" => {}, "item2" => {} } environment "environment1", {} environment "environment2", {} group "group1", {} group "group2", {} node "node1", {} node "node2", {} org_invite "user1" org_member "user2" policy "policy1", "1.2.3", {} policy "policy2", "1.2.3", {} policy "policy2", "1.3.5", {} policy_group "policy_group1", { "policies" => { "policy1" => { "revision_id" => "1.2.3" } } } policy_group "policy_group2", { "policies" => { "policy2" => { "revision_id" => "1.3.5" } } } role "role1", {} role "role2", {} user "user1", {} user "user2", {} end it "knife list -Rfp / returns everything" do knife("list -Rfp /").should_succeed <<-EOM /acls/ /acls/clients/ /acls/clients/client1.json /acls/clients/client2.json /acls/clients/foo-validator.json /acls/containers/ /acls/containers/clients.json /acls/containers/container1.json /acls/containers/container2.json /acls/containers/containers.json /acls/containers/cookbook_artifacts.json /acls/containers/cookbooks.json /acls/containers/data.json /acls/containers/environments.json /acls/containers/groups.json /acls/containers/nodes.json /acls/containers/policies.json /acls/containers/policy_groups.json /acls/containers/roles.json /acls/containers/sandboxes.json /acls/cookbook_artifacts/ /acls/cookbook_artifacts/cookbook_artifact1.json /acls/cookbook_artifacts/cookbook_artifact2.json /acls/cookbooks/ /acls/cookbooks/cookbook1.json /acls/cookbooks/cookbook2.json /acls/data_bags/ /acls/data_bags/bag1.json /acls/data_bags/bag2.json /acls/environments/ /acls/environments/_default.json /acls/environments/environment1.json /acls/environments/environment2.json /acls/groups/ /acls/groups/admins.json /acls/groups/billing-admins.json /acls/groups/clients.json /acls/groups/group1.json /acls/groups/group2.json /acls/groups/users.json /acls/nodes/ /acls/nodes/node1.json /acls/nodes/node2.json /acls/organization.json /acls/policies/ /acls/policies/policy1.json /acls/policies/policy2.json /acls/policy_groups/ /acls/policy_groups/policy_group1.json /acls/policy_groups/policy_group2.json /acls/roles/ /acls/roles/role1.json /acls/roles/role2.json /clients/ /clients/client1.json /clients/client2.json /clients/foo-validator.json /containers/ /containers/clients.json /containers/container1.json /containers/container2.json /containers/containers.json /containers/cookbook_artifacts.json /containers/cookbooks.json /containers/data.json /containers/environments.json /containers/groups.json /containers/nodes.json /containers/policies.json /containers/policy_groups.json /containers/roles.json /containers/sandboxes.json /cookbook_artifacts/ /cookbook_artifacts/cookbook_artifact1-1x1/ /cookbook_artifacts/cookbook_artifact1-1x1/metadata.rb /cookbook_artifacts/cookbook_artifact2-2x2/ /cookbook_artifacts/cookbook_artifact2-2x2/metadata.rb /cookbook_artifacts/cookbook_artifact2-2x2/recipes/ /cookbook_artifacts/cookbook_artifact2-2x2/recipes/default.rb /cookbooks/ /cookbooks/cookbook1/ /cookbooks/cookbook1/metadata.rb /cookbooks/cookbook2/ /cookbooks/cookbook2/metadata.rb /cookbooks/cookbook2/recipes/ /cookbooks/cookbook2/recipes/default.rb /data_bags/ /data_bags/bag1/ /data_bags/bag1/item1.json /data_bags/bag1/item2.json /data_bags/bag2/ /data_bags/bag2/item1.json /data_bags/bag2/item2.json /environments/ /environments/_default.json /environments/environment1.json /environments/environment2.json /groups/ /groups/admins.json /groups/billing-admins.json /groups/clients.json /groups/group1.json /groups/group2.json /groups/users.json /invitations.json /members.json /nodes/ /nodes/node1.json /nodes/node2.json /org.json /policies/ /policies/policy1-1.2.3.json /policies/policy2-1.2.3.json /policies/policy2-1.3.5.json /policy_groups/ /policy_groups/policy_group1.json /policy_groups/policy_group2.json /roles/ /roles/role1.json /roles/role2.json EOM end end end end chef-12.14.60/spec/integration/knife/node_bulk_delete_spec.rb000066400000000000000000000025251276456504500240700ustar00rootroot00000000000000# # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" describe "knife node bulk delete", :workstation do include IntegrationSupport include KnifeSupport include_context "default config options" when_the_chef_server "has some nodes" do before do node "cons", {} node "car", {} node "cdr", {} node "cat", {} end it "deletes all matching nodes" do knife("node bulk delete ^ca.*", input: "Y").should_succeed <) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" require "chef/knife/raw" require "chef/knife/show" describe "knife raw", :workstation do include IntegrationSupport include KnifeSupport include AppServerSupport include_context "default config options" when_the_chef_server "has one of each thing" do before do client "x", "{}" cookbook "x", "1.0.0" data_bag "x", { "y" => "{}" } environment "x", "{}" node "x", "{}" role "x", "{}" user "x", "{}" end it "knife raw /nodes/x returns the node", :skip => (RUBY_VERSION < "1.9") do knife("raw /nodes/x").should_succeed < (RUBY_VERSION < "1.9") do knife("raw -m DELETE /roles/x").should_succeed < (RUBY_VERSION < "1.9") do Tempfile.open("raw_put_input") do |file| file.write < (RUBY_VERSION < "1.9") do Tempfile.open("raw_put_input") do |file| file.write < "application/json" }, ['{ "x": "y", "a": "b" }'] ] end @raw_server, @raw_server_thread = start_app_server(app, 9018) end after :each do @raw_server.shutdown if @raw_server @raw_server_thread.kill if @raw_server_thread end it "knife raw /blah returns the prettified json", :skip => (RUBY_VERSION < "1.9") do knife("raw /blah").should_succeed < "text" }, ['{ "x": "y", "a": "b" }'] ] end @raw_server, @raw_server_thread = start_app_server(app, 9018) end after :each do @raw_server.shutdown if @raw_server @raw_server_thread.kill if @raw_server_thread end it "knife raw /blah returns the raw text" do knife("raw /blah").should_succeed(<) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" require "chef/knife/list" describe "redirection", :workstation do include IntegrationSupport include KnifeSupport include AppServerSupport include_context "default config options" when_the_chef_server "has a role" do before { role "x", {} } context "and another server redirects to it with 302" do before :each do real_chef_server_url = Chef::Config.chef_server_url Chef::Config.chef_server_url = "http://localhost:9018" app = lambda do |env| [302, { "Content-Type" => "text", "Location" => "#{real_chef_server_url}#{env['PATH_INFO']}" }, ["302 found"] ] end @redirector_server, @redirector_server_thread = start_app_server(app, 9018) end after :each do @redirector_server.shutdown if @redirector_server @redirector_thread.kill if @redirector_thread end it "knife list /roles returns the role" do knife("list /roles").should_succeed "/roles/x.json\n" end end end end chef-12.14.60/spec/integration/knife/role_bulk_delete_spec.rb000066400000000000000000000025251276456504500241040ustar00rootroot00000000000000# # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" describe "knife role bulk delete", :workstation do include IntegrationSupport include KnifeSupport include_context "default config options" when_the_chef_server "has some roles" do before do role "cons", {} role "car", {} role "cdr", {} role "cat", {} end it "deletes all matching roles" do knife("role bulk delete ^ca.*", input: "Y").should_succeed <) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/knife/serve" require "chef/server_api" describe "knife serve", :workstation do include IntegrationSupport include KnifeSupport include AppServerSupport when_the_repository "also has one of each thing" do before { file "nodes/x.json", { "foo" => "bar" } } it "knife serve serves up /nodes/x" do exception = nil t = Thread.new do begin knife("serve --chef-zero-port=8890") rescue exception = $! end end begin Chef::Config.log_level = :debug Chef::Config.chef_server_url = "http://localhost:8890" Chef::Config.node_name = nil Chef::Config.client_key = nil api = Chef::ServerAPI.new expect(api.get("nodes/x")["name"]).to eq("x") rescue if exception raise exception else raise end ensure t.kill end end end end chef-12.14.60/spec/integration/knife/show_spec.rb000066400000000000000000000117631276456504500215700ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "support/shared/context/config" require "chef/knife/show" describe "knife show", :workstation do include IntegrationSupport include KnifeSupport include_context "default config options" when_the_chef_server "has one of each thing" do before do client "x", "{}" cookbook "x", "1.0.0" data_bag "x", { "y" => "{}" } environment "x", "{}" node "x", "{}" role "x", "{}" user "x", "{}" end when_the_repository "also has one of each thing" do before do file "clients/x.json", { "foo" => "bar" } file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") file "data_bags/x/y.json", { "foo" => "bar" } file "environments/_default.json", { "foo" => "bar" } file "environments/x.json", { "foo" => "bar" } file "nodes/x.json", { "foo" => "bar" } file "roles/x.json", { "foo" => "bar" } file "users/x.json", { "foo" => "bar" } end it "knife show /cookbooks/x/metadata.rb shows the remote version" do knife("show /cookbooks/x/metadata.rb").should_succeed < (RUBY_VERSION < "1.9") do knife("show /environments/x.json").should_succeed < (RUBY_VERSION < "1.9") do knife("show /roles/x.json").should_succeed < { "foo" => "bar" }, "cookbook_versions" => { "blah" => "= 1.0.0" }, "override_attributes" => { "x" => "y" }, "description" => "woo", "name" => "x", } end it "knife show shows the attributes in a predetermined order", :skip => (RUBY_VERSION < "1.9") do knife("show /environments/x.json").should_succeed <) # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/knife/upload" require "chef/knife/diff" require "chef/knife/raw" require "chef/json_compat" describe "knife upload", :workstation do include IntegrationSupport include KnifeSupport context "without versioned cookbooks" do when_the_chef_server "has one of each thing" do before do client "x", {} cookbook "x", "1.0.0" data_bag "x", { "y" => {} } environment "x", {} node "x", {} role "x", {} user "x", {} end when_the_repository "has only top-level directories" do before do directory "clients" directory "cookbooks" directory "data_bags" directory "environments" directory "nodes" directory "roles" directory "users" end it "knife upload does nothing" do knife("upload /").should_succeed "" knife("diff --name-status /").should_succeed < "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n") Deleted extra entry /clients/chef-validator.json (purge is on) Deleted extra entry /clients/chef-webui.json (purge is on) Deleted extra entry /clients/x.json (purge is on) Deleted extra entry /cookbooks/x (purge is on) Deleted extra entry /data_bags/x (purge is on) Deleted extra entry /environments/x.json (purge is on) Deleted extra entry /nodes/x.json (purge is on) Deleted extra entry /roles/x.json (purge is on) Deleted extra entry /users/admin.json (purge is on) Deleted extra entry /users/x.json (purge is on) EOM knife("diff --name-status /").should_succeed < true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") file "data_bags/x/y.json", {} file "environments/_default.json", { "description" => "The default Chef environment" } file "environments/x.json", {} file "nodes/x.json", { "normal" => { "tags" => [] } } file "roles/x.json", {} file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife upload makes no changes" do knife("upload /cookbooks/x").should_succeed "" knife("diff --name-status /").should_succeed "" end it "knife upload --purge makes no changes" do knife("upload --purge /").should_succeed "" knife("diff --name-status /").should_succeed "" end context "except the role file" do before do file "roles/x.json", { "description" => "blarghle" } end it "knife upload changes the role" do knife("upload /").should_succeed "Updated /roles/x.json\n" knife("diff --name-status /").should_succeed "" end it "knife upload --no-diff does not change the role" do knife("upload --no-diff /").should_succeed "" knife("diff --name-status /").should_succeed "M\t/roles/x.json\n" end end context "except the role file is textually different, but not ACTUALLY different" do before do file "roles/x.json", <= 13" do knife("upload /cookbooks").should_fail "" # FIXME: include the error message here end end context "as well as one extra copy of each thing" do before do file "clients/y.json", { "public_key" => ChefZero::PUBLIC_KEY } file "cookbooks/x/blah.rb", "" file "cookbooks/y/metadata.rb", cb_metadata("y", "1.0.0") file "data_bags/x/z.json", {} file "data_bags/y/zz.json", {} file "environments/y.json", {} file "nodes/y.json", {} file "roles/y.json", {} file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife upload adds the new files" do knife("upload /").should_succeed < /USAGE/ end end end end when_the_chef_server "is empty" do when_the_repository "has a data bag item" do before do file "data_bags/x/y.json", { "foo" => "bar" } end it "knife upload of the data bag uploads only the values in the data bag item and no other" do knife("upload /data_bags/x/y.json").should_succeed < false).keys.sort).to eq(%w{foo id}) end it "knife upload /data_bags/x /data_bags/x/y.json uploads x once" do knife("upload /data_bags/x /data_bags/x/y.json").should_succeed < "aaa", "data_bag" => "bbb" } end it "upload preserves chef_type and data_bag" do knife("upload /data_bags/x/y.json").should_succeed < false) expect(result.keys.sort).to eq(%w{chef_type data_bag id}) expect(result["chef_type"]).to eq("aaa") expect(result["data_bag"]).to eq("bbb") end end # Test upload of an item when the other end doesn't even have the container when_the_repository "has two data bag items" do before do file "data_bags/x/y.json", {} file "data_bags/x/z.json", {} end it "knife upload of one data bag item itself succeeds" do knife("upload /data_bags/x/y.json").should_succeed < {}, "modified" => {}, "unmodified" => {} } end when_the_repository "has a modified, unmodified, added and deleted data bag item" do before do file "data_bags/x/added.json", {} file "data_bags/x/modified.json", { "foo" => "bar" } file "data_bags/x/unmodified.json", {} end it "knife upload of the modified file succeeds" do knife("upload /data_bags/x/modified.json").should_succeed < /USAGE/ end it "knife upload --purge . uploads everything" do knife("upload --purge .").should_succeed < "" } end when_the_repository "has a modified, extra and missing file for the cookbook" do before do file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "#modified") file "cookbooks/x/y.rb", "hi" end it "knife upload of any individual file fails" do knife("upload /cookbooks/x/metadata.rb").should_fail "ERROR: /cookbooks/x/metadata.rb cannot be updated.\n" knife("upload /cookbooks/x/y.rb").should_fail "ERROR: /cookbooks/x cannot have a child created under it.\n" knife("upload --purge /cookbooks/x/z.rb").should_fail "ERROR: /cookbooks/x/z.rb cannot be deleted.\n" end # TODO this is a bit of an inconsistency: if we didn't specify --purge, # technically we shouldn't have deleted missing files. But ... cookbooks # are a special case. it "knife upload of the cookbook itself succeeds" do knife("upload /cookbooks/x").should_succeed < true end when_the_repository "has an update to said cookbook" do before do file "cookbooks/frozencook/metadata.rb", cb_metadata("frozencook", "1.0.0", "# This is different") end it "knife upload fails to upload the frozen cookbook" do knife("upload /cookbooks/frozencook").should_fail "ERROR: /cookbooks failed to write: Cookbook frozencook is frozen\n" end it "knife upload --force uploads the frozen cookbook" do knife("upload --force /cookbooks/frozencook").should_succeed < "" } cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } end it "knife upload /cookbooks/x uploads the local version" do knife("diff --name-status /cookbooks").should_succeed < "" } cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } end it "knife upload /cookbooks/x uploads the local version" do knife("upload --purge /cookbooks/x").should_succeed < "hi" } end it "knife upload /cookbooks/x uploads the local version" do knife("diff --name-status /cookbooks").should_succeed < "hi" } end it "knife upload /cookbooks/x uploads the new version" do knife("upload --purge /cookbooks/x").should_succeed < warn) end end when_the_repository "has the same environment with the wrong name in the file" do before do file "environments/x.json", { "name" => "y" } end it "knife upload fails" do knife("upload /environments/x.json").should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n" knife("diff --name-status /environments/x.json").should_succeed "M\t/environments/x.json\n" end end when_the_repository "has the same environment with no name in the file" do before do file "environments/x.json", { "description" => "hi" } end it "knife upload succeeds" do knife("upload /environments/x.json").should_succeed "Updated /environments/x.json\n" knife("diff --name-status /environments/x.json").should_succeed "" end end end when_the_chef_server "is empty" do when_the_repository "has an environment with the wrong name in the file" do before do file "environments/x.json", { "name" => "y" } end it "knife upload fails" do knife("upload /environments/x.json").should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n" knife("diff --name-status /environments/x.json").should_succeed "A\t/environments/x.json\n" end end when_the_repository "has an environment with no name in the file" do before do file "environments/x.json", { "description" => "hi" } end it "knife upload succeeds" do knife("upload /environments/x.json").should_succeed "Created /environments/x.json\n" knife("diff --name-status /environments/x.json").should_succeed "" end end when_the_repository "has a data bag with no id in the file" do before do file "data_bags/bag/x.json", { "foo" => "bar" } end it "knife upload succeeds" do knife("upload /data_bags/bag/x.json").should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n" knife("diff --name-status /data_bags/bag/x.json").should_succeed "" end end end when_the_chef_server "is empty" do when_the_repository "has a cookbook with an invalid chef_version constraint in it" do before do file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'") end it "knife upload succeeds" do knife("upload /cookbooks/x").should_succeed < {} } environment "x", {} node "x", {} role "x", {} user "x", {} end when_the_repository "has only top-level directories" do before do directory "clients" directory "cookbooks" directory "data_bags" directory "environments" directory "nodes" directory "roles" directory "users" end it "knife upload does nothing" do knife("upload /").should_succeed "" knife("diff --name-status /").should_succeed < "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n") Deleted extra entry /clients/chef-validator.json (purge is on) Deleted extra entry /clients/chef-webui.json (purge is on) Deleted extra entry /clients/x.json (purge is on) Deleted extra entry /cookbooks/x-1.0.0 (purge is on) Deleted extra entry /data_bags/x (purge is on) Deleted extra entry /environments/x.json (purge is on) Deleted extra entry /nodes/x.json (purge is on) Deleted extra entry /roles/x.json (purge is on) Deleted extra entry /users/admin.json (purge is on) Deleted extra entry /users/x.json (purge is on) EOM knife("diff --name-status /").should_succeed < true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/chef-webui.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0") file "data_bags/x/y.json", {} file "environments/_default.json", { "description" => "The default Chef environment" } file "environments/x.json", {} file "nodes/x.json", { "normal" => { "tags" => [] } } file "roles/x.json", {} file "users/admin.json", { "admin" => true, "public_key" => ChefZero::PUBLIC_KEY } file "users/x.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife upload makes no changes" do knife("upload /cookbooks/x-1.0.0").should_succeed "" knife("diff --name-status /").should_succeed "" end it "knife upload --purge makes no changes" do knife("upload --purge /").should_succeed "" knife("diff --name-status /").should_succeed "" end context "except the role file" do before do file "roles/x.json", { "description" => "blarghle" } end it "knife upload changes the role" do knife("upload /").should_succeed "Updated /roles/x.json\n" knife("diff --name-status /").should_succeed "" end end context "except the role file is textually different, but not ACTUALLY different" do before do file "roles/x.json", < ChefZero::PUBLIC_KEY } file "cookbooks/x-1.0.0/blah.rb", "" file "cookbooks/x-2.0.0/metadata.rb", cb_metadata("x", "2.0.0") file "cookbooks/y-1.0.0/metadata.rb", cb_metadata("y", "1.0.0") file "data_bags/x/z.json", {} file "data_bags/y/zz.json", {} file "environments/y.json", {} file "nodes/y.json", {} file "roles/y.json", {} file "users/y.json", { "public_key" => ChefZero::PUBLIC_KEY } end it "knife upload adds the new files" do knife("upload /").should_succeed < /USAGE/ end end end end # Test upload of an item when the other end doesn't even have the container when_the_chef_server "is empty" do when_the_repository "has two data bag items" do before do file "data_bags/x/y.json", {} file "data_bags/x/z.json", {} end it "knife upload of one data bag item itself succeeds" do knife("upload /data_bags/x/y.json").should_succeed < {}, "modified" => {}, "unmodified" => {} } end when_the_repository "has a modified, unmodified, added and deleted data bag item" do before do file "data_bags/x/added.json", {} file "data_bags/x/modified.json", { "foo" => "bar" } file "data_bags/x/unmodified.json", {} end it "knife upload of the modified file succeeds" do knife("upload /data_bags/x/modified.json").should_succeed < /USAGE/ end it "knife upload --purge . uploads everything" do knife("upload --purge .").should_succeed < "" } end when_the_repository "has a modified, extra and missing file for the cookbook" do before do file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0", "#modified") file "cookbooks/x-1.0.0/y.rb", "hi" end it "knife upload of any individual file fails" do knife("upload /cookbooks/x-1.0.0/metadata.rb").should_fail "ERROR: /cookbooks/x-1.0.0/metadata.rb cannot be updated.\n" knife("upload /cookbooks/x-1.0.0/y.rb").should_fail "ERROR: /cookbooks/x-1.0.0 cannot have a child created under it.\n" knife("upload --purge /cookbooks/x-1.0.0/z.rb").should_fail "ERROR: /cookbooks/x-1.0.0/z.rb cannot be deleted.\n" end # TODO this is a bit of an inconsistency: if we didn't specify --purge, # technically we shouldn't have deleted missing files. But ... cookbooks # are a special case. it "knife upload of the cookbook itself succeeds" do knife("upload /cookbooks/x-1.0.0").should_succeed < "" } cookbook "x", "1.0.1", { "onlyin1.0.1.rb" => "hi" } end it "knife upload /cookbooks uploads the local version" do knife("diff --name-status /cookbooks").should_succeed < "" } cookbook "x", "0.9.9", { "onlyin0.9.9.rb" => "hi" } end it "knife upload /cookbooks uploads the local version" do knife("upload --purge /cookbooks").should_succeed < "hi" } end it "knife upload /cookbooks/x uploads the local version" do knife("diff --name-status /cookbooks").should_succeed < "hi" } end it "knife upload /cookbooks/x uploads the new version" do knife("upload --purge /cookbooks").should_succeed < "y" } end it "knife upload fails" do knife("upload /environments/x.json").should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n" knife("diff --name-status /environments/x.json").should_succeed "M\t/environments/x.json\n" end end when_the_repository "has the same environment with no name in the file" do before do file "environments/x.json", { "description" => "hi" } end it "knife upload succeeds" do knife("upload /environments/x.json").should_succeed "Updated /environments/x.json\n" knife("diff --name-status /environments/x.json").should_succeed "" end end end when_the_chef_server "is empty" do when_the_repository "has an environment with the wrong name in the file" do before do file "environments/x.json", { "name" => "y" } end it "knife upload fails" do knife("upload /environments/x.json").should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n" knife("diff --name-status /environments/x.json").should_succeed "A\t/environments/x.json\n" end end when_the_repository "has an environment with no name in the file" do before do file "environments/x.json", { "description" => "hi" } end it "knife upload succeeds" do knife("upload /environments/x.json").should_succeed "Created /environments/x.json\n" knife("diff --name-status /environments/x.json").should_succeed "" end end when_the_repository "has a data bag with no id in the file" do before do file "data_bags/bag/x.json", { "foo" => "bar" } end it "knife upload succeeds" do knife("upload /data_bags/bag/x.json").should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n" knife("diff --name-status /data_bags/bag/x.json").should_succeed "" end end end when_the_chef_server "is empty" do when_the_repository "has a cookbook with an invalid chef_version constraint in it" do before do file "cookbooks/x-1.0.0/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'") end it "knife upload succeeds" do knife("upload /cookbooks/x-1.0.0").should_succeed < true, "json_class" => "Chef::WebUIUser" } end it "knife upload /users/x.json succeeds" do knife("upload /users/x.json").should_succeed "Updated /users/x.json\n" end end end when_the_chef_server "is in Enterprise mode", :osc_compat => false, :single_org => false do before do user "foo", {} user "bar", {} user "foobar", {} organization "foo", { "full_name" => "Something" } end before :each do Chef::Config.chef_server_url = URI.join(Chef::Config.chef_server_url, "/organizations/foo") end context "and has nothing but a single group named blah" do group "blah", {} when_the_repository "has at least one of each thing" do before do # TODO We have to upload acls for an existing group due to a lack of # dependency detection during upload. Fix that! file "acls/groups/blah.json", {} file "clients/x.json", { "public_key" => ChefZero::PUBLIC_KEY } file "containers/x.json", {} file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0") file "cookbook_artifacts/x-1x1/metadata.rb", cb_metadata("x", "1.0.0") file "data_bags/x/y.json", {} file "environments/x.json", {} file "groups/x.json", {} file "invitations.json", [ "foo" ] file "members.json", [ "bar" ] file "org.json", { "full_name" => "wootles" } file "nodes/x.json", { "normal" => { "tags" => [] } } file "policies/x-1.0.0.json", {} file "policies/blah-1.0.0.json", {} file "policy_groups/x.json", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" } } } file "roles/x.json", {} end it "knife upload / uploads everything" do knife("upload /").should_succeed < "Something" } # acl_for %w(organizations foo groups blah) client "x", {} cookbook "x", "1.0.0" cookbook_artifact "x", "1x1", "metadata.rb" => cb_metadata("x", "1.0.0") container "x", {} data_bag "x", { "y" => {} } environment "x", {} group "x", {} org_invite "foo" org_member "bar" node "x", {} policy "x", "1.0.0", {} policy "blah", "1.0.0", {} policy_group "x", { "policies" => { "x" => { "revision_id" => "1.0.0" }, "blah" => { "revision_id" => "1.0.0" }, }, } role "x", {} end it "knife upload makes no changes" do knife("upload /").should_succeed < [ "blah" ] } end it "should fail because policies are not updateable" do knife("upload /policies/x-1.0.0.json").should_fail < { "default.rb" => "" } } end it "should fail because cookbook_artifacts cannot be updated" do knife("upload /cookbook_artifacts/x-1x1").should_fail < true } container "x", {} cookbook "x", "1.0.0", { "recipes" => { "default.rb" => "" } } cookbook_artifact "x", "1x1", { "metadata.rb" => cb_metadata("x", "1.0.0") } data_bag "x", { "y" => { "a" => "b" } } environment "x", { "description" => "foo" } group "x", { "groups" => [ "admin" ] } node "x", { "run_list" => [ "blah" ] } policy "x", "1.0.0", {} policy "x", "1.0.1", {} policy "y", "1.0.0", {} policy_group "x", { "policies" => { "x" => { "revision_id" => "1.0.1" }, "y" => { "revision_id" => "1.0.0" }, }, } role "x", { "run_list" => [ "blah" ] } end it "knife upload updates everything" do knife("upload /").should_succeed < "Something" } end it "knife upload / emits a warning for bar and adds foo and foobar" do knife("upload /").should_succeed "" expect(api.get("/")["full_name"]).to eq("Something") end end when_the_repository "has an org.json that changes full_name" do before do file "org.json", { "full_name" => "Something Else" } end it "knife upload / emits a warning for bar and adds foo and foobar" do knife("upload /").should_succeed "Updated /org.json\n" expect(api.get("/")["full_name"]).to eq("Something Else") end end context "and has invited foo and bar is already a member" do org_invite "foo" org_member "bar" when_the_repository "wants to invite foo, bar and foobar" do before do file "invitations.json", %w{foo bar foobar} end it "knife upload / emits a warning for bar and invites foobar" do knife("upload /").should_succeed "Updated /invitations.json\n", :stderr => "WARN: Could not invite bar to organization foo: User bar is already in organization foo\n" expect(api.get("association_requests").map { |a| a["username"] }).to eq(%w{foo foobar}) expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ "bar" ]) end end when_the_repository "wants to make foo, bar and foobar members" do before do file "members.json", %w{foo bar foobar} end it "knife upload / emits a warning for bar and adds foo and foobar" do knife("upload /").should_succeed "Updated /members.json\n" expect(api.get("association_requests").map { |a| a["username"] }).to eq([ ]) expect(api.get("users").map { |a| a["user"]["username"] }).to eq(%w{bar foo foobar}) end end when_the_repository "wants to invite foo and have bar as a member" do before do file "invitations.json", [ "foo" ] file "members.json", [ "bar" ] end it "knife upload / does nothing" do knife("upload /").should_succeed "" expect(api.get("association_requests").map { |a| a["username"] }).to eq([ "foo" ]) expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ "bar" ]) end end end context "and has invited bar and foo" do org_invite "bar", "foo" when_the_repository "wants to invite foo and bar (different order)" do before do file "invitations.json", %w{foo bar} end it "knife upload / does nothing" do knife("upload /").should_succeed "" expect(api.get("association_requests").map { |a| a["username"] }).to eq(%w{bar foo}) expect(api.get("users").map { |a| a["user"]["username"] }).to eq([ ]) end end end context "and has already added bar and foo as members of the org" do org_member "bar", "foo" when_the_repository "wants to add foo and bar (different order)" do before do file "members.json", %w{foo bar} end it "knife upload / does nothing" do knife("upload /").should_succeed "" expect(api.get("association_requests").map { |a| a["username"] }).to eq([ ]) expect(api.get("users").map { |a| a["user"]["username"] }).to eq(%w{bar foo}) end end end end end end chef-12.14.60/spec/integration/recipes/000077500000000000000000000000001276456504500175775ustar00rootroot00000000000000chef-12.14.60/spec/integration/recipes/lwrp_inline_resources_spec.rb000066400000000000000000000126751276456504500255650ustar00rootroot00000000000000require "support/shared/integration/integration_helper" require "chef/mixin/shell_out" describe "LWRPs with inline resources" do include IntegrationSupport include Chef::Mixin::ShellOut let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) } # 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' --minimal-ohai" } context "with a use_inline_resources provider with 'def action_a' instead of action :a" do class LwrpInlineResourcesTest < Chef::Resource::LWRPBase resource_name :lwrp_inline_resources_test actions :a, :nothing default_action :a property :ran_a class Provider < Chef::Provider::LWRPBase provides :lwrp_inline_resources_test use_inline_resources def action_a r = new_resource ruby_block "run a" do block { r.ran_a "ran a" } end end end end it "this is totally a bug, but for backcompat purposes, it adds the resources to the main resource collection and does not get marked updated" do r = nil expect_recipe do r = lwrp_inline_resources_test "hi" end.to have_updated("ruby_block[run a]", :run) expect(r.ran_a).to eq "ran a" end end context "with an inline resource with a property that shadows the enclosing provider's property" do class LwrpShadowedPropertyTest < Chef::Resource::LWRPBase PATH = ::File.join(Dir.tmpdir, "shadow-property.txt") use_automatic_resource_name actions :fiddle property :content action :fiddle do file PATH do content new_resource.content action [:create, :delete] end end end after { File.delete(LwrpShadowedPropertyTest::PATH) if File.exists?(LwrpShadowedPropertyTest::PATH) } # https://github.com/chef/chef/issues/4334 it "does not warn spuriously" do expect(Chef::Log).to_not receive(:warn).with(/is declared in both/) expect_recipe do lwrp_shadowed_property_test "fnord" do action :fiddle end end end end context "with an inline_resources provider with two actions, one calling the other" do class LwrpInlineResourcesTest2 < Chef::Resource::LWRPBase resource_name :lwrp_inline_resources_test2 actions :a, :b, :nothing default_action :b property :ran_a property :ran_b class Provider < Chef::Provider::LWRPBase provides :lwrp_inline_resources_test2 use_inline_resources action :a do r = new_resource ruby_block "run a" do block { r.ran_a "ran a" } end end action :b do action_a r = new_resource # Grab ran_a right now, before we converge ran_a = r.ran_a ruby_block "run b" do block { r.ran_b "ran b: ran_a value was #{ran_a.inspect}" } end end end end it "resources declared in b are executed immediately inline" do r = nil expect_recipe do r = lwrp_inline_resources_test2 "hi" do action :b end end.to have_updated("lwrp_inline_resources_test2[hi]", :b). and have_updated("ruby_block[run a]", :run). and have_updated("ruby_block[run b]", :run) expect(r.ran_b).to eq "ran b: ran_a value was \"ran a\"" end end when_the_repository "has a cookbook with a nested LWRP" do before do directory "cookbooks/x" do file "resources/do_nothing.rb", <<-EOM actions :create, :nothing default_action :create EOM file "providers/do_nothing.rb", <<-EOM action :create do end EOM file "resources/my_machine.rb", <<-EOM actions :create, :nothing default_action :create EOM file "providers/my_machine.rb", <<-EOM use_inline_resources action :create do x_do_nothing 'a' x_do_nothing 'b' end EOM file "recipes/default.rb", <<-EOM x_my_machine "me" x_my_machine "you" EOM end # directory 'cookbooks/x' end it "should complete with success" do file "config/client.rb", < chef_dir) actual = result.stdout.lines.map { |l| l.chomp }.join("\n") expected = < chef_dir) expect(result.stdout).to match(/\* l_w_r_p_foo\[me\] action create \(up to date\)/) expect(result.stdout).not_to match(/WARN: You are overriding l_w_r_p_foo/) result.error! end end end chef-12.14.60/spec/integration/recipes/noop_resource_spec.rb000066400000000000000000000011221276456504500240140ustar00rootroot00000000000000require "support/shared/integration/integration_helper" describe "Resources with a no-op provider" do include IntegrationSupport context "with noop provider providing foo" do before(:context) do class NoOpFoo < Chef::Resource resource_name "hi_there" default_action :update end Chef::Provider::Noop.provides :hi_there end it "does not blow up a run with a noop'd resource" do recipe = converge do hi_there "blah" do action :update end end expect(recipe.logged_warnings).to eq "" end end end chef-12.14.60/spec/integration/recipes/notifies_spec.rb000066400000000000000000000201721276456504500227600ustar00rootroot00000000000000require "support/shared/integration/integration_helper" require "chef/mixin/shell_out" describe "notifications" do include IntegrationSupport include Chef::Mixin::ShellOut let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) } let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } when_the_repository "notifies delayed one" do before do directory "cookbooks/x" do file "resources/notifying_test.rb", < chef_dir) # our delayed notification should run at the end of the parent run_context after the baz resource expect(result.stdout).to match(/\* log\[bar\] action write\s+\* log\[baz\] action write\s+\* log\[foo\] action write/) result.error! end end when_the_repository "notifies delayed two" do before do directory "cookbooks/x" do file "resources/notifying_test.rb", < chef_dir) # our delayed notification should run at the end of the parent run_context after the baz resource expect(result.stdout).to match(/\* log\[bar\] action write\s+\* log\[baz\] action write\s+\* log\[foo\] action write/) # and only run once expect(result.stdout).not_to match(/\* log\[foo\] action write.*\* log\[foo\] action write/) result.error! end end when_the_repository "notifies delayed three" do before do directory "cookbooks/x" do file "resources/notifying_test.rb", < chef_dir) # the delayed notification from the sub-resource is de-duplicated by the notification already in the parent run_context expect(result.stdout).to match(/\* log\[quux\] action write\s+\* notifying_test\[whatever\] action run\s+\* log\[bar\] action write\s+\* log\[baz\] action write\s+\* log\[foo\] action write\s+\* log\[baz\] action write/) # and only run once expect(result.stdout).not_to match(/\* log\[foo\] action write.*\* log\[foo\] action write/) result.error! end end when_the_repository "notifies delayed four" do before do directory "cookbooks/x" do file "recipes/default.rb", < chef_dir) # the delayed notification from the sub-resource is de-duplicated by the notification already in the parent run_context expect(result.stdout).to match(/\* log\[bar\] action write\s+\* log\[baz\] action write\s+\* log\[foo\] action write/) # and only run once expect(result.stdout).not_to match(/\* log\[foo\] action write.*\* log\[foo\] action write/) result.error! end end when_the_repository "notifies immediately" do before do directory "cookbooks/x" do file "resources/notifying_test.rb", < chef_dir) expect(result.stdout).to match(/\* log\[bar\] action write\s+\* log\[foo\] action write\s+\* log\[baz\] action write/) result.error! end end when_the_repository "uses old notifies syntax" do before do directory "cookbooks/x" do file "resources/notifying_test.rb", < chef_dir) expect(result.stdout).to match(/\* log\[bar\] action write\s+\* log\[foo\] action write\s+\* log\[baz\] action write/) result.error! end end when_the_repository "does not have a matching resource" do before do directory "cookbooks/x" do file "resources/notifying_test.rb", < chef_dir) expect(result.stdout).to match(/Chef::Exceptions::ResourceNotFound/) expect(result.exitstatus).not_to eql(0) end end when_the_repository "encounters identical resources in parent and child resource collections" do before do directory "cookbooks/x" do file "resources/cloning_test.rb", < chef_dir) expect(result.stdout).not_to match(/CHEF-3694/) result.error! end end end chef-12.14.60/spec/integration/recipes/notifying_block_spec.rb000066400000000000000000000074021276456504500243210ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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 "support/shared/integration/integration_helper" require "chef/mixin/shell_out" describe "notifying_block" do include IntegrationSupport include Chef::Mixin::ShellOut let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) } let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" } when_the_repository "notifying_block test one" do before do directory "cookbooks/x" do file "recipes/default.rb", <<-EOM notifying_block do log "gamma" do action :nothing end log "alpha" do notifies :write, "log[gamma]", :delayed end log "beta" do notifies :write, "log[gamma]", :delayed end end log "delta" EOM end file "config/client.rb", <<-EOM local_mode true cookbook_path "#{path_to('cookbooks')}" log_level :warn EOM end # implicitly tests - # 1. notifying block opens up a subcontext # 2. delayed notifications are de-dup'd in the subcontext # 3. delayed notifications (to resources inside the subcontext) are run at the end of the subcontext it "should run alpha, beta, gamma, and delta in that order" do result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'x::default'", :cwd => chef_dir) expect(result.stdout).to match(/\* log\[alpha\] action write\s+\* log\[beta\] action write\s+\* log\[gamma\] action write\s+Converging 1 resources\s+\* log\[delta\] action write/) result.error! end end when_the_repository "notifying_block test two" do before do directory "cookbooks/x" do file "resources/nb_test.rb", <<-EOM default_action :run provides :nb_test resource_name :nb_test action :run do notifying_block do log "foo" do notifies :write, 'log[bar]', :delayed end end end EOM file "recipes/default.rb", <<-EOM log "bar" do action :nothing end log "baz" do action :nothing end nb_test "testing" do notifies :write, 'log[baz]', :delayed end log "quux" EOM end file "config/client.rb", <<-EOM local_mode true cookbook_path "#{path_to('cookbooks')}" log_level :warn EOM end # implicitly tests - # 1. notifying block will correctly update wrapping new_resource updated_by_last_action status # 2. delayed notifications from a subcontext inside a resource will notify resources in their outer run_context it "should run foo, quux, bar, and baz in that order" do result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'x::default'", :cwd => chef_dir) expect(result.stdout).to match(/\* log\[foo\] action write\s+\* log\[quux\] action write\s+\* log\[bar\] action write\s+\* log\[baz\] action write/) result.error! end end end chef-12.14.60/spec/integration/recipes/provider_choice.rb000066400000000000000000000021351276456504500232710ustar00rootroot00000000000000require "support/shared/integration/integration_helper" describe "Recipe DSL methods" do include IntegrationSupport context "With resource class providing 'provider_thingy'" do before :context do class Chef::Resource::ProviderThingy < Chef::Resource resource_name :provider_thingy default_action :create def to_s "provider_thingy resource class" end end end context "And class Chef::Provider::ProviderThingy with no provides" do before :context do class Chef::Provider::ProviderThingy < Chef::Provider def load_current_resource end def action_create Chef::Log.warn("hello from #{self.class.name}") end end end it "provider_thingy 'blah' runs the provider and warns" do recipe = converge do provider_thingy("blah") {} end expect(recipe.logged_warnings).to match /hello from Chef::Provider::ProviderThingy/ expect(recipe.logged_warnings).to match /you must use 'provides' to provide DSL/i end end end end chef-12.14.60/spec/integration/recipes/recipe_dsl_spec.rb000066400000000000000000001532421276456504500232560ustar00rootroot00000000000000require "support/shared/integration/integration_helper" describe "Recipe DSL methods" do include IntegrationSupport module Namer extend self attr_accessor :current_index end before(:all) { Namer.current_index = 1 } before { Namer.current_index += 1 } context "with resource 'base_thingy' declared as BaseThingy" do before(:context) do class BaseThingy < Chef::Resource resource_name "base_thingy" default_action :create class< chef_dir) end def mode_of(path) path = path_to(path) stat = File.stat(path) (stat.mode & 0777).to_s(8) end it "creates all directories and files with the correct permissions" do expect(mode_of("dest_dir/sub1")).to eq "754" expect(mode_of("dest_dir/sub1/aaa")).to eq "777" expect(mode_of("dest_dir/sub1/zzz")).to eq "754" expect(mode_of("dest_dir/sub1/zzz/file")).to eq "777" expect(mode_of("dest_dir/sub2")).to eq "754" expect(mode_of("dest_dir/sub2/aaa")).to eq "754" expect(mode_of("dest_dir/sub2/aaa/file")).to eq "777" expect(mode_of("dest_dir/sub2/zzz")).to eq "777" end end end end chef-12.14.60/spec/integration/recipes/resource_action_spec.rb000066400000000000000000000432641276456504500243330ustar00rootroot00000000000000require "support/shared/integration/integration_helper" # Houses any classes we declare module ResourceActionSpec describe "Resource.action" do include IntegrationSupport shared_context "ActionJackson" do it "the default action is the first declared action" do converge <<-EOM, __FILE__, __LINE__ + 1 #{resource_dsl} "hi" do foo "foo!" end EOM expect(ActionJackson.ran_action).to eq :access_recipe_dsl expect(ActionJackson.succeeded).to eq true end it "the action can access recipe DSL" do converge <<-EOM, __FILE__, __LINE__ + 1 #{resource_dsl} "hi" do foo "foo!" action :access_recipe_dsl end EOM expect(ActionJackson.ran_action).to eq :access_recipe_dsl expect(ActionJackson.succeeded).to eq true end it "the action can access attributes" do converge <<-EOM, __FILE__, __LINE__ + 1 #{resource_dsl} "hi" do foo "foo!" action :access_attribute end EOM expect(ActionJackson.ran_action).to eq :access_attribute expect(ActionJackson.succeeded).to eq "foo!" end it "the action can access public methods" do converge <<-EOM, __FILE__, __LINE__ + 1 #{resource_dsl} "hi" do foo "foo!" action :access_method end EOM expect(ActionJackson.ran_action).to eq :access_method expect(ActionJackson.succeeded).to eq "foo_public!" end it "the action can access protected methods" do converge <<-EOM, __FILE__, __LINE__ + 1 #{resource_dsl} "hi" do foo "foo!" action :access_protected_method end EOM expect(ActionJackson.ran_action).to eq :access_protected_method expect(ActionJackson.succeeded).to eq "foo_protected!" end it "the action cannot access private methods" do expect do converge(<<-EOM, __FILE__, __LINE__ + 1) #{resource_dsl} "hi" do foo "foo!" action :access_private_method end EOM end.to raise_error(NameError) expect(ActionJackson.ran_action).to eq :access_private_method end it "the action cannot access resource instance variables" do converge <<-EOM, __FILE__, __LINE__ + 1 #{resource_dsl} "hi" do foo "foo!" action :access_instance_variable end EOM expect(ActionJackson.ran_action).to eq :access_instance_variable expect(ActionJackson.succeeded).to be_nil end it "the action does not compile until the prior resource has converged" do converge <<-EOM, __FILE__, __LINE__ + 1 ruby_block "wow" do block do ResourceActionSpec::ActionJackson.ruby_block_converged = "ruby_block_converged!" end end #{resource_dsl} "hi" do foo "foo!" action :access_class_method end EOM expect(ActionJackson.ran_action).to eq :access_class_method expect(ActionJackson.succeeded).to eq "ruby_block_converged!" end it "the action's resources converge before the next resource converges" do converge <<-EOM, __FILE__, __LINE__ + 1 #{resource_dsl} "hi" do foo "foo!" action :access_attribute end ruby_block "wow" do block do ResourceActionSpec::ActionJackson.ruby_block_converged = ResourceActionSpec::ActionJackson.succeeded end end EOM expect(ActionJackson.ran_action).to eq :access_attribute expect(ActionJackson.succeeded).to eq "foo!" expect(ActionJackson.ruby_block_converged).to eq "foo!" end end context "With resource 'action_jackson'" do class ActionJackson < Chef::Resource use_automatic_resource_name def foo(value = nil) @foo = value if value @foo end def blarghle(value = nil) @blarghle = value if value @blarghle end class < chef_dir) result.error! expect(result.stdout).to include("ITWORKS") end it "should evaluate its node.json file" do file "config/solo.rb", < chef_dir) result.error! expect(result.stdout).to include("ITWORKS") end end when_the_repository "has a cookbook with an undeclared dependency" do before do file "cookbooks/x/metadata.rb", cookbook_x_100_metadata_rb file "cookbooks/x/recipes/default.rb", 'include_recipe "ancient::aliens"' file "cookbooks/ancient/metadata.rb", cookbook_ancient_100_metadata_rb file "cookbooks/ancient/recipes/aliens.rb", 'print "it was aliens"' end it "should exit with an error" do file "config/solo.rb", < chef_dir) expect(result.exitstatus).to eq(0) # For CHEF-5120 this becomes 1 expect(result.stdout).to include("WARN: MissingCookbookDependency") end end when_the_repository "has a cookbook with an incompatible chef_version" do before do file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "\nchef_version '~> 999.0'") file "cookbooks/x/recipes/default.rb", 'puts "ITWORKS"' file "config/solo.rb", < chef_dir) expect(result.exitstatus).to eq(1) expect(result.stdout).to include("Chef::Exceptions::CookbookChefVersionMismatch") end end when_the_repository "has a cookbook with an incompatible ohai_version" do before do file "cookbooks/x/metadata.rb", cb_metadata("x", "1.0.0", "\nohai_version '~> 999.0'") file "cookbooks/x/recipes/default.rb", 'puts "ITWORKS"' file "config/solo.rb", < chef_dir) expect(result.exitstatus).to eq(1) expect(result.stdout).to include("Chef::Exceptions::CookbookOhaiVersionMismatch") end end when_the_repository "has a cookbook with a recipe with sleep" do before do directory "logs" file "logs/runs.log", "" file "cookbooks/x/metadata.rb", cookbook_x_100_metadata_rb file "cookbooks/x/recipes/default.rb", < chef_dir) Process.waitpid(s1) end # Instantiate the second chef-solo run threads << Thread.new do s2 = Process.spawn("#{chef_solo} -c \"#{path_to('config/solo.rb')}\" -o 'x::default' -l debug -L #{path_to('logs/runs.log')}", :chdir => chef_dir) Process.waitpid(s2) end threads.each(&:join) end end.not_to raise_error # Unfortunately file / directory helpers in integration tests # are implemented using before(:each) so we need to do all below # checks in one example. run_log = File.read(path_to("logs/runs.log")) # second run should have a message which indicates it's waiting for the first run expect(run_log).to match(/Chef client [0-9]+ is running, will wait for it to finish and then run./) # both of the runs should succeed expect(run_log.lines.reject { |l| !l.include? "INFO: Chef Run complete in" }.length).to eq(2) end end end chef-12.14.60/spec/rcov.opts000066400000000000000000000000411276456504500154750ustar00rootroot00000000000000--exclude spec,bin,/Library/Ruby chef-12.14.60/spec/scripts/000077500000000000000000000000001276456504500153115ustar00rootroot00000000000000chef-12.14.60/spec/scripts/ssl-serve.rb000066400000000000000000000026051276456504500175640ustar00rootroot00000000000000# ssl-serve.rb # USAGE: ruby ssl-serve.rb # # ssl-serve is a script that serves a local directory over SSL. # You can use it to test various HTTP behaviors in chef, like chef-client's # `-j` and `-c` options and remote_file with https connections. # require "pp" require "openssl" require "webrick" require "webrick/https" $ssl = true CHEF_SPEC_DATA = File.expand_path("../../data", __FILE__) cert_text = File.read(File.expand_path("ssl/chef-rspec.cert", CHEF_SPEC_DATA)) cert = OpenSSL::X509::Certificate.new(cert_text) key_text = File.read(File.expand_path("ssl/chef-rspec.key", CHEF_SPEC_DATA)) key = OpenSSL::PKey::RSA.new(key_text) server_opts = {} if $ssl server_opts.merge!( { :SSLEnable => true, :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE, :SSLCertificate => cert, :SSLPrivateKey => key }) end # 5 == debug, 3 == warning LOGGER = WEBrick::Log.new(STDOUT, 5) DEFAULT_OPTIONS = { :server => "webrick", :Port => 9000, :Host => "localhost", :environment => :none, :Logger => LOGGER, :DocumentRoot => File.expand_path("#{Dir.tmpdir}/chef-118-sampledata") #:AccessLog => [] # Remove this option to enable the access log when debugging. } webrick_opts = DEFAULT_OPTIONS.merge(server_opts) pp :webrick_opts => webrick_opts server = WEBrick::HTTPServer.new(webrick_opts) trap("INT") { server.shutdown } server.start chef-12.14.60/spec/spec_helper.rb000066400000000000000000000235001276456504500164400ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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. # If you need to add anything in here, don't. # Add it to one of the files in spec/support # Abuse ruby's constant lookup to avoid undefined constant errors module Shell JUST_TESTING_MOVE_ALONG = true unless defined? JUST_TESTING_MOVE_ALONG IRB = nil unless defined? IRB end # Ruby 1.9 Compat $:.unshift File.expand_path("../..", __FILE__) require "rubygems" require "rspec/mocks" $:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) $:.unshift(File.expand_path("../lib", __FILE__)) $:.unshift(File.dirname(__FILE__)) if ENV["COVERAGE"] require "simplecov" SimpleCov.start do add_filter "/spec/" add_group "Remote File", "remote_file" add_group "Resources", "/resource/" add_group "Providers", "/provider/" add_group "Knife", "knife" end end require "chef" require "chef/knife" Dir["lib/chef/knife/**/*.rb"]. map { |f| f.gsub("lib/", "") }. map { |f| f.gsub(%r{\.rb$}, "") }. each { |f| require f } require "chef/resource_resolver" require "chef/provider_resolver" require "chef/mixins" require "chef/dsl" require "chef/application" require "chef/applications" require "chef/shell" require "chef/util/file_edit" require "chef/config" require "chef/chef_fs/file_system_cache" if ENV["CHEF_FIPS"] == "1" Chef::Config.init_openssl end # If you want to load anything into the testing environment # without versioning it, add it to spec/support/local_gems.rb require "spec/support/local_gems.rb" if File.exists?(File.join(File.dirname(__FILE__), "support", "local_gems.rb")) # Explicitly require spec helpers that need to load first require "spec/support/platform_helpers" require "spec/support/shared/unit/mock_shellout" # Autoloads support files # Excludes support/platforms by default # Do not change the gsub. Dir["spec/support/**/*.rb"]. reject { |f| f =~ %r{^spec/support/platforms} }. reject { |f| f =~ %r{^spec/support/pedant} }. map { |f| f.gsub(%r{.rb$}, "") }. map { |f| f.gsub(%r{spec/}, "") }. each { |f| require f } OHAI_SYSTEM = Ohai::System.new OHAI_SYSTEM.all_plugins(["platform", "hostname", "languages/powershell"]) test_node = Chef::Node.new test_node.automatic["os"] = (OHAI_SYSTEM["os"] || "unknown_os").dup.freeze test_node.automatic["platform_family"] = (OHAI_SYSTEM["platform_family"] || "unknown_platform_family").dup.freeze test_node.automatic["platform"] = (OHAI_SYSTEM["platform"] || "unknown_platform").dup.freeze test_node.automatic["platform_version"] = (OHAI_SYSTEM["platform_version"] || "unknown_platform_version").dup.freeze TEST_NODE = test_node.freeze TEST_OS = TEST_NODE["os"] TEST_PLATFORM = TEST_NODE["platform"] TEST_PLATFORM_VERSION = TEST_NODE["platform_version"] TEST_PLATFORM_FAMILY = TEST_NODE["platform_family"] RSpec.configure do |config| config.include(Matchers) config.include(MockShellout::RSpec) config.filter_run :focus => true config.filter_run_excluding :external => true # Explicitly disable :should syntax config.expect_with :rspec do |c| c.syntax = :expect end config.mock_with :rspec do |c| c.syntax = :expect end # Only run these tests on platforms that are also chef workstations config.filter_run_excluding :workstation if solaris? || aix? # Tests that randomly fail, but may have value. config.filter_run_excluding :volatile => true config.filter_run_excluding :volatile_on_solaris => true if solaris? config.filter_run_excluding :volatile_from_verify => false config.filter_run_excluding :skip_appveyor => true if ENV["APPVEYOR"] config.filter_run_excluding :appveyor_only => true unless ENV["APPVEYOR"] config.filter_run_excluding :skip_travis => true if ENV["TRAVIS"] config.filter_run_excluding :windows_only => true unless windows? config.filter_run_excluding :not_supported_on_mac_osx_106 => true if mac_osx_106? config.filter_run_excluding :not_supported_on_mac_osx => true if mac_osx? config.filter_run_excluding :mac_osx_only => true if !mac_osx? config.filter_run_excluding :not_supported_on_win2k3 => true if windows_win2k3? config.filter_run_excluding :not_supported_on_solaris => true if solaris? config.filter_run_excluding :not_supported_on_gce => true if gce? config.filter_run_excluding :not_supported_on_nano => true if windows_nano_server? config.filter_run_excluding :win2k3_only => true unless windows_win2k3? config.filter_run_excluding :windows_2008r2_or_later => true unless windows_2008r2_or_later? config.filter_run_excluding :windows64_only => true unless windows64? config.filter_run_excluding :windows32_only => true unless windows32? config.filter_run_excluding :windows_nano_only => true unless windows_nano_server? config.filter_run_excluding :ruby64_only => true unless ruby_64bit? config.filter_run_excluding :ruby32_only => true unless ruby_32bit? config.filter_run_excluding :windows_powershell_dsc_only => true unless windows_powershell_dsc? config.filter_run_excluding :windows_powershell_no_dsc_only => true unless ! windows_powershell_dsc? config.filter_run_excluding :windows_domain_joined_only => true unless windows_domain_joined? config.filter_run_excluding :windows_not_domain_joined_only => true if windows_domain_joined? config.filter_run_excluding :solaris_only => true unless solaris? config.filter_run_excluding :system_windows_service_gem_only => true unless system_windows_service_gem? config.filter_run_excluding :unix_only => true unless unix? config.filter_run_excluding :linux_only => true unless linux? config.filter_run_excluding :aix_only => true unless aix? config.filter_run_excluding :debian_family_only => true unless debian_family? config.filter_run_excluding :supports_cloexec => true unless supports_cloexec? config.filter_run_excluding :selinux_only => true unless selinux_enabled? config.filter_run_excluding :requires_root => true unless root? config.filter_run_excluding :requires_root_or_running_windows => true unless root? || windows? config.filter_run_excluding :requires_unprivileged_user => true if root? config.filter_run_excluding :uses_diff => true unless has_diff? config.filter_run_excluding :openssl_gte_101 => true unless openssl_gte_101? config.filter_run_excluding :openssl_lt_101 => true unless openssl_lt_101? config.filter_run_excluding :aes_256_gcm_only => true unless aes_256_gcm? config.filter_run_excluding :broken => true config.filter_run_excluding :not_wpar => true unless wpar? config.filter_run_excluding :not_supported_under_fips => true if fips? # these let us use chef: ">= 13" or ruby: "~> 2.0.0" or any other Gem::Dependency-style constraint config.filter_run_excluding chef: DependencyProc.with(Chef::VERSION) config.filter_run_excluding ruby: DependencyProc.with(RUBY_VERSION) running_platform_arch = `uname -m`.strip unless windows? config.filter_run_excluding :arch => lambda {|target_arch| running_platform_arch != target_arch } # Functional Resource tests that are provider-specific: # context "on platforms that use useradd", :provider => {:user => Chef::Provider::User::Useradd}} do #... config.filter_run_excluding :provider => lambda {|criteria| type, target_provider = criteria.first node = TEST_NODE.dup resource_class = Chef::ResourceResolver.resolve(type, node: node) if resource_class resource = resource_class.new("test", Chef::RunContext.new(node, nil, nil)) begin provider = resource.provider_for_action(Array(resource_class.default_action).first) provider.class != target_provider rescue Chef::Exceptions::ProviderNotFound # no provider for platform true end else true end } config.run_all_when_everything_filtered = true config.before(:each) do Chef.reset! Chef::ChefFS::FileSystemCache.instance.reset! Chef::Config.reset # By default, treat deprecation warnings as errors in tests. Chef::Config.treat_deprecation_warnings_as_errors(true) # Set environment variable so the setting persists in child processes ENV["CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS"] = "1" end # raise if anyone commits any test to CI with :focus set on it config.before(:example, :focus) do raise "This example was committed with `:focus` and should not have been" end if ENV["CI"] config.before(:suite) do ARGV.clear end end require "webrick/utils" require "thread" # Webrick uses a centralized/synchronized timeout manager. It works by # starting a thread to check for timeouts on an interval. The timeout # checker thread cannot be stopped or canceled in any easy way, and it # makes calls to Time.new, which fail when rspec is in the process of # creating a method stub for that method. Since our tests don't rely on # any timeout behavior enforced by webrick, disable the timeout manager # via a monkey patch. # # Hopefully this fails loudly if the webrick code should change. As of this # writing, the relevant code is in webrick/utils, which can be located on # your system with: # # $ gem which webrick/utils module WEBrick module Utils class TimeoutHandler def initialize end def register(*args) end def cancel(*args) end end end end # Enough stuff needs json serialization that I'm just adding it here for equality asserts require "chef/json_compat" chef-12.14.60/spec/stress/000077500000000000000000000000001276456504500151455ustar00rootroot00000000000000chef-12.14.60/spec/stress/win32/000077500000000000000000000000001276456504500161075ustar00rootroot00000000000000chef-12.14.60/spec/stress/win32/file_spec.rb000066400000000000000000000025261276456504500203720ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/win32/file" if windows? describe "Chef::ReservedNames::Win32::File", :windows_only do before(:each) do @path = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "data", "old_home_dir", "my-dot-emacs")) end it "should not leak significant memory", :volatile do test = lambda { Chef::ReservedNames::Win32::File.symlink?(@path) } expect(test).not_to leak_memory(:warmup => 50000, :iterations => 50000) end it "should not leak handles", :volatile do test = lambda { Chef::ReservedNames::Win32::File.symlink?(@path) } expect(test).not_to leak_handles(:warmup => 50, :iterations => 100) end end chef-12.14.60/spec/stress/win32/memory_spec.rb000066400000000000000000000014171276456504500207610ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe "Chef::ReservedNames::Win32::Memory", :windows_only do end chef-12.14.60/spec/stress/win32/security_spec.rb000066400000000000000000000045371276456504500213260ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" if windows? require "chef/win32/security" require "tmpdir" require "fileutils" end describe "Chef::ReservedNames::Win32::Security", :windows_only do def monkeyfoo File.join(CHEF_SPEC_DATA, "monkeyfoo").tr("/", "\\") end before :all do @test_tempdir = File.join(Dir.tmpdir, "cheftests", "chef_win32_security") FileUtils.mkdir_p(@test_tempdir) @monkeyfoo = File.join(@test_tempdir, "monkeyfoo.txt") end before :each do File.delete(@monkeyfoo) if File.exist?(@monkeyfoo) # Make a file. File.open(@monkeyfoo, "w") do |file| file.write("hi") end end after :all do FileUtils.rm_rf(@test_tempdir) end it "should not leak when retrieving and reading the ACE from a file", :volatile do expect do sids = Chef::ReservedNames::Win32::Security::SecurableObject.new(@monkeyfoo).security_descriptor.dacl.select { |ace| ace.sid } GC.start end.not_to leak_memory(:warmup => 50, :iterations => 100) end it "should not leak when creating a new ACL and setting it on a file", :volatile do securable_object = Security::SecurableObject.new(@monkeyfoo) expect do securable_object.dacl = Chef::ReservedNames::Win32::Security::ACL.create([ Chef::ReservedNames::Win32::Security::ACE.access_allowed(Chef::ReservedNames::Win32::Security::SID.Everyone, Chef::ReservedNames::Win32::API::Security::GENERIC_READ), Chef::ReservedNames::Win32::Security::ACE.access_denied(Chef::ReservedNames::Win32::Security::SID.from_account("Users"), Chef::ReservedNames::Win32::API::Security::GENERIC_ALL), ]) GC.start end.not_to leak_memory(:warmup => 50, :iterations => 100) end end chef-12.14.60/spec/support/000077500000000000000000000000001276456504500153365ustar00rootroot00000000000000chef-12.14.60/spec/support/chef_helpers.rb000066400000000000000000000101671276456504500203170ustar00rootroot00000000000000# 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. # CHEF_SPEC_DATA = File.expand_path(File.dirname(__FILE__) + "/../data/") CHEF_SPEC_ASSETS = File.expand_path(File.dirname(__FILE__) + "/../functional/assets/") CHEF_SPEC_BACKUP_PATH = File.join(Dir.tmpdir, "test-backup-path") Chef::Config[:log_level] = :fatal Chef::Config[:persistent_queue] = false Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH Chef::Log.init(StringIO.new) Chef::Log.level(Chef::Config.log_level) Chef::Config.solo(false) def sha256_checksum(path) OpenSSL::Digest::SHA256.hexdigest(File.read(path)) end # From Ruby 1.9.2+ # Here for backwards compatibility with Ruby 1.8.7 # http://rubydoc.info/stdlib/tmpdir/1.9.2/Dir/Tmpname def make_tmpname(prefix_suffix, n = nil) case prefix_suffix when String prefix = prefix_suffix suffix = "" when Array prefix = prefix_suffix[0] suffix = prefix_suffix[1] else raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}" end t = Time.now.strftime("%Y%m%d") path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}" path << "-#{n}" if n path << suffix end # NOTE: # This is a temporary fix to get tests passing on systems that have no `diff` # until we can replace shelling out to `diff` with ruby diff-lcs def has_diff? begin diff_cmd = Mixlib::ShellOut.new("diff -v") diff_cmd.run_command true rescue Errno::ENOENT false end end # This is a helper to determine if the ruby in the PATH contains # win32/service gem. windows_service_manager tests create a windows # service that starts with the system ruby and requires this gem. def system_windows_service_gem? windows_service_gem_check_command = %q{ruby -r "win32/daemon" -e ":noop"} if defined?(Bundler) Bundler.with_clean_env do # This returns true if the gem can be loaded system(windows_service_gem_check_command) end else # This returns true if the gem can be loaded system(windows_service_gem_check_command) end end # This is a helper to canonicalize paths that we're using in the file # tests. def canonicalize_path(path) windows? ? path.tr("/", '\\') : path end # Makes a temp directory with a canonical path on any platform. # Only really needed to work around an issue on Windows where # Ruby's temp library generates paths with short names. def make_canonical_temp_directory temp_directory = Dir.mktmpdir if windows? # On Windows, temporary file / directory path names may have shortened # subdirectory names due to reliance on the TMP and TEMP environment variables # in some Windows APIs and duplicated logic in Ruby's temp file implementation. # To work around this in the unit test context, we obtain the long (canonical) # path name via a Windows system call so that this path name can be used # in expectations that assume the ability to canonically name paths in comparisons. # Note that this was not an issue prior to Ruby 2.2 -- with Ruby 2.2, # some Chef code started to use long file names, while Ruby's temp file implementation # continued to return the shortened names -- this would cause these particular tests to # fail if the username happened to be longer than 8 characters. Chef::ReservedNames::Win32::File.get_long_path_name(temp_directory) else temp_directory end end # Check if a cmd exists on the PATH def which(cmd) paths = ENV["PATH"].split(File::PATH_SEPARATOR) + [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ] paths.each do |path| filename = File.join(path, cmd) return filename if File.executable?(filename) end false end chef-12.14.60/spec/support/key_helpers.rb000066400000000000000000000064441276456504500202050ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" shared_examples_for "a knife key command" do let(:stderr) { StringIO.new } let(:command) do c = described_class.new([]) c.ui.config[:disable_editing] = true allow(c.ui).to receive(:stderr).and_return(stderr) allow(c.ui).to receive(:stdout).and_return(stderr) allow(c).to receive(:show_usage) c end context "before apply_params! is called" do context "when apply_params! is called with invalid args (missing actor)" do let(:params) { [] } it "shows the usage" do expect(command).to receive(:show_usage) expect { command.apply_params!(params) }.to exit_with_code(1) end it "outputs the proper error" do expect { command.apply_params!(params) }.to exit_with_code(1) expect(stderr.string).to include(command.actor_missing_error) end it "exits 1" do expect { command.apply_params!(params) }.to exit_with_code(1) end end end # before apply_params! is called context "after apply_params! is called with valid args" do before do command.apply_params!(params) end it "properly defines the actor" do expect(command.actor).to eq("charmander") end end # after apply_params! is called with valid args context "when the command is run" do before do allow(command).to receive(:service_object).and_return(service_object) allow(command).to receive(:name_args).and_return(["charmander"]) end context "when the command is successful" do before do expect(service_object).to receive(:run) end end end end # a knife key command shared_examples_for "a knife key command with a keyname as the second arg" do let(:stderr) { StringIO.new } let(:command) do c = described_class.new([]) c.ui.config[:disable_editing] = true allow(c.ui).to receive(:stderr).and_return(stderr) allow(c.ui).to receive(:stdout).and_return(stderr) allow(c).to receive(:show_usage) c end context "before apply_params! is called" do context "when apply_params! is called with invalid args (missing keyname)" do let(:params) { ["charmander"] } it "shows the usage" do expect(command).to receive(:show_usage) expect { command.apply_params!(params) }.to exit_with_code(1) end it "outputs the proper error" do expect { command.apply_params!(params) }.to exit_with_code(1) expect(stderr.string).to include(command.keyname_missing_error) end it "exits 1" do expect { command.apply_params!(params) }.to exit_with_code(1) end end end # before apply_params! is called end chef-12.14.60/spec/support/lib/000077500000000000000000000000001276456504500161045ustar00rootroot00000000000000chef-12.14.60/spec/support/lib/chef/000077500000000000000000000000001276456504500170115ustar00rootroot00000000000000chef-12.14.60/spec/support/lib/chef/provider/000077500000000000000000000000001276456504500206435ustar00rootroot00000000000000chef-12.14.60/spec/support/lib/chef/provider/easy.rb000066400000000000000000000016141276456504500221330ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Provider class Easy < Chef::Provider def load_current_resource true end def action_sell true end def action_buy true end end end end chef-12.14.60/spec/support/lib/chef/provider/openldap_includer.rb000066400000000000000000000015751276456504500246670ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Provider class OpenldapIncluder < Chef::Provider::LWRPBase provides :openldap_includer def action_run include_recipe "openldap::default" end end end end chef-12.14.60/spec/support/lib/chef/provider/snakeoil.rb000066400000000000000000000017621276456504500230030ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Provider class SnakeOil < Chef::Provider def load_current_resource true end def action_purr @new_resource.updated_by_last_action(true) true end def action_sell true end def action_buy true end end end end chef-12.14.60/spec/support/lib/chef/resource/000077500000000000000000000000001276456504500206405ustar00rootroot00000000000000chef-12.14.60/spec/support/lib/chef/resource/cat.rb000066400000000000000000000020121276456504500217270ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Resource class Cat < Chef::Resource attr_accessor :action def initialize(name, run_context = nil) super @action = "sell" end def pretty_kitty(arg = nil) if arg == true || arg == false @pretty_kitty = arg end @pretty_kitty end end end end chef-12.14.60/spec/support/lib/chef/resource/one_two_three_four.rb000066400000000000000000000017661276456504500250730ustar00rootroot00000000000000# # Author:: John Hampton () # Copyright:: Copyright 2009-2016, CleanOffer, 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 Resource class OneTwoThreeFour < Chef::Resource attr_reader :i_can_count def i_can_count(tf) @i_can_count = tf end def something(arg = nil) if arg == true || arg == false @something = arg end @something end end end end chef-12.14.60/spec/support/lib/chef/resource/openldap_includer.rb000066400000000000000000000015051276456504500246550ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # class Chef class Resource class OpenldapIncluder < Chef::Resource::LWRPBase allowed_actions :run default_action :run end end end chef-12.14.60/spec/support/lib/chef/resource/with_state.rb000066400000000000000000000015131276456504500233400ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/knife" require "chef/json_compat" class Chef class Resource class WithState < Chef::Resource attr_accessor :state end end end chef-12.14.60/spec/support/lib/chef/resource/zen_follower.rb000066400000000000000000000016471276456504500237020ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife" require "chef/json_compat" class Chef class Resource class ZenFollower < Chef::Resource provides :follower, platform: "zen" def master(arg = nil) if !arg.nil? @master = arg end @master end end end end chef-12.14.60/spec/support/lib/chef/resource/zen_master.rb000066400000000000000000000020311276456504500233300ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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/knife" require "chef/json_compat" class Chef class Resource class ZenMaster < Chef::Resource allowed_actions :win, :score attr_reader :peace def peace(tf) @peace = tf end def something(arg = nil) if !arg.nil? @something = arg end @something end end end end chef-12.14.60/spec/support/lib/library_load_order.rb000066400000000000000000000006061276456504500222710ustar00rootroot00000000000000# Helper module to track the load order of library files. # Used by `cookbook_compiler_spec.rb` # # This module must be loaded for any tests that load the cookbook # data/run_context/cookbooks/test to succeed. module LibraryLoadOrder extend self def load_order @load_order ||= [] end def reset! @load_order = nil end def record(file) load_order << file end end chef-12.14.60/spec/support/matchers/000077500000000000000000000000001276456504500171445ustar00rootroot00000000000000chef-12.14.60/spec/support/matchers/leak.rb000066400000000000000000000047001276456504500204060ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # module Matchers module LeakBase include RSpec::Matchers def initialize(opts = {}) @warmup = opts[:warmup] || 5 @iterations = opts[:iterations] || 100 @variance = opts[:variance] || 5000 end def failure_message "expected final measure [#{@final_measure}] to be greater than or within +/- #{@variance} delta of initial measure [#{@initial_measure}]" end def failure_message_when_negated "expected final measure [#{@final_measure}] to be less than or within +/- #{@variance} delta of initial measure [#{@initial_measure}]" end private def match(measure, given_proc) profiler.start @initial_measure = 0 @final_measure = 0 @warmup.times do given_proc.call end @initial_measure = profiler.send(measure) @iterations.times do given_proc.call end profiler.stop @final_measure = profiler.send(measure) @final_measure > (@initial_measure + @variance) end def profiler @profiler ||= begin if Chef::Platform.windows? require File.join(File.dirname(__FILE__), "..", "platforms", "prof", "win32") RSpec::Prof::Win32::Profiler.new else require File.join(File.dirname(__FILE__), "..", "prof", "gc") RSpec::Prof::GC::Profiler.new end end end end class LeakMemory include LeakBase def matches?(given_proc) match(:working_set_size, given_proc) end end class LeakHandles include LeakBase def matches?(given_proc) match(:handle_count, given_proc) end end def leak_memory(opts, &block) Matchers::LeakMemory.new(opts, &block) end def leak_handles(opts, &block) Matchers::LeakHandles.new(opts, &block) end end chef-12.14.60/spec/support/mock/000077500000000000000000000000001276456504500162675ustar00rootroot00000000000000chef-12.14.60/spec/support/mock/constant.rb000066400000000000000000000027571276456504500204600ustar00rootroot00000000000000# Allows easy mocking of global and class constants # Inspired by: # http://missingbit.blogspot.com/2011/07/stubbing-constants-in-rspec_20.html # http://digitaldumptruck.jotabout.com/?p=551 def mock_constants(constants) saved_constants = {} constants.each do |constant, val| source_object, const_name = parse_constant(constant) saved_constants[constant] = source_object.const_get(const_name) with_warnings(nil) { source_object.const_set(const_name, val) } end begin yield ensure constants.each do |constant, val| source_object, const_name = parse_constant(constant) with_warnings(nil) { source_object.const_set(const_name, saved_constants[constant]) } end end end def parse_constant(constant) source, _, constant_name = constant.to_s.rpartition("::") [constantize(source), constant_name] end # Taken from ActiveSupport # File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 3 # # Sets $VERBOSE for the duration of the block and back to its original value afterwards. def with_warnings(flag) old_verbose, $VERBOSE = $VERBOSE, flag yield ensure $VERBOSE = old_verbose end # File activesupport/lib/active_support/inflector/methods.rb, line 209 def constantize(camel_cased_word) names = camel_cased_word.split("::") names.shift if names.empty? || names.first.empty? constant = Object names.each do |name| constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name) end constant end chef-12.14.60/spec/support/mock/platform.rb000066400000000000000000000017551276456504500204500ustar00rootroot00000000000000# makes Chef think it's running on a certain platform..useful for unit testing # platform-specific functionality. # # If a block is given yields to the block with +RUBY_PLATFORM+ set to # 'i386-mingw32' (windows) or 'x86_64-darwin11.2.0' (unix). Usueful for # testing code that mixes in platform specific modules like +Chef::Mixin::Securable+ # or +Chef::FileAccessControl+ def platform_mock(platform = :unix) allow(ChefConfig).to receive(:windows?).and_return(platform == :windows ? true : false) ENV["SYSTEMDRIVE"] = (platform == :windows ? "C:" : nil) if platform == :windows Chef::Config.set_defaults_for_windows else Chef::Config.set_defaults_for_nix end if block_given? mock_constants({ "RUBY_PLATFORM" => (platform == :windows ? "i386-mingw32" : "x86_64-darwin11.2.0"), "File::PATH_SEPARATOR" => (platform == :windows ? ";" : ":"), "File::ALT_SEPARATOR" => (platform == :windows ? "\\" : nil) }) do yield end end end chef-12.14.60/spec/support/platform_helpers.rb000066400000000000000000000106171276456504500212360ustar00rootroot00000000000000require "fcntl" require "chef/mixin/shell_out" require "ohai/mixin/gce_metadata" class ShellHelpers extend Chef::Mixin::ShellOut end # magic stolen from bundler/spec/support/less_than_proc.rb class DependencyProc < Proc attr_accessor :present def self.with(present) provided = Gem::Version.new(present.dup) new do |required| !Gem::Requirement.new(required).satisfied_by?(provided) end.tap { |l| l.present = present } end def inspect "\"#{present}\"" end end def ruby_64bit? !!(RbConfig::CONFIG["host_cpu"] =~ /x86_64/) end def ruby_32bit? !!(RbConfig::CONFIG["host_cpu"] =~ /i686/) end def windows? !!(RUBY_PLATFORM =~ /mswin|mingw|windows/) end def ohai # This is defined in spec_helper; it has the `platform` populated. OHAI_SYSTEM end require "wmi-lite/wmi" if windows? def windows_domain_joined? return false unless windows? wmi = WmiLite::Wmi.new computer_system = wmi.first_of("Win32_ComputerSystem") computer_system["partofdomain"] end def windows_win2k3? return false unless windows? wmi = WmiLite::Wmi.new host = wmi.first_of("Win32_OperatingSystem") (host["version"] && host["version"].start_with?("5.2")) end def windows_2008r2_or_later? return false unless windows? wmi = WmiLite::Wmi.new host = wmi.first_of("Win32_OperatingSystem") version = host["version"] return false unless version components = version.split(".").map do |component| component.to_i end components.length >= 2 && components[0] >= 6 && components[1] >= 1 end def windows_powershell_dsc? return false unless windows? supports_dsc = false begin wmi = WmiLite::Wmi.new("root/microsoft/windows/desiredstateconfiguration") lcm = wmi.query("SELECT * FROM meta_class WHERE __this ISA 'MSFT_DSCLocalConfigurationManager'") supports_dsc = !! lcm rescue WmiLite::WmiException end supports_dsc end def windows_nano_server? require "chef/platform/query_helpers" Chef::Platform.windows_nano_server? end def mac_osx_106? if File.exists? "/usr/bin/sw_vers" result = ShellHelpers.shell_out("/usr/bin/sw_vers") result.stdout.each_line do |line| if line =~ /^ProductVersion:\s10.6.*$/ return true end end end false end def mac_osx? if File.exists? "/usr/bin/sw_vers" result = ShellHelpers.shell_out("/usr/bin/sw_vers") result.stdout.each_line do |line| if line =~ /^ProductName:\sMac OS X.*$/ return true end end end false end # detects if the hardware is 64-bit (evaluates to true in "WOW64" mode in a 32-bit app on a 64-bit system) def windows64? windows? && ( ENV["PROCESSOR_ARCHITECTURE"] == "AMD64" || ENV["PROCESSOR_ARCHITEW6432"] == "AMD64" ) end # detects if the hardware is 32-bit def windows32? windows? && !windows64? end # def jruby? def unix? !windows? end def linux? !!(RUBY_PLATFORM =~ /linux/) end def os_x? !!(RUBY_PLATFORM =~ /darwin/) end def solaris? !!(RUBY_PLATFORM =~ /solaris/) end def freebsd? !!(RUBY_PLATFORM =~ /freebsd/) end def debian_family? !!(ohai[:platform_family] == "debian") end def aix? !!(RUBY_PLATFORM =~ /aix/) end def wpar? !((ohai[:virtualization] || {})[:wpar_no].nil?) end def supports_cloexec? Fcntl.const_defined?("F_SETFD") && Fcntl.const_defined?("FD_CLOEXEC") end DEV_NULL = windows? ? "NUL" : "/dev/null" def selinux_enabled? # This code is currently copied from lib/chef/util/selinux to make # specs independent of product. selinuxenabled_path = which("selinuxenabled") if selinuxenabled_path cmd = Mixlib::ShellOut.new(selinuxenabled_path, :returns => [0, 1]) cmd_result = cmd.run_command case cmd_result.exitstatus when 1 return false when 0 return true else raise "Unknown exit code from command #{selinuxenabled_path}: #{cmd.exitstatus}" end else # We assume selinux is not enabled if selinux utils are not # installed. return false end end def suse? File.exists?("/etc/SuSE-release") end def root? return false if windows? Process.euid == 0 end def openssl_gte_101? OpenSSL::OPENSSL_VERSION_NUMBER >= 10001000 end def openssl_lt_101? !openssl_gte_101? end def aes_256_gcm? OpenSSL::Cipher.ciphers.include?("aes-256-gcm") end def fips? ENV["CHEF_FIPS"] == "1" end class GCEDetector extend Ohai::Mixin::GCEMetadata end def gce? GCEDetector.can_metadata_connect?(Ohai::Mixin::GCEMetadata::GCE_METADATA_ADDR, 80) rescue SocketError false end chef-12.14.60/spec/support/platforms/000077500000000000000000000000001276456504500173455ustar00rootroot00000000000000chef-12.14.60/spec/support/platforms/prof/000077500000000000000000000000001276456504500203135ustar00rootroot00000000000000chef-12.14.60/spec/support/platforms/prof/gc.rb000066400000000000000000000031141276456504500212300ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # module RSpec module Prof module GC class Profiler # GC 1 invokes. # Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC time(ms) # 1 0.012 159240 212940 10647 0.00000000000001530000 LINE_PATTERN = /^\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)$/ def start ::GC::Profiler.enable unless ::GC::Profiler.enabled? end def stop ::GC::Profiler.disable end def working_set_size begin ::GC.start ::GC::Profiler.result.scan(LINE_PATTERN)[-1][2].to_i if ::GC::Profiler.enabled? ensure ::GC::Profiler.clear end end def handle_count 0 end end end end end chef-12.14.60/spec/support/platforms/prof/win32.rb000066400000000000000000000022211276456504500215770ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/win32/process" module RSpec module Prof module Win32 class Profiler def start GC.start end def stop GC.start end def working_set_size Chef::ReservedNames::Win32::Process.get_current_process.memory_info[:WorkingSetSize] end def handle_count Chef::ReservedNames::Win32::Process.get_current_process.handle_count end end end end end chef-12.14.60/spec/support/platforms/win32/000077500000000000000000000000001276456504500203075ustar00rootroot00000000000000chef-12.14.60/spec/support/platforms/win32/spec_service.rb000066400000000000000000000031061276456504500233060ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-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 "win32/daemon" class SpecService < ::Win32::Daemon def service_init @test_service_file = "#{ENV['TMP']}/spec_service_file" end def service_main(*startup_parameters) while running? if !File.exists?(@test_service_file) File.open(@test_service_file, "wb") do |f| f.write("This file is created by SpecService") end end sleep 1 end end ################################################################################ # Control Signal Callback Methods ################################################################################ def service_stop end def service_pause end def service_resume end def service_shutdown end end # To run this file as a service, it must be called as a script from within # the Windows Service framework. In that case, kick off the main loop! if __FILE__ == $0 SpecService.mainloop end chef-12.14.60/spec/support/shared/000077500000000000000000000000001276456504500166045ustar00rootroot00000000000000chef-12.14.60/spec/support/shared/context/000077500000000000000000000000001276456504500202705ustar00rootroot00000000000000chef-12.14.60/spec/support/shared/context/client.rb000066400000000000000000000221431276456504500220750ustar00rootroot00000000000000 require "spec_helper" # Stubs a basic client object shared_context "client" do let(:fqdn) { "hostname.example.org" } let(:hostname) { "hostname" } let(:machinename) { "machinename.example.org" } let(:platform) { "example-platform" } let(:platform_version) { "example-platform-1.0" } let(:ohai_data) do { :fqdn => fqdn, :hostname => hostname, :machinename => machinename, :platform => platform, :platform_version => platform_version, } end let(:ohai_system) do ohai = instance_double("Ohai::System", :all_plugins => true, :data => ohai_data) allow(ohai).to receive(:[]) do |k| ohai_data[k] end ohai end let(:node) do Chef::Node.new.tap do |n| n.name(fqdn) n.chef_environment("_default") end end let(:json_attribs) { nil } let(:client_opts) { {} } let(:client) do Chef::Config[:event_loggers] = [] Chef::Client.new(json_attribs, client_opts).tap do |c| c.node = node end end before do Chef::Log.logger = Logger.new(StringIO.new) # Node/Ohai data #Chef::Config[:node_name] = fqdn allow(Ohai::System).to receive(:new).and_return(ohai_system) end end # Stubs a client for a client run. # Requires a client object be defined in the scope of this included context. # e.g.: # describe "some functionality" do # include_context "client" # include_context "a client run" # ... # end shared_context "a client run" do let(:stdout) { StringIO.new } let(:stderr) { StringIO.new } let(:api_client_exists?) { false } let(:enable_fork) { false } let(:http_cookbook_sync) { double("Chef::ServerAPI (cookbook sync)") } let(:http_node_load) { double("Chef::ServerAPI (node)") } let(:http_node_save) { double("Chef::ServerAPI (node save)") } let(:reporting_rest_client) { double("Chef::ServerAPI (reporting client)") } let(:runner) { instance_double("Chef::Runner") } let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) } def stub_for_register # --Client.register # Make sure Client#register thinks the client key doesn't # exist, so it tries to register and create one. allow(File).to receive(:exists?).and_call_original expect(File).to receive(:exists?). with(Chef::Config[:client_key]). exactly(:once). and_return(api_client_exists?) unless api_client_exists? # Client.register will register with the validation client name. expect_any_instance_of(Chef::ApiClient::Registration).to receive(:run) end end def stub_for_node_load # Client.register will then turn around create another # Chef::ServerAPI object, this time with the client key it got from the # previous step. expect(Chef::ServerAPI).to receive(:new). with(Chef::Config[:chef_server_url], client_name: fqdn, signing_key_filename: Chef::Config[:client_key]). exactly(:once). and_return(http_node_load) # --Client#build_node # looks up the node, which we will return, then later saves it. expect(Chef::Node).to receive(:find_or_create).with(fqdn).and_return(node) # --ResourceReporter#node_load_completed # gets a run id from the server for storing resource history # (has its own tests, so stubbing it here.) expect_any_instance_of(Chef::ResourceReporter).to receive(:node_load_completed) end def stub_rest_clean allow(client).to receive(:rest_clean).and_return(reporting_rest_client) end def stub_for_sync_cookbooks # --Client#setup_run_context # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync # expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks) expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync) expect(http_cookbook_sync).to receive(:post). with("environments/_default/cookbook_versions", { :run_list => [] }). and_return({}) end def stub_for_converge # define me end def stub_for_audit # define me end def stub_for_node_save # define me end def stub_for_run # define me end before do Chef::Config[:client_fork] = enable_fork Chef::Config[:cache_path] = windows? ? 'C:\chef' : "/var/chef" Chef::Config[:why_run] = false Chef::Config[:audit_mode] = :enabled stub_const("Chef::Client::STDOUT_FD", stdout) stub_const("Chef::Client::STDERR_FD", stderr) stub_rest_clean stub_for_register stub_for_node_load stub_for_sync_cookbooks stub_for_converge stub_for_audit stub_for_node_save expect_any_instance_of(Chef::RunLock).to receive(:acquire) expect_any_instance_of(Chef::RunLock).to receive(:save_pid) expect_any_instance_of(Chef::RunLock).to receive(:release) # Post conditions: check that node has been filled in correctly expect(client).to receive(:run_started) stub_for_run end end shared_context "converge completed" do def stub_for_converge # --Client#converge expect(Chef::Runner).to receive(:new).and_return(runner) expect(runner).to receive(:converge).and_return(true) end def stub_for_node_save allow(node).to receive(:data_for_save).and_return(node.for_json) # --Client#save_updated_node expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url], client_name: fqdn, signing_key_filename: Chef::Config[:client_key], validate_utf8: false).and_return(http_node_save) expect(http_node_save).to receive(:put).with("nodes/#{fqdn}", node.for_json).and_return(true) end end shared_context "converge failed" do let(:converge_error) do err = Chef::Exceptions::UnsupportedAction.new("Action unsupported") err.set_backtrace([ "/path/recipe.rb:15", "/path/recipe.rb:12" ]) err end def stub_for_converge expect(Chef::Runner).to receive(:new).and_return(runner) expect(runner).to receive(:converge).and_raise(converge_error) end def stub_for_node_save expect(client).to_not receive(:save_updated_node) end end shared_context "audit phase completed" do def stub_for_audit # -- Client#run_audits expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) expect(audit_runner).to receive(:run).and_return(true) expect(client.events).to receive(:audit_phase_complete) end end shared_context "audit phase failed with error" do let(:audit_error) do err = RuntimeError.new("Unexpected audit error") err.set_backtrace([ "/path/recipe.rb:57", "/path/recipe.rb:55" ]) err end def stub_for_audit expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) expect(Chef::Audit::Logger).to receive(:read_buffer).and_return("Audit mode output!") expect(audit_runner).to receive(:run).and_raise(audit_error) expect(client.events).to receive(:audit_phase_failed).with(audit_error, "Audit mode output!") end end shared_context "audit phase completed with failed controls" do let(:audit_runner) do instance_double("Chef::Audit::Runner", :failed? => true, :num_failed => 1, :num_total => 3) end let(:audit_error) do err = Chef::Exceptions::AuditsFailed.new(audit_runner.num_failed, audit_runner.num_total) err.set_backtrace([ "/path/recipe.rb:108", "/path/recipe.rb:103" ]) err end def stub_for_audit expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner) expect(Chef::Audit::Logger).to receive(:read_buffer).and_return("Audit mode output!") expect(audit_runner).to receive(:run) expect(Chef::Exceptions::AuditsFailed).to receive(:new).with( audit_runner.num_failed, audit_runner.num_total ).and_return(audit_error) expect(client.events).to receive(:audit_phase_failed).with(audit_error, "Audit mode output!") end end shared_context "run completed" do def stub_for_run expect(client).to receive(:run_completed_successfully) # --ResourceReporter#run_completed # updates the server with the resource history # (has its own tests, so stubbing it here.) expect_any_instance_of(Chef::ResourceReporter).to receive(:run_completed) # --AuditReporter#run_completed # posts the audit data to server. # (has its own tests, so stubbing it here.) expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_completed) end end shared_context "run failed" do def stub_for_run expect(client).to receive(:run_failed) # --ResourceReporter#run_completed # updates the server with the resource history # (has its own tests, so stubbing it here.) expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed) # --AuditReporter#run_completed # posts the audit data to server. # (has its own tests, so stubbing it here.) expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed) end before do expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) end end chef-12.14.60/spec/support/shared/context/config.rb000066400000000000000000000010171276456504500220610ustar00rootroot00000000000000 # # Define config file setups for spec tests here. # https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-context # # Required chef files here: require "chef/config" # Required spec files here: require "spec_helper" # Basic config. Nothing fancy. shared_context "default config options" do before do Chef::Config[:cache_path] = windows? ? 'C:\chef' : "/var/chef" end # Don't need to have an after block to reset the config... # The spec_helper.rb takes care of resetting the config state. end chef-12.14.60/spec/support/shared/context/win32.rb000066400000000000000000000022121276456504500215540ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # RSpec.shared_context "Win32" do before(:all) do @original_win32 = if defined?(Win32) win32 = Object.send(:const_get, "Win32") Object.send(:remove_const, "Win32") win32 else nil end Win32 = Module.new end after(:all) do Object.send(:remove_const, "Win32") if defined?(Win32) Object.send(:const_set, "Win32", @original_win32) if @original_win32 end end chef-12.14.60/spec/support/shared/examples/000077500000000000000000000000001276456504500204225ustar00rootroot00000000000000chef-12.14.60/spec/support/shared/examples/client.rb000066400000000000000000000036051276456504500222310ustar00rootroot00000000000000 require "spec_helper" require "spec/support/shared/context/client" # requires platform and platform_version be defined shared_examples "a completed run" do include_context "run completed" it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do # This is what we're testing. expect(client.run).to be true # fork is stubbed, so we can see the outcome of the run expect(node.automatic_attrs[:platform]).to eq(platform) expect(node.automatic_attrs[:platform_version]).to eq(platform_version) end end shared_examples "a completed run with audit failure" do include_context "run completed" before do expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError) end it "converges, runs audits, saves the node and raises the error in a wrapping error" do expect { client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| expect(error.wrapped_errors.size).to eq(run_errors.size) run_errors.each do |run_error| expect(error.wrapped_errors).to include(run_error) expect(error.backtrace).to include(*run_error.backtrace) end end # fork is stubbed, so we can see the outcome of the run expect(node.automatic_attrs[:platform]).to eq(platform) expect(node.automatic_attrs[:platform_version]).to eq(platform_version) end end shared_examples "a failed run" do include_context "run failed" it "skips node save and raises the error in a wrapping error" do expect { client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| expect(error.wrapped_errors.size).to eq(run_errors.size) run_errors.each do |run_error| expect(error.wrapped_errors).to include(run_error) expect(error.backtrace).to include(*run_error.backtrace) end end end end chef-12.14.60/spec/support/shared/functional/000077500000000000000000000000001276456504500207465ustar00rootroot00000000000000chef-12.14.60/spec/support/shared/functional/diff_disabled.rb000066400000000000000000000002341276456504500240310ustar00rootroot00000000000000 shared_context "diff disabled" do before do Chef::Config[:diff_disabled] = true end after do Chef::Config[:diff_disabled] = false end end chef-12.14.60/spec/support/shared/functional/directory_resource.rb000066400000000000000000000121561276456504500252130ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # shared_examples_for "a directory resource" do include_context "diff disabled" let(:expect_updated?) { true } context "when the target directory does not exist" do before do # assert pre-condition expect(File).not_to exist(path) end describe "when running action :create" do context "and the recursive option is not set" do before do resource.run_action(:create) end it "creates the directory when the :create action is run" do expect(File).to exist(path) end it "is marked updated by last action" do expect(resource).to be_updated_by_last_action end end context "and the recursive option is set" do before do expect(File).not_to exist(path) resource.recursive(true) @recursive_path = File.join(path, "red-headed-stepchild") resource.path(@recursive_path) resource.run_action(:create) end it "recursively creates required directories" do expect(File).to exist(path) expect(File).to exist(@recursive_path) end it "is marked updated by last action" do expect(resource).to be_updated_by_last_action end end end # Set up the context for security tests def allowed_acl(sid, expected_perms) [ ACE.access_allowed(sid, expected_perms[:specific]), ACE.access_allowed(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::INHERIT_ONLY_ACE | Chef::ReservedNames::Win32::API::Security::CONTAINER_INHERIT_ACE | Chef::ReservedNames::Win32::API::Security::OBJECT_INHERIT_ACE)), ] end def denied_acl(sid, expected_perms) [ ACE.access_denied(sid, expected_perms[:specific]), ACE.access_denied(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::INHERIT_ONLY_ACE | Chef::ReservedNames::Win32::API::Security::CONTAINER_INHERIT_ACE | Chef::ReservedNames::Win32::API::Security::OBJECT_INHERIT_ACE)), ] end def parent_inheritable_acls dummy_directory_path = File.join(test_file_dir, "dummy_directory") dummy_directory = FileUtils.mkdir_p(dummy_directory_path) dummy_desc = get_security_descriptor(dummy_directory_path) FileUtils.rm_rf(dummy_directory_path) dummy_desc end it_behaves_like "a securable resource without existing target" end context "when the target directory exists" do before(:each) do # For resources such as remote_directory, simply creating the base # directory isn't enough to test that the system is in the desired state, # so we run the resource twice--otherwise the updated_by_last_action test # will fail. resource.dup.run_action(:create) expect(File).to exist(path) resource.run_action(:create) end describe "when running action :create" do before do resource.run_action(:create) end it "does not re-create the directory" do expect(File).to exist(path) end it "is not marked updated by last action" do expect(resource).not_to be_updated_by_last_action end end describe "when running action :delete" do context "without the recursive option" do before do resource.run_action(:delete) end it "deletes the directory" do expect(File).not_to exist(path) end it "is marked as updated by last action" do expect(resource).to be_updated_by_last_action end end context "with the recursive option" do before do FileUtils.mkdir(File.join(path, "red-headed-stepchild")) resource.recursive(true) resource.run_action(:delete) end it "recursively deletes directories" do expect(File).not_to exist(path) end end end end end shared_context Chef::Resource::Directory do # We create the directory than tmp to exercise different file # deployment strategies more completely. let(:test_file_dir) do if windows? File.join(ENV["systemdrive"], "test-dir") else File.join(CHEF_SPEC_DATA, "test-dir") end end let(:path) do File.join(test_file_dir, make_tmpname(directory_base)) end before do FileUtils.mkdir_p(test_file_dir) end after do FileUtils.rm_rf(test_file_dir) end after(:each) do FileUtils.rm_r(path) if File.exists?(path) end end chef-12.14.60/spec/support/shared/functional/file_resource.rb000066400000000000000000000742001276456504500241240ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # shared_context "deploying with move" do before do Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH Chef::Config[:file_atomic_update] = true end end shared_context "deploying with copy" do before do Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH Chef::Config[:file_atomic_update] = false end end shared_context "deploying via tmpdir" do before do Chef::Config[:file_staging_uses_destdir] = false Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH end end shared_context "deploying via destdir" do before do Chef::Config[:file_staging_uses_destdir] = true Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH end end shared_examples_for "a file with the wrong content" do before do # Assert starting state is as expected expect(File).to exist(path) # Kinda weird, in this case @expected_checksum is the cksum of the file # with incorrect content. expect(sha256_checksum(path)).to eq(@expected_checksum) end describe "when diff is disabled" do include_context "diff disabled" context "when running action :create" do context "with backups enabled" do before do resource.run_action(:create) end it "overwrites the file with the updated content when the :create action is run" do expect(File.stat(path).mtime).to be > @expected_mtime expect(sha256_checksum(path)).not_to eq(@expected_checksum) end it "backs up the existing file" do expect(Dir.glob(backup_glob).size).to equal(1) end it "is marked as updated by last action" do expect(resource).to be_updated_by_last_action end it "should restore the security contexts on selinux", :selinux_only do expect(selinux_security_context_restored?(path)).to be_truthy end end context "with backups disabled" do before do resource.backup(0) resource.run_action(:create) end it "should not attempt to backup the existing file if :backup == 0" do expect(Dir.glob(backup_glob).size).to equal(0) end it "should restore the security contexts on selinux", :selinux_only do expect(selinux_security_context_restored?(path)).to be_truthy end end context "with a checksum that does not match the content to deploy" do before do resource.checksum("aAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaA") end it "raises an exception" do expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::ChecksumMismatch) end end end describe "when running action :create_if_missing" do before do resource.run_action(:create_if_missing) end it "doesn't overwrite the file when the :create_if_missing action is run" do expect(File.stat(path).mtime).to eq(@expected_mtime) expect(sha256_checksum(path)).to eq(@expected_checksum) end it "is not marked as updated" do expect(resource).not_to be_updated_by_last_action end it "should restore the security contexts on selinux", :selinux_only do expect(selinux_security_context_restored?(path)).to be_truthy end end describe "when running action :delete" do before do resource.run_action(:delete) end it "deletes the file" do expect(File).not_to exist(path) end it "is marked as updated by last action" do expect(resource).to be_updated_by_last_action end end end context "when diff is enabled" do describe "sensitive attribute" do context "should be insensitive by default" do it { expect(resource.sensitive).to(be_falsey) } end context "when set" do before { resource.sensitive(true) } it "should be set on the resource" do expect(resource.sensitive).to(be_truthy) end context "when running :create action" do let(:provider) { resource.provider_for_action(:create) } let(:reporter_messages) { provider.instance_variable_get("@converge_actions").actions[0][0] } before do provider.run_action end it "should suppress the diff" do expect(resource.diff).to(include("suppressed sensitive resource")) expect(reporter_messages[1]).to eq("suppressed sensitive resource") end it "should still include the updated checksums" do expect(reporter_messages[0]).to include("update content in file") end end end end end end shared_examples_for "a file with the correct content" do before do # Assert starting state is as expected expect(File).to exist(path) expect(sha256_checksum(path)).to eq(@expected_checksum) end include_context "diff disabled" describe "when running action :create" do before do resource.run_action(:create) end it "does not overwrite the original when the :create action is run" do expect(sha256_checksum(path)).to eq(@expected_checksum) end it "does not update the mtime of the file when the :create action is run" do expect(File.stat(path).mtime).to eq(@expected_mtime) end it "is not marked as updated by last action" do expect(resource).not_to be_updated_by_last_action end it "should restore the security contexts on selinux", :selinux_only do expect(selinux_security_context_restored?(path)).to be_truthy end end describe "when running action :create_if_missing" do before do resource.run_action(:create_if_missing) end it "doesn't overwrite the file when the :create_if_missing action is run" do expect(sha256_checksum(path)).to eq(@expected_checksum) end it "is not marked as updated by last action" do expect(resource).not_to be_updated_by_last_action end it "should restore the security contexts on selinux", :selinux_only do expect(selinux_security_context_restored?(path)).to be_truthy end end describe "when running action :delete" do before do resource.run_action(:delete) end it "deletes the file when the :delete action is run" do expect(File).not_to exist(path) end it "is marked as updated by last action" do expect(resource).to be_updated_by_last_action end end end shared_examples_for "a file resource" do describe "when deploying with :move" do include_context "deploying with move" describe "when deploying via tmpdir" do include_context "deploying via tmpdir" it_behaves_like "a configured file resource" end describe "when deploying via destdir" do include_context "deploying via destdir" it_behaves_like "a configured file resource" end end describe "when deploying with :copy" do include_context "deploying with copy" describe "when deploying via tmpdir" do include_context "deploying via tmpdir" it_behaves_like "a configured file resource" end describe "when deploying via destdir" do include_context "deploying via destdir" it_behaves_like "a configured file resource" end end describe "when running under why run" do before do Chef::Config[:why_run] = true Chef::Config[:ssl_verify_mode] = :verify_none end after do Chef::Config[:why_run] = false end context "and the resource has a path with a missing intermediate directory" do # CHEF-3978 let(:path) do File.join(test_file_dir, "intermediate_dir", make_tmpname(file_base)) end it "successfully doesn't create the file" do resource.run_action(:create) # should not raise expect(File).not_to exist(path) end end end describe "when setting atomic_update" do it "booleans should work" do expect { resource.atomic_update(true) }.not_to raise_error expect { resource.atomic_update(false) }.not_to raise_error end it "anything else should raise an error" do expect { resource.atomic_update(:copy) }.to raise_error(ArgumentError) expect { resource.atomic_update(:move) }.to raise_error(ArgumentError) expect { resource.atomic_update(958) }.to raise_error(ArgumentError) end end end shared_examples_for "file resource not pointing to a real file" do def symlink?(file_path) if windows? Chef::ReservedNames::Win32::File.symlink?(file_path) else File.symlink?(file_path) end end def real_file?(file_path) !symlink?(file_path) && File.file?(file_path) end before do Chef::Config[:ssl_verify_mode] = :verify_none end describe "when force_unlink is set to true" do it ":create unlinks the target" do expect(real_file?(path)).to be_falsey resource.force_unlink(true) resource.run_action(:create) expect(real_file?(path)).to be_truthy expect(binread(path)).to eq(expected_content) expect(resource).to be_updated_by_last_action end end describe "when force_unlink is set to false" do it ":create raises an error" do expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::FileTypeMismatch) end end describe "when force_unlink is not set (default)" do it ":create raises an error" do expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::FileTypeMismatch) end end end shared_examples_for "a configured file resource" do include_context "diff disabled" before do Chef::Log.level = :info Chef::Config[:ssl_verify_mode] = :verify_none end # note the stripping of the drive letter from the tmpdir on windows let(:backup_glob) { File.join(CHEF_SPEC_BACKUP_PATH, test_file_dir.sub(/^([A-Za-z]:)/, ""), "#{file_base}*") } # Most tests update the resource, but a few do not. We need to test that the # resource is marked updated or not correctly, but the test contexts are # composed between correct/incorrect content and correct/incorrect # permissions. We override this "let" definition in the context where content # and permissions are correct. let(:expect_updated?) { true } include Chef::Mixin::ShellOut def selinux_security_context_restored?(path) @restorecon_path = which("restorecon") if @restorecon_path.nil? restorecon_test_command = "#{@restorecon_path} -n -v #{path}" cmdresult = shell_out(restorecon_test_command) # restorecon will print the required changes to stdout if any is # needed cmdresult.stdout.empty? end def binread(file) content = File.open(file, "rb") do |f| f.read end content.force_encoding(Encoding::BINARY) if "".respond_to?(:force_encoding) content end context "when the target file is a symlink", :not_supported_on_win2k3 do let(:symlink_target) do File.join(CHEF_SPEC_DATA, "file-test-target") end describe "when configured not to manage symlink's target" do before(:each) do # configure not to manage symlink source resource.manage_symlink_source(false) # create symlinks for test context FileUtils.touch(symlink_target) if windows? Chef::ReservedNames::Win32::File.symlink(symlink_target, path) else File.symlink(symlink_target, path) end end after(:each) do FileUtils.rm_rf(symlink_target) FileUtils.rm_rf(path) end describe "when symlink target has correct content" do before(:each) do File.open(symlink_target, "wb") { |f| f.print expected_content } end it_behaves_like "file resource not pointing to a real file" end describe "when symlink target has the wrong content" do before(:each) do File.open(symlink_target, "wb") { |f| f.print "This is so wrong!!!" } end after(:each) do # symlink should never be followed expect(binread(symlink_target)).to eq("This is so wrong!!!") end it_behaves_like "file resource not pointing to a real file" end end # Unix-only for now. Windows behavior may differ because of how ACL # management handles symlinks. Since symlinks are rare on Windows and this # feature primarily exists to support the case where a well-known file # (e.g., resolv.conf) has been converted to a symlink, we're okay with the # discrepancy. context "when configured to manage the symlink source", :unix_only do before do resource.manage_symlink_source(true) end context "but the symlink is part of a loop" do let(:link1_path) { File.join(CHEF_SPEC_DATA, "points-to-link2") } let(:link2_path) { File.join(CHEF_SPEC_DATA, "points-to-link1") } before do # point resource at link1: resource.path(link1_path) # create symlinks for test context File.symlink(link1_path, link2_path) File.symlink(link2_path, link1_path) end after(:each) do FileUtils.rm_rf(link1_path) FileUtils.rm_rf(link2_path) end it "raises an InvalidSymlink error" do expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::InvalidSymlink) end it "issues a warning/assumption in whyrun mode" do begin Chef::Config[:why_run] = true resource.run_action(:create) # should not raise ensure Chef::Config[:why_run] = false end end end context "but the symlink points to a nonexistent file" do let(:link_path) { File.join(CHEF_SPEC_DATA, "points-to-nothing") } let(:not_existent_source) { File.join(CHEF_SPEC_DATA, "i-am-not-here") } before do resource.path(link_path) # create symlinks for test context File.symlink(not_existent_source, link_path) FileUtils.rm_rf(not_existent_source) end after(:each) do FileUtils.rm_rf(link_path) end it "raises an InvalidSymlink error" do expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::InvalidSymlink) end it "issues a warning/assumption in whyrun mode" do begin Chef::Config[:why_run] = true resource.run_action(:create) # should not raise ensure Chef::Config[:why_run] = false end end end context "but the symlink is points to a non-file fs entry" do let(:link_path) { File.join(CHEF_SPEC_DATA, "points-to-dir") } let(:not_a_file_path) { File.join(CHEF_SPEC_DATA, "dir-at-end-of-symlink") } before do # point resource at link1: resource.path(link_path) # create symlinks for test context File.symlink(not_a_file_path, link_path) Dir.mkdir(not_a_file_path) end after(:each) do FileUtils.rm_rf(link_path) FileUtils.rm_rf(not_a_file_path) end it "raises an InvalidSymlink error" do expect { resource.run_action(:create) }.to raise_error(Chef::Exceptions::FileTypeMismatch) end it "issues a warning/assumption in whyrun mode" do begin Chef::Config[:why_run] = true resource.run_action(:create) # should not raise ensure Chef::Config[:why_run] = false end end end context "when the symlink source is a real file" do let(:wrong_content) { "this is the wrong content" } let(:link_path) { File.join(CHEF_SPEC_DATA, "points-to-real-file") } before do # point resource at link: resource.path(link_path) # create symlinks for test context File.symlink(path, link_path) end after(:each) do # shared examples should not change our test setup of a file resource # pointing at a symlink: expect(resource.path).to eq(link_path) FileUtils.rm_rf(link_path) end context "and the permissions are incorrect" do before do # Create source (real) file File.open(path, "wb") { |f| f.write(expected_content) } end include_context "setup broken permissions" include_examples "a securable resource with existing target" it "does not replace the symlink with a real file" do resource.run_action(:create) expect(File).to be_symlink(link_path) end end context "and the content is incorrect" do before do # Create source (real) file File.open(path, "wb") { |f| f.write(wrong_content) } end it "marks the resource as updated" do resource.run_action(:create) expect(resource).to be_updated_by_last_action end it "does not replace the symlink with a real file" do resource.run_action(:create) expect(File).to be_symlink(link_path) end end context "and the content and permissions are correct" do let(:expect_updated?) { false } before do # Create source (real) file File.open(path, "wb") { |f| f.write(expected_content) } end include_context "setup correct permissions" include_examples "a securable resource with existing target" end end context "when the symlink points to a symlink which points to a real file" do let(:wrong_content) { "this is the wrong content" } let(:link_to_file_path) { File.join(CHEF_SPEC_DATA, "points-to-real-file") } let(:link_to_link_path) { File.join(CHEF_SPEC_DATA, "points-to-next-link") } before do # point resource at link: resource.path(link_to_link_path) # create symlinks for test context File.symlink(path, link_to_file_path) File.symlink(link_to_file_path, link_to_link_path) # Create source (real) file File.open(path, "wb") { |f| f.write(wrong_content) } end include_context "setup broken permissions" include_examples "a securable resource with existing target" after(:each) do # shared examples should not change our test setup of a file resource # pointing at a symlink: expect(resource.path).to eq(link_to_link_path) FileUtils.rm_rf(link_to_file_path) FileUtils.rm_rf(link_to_link_path) end it "does not replace the symlink with a real file" do resource.run_action(:create) expect(File).to be_symlink(link_to_link_path) expect(File).to be_symlink(link_to_file_path) end end end end context "when the target file does not exist" do before(:each) do FileUtils.rm_rf(path) end after(:each) do FileUtils.rm_rf(path) end def symlink?(file_path) if windows? Chef::ReservedNames::Win32::File.symlink?(file_path) else File.symlink?(file_path) end end def real_file?(file_path) !symlink?(file_path) && File.file?(file_path) end describe "when force_unlink is set to true" do it ":create updates the target" do resource.force_unlink(true) resource.run_action(:create) expect(real_file?(path)).to be_truthy expect(binread(path)).to eq(expected_content) expect(resource).to be_updated_by_last_action end end describe "when force_unlink is set to false" do it ":create updates the target" do resource.force_unlink(true) resource.run_action(:create) expect(real_file?(path)).to be_truthy expect(binread(path)).to eq(expected_content) expect(resource).to be_updated_by_last_action end end describe "when force_unlink is not set (default)" do it ":create updates the target" do resource.force_unlink(true) resource.run_action(:create) expect(real_file?(path)).to be_truthy expect(binread(path)).to eq(expected_content) expect(resource).to be_updated_by_last_action end end end context "when the target file is a directory" do before(:each) do FileUtils.mkdir_p(path) end after(:each) do FileUtils.rm_rf(path) end it_behaves_like "file resource not pointing to a real file" end context "when the target file is a blockdev", :unix_only, :requires_root, :not_supported_on_solaris do include Chef::Mixin::ShellOut let(:path) do File.join(CHEF_SPEC_DATA, "testdev") end before(:each) do result = shell_out("mknod #{path} b 1 2") result.stderr.empty? end after(:each) do FileUtils.rm_rf(path) end it_behaves_like "file resource not pointing to a real file" end context "when the target file is a chardev", :unix_only, :requires_root, :not_supported_on_solaris do include Chef::Mixin::ShellOut let(:path) do File.join(CHEF_SPEC_DATA, "testdev") end before(:each) do result = shell_out("mknod #{path} c 1 2") result.stderr.empty? end after(:each) do FileUtils.rm_rf(path) end it_behaves_like "file resource not pointing to a real file" end context "when the target file is a pipe", :unix_only do include Chef::Mixin::ShellOut let(:path) do File.join(CHEF_SPEC_DATA, "testpipe") end before(:each) do result = shell_out("mkfifo #{path}") result.stderr.empty? end after(:each) do FileUtils.rm_rf(path) end it_behaves_like "file resource not pointing to a real file" end context "when the target file is a socket", :unix_only do require "socket" # It turns out that the path to a socket can have at most ~104 # bytes. Therefore we are creating our sockets in tmpdir so that # they have a shorter path. let(:test_socket_dir) { File.join(Dir.tmpdir, "sockets") } before do FileUtils.mkdir_p(test_socket_dir) end after do FileUtils.rm_rf(test_socket_dir) end let(:path) do File.join(test_socket_dir, "testsocket") end before(:each) do expect(path.bytesize).to be <= 104 UNIXServer.new(path) end after(:each) do FileUtils.rm_rf(path) end it_behaves_like "file resource not pointing to a real file" end # Regression test for http://tickets.opscode.com/browse/CHEF-4082 context "when notification is configured" do describe "when path is specified with normal separator" do before do @notified_resource = Chef::Resource.new("punk", resource.run_context) resource.notifies(:run, @notified_resource, :immediately) resource.run_action(:create) end it "should notify the other resources correctly" do expect(resource).to be_updated_by_last_action expect(resource.run_context.immediate_notifications(resource).length).to eq(1) end end describe "when path is specified with windows separator", :windows_only do let(:path) do File.join(test_file_dir, make_tmpname(file_base)).gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR) end before do @notified_resource = Chef::Resource.new("punk", resource.run_context) resource.notifies(:run, @notified_resource, :immediately) resource.run_action(:create) end it "should notify the other resources correctly" do expect(resource).to be_updated_by_last_action expect(resource.run_context.immediate_notifications(resource).length).to eq(1) end end end context "when the target file does not exist" do before do # Assert starting state is expected expect(File).not_to exist(path) end describe "when running action :create" do before do resource.run_action(:create) end it "creates the file when the :create action is run" do expect(File).to exist(path) end it "creates the file with the correct content when the :create action is run" do expect(binread(path)).to eq(expected_content) end it "is marked as updated by last action" do expect(resource).to be_updated_by_last_action end it "should restore the security contexts on selinux", :selinux_only do expect(selinux_security_context_restored?(path)).to be_truthy end end describe "when running action :create_if_missing" do before do resource.run_action(:create_if_missing) end it "creates the file with the correct content" do expect(binread(path)).to eq(expected_content) end it "is marked as updated by last action" do expect(resource).to be_updated_by_last_action end it "should restore the security contexts on selinux", :selinux_only do expect(selinux_security_context_restored?(path)).to be_truthy end end describe "when running action :delete" do before do resource.run_action(:delete) end it "deletes the file when the :delete action is run" do expect(File).not_to exist(path) end it "is not marked updated by last action" do expect(resource).not_to be_updated_by_last_action end end end # Set up the context for security tests def allowed_acl(sid, expected_perms) [ ACE.access_allowed(sid, expected_perms[:specific]) ] end def denied_acl(sid, expected_perms) [ ACE.access_denied(sid, expected_perms[:specific]) ] end def parent_inheritable_acls dummy_file_path = File.join(test_file_dir, "dummy_file") FileUtils.touch(dummy_file_path) dummy_desc = get_security_descriptor(dummy_file_path) FileUtils.rm_rf(dummy_file_path) dummy_desc end it_behaves_like "a securable resource without existing target" context "when the target file has the wrong content" do before(:each) do File.open(path, "wb") { |f| f.print "This is so wrong!!!" } now = Time.now.to_i File.utime(now - 9000, now - 9000, path) @expected_mtime = File.stat(path).mtime @expected_checksum = sha256_checksum(path) end describe "and the target file has the correct permissions" do include_context "setup correct permissions" it_behaves_like "a file with the wrong content" it_behaves_like "a securable resource with existing target" end context "and the target file has incorrect permissions" do include_context "setup broken permissions" it_behaves_like "a file with the wrong content" it_behaves_like "a securable resource with existing target" end end context "when the target file has the correct content" do before(:each) do File.open(path, "wb") { |f| f.print expected_content } now = Time.now.to_i File.utime(now - 9000, now - 9000, path) @expected_mtime = File.stat(path).mtime @expected_checksum = sha256_checksum(path) end describe "and the target file has the correct permissions" do # When permissions and content are correct, chef should do nothing and # the resource should not be marked updated. let(:expect_updated?) { false } include_context "setup correct permissions" it_behaves_like "a file with the correct content" it_behaves_like "a securable resource with existing target" end context "and the target file has incorrect permissions" do include_context "setup broken permissions" it_behaves_like "a file with the correct content" it_behaves_like "a securable resource with existing target" end end # Regression test for http://tickets.opscode.com/browse/CHEF-4419 context "when the path starts with '/' and target file exists", :windows_only do let(:path) do File.join(test_file_dir[2..test_file_dir.length], make_tmpname(file_base)) end before do File.open(path, "wb") { |f| f.print expected_content } now = Time.now.to_i File.utime(now - 9000, now - 9000, path) @expected_mtime = File.stat(path).mtime @expected_checksum = sha256_checksum(path) end describe ":create action should run without any updates" do before do # Assert starting state is as expected expect(File).to exist(path) expect(sha256_checksum(path)).to eq(@expected_checksum) resource.run_action(:create) end it "does not overwrite the original when the :create action is run" do expect(sha256_checksum(path)).to eq(@expected_checksum) end it "does not update the mtime of the file when the :create action is run" do expect(File.stat(path).mtime).to eq(@expected_mtime) end it "is not marked as updated by last action" do expect(resource).not_to be_updated_by_last_action end end end end shared_context Chef::Resource::File do if windows? require "chef/win32/file" end # We create the files in a different directory than tmp to exercise # different file deployment strategies more completely. let(:test_file_dir) do if windows? File.join(ENV["systemdrive"], "test-dir") else File.join(CHEF_SPEC_DATA, "test-dir") end end let(:path) do File.join(test_file_dir, make_tmpname(file_base)) end before do FileUtils.rm_rf(test_file_dir) FileUtils.mkdir_p(test_file_dir) end after(:each) do FileUtils.rm_r(path) if File.exists?(path) FileUtils.rm_r(CHEF_SPEC_BACKUP_PATH) if File.exists?(CHEF_SPEC_BACKUP_PATH) end after do FileUtils.rm_rf(test_file_dir) end end chef-12.14.60/spec/support/shared/functional/http.rb000066400000000000000000000174151276456504500222620ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # # shared code for Chef::REST and Chef::HTTP::Simple and other Chef::HTTP wrappers # module ChefHTTPShared def nyan_uncompressed_filename File.join(CHEF_SPEC_DATA, "remote_file", "nyan_cat.png") end def nyan_compressed_filename File.join(CHEF_SPEC_DATA, "remote_file", "nyan_cat.png.gz") end def binread(file) content = File.open(file, "rb") do |f| f.read end content.force_encoding(Encoding::BINARY) if "".respond_to?(:force_encoding) content end def start_tiny_server(server_opts = {}) nyan_uncompressed_size = File::Stat.new(nyan_uncompressed_filename).size nyan_compressed_size = File::Stat.new(nyan_compressed_filename).size @server = TinyServer::Manager.new(server_opts) @server.start @api = TinyServer::API.instance @api.clear # # trivial endpoints # # just a normal file # (expected_content should be uncompressed) @api.get("/nyan_cat.png", 200) do File.open(nyan_uncompressed_filename, "rb") do |f| f.read end end # this ends in .gz, we do not uncompress it and drop it on the filesystem as a .gz file (the internet often lies) # (expected_content should be compressed) @api.get("/nyan_cat.png.gz", 200, nil, { "Content-Type" => "application/gzip", "Content-Encoding" => "gzip" } ) do File.open(nyan_compressed_filename, "rb") do |f| f.read end end # this is an uncompressed file that was compressed by some mod_gzip-ish webserver thingy, so we will expand it # (expected_content should be uncompressed) @api.get("/nyan_cat_compressed.png", 200, nil, { "Content-Type" => "application/gzip", "Content-Encoding" => "gzip" } ) do File.open(nyan_compressed_filename, "rb") do |f| f.read end end # # endpoints that set Content-Length correctly # # (expected_content should be uncompressed) @api.get("/nyan_cat_content_length.png", 200, nil, { "Content-Length" => nyan_uncompressed_size.to_s, } ) do File.open(nyan_uncompressed_filename, "rb") do |f| f.read end end # (expected_content should be uncompressed) @api.get("/nyan_cat_content_length_compressed.png", 200, nil, { "Content-Length" => nyan_compressed_size.to_s, "Content-Type" => "application/gzip", "Content-Encoding" => "gzip", } ) do File.open(nyan_compressed_filename, "rb") do |f| f.read end end # # endpoints that simulate truncated downloads (bad content-length header) # # (expected_content should be uncompressed) @api.get("/nyan_cat_truncated.png", 200, nil, { "Content-Length" => (nyan_uncompressed_size + 1).to_s, } ) do File.open(nyan_uncompressed_filename, "rb") do |f| f.read end end # (expected_content should be uncompressed) @api.get("/nyan_cat_truncated_compressed.png", 200, nil, { "Content-Length" => (nyan_compressed_size + 1).to_s, "Content-Type" => "application/gzip", "Content-Encoding" => "gzip", } ) do File.open(nyan_compressed_filename, "rb") do |f| f.read end end # # in the presence of a transfer-encoding header, we must ignore the content-length (this bad content-length should work) # # (expected_content should be uncompressed) @api.get("/nyan_cat_transfer_encoding.png", 200, nil, { "Content-Length" => (nyan_uncompressed_size + 1).to_s, "Transfer-Encoding" => "anything", } ) do File.open(nyan_uncompressed_filename, "rb") do |f| f.read end end # # 403 with a Content-Length # @api.get("/forbidden", 403, "Forbidden", { "Content-Length" => "Forbidden".bytesize.to_s, } ) @api.post("/posty", 200, "Hi!") # # 400 with an error # @api.get("/bad_request", 400, '{ "error": [ "Your request is just terrible." ] }') @api.post("/bad_request", 400, '{ "error": [ "Your request is just terrible." ] }') end def stop_tiny_server @server.stop @server = @api = nil end end shared_examples_for "downloading all the things" do describe "when downloading a simple uncompressed file" do let(:source) { "http://localhost:9000/nyan_cat.png" } let(:expected_content) { binread(nyan_uncompressed_filename) } it_behaves_like "downloads requests correctly" end describe "when downloading a compressed file that should be left compressed" do let(:source) { "http://localhost:9000/nyan_cat.png.gz" } let(:expected_content) { binread(nyan_compressed_filename) } # its the callers responsibility to disable_gzip when downloading a .gz url let(:http_client) { http_client_disable_gzip } it_behaves_like "downloads requests correctly" end describe "when downloading a file that has been compressed by the webserver" do let(:source) { "http://localhost:9000/nyan_cat_compressed.png" } let(:expected_content) { binread(nyan_uncompressed_filename) } it_behaves_like "downloads requests correctly" end describe "when downloading an uncompressed file with a correct content_length" do let(:source) { "http://localhost:9000/nyan_cat_content_length.png" } let(:expected_content) { binread(nyan_uncompressed_filename) } it_behaves_like "downloads requests correctly" end describe "when downloading a file that has been compressed by the webserver with a correct content_length" do let(:source) { "http://localhost:9000/nyan_cat_content_length_compressed.png" } let(:expected_content) { binread(nyan_uncompressed_filename) } it_behaves_like "downloads requests correctly" end describe "when downloading an uncompressed file that is truncated" do let(:source) { "http://localhost:9000/nyan_cat_truncated.png" } let(:expected_content) { binread(nyan_uncompressed_filename) } it_behaves_like "validates content length and throws an exception" end describe "when downloading a file that has been compressed by the webserver that is truncated" do let(:source) { "http://localhost:9000/nyan_cat_truncated_compressed.png" } let(:expected_content) { binread(nyan_uncompressed_filename) } it_behaves_like "validates content length and throws an exception" end describe "when downloading a file that has transfer encoding set with a bad content length that should be ignored" do let(:source) { "http://localhost:9000/nyan_cat_transfer_encoding.png" } let(:expected_content) { binread(nyan_uncompressed_filename) } it_behaves_like "downloads requests correctly" end describe "when downloading an endpoint that 403s" do let(:source) { "http://localhost:9000/forbidden" } it_behaves_like "an endpoint that 403s" end describe "when downloading an endpoint that 403s" do let(:source) { "http://localhost:9000/nyan_cat_content_length_compressed.png" } let(:expected_content) { binread(nyan_uncompressed_filename) } let(:source2) { "http://localhost:9000/forbidden" } it_behaves_like "a 403 after a successful request when reusing the request object" end end chef-12.14.60/spec/support/shared/functional/knife.rb000066400000000000000000000021411276456504500223650ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: AJ Christensen () # Author:: Ho-Sheng Hsiao () # 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. # module SpecHelpers module Knife def redefine_argv(value) Object.send(:remove_const, :ARGV) Object.send(:const_set, :ARGV, value) end def with_argv(*argv) original_argv = ARGV redefine_argv(argv.flatten) begin yield ensure redefine_argv(original_argv) end end end end chef-12.14.60/spec/support/shared/functional/securable_resource.rb000066400000000000000000000433501276456504500251540ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Author:: Mark Mzyk () # Author:: John Keiser () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "etc" require "functional/resource/base" shared_context "setup correct permissions" do if windows? include_context "use Windows permissions" end # I could not get this to work with :requires_unprivileged_user for whatever # reason. The setup when running as root is the same as non-root, except we # also do a chown, so this sets up correct context for either case. before :each, :unix_only do File.chmod(0776, path) now = Time.now.to_i File.utime(now - 9000, now - 9000, path) end # Root only context. before :each, :unix_only, :requires_root do if ohai[:platform] == "aix" File.chown(Etc.getpwnam("guest").uid, 1337, path) else File.chown(Etc.getpwnam("nobody").uid, 1337, path) end end before :each, :windows_only do so = SecurableObject.new(path) so.owner = SID.Administrator so.group = SID.Administrators dacl = ACL.create(denied_acl(SID.Guest, expected_modify_perms) + allowed_acl(SID.Guest, expected_read_perms)) so.dacl = dacl end end shared_context "setup broken permissions" do if windows? include_context "use Windows permissions" end before :each, :unix_only do File.chmod(0644, path) end before :each, :unix_only, :requires_root do File.chown(0, 0, path) end before :each, :windows_only do so = SecurableObject.new(path) so.owner = SID.Guest so.group = SID.Everyone dacl = ACL.create(allowed_acl(SID.Guest, expected_modify_perms)) so.set_dacl(dacl, true) end end shared_context "use Windows permissions", :windows_only do if windows? SID ||= Chef::ReservedNames::Win32::Security::SID ACE ||= Chef::ReservedNames::Win32::Security::ACE ACL ||= Chef::ReservedNames::Win32::Security::ACL SecurableObject ||= Chef::ReservedNames::Win32::Security::SecurableObject # rubocop:disable Style/ConstantName end def get_security_descriptor(path) Chef::ReservedNames::Win32::Security.get_named_security_info(path) end def explicit_aces descriptor.dacl.select { |ace| ace.explicit? } end def extract_ace_properties(aces) hashes = [] aces.each do |ace| hashes << { :mask => ace.mask, :type => ace.type, :flags => ace.flags } end hashes end # Standard expected rights let(:expected_read_perms) do { :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ, :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ, } end let(:expected_read_execute_perms) do { :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE, :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE, } end let(:expected_write_perms) do { :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE, :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE, } end let(:expected_modify_perms) do { :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::DELETE, :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::DELETE, } end let(:expected_full_control_perms) do { :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_ALL, :specific => Chef::ReservedNames::Win32::API::Security::FILE_ALL_ACCESS, } end RSpec::Matchers.define :have_expected_properties do |mask, type, flags| match do |ace| ace.mask == mask && ace.type == type && ace.flags == flags end end def descriptor get_security_descriptor(path) end end shared_examples_for "a securable resource with existing target" do include_context "diff disabled" context "on Unix", :unix_only do if ohai[:platform] == "aix" let(:expected_user_name) { "guest" } else let(:expected_user_name) { "nobody" } end let(:expected_uid) { Etc.getpwnam(expected_user_name).uid } let(:desired_gid) { 1337 } let(:expected_gid) { 1337 } describe "when setting the owner", :requires_root do before do resource.owner expected_user_name resource.run_action(:create) end it "should set an owner" do expect(File.lstat(path).uid).to eq(expected_uid) end it "is marked as updated only if changes are made" do expect(resource.updated_by_last_action?).to eq(expect_updated?) end end describe "when setting the group", :requires_root do before do resource.group desired_gid resource.run_action(:create) end it "should set a group" do expect(File.lstat(path).gid).to eq(expected_gid) end it "is marked as updated only if changes are made" do expect(resource.updated_by_last_action?).to eq(expect_updated?) end end describe "when setting the permissions from octal given as a String" do before do @mode_string = "776" resource.mode @mode_string resource.run_action(:create) end it "is marked as updated only if changes are made" do expect(resource.updated_by_last_action?).to eq(expect_updated?) end end describe "when setting permissions from a literal octal Integer" do before do @mode_integer = 0776 resource.mode @mode_integer resource.run_action(:create) end it "is marked as updated only if changes are made" do expect(resource.updated_by_last_action?).to eq(expect_updated?) end end describe "when setting the suid bit", :requires_root do before do @suid_mode = 04776 resource.mode @suid_mode resource.run_action(:create) end it "should set the suid bit" do expect(File.lstat(path).mode & 007777).to eq(@suid_mode & 007777) end it "should retain the suid bit when updating the user" do resource.user 1338 resource.run_action(:create) expect(File.lstat(path).mode & 007777).to eq(@suid_mode & 007777) end end end context "on Windows", :windows_only do include_context "use Windows permissions" describe "when setting owner" do before do resource.owner(SID.admin_account_name) resource.run_action(:create) end it "should set the owner" do expect(descriptor.owner).to eq(SID.Administrator) end it "is marked as updated only if changes are made" do expect(resource.updated_by_last_action?).to eq(expect_updated?) end end describe "when setting group" do before do resource.group("Administrators") resource.run_action(:create) end it "should set the group" do expect(descriptor.group).to eq(SID.Administrators) end it "is marked as updated only if changes are made" do expect(resource.updated_by_last_action?).to eq(expect_updated?) end end describe "when setting rights and deny_rights" do before do resource.deny_rights(:modify, "Guest") resource.rights(:read, "Guest") resource.run_action(:create) end it "should set the rights and deny_rights" do expect(explicit_aces).to eq(denied_acl(SID.Guest, expected_modify_perms) + allowed_acl(SID.Guest, expected_read_perms)) end it "is marked as updated only if changes are made" do expect(resource.updated_by_last_action?).to eq(expect_updated?) end end end end shared_examples_for "a securable resource without existing target" do include_context "diff disabled" context "on Windows", :windows_only do include_context "use Windows permissions" it "leaves owner as system default on create if owner is not specified" do expect(File.exist?(path)).to eq(false) resource.run_action(:create) expect(descriptor.owner).to eq(SID.default_security_object_owner) end it "sets owner when owner is specified" do resource.owner "Guest" resource.run_action(:create) expect(descriptor.owner).to eq(SID.Guest) end it "fails to set owner when owner has invalid characters" do expect { resource.owner 'Lance "The Nose" Glindenberry III' }.to raise_error(Chef::Exceptions::ValidationFailed) end it "sets owner when owner is specified with a \\" do resource.owner "#{ENV['USERDOMAIN']}\\Guest" resource.run_action(:create) expect(descriptor.owner).to eq(SID.Guest) end it "leaves owner alone if owner is not specified and resource already exists" do arbitrary_non_default_owner = SID.Guest expect(arbitrary_non_default_owner).not_to eq(SID.default_security_object_owner) resource.owner "Guest" # Change to arbitrary_non_default_owner once issue #1508 is fixed resource.run_action(:create) expect(descriptor.owner).to eq(arbitrary_non_default_owner) new_resource = create_resource expect(new_resource.owner).to eq(nil) new_resource.run_action(:create) expect(descriptor.owner).to eq(arbitrary_non_default_owner) end it "leaves group as system default on create if group is not specified" do expect(resource.group).to eq(nil) expect(File.exist?(path)).to eq(false) resource.run_action(:create) expect(descriptor.group).to eq(SID.default_security_object_group) end it "sets group when group is specified" do resource.group "Everyone" resource.run_action(:create) expect(descriptor.group).to eq(SID.Everyone) end it "fails to set group when group has invalid characters" do expect { resource.group 'Lance "The Nose" Glindenberry III' }.to raise_error(Chef::Exceptions::ValidationFailed) end it "leaves group alone if group is not specified and resource already exists" do arbitrary_non_default_group = SID.Everyone expect(arbitrary_non_default_group).not_to eq(SID.default_security_object_group) resource.group "Everyone" # Change to arbitrary_non_default_group once issue #1508 is fixed resource.run_action(:create) expect(descriptor.group).to eq(arbitrary_non_default_group) new_resource = create_resource expect(new_resource.group).to eq(nil) new_resource.run_action(:create) expect(descriptor.group).to eq(arbitrary_non_default_group) end describe "with rights and deny_rights attributes" do it "correctly sets :read rights" do resource.rights(:read, "Guest") resource.run_action(:create) expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_read_perms)) end it "correctly sets :read_execute rights" do resource.rights(:read_execute, "Guest") resource.run_action(:create) expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_read_execute_perms)) end it "correctly sets :write rights" do resource.rights(:write, "Guest") resource.run_action(:create) expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_write_perms)) end it "correctly sets :modify rights" do resource.rights(:modify, "Guest") resource.run_action(:create) expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_modify_perms)) end it "correctly sets :full_control rights" do resource.rights(:full_control, "Guest") resource.run_action(:create) expect(explicit_aces).to eq(allowed_acl(SID.Guest, expected_full_control_perms)) end it "correctly sets deny_rights" do # deny is an ACE with full rights, but is a deny type ace, not an allow type resource.deny_rights(:full_control, "Guest") resource.run_action(:create) expect(explicit_aces).to eq(denied_acl(SID.Guest, expected_full_control_perms)) end it "Sets multiple rights" do resource.rights(:read, "Everyone") resource.rights(:modify, "Guest") resource.run_action(:create) expect(explicit_aces).to eq( allowed_acl(SID.Everyone, expected_read_perms) + allowed_acl(SID.Guest, expected_modify_perms) ) end it "Sets deny_rights ahead of rights" do resource.rights(:read, "Everyone") resource.deny_rights(:modify, "Guest") resource.run_action(:create) expect(explicit_aces).to eq( denied_acl(SID.Guest, expected_modify_perms) + allowed_acl(SID.Everyone, expected_read_perms) ) end it "Sets deny_rights ahead of rights when specified in reverse order" do resource.deny_rights(:modify, "Guest") resource.rights(:read, "Everyone") resource.run_action(:create) expect(explicit_aces).to eq( denied_acl(SID.Guest, expected_modify_perms) + allowed_acl(SID.Everyone, expected_read_perms) ) end end context "with a mode attribute" do if windows? Security ||= Chef::ReservedNames::Win32::API::Security # rubocop:disable Style/ConstantName end it "respects mode in string form as an octal number" do #on windows, mode cannot modify owner and/or group permissons #unless the owner and/or group as appropriate is specified resource.mode "400" resource.owner "Guest" resource.group "Everyone" resource.run_action(:create) expect(explicit_aces).to eq([ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ) ]) end it "respects mode in numeric form as a ruby-interpreted octal" do resource.mode 0700 resource.owner "Guest" resource.run_action(:create) expect(explicit_aces).to eq([ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_WRITE | Security::FILE_GENERIC_EXECUTE | Security::DELETE) ]) end it "respects the owner, group and everyone bits of mode" do resource.mode 0754 resource.owner "Guest" resource.group "Administrators" resource.run_action(:create) expect(explicit_aces).to eq([ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_WRITE | Security::FILE_GENERIC_EXECUTE | Security::DELETE), ACE.access_allowed(SID.Administrators, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_EXECUTE), ACE.access_allowed(SID.Everyone, Security::FILE_GENERIC_READ), ]) end it "respects the individual read, write and execute bits of mode" do resource.mode 0421 resource.owner "Guest" resource.group "Administrators" resource.run_action(:create) expect(explicit_aces).to eq([ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ), ACE.access_allowed(SID.Administrators, Security::FILE_GENERIC_WRITE | Security::DELETE), ACE.access_allowed(SID.Everyone, Security::FILE_GENERIC_EXECUTE), ]) end it "warns when mode tries to set owner bits but owner is not specified" do @warn = [] allow(Chef::Log).to receive(:warn) { |msg| @warn << msg } resource.mode 0400 resource.run_action(:create) expect(@warn.include?("Mode 400 includes bits for the owner, but owner is not specified")).to be_truthy end it "warns when mode tries to set group bits but group is not specified" do @warn = [] allow(Chef::Log).to receive(:warn) { |msg| @warn << msg } resource.mode 0040 resource.run_action(:create) expect(@warn.include?("Mode 040 includes bits for the group, but group is not specified")).to be_truthy end end it "does not inherit aces if inherits is set to false" do # We need at least one ACE if we're creating a securable without # inheritance resource.rights(:full_control, "Administrators") resource.inherits(false) resource.run_action(:create) descriptor.dacl.each do |ace| expect(ace.inherited?).to eq(false) end end it "has the inheritable acls of parent directory if no acl is specified" do expect(File.exist?(path)).to eq(false) # Collect the inheritable acls form the parent by creating a file without # any specific ACLs parent_acls = parent_inheritable_acls # On certain flavors of Windows the default list of ACLs sometimes includes # non-inherited ACLs. Filter them out here. parent_inherited_acls = parent_acls.dacl.collect do |ace| ace.inherited? end resource.run_action(:create) # Similarly filter out the non-inherited ACLs resource_inherited_acls = descriptor.dacl.collect do |ace| ace.inherited? end expect(resource_inherited_acls).to eq(parent_inherited_acls) end end end chef-12.14.60/spec/support/shared/functional/securable_resource_with_reporting.rb000066400000000000000000000311071276456504500302750ustar00rootroot00000000000000 require "functional/resource/base" ALL_EXPANDED_PERMISSIONS = ["generic read", "generic write", "generic execute", "generic all", "delete", "read permissions", "change permissions", "take ownership", "synchronize", "access system security", "read data / list directory", "write data / add file", "append data / add subdirectory", "read extended attributes", "write extended attributes", "execute / traverse", "delete child", "read attributes", "write attributes"] shared_examples_for "a securable resource with reporting" do include_context "diff disabled" let(:current_resource) do provider = resource.provider_for_action(resource.action) provider.load_current_resource provider.current_resource end # Default mode varies based on implementation. Providers that use a tempfile # will default to 0600. Providers that use File.open will default to 0666 - # umask # let(:default_mode) { (0666 & ~File.umask).to_s(8) } describe "reading file security metadata for reporting on unix", :unix_only => true do # According to POSIX standard created files get either the # effective gid of the process or inherits the gid of the parent # directory based on file system. Since it's hard to guess what # would happen on each platform we create a dummy file and see # what the group name should be. before do FileUtils.touch(path) @expected_gid = File.stat(path).gid @expected_group_name = Etc.getgrgid(@expected_gid).name FileUtils.rm_rf(path) end context "when the target file doesn't exist" do before do resource.action(:create) end it "has empty values for file metadata in 'current_resource'" do expect(current_resource.owner).to be_nil expect(current_resource.group).to be_nil expect(current_resource.mode).to be_nil end context "and no security metadata is specified in new_resource" do it "sets the metadata values on the new_resource as strings after creating" do resource.run_action(:create) # TODO: most stable way to specify? expect(resource.owner).to eq(Etc.getpwuid(Process.uid).name) expect(resource.group).to eq(@expected_group_name) expect(resource.mode).to eq("0#{default_mode}") end end context "and owner is specified with a String (username) in new_resource", :requires_root => true do # TODO/bug: duplicated from the "securable resource" tests if ohai[:platform] == "aix" let(:expected_user_name) { "guest" } else let(:expected_user_name) { "nobody" } end before do resource.owner(expected_user_name) resource.run_action(:create) end it "sets the owner on new_resource to the username (String) of the desired owner" do expect(resource.owner).to eq(expected_user_name) end end context "and owner is specified with an Integer (uid) in new_resource", :requires_root => true do # TODO: duplicated from "securable resource" if ohai[:platform] == "aix" let(:expected_user_name) { "guest" } else let(:expected_user_name) { "nobody" } end let(:expected_uid) { Etc.getpwnam(expected_user_name).uid } let(:desired_gid) { 1337 } let(:expected_gid) { 1337 } before do resource.owner(expected_uid) resource.run_action(:create) end it "sets the owner on new_resource to the uid (Integer) of the desired owner" do expect(resource.owner).to eq(expected_uid) end end context "and group is specified with a String (group name)", :requires_root => true do let(:expected_group_name) { Etc.getgrent.name } before do resource.group(expected_group_name) resource.run_action(:create) end it "sets the group on new_resource to the group name (String) of the group" do expect(resource.group).to eq(expected_group_name) end end context "and group is specified with an Integer (gid)", :requires_root => true do let(:expected_gid) { Etc.getgrent.gid } before do resource.group(expected_gid) resource.run_action(:create) end it "sets the group on new_resource to the gid (Integer)" do expect(resource.group).to eq(expected_gid) end end context "and mode is specified as a String" do # Need full permission for owner here or else remote directory gets # into trouble trying to manage nested directories let(:set_mode) { "0740" } let(:expected_mode) { "0740" } before do resource.mode(set_mode) resource.run_action(:create) end it "sets mode on the new_resource as a String" do expect(resource.mode).to eq(expected_mode) end end context "and mode is specified as an Integer" do let(:set_mode) { 00740 } let(:expected_mode) { "0740" } before do resource.mode(set_mode) resource.run_action(:create) end it "sets mode on the new resource as a String" do expect(resource.mode).to eq(expected_mode) end end end context "when the target file exists" do before do FileUtils.touch(resource.path) resource.action(:create) end context "and no security metadata is specified in new_resource" do it "sets the current values on current resource as strings" do # TODO: most stable way to specify? expect(current_resource.owner).to eq(Etc.getpwuid(Process.uid).name) expect(current_resource.group).to eq(@expected_group_name) expect(current_resource.mode).to eq("0#{(0666 & ~File.umask).to_s(8)}") end end context "and owner is specified with a String (username) in new_resource" do let(:expected_user_name) { Etc.getpwuid(Process.uid).name } before do resource.owner(expected_user_name) end it "sets the owner on new_resource to the username (String) of the desired owner" do expect(current_resource.owner).to eq(expected_user_name) end end context "and owner is specified with an Integer (uid) in new_resource" do let(:expected_uid) { Process.uid } before do resource.owner(expected_uid) end it "sets the owner on new_resource to the uid (Integer) of the desired owner" do expect(current_resource.owner).to eq(expected_uid) end end context "and group is specified with a String (group name)" do before do resource.group(@expected_group_name) end it "sets the group on new_resource to the group name (String) of the group" do expect(current_resource.group).to eq(@expected_group_name) end end context "and group is specified with an Integer (gid)" do before do resource.group(@expected_gid) end it "sets the group on new_resource to the gid (Integer)" do expect(current_resource.group).to eq(@expected_gid) end end context "and mode is specified as a String" do let(:default_create_mode) { 0666 & ~File.umask } let(:expected_mode) { "0#{default_create_mode.to_s(8)}" } before do resource.mode(expected_mode) end it "sets mode on the new_resource as a String" do expect(current_resource.mode).to eq(expected_mode) end end context "and mode is specified as an Integer" do let(:set_mode) { 0666 & ~File.umask } let(:expected_mode) { "0#{set_mode.to_s(8)}" } before do resource.mode(set_mode) end it "sets mode on the new resource as a String" do expect(current_resource.mode).to eq(expected_mode) end end end end describe "reading file security metadata for reporting on windows", :windows_only do context "when the target file doesn't exist" do # Windows reporting data should look like this (+/- ish): # { "owner" => "bob", "checksum" => "ffff", "access control" => { "bob" => { "permissions" => ["perm1", "perm2", ...], "flags" => [] }}} before do resource.action(:create) end it "has empty values for file metadata in 'current_resource'" do skip "windows reporting not yet fully supported" expect(current_resource.owner).to be_nil expect(current_resource.expanded_rights).to be_nil end context "and no security metadata is specified in new_resource" do before do skip "windows reporting not yet fully supported" end it "sets the metadata values on the new_resource as strings after creating" do resource.run_action(:create) # TODO: most stable way to specify? expect(resource.owner).to eq(etc.getpwuid(process.uid).name) expect(resource.state[:expanded_rights]).to eq({ "CURRENTUSER" => { "permissions" => ALL_EXPANDED_PERMISSIONS, "flags" => [] } }) expect(resource.state[:expanded_deny_rights]).to eq({}) expect(resource.state[:inherits]).to be_truthy end end context "and owner is specified with a string (username) in new_resource" do # TODO/bug: duplicated from the "securable resource" tests let(:expected_user_name) { "Guest" } before do resource.owner(expected_user_name) resource.run_action(:create) end it "sets the owner on new_resource to the username (string) of the desired owner" do expect(resource.owner).to eq(expected_user_name) end end context "and owner is specified with a fully qualified domain user" do # TODO: duplicated from "securable resource" let(:expected_user_name) { 'domain\user' } before do skip "windows reporting not yet fully supported" resource.owner(expected_user_name) resource.run_action(:create) end it "sets the owner on new_resource to the fully qualified name of the desired owner" do expect(resource.owner).to eq(expected_user_name) end end end context "when the target file exists" do before do skip "windows reporting not yet fully supported" FileUtils.touch(resource.path) resource.action(:create) end context "and no security metadata is specified in new_resource" do it "sets the current values on current resource as strings" do # TODO: most stable way to specify? expect(current_resource.owner).to eq(etc.getpwuid(process.uid).name) expect(current_resource.expanded_rights).to eq({ "CURRENTUSER" => ALL_EXPANDED_PERMISSIONS }) end end context "and owner is specified with a string (username) in new_resource" do let(:expected_user_name) { etc.getpwuid(process.uid).name } before do resource.owner(expected_user_name) end it "sets the owner on current_resource to the username (string) of the desired owner" do expect(current_resource.owner).to eq(expected_user_name) end end context "and owner is specified as a fully qualified 'domain\\user' in new_resource" do let(:expected_user_name) { 'domain\user' } before do resource.owner(expected_user_name) end it "sets the owner on current_resource to the fully qualified name of the desired owner" do expect(current_resource.owner).to eq(expected_uid) end end context "and access rights are specified on the new_resource" do # TODO: before do blah it "sets the expanded_rights on the current resource" do skip end end context "and no access rights are specified on the current resource" do # TODO: before do blah it "sets the expanded rights on the current resource" do skip end end end end end chef-12.14.60/spec/support/shared/functional/win32_service.rb000066400000000000000000000031011276456504500237500ustar00rootroot00000000000000 require "chef/application/windows_service_manager" shared_context "using Win32::Service" do # Some helper methods. def test_service_exists? ::Win32::Service.exists?("spec-service") end def test_service_state ::Win32::Service.status("spec-service").current_state end def service_manager Chef::Application::WindowsServiceManager.new(test_service) end def cleanup # Uninstall if the test service is installed. if test_service_exists? # We can only uninstall when the service is stopped. if test_service_state != "stopped" ::Win32::Service.send("stop", "spec-service") sleep 1 while test_service_state != "stopped" end ::Win32::Service.delete("spec-service") end # Delete the test_service_file if it exists if File.exists?(test_service_file) File.delete(test_service_file) end end # Definition for the test-service let(:test_service) do { :service_name => "spec-service", :service_display_name => "Spec Test Service", :service_description => "Service for testing Chef::Application::WindowsServiceManager.", :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), "../../platforms/win32/spec_service.rb")), :delayed_start => true, } end # Test service creates a file for us to verify that it is running. # Since our test service is running as Local System we should look # for the file it creates under SYSTEM temp directory let(:test_service_file) do "#{ENV['SystemDrive']}\\windows\\temp\\spec_service_file" end end chef-12.14.60/spec/support/shared/functional/windows_script.rb000066400000000000000000000154761276456504500243660ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-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. # # Shared context used by both Powershell and Batch script provider # tests. require "chef/platform/query_helpers" shared_context Chef::Resource::WindowsScript do before(:all) do @ohai_reader = Ohai::System.new @ohai_reader.all_plugins(%w{platform kernel}) new_node = Chef::Node.new new_node.consume_external_attrs(@ohai_reader.data, {}) events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(new_node, {}, events) end let(:script_output_path) do File.join(Dir.tmpdir, make_tmpname("windows_script_test")) end before(:each) do File.delete(script_output_path) if File.exists?(script_output_path) end after(:each) do File.delete(script_output_path) if File.exists?(script_output_path) end let!(:resource) do Chef::Resource::WindowsScript::Batch.new("Batch resource functional test", @run_context) end shared_examples_for "a script resource with architecture attribute" do context "with the given architecture attribute value" do let(:expected_architecture) do if resource_architecture expected_architecture = resource_architecture else expected_architecture = @ohai_reader.data["kernel"]["machine"].to_sym end end let(:expected_architecture_output) do expected_architecture == :i386 ? "X86" : "AMD64" end let(:guard_script_suffix) do "guard" end let(:guard_script_output_path) do "#{script_output_path}#{guard_script_suffix}" end let(:resource_command) do "#{architecture_command} #{output_command} #{script_output_path}" end let(:resource_guard_command) do "#{architecture_command} #{output_command} #{guard_script_output_path}" end before(:each) do resource.code resource_command (resource.architecture resource_architecture) if resource_architecture resource.returns(0) end it "creates a process with the expected architecture" do resource.run_action(:run) expect(get_process_architecture).to eq(expected_architecture_output.downcase) end it "executes guards with the same architecture as the resource" do resource.only_if resource_guard_command resource.run_action(:run) expect(get_process_architecture).to eq(expected_architecture_output.downcase) expect(get_guard_process_architecture).to eq(expected_architecture_output.downcase) expect(get_guard_process_architecture).to eq(get_process_architecture) end context "when the guard's architecture is specified as 64-bit" do let (:guard_architecture) { :x86_64 } it "executes a 64-bit guard", :windows64_only do resource.only_if resource_guard_command, :architecture => guard_architecture resource.run_action(:run) expect(get_guard_process_architecture).to eq("amd64") end end context "when the guard's architecture is specified as 32-bit", :not_supported_on_nano do let (:guard_architecture) { :i386 } it "executes a 32-bit guard" do resource.only_if resource_guard_command, :architecture => guard_architecture resource.run_action(:run) expect(get_guard_process_architecture).to eq("x86") end end context "when the guard's architecture is specified as 32-bit", :windows_nano_only do let (:guard_architecture) { :i386 } it "raises an error" do resource.only_if resource_guard_command, :architecture => guard_architecture expect { resource.run_action(:run) }.to raise_error( Chef::Exceptions::Win32ArchitectureIncorrect, /cannot execute script with requested architecture 'i386' on Windows Nano Server/) end end end end shared_examples_for "a Windows script running on Windows" do describe "when the run action is invoked on Windows" do it "executes the script code" do resource.code("whoami > \"#{script_output_path}\"") resource.returns(0) resource.run_action(:run) end end context "when $env:TMP has a space" do before(:each) do @dir = Dir.mktmpdir("Jerry Smith") @original_env = ENV.to_hash.dup ENV.delete("TMP") ENV["TMP"] = @dir end after(:each) do FileUtils.remove_entry_secure(@dir) ENV.clear ENV.update(@original_env) end it "executes the script code" do resource.code("whoami > \"#{script_output_path}\"") resource.returns(0) resource.run_action(:run) end end context "when evaluating guards" do it "has a guard_interpreter attribute set to the short name of the resource" do pending "powershell.exe always exits with 0 on nano" if Chef::Platform.windows_nano_server? expect(resource.guard_interpreter).to eq(resource.resource_name) resource.not_if "findstr.exe /thiscommandhasnonzeroexitstatus" expect(Chef::Resource).to receive(:resource_for_node).and_call_original expect(resource.class).to receive(:new).and_call_original expect(resource.should_skip?(:run)).to be_falsey end end context "when the architecture attribute is not set" do let(:resource_architecture) { nil } it_behaves_like "a script resource with architecture attribute" end context "when the architecture attribute is :i386", :not_supported_on_nano do let(:resource_architecture) { :i386 } it_behaves_like "a script resource with architecture attribute" end context "when the architecture attribute is :x86_64" do let(:resource_architecture) { :x86_64 } it_behaves_like "a script resource with architecture attribute" end end def get_windows_script_output(suffix = "") File.read("#{script_output_path}#{suffix}") end def source_contains_case_insensitive_content?( source, content ) source.downcase.include?(content.downcase) end def get_guard_process_architecture get_process_architecture(guard_script_suffix) end def get_process_architecture(suffix = "") get_windows_script_output(suffix).strip.downcase end end chef-12.14.60/spec/support/shared/integration/000077500000000000000000000000001276456504500211275ustar00rootroot00000000000000chef-12.14.60/spec/support/shared/integration/app_server_support.rb000066400000000000000000000023031276456504500254140ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # Copyright:: Copyright 2012-2016, 2013-2015 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 "rack" require "stringio" module AppServerSupport def start_app_server(app, port) server = nil thread = Thread.new do Rack::Handler::WEBrick.run(app, :Port => 9018, :AccessLog => [], :Logger => WEBrick::Log.new(StringIO.new, 7) ) do |found_server| server = found_server end end Timeout.timeout(30) do sleep(0.01) until server && server.status == :Running end [server, thread] end end chef-12.14.60/spec/support/shared/integration/integration_helper.rb000066400000000000000000000110121276456504500253310ustar00rootroot00000000000000# # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # 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 "tmpdir" require "fileutils" require "chef/config" require "chef/json_compat" require "chef/server_api" require "support/shared/integration/knife_support" require "support/shared/integration/app_server_support" require "cheffish/rspec/chef_run_support" require "spec_helper" module Cheffish class BasicChefClient def_delegators :@run_context, :before_notifications end end module IntegrationSupport include ChefZero::RSpec def self.included(includer_class) includer_class.extend(Cheffish::RSpec::ChefRunSupport) includer_class.extend(ClassMethods) end module ClassMethods include ChefZero::RSpec def when_the_repository(desc, *tags, &block) context("when the chef repo #{desc}", *tags) do include_context "with a chef repo" module_eval(&block) end end def with_versioned_cookbooks(&block) context("with versioned cookbooks") do include_context "with versioned cookbooks" module_eval(&block) end end end def api Chef::ServerAPI.new end def directory(relative_path, &block) old_parent_path = @parent_path @parent_path = path_to(relative_path) FileUtils.mkdir_p(@parent_path) instance_eval(&block) if block @parent_path = old_parent_path end def file(relative_path, contents) filename = path_to(relative_path) dir = File.dirname(filename) FileUtils.mkdir_p(dir) unless dir == "." File.open(filename, "w") do |file| raw = case contents when Hash, Array Chef::JSONCompat.to_json_pretty(contents) else contents end file.write(raw) end end def symlink(relative_path, relative_dest) filename = path_to(relative_path) dir = File.dirname(filename) FileUtils.mkdir_p(dir) unless dir == "." dest_filename = path_to(relative_dest) File.symlink(dest_filename, filename) end def path_to(relative_path) File.expand_path(relative_path, (@parent_path || @repository_dir)) end def cb_metadata(name, version, extra_text = "") "name #{name.inspect}; version #{version.inspect}#{extra_text}" end def cwd(relative_path) @old_cwd = Dir.pwd Dir.chdir(path_to(relative_path)) end RSpec.shared_context "with a chef repo" do before :each do raise "Can only create one directory per test" if @repository_dir @repository_dir = Dir.mktmpdir("chef_repo") Chef::Config.chef_repo_path = @repository_dir %w{client cookbook data_bag environment node role user}.each do |object_name| Chef::Config.delete("#{object_name}_path".to_sym) end end after :each do if @repository_dir begin %w{client cookbook data_bag environment node role user}.each do |object_name| Chef::Config.delete("#{object_name}_path".to_sym) end Chef::Config.delete(:chef_repo_path) # TODO: "force" actually means "silence all exceptions". this # silences a weird permissions error on Windows that we should track # down, but for now there's no reason for it to blow up our CI. FileUtils.remove_entry_secure(@repository_dir, force = Chef::Platform.windows?) ensure @repository_dir = nil end end Dir.chdir(@old_cwd) if @old_cwd end end # Versioned cookbooks RSpec.shared_context "with versioned cookbooks", :versioned_cookbooks => true do before(:each) { Chef::Config[:versioned_cookbooks] = true } after(:each) { Chef::Config.delete(:versioned_cookbooks) } end RSpec.shared_context "without versioned cookbooks", :versioned_cookbooks => false do # Just make sure this goes back to default before(:each) { Chef::Config[:versioned_cookbooks] = false } after(:each) { Chef::Config.delete(:versioned_cookbooks) } end end chef-12.14.60/spec/support/shared/integration/knife_support.rb000066400000000000000000000132661276456504500243540ustar00rootroot00000000000000# # Author:: John Keiser () # Copyright:: Copyright 2013-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/knife" require "chef/application/knife" require "logger" require "chef/log" require "chef/chef_fs/file_system_cache" module KnifeSupport DEBUG = ENV["DEBUG"] def knife(*args, input: nil) # Allow knife('role from file roles/blah.json') rather than requiring the # arguments to be split like knife('role', 'from', 'file', 'roles/blah.json') # If any argument will have actual spaces in it, the long form is required. # (Since knife commands always start with the command name, and command # names with spaces are always multiple args, this is safe.) if args.length == 1 args = args[0].split(/\s+/) end # Make output stable Chef::Config[:concurrency] = 1 # Work on machines where we can't access /var Dir.mktmpdir("checksums") do |checksums_cache_dir| Chef::Config[:cache_options] = { :path => checksums_cache_dir, :skip_expires => true, } # This is Chef::Knife.run without load_commands--we'll load stuff # ourselves, thank you very much stdout = StringIO.new stderr = StringIO.new stdin = if input StringIO.new(input) else STDIN end old_loggers = Chef::Log.loggers old_log_level = Chef::Log.level begin puts "knife: #{args.join(' ')}" if DEBUG subcommand_class = Chef::Knife.subcommand_class_from(args) subcommand_class.options = Chef::Application::Knife.options.merge(subcommand_class.options) subcommand_class.load_deps instance = subcommand_class.new(args) # Capture stdout/stderr instance.ui = Chef::Knife::UI.new(stdout, stderr, stdin, disable_editing: true) # Don't print stuff Chef::Config[:verbosity] = ( DEBUG ? 2 : 0 ) instance.config[:config_file] = File.join(CHEF_SPEC_DATA, "null_config.rb") # Ensure the ChefFS cache is empty Chef::ChefFS::FileSystemCache.instance.reset! # Configure chef with a (mostly) blank knife.rb # We set a global and then mutate it in our stub knife.rb so we can be # extra sure that we're not loading someone's real knife.rb and then # running test scenarios against a real chef server. If things don't # smell right, abort. $__KNIFE_INTEGRATION_FAILSAFE_CHECK = "ole" instance.configure_chef unless $__KNIFE_INTEGRATION_FAILSAFE_CHECK == "ole ole" raise Exception, "Potential misconfiguration of integration tests detected. Aborting test." end logger = Logger.new(stderr) logger.formatter = proc { |severity, datetime, progname, msg| "#{severity}: #{msg}\n" } Chef::Log.use_log_devices([logger]) Chef::Log.level = ( DEBUG ? :debug : :warn ) Chef::Log::Formatter.show_time = false instance.run_with_pretty_exceptions(true) exit_code = 0 # This is how rspec catches exit() rescue SystemExit => e exit_code = e.status ensure Chef::Log.use_log_devices(old_loggers) Chef::Log.level = old_log_level Chef::Config.delete(:cache_options) Chef::Config.delete(:concurrency) end KnifeResult.new(stdout.string, stderr.string, exit_code) end end class KnifeResult include ::RSpec::Matchers def initialize(stdout, stderr, exit_code) @stdout = stdout @stderr = stderr @exit_code = exit_code end attr_reader :stdout attr_reader :stderr attr_reader :exit_code def should_fail(*args) expected = {} args.each do |arg| if arg.is_a?(Hash) expected.merge!(arg) elsif arg.is_a?(Integer) expected[:exit_code] = arg else expected[:stderr] = arg end end expected[:exit_code] = 1 if !expected[:exit_code] should_result_in(expected) end def should_succeed(*args) expected = {} args.each do |arg| if arg.is_a?(Hash) expected.merge!(arg) else expected[:stdout] = arg end end should_result_in(expected) end private def should_result_in(expected) expected[:stdout] = "" if !expected[:stdout] expected[:stderr] = "" if !expected[:stderr] expected[:exit_code] = 0 if !expected[:exit_code] # TODO make this go away stderr_actual = @stderr.sub(/^WARNING: No knife configuration file found\n/, "") if expected[:stderr].is_a?(Regexp) expect(stderr_actual).to match(expected[:stderr]) else expect(stderr_actual).to eq(expected[:stderr]) end stdout_actual = @stdout if Chef::Platform.windows? stderr_actual = stderr_actual.gsub("\r\n", "\n") stdout_actual = stdout_actual.gsub("\r\n", "\n") end expect(@exit_code).to eq(expected[:exit_code]) if expected[:stdout].is_a?(Regexp) expect(stdout_actual).to match(expected[:stdout]) else expect(stdout_actual).to eq(expected[:stdout]) end end end end chef-12.14.60/spec/support/shared/matchers/000077500000000000000000000000001276456504500204125ustar00rootroot00000000000000chef-12.14.60/spec/support/shared/matchers/exit_with_code.rb000066400000000000000000000013261276456504500237370ustar00rootroot00000000000000require "rspec/expectations" # Lifted from http://stackoverflow.com/questions/1480537/how-can-i-validate-exits-and-aborts-in-rspec RSpec::Matchers.define :exit_with_code do |exp_code| actual = nil match do |block| begin block.call rescue SystemExit => e actual = e.status end actual && actual == exp_code end failure_message do |block| "expected block to call exit(#{exp_code}) but exit" + (actual.nil? ? " not called" : "(#{actual}) was called") end failure_message_when_negated do |block| "expected block not to call exit(#{exp_code})" end description do "expect block to call exit(#{exp_code})" end def supports_block_expectations? true end end chef-12.14.60/spec/support/shared/matchers/match_environment_variable.rb000066400000000000000000000007371276456504500263330ustar00rootroot00000000000000 require "rspec/expectations" require "spec/support/platform_helpers" RSpec::Matchers.define :match_environment_variable do |varname| match do |actual| expected = if windows? && ENV[varname].nil? # On Windows, if an environment variable is not set, the command # `echo %VARNAME%` outputs %VARNAME% "%#{varname}%" else ENV[varname].to_s end actual == expected end end chef-12.14.60/spec/support/shared/shared_examples.rb000066400000000000000000000006711276456504500223010ustar00rootroot00000000000000# For storing any examples shared between multiple tests # Any object which defines a .to_json should import this test shared_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) do raise "You must define the subject when including this test" end it "should allow consumers to call #to_json or Chef::JSONCompat.to_json" do expect(jsonable.to_json).to eq(Chef::JSONCompat.to_json(jsonable)) end end chef-12.14.60/spec/support/shared/unit/000077500000000000000000000000001276456504500175635ustar00rootroot00000000000000chef-12.14.60/spec/support/shared/unit/api_error_inspector.rb000066400000000000000000000150661276456504500241700ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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. # # == API Error Inspector Examples # These tests are work in progress. They exercise the code enough to ensure it # runs without error, but don't make assertions about the output. This is # because aspects such as how information gets formatted, what's included, etc. # are still in flux. When testing an inspector, change the outputter to use # STDOUT and manually check the output. shared_examples_for "an api error inspector" do before do @node_name = "test-node.example.com" @config = { :validation_client_name => "testorg-validator", :validation_key => "/etc/chef/testorg-validator.pem", :chef_server_url => "https://chef-api.example.com", :node_name => "testnode-name", :client_key => "/etc/chef/client.pem", } @description = Chef::Formatters::ErrorDescription.new("Error registering the node:") @outputter = Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR) #@outputter = Chef::Formatters::IndentableOutputStream.new(STDOUT, STDERR) end describe "when explaining a network error" do before do @exception = Errno::ECONNREFUSED.new("connection refused") @inspector = described_class.new(@node_name, @exception, @config) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end describe "when explaining a 'private key missing' error" do before do @exception = Chef::Exceptions::PrivateKeyMissing.new("no private key yo") @inspector = described_class.new(@node_name, @exception, @config) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end describe "when explaining a 401 caused by clock skew" do before do @response_body = "synchronize the clock on your host" @response = Net::HTTPUnauthorized.new("1.1", "401", "(response) unauthorized") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) unauthorized", @response) @inspector = described_class.new(@node_name, @exception, @config) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end describe "when explaining a 401 (no clock skew)" do before do @response_body = "check your key and node name" @response = Net::HTTPUnauthorized.new("1.1", "401", "(response) unauthorized") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) unauthorized", @response) @inspector = described_class.new(@node_name, @exception, @config) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end describe "when explaining a 403" do before do @response_body = "forbidden" @response = Net::HTTPForbidden.new("1.1", "403", "(response) forbidden") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) forbidden", @response) @inspector = described_class.new(@node_name, @exception, @config) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end describe "when explaining a 400" do before do @response_body = "didn't like your data" @response = Net::HTTPBadRequest.new("1.1", "400", "(response) bad request") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) bad request", @response) @inspector = described_class.new(@node_name, @exception, @config) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end describe "when explaining a 404" do before do @response_body = "probably caused by a redirect to a get" @response = Net::HTTPNotFound.new("1.1", "404", "(response) not found") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) not found", @response) @inspector = described_class.new(@node_name, @exception, @config) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end describe "when explaining a 500" do before do @response_body = "sad trombone" @response = Net::HTTPInternalServerError.new("1.1", "500", "(response) internal server error") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPFatalError.new("(exception) internal server error", @response) @inspector = described_class.new(@node_name, @exception, @config) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end describe "when explaining a 503" do before do @response_body = "sad trombone orchestra" @response = Net::HTTPBadGateway.new("1.1", "502", "(response) bad gateway") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPFatalError.new("(exception) bad gateway", @response) @inspector = described_class.new(@node_name, @exception, @config) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end describe "when explaining an unknown error" do before do @exception = RuntimeError.new("(exception) something went wrong") @inspector = described_class.new(@node_name, @exception, @config) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end end chef-12.14.60/spec/support/shared/unit/api_versioning.rb000066400000000000000000000054441276456504500231330ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/exceptions" shared_examples_for "version handling" do let(:response_406) { OpenStruct.new(:code => "406") } let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } before do allow(rest_v1).to receive(http_verb).and_raise(exception_406) end context "when the server does not support the min or max server API version that Chef::UserV1 supports" do before do allow(object).to receive(:server_client_api_version_intersection).and_return([]) end it "raises the original exception" do expect { object.send(method) }.to raise_error(exception_406) end end # when the server does not support the min or max server API version that Chef::UserV1 supports end # version handling shared_examples_for "user and client reregister" do let(:response_406) { OpenStruct.new(:code => "406") } let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } let(:generic_exception) { Exception.new } let(:min_version) { "2" } let(:max_version) { "5" } let(:return_hash_406) do { "min_version" => min_version, "max_version" => max_version, "request_version" => "30", } end context "when V0 is not supported by the server" do context "when the exception is 406 and returns x-ops-server-api-version header" do before do allow(rest_v0).to receive(:put).and_raise(exception_406) allow(response_406).to receive(:[]).with("x-ops-server-api-version").and_return(Chef::JSONCompat.to_json(return_hash_406)) end it "raises an error about only V0 being supported" do expect(object).to receive(:reregister_only_v0_supported_error_msg).with(max_version, min_version) expect { object.reregister }.to raise_error(Chef::Exceptions::OnlyApiVersion0SupportedForAction) end end context "when the exception is not versioning related" do before do allow(rest_v0).to receive(:put).and_raise(generic_exception) end it "raises the original error" do expect { object.reregister }.to raise_error(generic_exception) end end end end chef-12.14.60/spec/support/shared/unit/application_dot_d.rb000066400000000000000000000057331276456504500235740ustar00rootroot00000000000000# # 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. # shared_examples_for "an application that loads a dot d" do before do Chef::Config[dot_d_config_name] = client_d_dir end context "when client_d_dir is set to nil" do let(:client_d_dir) { nil } it "does not raise an exception" do expect { app.reconfigure }.not_to raise_error end end context "when client_d_dir is set to a directory with configuration" do # We're not going to mock out globbing the directory. We want to # make sure that we are correctly globbing. let(:client_d_dir) do Chef::Util::PathHelper.cleanpath( File.join(File.dirname(__FILE__), "../../../data/client.d_00")) end it "loads the configuration in order" do expect(IO).to receive(:read).with(Pathname.new("#{client_d_dir}/00-foo.rb").cleanpath.to_s).and_return("foo 0") expect(IO).to receive(:read).with(Pathname.new("#{client_d_dir}/01-bar.rb").cleanpath.to_s).and_return("bar 0") allow(app).to receive(:apply_config).with(anything(), Chef::Config.platform_specific_path("/etc/chef/client.rb")).and_call_original.ordered expect(app).to receive(:apply_config).with("foo 0", Pathname.new("#{client_d_dir}/00-foo.rb").cleanpath.to_s).and_call_original.ordered expect(app).to receive(:apply_config).with("bar 0", Pathname.new("#{client_d_dir}/01-bar.rb").cleanpath.to_s).and_call_original.ordered app.reconfigure end end context "when client_d_dir is set to a directory without configuration" do let(:client_d_dir) do Chef::Util::PathHelper.cleanpath( File.join(File.dirname(__FILE__), "../../data/client.d_01")) end # client.d_01 has a nested folder with a rb file that if # executed, would raise an exception. If it is executed, # it means we are loading configs that are deeply nested # inside of client.d. For example, client.d/foo/bar.rb # should not run, but client.d/foo.rb should. it "does not raise an exception" do expect { app.reconfigure }.not_to raise_error end end context "when client_d_dir is set to a directory containing a directory named foo.rb" do # foo.rb as a directory should be ignored let(:client_d_dir) do Chef::Util::PathHelper.cleanpath( File.join(File.dirname(__FILE__), "../../data/client.d_02")) end it "does not raise an exception" do expect { app.reconfigure }.not_to raise_error end end end chef-12.14.60/spec/support/shared/unit/execute_resource.rb000066400000000000000000000076071276456504500234730ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # 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" shared_examples_for "an execute resource" do before(:each) do @resource = execute_resource end it "should create a new Chef::Resource::Execute" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Execute) end it "should set the command to the first argument to new" do expect(@resource.command).to eql(resource_instance_name) end it "should accept an array on instantiation, too" do resource = Chef::Resource::Execute.new(%w{something else}) expect(resource).to be_a_kind_of(Chef::Resource) expect(resource).to be_a_kind_of(Chef::Resource::Execute) expect(resource.command).to eql(%w{something else}) end it "should accept a string for the command to run" do @resource.command "something" expect(@resource.command).to eql("something") end it "should accept an array for the command to run" do @resource.command %w{something else} expect(@resource.command).to eql(%w{something else}) end it "should accept a string for the cwd" do @resource.cwd "something" expect(@resource.cwd).to eql("something") end it "should accept a hash for the environment" do test_hash = { :one => :two } @resource.environment(test_hash) expect(@resource.environment).to eql(test_hash) end it "allows the environment to be specified with #env" do expect(@resource).to respond_to(:env) end it "should accept a string for the group" do @resource.group "something" expect(@resource.group).to eql("something") end it "should accept an integer for the group" do @resource.group 1 expect(@resource.group).to eql(1) end it "should accept an array for the execution path in Chef-12 and log deprecation message", chef: "< 13" do expect(Chef::Log).to receive(:warn).at_least(:once) @resource.path ["woot"] expect(@resource.path).to eql(["woot"]) end it "should raise an exception in chef-13", chef: ">= 13" do expect(@resource.path [ "woot" ]).to raise_error end it "should accept an integer for the return code" do @resource.returns 1 expect(@resource.returns).to eql(1) end it "should accept an integer for the timeout" do @resource.timeout 1 expect(@resource.timeout).to eql(1) end it "should accept a string for the user" do @resource.user "something" expect(@resource.user).to eql("something") end it "should accept an integer for the user" do @resource.user 1 expect(@resource.user).to eql(1) end it "should accept a string for creates" do @resource.creates "something" expect(@resource.creates).to eql("something") end it "should accept a boolean for live streaming" do @resource.live_stream true expect(@resource.live_stream).to be true end describe "when it has cwd, environment, group, path, return value, and a user" do before do @resource.command("grep") @resource.cwd("/tmp/") @resource.environment({ :one => :two }) @resource.group("legos") @resource.returns(1) @resource.user("root") end it "returns the command as its identity" do expect(@resource.identity).to eq("grep") end end end chef-12.14.60/spec/support/shared/unit/file_system_support.rb000066400000000000000000000044241276456504500242330ustar00rootroot00000000000000# # Author:: John Keiser () # 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 "chef/chef_fs/file_system" require "chef/chef_fs/file_system/memory/memory_root" require "chef/chef_fs/file_system/memory/memory_dir" require "chef/chef_fs/file_system/memory/memory_file" module FileSystemSupport def memory_fs(pretty_name, value, cannot_be_in_regex = nil) if !value.is_a?(Hash) raise "memory_fs() must take a Hash" end dir = Chef::ChefFS::FileSystem::Memory::MemoryRoot.new(pretty_name, cannot_be_in_regex) value.each do |key, child| dir.add_child(memory_fs_value(child, key.to_s, dir)) end dir end def memory_fs_value(value, name = "", parent = nil) if value.is_a?(Hash) dir = Chef::ChefFS::FileSystem::Memory::MemoryDir.new(name, parent) value.each do |key, child| dir.add_child(memory_fs_value(child, key.to_s, dir)) end dir else Chef::ChefFS::FileSystem::Memory::MemoryFile.new(name, parent, value || "#{name}\n") end end def pattern(p) Chef::ChefFS::FilePattern.new(p) end def return_paths(*expected) ReturnPaths.new(expected) end def no_blocking_calls_allowed [ Chef::ChefFS::FileSystem::Memory::MemoryFile, Chef::ChefFS::FileSystem::Memory::MemoryDir ].each do |c| [ :children, :exists?, :read ].each do |m| allow_any_instance_of(c).to receive(m).and_raise("#{m} should not be called") end end end def list_should_yield_paths(fs, pattern_str, *expected_paths) result_paths = [] Chef::ChefFS::FileSystem.list(fs, pattern(pattern_str)).each { |result| result_paths << result.path } expect(result_paths).to match_array(expected_paths) end end chef-12.14.60/spec/support/shared/unit/knife_shared.rb000066400000000000000000000023201276456504500225270ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # shared_examples_for "mandatory field missing" do context "when field is nil" do before do knife.name_args = name_args end it "exits 1" do expect { knife.run }.to raise_error(SystemExit) end it "prints the usage" do expect(knife).to receive(:show_usage) expect { knife.run }.to raise_error(SystemExit) end it "prints a relevant error message" do expect { knife.run }.to raise_error(SystemExit) expect(stderr.string).to match /You must specify a #{fieldname}/ end end end chef-12.14.60/spec/support/shared/unit/mock_shellout.rb000066400000000000000000000025431276456504500227640ustar00rootroot00000000000000# # Author:: John Keiser # Copyright:: Copyright 2015-2016, John Keiser. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # # Mocks shellout results. Examples: # mock_shellout_command("systemctl --all", exitstatus: 1) # class MockShellout module RSpec def mock_shellout_command(command, **result) allow(::Mixlib::ShellOut).to receive(:new).with(command, anything).and_return MockShellout.new(result) end end def initialize(**properties) @properties = { stdout: "", stderr: "", exitstatus: 0, }.merge(properties) end def method_missing(name, *args) @properties[name.to_sym] end def error? exitstatus != 0 end def error! raise Mixlib::ShellOut::ShellCommandFailed, "Expected process to exit with 0, but received #{exitstatus}" if error? end end chef-12.14.60/spec/support/shared/unit/platform_introspector.rb000066400000000000000000000175001276456504500245520ustar00rootroot00000000000000# # Author:: Seth Falcon () # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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. # shared_examples_for "a platform introspector" do before(:each) do @platform_hash = {} %w{openbsd freebsd}.each do |x| @platform_hash[x] = { "default" => x, "1.2.3" => "#{x}-1.2.3", } end @platform_hash["debian"] = { %w{5 6} => "debian-5/6", "default" => "debian" } @platform_hash["default"] = "default" # The following @platform_hash keys are used for testing version constraints @platform_hash["exact_match"] = { "1.2.3" => "exact", ">= 1.0" => "not exact" } @platform_hash["multiple_matches"] = { "~> 2.3.4" => "matched ~> 2.3.4", ">= 2.3" => "matched >=2.3" } @platform_hash["invalid_cookbook_version"] = { ">= 21" => "Matches a single number" } @platform_hash["successful_matches"] = { "< 3.0" => "matched < 3.0", ">= 3.0" => "matched >= 3.0" } @platform_family_hash = { "debian" => "debian value", [:rhel, :fedora] => "redhatty value", "suse" => "suse value", :default => "default value", } end it "returns a default value when there is no known platform" do node = Hash.new expect(platform_introspector.value_for_platform(@platform_hash)).to eq("default") end it "returns a default value when there is no known platform family" do expect(platform_introspector.value_for_platform_family(@platform_family_hash)).to eq("default value") end it "returns a default value when the current platform doesn't match" do node.automatic_attrs[:platform] = "not-a-known-platform" expect(platform_introspector.value_for_platform(@platform_hash)).to eq("default") end it "returns a default value when current platform_family doesn't match" do node.automatic_attrs[:platform_family] = "ultra-derived-linux" expect(platform_introspector.value_for_platform_family(@platform_family_hash)).to eq("default value") end it "returns a value based on the current platform" do node.automatic_attrs[:platform] = "openbsd" expect(platform_introspector.value_for_platform(@platform_hash)).to eq("openbsd") end it "returns a value based on the current platform family" do node.automatic_attrs[:platform_family] = "debian" expect(platform_introspector.value_for_platform_family(@platform_family_hash)).to eq("debian value") end it "returns a version-specific value based on the current platform" do node.automatic_attrs[:platform] = "openbsd" node.automatic_attrs[:platform_version] = "1.2.3" expect(platform_introspector.value_for_platform(@platform_hash)).to eq("openbsd-1.2.3") end it "returns a value based on the current platform if version not found" do node.automatic_attrs[:platform] = "openbsd" node.automatic_attrs[:platform_version] = "0.0.0" expect(platform_introspector.value_for_platform(@platform_hash)).to eq("openbsd") end it "returns the exact match" do node.automatic_attrs[:platform] = "exact_match" node.automatic_attrs[:platform_version] = "1.2.3" expect(platform_introspector.value_for_platform(@platform_hash)).to eq("exact") end it "raises RuntimeError" do node.automatic_attrs[:platform] = "multiple_matches" node.automatic_attrs[:platform_version] = "2.3.4" expect { platform_introspector.value_for_platform(@platform_hash) }.to raise_error(RuntimeError) end it "should not require .0 to match >= 21.0" do node.automatic_attrs[:platform] = "invalid_cookbook_version" node.automatic_attrs[:platform_version] = "21" expect(platform_introspector.value_for_platform(@platform_hash)).to eq("Matches a single number") end it "should return the value for that match" do node.automatic_attrs[:platform] = "successful_matches" node.automatic_attrs[:platform_version] = "2.9" expect(platform_introspector.value_for_platform(@platform_hash)).to eq("matched < 3.0") end describe "when platform versions is an array" do it "returns a version-specific value based on the current platform" do node.automatic_attrs[:platform] = "debian" node.automatic_attrs[:platform_version] = "6" expect(platform_introspector.value_for_platform(@platform_hash)).to eq("debian-5/6") end it "returns a value based on the current platform if version not found" do node.automatic_attrs[:platform] = "debian" node.automatic_attrs[:platform_version] = "0.0.0" expect(platform_introspector.value_for_platform(@platform_hash)).to eq("debian") end end describe "when checking platform?" do it "returns true if the node is a provided platform and platforms are provided as symbols" do node.automatic_attrs[:platform] = "ubuntu" expect(platform_introspector.platform?([:redhat, :ubuntu])).to eq(true) end it "returns true if the node is a provided platform and platforms are provided as strings" do node.automatic_attrs[:platform] = "ubuntu" expect(platform_introspector.platform?(%w{redhat ubuntu})).to eq(true) end it "returns false if the node is not of the provided platforms" do node.automatic_attrs[:platform] = "ubuntu" expect(platform_introspector.platform?(:splatlinux)).to eq(false) end end describe "when checking platform_family?" do it "returns true if the node is in a provided platform family and families are provided as symbols" do node.automatic_attrs[:platform_family] = "debian" expect(platform_introspector.platform_family?([:rhel, :debian])).to eq(true) end it "returns true if the node is a provided platform and platforms are provided as strings" do node.automatic_attrs[:platform_family] = "rhel" expect(platform_introspector.platform_family?(%w{rhel debian})).to eq(true) end it "returns false if the node is not of the provided platforms" do node.automatic_attrs[:platform_family] = "suse" expect(platform_introspector.platform_family?(:splatlinux)).to eq(false) end it "returns false if the node is not of the provided platforms and platform_family is not set" do expect(platform_introspector.platform_family?(:splatlinux)).to eq(false) end end # NOTE: this is a regression test for bug CHEF-1514 describe "when the value is an array" do before do @platform_hash = { "debian" => { "4.0" => [ :restart, :reload ], "default" => [ :restart, :reload, :status ] }, "ubuntu" => { "default" => [ :restart, :reload, :status ] }, "centos" => { "default" => [ :restart, :reload, :status ] }, "redhat" => { "default" => [ :restart, :reload, :status ] }, "fedora" => { "default" => [ :restart, :reload, :status ] }, "default" => { "default" => [:restart, :reload ] } } end it "returns the correct default for a given platform" do node.automatic_attrs[:platform] = "debian" node.automatic_attrs[:platform_version] = "9000" expect(platform_introspector.value_for_platform(@platform_hash)).to eq([ :restart, :reload, :status ]) end it "returns the correct platform+version specific value " do node.automatic_attrs[:platform] = "debian" node.automatic_attrs[:platform_version] = "4.0" expect(platform_introspector.value_for_platform(@platform_hash)).to eq([:restart, :reload]) end end end chef-12.14.60/spec/support/shared/unit/provider/000077500000000000000000000000001276456504500214155ustar00rootroot00000000000000chef-12.14.60/spec/support/shared/unit/provider/file.rb000066400000000000000000000775701276456504500227010ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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 "tmpdir" if windows? require "chef/win32/file" end # Filesystem stubs def file_symlink_class if windows? Chef::ReservedNames::Win32::File else File end end def normalized_path File.expand_path(resource_path) end # forwards-vs-reverse slashes on windows sucks def windows_path windows? ? normalized_path.tr('\\', "/") : normalized_path end # this is all getting a bit stupid, CHEF-4802 cut to remove all this def setup_normal_file [ resource_path, normalized_path, windows_path].each do |path| allow(File).to receive(:file?).with(path).and_return(true) allow(File).to receive(:exists?).with(path).and_return(true) allow(File).to receive(:exist?).with(path).and_return(true) allow(File).to receive(:directory?).with(path).and_return(false) allow(File).to receive(:writable?).with(path).and_return(true) allow(file_symlink_class).to receive(:symlink?).with(path).and_return(false) allow(File).to receive(:realpath?).with(path).and_return(normalized_path) end allow(File).to receive(:directory?).with(enclosing_directory).and_return(true) end def setup_missing_file [ resource_path, normalized_path, windows_path].each do |path| allow(File).to receive(:file?).with(path).and_return(false) allow(File).to receive(:realpath?).with(path).and_return(resource_path) allow(File).to receive(:exists?).with(path).and_return(false) allow(File).to receive(:exist?).with(path).and_return(false) allow(File).to receive(:directory?).with(path).and_return(false) allow(File).to receive(:writable?).with(path).and_return(false) allow(file_symlink_class).to receive(:symlink?).with(path).and_return(false) end allow(File).to receive(:directory?).with(enclosing_directory).and_return(true) end def setup_symlink [ resource_path, normalized_path, windows_path].each do |path| allow(File).to receive(:file?).with(path).and_return(true) allow(File).to receive(:realpath?).with(path).and_return(normalized_path) allow(File).to receive(:exists?).with(path).and_return(true) allow(File).to receive(:exist?).with(path).and_return(true) allow(File).to receive(:directory?).with(path).and_return(false) allow(File).to receive(:writable?).with(path).and_return(true) allow(file_symlink_class).to receive(:symlink?).with(path).and_return(true) end allow(File).to receive(:directory?).with(enclosing_directory).and_return(true) end def setup_unwritable_file [ resource_path, normalized_path, windows_path].each do |path| allow(File).to receive(:file?).with(path).and_return(false) allow(File).to receive(:realpath?).with(path).and_raise(Errno::ENOENT) allow(File).to receive(:exists?).with(path).and_return(true) allow(File).to receive(:exist?).with(path).and_return(true) allow(File).to receive(:directory?).with(path).and_return(false) allow(File).to receive(:writable?).with(path).and_return(false) allow(file_symlink_class).to receive(:symlink?).with(path).and_return(false) end allow(File).to receive(:directory?).with(enclosing_directory).and_return(true) end def setup_missing_enclosing_directory [ resource_path, normalized_path, windows_path].each do |path| allow(File).to receive(:file?).with(path).and_return(false) allow(File).to receive(:realpath?).with(path).and_raise(Errno::ENOENT) allow(File).to receive(:exists?).with(path).and_return(false) allow(File).to receive(:exist?).with(path).and_return(false) allow(File).to receive(:directory?).with(path).and_return(false) allow(File).to receive(:writable?).with(path).and_return(false) allow(file_symlink_class).to receive(:symlink?).with(path).and_return(false) end allow(File).to receive(:directory?).with(enclosing_directory).and_return(false) end # A File subclass that we use as a replacement for Tempfile. Some versions of # Tempfile call `File.exist?()` internally which will cause test failures if # `File.exist?()` has been stubbed. class BasicTempfile < ::File def self.make_tmp_path(basename) slug = "#{basename}-#{rand(1 << 128)}" File.join(Dir.tmpdir, slug) end def self.new(basename) super(make_tmp_path(basename), File::RDWR | File::CREAT | File::EXCL, 0600) end def unlink self.class.unlink(path) end end shared_examples_for Chef::Provider::File do let(:tempfile_path) do end let!(:tempfile) do BasicTempfile.new("rspec-shared-file-provider") end before(:each) do allow(content).to receive(:tempfile).and_return(tempfile) allow(File).to receive(:exist?).with(tempfile.path).and_call_original allow(File).to receive(:exists?).with(tempfile.path).and_call_original end after do tempfile.close if tempfile && !tempfile.closed? File.unlink(tempfile.path) rescue nil end it "should return a #{described_class}" do expect(provider).to be_a_kind_of(described_class) end it "should store the resource passed to new as new_resource" do expect(provider.new_resource).to eql(resource) end it "should store the node passed to new as node" do expect(provider.node).to eql(node) end context "when loading the current resource" do context "when running load_current_resource" do # # the content objects need the current_resource to be loaded (esp remote_file), so calling # for content inside of load_current_resource is totally crossing the streams... # it "should not try to load the content when the file is present" do setup_normal_file expect(provider).not_to receive(:tempfile) expect(provider).not_to receive(:content) provider.load_current_resource end it "should not try to load the content when the file is missing" do setup_missing_file expect(provider).not_to receive(:tempfile) expect(provider).not_to receive(:content) provider.load_current_resource end end context "when running load_current_resource and the file exists" do before do setup_normal_file end let(:tempfile_sha256) { "42971f0ddce0cb20cf7660a123ffa1a1543beb2f1e7cd9d65858764a27f3201d" } it "should load a current resource based on the one specified at construction" do provider.load_current_resource expect(provider.current_resource).to be_a_kind_of(Chef::Resource::File) end it "the loaded current_resource name should be the same as the resource name" do provider.load_current_resource expect(provider.current_resource.name).to eql(resource.name) end it "the loaded current_resource path should be the same as the resoure path" do provider.load_current_resource expect(provider.current_resource.path).to eql(resource.path) end it "the loaded current_resource content should be nil" do provider.load_current_resource expect(provider.current_resource.content).to eql(nil) end it "it should call checksum if we are managing content" do expect(provider).to receive(:managing_content?).at_least(:once).and_return(true) expect(provider).to receive(:checksum).with(resource.path).and_return(tempfile_sha256) provider.load_current_resource end it "it should not call checksum if we are not managing content" do expect(provider).to receive(:managing_content?).at_least(:once).and_return(false) expect(provider).not_to receive(:checksum) provider.load_current_resource end end context "when running load_current_resource and the file does not exist" do before do setup_missing_file end it "the current_resource should be a Chef::Resource::File" do provider.load_current_resource expect(provider.current_resource).to be_a_kind_of(Chef::Resource::File) end it "the current_resource name should be the same as the resource name" do provider.load_current_resource expect(provider.current_resource.name).to eql(resource.name) end it "the current_resource path should be the same as the resource path" do provider.load_current_resource expect(provider.current_resource.path).to eql(resource.path) end it "the loaded current_resource content should be nil" do provider.load_current_resource expect(provider.current_resource.content).to eql(nil) end it "it should not call checksum if we are not managing content" do expect(provider).not_to receive(:managing_content?) expect(provider).not_to receive(:checksum) provider.load_current_resource end end context "examining file security metadata on Unix with a file that exists" do before do # fake that we're on unix even if we're on windows allow(ChefConfig).to receive(:windows?).and_return(false) # mock up the filesystem to behave like unix setup_normal_file stat_struct = double("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000) resource_real_path = File.realpath(resource.path) expect(File).to receive(:stat).with(resource_real_path).at_least(:once).and_return(stat_struct) allow(Etc).to receive(:getgrgid).with(0).and_return(double("Group Ent", :name => "wheel")) allow(Etc).to receive(:getpwuid).with(0).and_return(double("User Ent", :name => "root")) end context "when the new_resource does not specify any state" do before do provider.load_current_resource end it "should load the permissions into the current_resource" do expect(provider.current_resource.mode).to eq("0600") expect(provider.current_resource.owner).to eq("root") expect(provider.current_resource.group).to eq("wheel") end it "should not set the new_resource permissions" do expect(provider.new_resource.group).to be_nil expect(provider.new_resource.owner).to be_nil expect(provider.new_resource.mode).to be_nil end end context "when the new_resource explicitly specifies resource state as numbers" do before do resource.owner(1) resource.group(1) resource.mode(0644) provider.load_current_resource end it "should load the permissions into the current_resource as numbers" do # Mode is always loaded as string for reporting purposes. expect(provider.current_resource.mode).to eq("0600") expect(provider.current_resource.owner).to eq(0) expect(provider.current_resource.group).to eq(0) end it "should not set the new_resource permissions" do expect(provider.new_resource.group).to eq(1) expect(provider.new_resource.owner).to eq(1) expect(provider.new_resource.mode).to eq(0644) end end context "when the new_resource explicitly specifies resource state as symbols" do before do resource.owner("macklemore") resource.group("seattlehiphop") resource.mode("0321") provider.load_current_resource end it "should load the permissions into the current_resource as symbols" do expect(provider.current_resource.mode).to eq("0600") expect(provider.current_resource.owner).to eq("root") expect(provider.current_resource.group).to eq("wheel") end it "should not set the new_resource permissions" do expect(provider.new_resource.group).to eq("seattlehiphop") expect(provider.new_resource.owner).to eq("macklemore") expect(provider.new_resource.mode).to eq("0321") end end end context "examining file security metadata on Unix with a file that does not exist" do before do # fake that we're on unix even if we're on windows allow(ChefConfig).to receive(:windows?).and_return(false) setup_missing_file end context "when the new_resource does not specify any state" do before do provider.load_current_resource end it "the current_resource permissions should be nil" do expect(provider.current_resource.mode).to be_nil expect(provider.current_resource.owner).to be_nil expect(provider.current_resource.group).to be_nil end it "should not set the new_resource permissions" do expect(provider.new_resource.group).to be_nil expect(provider.new_resource.owner).to be_nil expect(provider.new_resource.mode).to be_nil end end context "when the new_resource explicitly specifies resource state" do before do resource.owner(63945) resource.group(51948) resource.mode(0123) provider.load_current_resource end it "the current_resource permissions should be nil" do expect(provider.current_resource.mode).to be_nil expect(provider.current_resource.owner).to be_nil expect(provider.current_resource.group).to be_nil end it "should not set the new_resource permissions" do expect(provider.new_resource.group).to eq(51948) expect(provider.new_resource.owner).to eq(63945) expect(provider.new_resource.mode).to eq(0123) end end end end context "when loading the new_resource after the run" do before do # fake that we're on unix even if we're on windows allow(ChefConfig).to receive(:windows?).and_return(false) # mock up the filesystem to behave like unix setup_normal_file stat_struct = double("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000) resource_real_path = File.realpath(resource.path) allow(File).to receive(:stat).with(resource_real_path).and_return(stat_struct) allow(Etc).to receive(:getgrgid).with(0).and_return(double("Group Ent", :name => "wheel")) allow(Etc).to receive(:getpwuid).with(0).and_return(double("User Ent", :name => "root")) provider.send(:load_resource_attributes_from_file, resource) end it "new_resource should record the new permission information" do expect(provider.new_resource.group).to eq("wheel") expect(provider.new_resource.owner).to eq("root") expect(provider.new_resource.mode).to eq("0600") end end context "when reporting security metadata on windows" do it "records the file owner" do skip end it "records rights for each user in the ACL" do skip end it "records deny_rights for each user in the ACL" do skip end end context "define_resource_requirements" do context "when the enclosing directory does not exist" do before { setup_missing_enclosing_directory } [:create, :create_if_missing, :touch].each do |action| context "action #{action}" do it "raises EnclosingDirectoryDoesNotExist" do expect { provider.run_action(action) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist) end it "does not raise an exception in why-run mode" do Chef::Config[:why_run] = true expect { provider.run_action(action) }.not_to raise_error Chef::Config[:why_run] = false end end end end context "when the file exists but is not deletable" do before { setup_unwritable_file } it "action delete raises InsufficientPermissions" do expect { provider.run_action(:delete) }.to raise_error(Chef::Exceptions::InsufficientPermissions) end it "action delete also raises InsufficientPermissions in why-run mode" do Chef::Config[:why_run] = true expect { provider.run_action(:delete) }.to raise_error(Chef::Exceptions::InsufficientPermissions) Chef::Config[:why_run] = false end end end context "action create" do it "should create the file, update its contents and then set the acls on the file" do setup_missing_file expect(provider).to receive(:do_create_file) expect(provider).to receive(:do_contents_changes) expect(provider).to receive(:do_acl_changes) expect(provider).to receive(:load_resource_attributes_from_file) provider.run_action(:create) end context "do_validate_content" do before { setup_normal_file } let(:tempfile) do t = double("Tempfile", :path => "/tmp/foo-bar-baz", :closed? => true) allow(content).to receive(:tempfile).and_return(t) t end context "with user-supplied verifications" do it "calls #verify on each verification with tempfile path" do provider.new_resource.verify windows? ? "REM" : "true" provider.new_resource.verify windows? ? "REM" : "true" provider.send(:do_validate_content) end it "raises an exception if any verification fails" do allow(File).to receive(:directory?).with("C:\\Windows\\system32/cmd.exe").and_return(false) provider.new_resource.verify windows? ? "REM" : "true" provider.new_resource.verify windows? ? "cmd.exe /c exit 1" : "false" expect { provider.send(:do_validate_content) }.to raise_error(Chef::Exceptions::ValidationFailed) end end end context "do_create_file" do context "when the file exists" do before { setup_normal_file } it "should not create the file" do provider.load_current_resource expect(provider.deployment_strategy).not_to receive(:create).with(resource_path) provider.send(:do_create_file) expect(provider.send(:needs_creating?)).to eq(false) end end context "when the file does not exist" do before { setup_missing_file } it "should create the file" do provider.load_current_resource expect(provider.deployment_strategy).to receive(:create).with(resource_path) provider.send(:do_create_file) expect(provider.send(:needs_creating?)).to eq(true) end end end context "do_contents_changes" do context "when there is content to deploy" do before do setup_normal_file provider.load_current_resource tempfile = double("Tempfile", :path => "/tmp/foo-bar-baz") allow(content).to receive(:tempfile).and_return(tempfile) expect(File).to receive(:exists?).with("/tmp/foo-bar-baz").and_return(true) expect(tempfile).to receive(:close).once expect(tempfile).to receive(:unlink).once end context "when the contents have changed" do let(:tempfile_path) { "/tmp/foo-bar-baz" } let(:tempfile_sha256) { "42971f0ddce0cb20cf7660a123ffa1a1543beb2f1e7cd9d65858764a27f3201d" } let(:diff_for_reporting) { "+++\n---\n+foo\n-bar\n" } before do allow(provider).to receive(:contents_changed?).and_return(true) diff = double("Diff", :for_output => ["+++", "---", "+foo", "-bar"], :for_reporting => diff_for_reporting ) allow(diff).to receive(:diff).with(resource_path, tempfile_path).and_return(true) expect(provider).to receive(:diff).at_least(:once).and_return(diff) expect(provider).to receive(:checksum).with(tempfile_path).and_return(tempfile_sha256) allow(provider).to receive(:managing_content?).and_return(true) allow(provider).to receive(:checksum).with(resource_path).and_return(tempfile_sha256) expect(resource).not_to receive(:checksum).with(tempfile_sha256) # do not mutate the new resource expect(provider.deployment_strategy).to receive(:deploy).with(tempfile_path, normalized_path) end context "when the file was created" do before { expect(provider).to receive(:needs_creating?).at_least(:once).and_return(true) } it "does not backup the file" do expect(provider).not_to receive(:do_backup) provider.send(:do_contents_changes) end it "does not produce a diff for reporting" do provider.send(:do_contents_changes) expect(resource.diff).to be_nil end it "renders the final checksum correctly for reporting" do provider.send(:do_contents_changes) expect(resource.state_for_resource_reporter[:checksum]).to eql(tempfile_sha256) end end context "when the file was not created" do before do allow(provider).to receive(:do_backup) # stub do_backup expect(provider).to receive(:needs_creating?).at_least(:once).and_return(false) end it "backs up the file" do expect(provider).to receive(:do_backup) provider.send(:do_contents_changes) end it "produces a diff for reporting" do provider.send(:do_contents_changes) expect(resource.diff).to eq(diff_for_reporting) end it "renders the final checksum correctly for reporting" do provider.send(:do_contents_changes) expect(resource.state_for_resource_reporter[:checksum]).to eql(tempfile_sha256) end end end it "does nothing when the contents have not changed" do allow(provider).to receive(:contents_changed?).and_return(false) expect(provider).not_to receive(:diff) provider.send(:do_contents_changes) end end it "does nothing when there is no content to deploy (tempfile returned from contents is nil)" do expect(provider.send(:content)).to receive(:tempfile).at_least(:once).and_return(nil) expect(provider).not_to receive(:diff) expect { provider.send(:do_contents_changes) }.not_to raise_error end it "raises an exception when the content object returns a tempfile with a nil path" do tempfile = double("Tempfile", :path => nil) expect(provider.send(:content)).to receive(:tempfile).at_least(:once).and_return(tempfile) expect { provider.send(:do_contents_changes) }.to raise_error(RuntimeError) end it "raises an exception when the content object returns a tempfile that does not exist" do tempfile = double("Tempfile", :path => "/tmp/foo-bar-baz") expect(provider.send(:content)).to receive(:tempfile).at_least(:once).and_return(tempfile) expect(File).to receive(:exists?).with("/tmp/foo-bar-baz").and_return(false) expect { provider.send(:do_contents_changes) }.to raise_error(RuntimeError) end end context "do_acl_changes" do it "needs tests" do skip end end context "do_selinux" do context "when resource is updated" do before do setup_normal_file provider.load_current_resource allow(provider).to receive(:resource_updated?).and_return(true) end it "should check for selinux_enabled? by default" do expect(provider).to receive(:selinux_enabled?) provider.send(:do_selinux) end context "when selinux fixup is enabled in the config" do before do @original_selinux_fixup = Chef::Config[:enable_selinux_file_permission_fixup] Chef::Config[:enable_selinux_file_permission_fixup] = true end after do Chef::Config[:enable_selinux_file_permission_fixup] = @original_selinux_fixup end context "when selinux is enabled on the system" do before do expect(provider).to receive(:selinux_enabled?).and_return(true) end it "restores security context on the file" do expect(provider).to receive(:restore_security_context).with(normalized_path, false) provider.send(:do_selinux) end it "restores security context recursively when told so" do expect(provider).to receive(:restore_security_context).with(normalized_path, true) provider.send(:do_selinux, true) end end context "when selinux is disabled on the system" do before do expect(provider).to receive(:selinux_enabled?).and_return(false) end it "should not restore security context" do expect(provider).not_to receive(:restore_security_context) provider.send(:do_selinux) end end end context "when selinux fixup is disabled in the config" do before do @original_selinux_fixup = Chef::Config[:enable_selinux_file_permission_fixup] Chef::Config[:enable_selinux_file_permission_fixup] = false end after do Chef::Config[:enable_selinux_file_permission_fixup] = @original_selinux_fixup end it "should not check for selinux_enabled?" do expect(provider).not_to receive(:selinux_enabled?) provider.send(:do_selinux) end end end context "when resource is not updated" do before do allow(provider).to receive(:resource_updated?).and_return(false) end it "should not check for selinux_enabled?" do expect(provider).not_to receive(:selinux_enabled?) provider.send(:do_selinux) end end end end context "action delete" do context "when the file exists" do context "when the file is writable" do context "when the file is not a symlink" do before { setup_normal_file } it "should backup and delete the file and be updated by the last action" do expect(provider).to receive(:do_backup).at_least(:once).and_return(true) expect(File).to receive(:delete).with(resource_path).and_return(true) provider.run_action(:delete) expect(resource).to be_updated_by_last_action end end context "when the file is a symlink" do before { setup_symlink } it "should not backup the symlink" do expect(provider).not_to receive(:do_backup) expect(File).to receive(:delete).with(resource_path).and_return(true) provider.run_action(:delete) expect(resource).to be_updated_by_last_action end end end context "when the file is not writable" do before { setup_unwritable_file } it "should not try to backup or delete the file, and should not be updated by last action" do expect(provider).not_to receive(:do_backup) expect(File).not_to receive(:delete) expect { provider.run_action(:delete) }.to raise_error(Chef::Exceptions::InsufficientPermissions) expect(resource).not_to be_updated_by_last_action end end end context "when the file does not exist" do before { setup_missing_file } it "should not try to backup or delete the file, and should not be updated by last action" do expect(provider).not_to receive(:do_backup) expect(File).not_to receive(:delete) expect { provider.run_action(:delete) }.not_to raise_error expect(resource).not_to be_updated_by_last_action end end end context "action touch" do context "when the file does not exist" do before { setup_missing_file } it "should update the atime/mtime on action_touch" do expect(File).to receive(:utime).once expect(provider).to receive(:action_create) provider.run_action(:touch) expect(resource).to be_updated_by_last_action end end context "when the file exists" do before { setup_normal_file } it "should update the atime/mtime on action_touch" do expect(File).to receive(:utime).once expect(provider).to receive(:action_create) provider.run_action(:touch) expect(resource).to be_updated_by_last_action end end end context "action create_if_missing" do context "when the file does not exist" do before { setup_missing_file } it "should call action_create" do expect(provider).to receive(:action_create) provider.run_action(:create_if_missing) end end context "when the file exists" do before { setup_normal_file } it "should not call action_create" do expect(provider).not_to receive(:action_create) provider.run_action(:create_if_missing) end end end end shared_examples_for "a file provider with content field" do context "when testing managing_content?" do it "should be false when creating a file without content" do provider.action = :create allow(resource).to receive(:content).and_return(nil) allow(resource).to receive(:checksum).and_return(nil) expect(provider.send(:managing_content?)).to be_falsey end it "should be true when creating a file with content" do provider.action = :create allow(resource).to receive(:content).and_return("flurbleblobbleblooble") allow(resource).to receive(:checksum).and_return(nil) expect(provider.send(:managing_content?)).to be_truthy end it "should be true when checksum is set on the content (no matter how crazy)" do provider.action = :create_if_missing allow(resource).to receive(:checksum).and_return("1234123234234234") allow(resource).to receive(:content).and_return(nil) expect(provider.send(:managing_content?)).to be_truthy end it "should be false when action is create_if_missing" do provider.action = :create_if_missing allow(resource).to receive(:content).and_return("flurbleblobbleblooble") allow(resource).to receive(:checksum).and_return(nil) expect(provider.send(:managing_content?)).to be_falsey end end end shared_examples_for "a file provider with source field" do context "when testing managing_content?" do it "should be false when creating a file without content" do provider.action = :create allow(resource).to receive(:content).and_return(nil) allow(resource).to receive(:source).and_return(nil) allow(resource).to receive(:checksum).and_return(nil) expect(provider.send(:managing_content?)).to be_falsey end it "should be true when creating a file with content" do provider.action = :create allow(resource).to receive(:content).and_return(nil) allow(resource).to receive(:source).and_return("http://somewhere.com/something.php") allow(resource).to receive(:checksum).and_return(nil) expect(provider.send(:managing_content?)).to be_truthy end it "should be true when checksum is set on the content (no matter how crazy)" do provider.action = :create_if_missing allow(resource).to receive(:content).and_return(nil) allow(resource).to receive(:source).and_return(nil) allow(resource).to receive(:checksum).and_return("1234123234234234") expect(provider.send(:managing_content?)).to be_truthy end it "should be false when action is create_if_missing" do provider.action = :create_if_missing allow(resource).to receive(:content).and_return(nil) allow(resource).to receive(:source).and_return("http://somewhere.com/something.php") allow(resource).to receive(:checksum).and_return(nil) expect(provider.send(:managing_content?)).to be_falsey end end end chef-12.14.60/spec/support/shared/unit/provider/useradd_based_user_provider.rb000066400000000000000000000366441276456504500275140ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # 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. # # XXX: this used to be shared by solaris and linux classes, but at some # point became linux-specific. it is now a misnomer to call these 'shared' # examples and they should either realy get turned into shared examples or # should be copypasta'd back directly into the linux tests. shared_examples_for "a useradd-based user provider" do |supported_useradd_options| before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::User::LinuxUser.new("adam", @run_context) @new_resource.comment "Adam Jacob" @new_resource.uid 1000 @new_resource.gid 1000 @new_resource.home "/home/adam" @new_resource.shell "/usr/bin/zsh" @new_resource.password "abracadabra" @new_resource.system false @new_resource.manage_home false @new_resource.force false @new_resource.non_unique false @current_resource = Chef::Resource::User::LinuxUser.new("adam", @run_context) @current_resource.comment "Adam Jacob" @current_resource.uid 1000 @current_resource.gid 1000 @current_resource.home "/home/adam" @current_resource.shell "/usr/bin/zsh" @current_resource.password "abracadabra" @current_resource.system false @current_resource.manage_home false @current_resource.force false @current_resource.non_unique false end describe "when setting option" do supported_useradd_options.each do |attribute, option| it "should check for differences in #{attribute} between the new and current resources" do expect(@current_resource).to receive(attribute) expect(@new_resource).to receive(attribute) provider.universal_options end it "should set the option for #{attribute} if the new resources #{attribute} is not nil" do allow(@new_resource).to receive(attribute).and_return("hola") expect(provider.universal_options).to eql([option, "hola"]) end it "should set the option for #{attribute} if the new resources #{attribute} is not nil, without homedir management" do allow(@new_resource).to receive(:supports).and_return({ :manage_home => false, :non_unique => false }) allow(@new_resource).to receive(attribute).and_return("hola") expect(provider.universal_options).to eql([option, "hola"]) end it "should set the option for #{attribute} if the new resources #{attribute} is not nil, without homedir management (using real attributes)" do allow(@new_resource).to receive(:manage_home).and_return(false) allow(@new_resource).to receive(:non_unique).and_return(false) allow(@new_resource).to receive(:non_unique).and_return(false) allow(@new_resource).to receive(attribute).and_return("hola") expect(provider.universal_options).to eql([option, "hola"]) end end it "should combine all the possible options" do combined_opts = [] supported_useradd_options.sort { |a, b| a[0] <=> b[0] }.each do |attribute, option| allow(@new_resource).to receive(attribute).and_return("hola") combined_opts << option << "hola" end expect(provider.universal_options).to eql(combined_opts) end describe "when we want to create a system user" do before do @new_resource.manage_home(true) @new_resource.non_unique(false) end it "should set useradd -r" do @new_resource.system(true) expect(provider.useradd_options).to eq([ "-r", "-m" ]) end end describe "when the resource has a different home directory and supports home directory management" do before do @new_resource.home "/wowaweea" @new_resource.manage_home true end it "should set -m -d /homedir" do expect(provider.universal_options).to eq(%w{-d /wowaweea}) expect(provider.usermod_options).to eq(%w{-m}) end end describe "when the resource has a different home directory and supports home directory management (using real attributes)" do before do @new_resource.home("/wowaweea") @new_resource.manage_home true @new_resource.non_unique false end it "should set -m -d /homedir" do expect(provider.universal_options).to eq(%w{-d /wowaweea}) expect(provider.usermod_options).to eq(%w{-m}) end end it "when non_unique is false should not set -m" do @new_resource.non_unique false expect(provider.universal_options).to eql([ ]) end it "when non_unique is true should set -o" do @new_resource.non_unique true expect(provider.universal_options).to eql([ "-o" ]) end end describe "when creating a user" do before(:each) do @current_resource = Chef::Resource::User::LinuxUser.new(@new_resource.name, @run_context) @current_resource.username(@new_resource.username) provider.current_resource = @current_resource provider.new_resource.manage_home true provider.new_resource.home "/Users/mud" provider.new_resource.gid "23" end it "runs useradd with the computed command options" do command = ["useradd", "-c", "Adam Jacob", "-g", "23" ] command.concat(["-p", "abracadabra"]) if supported_useradd_options.key?("password") command.concat([ "-s", "/usr/bin/zsh", "-u", "1000", "-d", "/Users/mud", "-m", "adam" ]) expect(provider).to receive(:shell_out!).with(*command).and_return(true) provider.create_user end describe "and home is not specified for new system user resource" do before do provider.new_resource.system true # there is no public API to set attribute's value to nil provider.new_resource.instance_variable_set("@home", nil) end it "should not include -m or -d in the command options" do command = ["useradd", "-c", "Adam Jacob", "-g", "23"] command.concat(["-p", "abracadabra"]) if supported_useradd_options.key?("password") command.concat([ "-s", "/usr/bin/zsh", "-u", "1000", "-r", "-m", "adam" ]) expect(provider).to receive(:shell_out!).with(*command).and_return(true) provider.create_user end end end describe "when managing a user" do before(:each) do provider.new_resource.manage_home true provider.new_resource.home "/Users/mud" provider.new_resource.gid "23" end # CHEF-3423, -m must come before the username # CHEF-4305, -d must come before -m to support CentOS/RHEL 5 it "runs usermod with the computed command options" do command = ["usermod", "-g", "23", "-d", "/Users/mud", "-m", "adam" ] expect(provider).to receive(:shell_out!).with(*command).and_return(true) provider.manage_user end it "does not set the -r option to usermod" do @new_resource.system(true) command = ["usermod", "-g", "23", "-d", "/Users/mud", "-m", "adam" ] expect(provider).to receive(:shell_out!).with(*command).and_return(true) provider.manage_user end it "CHEF-3429: does not set -m if we aren't changing the home directory" do expect(provider).to receive(:updating_home?).at_least(:once).and_return(false) command = ["usermod", "-g", "23", "adam" ] expect(provider).to receive(:shell_out!).with(*command).and_return(true) provider.manage_user end end describe "when removing a user" do it "should run userdel with the new resources user name" do expect(provider).to receive(:shell_out!).with("userdel", @new_resource.username).and_return(true) provider.remove_user end it "should run userdel with the new resources user name and -r if manage_home is true" do @new_resource.manage_home true expect(provider).to receive(:shell_out!).with("userdel", "-r", @new_resource.username).and_return(true) provider.remove_user end it "should run userdel with the new resources user name if non_unique is true" do expect(provider).to receive(:shell_out!).with("userdel", @new_resource.username).and_return(true) provider.remove_user end it "should run userdel with the new resources user name and -f if force is true" do @new_resource.force(true) expect(provider).to receive(:shell_out!).with("userdel", "-f", @new_resource.username).and_return(true) provider.remove_user end end describe "when checking the lock" do # lazy initialize so we can modify stdout and stderr strings let(:passwd_s_status) do double("Mixlib::ShellOut command", :exitstatus => 0, :stdout => @stdout, :stderr => @stderr, :error! => nil) end before(:each) do # @node = Chef::Node.new # @new_resource = double("Chef::Resource::User", # :nil_object => true, # :username => "adam" # ) #provider = Chef::Provider::User::Useradd.new(@node, @new_resource) @stdout = "root P 09/02/2008 0 99999 7 -1" @stderr = "" end it "should return false if status begins with P" do expect(provider).to receive(:shell_out). with("passwd", "-S", @new_resource.username, { :returns => [0, 1] }). and_return(passwd_s_status) expect(provider.check_lock).to eql(false) end it "should return false if status begins with N" do @stdout = "root N" expect(provider).to receive(:shell_out). with("passwd", "-S", @new_resource.username, { :returns => [0, 1] }). and_return(passwd_s_status) expect(provider.check_lock).to eql(false) end it "should return true if status begins with L" do @stdout = "root L" expect(provider).to receive(:shell_out). with("passwd", "-S", @new_resource.username, { :returns => [0, 1] }). and_return(passwd_s_status) expect(provider.check_lock).to eql(true) end it "should raise a ShellCommandFailed exception if passwd -S exits with something other than 0 or 1" do expect(passwd_s_status).to receive(:error!).and_raise(Mixlib::ShellOut::ShellCommandFailed) expect(provider).to receive(:shell_out). with("passwd", "-S", @new_resource.username, { :returns => [0, 1] }). and_return(passwd_s_status) expect { provider.check_lock }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end it "should raise an error if the output isn't parsable" do expect(passwd_s_status).to receive(:stdout).and_return("") expect(passwd_s_status).to receive(:stderr).and_return("") expect(provider).to receive(:shell_out). with("passwd", "-S", @new_resource.username, { :returns => [0, 1] }). and_return(passwd_s_status) expect { provider.check_lock }.to raise_error(Chef::Exceptions::User) end context "when in why run mode" do before do passwd_status = double("Mixlib::ShellOut command", :exitstatus => 0, :stdout => "", :stderr => "passwd: user 'chef-test' does not exist\n") expect(provider).to receive(:shell_out). with("passwd", "-S", @new_resource.username, { :returns => [0, 1] }). and_return(passwd_status) # ubuntu returns 252 on user-does-not-exist so will raise if #error! is called or if # shell_out! is used allow(passwd_status).to receive(:error!).and_raise(Mixlib::ShellOut::ShellCommandFailed) Chef::Config[:why_run] = true end it "should return false if the user does not exist" do expect(provider.check_lock).to eql(false) end it "should not raise an error if the user does not exist" do expect { provider.check_lock }.not_to raise_error end end end describe "when locking the user" do it "should run usermod -L with the new resources username" do expect(provider).to receive(:shell_out!).with("usermod", "-L", @new_resource.username) provider.lock_user end end describe "when unlocking the user" do it "should run usermod -L with the new resources username" do expect(provider).to receive(:shell_out!).with("usermod", "-U", @new_resource.username) provider.unlock_user end end describe "when checking if home needs updating" do [ { "action" => "should return false if home matches", "current_resource_home" => [ "/home/laurent" ], "new_resource_home" => [ "/home/laurent" ], "expected_result" => false, }, { "action" => "should return true if home doesn't match", "current_resource_home" => [ "/home/laurent" ], "new_resource_home" => [ "/something/else" ], "expected_result" => true, }, { "action" => "should return false if home only differs by trailing slash", "current_resource_home" => [ "/home/laurent" ], "new_resource_home" => [ "/home/laurent/", "/home/laurent" ], "expected_result" => false, }, { "action" => "should return false if home is an equivalent path", "current_resource_home" => [ "/home/laurent" ], "new_resource_home" => [ "/home/./laurent", "/home/laurent" ], "expected_result" => false, }, ].each do |home_check| it home_check["action"] do provider.current_resource.home home_check["current_resource_home"].first @current_home_mock = double("Pathname") provider.new_resource.home home_check["new_resource_home"].first @new_home_mock = double("Pathname") expect(Pathname).to receive(:new).with(@current_resource.home).and_return(@current_home_mock) expect(@current_home_mock).to receive(:cleanpath).and_return(home_check["current_resource_home"].last) expect(Pathname).to receive(:new).with(@new_resource.home).and_return(@new_home_mock) expect(@new_home_mock).to receive(:cleanpath).and_return(home_check["new_resource_home"].last) expect(provider.updating_home?).to eq(home_check["expected_result"]) end end it "should return true if the current home does not exist but a home is specified by the new resource" do @new_resource = Chef::Resource::User::LinuxUser.new("adam", @run_context) @current_resource = Chef::Resource::User::LinuxUser.new("adam", @run_context) provider = Chef::Provider::User::Linux.new(@new_resource, @run_context) provider.current_resource = @current_resource @current_resource.home nil @new_resource.home "/home/kitten" expect(provider.updating_home?).to eq(true) end end end chef-12.14.60/spec/support/shared/unit/resource/000077500000000000000000000000001276456504500214125ustar00rootroot00000000000000chef-12.14.60/spec/support/shared/unit/resource/static_provider_resolution.rb000066400000000000000000000043161276456504500274270ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # # This is for typical "static" provider resolution which maps resources onto # providers based only on the node data. Its not really 'static' because it # all goes through the Chef::ProviderResolver, but the effective result is # a static mapping for the node (unlike the service resource which is # complicated). # def static_provider_resolution(opts = {}) action = opts[:action] provider_class = opts[:provider] resource_class = opts[:resource] name = opts[:name] os = opts[:os] platform_family = opts[:platform_family] platform_version = opts[:platform_version] describe resource_class, "static provider initialization" do let(:node) do node = Chef::Node.new node.automatic_attrs[:os] = os node.automatic_attrs[:platform_family] = platform_family node.automatic_attrs[:platform_version] = platform_version node end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:resource) { resource_class.new("foo", run_context) } it "should return a #{resource_class}" do expect(resource).to be_a_kind_of(resource_class) end it "should set the resource_name to #{name}" do expect(resource.resource_name).to eql(name) end it "should leave the provider nil" do expect(resource.provider).to eql(nil) end it "should resolve to a #{provider_class}" do expect(resource.provider_for_action(action)).to be_a(provider_class) end end end chef-12.14.60/spec/support/shared/unit/script_resource.rb000066400000000000000000000073601276456504500233310ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # 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" shared_examples_for "a script resource" do it "should create a new Chef::Resource::Script" do expect(script_resource).to be_a_kind_of(Chef::Resource) expect(script_resource).to be_a_kind_of(Chef::Resource::Script) end it "should have a resource name of :script" do expect(script_resource.resource_name).to eql(resource_name) end it "should set command to nil on the resource", chef: ">= 13" do expect(script_resource.command).to be nil end it "should set command to the name on the resource", chef: "< 13" do expect(script_resource.command).to eql script_resource.name end it "should accept a string for the code" do script_resource.code "hey jude" expect(script_resource.code).to eql("hey jude") end it "should accept a string for the flags" do script_resource.flags "-f" expect(script_resource.flags).to eql("-f") end it "should raise an exception if users set command on the resource", chef: ">= 13" do expect { script_resource.command("foo") }.to raise_error(Chef::Exceptions::Script) end it "should not raise an exception if users set command on the resource", chef: "< 13" do expect { script_resource.command("foo") }.not_to raise_error end describe "when executing guards" do let(:resource) do resource = script_resource resource.run_context = run_context resource.code "echo hi" resource end let(:node) do node = Chef::Node.new node.automatic[:platform] = "debian" node.automatic[:platform_version] = "6.0" node end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } it "inherits exactly the :cwd, :environment, :group, :path, :user, and :umask attributes from a parent resource class" do inherited_difference = Chef::Resource::Script.guard_inherited_attributes - [:cwd, :environment, :group, :path, :user, :umask ] expect(inherited_difference).to eq([]) end it "when guard_interpreter is set to the default value, the guard command string should be evaluated by command execution and not through a resource" do expect_any_instance_of(Chef::Resource::Conditional).not_to receive(:evaluate_block) expect_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).not_to receive(:evaluate_action) expect_any_instance_of(Chef::GuardInterpreter::DefaultGuardInterpreter).to receive(:evaluate).and_return(true) resource.only_if "echo hi" expect(resource.should_skip?(:run)).to eq(nil) end it "when a valid guard_interpreter resource is specified, a block should be used to evaluate the guard" do expect_any_instance_of(Chef::GuardInterpreter::DefaultGuardInterpreter).not_to receive(:evaluate) expect_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(true) resource.guard_interpreter :script resource.only_if "echo hi" expect(resource.should_skip?(:run)).to eq(nil) end end end chef-12.14.60/spec/support/shared/unit/user_and_client_shared.rb000066400000000000000000000074651276456504500246100ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # shared_examples_for "user or client create" do context "when server API V1 is valid on the Chef Server receiving the request" do it "creates a new object via the API" do expect(rest_v1).to receive(:post).with(url, payload).and_return({}) object.create end it "creates a new object via the API with a public_key when it exists" do object.public_key "some_public_key" expect(rest_v1).to receive(:post).with(url, payload.merge({ :public_key => "some_public_key" })).and_return({}) object.create end context "raise error when create_key and public_key are both set" do before do object.public_key "key" object.create_key true end it "rasies the proper error" do expect { object.create }.to raise_error(error) end end context "when create_key == true" do before do object.create_key true end it "creates a new object via the API with create_key" do expect(rest_v1).to receive(:post).with(url, payload.merge({ :create_key => true })).and_return({}) object.create end end context "when chef_key is returned by the server" do let(:chef_key) do { "chef_key" => { "public_key" => "some_public_key", }, } end it "puts the public key into the objectr returned by create" do expect(rest_v1).to receive(:post).with(url, payload).and_return(payload.merge(chef_key)) new_object = object.create expect(new_object.public_key).to eq("some_public_key") end context "when private_key is returned in chef_key" do let(:chef_key) do { "chef_key" => { "public_key" => "some_public_key", "private_key" => "some_private_key", }, } end it "puts the private key into the object returned by create" do expect(rest_v1).to receive(:post).with(url, payload).and_return(payload.merge(chef_key)) new_object = object.create expect(new_object.private_key).to eq("some_private_key") end end end # when chef_key is returned by the server end # when server API V1 is valid on the Chef Server receiving the request context "when server API V1 is not valid on the Chef Server receiving the request" do context "when the server supports API V0" do before do allow(object).to receive(:server_client_api_version_intersection).and_return([0]) allow(rest_v1).to receive(:post).and_raise(exception_406) end it "creates a new object via the API" do expect(rest_v0).to receive(:post).with(url, payload).and_return({}) object.create end it "creates a new object via the API with a public_key when it exists" do object.public_key "some_public_key" expect(rest_v0).to receive(:post).with(url, payload.merge({ :public_key => "some_public_key" })).and_return({}) object.create end end # when the server supports API V0 end # when server API V1 is not valid on the Chef Server receiving the request end # user or client create chef-12.14.60/spec/support/shared/unit/windows_script_resource.rb000066400000000000000000000054551276456504500251060ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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 "support/shared/unit/execute_resource" require "support/shared/unit/script_resource" shared_examples_for "a Windows script resource" do before(:each) do node = Chef::Node.new node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s run_context = Chef::RunContext.new(node, nil, nil) @resource = resource_instance end it "should be a kind of Chef::Resource::WindowsScript" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::WindowsScript) end context "when evaluating guards" do it "should have a default_guard_interpreter attribute that is the same as the resource" do expect(@resource.default_guard_interpreter).to eq(@resource.resource_name) end it "should default to using guard_interpreter attribute that is the same as the resource" do expect(@resource.guard_interpreter).to eq(@resource.resource_name) end it "should use a resource to evaluate the guard when guard_interpreter is not specified" do expect_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(true) expect_any_instance_of(Chef::GuardInterpreter::DefaultGuardInterpreter).not_to receive(:evaluate) @resource.only_if "echo hi" expect(@resource.should_skip?(:run)).to eq(nil) end describe "when the guard is given a ruby block" do it "should evaluate the guard if the guard_interpreter is set to its default value" do @resource.only_if { true } expect(@resource.should_skip?(:run)).to eq(nil) end it "should raise an exception if the guard_interpreter is overridden from its default value" do @resource.guard_interpreter :bash @resource.only_if { true } expect { @resource.should_skip?(:run) }.to raise_error(ArgumentError) end end end context "script with a default guard interpreter" do let(:script_resource) do resource_instance.guard_interpreter :default resource_instance end it_should_behave_like "a script resource" end end chef-12.14.60/spec/tiny_server.rb000066400000000000000000000117041276456504500165230ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 "rubygems" require "webrick" require "webrick/https" require "rack" require "thread" require "singleton" require "open-uri" require "chef/config" module TinyServer class Manager # 5 == debug, 3 == warning LOGGER = WEBrick::Log.new(STDOUT, 3) DEFAULT_OPTIONS = { Port: 9000, Host: "localhost", Logger: LOGGER, # SSLEnable: options[:ssl], # SSLCertName: [ [ 'CN', WEBrick::Utils::getservername ] ], AccessLog: [], # Remove this option to enable the access log when debugging. } def initialize(**options) @options = DEFAULT_OPTIONS.merge(options) @creator = caller.first end attr_reader :options attr_reader :creator attr_reader :server def start(timeout = 5) raise "Server already started!" if server # Create the server (but don't start yet) start_queue = Queue.new @server = create_server(StartCallback: proc { start_queue << true }) @server_thread = Thread.new do # Ensure any exceptions will cause the main rspec thread to fail too Thread.current.abort_on_exception = true server.start end # Wait for the StartCallback to tell us we've started Timeout.timeout(timeout) do start_queue.pop end end def stop(timeout = 5) if server server.shutdown @server = nil end if server_thread begin # Wait for a normal shutdown server_thread.join(timeout) rescue # If it wouldn't shut down normally, kill it. server_thread.kill server_thread.join(timeout) end @server_thread = nil end end private attr_reader :server_thread def create_server(**extra_options) server = WEBrick::HTTPServer.new(**options, **extra_options) server.mount("/", Rack::Handler::WEBrick, API.instance) server end end class API include Singleton GET = "GET" PUT = "PUT" POST = "POST" DELETE = "DELETE" attr_reader :routes def initialize clear end def clear @routes = { GET => [], PUT => [], POST => [], DELETE => [] } end def get(path, response_code, data = nil, headers = nil, &block) @routes[GET] << Route.new(path, Response.new(response_code, data, headers, &block)) end def put(path, response_code, data = nil, headers = nil, &block) @routes[PUT] << Route.new(path, Response.new(response_code, data, headers, &block)) end def post(path, response_code, data = nil, headers = nil, &block) @routes[POST] << Route.new(path, Response.new(response_code, data, headers, &block)) end def delete(path, response_code, data = nil, headers = nil, &block) @routes[DELETE] << Route.new(path, Response.new(response_code, data, headers, &block)) end def call(env) if response = response_for_request(env) response.call else debug_info = { :message => "no data matches the request for #{env['REQUEST_URI']}", :available_routes => @routes, :request => env } # Uncomment me for glorious debugging # pp :not_found => debug_info [404, { "Content-Type" => "application/json" }, [ Chef::JSONCompat.to_json(debug_info) ]] end end def response_for_request(env) if route = @routes[env["REQUEST_METHOD"]].find { |route| route.matches_request?(env["REQUEST_URI"]) } route.response end end end class Route attr_reader :response def initialize(path_spec, response) @path_spec, @response = path_spec, response end def matches_request?(uri) uri = URI.parse(uri).request_uri @path_spec === uri end def to_s "#{@path_spec} => (#{@response})" end end class Response HEADERS = { "Content-Type" => "application/json" } def initialize(response_code = 200, data = nil, headers = nil, &block) @response_code, @data = response_code, data @response_headers = headers ? HEADERS.merge(headers) : HEADERS @block = block_given? ? block : nil end def call data = @data || @block.call [@response_code, @response_headers, Array(data)] end def to_s "#{@response_code} => #{(@data || @block)}" end end end chef-12.14.60/spec/unit/000077500000000000000000000000001276456504500146015ustar00rootroot00000000000000chef-12.14.60/spec/unit/api_client/000077500000000000000000000000001276456504500167105ustar00rootroot00000000000000chef-12.14.60/spec/unit/api_client/registration_spec.rb000066400000000000000000000245301276456504500227650ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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 "tempfile" require "chef/api_client/registration" describe Chef::ApiClient::Registration do let(:key_location) do make_tmpname("client-registration-key") end let(:client_name) { "silent-bob" } subject(:registration) { Chef::ApiClient::Registration.new(client_name, key_location) } let(:private_key_data) do File.open(Chef::Config[:validation_key], "r") { |f| f.read.chomp } end let(:http_mock) { double("Chef::ServerAPI mock") } let(:expected_post_data) do { :name => client_name, :admin => false, :public_key => generated_public_key.to_pem } end let(:expected_put_data) do { :name => client_name, :admin => false, :public_key => generated_public_key.to_pem } end let(:server_v10_response) do { "uri" => "https://chef.local/clients/#{client_name}", "private_key" => "--begin rsa key etc--", } end # Server v11 includes `json_class` on all replies let(:server_v11_response) do response = Chef::ApiClient.new response.name(client_name) response.private_key("--begin rsa key etc--") response end let(:response_409) { Net::HTTPConflict.new("1.1", "409", "Conflict") } let(:exception_409) { Net::HTTPServerException.new("409 conflict", response_409) } let(:generated_private_key_pem) { IO.read(File.expand_path("ssl/private_key.pem", CHEF_SPEC_DATA)) } let(:generated_private_key) { OpenSSL::PKey::RSA.new(generated_private_key_pem) } let(:generated_public_key) { generated_private_key.public_key } let(:create_with_pkey_response) do { "uri" => "", "chef_key" => { "public_key" => generated_public_key.to_pem, }, } end let(:update_with_pkey_response) do { "name" => client_name, "admin" => false, "public_key" => generated_public_key, "validator" => false, "private_key" => false, "clientname" => client_name } end before do Chef::Config[:validation_client_name] = "test-validator" Chef::Config[:validation_key] = File.expand_path("ssl/private_key.pem", CHEF_SPEC_DATA) allow(OpenSSL::PKey::RSA).to receive(:generate).with(2048).and_return(generated_private_key) end after do File.unlink(key_location) if File.exist?(key_location) end it "has an HTTP client configured with validator credentials" do expect(registration.http_api).to be_a_kind_of(Chef::ServerAPI) expect(registration.http_api.options[:client_name]).to eq("test-validator") auth = registration.http_api.middlewares.find { |klass| klass.kind_of? Chef::HTTP::Authenticator } expect(auth.client_name).to eq("test-validator") end describe "when creating/updating the client on the server" do before do allow(registration).to receive(:http_api).and_return(http_mock) end it "posts a locally generated public key to the server to create a client" do expect(http_mock).to receive(:post). with("clients", expected_post_data). and_return(create_with_pkey_response) expect(registration.run.public_key).to eq(create_with_pkey_response["chef_key"]["public_key"]) expect(OpenSSL::PKey::RSA.new(registration.private_key).to_s).to eq(OpenSSL::PKey::RSA.new(generated_private_key_pem).to_s) end it "puts a locally generated public key to the server to update a client" do expect(http_mock).to receive(:post). with("clients", expected_post_data). and_raise(exception_409) expect(http_mock).to receive(:put). with("clients/#{client_name}", expected_put_data). and_return(update_with_pkey_response) expect(registration.run.public_key).to eq(update_with_pkey_response["public_key"].to_pem) expect(OpenSSL::PKey::RSA.new(registration.private_key).to_s).to eq(OpenSSL::PKey::RSA.new(generated_private_key_pem).to_s) end it "writes the generated private key to disk" do expect(http_mock).to receive(:post). with("clients", expected_post_data). and_return(create_with_pkey_response) registration.run expect(OpenSSL::PKey::RSA.new(IO.read(key_location)).to_s).to eq(OpenSSL::PKey::RSA.new(generated_private_key_pem).to_s) end context "and the client already exists on a Chef 11 server" do it "requests a new key from the server and saves it" do expect(http_mock).to receive(:post).and_raise(exception_409) expect(http_mock).to receive(:put). with("clients/#{client_name}", expected_put_data). and_return(update_with_pkey_response) expect(registration.run.public_key).to eq(update_with_pkey_response["public_key"].to_pem) expect(OpenSSL::PKey::RSA.new(registration.private_key).to_s).to eq(OpenSSL::PKey::RSA.new(generated_private_key_pem).to_s) end end context "when local key generation is disabled" do let(:expected_post_data) do { :name => client_name, :admin => false } end let(:expected_put_data) do { :name => client_name, :admin => false, :private_key => true } end before do Chef::Config[:local_key_generation] = false expect(OpenSSL::PKey::RSA).not_to receive(:generate) end it "creates a new ApiClient on the server using the validator identity" do expect(http_mock).to receive(:post). with("clients", expected_post_data). and_return(server_v10_response) expect(registration.run.private_key).to eq(server_v10_response["private_key"]) expect(registration.private_key).to eq("--begin rsa key etc--") end context "and the client already exists on a Chef 11 server" do it "requests a new key from the server and saves it" do expect(http_mock).to receive(:post).and_raise(exception_409) expect(http_mock).to receive(:put). with("clients/#{client_name}", expected_put_data). and_return(server_v11_response) expect(registration.run).to eq(server_v11_response) expect(registration.private_key).to eq("--begin rsa key etc--") end end context "and the client already exists on a Chef 10 server" do it "requests a new key from the server and saves it" do expect(http_mock).to receive(:post).with("clients", expected_post_data). and_raise(exception_409) expect(http_mock).to receive(:put). with("clients/#{client_name}", expected_put_data). and_return(server_v10_response) expect(registration.run.private_key).to eq(server_v10_response["private_key"]) expect(registration.private_key).to eq("--begin rsa key etc--") end end end end describe "when writing the private key to disk" do before do allow(registration).to receive(:private_key).and_return("--begin rsa key etc--") end # Permission read via File.stat is busted on windows, though creating the # file with 0600 has the desired effect of giving access rights to the # owner only. A platform-specific functional test would be helpful. it "creates the file with 0600 permissions", :unix_only do expect(File).not_to exist(key_location) registration.write_key expect(File).to exist(key_location) stat = File.stat(key_location) expect(stat.mode & 07777).to eq(0600) end it "writes the private key content to the file" do registration.write_key expect(IO.read(key_location)).to eq("--begin rsa key etc--") end context "when the client key location is a symlink" do it "does not follow the symlink", :unix_only do expected_flags = (File::CREAT | File::TRUNC | File::RDWR) if defined?(File::NOFOLLOW) expected_flags |= File::NOFOLLOW end expect(registration.file_flags).to eq(expected_flags) end context "with follow_client_key_symlink set to true" do before do Chef::Config[:follow_client_key_symlink] = true end it "follows the symlink", :unix_only do expect(registration.file_flags).to eq(File::CREAT | File::TRUNC | File::RDWR) end end end end describe "when registering a client" do before do allow(registration).to receive(:http_api).and_return(http_mock) end it "creates the client on the server and writes the key" do expect(http_mock).to receive(:post).ordered.and_return(server_v10_response) registration.run expect(OpenSSL::PKey::RSA.new(IO.read(key_location)).to_s).to eq(OpenSSL::PKey::RSA.new(generated_private_key_pem).to_s) end it "retries up to 5 times" do response_500 = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error") exception_500 = Net::HTTPFatalError.new("500 Internal Server Error", response_500) expect(http_mock).to receive(:post).ordered.and_raise(exception_500) # 1 expect(http_mock).to receive(:post).ordered.and_raise(exception_500) # 2 expect(http_mock).to receive(:post).ordered.and_raise(exception_500) # 3 expect(http_mock).to receive(:post).ordered.and_raise(exception_500) # 4 expect(http_mock).to receive(:post).ordered.and_raise(exception_500) # 5 expect(http_mock).to receive(:post).ordered.and_return(server_v10_response) registration.run expect(OpenSSL::PKey::RSA.new(IO.read(key_location)).to_s).to eq(OpenSSL::PKey::RSA.new(generated_private_key_pem).to_s) end it "gives up retrying after the max attempts" do response_500 = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error") exception_500 = Net::HTTPFatalError.new("500 Internal Server Error", response_500) expect(http_mock).to receive(:post).exactly(6).times.and_raise(exception_500) expect { registration.run }.to raise_error(Net::HTTPFatalError) end end end chef-12.14.60/spec/unit/api_client_spec.rb000066400000000000000000000242261276456504500202550ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "chef/api_client" require "tempfile" # DEPRECATION NOTE # # This code will be removed in Chef 13 in favor of the code in Chef::ApiClientV1, # which will be moved to this namespace. New development should occur in # Chef::ApiClientV1 until the time before Chef 13. describe Chef::ApiClient do before(:each) do @client = Chef::ApiClient.new end it "has a name attribute" do @client.name("ops_master") expect(@client.name).to eq("ops_master") end it "does not allow spaces in the name" do expect { @client.name "ops master" }.to raise_error(ArgumentError) end it "only allows string values for the name" do expect { @client.name Hash.new }.to raise_error(ArgumentError) end it "has an admin flag attribute" do @client.admin(true) expect(@client.admin).to be_truthy end it "defaults to non-admin" do expect(@client.admin).to be_falsey end it "allows only boolean values for the admin flag" do expect { @client.admin(false) }.not_to raise_error expect { @client.admin(Hash.new) }.to raise_error(ArgumentError) end it "has a 'validator' flag attribute" do @client.validator(true) expect(@client.validator).to be_truthy end it "defaults to non-validator" do expect(@client.validator).to be_falsey end it "allows only boolean values for the 'validator' flag" do expect { @client.validator(false) }.not_to raise_error expect { @client.validator(Hash.new) }.to raise_error(ArgumentError) end it "has a public key attribute" do @client.public_key("super public") expect(@client.public_key).to eq("super public") end it "accepts only String values for the public key" do expect { @client.public_key "" }.not_to raise_error expect { @client.public_key Hash.new }.to raise_error(ArgumentError) end it "has a private key attribute" do @client.private_key("super private") expect(@client.private_key).to eq("super private") end it "accepts only String values for the private key" do expect { @client.private_key "" }.not_to raise_error expect { @client.private_key Hash.new }.to raise_error(ArgumentError) end describe "when serializing to JSON" do before(:each) do @client.name("black") @client.public_key("crowes") @json = @client.to_json end it "serializes as a JSON object" do expect(@json).to match(/^\{.+\}$/) end it "includes the name value" do expect(@json).to include(%q{"name":"black"}) end it "includes the public key value" do expect(@json).to include(%{"public_key":"crowes"}) end it "includes the 'admin' flag" do expect(@json).to include(%q{"admin":false}) end it "includes the 'validator' flag" do expect(@json).to include(%q{"validator":false}) end it "includes the private key when present" do @client.private_key("monkeypants") expect(@client.to_json).to include(%q{"private_key":"monkeypants"}) end it "does not include the private key if not present" do expect(@json).not_to include("private_key") end end describe "when deserializing from JSON (string) using ApiClient#from_json" do let(:client_string) do "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true}" end let(:client) do Chef::ApiClient.from_json(client_string) end it "does not require a 'json_class' string" do expect(Chef::JSONCompat.parse(client_string)["json_class"]).to eq(nil) end it "should deserialize to a Chef::ApiClient object" do expect(client).to be_a_kind_of(Chef::ApiClient) end it "preserves the name" do expect(client.name).to eq("black") end it "preserves the public key" do expect(client.public_key).to eq("crowes") end it "preserves the admin status" do expect(client.admin).to be_truthy end it "preserves the 'validator' status" do expect(client.validator).to be_truthy end it "includes the private key if present" do expect(client.private_key).to eq("monkeypants") end end describe "when deserializing from JSON (hash) using JSONCompat#from_json" do let(:client_hash) do { "name" => "black", "public_key" => "crowes", "private_key" => "monkeypants", "admin" => true, "validator" => true, "json_class" => "Chef::ApiClient", } end let(:client) do Chef::ApiClient.from_hash(Chef::JSONCompat.parse(Chef::JSONCompat.to_json(client_hash))) end it "should deserialize to a Chef::ApiClient object" do expect(client).to be_a_kind_of(Chef::ApiClient) end it "preserves the name" do expect(client.name).to eq("black") end it "preserves the public key" do expect(client.public_key).to eq("crowes") end it "preserves the admin status" do expect(client.admin).to be_truthy end it "preserves the 'validator' status" do expect(client.validator).to be_truthy end it "includes the private key if present" do expect(client.private_key).to eq("monkeypants") end end describe "when loading from JSON" do before do end before(:each) do client = { "name" => "black", "clientname" => "black", "public_key" => "crowes", "private_key" => "monkeypants", "admin" => true, "validator" => true, "json_class" => "Chef::ApiClient", } @http_client = double("Chef::ServerAPI mock") allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) expect(@http_client).to receive(:get).with("clients/black").and_return(client) @client = Chef::ApiClient.load(client["name"]) end it "should deserialize to a Chef::ApiClient object" do expect(@client).to be_a_kind_of(Chef::ApiClient) end it "preserves the name" do expect(@client.name).to eq("black") end it "preserves the public key" do expect(@client.public_key).to eq("crowes") end it "preserves the admin status" do expect(@client.admin).to be_a_kind_of(TrueClass) end it "preserves the 'validator' status" do expect(@client.validator).to be_a_kind_of(TrueClass) end it "includes the private key if present" do expect(@client.private_key).to eq("monkeypants") end end describe "with correctly configured API credentials" do before do Chef::Config[:node_name] = "silent-bob" Chef::Config[:client_key] = File.expand_path("ssl/private_key.pem", CHEF_SPEC_DATA) end after do Chef::Config[:node_name] = nil Chef::Config[:client_key] = nil end let :private_key_data do File.open(Chef::Config[:client_key], "r") { |f| f.read.chomp } end end describe "when requesting a new key" do before do @http_client = double("Chef::ServerAPI mock") allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) end context "and the client does not exist on the server" do before do @a_404_response = Net::HTTPNotFound.new("404 not found and such", nil, nil) @a_404_exception = Net::HTTPServerException.new("404 not found exception", @a_404_response) expect(@http_client).to receive(:get).with("clients/lost-my-key").and_raise(@a_404_exception) end it "raises a 404 error" do expect { Chef::ApiClient.reregister("lost-my-key") }.to raise_error(Net::HTTPServerException) end end context "and the client exists" do before do @api_client_without_key = Chef::ApiClient.new @api_client_without_key.name("lost-my-key") expect(@http_client).to receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key) end context "and the client exists on a Chef 11-like server" do before do @api_client_with_key = Chef::ApiClient.new @api_client_with_key.name("lost-my-key") @api_client_with_key.private_key("the new private key") expect(@http_client).to receive(:put). with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true). and_return(@api_client_with_key) end it "returns an ApiClient with a private key" do response = Chef::ApiClient.reregister("lost-my-key") # no sane == method for ApiClient :'( expect(response).to eq(@api_client_without_key) expect(response.private_key).to eq("the new private key") expect(response.name).to eq("lost-my-key") expect(response.admin).to be_falsey end end context "and the client exists on a Chef 10-like server" do before do @api_client_with_key = { "name" => "lost-my-key", "private_key" => "the new private key" } expect(@http_client).to receive(:put). with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true). and_return(@api_client_with_key) end it "returns an ApiClient with a private key" do response = Chef::ApiClient.reregister("lost-my-key") # no sane == method for ApiClient :'( expect(response).to eq(@api_client_without_key) expect(response.private_key).to eq("the new private key") expect(response.name).to eq("lost-my-key") expect(response.admin).to be_falsey expect(response.validator).to be_falsey end end end end end chef-12.14.60/spec/unit/api_client_v1_spec.rb000066400000000000000000000324711276456504500206640ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "chef/api_client_v1" require "tempfile" describe Chef::ApiClientV1 do before(:each) do @client = Chef::ApiClientV1.new end it "has a name attribute" do @client.name("ops_master") expect(@client.name).to eq("ops_master") end it "does not allow spaces in the name" do expect { @client.name "ops master" }.to raise_error(ArgumentError) end it "only allows string values for the name" do expect { @client.name Hash.new }.to raise_error(ArgumentError) end it "has an admin flag attribute" do @client.admin(true) expect(@client.admin).to be_truthy end it "defaults to non-admin" do expect(@client.admin).to be_falsey end it "allows only boolean values for the admin flag" do expect { @client.admin(false) }.not_to raise_error expect { @client.admin(Hash.new) }.to raise_error(ArgumentError) end it "has an create_key flag attribute" do @client.create_key(true) expect(@client.create_key).to be_truthy end it "create_key defaults to false" do expect(@client.create_key).to be_falsey end it "allows only boolean values for the create_key flag" do expect { @client.create_key(false) }.not_to raise_error expect { @client.create_key(Hash.new) }.to raise_error(ArgumentError) end it "has a 'validator' flag attribute" do @client.validator(true) expect(@client.validator).to be_truthy end it "defaults to non-validator" do expect(@client.validator).to be_falsey end it "allows only boolean values for the 'validator' flag" do expect { @client.validator(false) }.not_to raise_error expect { @client.validator(Hash.new) }.to raise_error(ArgumentError) end it "has a public key attribute" do @client.public_key("super public") expect(@client.public_key).to eq("super public") end it "accepts only String values for the public key" do expect { @client.public_key "" }.not_to raise_error expect { @client.public_key Hash.new }.to raise_error(ArgumentError) end it "has a private key attribute" do @client.private_key("super private") expect(@client.private_key).to eq("super private") end it "accepts only String values for the private key" do expect { @client.private_key "" }.not_to raise_error expect { @client.private_key Hash.new }.to raise_error(ArgumentError) end describe "when serializing to JSON" do before(:each) do @client.name("black") @client.public_key("crowes") @json = @client.to_json end it "serializes as a JSON object" do expect(@json).to match(/^\{.+\}$/) end it "includes the name value" do expect(@json).to include(%q{"name":"black"}) end it "includes the public key value" do expect(@json).to include(%{"public_key":"crowes"}) end it "includes the 'admin' flag" do expect(@json).to include(%q{"admin":false}) end it "includes the 'validator' flag" do expect(@json).to include(%q{"validator":false}) end it "includes the 'create_key' flag when present" do @client.create_key(true) @json = @client.to_json expect(@json).to include(%q{"create_key":true}) end it "includes the private key when present" do @client.private_key("monkeypants") expect(@client.to_json).to include(%q{"private_key":"monkeypants"}) end it "does not include the private key if not present" do expect(@json).not_to include("private_key") end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @client } end end describe "when deserializing from JSON (string) using ApiClient#from_json" do let(:client_string) do "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true,\"create_key\":true}" end let(:client) do Chef::ApiClientV1.from_json(client_string) end it "does not require a 'json_class' string" do expect(Chef::JSONCompat.parse(client_string)["json_class"]).to eq(nil) end it "should deserialize to a Chef::ApiClientV1 object" do expect(client).to be_a_kind_of(Chef::ApiClientV1) end it "preserves the name" do expect(client.name).to eq("black") end it "preserves the public key" do expect(client.public_key).to eq("crowes") end it "preserves the admin status" do expect(client.admin).to be_truthy end it "preserves the create_key status" do expect(client.create_key).to be_truthy end it "preserves the 'validator' status" do expect(client.validator).to be_truthy end it "includes the private key if present" do expect(client.private_key).to eq("monkeypants") end end describe "when deserializing from JSON (hash) using ApiClientV1#from_json" do let(:client_hash) do { "name" => "black", "public_key" => "crowes", "private_key" => "monkeypants", "admin" => true, "validator" => true, "create_key" => true, } end let(:client) do Chef::ApiClientV1.from_json(Chef::JSONCompat.to_json(client_hash)) end it "should deserialize to a Chef::ApiClientV1 object" do expect(client).to be_a_kind_of(Chef::ApiClientV1) end it "preserves the name" do expect(client.name).to eq("black") end it "preserves the public key" do expect(client.public_key).to eq("crowes") end it "preserves the admin status" do expect(client.admin).to be_truthy end it "preserves the create_key status" do expect(client.create_key).to be_truthy end it "preserves the 'validator' status" do expect(client.validator).to be_truthy end it "includes the private key if present" do expect(client.private_key).to eq("monkeypants") end end describe "when loading from JSON" do before do end before(:each) do client = { "name" => "black", "clientname" => "black", "public_key" => "crowes", "private_key" => "monkeypants", "admin" => true, "create_key" => true, "validator" => true, } @http_client = double("Chef::ServerAPI mock") allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) expect(@http_client).to receive(:get).with("clients/black").and_return(client) @client = Chef::ApiClientV1.load(client["name"]) end it "should deserialize to a Chef::ApiClientV1 object" do expect(@client).to be_a_kind_of(Chef::ApiClientV1) end it "preserves the name" do expect(@client.name).to eq("black") end it "preserves the public key" do expect(@client.public_key).to eq("crowes") end it "preserves the admin status" do expect(@client.admin).to be_a_kind_of(TrueClass) end it "preserves the create_key status" do expect(@client.create_key).to be_a_kind_of(TrueClass) end it "preserves the 'validator' status" do expect(@client.validator).to be_a_kind_of(TrueClass) end it "includes the private key if present" do expect(@client.private_key).to eq("monkeypants") end end describe "with correctly configured API credentials" do before do Chef::Config[:node_name] = "silent-bob" Chef::Config[:client_key] = File.expand_path("ssl/private_key.pem", CHEF_SPEC_DATA) end after do Chef::Config[:node_name] = nil Chef::Config[:client_key] = nil end let :private_key_data do File.open(Chef::Config[:client_key], "r") { |f| f.read.chomp } end end describe "when requesting a new key" do before do @http_client = double("Chef::ServerAPI mock") allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) end context "and the client does not exist on the server" do before do @a_404_response = Net::HTTPNotFound.new("404 not found and such", nil, nil) @a_404_exception = Net::HTTPServerException.new("404 not found exception", @a_404_response) expect(@http_client).to receive(:get).with("clients/lost-my-key").and_raise(@a_404_exception) end it "raises a 404 error" do expect { Chef::ApiClientV1.reregister("lost-my-key") }.to raise_error(Net::HTTPServerException) end end end describe "Versioned API Interactions" do let(:response_406) { OpenStruct.new(:code => "406") } let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } let(:payload) do { :name => "some_name", :validator => true, :admin => true, } end before do @client = Chef::ApiClientV1.new allow(@client).to receive(:chef_rest_v0).and_return(double("chef rest root v0 object")) allow(@client).to receive(:chef_rest_v1).and_return(double("chef rest root v1 object")) @client.name "some_name" @client.validator true @client.admin true end describe "create" do # from spec/support/shared/unit/user_and_client_shared.rb it_should_behave_like "user or client create" do let(:object) { @client } let(:error) { Chef::Exceptions::InvalidClientAttribute } let(:rest_v0) { @client.chef_rest_v0 } let(:rest_v1) { @client.chef_rest_v1 } let(:url) { "clients" } end context "when API V1 is not supported by the server" do # from spec/support/shared/unit/api_versioning.rb it_should_behave_like "version handling" do let(:object) { @client } let(:method) { :create } let(:http_verb) { :post } let(:rest_v1) { @client.chef_rest_v1 } end end end # create describe "update" do context "when a valid client is defined" do shared_examples_for "client updating" do it "updates the client" do expect(rest). to receive(:put).with("clients/some_name", payload).and_return(payload) @client.update end context "when only the name field exists" do before do # needed since there is no way to set to nil via code @client.instance_variable_set(:@validator, nil) @client.instance_variable_set(:@admin, nil) end after do @client.validator true @client.admin true end it "updates the client with only the name" do expect(rest). to receive(:put).with("clients/some_name", { :name => "some_name" }).and_return({ :name => "some_name" }) @client.update end end end context "when API V1 is supported by the server" do it_should_behave_like "client updating" do let(:rest) { @client.chef_rest_v1 } end end # when API V1 is supported by the server context "when API V1 is not supported by the server" do context "when no version is supported" do # from spec/support/shared/unit/api_versioning.rb it_should_behave_like "version handling" do let(:object) { @client } let(:method) { :create } let(:http_verb) { :post } let(:rest_v1) { @client.chef_rest_v1 } end end # when no version is supported context "when API V0 is supported" do before do allow(@client.chef_rest_v1).to receive(:put).and_raise(exception_406) allow(@client).to receive(:server_client_api_version_intersection).and_return([0]) end it_should_behave_like "client updating" do let(:rest) { @client.chef_rest_v0 } end end end # when API V1 is not supported by the server end # when a valid client is defined end # update # DEPRECATION # This can be removed after API V0 support is gone describe "reregister" do context "when server API V0 is valid on the Chef Server receiving the request" do it "creates a new object via the API" do expect(@client.chef_rest_v0).to receive(:put).with("clients/#{@client.name}", payload.merge({ :private_key => true })).and_return({}) @client.reregister end end # when server API V0 is valid on the Chef Server receiving the request context "when server API V0 is not supported by the Chef Server" do # from spec/support/shared/unit/api_versioning.rb it_should_behave_like "user and client reregister" do let(:object) { @client } let(:rest_v0) { @client.chef_rest_v0 } end end # when server API V0 is not supported by the Chef Server end # reregister end end chef-12.14.60/spec/unit/application/000077500000000000000000000000001276456504500171045ustar00rootroot00000000000000chef-12.14.60/spec/unit/application/agent_spec.rb000066400000000000000000000000001276456504500215270ustar00rootroot00000000000000chef-12.14.60/spec/unit/application/apply_spec.rb000066400000000000000000000077661276456504500216100ustar00rootroot00000000000000# # Author:: Bryan W. Berry () # Copyright:: Copyright 2012-2016, Bryan W. Berry # 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::Application::Apply do before do @app = Chef::Application::Apply.new allow(@app).to receive(:configure_logging).and_return(true) allow(Chef::Log).to receive(:debug).with("FIPS mode is enabled.") @recipe_text = "package 'nyancat'" Chef::Config[:solo_legacy_mode] = true end describe "configuring the application" do it "should set solo mode to true" do @app.reconfigure expect(Chef::Config[:solo_legacy_mode]).to be_truthy end end describe "read_recipe_file" do before do @recipe_file_name = "foo.rb" @recipe_path = File.expand_path(@recipe_file_name) @recipe_file = double("Tempfile (mock)", :read => @recipe_text) allow(@app).to receive(:open).with(@recipe_path).and_return(@recipe_file) allow(File).to receive(:exist?).with(@recipe_path).and_return(true) allow(Chef::Application).to receive(:fatal!).and_return(true) end it "should read text properly" do expect(@app.read_recipe_file(@recipe_file_name)[0]).to eq(@recipe_text) end it "should return a file_handle" do expect(@app.read_recipe_file(@recipe_file_name)[1]).to be_instance_of(RSpec::Mocks::Double) end 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", Chef::Exceptions::RecipeNotFound.new) @app.read_recipe_file(nil) end end describe "when recipe doesn't exist" do before 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/, Chef::Exceptions::RecipeNotFound.new) @app.read_recipe_file(@recipe_file_name) end end end describe "temp_recipe_file" do before do @app.instance_variable_set(:@recipe_text, @recipe_text) @app.temp_recipe_file @recipe_fh = @app.instance_variable_get(:@recipe_fh) end it "should open a tempfile" do expect(@recipe_fh.path).to match(/.*recipe-temporary-file.*/) end it "should write recipe text to the tempfile" do expect(@recipe_fh.read).to eq(@recipe_text) end it "should save the filename for later use" do expect(@recipe_fh.path).to eq(@app.instance_variable_get(:@recipe_filename)) end end describe "recipe_file_arg" do before do ARGV.clear end it "should exit and log message" do expect(Chef::Log).to receive(:debug).with(/^No recipe file provided/) expect { @app.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) } end end describe "when the json_attribs configuration option is specified" do let(:json_attribs) { { "a" => "b" } } let(:config_fetcher) { double(Chef::ConfigFetcher, :fetch_json => json_attribs) } let(:json_source) { "https://foo.com/foo.json" } before do Chef::Config[:json_attribs] = json_source expect(Chef::ConfigFetcher).to receive(:new).with(json_source). and_return(config_fetcher) end it "reads the JSON attributes from the specified source" do @app.reconfigure expect(@app.json_attribs).to eq(json_attribs) end end end chef-12.14.60/spec/unit/application/client_spec.rb000066400000000000000000000361641276456504500217330ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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" shared_context "with signal handlers" do before do Chef::Config[:specific_recipes] = [] # normally gets set in @app.reconfigure @app = Chef::Application::Client.new @app.setup_signal_handlers # Default logger doesn't work correctly when logging from a trap handler. @app.configure_logging end end shared_context "with interval_sleep" do before do run_count = 0 # uncomment to debug failures... # Chef::Log.init($stderr) # Chef::Log.level = :debug allow(@app).to receive(:run_chef_client) do run_count += 1 if run_count > 3 exit 0 end # If everything is fine, sending USR1 to self should prevent # app to go into splay sleep forever. Process.kill("USR1", Process.pid) # On Ruby < 2.1, we need to give the signal handlers a little # more time, otherwise the test will fail because interleavings. sleep 1 end number_of_sleep_calls = 0 # This is a very complicated way of writing # @app.should_receive(:sleep).once. # We have to do it this way because the main loop of # Chef::Application::Client swallows most exceptions, and we need to be # able to expose our expectation failures to the parent process in the test. allow(@app).to receive(:interval_sleep) do |arg| number_of_sleep_calls += 1 if number_of_sleep_calls > 1 exit 127 end end end end describe Chef::Application::Client, "reconfigure" do let(:app) do a = described_class.new a.cli_arguments = [] a end before do Chef::Config.reset allow(Kernel).to receive(:trap).and_return(:ok) allow(::File).to receive(:read).and_call_original allow(::File).to receive(:read).with(Chef::Config.platform_specific_path("/etc/chef/client.rb")).and_return("") @original_argv = ARGV.dup ARGV.clear allow(app).to receive(:trap) allow(app).to receive(:configure_logging).and_return(true) Chef::Config[:interval] = 10 Chef::Config[:once] = false # protect the unit tests against accidental --delete-entire-chef-repo from firing # for real during tests. DO NOT delete this line. expect(FileUtils).not_to receive(:rm_rf) end after do ARGV.replace(@original_argv) end describe "parse cli_arguments" do it "should call set_specific_recipes" do expect(app).to receive(:set_specific_recipes).and_return(true) app.reconfigure end shared_examples "sets the configuration" do |cli_arguments, expected_config| describe cli_arguments do before do ARGV.replace(cli_arguments.split) app.reconfigure end it "sets #{expected_config}" do expect(Chef::Config.configuration).to include expected_config end end end describe "--named-run-list" do it_behaves_like "sets the configuration", "--named-run-list arglebargle-example", :named_run_list => "arglebargle-example" end describe "--no-listen" do it_behaves_like "sets the configuration", "--no-listen", :listen => false end describe "--daemonize", :unix_only do context "with no value" do it_behaves_like "sets the configuration", "--daemonize", :daemonize => true end context "with an integer value" do it_behaves_like "sets the configuration", "--daemonize 5", :daemonize => 5 end context "with a non-integer value" do it_behaves_like "sets the configuration", "--daemonize foo", :daemonize => true end end describe "--config-option" do context "with a single value" do it_behaves_like "sets the configuration", "--config-option chef_server_url=http://example", :chef_server_url => "http://example" end context "with two values" do it_behaves_like "sets the configuration", "--config-option chef_server_url=http://example --config-option policy_name=web", :chef_server_url => "http://example", :policy_name => "web" end context "with a boolean value" do it_behaves_like "sets the configuration", "--config-option minimal_ohai=true", :minimal_ohai => true end context "with an empty value" do it "should terminate with message" do expect(Chef::Application).to receive(:fatal!).with('Unparsable config option ""').and_raise("so ded") ARGV.replace(["--config-option", ""]) expect { app.reconfigure }.to raise_error "so ded" end end context "with an invalid value" do it "should terminate with message" do expect(Chef::Application).to receive(:fatal!).with('Unparsable config option "asdf"').and_raise("so ded") ARGV.replace(["--config-option", "asdf"]) expect { app.reconfigure }.to raise_error "so ded" end end end end describe "when configured to not fork the client process" do before do Chef::Config[:client_fork] = false Chef::Config[:daemonize] = false Chef::Config[:interval] = nil Chef::Config[:splay] = nil end context "when interval is given" do before do Chef::Config[:interval] = 600 allow(ChefConfig).to receive(:windows?).and_return(false) end it "should terminate with message" do expect(Chef::Application).to receive(:fatal!).with( "Unforked chef-client interval runs are disabled in Chef 12. Configuration settings: interval = 600 seconds Enable chef-client interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options." ) app.reconfigure end end context "when interval is given on windows" do before do Chef::Config[:interval] = 600 allow(ChefConfig).to receive(:windows?).and_return(true) end it "should not terminate" do expect(Chef::Application).not_to receive(:fatal!) app.reconfigure end end context "when configured to run once" do before do Chef::Config[:once] = true Chef::Config[:interval] = 1000 end it "should reconfigure chef-client" do app.reconfigure expect(Chef::Config[:interval]).to be_nil end end end describe "daemonized mode", :unix_only do let(:daemonize) { true } before do Chef::Config[:daemonize] = daemonize allow(Chef::Daemon).to receive(:daemonize) end context "when no interval has been set" do before do Chef::Config[:interval] = nil end it "should set the interval to 1800" do app.reconfigure expect(Chef::Config.interval).to eq(1800) end end context "when the daemonize option is an integer" do include_context "with signal handlers" include_context "with interval_sleep" let(:wait_secs) { 1 } let(:daemonize) { wait_secs } before do allow(@app).to receive(:interval_sleep).with(wait_secs).and_return true allow(@app).to receive(:interval_sleep).with(0).and_call_original end it "sleeps for the amount of time passed" do pid = fork do expect(@app).to receive(:interval_sleep).with(wait_secs) @app.run_application end _pid, result = Process.waitpid2(pid) expect(result.exitstatus).to eq 0 end end end describe "when configured to run once" do before do Chef::Config[:once] = true Chef::Config[:daemonize] = false Chef::Config[:splay] = 60 Chef::Config[:interval] = 1800 end it "ignores the splay" do app.reconfigure expect(Chef::Config.splay).to be_nil end it "forces the interval to nil" do app.reconfigure expect(Chef::Config.interval).to be_nil end end describe "when the json_attribs configuration option is specified" do let(:json_attribs) { { "a" => "b" } } let(:config_fetcher) { double(Chef::ConfigFetcher, :fetch_json => json_attribs) } let(:json_source) { "https://foo.com/foo.json" } before do allow(app).to receive(:configure_chef).and_return(true) Chef::Config[:json_attribs] = json_source expect(Chef::ConfigFetcher).to receive(:new).with(json_source). and_return(config_fetcher) end it "reads the JSON attributes from the specified source" do app.reconfigure expect(app.chef_client_json).to eq(json_attribs) end end describe "audit mode" do shared_examples "experimental feature" do before do allow(Chef::Log).to receive(:warn) end end shared_examples "unrecognized setting" do it "fatals with a message including the incorrect setting" do expect(Chef::Application).to receive(:fatal!).with(/Unrecognized setting #{mode} for audit mode/) app.reconfigure end end shared_context "set via config file" do before do Chef::Config[:audit_mode] = mode end end shared_context "set via command line" do before do ARGV.replace(["--audit-mode", mode]) end end describe "enabled via config file" do include_context "set via config file" do let(:mode) { :enabled } include_examples "experimental feature" end end describe "enabled via command line" do include_context "set via command line" do let(:mode) { "enabled" } include_examples "experimental feature" end end describe "audit_only via config file" do include_context "set via config file" do let(:mode) { :audit_only } include_examples "experimental feature" end end describe "audit-only via command line" do include_context "set via command line" do let(:mode) { "audit-only" } include_examples "experimental feature" end end describe "unrecognized setting via config file" do include_context "set via config file" do let(:mode) { :derp } include_examples "unrecognized setting" end end describe "unrecognized setting via command line" do include_context "set via command line" do let(:mode) { "derp" } include_examples "unrecognized setting" end end end describe "when both the pidfile and lockfile opts are set to the same value" do before do Chef::Config[:pid_file] = "/path/to/file" Chef::Config[:lockfile] = "/path/to/file" end it "should throw an exception" do expect { app.reconfigure }.to raise_error(Chef::Exceptions::PIDFileLockfileMatch) end end it_behaves_like "an application that loads a dot d" do let(:dot_d_config_name) { :client_d_dir } end end describe Chef::Application::Client, "setup_application" do before do @app = Chef::Application::Client.new # this is all stuff the reconfigure method needs allow(@app).to receive(:configure_opt_parser).and_return(true) allow(@app).to receive(:configure_chef).and_return(true) allow(@app).to receive(:configure_logging).and_return(true) end it "should change privileges" do expect(Chef::Daemon).to receive(:change_privilege).and_return(true) @app.setup_application end after do Chef::Config[:solo] = false end end describe Chef::Application::Client, "configure_chef" do let(:app) { Chef::Application::Client.new } before do @original_argv = ARGV.dup ARGV.clear allow(::File).to receive(:read).with(Chef::Config.platform_specific_path("/etc/chef/client.rb")).and_return("") app.configure_chef end after do ARGV.replace(@original_argv) end it "should set the colored output to true by default on windows and true on all other platforms as well" do if windows? expect(Chef::Config[:color]).to be_truthy else expect(Chef::Config[:color]).to be_truthy end end end describe Chef::Application::Client, "run_application", :unix_only do include_context "with signal handlers" before(:each) do @pipe = IO.pipe @client = Chef::Client.new allow(Chef::Client).to receive(:new).and_return(@client) allow(@client).to receive(:run) do @pipe[1].puts "started" sleep 1 @pipe[1].puts "finished" end end context "when sent SIGTERM", :volatile_on_solaris do context "when converging in forked process" do before do Chef::Config[:daemonize] = true allow(Chef::Daemon).to receive(:daemonize).and_return(true) end it "should exit hard with exitstatus 3", :volatile do pid = fork do @app.run_application end Process.kill("TERM", pid) _pid, result = Process.waitpid2(pid) expect(result.exitstatus).to eq(3) end it "should allow child to finish converging" do pid = fork do @app.run_application end expect(@pipe[0].gets).to eq("started\n") Process.kill("TERM", pid) Process.wait(pid) # The timeout value needs to be large enough for the child process to finish expect(IO.select([@pipe[0]], nil, nil, 15)).not_to be_nil expect(@pipe[0].gets).to eq("finished\n") end end context "when running unforked" do before(:each) do Chef::Config[:client_fork] = false Chef::Config[:daemonize] = false end it "should exit gracefully when sent during converge" do pid = fork do @app.run_application end expect(@pipe[0].gets).to eq("started\n") Process.kill("TERM", pid) _pid, result = Process.waitpid2(pid) expect(result.exitstatus).to eq(0) expect(IO.select([@pipe[0]], nil, nil, 0)).not_to be_nil expect(@pipe[0].gets).to eq("finished\n") end it "should exit hard when sent before converge" do pid = fork do sleep 3 @app.run_application end Process.kill("TERM", pid) _pid, result = Process.waitpid2(pid) expect(result.exitstatus).to eq(3) end end end describe "when splay is set" do include_context "with interval_sleep" before do Chef::Config[:splay] = 10 Chef::Config[:interval] = 10 end it "shouldn't sleep when sent USR1" do allow(@app).to receive(:interval_sleep).with(0).and_call_original pid = fork do @app.run_application end _pid, result = Process.waitpid2(pid) expect(result.exitstatus).to eq(0) end end end chef-12.14.60/spec/unit/application/exit_code_spec.rb000066400000000000000000000222371276456504500224140ustar00rootroot00000000000000# # Author:: Steven Murawski () # 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 chef-12.14.60/spec/unit/application/knife_spec.rb000066400000000000000000000170271276456504500215460ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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" require "#{CHEF_SPEC_DATA}/knife_subcommand/test_yourself" describe Chef::Application::Knife do include SpecHelpers::Knife before(:all) do class NoopKnifeCommand < Chef::Knife option :opt_with_default, :short => "-D VALUE", :long => "-optwithdefault VALUE", :default => "default-value" def run end end end after(:each) do # reset some really nasty global state NoopKnifeCommand.reset_config_loader! end before(:each) do # Prevent code from getting loaded on every test invocation. allow(Chef::Knife).to receive(:load_commands) @knife = Chef::Application::Knife.new allow(@knife).to receive(:puts) allow(@knife).to receive(:trap) allow(Chef::Knife).to receive(:list_commands) end it "should exit 1 and print the options if no arguments are given at all" do with_argv([]) do expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) } end end it "should exit 2 if run without a sub command" do with_argv("--user", "adam") do expect(Chef::Log).to receive(:error).with(/you need to pass a sub\-command/i) expect { @knife.run }.to raise_error(SystemExit) { |e| expect(e.status).to eq(2) } end end it "should run a sub command with the applications command line option prototype" do with_argv(*%w{noop knife command with some args}) do knife = double(Chef::Knife) expect(Chef::Knife).to receive(:run).with(ARGV, @knife.options).and_return(knife) expect(@knife).to receive(:exit).with(0) @knife.run end end it "should set the colored output to true by default on windows and true on all other platforms as well" do with_argv(*%w{noop knife command}) do expect(@knife).to receive(:exit).with(0) @knife.run end if windows? expect(Chef::Config[:color]).to be_truthy else expect(Chef::Config[:color]).to be_truthy end end context "when given fips flags" do context "when Chef::Config[:fips]=false" do before do # This is required because the chef-fips pipeline does # has a default value of true for fips Chef::Config[:fips] = false end it "does not initialize fips mode when no flags are passed" do with_argv(*%w{noop knife command}) do expect(@knife).to receive(:exit).with(0) expect(Chef::Config).not_to receive(:enable_fips_mode) @knife.run expect(Chef::Config[:fips]).to eq(false) end end it "overwrites the Chef::Config value when passed --fips" do with_argv(*%w{noop knife command --fips}) do expect(@knife).to receive(:exit).with(0) expect(Chef::Config).to receive(:enable_fips_mode) @knife.run expect(Chef::Config[:fips]).to eq(true) end end end context "when Chef::Config[:fips]=true" do before do Chef::Config[:fips] = true end it "initializes fips mode when passed --fips" do with_argv(*%w{noop knife command --fips}) do expect(@knife).to receive(:exit).with(0) expect(Chef::Config).to receive(:enable_fips_mode) @knife.run expect(Chef::Config[:fips]).to eq(true) end end it "overwrites the Chef::Config value when passed --no-fips" do with_argv(*%w{noop knife command --no-fips}) do expect(@knife).to receive(:exit).with(0) expect(Chef::Config).not_to receive(:enable_fips_mode) @knife.run expect(Chef::Config[:fips]).to eq(false) end end end end describe "when given a path to the client key" do it "expands a relative path relative to the CWD" do relative_path = ".chef/client.pem" allow(Dir).to receive(:pwd).and_return(CHEF_SPEC_DATA) with_argv(*%W{noop knife command -k #{relative_path}}) do expect(@knife).to receive(:exit).with(0) @knife.run end expect(Chef::Config[:client_key]).to eq(File.join(CHEF_SPEC_DATA, relative_path)) end it "expands a ~/home/path to the correct full path" do home_path = "~/.chef/client.pem" with_argv(*%W{noop knife command -k #{home_path}}) do expect(@knife).to receive(:exit).with(0) @knife.run end expect(Chef::Config[:client_key]).to eq(File.join(ENV["HOME"], ".chef/client.pem").gsub((File::ALT_SEPARATOR || '\\'), File::SEPARATOR)) end it "does not expand a full path" do full_path = if windows? "C:/chef/client.pem" else "/etc/chef/client.pem" end with_argv(*%W{noop knife command -k #{full_path}}) do expect(@knife).to receive(:exit).with(0) @knife.run end expect(Chef::Config[:client_key]).to eq(full_path) end end describe "with environment configuration" do before do Chef::Config[:environment] = nil end it "should default to no environment" do with_argv(*%w{noop knife command}) do expect(@knife).to receive(:exit).with(0) @knife.run end expect(Chef::Config[:environment]).to eq(nil) end it "should load the environment from the config file" do config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb") with_argv(*%W{noop knife command -c #{config_file}}) do expect(@knife).to receive(:exit).with(0) @knife.run end expect(Chef::Config[:environment]).to eq("production") end it "should load the environment from the CLI options" do with_argv(*%w{noop knife command -E development}) do expect(@knife).to receive(:exit).with(0) @knife.run end expect(Chef::Config[:environment]).to eq("development") end it "should override the config file environment with the CLI environment" do config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb") with_argv(*%W{noop knife command -c #{config_file} -E override}) do expect(@knife).to receive(:exit).with(0) @knife.run end expect(Chef::Config[:environment]).to eq("override") end it "should override the config file environment with the CLI environment regardless of order" do config_file = File.join(CHEF_SPEC_DATA, "environment-config.rb") with_argv(*%W{noop knife command -E override -c #{config_file}}) do expect(@knife).to receive(:exit).with(0) @knife.run end expect(Chef::Config[:environment]).to eq("override") end it "should run a sub command with the applications command line option prototype" do with_argv(*%w{noop knife command with some args}) do knife = double(Chef::Knife) expect(Chef::Knife).to receive(:run).with(ARGV, @knife.options).and_return(knife) expect(@knife).to receive(:exit).with(0) @knife.run end end end end chef-12.14.60/spec/unit/application/server_spec.rb000066400000000000000000000000001276456504500217370ustar00rootroot00000000000000chef-12.14.60/spec/unit/application/solo_spec.rb000066400000000000000000000200561276456504500214220ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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::Application::Solo do let(:app) { Chef::Application::Solo.new } before do allow(Kernel).to receive(:trap).and_return(:ok) allow(app).to receive(:configure_opt_parser).and_return(true) allow(app).to receive(:configure_chef).and_return(true) allow(app).to receive(:configure_logging).and_return(true) allow(app).to receive(:trap) Chef::Config[:json_attribs] = false Chef::Config[:solo] = true Chef::Config[:solo_legacy_mode] = true # protect the unit tests against accidental --delete-entire-chef-repo from firing # for real during tests. DO NOT delete this line. expect(FileUtils).not_to receive(:rm_rf) end context "in legacy mode" do describe "configuring the application" do it "should call set_specific_recipes" do expect(app).to receive(:set_specific_recipes) app.reconfigure end it "should set solo mode to true" do app.reconfigure expect(Chef::Config[:solo]).to be_truthy end it "should set audit-mode to :disabled" do app.reconfigure expect(Chef::Config[:audit_mode]).to be :disabled end describe "when configured to not fork the client process" do before do Chef::Config[:client_fork] = false Chef::Config[:daemonize] = false Chef::Config[:interval] = nil Chef::Config[:splay] = nil end context "when interval is given" do before do Chef::Config[:interval] = 600 end it "should terminate with message" do expect(Chef::Application).to receive(:fatal!).with( "Unforked chef-client interval runs are disabled in Chef 12. Configuration settings: interval = 600 seconds Enable chef-client interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options." ) app.reconfigure end end end describe "when in daemonized mode and no interval has been set" do before do Chef::Config[:daemonize] = true end it "should set the interval to 1800" do Chef::Config[:interval] = nil app.reconfigure expect(Chef::Config[:interval]).to eq(1800) end end describe "when the json_attribs configuration option is specified" do let(:json_attribs) { { "a" => "b" } } let(:config_fetcher) { double(Chef::ConfigFetcher, :fetch_json => json_attribs) } let(:json_source) { "https://foo.com/foo.json" } before do Chef::Config[:json_attribs] = json_source expect(Chef::ConfigFetcher).to receive(:new).with(json_source). and_return(config_fetcher) end it "reads the JSON attributes from the specified source" do app.reconfigure expect(app.chef_client_json).to eq(json_attribs) end end it "downloads a tarball when the recipe_url configuration option is specified" do Chef::Config[:cookbook_path] = "#{Dir.tmpdir}/chef-solo/cookbooks" Chef::Config[:recipe_url] = "http://junglist.gen.nz/recipes.tgz" expect(FileUtils).to receive(:mkdir_p).with("#{Dir.tmpdir}/chef-solo").and_return(true) tarfile = StringIO.new("remote_tarball_content") target_file = StringIO.new expect(app).to receive(:open).with("http://junglist.gen.nz/recipes.tgz").and_yield(tarfile) expect(File).to receive(:open).with("#{Dir.tmpdir}/chef-solo/recipes.tgz", "wb").and_yield(target_file) archive = double(Mixlib::Archive) expect(Mixlib::Archive).to receive(:new).with("#{Dir.tmpdir}/chef-solo/recipes.tgz").and_return(archive) expect(archive).to receive(:extract).with("#{Dir.tmpdir}/chef-solo", { perms: false, ignore: /^\.$/ }) app.reconfigure expect(target_file.string).to eq("remote_tarball_content") end it "fetches the recipe_url first when both json_attribs and recipe_url are specified" do json_attribs = { "a" => "b" } config_fetcher = instance_double("Chef::ConfigFetcher", :fetch_json => json_attribs) Chef::Config[:json_attribs] = "https://foo.com/foo.json" Chef::Config[:recipe_url] = "http://icanhas.cheezburger.com/lolcats" Chef::Config[:cookbook_path] = "#{Dir.tmpdir}/chef-solo/cookbooks" expect(FileUtils).to receive(:mkdir_p).with("#{Dir.tmpdir}/chef-solo").and_return(true) archive = double(Mixlib::Archive) expect(Mixlib::Archive).to receive(:new).with("#{Dir.tmpdir}/chef-solo/recipes.tgz").and_return(archive) expect(archive).to receive(:extract).with("#{Dir.tmpdir}/chef-solo", { perms: false, ignore: /^\.$/ }) expect(app).to receive(:fetch_recipe_tarball).ordered expect(Chef::ConfigFetcher).to receive(:new).ordered.and_return(config_fetcher) app.reconfigure end end describe "after the application has been configured" do before do Chef::Config[:solo] = true Chef::Config[:solo_legacy_mode] = true allow(Chef::Daemon).to receive(:change_privilege) chef_client = double("Chef::Client") allow(Chef::Client).to receive(:new).and_return(chef_client) # this is all stuff the reconfigure method needs allow(app).to receive(:configure_opt_parser).and_return(true) allow(app).to receive(:configure_chef).and_return(true) allow(app).to receive(:configure_logging).and_return(true) end it "should change privileges" do expect(Chef::Daemon).to receive(:change_privilege).and_return(true) app.setup_application end end it_behaves_like "an application that loads a dot d" do let(:dot_d_config_name) { :solo_d_dir } end end context "in local mode" do before do Chef::Config[:solo_legacy_mode] = false end it "sets solo mode to true" do app.reconfigure expect(Chef::Config[:solo]).to be_truthy end it "sets local mode to true" do app.reconfigure 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 "sets the repo path" do expect(Chef::Config).to receive(:find_chef_repo_path).and_return("/var/chef") app.reconfigure expect(Chef::Config.has_key?(:chef_repo_path)).to be_truthy expect(Chef::Config[:chef_repo_path]).to eq ("/var/chef") 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) allow(app).to receive(:configure_chef).and_return(true) allow(app).to receive(:configure_logging).and_return(true) expect(Chef::Application::Client).to receive_message_chain(:new, :run) app.run end end end chef-12.14.60/spec/unit/application_spec.rb000066400000000000000000000315321276456504500204470ustar00rootroot00000000000000# # Author:: AJ Christensen () # Author:: Mark Mzyk (mmzyk@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::Application do before do @original_argv = ARGV.dup ARGV.clear Chef::Log.logger = Logger.new(StringIO.new) @app = Chef::Application.new allow(@app).to receive(:trap) allow(Dir).to receive(:chdir).and_return(0) allow(@app).to receive(:reconfigure) Chef::Log.init(STDERR) end after do ARGV.replace(@original_argv) end context "when there are no configuration errors" do before do expect(Chef::Log).to_not receive(:fatal) expect(Chef::Application).to_not receive(:fatal!) end describe "reconfigure" do before do @app = Chef::Application.new allow(@app).to receive(:configure_chef).and_return(true) allow(@app).to receive(:configure_logging).and_return(true) end it "should configure chef" do expect(@app).to receive(:configure_chef).and_return(true) @app.reconfigure end it "should configure logging" do expect(@app).to receive(:configure_logging).and_return(true) @app.reconfigure end it "should not receive set_specific_recipes" do expect(@app).to_not receive(:set_specific_recipes) @app.reconfigure end end describe Chef::Application do before do @app = Chef::Application.new end describe "run" do before do allow(@app).to receive(:setup_application).and_return(true) allow(@app).to receive(:run_application).and_return(true) allow(@app).to receive(:configure_chef).and_return(true) allow(@app).to receive(:configure_logging).and_return(true) end it "should reconfigure the application before running" do expect(@app).to receive(:reconfigure).and_return(true) @app.run end it "should setup the application before running it" do expect(@app).to receive(:setup_application).and_return(true) @app.run end it "should run the actual application" do expect(@app).to receive(:run_application).and_return(true) @app.run end end end describe "configure_chef" do before do # Silence warnings when no config file exists allow(Chef::Log).to receive(:warn) @app = Chef::Application.new allow(@app).to receive(:parse_options).and_return(true) expect(Chef::Config).to receive(:export_proxies).and_return(true) end it "should parse the commandline options" do expect(@app).to receive(:parse_options).and_return(true) @app.config[:config_file] = "/etc/chef/default.rb" #have a config file set, to prevent triggering error block @app.configure_chef end describe "when a config_file is present" do let(:config_content) { "rspec_ran('true')" } let(:config_location) { "/etc/chef/default.rb" } let(:config_location_pathname) do p = Pathname.new(config_location) allow(p).to receive(:realpath).and_return(config_location) p end before do @app.config[:config_file] = config_location # force let binding to get evaluated or else we stub Pathname.new before we try to use it. config_location_pathname allow(Pathname).to receive(:new).with(config_location).and_return(config_location_pathname) expect(File).to receive(:read). with(config_location). and_return(config_content) end it "should configure chef::config from a file" do expect(Chef::Config).to receive(:from_string).with(config_content, File.expand_path(config_location)) @app.configure_chef end it "should merge the local config hash into chef::config" do #File.should_receive(:open).with("/etc/chef/default.rb").and_yield(@config_file) @app.configure_chef expect(Chef::Config.rspec_ran).to eq("true") end context "when openssl fips" do before do allow(Chef::Config).to receive(:fips).and_return(true) end it "sets openssl in fips mode" do expect(Chef::Config).to receive(:enable_fips_mode) @app.configure_chef end end end describe "when there is no config_file defined" do before do @app.config[:config_file] = nil end it "should emit a warning" do expect(Chef::Config).not_to receive(:from_file).with("/etc/chef/default.rb") expect(Chef::Log).to receive(:warn).with("No config file found or specified on command line, using command line options.") @app.configure_chef end end describe "when the config file is set and not found" do before do @app.config[:config_file] = "/etc/chef/notfound" end it "should use the passed in command line options and defaults" do expect(Chef::Config).to receive(:merge!) @app.configure_chef end end end describe "when configuring the logger" do before do @app = Chef::Application.new allow(Chef::Log).to receive(:init) end it "should initialise the chef logger" do allow(Chef::Log).to receive(:level=) @monologger = double("Monologger") expect(MonoLogger).to receive(:new).with(Chef::Config[:log_location]).and_return(@monologger) expect(Chef::Log).to receive(:init).with(@monologger) @app.configure_logging end shared_examples_for "log_level_is_auto" do context "when STDOUT is to a tty" do before do allow(STDOUT).to receive(:tty?).and_return(true) end it "configures the log level to :warn" do @app.configure_logging expect(Chef::Log.level).to eq(:warn) end context "when force_logger is configured" do before do Chef::Config[:force_logger] = true end it "configures the log level to info" do @app.configure_logging expect(Chef::Log.level).to eq(:info) end end end context "when STDOUT is not to a tty" do before do allow(STDOUT).to receive(:tty?).and_return(false) end it "configures the log level to :info" do @app.configure_logging expect(Chef::Log.level).to eq(:info) end context "when force_formatter is configured" do before do Chef::Config[:force_formatter] = true end it "sets the log level to :warn" do @app.configure_logging expect(Chef::Log.level).to eq(:warn) end end end end context "when log_level is not set" do it_behaves_like "log_level_is_auto" end context "when log_level is :auto" do before do Chef::Config[:log_level] = :auto end it_behaves_like "log_level_is_auto" end describe "log_location" do shared_examples("sets log_location") do |config_value, expected_class| context "when the configured value is #{config_value.inspect}" do let(:logger_instance) { instance_double(expected_class).as_null_object } before do allow(expected_class).to receive(:new).and_return(logger_instance) Chef::Config[:log_location] = config_value end it "it sets log_location to an instance of #{expected_class}" do expect(expected_class).to receive(:new).with no_args @app.configure_logging expect(Chef::Config[:log_location]).to be logger_instance end end end if Chef::Platform.windows? it_behaves_like "sets log_location", :win_evt, Chef::Log::WinEvt it_behaves_like "sets log_location", "win_evt", Chef::Log::WinEvt else it_behaves_like "sets log_location", :syslog, Chef::Log::Syslog it_behaves_like "sets log_location", "syslog", Chef::Log::Syslog end end end end context "with an invalid log location" do it "logs a fatal error and exits" do Chef::Config[:log_location] = "/tmp/non-existing-dir/logfile" expect(Chef::Log).to receive(:fatal).at_least(:once) expect(Process).to receive(:exit) @app.configure_logging end end describe "class method: fatal!" do before do allow(STDERR).to receive(:puts).with("FATAL: blah").and_return(true) allow(Chef::Log).to receive(:fatal).and_return(true) allow(Process).to receive(:exit).and_return(true) end it "should log an error message to the logger" do expect(Chef::Log).to receive(:fatal).with("blah").and_return(true) Chef::Application.fatal! "blah" end describe "when an exit code is supplied" do it "should exit with the given exit code" do expect(Process).to receive(:exit).with(-100).and_return(true) Chef::Application.fatal! "blah", -100 end end describe "when an exit code is not supplied" do it "should exit with the default exit code" do expect(Process).to receive(:exit).with(-1).and_return(true) Chef::Application.fatal! "blah" end end end describe "setup_application" do before do @app = Chef::Application.new end it "should raise an error" do expect { @app.setup_application }.to raise_error(Chef::Exceptions::Application) end end describe "run_application" do before do @app = Chef::Application.new end it "should raise an error" do expect { @app.run_application }.to raise_error(Chef::Exceptions::Application) end end context "when the config file is not available" do it "should warn for bad config file path" do @app.config[:config_file] = "/tmp/non-existing-dir/file" config_file_regexp = Regexp.new @app.config[:config_file] expect(Chef::Log).to receive(:warn).at_least(:once).with(config_file_regexp).and_return(true) allow(Chef::Log).to receive(:warn).and_return(true) @app.configure_chef end end describe "run_chef_client" do context "with an application" do let(:app) { Chef::Application.new } context "when called with an invalid argument" do before do allow(app).to receive(:fork_chef_client).and_return(true) allow(app).to receive(:run_with_graceful_exit_option).and_return(true) end it "should raise an argument error detailing the problem" do specific_recipes_regexp = Regexp.new "received non-Array like specific_recipes argument" expect { app.run_chef_client(nil) }.to raise_error(ArgumentError, specific_recipes_regexp) end end context "when called with an Array-like argument (#size)" do before do allow(app).to receive(:fork_chef_client).and_return(true) allow(app).to receive(:run_with_graceful_exit_option).and_return(true) end it "should be cool" do expect { app.run_chef_client([]) }.not_to raise_error end end end end describe "configuration errors" do before do expect(Process).to receive(:exit) end def raises_informative_fatals_on_configure_chef config_file_regexp = Regexp.new @app.config[:config_file] expect(Chef::Log).to receive(:fatal). with(/Configuration error/) expect(Chef::Log).to receive(:fatal). with(config_file_regexp). at_least(1).times @app.configure_chef end describe "when config file exists but contains errors" do def create_config_file(text) @config_file = Tempfile.new("rspec-chef-config") @config_file.write(text) @config_file.close @app.config[:config_file] = @config_file.path end after(:each) do @config_file.unlink end it "should raise informative fatals for badly written config" do create_config_file("text that should break the config parsing") raises_informative_fatals_on_configure_chef end end end end chef-12.14.60/spec/unit/audit/000077500000000000000000000000001276456504500157075ustar00rootroot00000000000000chef-12.14.60/spec/unit/audit/audit_event_proxy_spec.rb000066400000000000000000000226671276456504500230330ustar00rootroot00000000000000# # Author:: Tyler Ball () # Author:: Claire McQuin () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/audit/audit_event_proxy" describe Chef::Audit::AuditEventProxy do let(:stdout) { StringIO.new } let(:events) { double("Chef::Events") } let(:audit_event_proxy) { Chef::Audit::AuditEventProxy.new(stdout) } before do Chef::Audit::AuditEventProxy.events = events end describe "#example_group_started" do let(:description) { "poots" } let(:group) do double("ExampleGroup", :parent_groups => parents, :description => description) end let(:notification) { double("Notification", :group => group) } context "when notified from a top-level example group" do let(:parents) { [double("ExampleGroup")] } it "notifies control_group_started event" do expect(Chef::Log).to receive(:debug). with("Entered \`control_group\` block named poots") expect(events).to receive(:control_group_started). with(description) audit_event_proxy.example_group_started(notification) end end context "when notified from an inner-level example group" do let(:parents) { [double("ExampleGroup"), double("OuterExampleGroup")] } it "does nothing" do expect(events).to_not receive(:control_group_started) audit_event_proxy.example_group_started(notification) end end end describe "#stop" do let(:examples) { [] } let(:notification) { double("Notification", :examples => examples) } let(:exception) { nil } let(:example) { double("Example", :exception => exception) } let(:control_group_name) { "audit test" } let(:control_data) { double("ControlData") } before do allow(Chef::Log).to receive(:info) # silence messages to output stream end it "sends a message that audits completed" do expect(Chef::Log).to receive(:info).with("Successfully executed all \`control_group\` blocks and contained examples") audit_event_proxy.stop(notification) end context "when an example succeeded" do let(:examples) { [example] } let(:excpetion) { nil } before do allow(audit_event_proxy).to receive(:build_control_from). with(example). and_return([control_group_name, control_data]) end it "notifies events" do expect(events).to receive(:control_example_success). with(control_group_name, control_data) audit_event_proxy.stop(notification) end end context "when an example failed" do let(:examples) { [example] } let(:exception) { double("ExpectationNotMet") } before do allow(audit_event_proxy).to receive(:build_control_from). with(example). and_return([control_group_name, control_data]) end it "notifies events" do expect(events).to receive(:control_example_failure). with(control_group_name, control_data, exception) audit_event_proxy.stop(notification) end end describe "#build_control_from" do let(:examples) { [example] } let(:example) do double("Example", :metadata => metadata, :description => example_description, :full_description => full_description, :exception => nil) end let(:metadata) do { :described_class => described_class, :example_group => example_group, :line_number => line, } end let(:example_group) do { :description => group_description, :parent_example_group => parent_group, } end let(:parent_group) do { :description => control_group_name, :parent_example_group => nil, } end let(:line) { 27 } let(:control_data) do { :name => example_description, :desc => full_description, :resource_type => resource_type, :resource_name => resource_name, :context => context, :line_number => line, } end shared_examples "built control" do before do if described_class allow(described_class).to receive(:instance_variable_get). with(:@name). and_return(resource_name) allow(described_class.class).to receive(:name). and_return(described_class.class) end end it "returns the controls block name and example metadata for reporting" do expect(events).to receive(:control_example_success). with(control_group_name, control_data) audit_event_proxy.stop(notification) end end describe "a top-level example" do # controls "port 111" do # it "has nobody listening" do # expect(port("111")).to_not be_listening # end # end # Description parts let(:group_description) { "port 111" } let(:example_description) { "has nobody listening" } let(:full_description) { group_description + " " + example_description } # Metadata fields let(:described_class) { nil } # Example group (metadata[:example_group]) fields let(:parent_group) { nil } # Expected returns let(:control_group_name) { group_description } # Control data fields let(:resource_type) { nil } let(:resource_name) { nil } let(:context) { [] } include_examples "built control" end describe "an example with an implicit subject" do # controls "application ports" do # control port(111) do # it { is_expected.to_not be_listening } # end # end # Description parts let(:control_group_name) { "application ports" } let(:group_description) { "#{resource_type} #{resource_name}" } let(:example_description) { "should not be listening" } let(:full_description) do [control_group_name, group_description, example_description].join(" ") end # Metadata fields let(:described_class) do double("Serverspec::Type::Port", :class => "Serverspec::Type::Port", :name => resource_name) end # Control data fields let(:resource_type) { "Port" } let(:resource_name) { "111" } let(:context) { [] } include_examples "built control" end describe "an example in a nested context" do # controls "application ports" do # control "port 111" do # it "is not listening" do # expect(port(111)).to_not be_listening # end # end # end # Description parts let(:control_group_name) { "application ports" } let(:group_description) { "port 111" } let(:example_description) { "is not listening" } let(:full_description) do [control_group_name, group_description, example_description].join(" ") end # Metadata fields let(:described_class) { nil } # Control data fields let(:resource_type) { nil } let(:resource_name) { nil } let(:context) { [group_description] } include_examples "built control" end describe "an example in a nested context including Serverspec" do # controls "application directory" do # control file("/tmp/audit") do # describe file("/tmp/audit/test_file") do # it "is a file" do # expect(subject).to be_file # end # end # end # end # Description parts let(:control_group_name) { "application directory" } let(:outer_group_description) { "File \"tmp/audit\"" } let(:group_description) { "#{resource_type} #{resource_name}" } let(:example_description) { "is a file" } let(:full_description) do [control_group_name, outer_group_description, group_description, example_description].join(" ") end # Metadata parts let(:described_class) do double("Serverspec::Type::File", :class => "Serverspec::Type::File", :name => resource_name) end # Example group parts let(:parent_group) do { :description => outer_group_description, :parent_example_group => control_group, } end let(:control_group) do { :description => control_group_name, :parent_example_group => nil, } end # Control data parts let(:resource_type) { "File" } let(:resource_name) { "/tmp/audit/test_file" } let(:context) { [outer_group_description] } include_examples "built control" end end end end chef-12.14.60/spec/unit/audit/audit_reporter_spec.rb000066400000000000000000000343031276456504500223010ustar00rootroot00000000000000# # Author:: Tyler Ball () # Author:: Claire McQuin () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Audit::AuditReporter do let(:rest) { double("rest") } let(:reporter) { described_class.new(rest) } let(:node) { double("node", :name => "sofreshsoclean") } let(:run_id) { 0 } let(:start_time) { Time.new(2014, 12, 3, 9, 31, 05, "-08:00") } let(:end_time) { Time.new(2014, 12, 3, 9, 36, 14, "-08:00") } let(:run_status) do instance_double(Chef::RunStatus, :node => node, :run_id => run_id, :start_time => start_time, :end_time => end_time) end describe "#audit_phase_start" do it "notifies audit phase start to debug log" do expect(Chef::Log).to receive(:debug).with(/Audit Reporter starting/) reporter.audit_phase_start(run_status) end it "initializes an AuditData object" do expect(Chef::Audit::AuditData).to receive(:new).with(run_status.node.name, run_status.run_id) reporter.audit_phase_start(run_status) end it "saves the run status" do reporter.audit_phase_start(run_status) expect(reporter.instance_variable_get(:@run_status)).to eq run_status end end describe "#run_completed" do let(:audit_data) { Chef::Audit::AuditData.new(node.name, run_id) } let(:run_data) { audit_data.to_hash } before do allow(reporter).to receive(:auditing_enabled?).and_return(true) allow(reporter).to receive(:run_status).and_return(run_status) allow(rest).to receive(:post).and_return(true) allow(reporter).to receive(:audit_data).and_return(audit_data) allow(reporter).to receive(:run_status).and_return(run_status) allow(audit_data).to receive(:to_hash).and_return(run_data) end describe "a successful run with auditing enabled" do it "sets run start and end times" do iso_start_time = "2014-12-03T17:31:05Z" iso_end_time = "2014-12-03T17:36:14Z" reporter.run_completed(node) expect(audit_data.start_time).to eq iso_start_time expect(audit_data.end_time).to eq iso_end_time end it "posts audit data to server endpoint" do headers = { "X-Ops-Audit-Report-Protocol-Version" => Chef::Audit::AuditReporter::PROTOCOL_VERSION, } expect(rest).to receive(:post). with("controls", run_data, headers) reporter.run_completed(node) end context "when audit phase failed" do let(:audit_error) do double("AuditError", :class => "Chef::Exceptions::AuditError", :message => "Audit phase failed with error message: derpderpderp", :backtrace => ["/path/recipe.rb:57", "/path/library.rb:106"]) end before do reporter.instance_variable_set(:@audit_phase_error, audit_error) end it "reports an error" do reporter.run_completed(node) expect(run_data).to have_key(:error) expect(run_data).to have_key(:error) expect(run_data[:error]).to eq <<-EOM.strip! Chef::Exceptions::AuditError: Audit phase failed with error message: derpderpderp /path/recipe.rb:57 /path/library.rb:106 EOM end end context "when unable to post to server" do let(:error) do e = StandardError.new e.set_backtrace(caller) e end before do expect(rest).to receive(:post).and_raise(error) allow(error).to receive(:respond_to?).and_call_original end context "the error is an http error" do let(:response) { double("response", :code => code) } before do expect(Chef::Log).to receive(:debug).with(/Sending audit report/) expect(Chef::Log).to receive(:debug).with(/Audit Report/) allow(error).to receive(:response).and_return(response) expect(error).to receive(:respond_to?).with(:response).and_return(true) end context "when the code is 404" do let(:code) { "404" } it "logs that the server doesn't support audit reporting" do expect(Chef::Log).to receive(:debug).with(/Server doesn't support audit reporting/) reporter.run_completed(node) end end shared_examples "non-404 error code" do it "saves the error report" do expect(Chef::FileCache).to receive(:store). with("failed-audit-data.json", an_instance_of(String), 0640). and_return(true) expect(Chef::FileCache).to receive(:load). with("failed-audit-data.json", false). and_return(true) expect(Chef::Log).to receive(:error).with(/Failed to post audit report to server/) reporter.run_completed(node) end end context "when the code is not 404" do include_examples "non-404 error code" do let(:code) { "505" } end end context "when there is no code" do include_examples "non-404 error code" do let(:code) { nil } end end end context "the error is not an http error" do it "logs the error" do expect(error).to receive(:respond_to?).with(:response).and_return(false) expect(Chef::Log).to receive(:error).with(/Failed to post audit report to server/) reporter.run_completed(node) end end context "when reporting url fatals are enabled" do before do allow(Chef::Config).to receive(:[]). with(:enable_reporting_url_fatals). and_return(true) end it "raises the error" do expect(error).to receive(:respond_to?).with(:response).and_return(false) allow(Chef::Log).to receive(:error).and_return(true) expect(Chef::Log).to receive(:error).with(/Reporting fatals enabled. Aborting run./) expect { reporter.run_completed(node) }.to raise_error(error) end end end end context "when auditing is not enabled" do before do allow(Chef::Log).to receive(:debug) end it "doesn't send reports" do expect(reporter).to receive(:auditing_enabled?).and_return(false) expect(Chef::Log).to receive(:debug).with("Audit Reports are disabled. Skipping sending reports.") reporter.run_completed(node) end end context "when the run fails before audits" do before do allow(Chef::Log).to receive(:debug) end it "doesn't send reports" do expect(reporter).to receive(:auditing_enabled?).and_return(true) expect(reporter).to receive(:run_status).and_return(nil) expect(Chef::Log).to receive(:debug).with("Run failed before audit mode was initialized, not sending audit report to server") reporter.run_completed(node) end end end describe "#run_failed" do let(:audit_data) { Chef::Audit::AuditData.new(node.name, run_id) } let(:run_data) { audit_data.to_hash } let(:audit_error) do double("AuditError", :class => "Chef::Exceptions::AuditError", :message => "Audit phase failed with error message: derpderpderp", :backtrace => ["/path/recipe.rb:57", "/path/library.rb:106"]) end let(:run_error) do double("RunError", :class => "Chef::Exceptions::RunError", :message => "This error shouldn't be reported.", :backtrace => ["fix it", "fix it", "fix it"]) end before do allow(reporter).to receive(:auditing_enabled?).and_return(true) allow(reporter).to receive(:run_status).and_return(run_status) allow(reporter).to receive(:audit_data).and_return(audit_data) allow(audit_data).to receive(:to_hash).and_return(run_data) end context "when no prior exception is stored" do it "reports no error" do expect(rest).to receive(:post) reporter.run_failed(run_error) expect(run_data).to_not have_key(:error) end end context "when some prior exception is stored" do before do reporter.instance_variable_set(:@audit_phase_error, audit_error) end it "reports the prior error" do expect(rest).to receive(:post) reporter.run_failed(run_error) expect(run_data).to have_key(:error) expect(run_data[:error]).to eq <<-EOM.strip! Chef::Exceptions::AuditError: Audit phase failed with error message: derpderpderp /path/recipe.rb:57 /path/library.rb:106 EOM end end end shared_context "audit data" do let(:control_group_foo) do instance_double(Chef::Audit::ControlGroupData, :metadata => double("foo metadata")) end let(:control_group_bar) do instance_double(Chef::Audit::ControlGroupData, :metadata => double("bar metadata")) end let(:ordered_control_groups) do { "foo" => control_group_foo, "bar" => control_group_bar, } end let(:audit_data) do instance_double(Chef::Audit::AuditData, :add_control_group => true) end let(:run_context) do instance_double(Chef::RunContext, :audits => ordered_control_groups) end before do allow(reporter).to receive(:ordered_control_groups).and_return(ordered_control_groups) allow(reporter).to receive(:audit_data).and_return(audit_data) allow(reporter).to receive(:run_status).and_return(run_status) allow(run_status).to receive(:run_context).and_return(run_context) end end describe "#audit_phase_complete" do include_context "audit data" it "notifies audit phase finished to debug log" do expect(Chef::Log).to receive(:debug).with(/Audit Reporter completed/) reporter.audit_phase_complete("Output from audit mode") end it "collects audit data" do ordered_control_groups.each do |_name, group| expect(audit_data).to receive(:add_control_group).with(group) end reporter.audit_phase_complete("Output from audit mode") end end describe "#audit_phase_failed" do include_context "audit data" let(:error) { double("Exception") } it "notifies audit phase failed to debug log" do expect(Chef::Log).to receive(:debug).with(/Audit Reporter failed/) reporter.audit_phase_failed(error, "Output from audit mode") end it "collects audit data" do ordered_control_groups.each do |_name, group| expect(audit_data).to receive(:add_control_group).with(group) end reporter.audit_phase_failed(error, "Output from audit mode") end end describe "#control_group_started" do include_context "audit data" let(:name) { "bat" } let(:control_group) do instance_double(Chef::Audit::ControlGroupData, :metadata => double("metadata")) end before do allow(Chef::Audit::ControlGroupData).to receive(:new). with(name, control_group.metadata). and_return(control_group) end it "stores the control group" do expect(ordered_control_groups).to receive(:has_key?).with(name).and_return(false) allow(run_context.audits).to receive(:[]).with(name).and_return(control_group) expect(ordered_control_groups).to receive(:store). with(name, control_group). and_call_original reporter.control_group_started(name) # stubbed :has_key? above, which is used by the have_key matcher, # so instead we check the response to Hash's #key? because luckily # #key? does not call #has_key? expect(ordered_control_groups.key?(name)).to be true expect(ordered_control_groups[name]).to eq control_group end context "when a control group with the same name has been seen" do it "raises an exception" do expect(ordered_control_groups).to receive(:has_key?).with(name).and_return(true) expect { reporter.control_group_started(name) }.to raise_error(Chef::Exceptions::AuditControlGroupDuplicate) end end end describe "#control_example_success" do include_context "audit data" let(:name) { "foo" } let(:example_data) { double("example data") } it "notifies the control group the example succeeded" do expect(control_group_foo).to receive(:example_success).with(example_data) reporter.control_example_success(name, example_data) end end describe "#control_example_failure" do include_context "audit data" let(:name) { "bar" } let(:example_data) { double("example data") } let(:error) { double("Exception", :message => "oopsie") } it "notifies the control group the example failed" do expect(control_group_bar).to receive(:example_failure). with(example_data, error.message) reporter.control_example_failure(name, example_data, error) end end describe "#auditing_enabled?" do shared_examples "enabled?" do |true_or_false| it "returns #{true_or_false}" do expect(Chef::Config).to receive(:[]). with(:audit_mode). and_return(audit_setting) expect(reporter.auditing_enabled?).to be true_or_false end end context "when auditing is disabled" do include_examples "enabled?", false do let(:audit_setting) { :disabled } end end context "when auditing in audit-only mode" do include_examples "enabled?", true do let(:audit_setting) { :audit_only } end end context "when auditing is enabled" do include_examples "enabled?", true do let(:audit_setting) { :enabled } end end end end chef-12.14.60/spec/unit/audit/control_group_data_spec.rb000066400000000000000000000334001276456504500231330ustar00rootroot00000000000000# # Author:: Tyler Ball () # Author:: Claire McQuin () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "securerandom" describe Chef::Audit::AuditData do let(:node_name) { "noodles" } let(:run_id) { SecureRandom.uuid } let(:audit_data) { described_class.new(node_name, run_id) } let(:control_group_1) { double("control group 1") } let(:control_group_2) { double("control group 2") } describe "#add_control_group" do context "when no control groups have been added" do it "stores the control group" do audit_data.add_control_group(control_group_1) expect(audit_data.control_groups).to include(control_group_1) end end context "when adding additional control groups" do before do audit_data.add_control_group(control_group_1) end it "stores the control group" do audit_data.add_control_group(control_group_2) expect(audit_data.control_groups).to include(control_group_2) end it "stores all control groups" do audit_data.add_control_group(control_group_2) expect(audit_data.control_groups).to include(control_group_1) end end end describe "#to_hash" do let(:audit_data_hash) { audit_data.to_hash } it "returns a hash" do expect(audit_data_hash).to be_a(Hash) end it "describes a Chef::Audit::AuditData object" do keys = [:node_name, :run_id, :start_time, :end_time, :control_groups] expect(audit_data_hash.keys).to match_array(keys) end describe ":control_groups" do let(:control_hash_1) { { :name => "control group 1" } } let(:control_hash_2) { { :name => "control group 2" } } let(:control_groups) { audit_data_hash[:control_groups] } context "with no control groups added" do it "is an empty list" do expect(control_groups).to eq [] end end context "with one control group added" do before do allow(audit_data).to receive(:control_groups).and_return([control_group_1]) end it "is a one-element list containing the control group hash" do expect(control_group_1).to receive(:to_hash).once.and_return(control_hash_1) expect(control_groups.size).to eq 1 expect(control_groups).to include(control_hash_1) end end context "with multiple control groups added" do before do allow(audit_data).to receive(:control_groups).and_return([control_group_1, control_group_2]) end it "is a list of control group hashes" do expect(control_group_1).to receive(:to_hash).and_return(control_hash_1) expect(control_group_2).to receive(:to_hash).and_return(control_hash_2) expect(control_groups.size).to eq 2 expect(control_groups).to include(control_hash_1) expect(control_groups).to include(control_hash_2) end end end end end describe Chef::Audit::ControlData do let(:name) { "ramen" } let(:resource_type) { double("Service") } let(:resource_name) { "mysql" } let(:context) { nil } let(:line_number) { 27 } let(:control_data) do described_class.new(name: name, resource_type: resource_type, resource_name: resource_name, context: context, line_number: line_number) end describe "#to_hash" do let(:control_data_hash) { control_data.to_hash } it "returns a hash" do expect(control_data_hash).to be_a(Hash) end it "describes a Chef::Audit::ControlData object" do keys = [:name, :resource_type, :resource_name, :context, :status, :details] expect(control_data_hash.keys).to match_array(keys) end context "when context is nil" do it "sets :context to an empty array" do expect(control_data_hash[:context]).to eq [] end end context "when context is non-nil" do let(:context) { ["outer"] } it "sets :context to its value" do expect(control_data_hash[:context]).to eq context end end end end describe Chef::Audit::ControlGroupData do let(:name) { "balloon" } let(:control_group_data) { described_class.new(name) } shared_context "control data" do let(:name) { "" } let(:resource_type) { nil } let(:resource_name) { nil } let(:context) { nil } let(:line_number) { 0 } let(:control_data) do { :name => name, :resource_type => resource_type, :resource_name => resource_name, :context => context, :line_number => line_number, } end end shared_context "control" do include_context "control data" let(:control) do Chef::Audit::ControlData.new(name: name, resource_type: resource_type, resource_name: resource_name, context: context, line_number: line_number) end before do allow(Chef::Audit::ControlData).to receive(:new). with(name: name, resource_type: resource_type, resource_name: resource_name, context: context, line_number: line_number). and_return(control) end end describe "#new" do it "has status \"success\"" do expect(control_group_data.status).to eq "success" end end describe "#example_success" do include_context "control" def notify_success control_group_data.example_success(control_data) end it "increments the number of successful audits" do num_success = control_group_data.number_succeeded notify_success expect(control_group_data.number_succeeded).to eq (num_success + 1) end it "does not increment the number of failed audits" do num_failed = control_group_data.number_failed notify_success expect(control_group_data.number_failed).to eq (num_failed) end it "marks the audit's status as success" do notify_success expect(control.status).to eq "success" end it "does not modify its own status" do expect(control_group_data).to_not receive(:status=) status = control_group_data.status notify_success expect(control_group_data.status).to eq status end it "saves the control" do controls = control_group_data.controls expect(controls).to_not include(control) notify_success expect(controls).to include(control) end end describe "#example_failure" do include_context "control" let(:details) { "poop" } def notify_failure control_group_data.example_failure(control_data, details) end it "does not increment the number of successful audits" do num_success = control_group_data.number_succeeded notify_failure expect(control_group_data.number_succeeded).to eq num_success end it "increments the number of failed audits" do num_failed = control_group_data.number_failed notify_failure expect(control_group_data.number_failed).to eq (num_failed + 1) end it "marks the audit's status as failure" do notify_failure expect(control.status).to eq "failure" end it "marks its own status as failure" do notify_failure expect(control_group_data.status).to eq "failure" end it "saves the control" do controls = control_group_data.controls expect(controls).to_not include(control) notify_failure expect(controls).to include(control) end context "when details are not provided" do let(:details) { nil } it "does not save details to the control" do default_details = control.details expect(control).to_not receive(:details=) notify_failure expect(control.details).to eq default_details end end context "when details are provided" do let(:details) { "yep that didn't work" } it "saves details to the control" do notify_failure expect(control.details).to eq details end end end shared_examples "multiple audits" do |success_or_failure| include_context "control" let(:num_success) { 0 } let(:num_failure) { 0 } before do if num_failure == 0 num_success.times { control_group_data.example_success(control_data) } elsif num_success == 0 num_failure.times { control_group_data.example_failure(control_data, nil) } end end it "counts the number of successful audits" do expect(control_group_data.number_succeeded).to eq num_success end it "counts the number of failed audits" do expect(control_group_data.number_failed).to eq num_failure end it "marks its status as \"#{success_or_failure}\"" do expect(control_group_data.status).to eq success_or_failure end end context "when all audits pass" do include_examples "multiple audits", "success" do let(:num_success) { 3 } end end context "when one audit fails" do shared_examples "mixed audit results" do include_examples "multiple audits", "failure" do let(:audit_results) { [] } let(:num_success) { audit_results.count("success") } let(:num_failure) { 1 } before do audit_results.each do |result| if result == "success" control_group_data.example_success(control_data) else control_group_data.example_failure(control_data, nil) end end end end end context "and it's the first audit" do include_examples "mixed audit results" do let(:audit_results) { %w{failure success success} } end end context "and it's an audit in the middle" do include_examples "mixed audit results" do let(:audit_results) { %w{success failure success} } end end context "and it's the last audit" do include_examples "mixed audit results" do let(:audit_results) { %w{success success failure} } end end end context "when all audits fail" do include_examples "multiple audits", "failure" do let(:num_failure) { 3 } end end describe "#to_hash" do let(:control_group_data_hash) { control_group_data.to_hash } it "returns a hash" do expect(control_group_data_hash).to be_a(Hash) end it "describes a Chef::Audit::ControlGroupData object" do keys = [:name, :status, :number_succeeded, :number_failed, :controls, :id] expect(control_group_data_hash.keys).to match_array(keys) end describe ":controls" do let(:control_group_controls) { control_group_data_hash[:controls] } context "with no controls added" do it "is an empty list" do expect(control_group_controls).to eq [] end end context "with one control added" do include_context "control" let(:control_list) { [control_data] } let(:control_hash) { control.to_hash } before do expect(control_group_data).to receive(:controls).twice.and_return(control_list) expect(control_data).to receive(:to_hash).and_return(control_hash) end it "is a one-element list containing the control hash" do expect(control_group_controls.size).to eq 1 expect(control_group_controls).to include(control_hash) end it "adds a sequence number to the control" do control_group_data.to_hash expect(control_hash).to have_key(:sequence_number) end end context "with multiple controls added" do let(:control_hash_1) { { :line_number => 27 } } let(:control_hash_2) { { :line_number => 13 } } let(:control_hash_3) { { :line_number => 35 } } let(:control_1) do double("control 1", :line_number => control_hash_1[:line_number], :to_hash => control_hash_1) end let(:control_2) do double("control 2", :line_number => control_hash_2[:line_number], :to_hash => control_hash_2) end let(:control_3) do double("control 3", :line_number => control_hash_3[:line_number], :to_hash => control_hash_3) end let(:control_list) { [control_1, control_2, control_3] } let(:ordered_control_hashes) { [control_hash_2, control_hash_1, control_hash_3] } before do # Another way to do this would be to call #example_success # or #example_failure per control hash, but we'd have to # then stub #create_control and it's a lot of extra stubbing work. # We can't stub the controls reader to return a list of # controls because of the call to sort! and the following # reading of controls. control_group_data.instance_variable_set(:@controls, control_list) end it "is a list of control group hashes ordered by line number" do expect(control_group_controls.size).to eq 3 expect(control_group_controls).to eq ordered_control_hashes end it "assigns sequence numbers in order" do control_group_data.to_hash ordered_control_hashes.each_with_index do |control_hash, idx| expect(control_hash[:sequence_number]).to eq idx + 1 end end end end end end chef-12.14.60/spec/unit/audit/logger_spec.rb000066400000000000000000000026051276456504500205300ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Audit::Logger do before(:each) do Chef::Audit::Logger.instance_variable_set(:@buffer, nil) end it "calling puts creates @buffer and adds the message" do Chef::Audit::Logger.puts("Output message") expect(Chef::Audit::Logger.read_buffer).to eq("Output message\n") end it "calling puts multiple times adds to the message" do Chef::Audit::Logger.puts("Output message") Chef::Audit::Logger.puts("Output message") Chef::Audit::Logger.puts("Output message") expect(Chef::Audit::Logger.read_buffer).to eq("Output message\nOutput message\nOutput message\n") end it "calling it before @buffer is set returns an empty string" do expect(Chef::Audit::Logger.read_buffer).to eq("") end end chef-12.14.60/spec/unit/audit/rspec_formatter_spec.rb000066400000000000000000000017231276456504500224500ustar00rootroot00000000000000# # Author:: Tyler Ball () # Author:: Claire McQuin () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/audit/rspec_formatter" describe Chef::Audit::RspecFormatter do let(:formatter) { Chef::Audit::RspecFormatter.new(nil) } it "should respond to close" do expect(formatter).to respond_to(:close) end end chef-12.14.60/spec/unit/audit/runner_spec.rb000066400000000000000000000117131276456504500205620ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "rspec/core/sandbox" require "chef/audit/runner" require "chef/audit/audit_event_proxy" require "chef/audit/rspec_formatter" require "rspec/support/spec/in_sub_process" require "rspec/support/spec/stderr_splitter" describe Chef::Audit::Runner do include RSpec::Support::InSubProcess let(:events) { double("events") } let(:run_context) { instance_double(Chef::RunContext, :events => events) } let(:runner) { Chef::Audit::Runner.new(run_context) } around(:each) do |ex| RSpec::Core::Sandbox.sandboxed { ex.run } end context "when we run in audit mode" do let(:paths) { [ "/opt/chef/lib/chef/", 'C:\windows/here/lib/chef/' , "/opt/chef/extra/folders/lib/chef/"] } it "excludes the current path from backtrace" do paths.each do |path| expect(runner.exclusion_pattern).to match(path) end end end describe "#initialize" do it "correctly sets the run_context during initialization" do expect(runner.instance_variable_get(:@run_context)).to eq(run_context) end end context "during #run" do describe "#setup" do let(:log_location) { File.join(Dir.tmpdir, "audit_log") } let(:color) { false } before do Chef::Config[:log_location] = log_location Chef::Config[:color] = color end it "sets all the config values" do # This runs the Serverspec includes - we don't want these hanging around in all subsequent tests so # we run this in a forked process. Keeps Serverspec files from getting loaded into main process. in_sub_process do runner.send(:setup) expect(RSpec.configuration.output_stream).to eq(Chef::Audit::Logger) expect(RSpec.configuration.error_stream).to eq(Chef::Audit::Logger) expect(RSpec.configuration.formatters.size).to eq(2) expect(RSpec.configuration.formatters).to include(instance_of(Chef::Audit::AuditEventProxy)) expect(RSpec.configuration.formatters).to include(instance_of(Chef::Audit::RspecFormatter)) expect(Chef::Audit::AuditEventProxy.class_variable_get(:@@events)).to eq(run_context.events) expect(RSpec.configuration.expectation_frameworks).to eq([RSpec::Matchers]) expect(RSpec::Matchers.configuration.syntax).to eq([:expect]) expect(RSpec.configuration.color).to eq(color) expect(RSpec.configuration.expose_dsl_globally?).to eq(false) expect(RSpec.configuration.backtrace_exclusion_patterns).to include(runner.exclusion_pattern) expect(Specinfra.configuration.backend).to eq(:exec) end end end describe "#register_control_groups" do let(:audits) { [] } let(:run_context) { instance_double(Chef::RunContext, :audits => audits) } it "adds the control group aliases" do runner.send(:register_control_groups) expect(RSpec::Core::DSL.example_group_aliases).to include(:__control_group__) expect(RSpec::Core::DSL.example_group_aliases).to include(:control) end context "audits exist" do let(:audits) { { "audit_name" => group } } let(:group) { Struct.new(:args, :block).new(["group_name"], nil) } it "sends the audits to the world" do runner.send(:register_control_groups) expect(RSpec.world.example_groups.size).to eq(1) # For whatever reason, `kind_of` is not working # expect(RSpec.world.example_groups).to include(kind_of(RSpec::Core::ExampleGroup)) => FAIL g = RSpec.world.example_groups[0] expect(g.ancestors).to include(RSpec::Core::ExampleGroup) expect(g.description).to eq("group_name") end end end describe "#do_run" do let(:rspec_runner) { instance_double(RSpec::Core::Runner) } it "executes the runner" do expect(RSpec::Core::Runner).to receive(:new).with(nil).and_return(rspec_runner) expect(rspec_runner).to receive(:run_specs).with([]) runner.send(:do_run) end end end describe "counters" do it "correctly calculates failed?" do expect(runner.failed?).to eq(false) end it "correctly calculates num_failed" do expect(runner.num_failed).to eq(0) end it "correctly calculates num_total" do expect(runner.num_total).to eq(0) end end end chef-12.14.60/spec/unit/chef_class_spec.rb000066400000000000000000000067741276456504500202500ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe "Chef class" do let(:platform) { "debian" } let(:node) do node = Chef::Node.new node.automatic["platform"] = platform node end let(:run_context) do Chef::RunContext.new(node, nil, nil) end let(:resource_priority_map) do double("Chef::Platform::ResourcePriorityMap") end let(:provider_priority_map) do double("Chef::Platform::ProviderPriorityMap") end before do Chef.set_run_context(run_context) Chef.set_node(node) Chef.set_resource_priority_map(resource_priority_map) Chef.set_provider_priority_map(provider_priority_map) end context "priority maps" do context "#get_provider_priority_array" do it "should use the current node to get the right priority_map" do expect(provider_priority_map).to receive(:get_priority_array).with(node, :http_request).and_return("stuff") expect(Chef.get_provider_priority_array(:http_request)).to eql("stuff") end end context "#get_resource_priority_array" do it "should use the current node to get the right priority_map" do expect(resource_priority_map).to receive(:get_priority_array).with(node, :http_request).and_return("stuff") expect(Chef.get_resource_priority_array(:http_request)).to eql("stuff") end end context "#set_provider_priority_array" do it "should delegate to the provider_priority_map" do expect(provider_priority_map).to receive(:set_priority_array).with(:http_request, %w{a b}, platform: "debian").and_return("stuff") expect(Chef.set_provider_priority_array(:http_request, %w{a b}, platform: "debian")).to eql("stuff") end end context "#set_priority_map_for_resource" do it "should delegate to the resource_priority_map" do expect(resource_priority_map).to receive(:set_priority_array).with(:http_request, %w{a b}, platform: "debian").and_return("stuff") expect(Chef.set_resource_priority_array(:http_request, %w{a b}, platform: "debian")).to eql("stuff") end end end context "#run_context" do it "should return the injected RunContext" do expect(Chef.run_context).to eql(run_context) end end context "#node" do it "should return the injected Node" do expect(Chef.node).to eql(node) end end context "#event_handler" do it "adds a new handler" do x = 1 Chef.event_handler do on :converge_start do x = 2 end end expect(Chef::Config[:event_handlers]).to_not be_empty Chef::Config[:event_handlers].first.send(:converge_start) expect(x).to eq(2) end it "raise error if unknown event type is passed" do expect do Chef.event_handler do on :yolo do end end end.to raise_error(Chef::Exceptions::InvalidEventType) end end end chef-12.14.60/spec/unit/chef_fs/000077500000000000000000000000001276456504500161765ustar00rootroot00000000000000chef-12.14.60/spec/unit/chef_fs/config_spec.rb000066400000000000000000000216231276456504500210060ustar00rootroot00000000000000# # Author:: Jess Mink () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/exceptions" require "lib/chef/chef_fs/config.rb" describe Chef::ChefFS::Config do describe "initialize" do it "warns when hosted setups use 'everything'" do base_config = Hash.new() base_config[:repo_mode] = "everything" base_config[:chef_server_url] = "http://foo.com/organizations/fake_org/" ui = double("ui") expect(ui).to receive(:warn) Chef::ChefFS::Config.new(base_config, Dir.pwd, {}, ui) end it "doesn't warn when hosted setups use 'hosted_everything'" do base_config = Hash.new() base_config[:repo_mode] = "hosted_everything" base_config[:chef_server_url] = "http://foo.com/organizations/fake_org/" ui = double("ui") expect(ui).to receive(:warn).exactly(0).times Chef::ChefFS::Config.new(base_config, Dir.pwd, {}, ui) end it "doesn't warn when non-hosted setups use 'everything'" do base_config = Hash.new() base_config[:repo_mode] = "everything" base_config[:chef_server_url] = "http://foo.com/" ui = double("ui") expect(ui).to receive(:warn).exactly(0).times Chef::ChefFS::Config.new(base_config, Dir.pwd, {}, ui) end end describe "local FS configuration" do let(:chef_config) do Mash.new({ client_path: "/base_path/clients", cookbook_path: "/base_path/cookbooks", data_bag_path: "/base_path/data_bags", environment_path: "/base_path/environments", node_path: "/base_path/nodes", role_path: "/base_path/roles", user_path: "/base_path/users", policy_path: "/base_path/policies", }) end let(:chef_fs_config) { Chef::ChefFS::Config.new(chef_config, Dir.pwd) } subject(:local_fs) { chef_fs_config.local_fs } def platform_path(*args) File.expand_path(*args) end it "sets the correct nodes path on the local FS object" do expect(local_fs.child_paths["nodes"]).to eq([platform_path("/base_path/nodes")]) end it "sets the correct cookbook path on the local FS object" do expect(local_fs.child_paths["cookbooks"]).to eq([platform_path("/base_path/cookbooks")]) end it "sets the correct data bag path on the local FS object" do expect(local_fs.child_paths["data_bags"]).to eq([platform_path("/base_path/data_bags")]) end it "sets the correct environment path on the local FS object" do expect(local_fs.child_paths["environments"]).to eq([platform_path("/base_path/environments")]) end it "sets the correct role path on the local FS object" do expect(local_fs.child_paths["roles"]).to eq([platform_path("/base_path/roles")]) end it "sets the correct user path on the local FS object" do expect(local_fs.child_paths["users"]).to eq([platform_path("/base_path/users")]) end end describe "formats paths", :unix_only do let(:single_repo_path) do Mash.new({ chef_repo_path: "/base_path", }) end let(:double_repo_path) do Mash.new({ chef_repo_path: %w{ /base_path /second_base_path }, }) end describe "#server_path" do it "returns nil if no paths match" do cfg = Chef::ChefFS::Config.new(single_repo_path, "/my_repo/cookbooks") expect(cfg.server_path("foo")).to be_nil end context "with only repo paths" do it "returns / if in the repo path" do cwd = "/base_path/cookbooks" cfg = Chef::ChefFS::Config.new(single_repo_path, cwd) expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("/base_path/cookbooks", cwd).and_return("/base_path/cookbooks") expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("/base_path", cwd).and_return("/base_path/cookbooks") expect(cfg.server_path("/base_path/cookbooks")).to eq("/") end it "checks all the repo paths" do cwd = "/second_base_path/cookbooks" cfg = Chef::ChefFS::Config.new(double_repo_path, cwd) expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("/second_base_path/cookbooks", cwd).and_return("/second_base_path/cookbooks") expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("/base_path", cwd).and_return("/base_path/cookbooks") expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("/second_base_path", cwd).and_return("/second_base_path/cookbooks") expect(cfg.server_path("/second_base_path/cookbooks")).to eq("/") end end context "with specific object locations" do let(:single_cookbook_path) do Mash.new({ cookbook_path: "/base_path/cookbooks", role_path: "/base_path/roles", }) end let(:cwd) { "/base_path/cookbooks" } let(:cfg) { Chef::ChefFS::Config.new(single_cookbook_path, cwd) } before do expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("/base_path/cookbooks", cwd).and_return("/base_path/cookbooks") allow(Chef::ChefFS::PathUtils).to receive(:realest_path).with("/base_path/roles", cwd).and_return("/base_path/roles") end it "resolves a relative path" do expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("blah", cwd).and_return("/base_path/cookbooks/blah") expect(cfg.server_path("blah")).to eql("/cookbooks/blah") end it "resolves a relative path in a parent directory" do expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("../roles/blah", cwd).and_return("/base_path/roles/blah") expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("/base_path/roles", cwd).and_return("/base_path/roles") expect(cfg.server_path("../roles/blah")).to eql("/roles/blah") end it "ignores a relative path that's outside the repository" do expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("../../readme.txt", cwd).and_return("/readme.txt") expect(cfg.server_path("../../readme.txt")).to be_nil end it "deals with splat paths" do expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("*/*ab*", cwd).and_return("/base_path/cookbooks/*/*ab*") expect(cfg.server_path("*/*ab*")).to eql("/cookbooks/*/*ab*") end it "resolves an absolute path" do expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("/base_path/cookbooks/blah", cwd).and_return("/base_path/cookbooks/blah") expect(cfg.server_path("/base_path/cookbooks/blah")).to eql("/cookbooks/blah") end it "deals with an absolute path with splats" do expect(Chef::ChefFS::PathUtils).to receive(:realest_path).with("/*/cookbooks/blah", cwd).and_return("/*/cookbooks/blah") expect(cfg.server_path("/*/cookbooks/blah")).to be_nil end end end describe "#format_path" do Entry = Struct.new(:path) let(:config) do Mash.new({ chef_repo_path: "/base_path", cookbook_path: "/base_path/cookbooks", role_path: "/base_path/roles", }) end let (:path) { "/roles/foo.json" } let (:entry) { Entry.new(path) } it "returns the entry's path if the cwd isn't in the config" do cfg = Chef::ChefFS::Config.new(config, "/my_repo/cookbooks") expect(cfg).to receive(:base_path).and_return(nil) expect(cfg.format_path(entry)).to eq(path) end it "returns . if the cwd is the same as the entry's path" do cfg = Chef::ChefFS::Config.new(config, "/base_path/roles/foo.json") expect(cfg).to receive(:base_path).and_return("/roles/foo.json").at_least(:once) expect(cfg.format_path(entry)).to eq(".") end it "returns a relative path if the cwd is in the repo" do cfg = Chef::ChefFS::Config.new(config, "/base_path/roles") expect(cfg).to receive(:base_path).and_return("/roles").at_least(:once) expect(cfg.format_path(entry)).to eq("foo.json") end it "returns a relative path if the cwd is at the root of repo" do cfg = Chef::ChefFS::Config.new(config, "/base_path") expect(cfg).to receive(:base_path).and_return("/").at_least(:once) expect(cfg.format_path(entry)).to eq("roles/foo.json") end end end end chef-12.14.60/spec/unit/chef_fs/data_handler/000077500000000000000000000000001276456504500206045ustar00rootroot00000000000000chef-12.14.60/spec/unit/chef_fs/data_handler/group_handler_spec.rb000066400000000000000000000032471276456504500250020ustar00rootroot00000000000000# # Author:: Ryan Cragun () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "lib/chef/chef_fs/data_handler/group_data_handler" class TestEntry < Mash attr_accessor :name, :org def initialize(name, org) @name = name @org = org end end describe Chef::ChefFS::DataHandler::GroupDataHandler do describe "#normalize_for_post" do let(:entry) do TestEntry.new("workers.json", "hive") end let(:group) do { "name" => "worker_bees", "clients" => %w{honey sting}, "users" => %w{fizz buzz}, "actors" => %w{honey}, } end let(:normalized) do { "actors" => { "users" => %w{fizz buzz}, "clients" => %w{honey sting}, "groups" => [], }, "groupname" => "workers", "name" => "worker_bees", "orgname" => "hive", } end let(:handler) { described_class.new } it "normalizes the users, clients and groups into actors" do expect(handler.normalize_for_post(group, entry)).to eq(normalized) end end end chef-12.14.60/spec/unit/chef_fs/diff_spec.rb000066400000000000000000000274321276456504500204550ustar00rootroot00000000000000# # Author:: John Keiser () # 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/chef_fs/file_pattern" require "chef/chef_fs/command_line" # Removes the date stamp from the diff and replaces it with ' DATE' # example match: "/dev/null\t2012-10-16 16:15:54.000000000 +0000" # windows match: "--- /dev/null\tTue Oct 16 18:04:34 2012" def remove_os_differences(diff) diff = diff.gsub(/([+-]{3}.*)\t.*/, '\1 DATE') diff.gsub(/^@@ -\d(,\d)? \+\d(,\d)? @@/, "CONTEXT_LINE_NUMBERS") end describe "diff", :uses_diff => true do include FileSystemSupport context "with two filesystems with all types of difference" do let(:a) do memory_fs("a", { :both_dirs => { :sub_both_dirs => { :subsub => nil }, :sub_both_files => nil, :sub_both_files_different => "a\n", :sub_both_dirs_empty => {}, :sub_dirs_empty_in_a_filled_in_b => {}, :sub_dirs_empty_in_b_filled_in_a => { :subsub => nil }, :sub_a_only_dir => { :subsub => nil }, :sub_a_only_file => nil, :sub_dir_in_a_file_in_b => {}, :sub_file_in_a_dir_in_b => nil, }, :both_files => nil, :both_files_different => "a\n", :both_dirs_empty => {}, :dirs_empty_in_a_filled_in_b => {}, :dirs_empty_in_b_filled_in_a => { :subsub => nil }, :dirs_in_a_cannot_be_in_b => {}, :file_in_a_cannot_be_in_b => nil, :a_only_dir => { :subsub => nil }, :a_only_file => nil, :dir_in_a_file_in_b => {}, :file_in_a_dir_in_b => nil, }, /cannot_be_in_a/) end let(:b) do memory_fs("b", { :both_dirs => { :sub_both_dirs => { :subsub => nil }, :sub_both_files => nil, :sub_both_files_different => "b\n", :sub_both_dirs_empty => {}, :sub_dirs_empty_in_a_filled_in_b => { :subsub => nil }, :sub_dirs_empty_in_b_filled_in_a => {}, :sub_b_only_dir => { :subsub => nil }, :sub_b_only_file => nil, :sub_dir_in_a_file_in_b => nil, :sub_file_in_a_dir_in_b => {}, }, :both_files => nil, :both_files_different => "b\n", :both_dirs_empty => {}, :dirs_empty_in_a_filled_in_b => { :subsub => nil }, :dirs_empty_in_b_filled_in_a => {}, :dirs_in_b_cannot_be_in_a => {}, :file_in_b_cannot_be_in_a => nil, :b_only_dir => { :subsub => nil }, :b_only_file => nil, :dir_in_a_file_in_b => nil, :file_in_a_dir_in_b => {}, }, /cannot_be_in_b/) end it "Chef::ChefFS::CommandLine.diff_print(/)" do results = [] Chef::ChefFS::CommandLine.diff_print(pattern("/"), a, b, nil, nil) do |diff| results << remove_os_differences(diff) end expect(results).to match_array([ 'diff --knife a/both_dirs/sub_both_files_different b/both_dirs/sub_both_files_different --- a/both_dirs/sub_both_files_different DATE +++ b/both_dirs/sub_both_files_different DATE CONTEXT_LINE_NUMBERS -a +b ', 'diff --knife a/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub b/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub new file --- /dev/null DATE +++ b/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub DATE CONTEXT_LINE_NUMBERS +subsub ', 'diff --knife a/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub b/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub deleted file --- a/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub DATE +++ /dev/null DATE CONTEXT_LINE_NUMBERS -subsub ', "Only in a/both_dirs: sub_a_only_dir ", 'diff --knife a/both_dirs/sub_a_only_file b/both_dirs/sub_a_only_file deleted file --- a/both_dirs/sub_a_only_file DATE +++ /dev/null DATE CONTEXT_LINE_NUMBERS -sub_a_only_file ', "File a/both_dirs/sub_dir_in_a_file_in_b is a directory while file b/both_dirs/sub_dir_in_a_file_in_b is a regular file ", "File a/both_dirs/sub_file_in_a_dir_in_b is a regular file while file b/both_dirs/sub_file_in_a_dir_in_b is a directory ", "Only in b/both_dirs: sub_b_only_dir ", 'diff --knife a/both_dirs/sub_b_only_file b/both_dirs/sub_b_only_file new file --- /dev/null DATE +++ b/both_dirs/sub_b_only_file DATE CONTEXT_LINE_NUMBERS +sub_b_only_file ', 'diff --knife a/both_files_different b/both_files_different --- a/both_files_different DATE +++ b/both_files_different DATE CONTEXT_LINE_NUMBERS -a +b ', 'diff --knife a/dirs_empty_in_a_filled_in_b/subsub b/dirs_empty_in_a_filled_in_b/subsub new file --- /dev/null DATE +++ b/dirs_empty_in_a_filled_in_b/subsub DATE CONTEXT_LINE_NUMBERS +subsub ', 'diff --knife a/dirs_empty_in_b_filled_in_a/subsub b/dirs_empty_in_b_filled_in_a/subsub deleted file --- a/dirs_empty_in_b_filled_in_a/subsub DATE +++ /dev/null DATE CONTEXT_LINE_NUMBERS -subsub ', "Only in a: a_only_dir ", 'diff --knife a/a_only_file b/a_only_file deleted file --- a/a_only_file DATE +++ /dev/null DATE CONTEXT_LINE_NUMBERS -a_only_file ', "File a/dir_in_a_file_in_b is a directory while file b/dir_in_a_file_in_b is a regular file ", "File a/file_in_a_dir_in_b is a regular file while file b/file_in_a_dir_in_b is a directory ", "Only in b: b_only_dir ", 'diff --knife a/b_only_file b/b_only_file new file --- /dev/null DATE +++ b/b_only_file DATE CONTEXT_LINE_NUMBERS +b_only_file ' ]) end it "Chef::ChefFS::CommandLine.diff_print(/both_dirs)" do results = [] Chef::ChefFS::CommandLine.diff_print(pattern("/both_dirs"), a, b, nil, nil) do |diff| results << remove_os_differences(diff) end expect(results).to match_array([ 'diff --knife a/both_dirs/sub_both_files_different b/both_dirs/sub_both_files_different --- a/both_dirs/sub_both_files_different DATE +++ b/both_dirs/sub_both_files_different DATE CONTEXT_LINE_NUMBERS -a +b ', 'diff --knife a/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub b/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub new file --- /dev/null DATE +++ b/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub DATE CONTEXT_LINE_NUMBERS +subsub ', 'diff --knife a/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub b/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub deleted file --- a/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub DATE +++ /dev/null DATE CONTEXT_LINE_NUMBERS -subsub ', "Only in a/both_dirs: sub_a_only_dir ", 'diff --knife a/both_dirs/sub_a_only_file b/both_dirs/sub_a_only_file deleted file --- a/both_dirs/sub_a_only_file DATE +++ /dev/null DATE CONTEXT_LINE_NUMBERS -sub_a_only_file ', "File a/both_dirs/sub_dir_in_a_file_in_b is a directory while file b/both_dirs/sub_dir_in_a_file_in_b is a regular file ", "File a/both_dirs/sub_file_in_a_dir_in_b is a regular file while file b/both_dirs/sub_file_in_a_dir_in_b is a directory ", "Only in b/both_dirs: sub_b_only_dir ", 'diff --knife a/both_dirs/sub_b_only_file b/both_dirs/sub_b_only_file new file --- /dev/null DATE +++ b/both_dirs/sub_b_only_file DATE CONTEXT_LINE_NUMBERS +sub_b_only_file ' ]) end it "Chef::ChefFS::CommandLine.diff_print(/) with depth 1" do results = [] Chef::ChefFS::CommandLine.diff_print(pattern("/"), a, b, 1, nil) do |diff| results << remove_os_differences(diff) end expect(results).to match_array([ "Common subdirectories: b/both_dirs ", 'diff --knife a/both_files_different b/both_files_different --- a/both_files_different DATE +++ b/both_files_different DATE CONTEXT_LINE_NUMBERS -a +b ', "Common subdirectories: b/both_dirs_empty ", "Common subdirectories: b/dirs_empty_in_b_filled_in_a ", "Common subdirectories: b/dirs_empty_in_a_filled_in_b ", "Only in a: a_only_dir ", 'diff --knife a/a_only_file b/a_only_file deleted file --- a/a_only_file DATE +++ /dev/null DATE CONTEXT_LINE_NUMBERS -a_only_file ', "File a/dir_in_a_file_in_b is a directory while file b/dir_in_a_file_in_b is a regular file ", "File a/file_in_a_dir_in_b is a regular file while file b/file_in_a_dir_in_b is a directory ", "Only in b: b_only_dir ", 'diff --knife a/b_only_file b/b_only_file new file --- /dev/null DATE +++ b/b_only_file DATE CONTEXT_LINE_NUMBERS +b_only_file ' ]) end it "Chef::ChefFS::CommandLine.diff_print(/*_*) with depth 0" do results = [] Chef::ChefFS::CommandLine.diff_print(pattern("/*_*"), a, b, 0, nil) do |diff| results << remove_os_differences(diff) end expect(results).to match_array([ "Common subdirectories: b/both_dirs ", 'diff --knife a/both_files_different b/both_files_different --- a/both_files_different DATE +++ b/both_files_different DATE CONTEXT_LINE_NUMBERS -a +b ', "Common subdirectories: b/both_dirs_empty ", "Common subdirectories: b/dirs_empty_in_b_filled_in_a ", "Common subdirectories: b/dirs_empty_in_a_filled_in_b ", "Only in a: a_only_dir ", 'diff --knife a/a_only_file b/a_only_file deleted file --- a/a_only_file DATE +++ /dev/null DATE CONTEXT_LINE_NUMBERS -a_only_file ', "File a/dir_in_a_file_in_b is a directory while file b/dir_in_a_file_in_b is a regular file ", "File a/file_in_a_dir_in_b is a regular file while file b/file_in_a_dir_in_b is a directory ", "Only in b: b_only_dir ", 'diff --knife a/b_only_file b/b_only_file new file --- /dev/null DATE +++ b/b_only_file DATE CONTEXT_LINE_NUMBERS +b_only_file ' ]) end it "Chef::ChefFS::CommandLine.diff_print(/) in name-only mode" do results = [] Chef::ChefFS::CommandLine.diff_print(pattern("/"), a, b, nil, :name_only) do |diff| results << remove_os_differences(diff) end expect(results).to match_array([ "b/both_dirs/sub_both_files_different\n", "b/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub\n", "b/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub\n", "b/both_dirs/sub_a_only_dir\n", "b/both_dirs/sub_a_only_file\n", "b/both_dirs/sub_b_only_dir\n", "b/both_dirs/sub_b_only_file\n", "b/both_dirs/sub_dir_in_a_file_in_b\n", "b/both_dirs/sub_file_in_a_dir_in_b\n", "b/both_files_different\n", "b/dirs_empty_in_b_filled_in_a/subsub\n", "b/dirs_empty_in_a_filled_in_b/subsub\n", "b/a_only_dir\n", "b/a_only_file\n", "b/b_only_dir\n", "b/b_only_file\n", "b/dir_in_a_file_in_b\n", "b/file_in_a_dir_in_b\n", ]) end it "Chef::ChefFS::CommandLine.diff_print(/) in name-status mode" do results = [] Chef::ChefFS::CommandLine.diff_print(pattern("/"), a, b, nil, :name_status) do |diff| results << remove_os_differences(diff) end expect(results).to match_array([ "M\tb/both_dirs/sub_both_files_different\n", "D\tb/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub\n", "A\tb/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub\n", "D\tb/both_dirs/sub_a_only_dir\n", "D\tb/both_dirs/sub_a_only_file\n", "A\tb/both_dirs/sub_b_only_dir\n", "A\tb/both_dirs/sub_b_only_file\n", "T\tb/both_dirs/sub_dir_in_a_file_in_b\n", "T\tb/both_dirs/sub_file_in_a_dir_in_b\n", "M\tb/both_files_different\n", "D\tb/dirs_empty_in_b_filled_in_a/subsub\n", "A\tb/dirs_empty_in_a_filled_in_b/subsub\n", "D\tb/a_only_dir\n", "D\tb/a_only_file\n", "A\tb/b_only_dir\n", "A\tb/b_only_file\n", "T\tb/dir_in_a_file_in_b\n", "T\tb/file_in_a_dir_in_b\n", ]) end end end chef-12.14.60/spec/unit/chef_fs/file_pattern_spec.rb000066400000000000000000000517671276456504500222310ustar00rootroot00000000000000# # Author:: John Keiser () # 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/chef_fs/file_pattern" describe Chef::ChefFS::FilePattern do def p(str) Chef::ChefFS::FilePattern.new(str) end # Different kinds of patterns context 'with empty pattern ""' do let(:pattern) { Chef::ChefFS::FilePattern.new("") } it "match?" do expect(pattern.match?("")).to be_truthy expect(pattern.match?("/")).to be_falsey expect(pattern.match?("a")).to be_falsey expect(pattern.match?("a/b")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to eq("") end it "could_match_children?" do expect(pattern.could_match_children?("")).to be_falsey expect(pattern.could_match_children?("a/b")).to be_falsey end end context 'with root pattern "/"' do let(:pattern) { Chef::ChefFS::FilePattern.new("/") } it "match?" do expect(pattern.match?("/")).to be_truthy expect(pattern.match?("")).to be_falsey expect(pattern.match?("a")).to be_falsey expect(pattern.match?("/a")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to eq("/") end it "could_match_children?" do expect(pattern.could_match_children?("")).to be_falsey expect(pattern.could_match_children?("/")).to be_falsey expect(pattern.could_match_children?("a")).to be_falsey expect(pattern.could_match_children?("a/b")).to be_falsey expect(pattern.could_match_children?("/a")).to be_falsey end end context 'with simple pattern "abc"' do let(:pattern) { Chef::ChefFS::FilePattern.new("abc") } it "match?" do expect(pattern.match?("abc")).to be_truthy expect(pattern.match?("a")).to be_falsey expect(pattern.match?("abcd")).to be_falsey expect(pattern.match?("/abc")).to be_falsey expect(pattern.match?("")).to be_falsey expect(pattern.match?("/")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to eq("abc") end it "could_match_children?" do expect(pattern.could_match_children?("")).to be_falsey expect(pattern.could_match_children?("abc")).to be_falsey expect(pattern.could_match_children?("/abc")).to be_falsey end end context 'with simple pattern "/abc"' do let(:pattern) { Chef::ChefFS::FilePattern.new("/abc") } it "match?" do expect(pattern.match?("/abc")).to be_truthy expect(pattern.match?("abc")).to be_falsey expect(pattern.match?("a")).to be_falsey expect(pattern.match?("abcd")).to be_falsey expect(pattern.match?("")).to be_falsey expect(pattern.match?("/")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to eq("/abc") end it "could_match_children?" do expect(pattern.could_match_children?("abc")).to be_falsey expect(pattern.could_match_children?("/abc")).to be_falsey expect(pattern.could_match_children?("/")).to be_truthy expect(pattern.could_match_children?("")).to be_falsey end it "exact_child_name_under" do expect(pattern.exact_child_name_under("/")).to eq("abc") end end context 'with simple pattern "abc/def/ghi"' do let(:pattern) { Chef::ChefFS::FilePattern.new("abc/def/ghi") } it "match?" do expect(pattern.match?("abc/def/ghi")).to be_truthy expect(pattern.match?("/abc/def/ghi")).to be_falsey expect(pattern.match?("abc")).to be_falsey expect(pattern.match?("abc/def")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to eq("abc/def/ghi") end it "could_match_children?" do expect(pattern.could_match_children?("abc")).to be_truthy expect(pattern.could_match_children?("xyz")).to be_falsey expect(pattern.could_match_children?("/abc")).to be_falsey expect(pattern.could_match_children?("abc/def")).to be_truthy expect(pattern.could_match_children?("abc/xyz")).to be_falsey expect(pattern.could_match_children?("abc/def/ghi")).to be_falsey end it "exact_child_name_under" do expect(pattern.exact_child_name_under("abc")).to eq("def") expect(pattern.exact_child_name_under("abc/def")).to eq("ghi") end end context 'with simple pattern "/abc/def/ghi"' do let(:pattern) { Chef::ChefFS::FilePattern.new("/abc/def/ghi") } it "match?" do expect(pattern.match?("/abc/def/ghi")).to be_truthy expect(pattern.match?("abc/def/ghi")).to be_falsey expect(pattern.match?("/abc")).to be_falsey expect(pattern.match?("/abc/def")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to eq("/abc/def/ghi") end it "could_match_children?" do expect(pattern.could_match_children?("/abc")).to be_truthy expect(pattern.could_match_children?("/xyz")).to be_falsey expect(pattern.could_match_children?("abc")).to be_falsey expect(pattern.could_match_children?("/abc/def")).to be_truthy expect(pattern.could_match_children?("/abc/xyz")).to be_falsey expect(pattern.could_match_children?("/abc/def/ghi")).to be_falsey end it "exact_child_name_under" do expect(pattern.exact_child_name_under("/")).to eq("abc") expect(pattern.exact_child_name_under("/abc")).to eq("def") expect(pattern.exact_child_name_under("/abc/def")).to eq("ghi") end end context 'with simple pattern "a\*\b"', :skip => (Chef::Platform.windows?) do let(:pattern) { Chef::ChefFS::FilePattern.new('a\*\b') } it "match?" do expect(pattern.match?("a*b")).to be_truthy expect(pattern.match?("ab")).to be_falsey expect(pattern.match?("acb")).to be_falsey expect(pattern.match?("ab")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to eq("a*b") end it "could_match_children?" do expect(pattern.could_match_children?("a/*b")).to be_falsey end end context 'with star pattern "/abc/*/ghi"' do let(:pattern) { Chef::ChefFS::FilePattern.new("/abc/*/ghi") } it "match?" do expect(pattern.match?("/abc/def/ghi")).to be_truthy expect(pattern.match?("/abc/ghi")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to be_nil end it "could_match_children?" do expect(pattern.could_match_children?("/abc")).to be_truthy expect(pattern.could_match_children?("/xyz")).to be_falsey expect(pattern.could_match_children?("abc")).to be_falsey expect(pattern.could_match_children?("/abc/def")).to be_truthy expect(pattern.could_match_children?("/abc/xyz")).to be_truthy expect(pattern.could_match_children?("/abc/def/ghi")).to be_falsey end it "exact_child_name_under" do expect(pattern.exact_child_name_under("/")).to eq("abc") expect(pattern.exact_child_name_under("/abc")).to eq(nil) expect(pattern.exact_child_name_under("/abc/def")).to eq("ghi") end end context 'with star pattern "/abc/d*f/ghi"' do let(:pattern) { Chef::ChefFS::FilePattern.new("/abc/d*f/ghi") } it "match?" do expect(pattern.match?("/abc/def/ghi")).to be_truthy expect(pattern.match?("/abc/dxf/ghi")).to be_truthy expect(pattern.match?("/abc/df/ghi")).to be_truthy expect(pattern.match?("/abc/dxyzf/ghi")).to be_truthy expect(pattern.match?("/abc/d/ghi")).to be_falsey expect(pattern.match?("/abc/f/ghi")).to be_falsey expect(pattern.match?("/abc/ghi")).to be_falsey expect(pattern.match?("/abc/xyz/ghi")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to be_nil end it "could_match_children?" do expect(pattern.could_match_children?("/abc")).to be_truthy expect(pattern.could_match_children?("/xyz")).to be_falsey expect(pattern.could_match_children?("abc")).to be_falsey expect(pattern.could_match_children?("/abc/def")).to be_truthy expect(pattern.could_match_children?("/abc/xyz")).to be_falsey expect(pattern.could_match_children?("/abc/dxyzf")).to be_truthy expect(pattern.could_match_children?("/abc/df")).to be_truthy expect(pattern.could_match_children?("/abc/d")).to be_falsey expect(pattern.could_match_children?("/abc/f")).to be_falsey expect(pattern.could_match_children?("/abc/def/ghi")).to be_falsey end it "exact_child_name_under" do expect(pattern.exact_child_name_under("/")).to eq("abc") expect(pattern.exact_child_name_under("/abc")).to eq(nil) expect(pattern.exact_child_name_under("/abc/def")).to eq("ghi") end end context 'with star pattern "/abc/d??f/ghi"' do let(:pattern) { Chef::ChefFS::FilePattern.new("/abc/d??f/ghi") } it "match?" do expect(pattern.match?("/abc/deef/ghi")).to be_truthy expect(pattern.match?("/abc/deeef/ghi")).to be_falsey expect(pattern.match?("/abc/def/ghi")).to be_falsey expect(pattern.match?("/abc/df/ghi")).to be_falsey expect(pattern.match?("/abc/d/ghi")).to be_falsey expect(pattern.match?("/abc/f/ghi")).to be_falsey expect(pattern.match?("/abc/ghi")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to be_nil end it "could_match_children?" do expect(pattern.could_match_children?("/abc")).to be_truthy expect(pattern.could_match_children?("/xyz")).to be_falsey expect(pattern.could_match_children?("abc")).to be_falsey expect(pattern.could_match_children?("/abc/deef")).to be_truthy expect(pattern.could_match_children?("/abc/deeef")).to be_falsey expect(pattern.could_match_children?("/abc/def")).to be_falsey expect(pattern.could_match_children?("/abc/df")).to be_falsey expect(pattern.could_match_children?("/abc/d")).to be_falsey expect(pattern.could_match_children?("/abc/f")).to be_falsey expect(pattern.could_match_children?("/abc/deef/ghi")).to be_falsey end it "exact_child_name_under" do expect(pattern.exact_child_name_under("/")).to eq("abc") expect(pattern.exact_child_name_under("/abc")).to eq(nil) expect(pattern.exact_child_name_under("/abc/deef")).to eq("ghi") end end context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :skip => (Chef::Platform.windows?) do let(:pattern) { Chef::ChefFS::FilePattern.new("/abc/d[a-z][0-9]f/ghi") } it "match?" do expect(pattern.match?("/abc/de1f/ghi")).to be_truthy expect(pattern.match?("/abc/deef/ghi")).to be_falsey expect(pattern.match?("/abc/d11f/ghi")).to be_falsey expect(pattern.match?("/abc/de11f/ghi")).to be_falsey expect(pattern.match?("/abc/dee1f/ghi")).to be_falsey expect(pattern.match?("/abc/df/ghi")).to be_falsey expect(pattern.match?("/abc/d/ghi")).to be_falsey expect(pattern.match?("/abc/f/ghi")).to be_falsey expect(pattern.match?("/abc/ghi")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to be_nil end it "could_match_children?" do expect(pattern.could_match_children?("/abc")).to be_truthy expect(pattern.could_match_children?("/xyz")).to be_falsey expect(pattern.could_match_children?("abc")).to be_falsey expect(pattern.could_match_children?("/abc/de1f")).to be_truthy expect(pattern.could_match_children?("/abc/deef")).to be_falsey expect(pattern.could_match_children?("/abc/d11f")).to be_falsey expect(pattern.could_match_children?("/abc/de11f")).to be_falsey expect(pattern.could_match_children?("/abc/dee1f")).to be_falsey expect(pattern.could_match_children?("/abc/def")).to be_falsey expect(pattern.could_match_children?("/abc/df")).to be_falsey expect(pattern.could_match_children?("/abc/d")).to be_falsey expect(pattern.could_match_children?("/abc/f")).to be_falsey expect(pattern.could_match_children?("/abc/de1f/ghi")).to be_falsey end it "exact_child_name_under" do expect(pattern.exact_child_name_under("/")).to eq("abc") expect(pattern.exact_child_name_under("/abc")).to eq(nil) expect(pattern.exact_child_name_under("/abc/de1f")).to eq("ghi") end end context 'with star pattern "/abc/**/ghi"' do let(:pattern) { Chef::ChefFS::FilePattern.new("/abc/**/ghi") } it "match?" do expect(pattern.match?("/abc/def/ghi")).to be_truthy expect(pattern.match?("/abc/d/e/f/ghi")).to be_truthy expect(pattern.match?("/abc/ghi")).to be_falsey expect(pattern.match?("/abcdef/d/ghi")).to be_falsey expect(pattern.match?("/abc/d/defghi")).to be_falsey expect(pattern.match?("/xyz")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to be_nil end it "could_match_children?" do expect(pattern.could_match_children?("/abc")).to be_truthy expect(pattern.could_match_children?("/abc/d")).to be_truthy expect(pattern.could_match_children?("/abc/d/e")).to be_truthy expect(pattern.could_match_children?("/abc/d/e/f")).to be_truthy expect(pattern.could_match_children?("/abc/def/ghi")).to be_truthy expect(pattern.could_match_children?("abc")).to be_falsey expect(pattern.could_match_children?("/xyz")).to be_falsey end it "exact_child_name_under" do expect(pattern.exact_child_name_under("/")).to eq("abc") expect(pattern.exact_child_name_under("/abc")).to eq(nil) expect(pattern.exact_child_name_under("/abc/def")).to eq(nil) end end context 'with star pattern "/abc**/ghi"' do let(:pattern) { Chef::ChefFS::FilePattern.new("/abc**/ghi") } it "match?" do expect(pattern.match?("/abc/def/ghi")).to be_truthy expect(pattern.match?("/abc/d/e/f/ghi")).to be_truthy expect(pattern.match?("/abc/ghi")).to be_truthy expect(pattern.match?("/abcdef/ghi")).to be_truthy expect(pattern.match?("/abc/defghi")).to be_falsey expect(pattern.match?("/xyz")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to be_nil end it "could_match_children?" do expect(pattern.could_match_children?("/abc")).to be_truthy expect(pattern.could_match_children?("/abcdef")).to be_truthy expect(pattern.could_match_children?("/abc/d/e")).to be_truthy expect(pattern.could_match_children?("/abc/d/e/f")).to be_truthy expect(pattern.could_match_children?("/abc/def/ghi")).to be_truthy expect(pattern.could_match_children?("abc")).to be_falsey end it "exact_child_name_under" do expect(pattern.exact_child_name_under("/")).to eq(nil) expect(pattern.exact_child_name_under("/abc")).to eq(nil) expect(pattern.exact_child_name_under("/abc/def")).to eq(nil) end end context 'with star pattern "/abc/**ghi"' do let(:pattern) { Chef::ChefFS::FilePattern.new("/abc/**ghi") } it "match?" do expect(pattern.match?("/abc/def/ghi")).to be_truthy expect(pattern.match?("/abc/def/ghi/ghi")).to be_truthy expect(pattern.match?("/abc/def/ghi/jkl")).to be_falsey expect(pattern.match?("/abc/d/e/f/ghi")).to be_truthy expect(pattern.match?("/abc/ghi")).to be_truthy expect(pattern.match?("/abcdef/ghi")).to be_falsey expect(pattern.match?("/abc/defghi")).to be_truthy expect(pattern.match?("/xyz")).to be_falsey end it "exact_path" do expect(pattern.exact_path).to be_nil end it "could_match_children?" do expect(pattern.could_match_children?("/abc")).to be_truthy expect(pattern.could_match_children?("/abcdef")).to be_falsey expect(pattern.could_match_children?("/abc/d/e")).to be_truthy expect(pattern.could_match_children?("/abc/d/e/f")).to be_truthy expect(pattern.could_match_children?("/abc/def/ghi")).to be_truthy expect(pattern.could_match_children?("abc")).to be_falsey expect(pattern.could_match_children?("/xyz")).to be_falsey end it "exact_child_name_under" do expect(pattern.exact_child_name_under("/")).to eq("abc") expect(pattern.exact_child_name_under("/abc")).to eq(nil) expect(pattern.exact_child_name_under("/abc/def")).to eq(nil) end end context 'with star pattern "a**b**c"' do let(:pattern) { Chef::ChefFS::FilePattern.new("a**b**c") } it "match?" do expect(pattern.match?("axybzwc")).to be_truthy expect(pattern.match?("abc")).to be_truthy expect(pattern.match?("axyzwc")).to be_falsey expect(pattern.match?("ac")).to be_falsey expect(pattern.match?("a/x/y/b/z/w/c")).to be_truthy end it "exact_path" do expect(pattern.exact_path).to be_nil end end context "normalization tests" do it "handles trailing slashes" do expect(p("abc/").normalized_pattern).to eq("abc") expect(p("abc/").exact_path).to eq("abc") expect(p("abc/").match?("abc")).to be_truthy expect(p("//").normalized_pattern).to eq("/") expect(p("//").exact_path).to eq("/") expect(p("//").match?("/")).to be_truthy expect(p("/./").normalized_pattern).to eq("/") expect(p("/./").exact_path).to eq("/") expect(p("/./").match?("/")).to be_truthy end it "handles multiple slashes" do expect(p("abc//def").normalized_pattern).to eq("abc/def") expect(p("abc//def").exact_path).to eq("abc/def") expect(p("abc//def").match?("abc/def")).to be_truthy expect(p("abc//").normalized_pattern).to eq("abc") expect(p("abc//").exact_path).to eq("abc") expect(p("abc//").match?("abc")).to be_truthy end it "handles dot" do expect(p("abc/./def").normalized_pattern).to eq("abc/def") expect(p("abc/./def").exact_path).to eq("abc/def") expect(p("abc/./def").match?("abc/def")).to be_truthy expect(p("./abc/def").normalized_pattern).to eq("abc/def") expect(p("./abc/def").exact_path).to eq("abc/def") expect(p("./abc/def").match?("abc/def")).to be_truthy expect(p("/.").normalized_pattern).to eq("/") expect(p("/.").exact_path).to eq("/") expect(p("/.").match?("/")).to be_truthy end it "handles dotdot" do expect(p("abc/../def").normalized_pattern).to eq("def") expect(p("abc/../def").exact_path).to eq("def") expect(p("abc/../def").match?("def")).to be_truthy expect(p("abc/def/../..").normalized_pattern).to eq("") expect(p("abc/def/../..").exact_path).to eq("") expect(p("abc/def/../..").match?("")).to be_truthy expect(p("/*/../def").normalized_pattern).to eq("/def") expect(p("/*/../def").exact_path).to eq("/def") expect(p("/*/../def").match?("/def")).to be_truthy expect(p("/*/*/../def").normalized_pattern).to eq("/*/def") expect(p("/*/*/../def").exact_path).to be_nil expect(p("/*/*/../def").match?("/abc/def")).to be_truthy expect(p("/abc/def/../..").normalized_pattern).to eq("/") expect(p("/abc/def/../..").exact_path).to eq("/") expect(p("/abc/def/../..").match?("/")).to be_truthy expect(p("abc/../../def").normalized_pattern).to eq("../def") expect(p("abc/../../def").exact_path).to eq("../def") expect(p("abc/../../def").match?("../def")).to be_truthy end it "handles dotdot with double star" do expect(p("abc**/def/../ghi").exact_path).to be_nil expect(p("abc**/def/../ghi").match?("abc/ghi")).to be_truthy expect(p("abc**/def/../ghi").match?("abc/x/y/z/ghi")).to be_truthy expect(p("abc**/def/../ghi").match?("ghi")).to be_falsey end it "raises error on dotdot with overlapping double star" do expect { Chef::ChefFS::FilePattern.new("abc/**/../def").exact_path }.to raise_error(ArgumentError) expect { Chef::ChefFS::FilePattern.new("abc/**/abc/../../def").exact_path }.to raise_error(ArgumentError) end it "handles leading dotdot" do expect(p("../abc/def").exact_path).to eq("../abc/def") expect(p("../abc/def").match?("../abc/def")).to be_truthy expect(p("/../abc/def").exact_path).to eq("/abc/def") expect(p("/../abc/def").match?("/abc/def")).to be_truthy expect(p("..").exact_path).to eq("..") expect(p("..").match?("..")).to be_truthy expect(p("/..").exact_path).to eq("/") expect(p("/..").match?("/")).to be_truthy end end # match? # - single element matches (empty, fixed, ?, *, characters, escapes) # - nested matches # - absolute matches # - trailing slashes # - ** # exact_path # - empty # - single element and nested matches, with escapes # - absolute and relative # - ?, *, characters, ** # could_match_children? # # # # context 'with pattern "abc"' do end context 'with pattern "/abc"' do end context 'with pattern "abc/def/ghi"' do end context 'with pattern "/abc/def/ghi"' do end # Exercise the different methods to their maximum end chef-12.14.60/spec/unit/chef_fs/file_system/000077500000000000000000000000001276456504500205215ustar00rootroot00000000000000chef-12.14.60/spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb000066400000000000000000000021211276456504500252320ustar00rootroot00000000000000# # Author:: John Keiser () # 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/chef_fs/file_system/chef_server/cookbook_subdir" describe Chef::ChefFS::FileSystem::ChefServer::CookbookSubdir do let(:root) do Chef::ChefFS::FileSystem::BaseFSDir.new("", nil) end let(:cookbook_subdir) do Chef::ChefFS::FileSystem::ChefServer::CookbookSubdir.new("test", root, false, true) end it "can get child" do cookbook_subdir.child("test") end end chef-12.14.60/spec/unit/chef_fs/file_system/operation_failed_error_spec.rb000066400000000000000000000036251276456504500266030ustar00rootroot00000000000000# # Author:: John Keiser () # 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/chef_fs/file_system/operation_failed_error" describe Chef::ChefFS::FileSystem::OperationFailedError do context "message" do let(:error_message) { 'HTTP error writing: 400 "Bad Request"' } context "has a cause attribute and HTTP result code is 400" do it "include error cause" do allow_message_expectations_on_nil response_body = '{"error":["Invalid key test in request body"]}' allow(@response).to receive(:code).and_return("400") allow(@response).to receive(:body).and_return(response_body) exception = Net::HTTPServerException.new("(exception) unauthorized", @response) expect do raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, exception), error_message end.to raise_error(Chef::ChefFS::FileSystem::OperationFailedError, "#{error_message} cause: #{response_body}") end end context "does not have a cause attribute" do it "does not include error cause" do expect do raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self), error_message end.to raise_error(Chef::ChefFS::FileSystem::OperationFailedError, error_message) end end end end chef-12.14.60/spec/unit/chef_fs/file_system/repository/000077500000000000000000000000001276456504500227405ustar00rootroot00000000000000chef-12.14.60/spec/unit/chef_fs/file_system/repository/base_file_spec.rb000066400000000000000000000072231276456504500262140ustar00rootroot00000000000000# # 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/chef_fs/file_system/repository/base_file" require "chef/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/base_fs_object" require "chef/chef_fs/file_system/exceptions" require "chef/chef_fs/file_system/nonexistent_fs_object" describe Chef::ChefFS::FileSystem::Repository::BaseFile do let(:root) do Chef::ChefFS::FileSystem::BaseFSDir.new("", nil) end let(:tmp_dir) { Dir.mktmpdir } let(:parent) do Chef::ChefFS::FileSystem::Repository::Directory.new("test", root, tmp_dir) end let(:file) do file = described_class.new("test_file.json", parent) file.write_pretty_json = false file end let(:content) { '"name": "canteloup"' } let(:file_path) { File.join(tmp_dir, "test_file.json") } after do FileUtils.rm_f(file_path) end context "#is_json_file?" do it "returns false when the file is not json", pending: "We assume that everything is ruby or JSON" do file = described_class.new("test_file.dpkg", parent) expect(file.is_json_file?).to be_falsey end it "returns true when the file is json" do expect(file.is_json_file?).to be_truthy end end context "#name_valid?" do it "rejects dotfiles" do file = described_class.new(".test_file.json", parent) expect(file.name_valid?).to be_falsey end it "rejects non json files", pending: "We assume that everything is ruby or JSON" do file = described_class.new("test_file.dpkg", parent) expect(file.name_valid?).to be_falsey end it "allows ruby files" do file = described_class.new("test_file.rb", parent) expect(file.name_valid?).to be_truthy end it "allows correctly named files" do expect(file.name_valid?).to be_truthy end end context "#fs_entry_valid?" do it "rejects invalid names" do file = described_class.new("test_file.dpkg", parent) expect(file.fs_entry_valid?).to be_falsey end it "rejects missing files" do FileUtils.rm_f(file_path) expect(file.fs_entry_valid?).to be_falsey end it "allows present and properly named files" do FileUtils.touch(file_path) expect(file.fs_entry_valid?).to be_truthy end end context "#create" do it "doesn't create an existing file" do FileUtils.touch(file_path) expect { file.create('"name": "canteloup"') }.to raise_error(Chef::ChefFS::FileSystem::AlreadyExistsError) end it "creates a new file" do expect(file).to receive(:write).with(content) expect { file.create(content) }.to_not raise_error end end context "#write" do context "minimises a json object" do it "unless pretty json is off" do expect(file).to_not receive(:minimize) file.write(content) end it "correctly" do file = described_class.new("test_file.json", parent) file.write_pretty_json = true expect(file).to receive(:minimize).with(content, file).and_return(content) file.write(content) end end end end chef-12.14.60/spec/unit/chef_fs/file_system/repository/directory_spec.rb000066400000000000000000000117031276456504500263050ustar00rootroot00000000000000# # Author:: Thom May () # 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/chef_fs/file_system/repository/directory" require "chef/chef_fs/file_system/base_fs_object" require "chef/chef_fs/file_system/exceptions" require "chef/chef_fs/file_system/nonexistent_fs_object" CHILD_FILES = %w{ test1.json test2.json skip test3.json skip2 test4 } class TestDirectory < Chef::ChefFS::FileSystem::Repository::Directory def make_child_entry(name) TestFile.new(name, self) end def can_have_child?(name, is_dir) !is_dir && File.extname(name) == ".json" end def dir_ls CHILD_FILES end end class TestFile < Chef::ChefFS::FileSystem::BaseFSObject def fs_entry_valid? name.start_with? "test" end def name_valid? true end end describe Chef::ChefFS::FileSystem::Repository::Directory do let(:root) do Chef::ChefFS::FileSystem::BaseFSDir.new("", nil) end let(:tmp_dir) { Dir.mktmpdir } let(:directory) do described_class.new("test", root, tmp_dir) end let(:test_directory) do TestDirectory.new("test", root, tmp_dir) end let(:file_double) do double(TestFile, create: true, exists?: false) end context "#make_child_entry" do it "raises if not implemented" do expect { directory.send(:make_child_entry, "test") }.to raise_error("Not Implemented") end end context "#create_child" do it "creates a new TestFile" do expect(TestFile).to receive(:new).with("test_child", test_directory).and_return(file_double) allow(file_double).to receive(:file_path).and_return("#{test_directory}/test_child") expect(file_double).to receive(:write).with("test") test_directory.create_child("test_child", "test") end end context "#child" do it "returns a child if it's valid" do expect(test_directory.child("test")).to be_an_instance_of(TestFile) end it "returns a non existent object otherwise" do file_double = instance_double(TestFile, :name_valid? => false) expect(TestFile).to receive(:new).with("test_child", test_directory).and_return(file_double) expect(test_directory.child("test_child")).to be_an_instance_of(Chef::ChefFS::FileSystem::NonexistentFSObject) end end context "#children" do before do CHILD_FILES.sort.each do |child| expect(TestFile).to receive(:new).with(child, test_directory).and_call_original end end it "creates a child for each name" do test_directory.children end it "filters invalid names" do expect(test_directory.children.map { |c| c.name }).to eql %w{ test1.json test2.json test3.json } end end context "#empty?" do it "is true if there are no children" do expect(test_directory).to receive(:children).and_return([]) expect(test_directory.empty?).to be_truthy end it "is false if there are children" do expect(test_directory.empty?).to be_falsey end end describe "checks entry validity" do it "rejects dotfiles" do dir = described_class.new(".test", root, tmp_dir) expect(dir.name_valid?).to be_falsey end it "rejects files" do Tempfile.open("test") do |file| dir = described_class.new("test", root, file.path) expect(dir.name_valid?).to be_truthy expect(dir.fs_entry_valid?).to be_falsey end end it "accepts directories" do expect(directory.name_valid?).to be_truthy end end describe "creates directories" do it "doesn't create an existing directory" do expect { directory.create }.to raise_error(Chef::ChefFS::FileSystem::AlreadyExistsError) end it "creates a new directory" do FileUtils.rmdir(tmp_dir) expect(Dir).to receive(:mkdir).with(tmp_dir) expect { directory.create }.to_not raise_error end after do FileUtils.rmdir(tmp_dir) end end describe "deletes directories" do it "won't delete a non-existant directory" do FileUtils.rmdir(tmp_dir) expect { directory.delete(true) }.to raise_error(Chef::ChefFS::FileSystem::NotFoundError) end it "must delete recursively" do expect { directory.delete(false) }.to raise_error(Chef::ChefFS::FileSystem::MustDeleteRecursivelyError) end it "deletes a directory" do expect(FileUtils).to receive(:rm_r).with(tmp_dir) expect { directory.delete(true) }.to_not raise_error end end end chef-12.14.60/spec/unit/chef_fs/file_system_spec.rb000066400000000000000000000106371276456504500220670ustar00rootroot00000000000000# # Author:: John Keiser () # 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/chef_fs/file_system" require "chef/chef_fs/file_pattern" describe Chef::ChefFS::FileSystem do include FileSystemSupport context "with empty filesystem" do let(:fs) { memory_fs("", {}) } context "list" do it "/" do list_should_yield_paths(fs, "/", "/") end it "/a" do list_should_yield_paths(fs, "/a", "/a") end it "/a/b" do list_should_yield_paths(fs, "/a/b", "/a/b") end it "/*" do list_should_yield_paths(fs, "/*", "/") end end context "resolve_path" do it "/" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/").path).to eq("/") end it "nonexistent /a" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/a").path).to eq("/a") end it "nonexistent /a/b" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/a/b").path).to eq("/a/b") end end end context "with a populated filesystem" do let(:fs) do memory_fs("", { :a => { :aa => { :c => "", :zz => "", }, :ab => { :c => "", }, }, :x => "", :y => {}, }) end context "list" do it "/**" do list_should_yield_paths(fs, "/**", "/", "/a", "/x", "/y", "/a/aa", "/a/aa/c", "/a/aa/zz", "/a/ab", "/a/ab/c") end it "/" do list_should_yield_paths(fs, "/", "/") end it "/*" do list_should_yield_paths(fs, "/*", "/", "/a", "/x", "/y") end it "/*/*" do list_should_yield_paths(fs, "/*/*", "/a/aa", "/a/ab") end it "/*/*/*" do list_should_yield_paths(fs, "/*/*/*", "/a/aa/c", "/a/aa/zz", "/a/ab/c") end it "/*/*/?" do list_should_yield_paths(fs, "/*/*/?", "/a/aa/c", "/a/ab/c") end it "/a/*/c" do list_should_yield_paths(fs, "/a/*/c", "/a/aa/c", "/a/ab/c") end it "/**b/c" do list_should_yield_paths(fs, "/**b/c", "/a/ab/c") end it "/a/ab/c" do no_blocking_calls_allowed list_should_yield_paths(fs, "/a/ab/c", "/a/ab/c") end it "nonexistent /a/ab/blah" do no_blocking_calls_allowed list_should_yield_paths(fs, "/a/ab/blah", "/a/ab/blah") end it "nonexistent /a/ab/blah/bjork" do no_blocking_calls_allowed list_should_yield_paths(fs, "/a/ab/blah/bjork", "/a/ab/blah/bjork") end end context "resolve_path" do before(:each) do no_blocking_calls_allowed end it "resolves /" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/").path).to eq("/") end it "resolves /x" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/x").path).to eq("/x") end it "resolves /a" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/a").path).to eq("/a") end it "resolves /a/aa" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/a/aa").path).to eq("/a/aa") end it "resolves /a/aa/zz" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/a/aa/zz").path).to eq("/a/aa/zz") end it "resolves nonexistent /q/x/w" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/q/x/w").path).to eq("/q/x/w") end end context "empty?" do it "is not empty /" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/").empty?).to be false end it "is empty /y" do expect(Chef::ChefFS::FileSystem.resolve_path(fs, "/y").empty?).to be true end it 'is not a directory and can\'t be tested /x' do expect { Chef::ChefFS::FileSystem.resolve_path(fs, "/x").empty? }.to raise_error(NoMethodError) end end end end chef-12.14.60/spec/unit/chef_fs/parallelizer.rb000066400000000000000000000363311276456504500212170ustar00rootroot00000000000000require "spec_helper" require "chef/chef_fs/parallelizer" describe Chef::ChefFS::Parallelizer do before :each do @start_time = Time.now end def elapsed_time Time.now - @start_time end after :each do parallelizer.kill end context "With a Parallelizer with 5 threads" do let :parallelizer do Chef::ChefFS::Parallelizer.new(5) end def parallelize(inputs, options = {}, &block) parallelizer.parallelize(inputs, { :main_thread_processing => false }.merge(options), &block) end it "parallel_do creates unordered output as soon as it is available" do outputs = [] parallelizer.parallel_do([0.5, 0.3, 0.1]) do |val| sleep val outputs << val end expect(elapsed_time).to be < 0.6 expect(outputs).to eq([ 0.1, 0.3, 0.5 ]) end context "With :ordered => false (unordered output)" do it "An empty input produces an empty output" do expect(parallelize([], :ordered => false) do sleep 10 end.to_a).to eql([]) expect(elapsed_time).to be < 0.1 end it "10 sleep(0.2)s complete within 0.5 seconds" do expect(parallelize(1.upto(10), :ordered => false) do |i| sleep 0.2 "x" end.to_a).to eq(%w{x x x x x x x x x x}) expect(elapsed_time).to be < 0.5 end it "The output comes as soon as it is available" do enum = parallelize([0.5, 0.3, 0.1], :ordered => false) do |val| sleep val val end expect(enum.map do |value| expect(elapsed_time).to be < value + 0.1 value end).to eq([ 0.1, 0.3, 0.5 ]) end it "An exception in input is passed through but does NOT stop processing" do input = TestEnumerable.new(0.5, 0.3, 0.1) do raise "hi" end enum = parallelize(input, :ordered => false) { |x| sleep(x); x } results = [] expect { enum.each { |value| results << value } }.to raise_error "hi" expect(results).to eq([ 0.1, 0.3, 0.5 ]) expect(elapsed_time).to be < 0.6 end it "Exceptions in output are raised after all processing is done" do processed = 0 enum = parallelize([1, 2, "x", 3], :ordered => false) do |x| if x == "x" sleep 0.1 raise "hi" end sleep 0.2 processed += 1 x end results = [] expect { enum.each { |value| results << value } }.to raise_error "hi" expect(results.sort).to eq([ 1, 2, 3 ]) expect(elapsed_time).to be < 0.3 expect(processed).to eq(3) end it "Exceptions with :stop_on_exception are raised after all processing is done" do processed = 0 parallelized = parallelize([0.3, 0.3, "x", 0.3, 0.3, 0.3, 0.3, 0.3], :ordered => false, :stop_on_exception => true) do |x| if x == "x" sleep(0.1) raise "hi" end sleep(x) processed += 1 x end expect { parallelized.to_a }.to raise_error "hi" expect(processed).to eq(4) end end context "With :ordered => true (ordered output)" do it "An empty input produces an empty output" do expect(parallelize([]) do sleep 10 end.to_a).to eql([]) expect(elapsed_time).to be < 0.1 end it "10 sleep(0.2)s complete within 0.5 seconds" do expect(parallelize(1.upto(10), :ordered => true) do |i| sleep 0.2 "x" end.to_a).to eq(%w{x x x x x x x x x x}) expect(elapsed_time).to be < 0.5 end it "Output comes in the order of the input" do enum = parallelize([0.5, 0.3, 0.1]) do |val| sleep val val end.enum_for(:each_with_index) expect(enum.next).to eq([ 0.5, 0 ]) expect(enum.next).to eq([ 0.3, 1 ]) expect(enum.next).to eq([ 0.1, 2 ]) expect(elapsed_time).to be < 0.6 end it "Exceptions in input are raised in the correct sequence but do NOT stop processing" do input = TestEnumerable.new(0.5, 0.3, 0.1) do raise "hi" end results = [] enum = parallelize(input) { |x| sleep(x); x } expect { enum.each { |value| results << value } }.to raise_error "hi" expect(elapsed_time).to be < 0.6 expect(results).to eq([ 0.5, 0.3, 0.1 ]) end it "Exceptions in output are raised in the correct sequence and running processes do NOT stop processing" do processed = 0 enum = parallelize([1, 2, "x", 3]) do |x| if x == "x" sleep(0.1) raise "hi" end sleep(0.2) processed += 1 x end results = [] expect { enum.each { |value| results << value } }.to raise_error "hi" expect(results).to eq([ 1, 2 ]) expect(elapsed_time).to be < 0.3 expect(processed).to eq(3) end it "Exceptions with :stop_on_exception are raised after all processing is done" do processed = 0 parallelized = parallelize([0.3, 0.3, "x", 0.3, 0.3, 0.3, 0.3, 0.3], :ordered => false, :stop_on_exception => true) do |x| if x == "x" sleep(0.1) raise "hi" end sleep(x) processed += 1 x end expect { parallelized.to_a }.to raise_error "hi" expect(processed).to eq(4) end end it "When the input is slow, output still proceeds" do input = TestEnumerable.new do |&block| block.call(1) sleep 0.1 block.call(2) sleep 0.1 block.call(3) sleep 0.1 end enum = parallelize(input) { |x| x } expect(enum.map do |value| expect(elapsed_time).to be < (value + 1) * 0.1 value end).to eq([ 1, 2, 3 ]) end end context "With a Parallelizer with 1 thread" do let :parallelizer do Chef::ChefFS::Parallelizer.new(1) end context "when the thread is occupied with a job" do before :each do parallelizer started = false @occupying_job_finished = occupying_job_finished = [ false ] @thread = Thread.new do parallelizer.parallelize([0], :main_thread_processing => false) do |x| started = true sleep(0.3) occupying_job_finished[0] = true end.wait end sleep(0.01) until started end after :each do if RUBY_VERSION.to_f > 1.8 Thread.kill(@thread) end end it "parallelize with :main_thread_processing = true does not block" do expect(parallelizer.parallelize([1]) do |x| sleep(0.1) x end.to_a).to eq([ 1 ]) expect(elapsed_time).to be < 0.2 end it "parallelize with :main_thread_processing = false waits for the job to finish" do expect(parallelizer.parallelize([1], :main_thread_processing => false) do |x| sleep(0.1) x + 1 end.to_a).to eq([ 2 ]) expect(elapsed_time).to be > 0.3 end it "resizing the Parallelizer to 0 waits for the job to stop" do expect(elapsed_time).to be < 0.2 parallelizer.resize(0) expect(parallelizer.num_threads).to eq(0) expect(elapsed_time).to be > 0.25 expect(@occupying_job_finished).to eq([ true ]) end it "stopping the Parallelizer waits for the job to finish" do expect(elapsed_time).to be < 0.2 parallelizer.stop expect(parallelizer.num_threads).to eq(0) expect(elapsed_time).to be > 0.25 expect(@occupying_job_finished).to eq([ true ]) end it "resizing the Parallelizer to 2 does not stop the job" do expect(elapsed_time).to be < 0.2 parallelizer.resize(2) expect(parallelizer.num_threads).to eq(2) expect(elapsed_time).to be < 0.2 sleep(0.3) expect(@occupying_job_finished).to eq([ true ]) end end context "enumerable methods should run efficiently" do it ".count does not process anything" do outputs_processed = 0 input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6) enum = parallelizer.parallelize(input_mapper) do |x| outputs_processed += 1 sleep(0.05) # Just enough to yield and get other inputs in the queue x end expect(enum.count).to eq(6) expect(outputs_processed).to eq(0) expect(input_mapper.num_processed).to eq(6) end it ".count with arguments works normally" do outputs_processed = 0 input_mapper = TestEnumerable.new(1, 1, 1, 1, 2, 2, 2, 3, 3, 4) enum = parallelizer.parallelize(input_mapper) do |x| outputs_processed += 1 x end expect(enum.count { |x| x > 1 }).to eq(6) expect(enum.count(2)).to eq(3) expect(outputs_processed).to eq(20) expect(input_mapper.num_processed).to eq(20) end it ".first does not enumerate anything other than the first result(s)" do outputs_processed = 0 input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6) enum = parallelizer.parallelize(input_mapper) do |x| outputs_processed += 1 sleep(0.05) # Just enough to yield and get other inputs in the queue x end expect(enum.first).to eq(1) expect(enum.first(2)).to eq([1, 2]) expect(outputs_processed).to eq(3) expect(input_mapper.num_processed).to eq(3) end it ".take does not enumerate anything other than the first result(s)" do outputs_processed = 0 input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6) enum = parallelizer.parallelize(input_mapper) do |x| outputs_processed += 1 sleep(0.05) # Just enough to yield and get other inputs in the queue x end expect(enum.take(2)).to eq([1, 2]) expect(outputs_processed).to eq(2) expect(input_mapper.num_processed).to eq(2) end it ".drop does not process anything other than the last result(s)" do outputs_processed = 0 input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6) enum = parallelizer.parallelize(input_mapper) do |x| outputs_processed += 1 sleep(0.05) # Just enough to yield and get other inputs in the queue x end expect(enum.drop(2)).to eq([3, 4, 5, 6]) expect(outputs_processed).to eq(4) expect(input_mapper.num_processed).to eq(6) end if Enumerable.method_defined?(:lazy) it ".lazy.take does not enumerate anything other than the first result(s)" do outputs_processed = 0 input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6) enum = parallelizer.parallelize(input_mapper) do |x| outputs_processed += 1 sleep(0.05) # Just enough to yield and get other inputs in the queue x end expect(enum.lazy.take(2).to_a).to eq([1, 2]) expect(outputs_processed).to eq(2) expect(input_mapper.num_processed).to eq(2) end it ".drop does not process anything other than the last result(s)" do outputs_processed = 0 input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6) enum = parallelizer.parallelize(input_mapper) do |x| outputs_processed += 1 sleep(0.05) # Just enough to yield and get other inputs in the queue x end expect(enum.lazy.drop(2).to_a).to eq([3, 4, 5, 6]) expect(outputs_processed).to eq(4) expect(input_mapper.num_processed).to eq(6) end it "lazy enumerable is actually lazy" do outputs_processed = 0 input_mapper = TestEnumerable.new(1, 2, 3, 4, 5, 6) enum = parallelizer.parallelize(input_mapper) do |x| outputs_processed += 1 sleep(0.05) # Just enough to yield and get other inputs in the queue x end enum.lazy.take(2) enum.lazy.drop(2) sleep(0.1) expect(outputs_processed).to eq(0) expect(input_mapper.num_processed).to eq(0) end end end context "running enumerable multiple times should function correctly" do it ".map twice on the same parallel enumerable returns the correct results and re-processes the input" do outputs_processed = 0 input_mapper = TestEnumerable.new(1, 2, 3) enum = parallelizer.parallelize(input_mapper) do |x| outputs_processed += 1 x end expect(enum.map { |x| x }).to eq([1, 2, 3]) expect(enum.map { |x| x }).to eq([1, 2, 3]) expect(outputs_processed).to eq(6) expect(input_mapper.num_processed).to eq(6) end it ".first and then .map on the same parallel enumerable returns the correct results and re-processes the input" do outputs_processed = 0 input_mapper = TestEnumerable.new(1, 2, 3) enum = parallelizer.parallelize(input_mapper) do |x| outputs_processed += 1 x end expect(enum.first).to eq(1) expect(enum.map { |x| x }).to eq([1, 2, 3]) expect(outputs_processed).to be >= 4 expect(input_mapper.num_processed).to be >= 4 end it "two simultaneous enumerations throws an exception" do enum = parallelizer.parallelize([1, 2, 3]) { |x| x } a = enum.enum_for(:each) a.next expect do b = enum.enum_for(:each) b.next end.to raise_error end end end context "With a Parallelizer with 0 threads" do let :parallelizer do Chef::ChefFS::Parallelizer.new(0) end context "And main_thread_processing on" do it "succeeds in running" do expect(parallelizer.parallelize([0.5]) { |x| x * 2 }.to_a).to eq([1]) end end end context "With a Parallelizer with 10 threads" do let :parallelizer do Chef::ChefFS::Parallelizer.new(10) end it "does not have contention issues with large numbers of inputs" do expect(parallelizer.parallelize(1.upto(500)) { |x| x + 1 }.to_a).to eq(2.upto(501).to_a) end it "does not have contention issues with large numbers of inputs with ordering off" do expect(parallelizer.parallelize(1.upto(500), :ordered => false) { |x| x + 1 }.to_a.sort).to eq(2.upto(501).to_a) end it "does not have contention issues with large numbers of jobs and inputs with ordering off" do parallelizers = 0.upto(99).map do parallelizer.parallelize(1.upto(500)) { |x| x + 1 } end outputs = [] threads = 0.upto(99).map do |i| Thread.new { outputs[i] = parallelizers[i].to_a } end threads.each { |thread| thread.join } outputs.each { |output| expect(output.sort).to eq(2.upto(501).to_a) } end end class TestEnumerable include Enumerable def initialize(*values, &block) @values = values @block = block @num_processed = 0 end attr_reader :num_processed def each @values.each do |value| @num_processed += 1 yield(value) end if @block @block.call do |value| @num_processed += 1 yield(value) end end end end end chef-12.14.60/spec/unit/chef_fs/path_util_spec.rb000066400000000000000000000101261276456504500215260ustar00rootroot00000000000000# # Author:: Kartik Null Cating-Subramanian () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/chef_fs/path_utils" describe Chef::ChefFS::PathUtils do context "invoking join" do it "joins well-behaved distinct path elements" do expect(Chef::ChefFS::PathUtils.join("a", "b", "c")).to eq("a/b/c") end it "strips extraneous slashes in the middle of paths" do expect(Chef::ChefFS::PathUtils.join("a/", "/b", "/c/")).to eq("a/b/c") expect(Chef::ChefFS::PathUtils.join("a/", "/b", "///c/")).to eq("a/b/c") end it "preserves the whether the first element was absolute or not" do expect(Chef::ChefFS::PathUtils.join("/a/", "/b", "c/")).to eq("/a/b/c") expect(Chef::ChefFS::PathUtils.join("///a/", "/b", "c/")).to eq("/a/b/c") end end context "invoking is_absolute?" do it "confirms that paths starting with / are absolute" do expect(Chef::ChefFS::PathUtils.is_absolute?("/foo/bar/baz")).to be true expect(Chef::ChefFS::PathUtils.is_absolute?("/foo")).to be true end it "confirms that paths starting with // are absolute even though that looks like some windows network path" do expect(Chef::ChefFS::PathUtils.is_absolute?("//foo/bar/baz")).to be true end it "confirms that root is indeed absolute" do expect(Chef::ChefFS::PathUtils.is_absolute?("/")).to be true end it "confirms that paths starting without / are relative" do expect(Chef::ChefFS::PathUtils.is_absolute?("foo/bar/baz")).to be false expect(Chef::ChefFS::PathUtils.is_absolute?("a")).to be false end it "returns false for an empty path." do expect(Chef::ChefFS::PathUtils.is_absolute?("")).to be false end end context "invoking realest_path" do let(:good_path) { File.dirname(__FILE__) } let(:parent_path) { File.dirname(good_path) } it "handles paths with no wildcards or globs" do expect(Chef::ChefFS::PathUtils.realest_path(good_path)).to eq(File.expand_path(good_path)) end it "handles paths with .. and ." do expect(Chef::ChefFS::PathUtils.realest_path(good_path + "/../.")).to eq(File.expand_path(parent_path)) end it "handles paths with *" do expect(Chef::ChefFS::PathUtils.realest_path(good_path + "/*/foo")).to eq(File.expand_path(good_path + "/*/foo")) end it "handles directories that do not exist" do expect(Chef::ChefFS::PathUtils.realest_path(good_path + "/something/or/other")).to eq(File.expand_path(good_path + "/something/or/other")) end it "handles root correctly" do if Chef::Platform.windows? expect(Chef::ChefFS::PathUtils.realest_path("C:/")).to eq("C:/") else expect(Chef::ChefFS::PathUtils.realest_path("/")).to eq("/") end end end context "invoking descendant_path" do it "handles paths with various casing on windows" do allow(Chef::ChefFS).to receive(:windows?) { true } expect(Chef::ChefFS::PathUtils.descendant_path("C:/ab/b/c", "C:/AB/B")).to eq("c") expect(Chef::ChefFS::PathUtils.descendant_path("C:/ab/b/c", "c:/ab/B")).to eq("c") end it "returns nil if the path does not have the given ancestor" do expect(Chef::ChefFS::PathUtils.descendant_path("/D/E/F", "/A/B/C")).to be_nil expect(Chef::ChefFS::PathUtils.descendant_path("/A/B/D", "/A/B/C")).to be_nil end it "returns blank if the ancestor equals the path" do expect(Chef::ChefFS::PathUtils.descendant_path("/A/B/D", "/A/B/D")).to eq("") end end end chef-12.14.60/spec/unit/chef_spec.rb000066400000000000000000000015001276456504500170410ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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 do it "should have a version defined" do expect(Chef::VERSION).to match(/(\d+)\.(\d+)\.(\d+)/) end end chef-12.14.60/spec/unit/client_spec.rb000066400000000000000000000426621276456504500174300ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tim Hinderliter () # Author:: Christopher Walters () # 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" require "spec/support/shared/context/client" require "spec/support/shared/examples/client" require "chef/run_context" require "chef/server_api" require "rbconfig" class FooError < RuntimeError end describe Chef::Client do include_context "client" context "when minimal ohai is configured" do before do Chef::Config[:minimal_ohai] = true end it "runs ohai with only the minimum required plugins" do expected_filter = %w{fqdn machinename hostname platform platform_version os os_version} expect(ohai_system).to receive(:all_plugins).with(expected_filter) client.run_ohai end end describe "authentication protocol selection" do context "when FIPS is disabled" do before do Chef::Config[:fips] = false end it "defaults to 1.1" do expect(Chef::Config[:authentication_protocol_version]).to eq("1.1") end end context "when FIPS is enabled" do before do Chef::Config[:fips] = true end it "defaults to 1.3" do expect(Chef::Config[:authentication_protocol_version]).to eq("1.3") end after do Chef::Config[:fips] = false end end end describe "configuring output formatters" do context "when no formatter has been configured" do context "and STDOUT is a TTY" do before do allow(STDOUT).to receive(:tty?).and_return(true) end it "configures the :doc formatter" do expect(client.formatters_for_run).to eq([[:doc]]) end context "and force_logger is set" do before do Chef::Config[:force_logger] = true end it "configures the :null formatter" do expect(Chef::Config[:force_logger]).to be_truthy expect(client.formatters_for_run).to eq([[:null]]) end end end context "and STDOUT is not a TTY" do before do allow(STDOUT).to receive(:tty?).and_return(false) end it "configures the :null formatter" do expect(client.formatters_for_run).to eq([[:null]]) end context "and force_formatter is set" do before do Chef::Config[:force_formatter] = true end it "it configures the :doc formatter" do expect(client.formatters_for_run).to eq([[:doc]]) end end end end context "when a formatter is configured" do context "with no output path" do before do Chef::Config.add_formatter(:min) end it "does not configure a default formatter" do expect(client.formatters_for_run).to eq([[:min, nil]]) end it "configures the formatter for STDOUT/STDERR" do configured_formatters = client.configure_formatters min_formatter = configured_formatters[0] expect(min_formatter.output.out).to eq(STDOUT) expect(min_formatter.output.err).to eq(STDERR) end end context "with an output path" do before do @tmpout = Tempfile.open("rspec-for-client-formatter-selection-#{Process.pid}") Chef::Config.add_formatter(:min, @tmpout.path) end after do @tmpout.close unless @tmpout.closed? @tmpout.unlink end it "configures the formatter for the file path" do configured_formatters = client.configure_formatters min_formatter = configured_formatters[0] expect(min_formatter.output.out.path).to eq(@tmpout.path) expect(min_formatter.output.err.path).to eq(@tmpout.path) end end end end describe "a full client run" do shared_examples_for "a successful client run" do include_context "a client run" include_context "converge completed" include_context "audit phase completed" include_examples "a completed run" end describe "when running chef-client without fork" do include_examples "a successful client run" end describe "when the client key already exists" do include_examples "a successful client run" do let(:api_client_exists?) { true } end end context "when an override run list is given" do it "permits spaces in overriding run list" do Chef::Client.new(nil, :override_runlist => "role[a], role[b]") end describe "calling run" do include_examples "a successful client run" do let(:client_opts) { { :override_runlist => "recipe[override_recipe]" } } def stub_for_sync_cookbooks # --Client#setup_run_context # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync # expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks) expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync) expect(http_cookbook_sync).to receive(:post). with("environments/_default/cookbook_versions", { :run_list => ["override_recipe"] }). and_return({}) end def stub_for_node_save # Expect NO node save expect(node).not_to receive(:save) end before do # Client will try to compile and run override_recipe expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile) end end end end describe "when a permanent run list is passed as an option" do it "sets the new run list on the node" do client.run expect(node.run_list).to eq(Chef::RunList.new(new_runlist)) end include_examples "a successful client run" do let(:new_runlist) { "recipe[new_run_list_recipe]" } let(:client_opts) { { :runlist => new_runlist } } def stub_for_sync_cookbooks # --Client#setup_run_context # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync # expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks) expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync) expect(http_cookbook_sync).to receive(:post). with("environments/_default/cookbook_versions", { :run_list => ["new_run_list_recipe"] }). and_return({}) end before do # Client will try to compile and run the new_run_list_recipe, but we # do not create a fixture for this. expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile) end end end describe "when converge completes successfully" do include_context "a client run" include_context "converge completed" context "when audit mode is enabled" do describe "when audit phase errors" do include_context "audit phase failed with error" include_examples "a completed run with audit failure" do let(:run_errors) { [audit_error] } end end describe "when audit phase completed" do include_context "audit phase completed" include_examples "a completed run" end describe "when audit phase completed with failed controls" do include_context "audit phase completed with failed controls" include_examples "a completed run with audit failure" do let(:run_errors) { [audit_error] } end end end end describe "when converge errors" do include_context "a client run" include_context "converge failed" describe "when audit phase errors" do include_context "audit phase failed with error" include_examples "a failed run" do let(:run_errors) { [converge_error, audit_error] } end end describe "when audit phase completed" do include_context "audit phase completed" include_examples "a failed run" do let(:run_errors) { [converge_error] } end end describe "when audit phase completed with failed controls" do include_context "audit phase completed with failed controls" include_examples "a failed run" do let(:run_errors) { [converge_error, audit_error] } end end end end describe "when handling run failures" do it "should remove the run_lock on failure of #load_node" do @run_lock = double("Chef::RunLock", :acquire => true) allow(Chef::RunLock).to receive(:new).and_return(@run_lock) @events = double("Chef::EventDispatch::Dispatcher").as_null_object allow(Chef::EventDispatch::Dispatcher).to receive(:new).and_return(@events) # @events is created on Chef::Client.new, so we need to recreate it after mocking client = Chef::Client.new allow(client).to receive(:load_node).and_raise(Exception) expect(@run_lock).to receive(:release) expect { client.run }.to raise_error(Exception) end end describe "when notifying other objects of the status of the chef run" do before do Chef::Client.clear_notifications allow(Chef::Node).to receive(:find_or_create).and_return(node) allow(node).to receive(:save) client.load_node client.build_node end it "notifies observers that the run has started" do notified = false Chef::Client.when_run_starts do |run_status| expect(run_status.node).to eq(node) notified = true end client.run_started expect(notified).to be_truthy end it "notifies observers that the run has completed successfully" do notified = false Chef::Client.when_run_completes_successfully do |run_status| expect(run_status.node).to eq(node) notified = true end client.run_completed_successfully expect(notified).to be_truthy end it "notifies observers that the run failed" do notified = false Chef::Client.when_run_fails do |run_status| expect(run_status.node).to eq(node) notified = true end client.run_failed expect(notified).to be_truthy end end describe "build_node" do it "should expand the roles and recipes for the node" do node.run_list << "role[role_containing_cookbook1]" role_containing_cookbook1 = Chef::Role.new role_containing_cookbook1.name("role_containing_cookbook1") role_containing_cookbook1.run_list << "cookbook1" # build_node will call Node#expand! with server, which will # eventually hit the server to expand the included role. mock_chef_rest = double("Chef::ServerAPI") expect(mock_chef_rest).to receive(:get).with("roles/role_containing_cookbook1").and_return(role_containing_cookbook1.to_hash) expect(Chef::ServerAPI).to receive(:new).and_return(mock_chef_rest) # check pre-conditions. expect(node[:roles]).to be_nil expect(node[:recipes]).to be_nil expect(node[:expanded_run_list]).to be_nil allow(client.policy_builder).to receive(:node).and_return(node) client.policy_builder.select_implementation(node) allow(client.policy_builder.implementation).to receive(:node).and_return(node) # chefspec and possibly others use the return value of this method expect(client.build_node).to eq(node) # check post-conditions. expect(node[:roles]).not_to be_nil expect(node[:roles].length).to eq(1) expect(node[:roles]).to include("role_containing_cookbook1") expect(node[:recipes]).not_to be_nil expect(node[:recipes].length).to eq(2) expect(node[:recipes]).to include("cookbook1") expect(node[:recipes]).to include("cookbook1::default") expect(node[:expanded_run_list]).not_to be_nil expect(node[:expanded_run_list].length).to eq(1) expect(node[:expanded_run_list]).to include("cookbook1::default") end it "should set the environment from the specified configuration value" do expect(node.chef_environment).to eq("_default") Chef::Config[:environment] = "A" test_env = { "name" => "A" } mock_chef_rest = double("Chef::ServerAPI") expect(mock_chef_rest).to receive(:get).with("environments/A").and_return(test_env) expect(Chef::ServerAPI).to receive(:new).and_return(mock_chef_rest) allow(client.policy_builder).to receive(:node).and_return(node) client.policy_builder.select_implementation(node) allow(client.policy_builder.implementation).to receive(:node).and_return(node) expect(client.build_node).to eq(node) expect(node.chef_environment).to eq("A") end end describe "windows_admin_check" do context "platform is not windows" do before do allow(ChefConfig).to receive(:windows?).and_return(false) end it "shouldn't be called" do expect(client).not_to receive(:has_admin_privileges?) client.do_windows_admin_check end end context "platform is windows" do before do allow(ChefConfig).to receive(:windows?).and_return(true) end it "should be called" do expect(client).to receive(:has_admin_privileges?) client.do_windows_admin_check end context "admin privileges exist" do before do expect(client).to receive(:has_admin_privileges?).and_return(true) end it "should not log a warning message" do expect(Chef::Log).not_to receive(:warn) client.do_windows_admin_check end context "fatal admin check is configured" do it "should not raise an exception" do client.do_windows_admin_check #should not raise end end end context "admin privileges doesn't exist" do before do expect(client).to receive(:has_admin_privileges?).and_return(false) end it "should log a warning message" do expect(Chef::Log).to receive(:warn) client.do_windows_admin_check end context "fatal admin check is configured" do it "should raise an exception" do client.do_windows_admin_check # should not raise end end end end end describe "assert_cookbook_path_not_empty" do before do Chef::Config[:solo_legacy_mode] = true Chef::Config[:cookbook_path] = ["/path/to/invalid/cookbook_path"] end context "when any directory of cookbook_path contains no cookbook" do it "raises CookbookNotFound error" do expect do client.send(:assert_cookbook_path_not_empty, nil) end.to raise_error(Chef::Exceptions::CookbookNotFound, 'None of the cookbook paths set in Chef::Config[:cookbook_path], ["/path/to/invalid/cookbook_path"], contain any cookbooks') end end end describe "setting node name" do context "when machinename, hostname and fqdn are all set" do it "favors the fqdn" do expect(client.node_name).to eql(fqdn) end end context "when fqdn is missing" do # ohai 7 should always have machinename == return of hostname let(:fqdn) { nil } it "favors the machinename" do expect(client.node_name).to eql(machinename) end end context "when fqdn and machinename are missing" do # ohai 6 will not have machinename, return the short hostname let(:fqdn) { nil } let(:machinename) { nil } it "falls back to hostname" do expect(client.node_name).to eql(hostname) end end context "when they're all missing" do let(:machinename) { nil } let(:hostname) { nil } let(:fqdn) { nil } it "throws an exception" do expect { client.node_name }.to raise_error(Chef::Exceptions::CannotDetermineNodeName) end end end describe "always attempt to run handlers" do subject { client } before do # fail on the first thing in begin block allow_any_instance_of(Chef::RunLock).to receive(:save_pid).and_raise(NoMethodError) end context "when audit mode is enabled" do before do Chef::Config[:audit_mode] = :enabled end it "should run exception handlers on early fail" do expect(subject).to receive(:run_failed) expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error| expect(error.wrapped_errors.size).to eq 1 expect(error.wrapped_errors).to include(NoMethodError) end end end context "when audit mode is disabled" do before do Chef::Config[:audit_mode] = :disabled end it "should run exception handlers on early fail" do expect(subject).to receive(:run_failed) expect { subject.run }.to raise_error(NoMethodError) end end end end chef-12.14.60/spec/unit/config_fetcher_spec.rb000066400000000000000000000070071276456504500211110ustar00rootroot00000000000000require "spec_helper" require "chef/config_fetcher" describe Chef::ConfigFetcher do let(:valid_json) { Chef::JSONCompat.to_json({ :a => "b" }) } let(:invalid_json) { %q[{"syntax-error": "missing quote}] } let(:http) { double("Chef::HTTP::Simple") } let(:config_location_regex) { Regexp.escape(config_location) } let(:invalid_json_error_regex) { %r{Could not parse the provided JSON file \(#{config_location_regex}\)} } let(:fetcher) { Chef::ConfigFetcher.new(config_location) } context "when loading a local file" do let(:config_location) { "/etc/chef/client.rb" } let(:config_content) { "# The client.rb content" } it "reads the file from disk" do expect(::File).to receive(:read). with(config_location). and_return(config_content) expect(fetcher.read_config).to eq(config_content) end it "gives the expanded path to the config file" do expect(fetcher.expanded_path).to eq(File.expand_path(config_location)) end context "with a relative path" do let(:config_location) { "client.rb" } it "gives the expanded path to the config file" do expected = File.join(Dir.pwd, config_location) expect(fetcher.expanded_path).to eq(expected) end end context "and consuming JSON" do let(:config_location) { "/etc/chef/first-boot.json" } it "returns the parsed JSON" do expect(::File).to receive(:read). with(config_location). and_return(valid_json) expect(fetcher.fetch_json).to eq({ "a" => "b" }) end context "and the JSON is invalid" do it "reports the JSON error" do expect(::File).to receive(:read). with(config_location). and_return(invalid_json) expect(Chef::Application).to receive(:fatal!). with(invalid_json_error_regex, Chef::Exceptions::DeprecatedExitCode.new) fetcher.fetch_json end end end end context "with an HTTP URL config location" do let(:config_location) { "https://example.com/client.rb" } let(:config_content) { "# The client.rb content" } it "returns the config location unchanged for #expanded_path" do expect(fetcher.expanded_path).to eq(config_location) end describe "reading the file" do before do expect(Chef::HTTP::Simple).to receive(:new). with(config_location). and_return(http) end it "reads the file over HTTP" do expect(http).to receive(:get). with("").and_return(config_content) expect(fetcher.read_config).to eq(config_content) end context "and consuming JSON" do let(:config_location) { "https://example.com/foo.json" } it "fetches the file and parses it" do expect(http).to receive(:get). with("").and_return(valid_json) expect(fetcher.fetch_json).to eq({ "a" => "b" }) end context "and the JSON is invalid" do it "reports the JSON error" do expect(http).to receive(:get). with("").and_return(invalid_json) expect(Chef::Application).to receive(:fatal!). with(invalid_json_error_regex, Chef::Exceptions::DeprecatedExitCode.new) fetcher.fetch_json end end end end end context "with a nil config file argument" do let(:config_location) { nil } it "returns the config location unchanged for #expanded_path" do expect(fetcher.expanded_path).to eq(nil) end end end chef-12.14.60/spec/unit/config_spec.rb000066400000000000000000000013661276456504500174130ustar00rootroot00000000000000 require "spec_helper" require "chef/config" RSpec.describe Chef::Config do shared_examples_for "deprecated by ohai but not deprecated" do it "does not emit a deprecation warning when set" do expect(Chef::Log).to_not receive(:warn). with(/Ohai::Config\[:#{option}\] is deprecated/) Chef::Config[option] = value expect(Chef::Config[option]).to eq(value) end end describe ":log_level" do include_examples "deprecated by ohai but not deprecated" do let(:option) { :log_level } let(:value) { :debug } end end describe ":log_location" do include_examples "deprecated by ohai but not deprecated" do let(:option) { :log_location } let(:value) { "path/to/log" } end end end chef-12.14.60/spec/unit/cookbook/000077500000000000000000000000001276456504500164075ustar00rootroot00000000000000chef-12.14.60/spec/unit/cookbook/chefignore_spec.rb000066400000000000000000000034211276456504500220570ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Cookbook::Chefignore do before do @chefignore = Chef::Cookbook::Chefignore.new(File.join(CHEF_SPEC_DATA, "cookbooks")) end it "loads the globs in the chefignore file" do expect(@chefignore.ignores).to match_array(%w{recipes/ignoreme.rb ignored}) end it "removes items from an array that match the ignores" do file_list = %w{ recipes/ignoreme.rb recipes/dontignoreme.rb } expect(@chefignore.remove_ignores_from(file_list)).to eq(%w{recipes/dontignoreme.rb}) end it "determines if a file is ignored" do expect(@chefignore.ignored?("ignored")).to be_truthy expect(@chefignore.ignored?("recipes/ignoreme.rb")).to be_truthy expect(@chefignore.ignored?("recipes/dontignoreme.rb")).to be_falsey end context "when using the single cookbook pattern" do before do @chefignore = Chef::Cookbook::Chefignore.new(File.join(CHEF_SPEC_DATA, "standalone_cookbook")) end it "loads the globs in the chefignore file" do expect(@chefignore.ignores).to match_array(%w{recipes/ignoreme.rb ignored vendor/bundle/*}) end end end chef-12.14.60/spec/unit/cookbook/cookbook_version_loader_spec.rb000066400000000000000000000176151276456504500246610ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Cookbook::CookbookVersionLoader do before do allow(ChefConfig).to receive(:windows?) { false } end describe "loading a cookbook" do let(:chefignore) { nil } let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "cookbooks/openldap") } let(:cookbook_loader) { Chef::Cookbook::CookbookVersionLoader.new(cookbook_path, chefignore) } let(:loaded_cookbook) do cookbook_loader.load! cookbook_loader.cookbook_version end def full_path(cookbook_relative_path) File.join(cookbook_path, cookbook_relative_path) end it "loads attribute files of the cookbook" do expect(loaded_cookbook.attribute_filenames).to include(full_path("/attributes/default.rb")) expect(loaded_cookbook.attribute_filenames).to include(full_path("/attributes/smokey.rb")) end it "loads definition files" do expect(loaded_cookbook.definition_filenames).to include(full_path("/definitions/client.rb")) expect(loaded_cookbook.definition_filenames).to include(full_path("/definitions/server.rb")) end it "loads recipes" do expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/default.rb")) expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/gigantor.rb")) expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/one.rb")) expect(loaded_cookbook.recipe_filenames).to include(full_path("/recipes/return.rb")) end it "loads libraries" do expect(loaded_cookbook.library_filenames).to include(full_path("/libraries/openldap.rb")) expect(loaded_cookbook.library_filenames).to include(full_path("/libraries/openldap/version.rb")) end it "loads static files in the files/ dir" do expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file1.txt")) expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/remotesubdir/remote_subdir_file2.txt")) end it "loads files that start with a ." do expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/.dotfile")) expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/.ssh/id_rsa")) expect(loaded_cookbook.file_filenames).to include(full_path("/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir")) end it "loads root files that start with a ." do expect(loaded_cookbook.all_files).to include(full_path(".root_dotfile")) expect(loaded_cookbook.root_filenames).to include(full_path(".root_dotfile")) end it "loads all unignored files, even if they don't match a segment type" do expect(loaded_cookbook.all_files).to include(full_path("/spec/spec_helper.rb")) # Directories need to be filtered out, though: expect(loaded_cookbook.all_files).to_not include(full_path("/spec")) end it "should load the metadata for the cookbook" do expect(loaded_cookbook.metadata.name.to_s).to eq("openldap") expect(loaded_cookbook.metadata).to be_a_kind_of(Chef::Cookbook::Metadata) end context "when a cookbook has ignored files" do let(:chefignore) { Chef::Cookbook::Chefignore.new(File.join(CHEF_SPEC_DATA, "cookbooks")) } let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "kitchen/openldap") } it "skips ignored files" do expect(loaded_cookbook.recipe_filenames).to include(full_path("recipes/gigantor.rb")) expect(loaded_cookbook.recipe_filenames).to include(full_path("recipes/woot.rb")) expect(loaded_cookbook.recipe_filenames).to_not include(full_path("recipes/ignoreme.rb")) end end context "when the given path is not actually a cookbook" do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "cookbooks/NOTHING_HERE_FOLKS") } it "raises an error when loading with #load!" do expect { cookbook_loader.load! }.to raise_error(Chef::Exceptions::CookbookNotFoundInRepo) end it "skips the cookbook when called with #load" do expect { cookbook_loader.load }.to_not raise_error end end context "when a cookbook has a metadata name different than directory basename" do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "cookbooks/name-mismatch-versionnumber") } it "prefers the metadata name to the directory basename" do expect(loaded_cookbook.name).to eq(:"name-mismatch") end end context "when a cookbook has a metadata file with a ruby error [CHEF-2923]" do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "invalid-metadata-chef-repo/invalid-metadata") } it "raises an error when loading with #load!" do expect { cookbook_loader.load! }.to raise_error("THIS METADATA HAS A BUG") end it "raises an error when called with #load" do expect { cookbook_loader.load }.to raise_error("THIS METADATA HAS A BUG") end it "doesn't raise an error when determining the cookbook name" do expect { cookbook_loader.cookbook_name }.to_not raise_error end it "doesn't raise an error when metadata is first generated" do expect { cookbook_loader.metadata }.to_not raise_error end it "sets an error flag containing error information" do cookbook_loader.metadata expect(cookbook_loader.metadata_error).to be_a(StandardError) expect(cookbook_loader.metadata_error.message).to eq("THIS METADATA HAS A BUG") end end context "when a cookbook has a metadata file with invalid metadata" do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "incomplete-metadata-chef-repo/incomplete-metadata") } let(:error_message) do "Cookbook loaded at path(s) [#{cookbook_path}] has invalid metadata: The `name' attribute is required in cookbook metadata" end it "raises an error when loading with #load!" do expect { cookbook_loader.load! }.to raise_error(Chef::Exceptions::MetadataNotValid, error_message) end it "raises an error when called with #load" do expect { cookbook_loader.load }.to raise_error(Chef::Exceptions::MetadataNotValid, error_message) end it "uses the inferred cookbook name [CHEF-2923]" do # This behavior is intended to support the CHEF-2923 feature where # invalid metadata doesn't prevent you from uploading other cookbooks. # # The metadata is the definitive source of the cookbook name, but if # the metadata is incomplete/invalid, we can't read the name from it. # # The CookbookLoader stores CookbookVersionLoaders in a Hash with # cookbook names as the keys, and finds the loader in this Hash to call # #load on it when the user runs a command like `knife cookbook upload specific-cookbook` # # Most of the time this will work, but if the user tries to upload a # specific cookbook by name, has customized that cookbook's name (so it # doesn't match the inferred name), and that metadata file has a syntax # error, we might report a "cookbook not found" error instead of the # metadata syntax error that is the actual cause. expect(cookbook_loader.cookbook_name).to eq(:"incomplete-metadata") end end end end chef-12.14.60/spec/unit/cookbook/file_vendor_spec.rb000066400000000000000000000064341276456504500222510ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Cookbook::FileVendor do let(:file_vendor_class) { Class.new(described_class) } context "when configured to fetch files over http" do let(:http) { double("Chef::ServerAPI") } before do file_vendor_class.fetch_from_remote(http) end it "sets the vendor class to RemoteFileVendor" do expect(file_vendor_class.vendor_class).to eq(Chef::Cookbook::RemoteFileVendor) end it "sets the initialization options to the given http object" do expect(file_vendor_class.initialization_options).to eq(http) end context "with a manifest from a cookbook version" do # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest let(:manifest) { { :cookbook_name => "bob", :name => "bob-1.2.3" } } it "creates a RemoteFileVendor for a given manifest" do file_vendor = file_vendor_class.create_from_manifest(manifest) expect(file_vendor).to be_a_kind_of(Chef::Cookbook::RemoteFileVendor) expect(file_vendor.rest).to eq(http) expect(file_vendor.cookbook_name).to eq("bob") end end context "with a manifest from a cookbook artifact" do # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest let(:manifest) { { :name => "bob" } } it "creates a RemoteFileVendor for a given manifest" do file_vendor = file_vendor_class.create_from_manifest(manifest) expect(file_vendor).to be_a_kind_of(Chef::Cookbook::RemoteFileVendor) expect(file_vendor.rest).to eq(http) expect(file_vendor.cookbook_name).to eq("bob") end end end context "when configured to load files from disk" do let(:cookbook_path) { %w{/var/chef/cookbooks /var/chef/other_cookbooks} } # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest let(:manifest) { { :cookbook_name => "bob" } } before do file_vendor_class.fetch_from_disk(cookbook_path) end it "sets the vendor class to FileSystemFileVendor" do expect(file_vendor_class.vendor_class).to eq(Chef::Cookbook::FileSystemFileVendor) end it "sets the initialization options to the given cookbook paths" do expect(file_vendor_class.initialization_options).to eq(cookbook_path) end it "creates a FileSystemFileVendor for a given manifest" do file_vendor = file_vendor_class.create_from_manifest(manifest) expect(file_vendor).to be_a_kind_of(Chef::Cookbook::FileSystemFileVendor) expect(file_vendor.cookbook_name).to eq("bob") expect(file_vendor.repo_paths).to eq(cookbook_path) end end end chef-12.14.60/spec/unit/cookbook/metadata_spec.rb000066400000000000000000000750141276456504500215350ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Falcon () # 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" require "chef/cookbook/metadata" describe Chef::Cookbook::Metadata do let(:metadata) { Chef::Cookbook::Metadata.new } describe "when comparing for equality" do before do @fields = [ :name, :description, :long_description, :maintainer, :maintainer_email, :license, :platforms, :dependencies, :recommendations, :suggestions, :conflicting, :providing, :replacing, :attributes, :groupings, :recipes, :version, :source_url, :issues_url, :privacy, :ohai_versions, :chef_versions, :gems ] end it "does not depend on object identity for equality" do expect(metadata).to eq(metadata.dup) end it "is not equal to another object if it isn't have all of the metadata fields" do @fields.each_index do |field_to_remove| fields_to_include = @fields.dup fields_to_include.delete_at(field_to_remove) almost_duck_type = Struct.new(*fields_to_include).new @fields.each do |field| setter = "#{field}=" metadata_value = metadata.send(field) almost_duck_type.send(setter, metadata_value) if almost_duck_type.respond_to?(setter) expect(@mets).not_to eq(almost_duck_type) end end end it "is equal to another object if it has equal values for all metadata fields" do duck_type = Struct.new(*@fields).new @fields.each do |field| setter = "#{field}=" metadata_value = metadata.send(field) duck_type.send(setter, metadata_value) end expect(metadata).to eq(duck_type) end it "is not equal if any values are different" do duck_type_class = Struct.new(*@fields) @fields.each do |field_to_change| duck_type = duck_type_class.new @fields.each do |field| setter = "#{field}=" metadata_value = metadata.send(field) duck_type.send(setter, metadata_value) end duck_type.send("#{field_to_change}=".to_sym, :epic_fail) expect(metadata).not_to eq(duck_type) end end end describe "when first created" do it "has no name" do expect(metadata.name).to eq(nil) end it "has an empty description" do expect(metadata.description).to eq("") end it "has an empty long description" do expect(metadata.long_description).to eq("") end it "defaults to 'all rights reserved' license" do expect(metadata.license).to eq("All rights reserved") end it "has an empty maintainer field" do expect(metadata.maintainer).to eq(nil) end it "has an empty maintainer_email field" do expect(metadata.maintainer).to eq(nil) end it "has an empty platforms list" do expect(metadata.platforms).to eq(Mash.new) end it "has an empty dependencies list" do expect(metadata.dependencies).to eq(Mash.new) end it "has an empty recommends list" do expect(metadata.recommendations).to eq(Mash.new) end it "has an empty suggestions list" do expect(metadata.suggestions).to eq(Mash.new) end it "has an empty conflicts list" do expect(metadata.conflicting).to eq(Mash.new) end it "has an empty replaces list" do expect(metadata.replacing).to eq(Mash.new) end it "has an empty attributes list" do expect(metadata.attributes).to eq(Mash.new) end it "has an empty groupings list" do expect(metadata.groupings).to eq(Mash.new) end it "has an empty recipes list" do expect(metadata.recipes).to eq(Mash.new) end it "has an empty source_url string" do expect(metadata.source_url).to eq("") end it "has an empty issues_url string" do expect(metadata.issues_url).to eq("") end it "is not private" do expect(metadata.privacy).to eq(false) end end describe "validation" do context "when no required fields are set" do it "is not valid" do expect(metadata).not_to be_valid end it "has a list of validation errors" do expected_errors = ["The `name' attribute is required in cookbook metadata"] expect(metadata.errors).to eq(expected_errors) end end context "when all required fields are set" do before do metadata.name "a-valid-name" end it "is valid" do expect(metadata).to be_valid end it "has no validation errors" do expect(metadata.errors).to be_empty end end end describe "adding a supported platform" do it "should support adding a supported platform with a single expression" do metadata.supports("ubuntu", ">= 8.04") expect(metadata.platforms["ubuntu"]).to eq(">= 8.04") end end describe "meta-data attributes" do params = { :maintainer => "Adam Jacob", :maintainer_email => "adam@opscode.com", :license => "Apache v2.0", :description => "Foobar!", :long_description => "Much Longer\nSeriously", :version => "0.6.0", :source_url => "http://example.com", :issues_url => "http://example.com/issues", :privacy => true, } params.sort { |a, b| a.to_s <=> b.to_s }.each do |field, field_value| describe field do it "should be set-able via #{field}" do expect(metadata.send(field, field_value)).to eql(field_value) end it "should be get-able via #{field}" do metadata.send(field, field_value) expect(metadata.send(field)).to eql(field_value) end end end describe "version transformation" do it "should transform an '0.6' version to '0.6.0'" do expect(metadata.send(:version, "0.6")).to eql("0.6.0") end it "should spit out '0.6.0' after transforming '0.6'" do metadata.send(:version, "0.6") expect(metadata.send(:version)).to eql("0.6.0") end end end describe "describing dependencies" do dep_types = { :depends => [ :dependencies, "foo::bar", "> 0.2" ], :recommends => [ :recommendations, "foo::bar", ">= 0.2" ], :suggests => [ :suggestions, "foo::bar", "> 0.2" ], :conflicts => [ :conflicting, "foo::bar", "~> 0.2" ], :provides => [ :providing, "foo::bar", "<= 0.2" ], :replaces => [ :replacing, "foo::bar", "= 0.2.1" ], } dep_types.sort { |a, b| a.to_s <=> b.to_s }.each do |dep, dep_args| check_with = dep_args.shift describe dep do it "should be set-able via #{dep}" do expect(metadata.send(dep, *dep_args)).to eq(dep_args[1]) end it "should be get-able via #{check_with}" do metadata.send(dep, *dep_args) expect(metadata.send(check_with)).to eq({ dep_args[0] => dep_args[1] }) end end end dep_types = { :depends => [ :dependencies, "foo::bar", ">0.2", "> 0.2" ], :recommends => [ :recommendations, "foo::bar", ">=0.2", ">= 0.2" ], :suggests => [ :suggestions, "foo::bar", ">0.2", "> 0.2" ], :conflicts => [ :conflicting, "foo::bar", "~>0.2", "~> 0.2" ], :provides => [ :providing, "foo::bar", "<=0.2", "<= 0.2" ], :replaces => [ :replacing, "foo::bar", "=0.2.1", "= 0.2.1" ], } dep_types.sort { |a, b| a.to_s <=> b.to_s }.each do |dep, dep_args| check_with = dep_args.shift normalized_version = dep_args.pop describe dep do it "should be set-able and normalized via #{dep}" do expect(metadata.send(dep, *dep_args)).to eq(normalized_version) end it "should be get-able and normalized via #{check_with}" do metadata.send(dep, *dep_args) expect(metadata.send(check_with)).to eq({ dep_args[0] => normalized_version }) end end end describe "in the obsoleted format" do dep_types = { :depends => [ "foo::bar", "> 0.2", "< 1.0" ], :recommends => [ "foo::bar", ">= 0.2", "< 1.0" ], :suggests => [ "foo::bar", "> 0.2", "< 1.0" ], :conflicts => [ "foo::bar", "> 0.2", "< 1.0" ], :provides => [ "foo::bar", "> 0.2", "< 1.0" ], :replaces => [ "foo::bar", "> 0.2.1", "< 1.0" ], } dep_types.each do |dep, dep_args| it "for #{dep} raises an informative error instead of vomiting on your shoes" do expect { metadata.send(dep, *dep_args) }.to raise_error(Chef::Exceptions::ObsoleteDependencySyntax) end end end describe "with obsolete operators" do dep_types = { :depends => [ "foo::bar", ">> 0.2"], :recommends => [ "foo::bar", ">> 0.2"], :suggests => [ "foo::bar", ">> 0.2"], :conflicts => [ "foo::bar", ">> 0.2"], :provides => [ "foo::bar", ">> 0.2"], :replaces => [ "foo::bar", ">> 0.2.1"], } dep_types.each do |dep, dep_args| it "for #{dep} raises an informative error instead of vomiting on your shoes" do expect { metadata.send(dep, *dep_args) }.to raise_error(Chef::Exceptions::InvalidVersionConstraint) end end end it "strips out self-dependencies", chef: "< 13" do metadata.name("foo") expect(Chef::Log).to receive(:warn).with( "Ignoring self-dependency in cookbook foo, please remove it (in the future this will be fatal)." ) metadata.depends("foo") expect(metadata.dependencies).to eql({}) end it "errors on self-dependencies", chef: ">= 13" do metadata.name("foo") expect { metadata.depends("foo") }.to raise_error # FIXME: add the error type end end describe "chef_version" do def expect_chef_version_works(*args) ret = [] args.each do |arg| metadata.send(:chef_version, *arg) ret << Gem::Dependency.new("chef", *arg) end expect(metadata.send(:chef_versions)).to eql(ret) end it "should work with a single simple constraint" do expect_chef_version_works(["~> 12"]) end it "should work with a single complex constraint" do expect_chef_version_works([">= 12.0.1", "< 12.5.1"]) end it "should work with multiple simple constraints" do expect_chef_version_works(["~> 12.5.1"], ["~> 11.18.10"]) end it "should work with multiple complex constraints" do expect_chef_version_works([">= 11.14.2", "< 11.18.10"], [">= 12.2.1", "< 12.5.1"]) end it "should fail validation on a simple pessimistic constraint" do expect_chef_version_works(["~> 999.0"]) expect { metadata.validate_chef_version! }.to raise_error(Chef::Exceptions::CookbookChefVersionMismatch) end it "should fail validation when that valid chef versions are too big" do expect_chef_version_works([">= 999.0", "< 999.9"]) expect { metadata.validate_chef_version! }.to raise_error(Chef::Exceptions::CookbookChefVersionMismatch) end it "should fail validation when that valid chef versions are too small" do expect_chef_version_works([">= 0.0.1", "< 0.0.9"]) expect { metadata.validate_chef_version! }.to raise_error(Chef::Exceptions::CookbookChefVersionMismatch) end it "should fail validation when all ranges fail" do expect_chef_version_works([">= 999.0", "< 999.9"], [">= 0.0.1", "< 0.0.9"]) expect { metadata.validate_chef_version! }.to raise_error(Chef::Exceptions::CookbookChefVersionMismatch) end it "should pass validation when one constraint passes" do expect_chef_version_works([">= 999.0", "< 999.9"], ["= #{Chef::VERSION}"]) expect { metadata.validate_chef_version! }.not_to raise_error end end describe "ohai_version" do def expect_ohai_version_works(*args) ret = [] args.each do |arg| metadata.send(:ohai_version, *arg) ret << Gem::Dependency.new("ohai", *arg) end expect(metadata.send(:ohai_versions)).to eql(ret) end it "should work with a single simple constraint" do expect_ohai_version_works(["~> 12"]) end it "should work with a single complex constraint" do expect_ohai_version_works([">= 12.0.1", "< 12.5.1"]) end it "should work with multiple simple constraints" do expect_ohai_version_works(["~> 12.5.1"], ["~> 11.18.10"]) end it "should work with multiple complex constraints" do expect_ohai_version_works([">= 11.14.2", "< 11.18.10"], [">= 12.2.1", "< 12.5.1"]) end it "should fail validation on a simple pessimistic constraint" do expect_ohai_version_works(["~> 999.0"]) expect { metadata.validate_ohai_version! }.to raise_error(Chef::Exceptions::CookbookOhaiVersionMismatch) end it "should fail validation when that valid chef versions are too big" do expect_ohai_version_works([">= 999.0", "< 999.9"]) expect { metadata.validate_ohai_version! }.to raise_error(Chef::Exceptions::CookbookOhaiVersionMismatch) end it "should fail validation when that valid chef versions are too small" do expect_ohai_version_works([">= 0.0.1", "< 0.0.9"]) expect { metadata.validate_ohai_version! }.to raise_error(Chef::Exceptions::CookbookOhaiVersionMismatch) end it "should fail validation when all ranges fail" do expect_ohai_version_works([">= 999.0", "< 999.9"], [">= 0.0.1", "< 0.0.9"]) expect { metadata.validate_ohai_version! }.to raise_error(Chef::Exceptions::CookbookOhaiVersionMismatch) end it "should pass validation when one constraint passes" do expect_ohai_version_works([">= 999.0", "< 999.9"], ["= #{Ohai::VERSION}"]) expect { metadata.validate_ohai_version! }.not_to raise_error end end describe "gem" do def expect_gem_works(*args) ret = [] args.each do |arg| metadata.send(:gem, *arg) ret << arg end expect(metadata.send(:gems)).to eql(ret) end it "works on a simple case" do expect_gem_works(["foo", "~> 1.2"]) end it "works if there's two gems" do expect_gem_works(["foo", "~> 1.2"], ["bar", "~> 2.0"]) end it "works if there's a more complicated constraint" do expect_gem_works(["foo", "~> 1.2"], ["bar", ">= 2.4", "< 4.0"]) end end describe "attribute groupings" do it "should allow you set a grouping" do group = { "title" => "MySQL Tuning", "description" => "Setting from the my.cnf file that allow you to tune your mysql server", } expect(metadata.grouping("/db/mysql/databases/tuning", group)).to eq(group) end it "should not accept anything but a string for display_name" do expect do metadata.grouping("db/mysql/databases", :title => "foo") end.not_to raise_error expect do metadata.grouping("db/mysql/databases", :title => Hash.new) end.to raise_error(ArgumentError) end it "should not accept anything but a string for the description" do expect do metadata.grouping("db/mysql/databases", :description => "foo") end.not_to raise_error expect do metadata.grouping("db/mysql/databases", :description => Hash.new) end.to raise_error(ArgumentError) end end describe "cookbook attributes" do it "should allow you set an attributes metadata" do attrs = { "display_name" => "MySQL Databases", "description" => "Description of MySQL", "choice" => %w{dedicated shared}, "calculated" => false, "type" => "string", "required" => "recommended", "recipes" => [ "mysql::server", "mysql::master" ], "default" => [ ], "source_url" => "http://example.com", "issues_url" => "http://example.com/issues", "privacy" => true, } expect(metadata.attribute("/db/mysql/databases", attrs)).to eq(attrs) end it "should not accept anything but a string for display_name" do expect do metadata.attribute("db/mysql/databases", :display_name => "foo") end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :display_name => Hash.new) end.to raise_error(ArgumentError) end it "should not accept anything but a string for the description" do expect do metadata.attribute("db/mysql/databases", :description => "foo") end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :description => Hash.new) end.to raise_error(ArgumentError) end it "should not accept anything but a string for the source_url" do expect do metadata.attribute("db/mysql/databases", :source_url => "foo") end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :source_url => Hash.new) end.to raise_error(ArgumentError) end it "should not accept anything but a string for the issues_url" do expect do metadata.attribute("db/mysql/databases", :issues_url => "foo") end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :issues_url => Hash.new) end.to raise_error(ArgumentError) end it "should not accept anything but true or false for the privacy flag" do expect do metadata.attribute("db/mysql/databases", :privacy => true) end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :privacy => false) end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :privacy => "true") end.to raise_error(ArgumentError) end it "should not accept anything but an array of strings for choice" do expect do metadata.attribute("db/mysql/databases", :choice => %w{dedicated shared}) end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :choice => [10, "shared"]) end.to raise_error(ArgumentError) expect do metadata.attribute("db/mysql/databases", :choice => Hash.new) end.to raise_error(ArgumentError) end it "should set choice to empty array by default" do metadata.attribute("db/mysql/databases", {}) expect(metadata.attributes["db/mysql/databases"][:choice]).to eq([]) end it "should let calculated be true or false" do expect do metadata.attribute("db/mysql/databases", :calculated => true) end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :calculated => false) end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :calculated => Hash.new) end.to raise_error(ArgumentError) end it "should set calculated to false by default" do metadata.attribute("db/mysql/databases", {}) expect(metadata.attributes["db/mysql/databases"][:calculated]).to eq(false) end it "accepts String for the attribute type" do expect do metadata.attribute("db/mysql/databases", :type => "string") end.not_to raise_error end it "accepts Array for the attribute type" do expect do metadata.attribute("db/mysql/databases", :type => "array") end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :type => Array.new) end.to raise_error(ArgumentError) end it "accepts symbol for the attribute type" do expect do metadata.attribute("db/mysql/databases", :type => "symbol") end.not_to raise_error end it "should let type be hash (backwards compatibility only)" do expect do metadata.attribute("db/mysql/databases", :type => "hash") end.not_to raise_error end it "should let required be required, recommended or optional" do expect do metadata.attribute("db/mysql/databases", :required => "required") end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :required => "recommended") end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :required => "optional") end.not_to raise_error end it "should convert required true to required" do expect do metadata.attribute("db/mysql/databases", :required => true) end.not_to raise_error #attrib = metadata.attributes["db/mysql/databases"][:required].should == "required" end it "should convert required false to optional" do expect do metadata.attribute("db/mysql/databases", :required => false) end.not_to raise_error #attrib = metadata.attributes["db/mysql/databases"][:required].should == "optional" end it "should set required to 'optional' by default" do metadata.attribute("db/mysql/databases", {}) expect(metadata.attributes["db/mysql/databases"][:required]).to eq("optional") end it "should make sure recipes is an array" do expect do metadata.attribute("db/mysql/databases", :recipes => []) end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :required => Hash.new) end.to raise_error(ArgumentError) end it "should set recipes to an empty array by default" do metadata.attribute("db/mysql/databases", {}) expect(metadata.attributes["db/mysql/databases"][:recipes]).to eq([]) end it "should allow the default value to be a string, array, hash, boolean or numeric" do expect do metadata.attribute("db/mysql/databases", :default => []) end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :default => {}) end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :default => "alice in chains") end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :default => 1337) end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :default => true) end.not_to raise_error expect do metadata.attribute("db/mysql/databases", :required => :not_gonna_do_it) end.to raise_error(ArgumentError) end it "should limit the types allowed in the choice array" do options = { :type => "string", :choice => %w{test1 test2}, :default => "test1", } expect do metadata.attribute("test_cookbook/test", options) end.not_to raise_error options = { :type => "boolean", :choice => [ true, false ], :default => true, } expect do metadata.attribute("test_cookbook/test", options) end.not_to raise_error options = { :type => "numeric", :choice => [ 1337, 420 ], :default => 1337, } expect do metadata.attribute("test_cookbook/test", options) end.not_to raise_error options = { :type => "numeric", :choice => [ true, "false" ], :default => false, } expect do metadata.attribute("test_cookbook/test", options) end.to raise_error(Chef::Exceptions::ValidationFailed) end it "should error if default used with calculated" do expect do attrs = { :calculated => true, :default => [ "I thought you said calculated" ], } metadata.attribute("db/mysql/databases", attrs) end.to raise_error(ArgumentError) expect do attrs = { :calculated => true, :default => "I thought you said calculated", } metadata.attribute("db/mysql/databases", attrs) end.to raise_error(ArgumentError) end it "should allow a default that is a choice" do expect do attrs = { :choice => %w{a b c}, :default => "b", } metadata.attribute("db/mysql/databases", attrs) end.not_to raise_error expect do attrs = { :choice => %w{a b c d e}, :default => %w{b d}, } metadata.attribute("db/mysql/databases", attrs) end.not_to raise_error end it "should error if default is not a choice" do expect do attrs = { :choice => %w{a b c}, :default => "d", } metadata.attribute("db/mysql/databases", attrs) end.to raise_error(ArgumentError) expect do attrs = { :choice => %w{a b c d e}, :default => %w{b z}, } metadata.attribute("db/mysql/databases", attrs) end.to raise_error(ArgumentError) end end describe "recipes" do let(:cookbook) do c = Chef::CookbookVersion.new("test_cookbook") c.recipe_files = [ "default.rb", "enlighten.rb" ] c end before(:each) do metadata.name("test_cookbook") metadata.recipes_from_cookbook_version(cookbook) end it "should have the names of the recipes" do expect(metadata.recipes["test_cookbook"]).to eq("") expect(metadata.recipes["test_cookbook::enlighten"]).to eq("") end it "should let you set the description for a recipe" do metadata.recipe "test_cookbook", "It, um... tests stuff?" expect(metadata.recipes["test_cookbook"]).to eq("It, um... tests stuff?") end it "should automatically provide each recipe" do expect(metadata.providing.has_key?("test_cookbook")).to eq(true) expect(metadata.providing.has_key?("test_cookbook::enlighten")).to eq(true) end end describe "json" do before(:each) do metadata.version "1.0" metadata.maintainer "Bobo T. Clown" metadata.maintainer_email "bobo@example.com" metadata.long_description "I have a long arm!" metadata.supports :ubuntu, "> 8.04" metadata.depends "bobo", "= 1.0" metadata.depends "bubu", "=1.0" metadata.depends "bobotclown", "= 1.1" metadata.recommends "snark", "< 3.0" metadata.suggests "kindness", "> 2.0" metadata.conflicts "hatred" metadata.provides "foo(:bar, :baz)" metadata.replaces "snarkitron" metadata.recipe "test_cookbook::enlighten", "is your buddy" metadata.attribute "bizspark/has_login", :display_name => "You have nothing" metadata.version "1.2.3" metadata.gem "foo", "~> 1.2" metadata.gem "bar", ">= 2.2", "< 4.0" metadata.chef_version ">= 11.14.2", "< 11.18.10" metadata.chef_version ">= 12.2.1", "< 12.5.1" metadata.ohai_version ">= 7.1.0", "< 7.5.0" metadata.ohai_version ">= 8.0.1", "< 8.6.0" end it "should produce the same output from to_json and Chef::JSONCompat" do # XXX: fairly certain this is testing ruby method dispatch expect(metadata.to_json).to eq(Chef::JSONCompat.to_json(metadata)) end describe "serialize" do let(:deserialized_metadata) { Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(metadata)) } it "should serialize to a json hash" do expect(deserialized_metadata).to be_a_kind_of(Hash) end %w{ name description long_description maintainer maintainer_email license platforms dependencies suggestions recommendations conflicting providing replacing attributes recipes version source_url issues_url privacy gems }.each do |t| it "should include '#{t}'" do expect(deserialized_metadata[t]).to eq(metadata.send(t.to_sym)) end end %w{ ohai_versions chef_versions }.each do |t| it "should include '#{t}'" do expect(deserialized_metadata[t]).to eq(metadata.gem_requirements_to_array(*metadata.send(t.to_sym))) end end end describe "deserialize" do let(:deserialized_metadata) { Chef::Cookbook::Metadata.from_json(Chef::JSONCompat.to_json(metadata)) } it "should deserialize to a Chef::Cookbook::Metadata object" do expect(deserialized_metadata).to be_a_kind_of(Chef::Cookbook::Metadata) end %w{ name description long_description maintainer maintainer_email license platforms dependencies suggestions recommendations conflicting providing replacing attributes recipes version source_url issues_url privacy chef_versions ohai_versions gems }.each do |t| it "should match '#{t}'" do expect(deserialized_metadata.send(t.to_sym)).to eq(metadata.send(t.to_sym)) end end end describe "from_hash" do before(:each) do @hash = metadata.to_hash end [:dependencies, :recommendations, :suggestions, :conflicting, :replacing].each do |to_check| it "should transform deprecated greater than syntax for :#{to_check}" do @hash[to_check.to_s]["foo::bar"] = ">> 0.2" deserial = Chef::Cookbook::Metadata.from_hash(@hash) expect(deserial.send(to_check)["foo::bar"]).to eq("> 0.2") end it "should transform deprecated less than syntax for :#{to_check}" do @hash[to_check.to_s]["foo::bar"] = "<< 0.2" deserial = Chef::Cookbook::Metadata.from_hash(@hash) expect(deserial.send(to_check)["foo::bar"]).to eq("< 0.2") end it "should ignore multiple dependency constraints for :#{to_check}" do @hash[to_check.to_s]["foo::bar"] = [ ">= 1.0", "<= 5.2" ] deserial = Chef::Cookbook::Metadata.from_hash(@hash) expect(deserial.send(to_check)["foo::bar"]).to eq([]) end it "should accept an empty array of dependency constraints for :#{to_check}" do @hash[to_check.to_s]["foo::bar"] = [] deserial = Chef::Cookbook::Metadata.from_hash(@hash) expect(deserial.send(to_check)["foo::bar"]).to eq([]) end it "should accept single-element arrays of dependency constraints for :#{to_check}" do @hash[to_check.to_s]["foo::bar"] = [ ">= 2.0" ] deserial = Chef::Cookbook::Metadata.from_hash(@hash) expect(deserial.send(to_check)["foo::bar"]).to eq(">= 2.0") end end end end end chef-12.14.60/spec/unit/cookbook/synchronizer_spec.rb000066400000000000000000000464741276456504500225220ustar00rootroot00000000000000require "spec_helper" require "chef/cookbook/synchronizer" require "chef/cookbook_version" describe Chef::CookbookCacheCleaner do describe "when cleaning up unused cookbook components" do let(:cleaner) do cleaner = Chef::CookbookCacheCleaner.instance cleaner.reset! cleaner end let(:file_cache) { double("Chef::FileCache with files from unused cookbooks") } let(:unused_template_files) do %w{ cookbooks/unused/templates/default/foo.conf.erb cookbooks/unused/tempaltes/default/bar.conf.erb } end let(:valid_cached_cb_files) do %w{ cookbooks/valid1/recipes/default.rb cookbooks/valid2/recipes/default.rb } end before do valid_cached_cb_files.each do |cbf| cleaner.mark_file_as_valid(cbf) end end it "removes all files not validated during the chef run" do expect(file_cache).to receive(:find).with(File.join(%w{cookbooks ** {*,.*}})).and_return(valid_cached_cb_files + unused_template_files) unused_template_files.each do |cbf| expect(file_cache).to receive(:delete).with(cbf) end allow(cleaner).to receive(:cache).and_return(file_cache) cleaner.cleanup_file_cache end it "does not remove anything when skip_removal is true" do cleaner.skip_removal = true allow(cleaner.cache).to receive(:find).and_return(%w{cookbooks/valid1/recipes/default.rb cookbooks/valid2/recipes/default.rb}) expect(cleaner.cache).not_to receive(:delete) cleaner.cleanup_file_cache end it "does not remove anything on chef-solo" do Chef::Config[:solo_legacy_mode] = true allow(cleaner.cache).to receive(:find).and_return(%w{cookbooks/valid1/recipes/default.rb cookbooks/valid2/recipes/default.rb}) expect(cleaner.cache).not_to receive(:delete) cleaner.cleanup_file_cache end end end describe Chef::CookbookSynchronizer do let(:cookbook_a_default_recipe) do { "path" => "recipes/default.rb", "url" => "http://chef.example.com/abc123", "checksum" => "abc123", } end let(:cookbook_a_default_attrs) do { "path" => "attributes/default.rb", "url" => "http://chef.example.com/abc456", "checksum" => "abc456", } end let(:cookbook_a_template) do { "path" => "templates/default/apache2.conf.erb", "url" => "http://chef.example.com/ffffff", "checksum" => "abc125", } end let(:cookbook_a_file) do { "path" => "files/default/megaman.conf", "url" => "http://chef.example.com/megaman.conf", "checksum" => "abc124", } end let(:cookbook_a_manifest) do segments = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ] cookbook_a_manifest = segments.inject({}) { |h, segment| h[segment.to_s] = []; h } cookbook_a_manifest["recipes"] = [ cookbook_a_default_recipe ] cookbook_a_manifest["attributes"] = [ cookbook_a_default_attrs ] cookbook_a_manifest["templates"] = [ cookbook_a_template ] cookbook_a_manifest["files"] = [ cookbook_a_file ] cookbook_a_manifest end let(:cookbook_a) do cookbook_a = Chef::CookbookVersion.new("cookbook_a") cookbook_a.manifest = cookbook_a_manifest cookbook_a end let(:cookbook_manifest) do { "cookbook_a" => cookbook_a, } end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:no_lazy_load) { true } let(:synchronizer) do Chef::Config[:no_lazy_load] = no_lazy_load Chef::CookbookSynchronizer.new(cookbook_manifest, events) end it "lists the cookbook names" do expect(synchronizer.cookbook_names).to eq(%w{cookbook_a}) end it "lists the cookbook manifests" do expect(synchronizer.cookbooks).to eq([cookbook_a]) end context "#clear_obsoleted_cookbooks" do after do # Singletons == Global State == Bad Chef::CookbookCacheCleaner.instance.skip_removal = nil end it "behaves correctly when remove_obsoleted_files is false" do synchronizer.remove_obsoleted_files = false expect(synchronizer).not_to receive(:remove_old_cookbooks) expect(synchronizer).to receive(:remove_deleted_files) synchronizer.clear_obsoleted_cookbooks expect(Chef::CookbookCacheCleaner.instance.skip_removal).to be true end it "behaves correctly when remove_obsoleted_files is true" do synchronizer.remove_obsoleted_files = true expect(synchronizer).to receive(:remove_old_cookbooks) expect(synchronizer).to receive(:remove_deleted_files) synchronizer.clear_obsoleted_cookbooks expect(Chef::CookbookCacheCleaner.instance.skip_removal).to be nil end end context "#remove_old_cookbooks" do let(:file_cache) { double("Chef::FileCache with files from unused cookbooks") } let(:cookbook_manifest) do { "valid1" => {}, "valid2" => {} } end it "removes unneeded cookbooks" do valid_cached_cb_files = %w{cookbooks/valid1/recipes/default.rb cookbooks/valid2/recipes/default.rb} obsolete_cb_files = %w{cookbooks/old1/recipes/default.rb cookbooks/old2/recipes/default.rb} expect(file_cache).to receive(:find).with(File.join(%w{cookbooks ** {*,.*}})).and_return(valid_cached_cb_files + obsolete_cb_files) expect(file_cache).to receive(:delete).with("cookbooks/old1/recipes/default.rb") expect(file_cache).to receive(:delete).with("cookbooks/old2/recipes/default.rb") allow(synchronizer).to receive(:cache).and_return(file_cache) synchronizer.remove_old_cookbooks end end context "#remove_deleted_files" do let(:file_cache) { double("Chef::FileCache with files from unused cookbooks") } let(:cookbook_manifest) do { "valid1" => {}, "valid2" => {} } end it "removes only deleted files" do valid_cached_cb_files = %w{cookbooks/valid1/recipes/default.rb cookbooks/valid2/recipes/default.rb} obsolete_cb_files = %w{cookbooks/valid1/recipes/deleted.rb cookbooks/valid2/recipes/deleted.rb} expect(file_cache).to receive(:find).with(File.join(%w{cookbooks ** {*,.*}})).and_return(valid_cached_cb_files + obsolete_cb_files) # valid1 is a cookbook in our run_list expect(synchronizer).to receive(:have_cookbook?).with("valid1").at_least(:once).and_return(true) # valid2 is a cookbook not in our run_list (we're simulating an override run_list where valid2 needs to be preserved) expect(synchronizer).to receive(:have_cookbook?).with("valid2").at_least(:once).and_return(false) expect(file_cache).to receive(:delete).with("cookbooks/valid1/recipes/deleted.rb") expect(synchronizer).to receive(:cookbook_segment).with("valid1", "recipes").at_least(:once).and_return([ { "path" => "recipes/default.rb" }]) allow(synchronizer).to receive(:cache).and_return(file_cache) synchronizer.remove_deleted_files end end let(:cookbook_a_default_recipe_tempfile) do double("Tempfile for cookbook_a default.rb recipe", :path => "/tmp/cookbook_a_recipes_default_rb") end let(:cookbook_a_default_attribute_tempfile) do double("Tempfile for cookbook_a default.rb attr file", :path => "/tmp/cookbook_a_attributes_default_rb") end let(:cookbook_a_file_default_tempfile) do double("Tempfile for cookbook_a megaman.conf file", :path => "/tmp/cookbook_a_file_default_tempfile") end let(:cookbook_a_template_default_tempfile) do double("Tempfile for cookbook_a apache.conf.erb template", :path => "/tmp/cookbook_a_template_default_tempfile") end def setup_common_files_missing_expectations # Files are not in the cache: expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/recipes/default.rb"). and_return(false) expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/attributes/default.rb"). and_return(false) # Fetch and copy default.rb recipe expect(server_api).to receive(:streaming_request). with("http://chef.example.com/abc123"). and_return(cookbook_a_default_recipe_tempfile) expect(file_cache).to receive(:move_to). with("/tmp/cookbook_a_recipes_default_rb", "cookbooks/cookbook_a/recipes/default.rb") expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/recipes/default.rb", false). and_return("/file-cache/cookbooks/cookbook_a/recipes/default.rb") # Fetch and copy default.rb attribute file expect(server_api).to receive(:streaming_request). with("http://chef.example.com/abc456"). and_return(cookbook_a_default_attribute_tempfile) expect(file_cache).to receive(:move_to). with("/tmp/cookbook_a_attributes_default_rb", "cookbooks/cookbook_a/attributes/default.rb") expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/attributes/default.rb", false). and_return("/file-cache/cookbooks/cookbook_a/attributes/default.rb") end def setup_no_lazy_files_and_templates_missing_expectations expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/files/default/megaman.conf"). and_return(false) expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/templates/default/apache2.conf.erb"). and_return(false) expect(server_api).to receive(:streaming_request). with("http://chef.example.com/megaman.conf"). and_return(cookbook_a_file_default_tempfile) expect(file_cache).to receive(:move_to). with("/tmp/cookbook_a_file_default_tempfile", "cookbooks/cookbook_a/files/default/megaman.conf") expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/files/default/megaman.conf", false). and_return("/file-cache/cookbooks/cookbook_a/default/megaman.conf") expect(server_api).to receive(:streaming_request). with("http://chef.example.com/ffffff"). and_return(cookbook_a_template_default_tempfile) expect(file_cache).to receive(:move_to). with("/tmp/cookbook_a_template_default_tempfile", "cookbooks/cookbook_a/templates/default/apache2.conf.erb") expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/templates/default/apache2.conf.erb", false). and_return("/file-cache/cookbooks/cookbook_a/templates/default/apache2.conf.erb") end def setup_common_files_chksum_mismatch_expectations # Files are in the cache: expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/recipes/default.rb"). and_return(true) expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/attributes/default.rb"). and_return(true) # Fetch and copy default.rb recipe expect(server_api).to receive(:streaming_request). with("http://chef.example.com/abc123"). and_return(cookbook_a_default_recipe_tempfile) expect(file_cache).to receive(:move_to). with("/tmp/cookbook_a_recipes_default_rb", "cookbooks/cookbook_a/recipes/default.rb") expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/recipes/default.rb", false). twice. and_return("/file-cache/cookbooks/cookbook_a/recipes/default.rb") # Current file has fff000, want abc123 expect(Chef::CookbookVersion).to receive(:checksum_cookbook_file). with("/file-cache/cookbooks/cookbook_a/recipes/default.rb"). and_return("fff000") # Fetch and copy default.rb attribute file expect(server_api).to receive(:streaming_request). with("http://chef.example.com/abc456"). and_return(cookbook_a_default_attribute_tempfile) expect(file_cache).to receive(:move_to). with("/tmp/cookbook_a_attributes_default_rb", "cookbooks/cookbook_a/attributes/default.rb") expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/attributes/default.rb", false). twice. and_return("/file-cache/cookbooks/cookbook_a/attributes/default.rb") # Current file has fff000, want abc456 expect(Chef::CookbookVersion).to receive(:checksum_cookbook_file). with("/file-cache/cookbooks/cookbook_a/attributes/default.rb"). and_return("fff000") end def setup_no_lazy_files_and_templates_chksum_mismatch_expectations # Files are in the cache: expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/files/default/megaman.conf"). and_return(true) expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/templates/default/apache2.conf.erb"). and_return(true) # Fetch and copy megaman.conf expect(server_api).to receive(:streaming_request). with("http://chef.example.com/megaman.conf"). and_return(cookbook_a_file_default_tempfile) expect(file_cache).to receive(:move_to). with("/tmp/cookbook_a_file_default_tempfile", "cookbooks/cookbook_a/files/default/megaman.conf") expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/files/default/megaman.conf", false). twice. and_return("/file-cache/cookbooks/cookbook_a/default/megaman.conf") # Fetch and copy apache2.conf template expect(server_api).to receive(:streaming_request). with("http://chef.example.com/ffffff"). and_return(cookbook_a_template_default_tempfile) expect(file_cache).to receive(:move_to). with("/tmp/cookbook_a_template_default_tempfile", "cookbooks/cookbook_a/templates/default/apache2.conf.erb") expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/templates/default/apache2.conf.erb", false). twice. and_return("/file-cache/cookbooks/cookbook_a/templates/default/apache2.conf.erb") # Current file has fff000 expect(Chef::CookbookVersion).to receive(:checksum_cookbook_file). with("/file-cache/cookbooks/cookbook_a/default/megaman.conf"). and_return("fff000") # Current file has fff000 expect(Chef::CookbookVersion).to receive(:checksum_cookbook_file). with("/file-cache/cookbooks/cookbook_a/templates/default/apache2.conf.erb"). and_return("fff000") end def setup_common_files_present_expectations # Files are in the cache: expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/recipes/default.rb"). and_return(true) expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/attributes/default.rb"). and_return(true) # Current file has abc123, want abc123 expect(Chef::CookbookVersion).to receive(:checksum_cookbook_file). with("/file-cache/cookbooks/cookbook_a/recipes/default.rb"). and_return("abc123") # Current file has abc456, want abc456 expect(Chef::CookbookVersion).to receive(:checksum_cookbook_file). with("/file-cache/cookbooks/cookbook_a/attributes/default.rb"). and_return("abc456") # :load called twice expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/recipes/default.rb", false). twice. and_return("/file-cache/cookbooks/cookbook_a/recipes/default.rb") expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/attributes/default.rb", false). twice. and_return("/file-cache/cookbooks/cookbook_a/attributes/default.rb") end def setup_no_lazy_files_and_templates_present_expectations # Files are in the cache: expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/files/default/megaman.conf"). and_return(true) expect(file_cache).to receive(:has_key?). with("cookbooks/cookbook_a/templates/default/apache2.conf.erb"). and_return(true) # Current file has abc124, want abc124 expect(Chef::CookbookVersion).to receive(:checksum_cookbook_file). with("/file-cache/cookbooks/cookbook_a/default/megaman.conf"). and_return("abc124") # Current file has abc125, want abc125 expect(Chef::CookbookVersion).to receive(:checksum_cookbook_file). with("/file-cache/cookbooks/cookbook_a/templates/default/apache2.conf.erb"). and_return("abc125") # :load called twice expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/files/default/megaman.conf", false). twice. and_return("/file-cache/cookbooks/cookbook_a/default/megaman.conf") expect(file_cache).to receive(:load). with("cookbooks/cookbook_a/templates/default/apache2.conf.erb", false). twice. and_return("/file-cache/cookbooks/cookbook_a/templates/default/apache2.conf.erb") end describe "#server_api" do it "sets keepalive to true" do expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url], keepalives: true) synchronizer.server_api end end describe "when syncing cookbooks with the server" do let(:server_api) { double("Chef::ServerAPI (mock)") } let(:file_cache) { double("Chef::FileCache (mock)") } before do # Would rather not stub out methods on the test subject, but setting up # the state is a PITA and tests for this behavior are above. allow(synchronizer).to receive(:clear_obsoleted_cookbooks) allow(synchronizer).to receive(:server_api).and_return(server_api) allow(synchronizer).to receive(:cache).and_return(file_cache) end context "when the cache does not contain the desired files" do before do setup_common_files_missing_expectations end context "Chef::Config[:no_lazy_load] is false" do let(:no_lazy_load) { false } it "fetches eagerly loaded files" do synchronizer.sync_cookbooks end it "does not fetch templates or cookbook files" do # Implicitly tested in previous test; this test is just for behavior specification. expect(server_api).not_to receive(:streaming_request). with("http://chef.example.com/ffffff") synchronizer.sync_cookbooks end end context "Chef::Config[:no_lazy_load] is true" do let(:no_lazy_load) { true } before do setup_no_lazy_files_and_templates_missing_expectations end it "fetches templates and cookbook files" do synchronizer.sync_cookbooks end end end context "when the cache contains outdated files" do before do setup_common_files_chksum_mismatch_expectations end context "Chef::Config[:no_lazy_load] is true" do let(:no_lazy_load) { true } before do setup_no_lazy_files_and_templates_chksum_mismatch_expectations end it "updates the outdated files" do synchronizer.sync_cookbooks end end context "Chef::Config[:no_lazy_load] is false" do let(:no_lazy_load) { false } it "updates the outdated files" do synchronizer.sync_cookbooks end end end context "when the cache is up to date" do before do setup_common_files_present_expectations end context "Chef::Config[:no_lazy_load] is true" do let(:no_lazy_load) { true } before do setup_no_lazy_files_and_templates_present_expectations end it "does not update files" do expect(file_cache).not_to receive(:move_to) expect(server_api).not_to receive(:streaming_request) synchronizer.sync_cookbooks end end context "Chef::Config[:no_lazy_load] is false" do let(:no_lazy_load) { false } it "does not update files" do expect(file_cache).not_to receive(:move_to) expect(server_api).not_to receive(:streaming_request) synchronizer.sync_cookbooks end end end end end chef-12.14.60/spec/unit/cookbook/syntax_check_spec.rb000066400000000000000000000210201276456504500224240ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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/cookbook/syntax_check" describe Chef::Cookbook::SyntaxCheck do before do allow(ChefConfig).to receive(:windows?) { false } end let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "cookbooks", "openldap") } let(:syntax_check) { Chef::Cookbook::SyntaxCheck.new(cookbook_path) } let(:open_ldap_cookbook_files) do %w{ attributes/default.rb attributes/smokey.rb definitions/client.rb definitions/server.rb libraries/openldap.rb libraries/openldap/version.rb metadata.rb recipes/default.rb recipes/gigantor.rb recipes/one.rb recipes/return.rb spec/spec_helper.rb }.map { |f| File.join(cookbook_path, f) } end before do Chef::Log.logger = Logger.new(StringIO.new) @original_log_level = Chef::Log.level Chef::Log.level = :warn # suppress "Syntax OK" messages @attr_files = %w{default.rb smokey.rb}.map { |f| File.join(cookbook_path, "attributes", f) } @libr_files = %w{openldap.rb openldap/version.rb}.map { |f| File.join(cookbook_path, "libraries", f) } @defn_files = %w{client.rb server.rb}.map { |f| File.join(cookbook_path, "definitions", f) } @recipes = %w{default.rb gigantor.rb one.rb return.rb}.map { |f| File.join(cookbook_path, "recipes", f) } @spec_files = [ File.join(cookbook_path, "spec", "spec_helper.rb") ] @ruby_files = @attr_files + @libr_files + @defn_files + @recipes + @spec_files + [File.join(cookbook_path, "metadata.rb")] basenames = %w{ helpers_via_partial_test.erb helper_test.erb helpers.erb openldap_stuff.conf.erb nested_openldap_partials.erb nested_partial.erb openldap_variable_stuff.conf.erb test.erb some_windows_line_endings.erb all_windows_line_endings.erb no_windows_line_endings.erb } @template_files = basenames.map { |f| File.join(cookbook_path, "templates", "default", f) } end after do Chef::Log.level = @original_log_level end it "creates a syntax checker given the cookbook name when Chef::Config.cookbook_path is set" do Chef::Config[:cookbook_path] = File.dirname(cookbook_path) syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:openldap) expect(syntax_check.cookbook_path).to eq(cookbook_path) expect(syntax_check.ruby_files.sort).to eq(open_ldap_cookbook_files.sort) end it "creates a syntax checker given the cookbook name and cookbook_path" do syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:openldap, File.join(CHEF_SPEC_DATA, "cookbooks")) expect(syntax_check.cookbook_path).to eq(cookbook_path) expect(syntax_check.ruby_files.sort).to eq(open_ldap_cookbook_files.sort) end context "when using a standalone cookbook" do let(:cookbook_path) { File.join(CHEF_SPEC_DATA, "standalone_cookbook") } it "creates a syntax checker given the cookbook name and cookbook_path for a standalone cookbook" do syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:standalone_cookbook, CHEF_SPEC_DATA) expect(syntax_check.cookbook_path).to eq(cookbook_path) expect(syntax_check.ruby_files).to eq([File.join(cookbook_path, "recipes/default.rb")]) end end describe "when first created" do it "has the path to the cookbook to syntax check" do expect(syntax_check.cookbook_path).to eq(cookbook_path) end it "lists the ruby files in the cookbook" do expect(syntax_check.ruby_files.sort).to eq(@ruby_files.sort) end it "lists the erb templates in the cookbook" do expect(syntax_check.template_files.sort).to eq(@template_files.sort) end end describe "when validating cookbooks" do let(:cache_path) { Dir.mktmpdir } before do Chef::Config[:syntax_check_cache_path] = cache_path end after do FileUtils.rm_rf(cache_path) if File.exist?(cache_path) end describe "and the files have not been syntax checked previously" do it "shows that all ruby files require a syntax check" do expect(syntax_check.untested_ruby_files.sort).to eq(@ruby_files.sort) end it "shows that all template files require a syntax check" do expect(syntax_check.untested_template_files.sort).to eq(@template_files.sort) end it "removes a ruby file from the list of untested files after it is marked as validated" do recipe = File.join(cookbook_path, "recipes", "default.rb") syntax_check.validated(recipe) expect(syntax_check.untested_ruby_files).not_to include(recipe) end it "removes a template file from the list of untested files after it is marked as validated" do template = File.join(cookbook_path, "templates", "default", "test.erb") syntax_check.validated(template) expect(syntax_check.untested_template_files).not_to include(template) end it "validates all ruby files" do expect(syntax_check.validate_ruby_files).to be_truthy expect(syntax_check.untested_ruby_files).to be_empty end it "validates all templates" do expect(syntax_check.validate_templates).to be_truthy expect(syntax_check.untested_template_files).to be_empty end describe "and a file has a syntax error" do before do cookbook_path = File.join(CHEF_SPEC_DATA, "cookbooks", "borken") syntax_check.cookbook_path.replace(cookbook_path) end it "it indicates that a ruby file has a syntax error" do expect(syntax_check.validate_ruby_files).to be_falsey end it "does not remove the invalid file from the list of untested files" do expect(syntax_check.untested_ruby_files).to include(File.join(cookbook_path, "recipes", "default.rb")) syntax_check.validate_ruby_files expect(syntax_check.untested_ruby_files).to include(File.join(cookbook_path, "recipes", "default.rb")) end it "indicates that a template file has a syntax error" do expect(syntax_check.validate_templates).to be_falsey end it "does not remove the invalid template from the list of untested templates" do expect(syntax_check.untested_template_files).to include(File.join(cookbook_path, "templates", "default", "borken.erb")) expect { syntax_check.validate_templates }.not_to change(syntax_check, :untested_template_files) end end describe "and an ignored file has a syntax error" do before do cookbook_path = File.join(CHEF_SPEC_DATA, "cookbooks", "ignorken") Chef::Config[:cookbook_path] = File.dirname(cookbook_path) syntax_check.cookbook_path.replace(cookbook_path) @ruby_files = [File.join(cookbook_path, "metadata.rb"), File.join(cookbook_path, "recipes/default.rb")] end it "shows that ignored ruby files do not require a syntax check" do expect(syntax_check.untested_ruby_files.sort).to eq(@ruby_files.sort) end it "does not indicate that a ruby file has a syntax error" do expect(syntax_check.validate_ruby_files).to be_truthy expect(syntax_check.untested_ruby_files).to be_empty end end end describe "and the files have been syntax checked previously" do before do syntax_check.untested_ruby_files.each { |f| syntax_check.validated(f) } syntax_check.untested_template_files.each { |f| syntax_check.validated(f) } end it "does not syntax check ruby files" do expect(syntax_check).not_to receive(:shell_out) expect(syntax_check.validate_ruby_files).to be_truthy end it "does not syntax check templates" do expect(syntax_check).not_to receive(:shell_out) expect(syntax_check.validate_templates).to be_truthy end end end end chef-12.14.60/spec/unit/cookbook_loader_spec.rb000066400000000000000000000250551276456504500213030ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::CookbookLoader do before do allow(ChefConfig).to receive(:windows?) { false } end let(:repo_paths) do [ File.expand_path(File.join(CHEF_SPEC_DATA, "kitchen")), File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")), ] end let(:cookbook_loader) { Chef::CookbookLoader.new(repo_paths) } it "checks each directory only once when loading (CHEF-3487)" do cookbook_paths = [] repo_paths.each do |repo_path| cookbook_paths |= Dir[File.join(repo_path, "*")] end cookbook_paths.delete_if { |path| File.basename(path) == "chefignore" } cookbook_paths.each do |cookbook_path| expect(Chef::Cookbook::CookbookVersionLoader).to receive(:new). with(cookbook_path, anything). once. and_call_original end expect(Chef::Log).to receive(:deprecation).with(/The cookbook\(s\): openldap exist in multiple places in your cookbook_path./) cookbook_loader.load_cookbooks end context "after loading all cookbooks" do before(:each) do expect(Chef::Log).to receive(:deprecation).with(/The cookbook\(s\): openldap exist in multiple places in your cookbook_path./) cookbook_loader.load_cookbooks end it "should be possible to reload all the cookbooks without triggering deprecation warnings on all of them" do start_merged_cookbooks = cookbook_loader.merged_cookbooks expect(Chef::Log).to receive(:deprecation).with(/The cookbook\(s\): openldap exist in multiple places in your cookbook_path./) cookbook_loader.load_cookbooks expect(cookbook_loader.merged_cookbooks).to eql(start_merged_cookbooks) end describe "[]" do it "should return cookbook objects with []" do expect(cookbook_loader[:openldap]).to be_a_kind_of(Chef::CookbookVersion) end it "should raise an exception if it cannot find a cookbook with []" do expect { cookbook_loader[:monkeypoop] }.to raise_error(Chef::Exceptions::CookbookNotFoundInRepo) end it "should allow you to look up available cookbooks with [] and a symbol" do expect(cookbook_loader[:openldap].name).to eql(:openldap) end it "should allow you to look up available cookbooks with [] and a string" do expect(cookbook_loader["openldap"].name).to eql(:openldap) end end describe "each" do it "should allow you to iterate over cookbooks with each" do seen = Hash.new cookbook_loader.each do |cookbook_name, cookbook| seen[cookbook_name] = true end expect(seen).to have_key("openldap") expect(seen).to have_key("apache2") end it "should iterate in alphabetical order" do seen = Array.new cookbook_loader.each do |cookbook_name, cookbook| seen << cookbook_name end expect(seen[0]).to eq("angrybash") expect(seen[1]).to eq("apache2") expect(seen[2]).to eq("borken") expect(seen[3]).to eq("ignorken") expect(seen[4]).to eq("java") expect(seen[5]).to eq("name-mismatch") expect(seen[6]).to eq("openldap") end end describe "referencing cookbook files" do it "should find all the cookbooks in the cookbook path" do expect(cookbook_loader).to have_key(:openldap) expect(cookbook_loader).to have_key(:apache2) end it "should allow you to override an attribute file via cookbook_path" do expect(cookbook_loader[:openldap].attribute_filenames.detect do |f| f =~ /cookbooks\/openldap\/attributes\/default.rb/ end).not_to eql(nil) expect(cookbook_loader[:openldap].attribute_filenames.detect do |f| f =~ /kitchen\/openldap\/attributes\/default.rb/ end).to eql(nil) end it "should load different attribute files from deeper paths" do expect(cookbook_loader[:openldap].attribute_filenames.detect do |f| f =~ /kitchen\/openldap\/attributes\/robinson.rb/ end).not_to eql(nil) end it "should allow you to override a definition file via cookbook_path" do expect(cookbook_loader[:openldap].definition_filenames.detect do |f| f =~ /cookbooks\/openldap\/definitions\/client.rb/ end).not_to eql(nil) expect(cookbook_loader[:openldap].definition_filenames.detect do |f| f =~ /kitchen\/openldap\/definitions\/client.rb/ end).to eql(nil) end it "should load definition files from deeper paths" do expect(cookbook_loader[:openldap].definition_filenames.detect do |f| f =~ /kitchen\/openldap\/definitions\/drewbarrymore.rb/ end).not_to eql(nil) end it "should allow you to override a recipe file via cookbook_path" do expect(cookbook_loader[:openldap].recipe_filenames.detect do |f| f =~ /cookbooks\/openldap\/recipes\/gigantor.rb/ end).not_to eql(nil) expect(cookbook_loader[:openldap].recipe_filenames.detect do |f| f =~ /kitchen\/openldap\/recipes\/gigantor.rb/ end).to eql(nil) end it "should load recipe files from deeper paths" do expect(cookbook_loader[:openldap].recipe_filenames.detect do |f| f =~ /kitchen\/openldap\/recipes\/woot.rb/ end).not_to eql(nil) end it "should allow you to have an 'ignore' file, which skips loading files in later cookbooks" do expect(cookbook_loader[:openldap].recipe_filenames.detect do |f| f =~ /kitchen\/openldap\/recipes\/ignoreme.rb/ end).to eql(nil) end it "should find files that start with a ." do expect(cookbook_loader[:openldap].file_filenames.detect do |f| f =~ /\.dotfile$/ end).to match(/\.dotfile$/) expect(cookbook_loader[:openldap].file_filenames.detect do |f| f =~ /\.ssh\/id_rsa$/ end).to match(/\.ssh\/id_rsa$/) end it "should load the metadata for the cookbook" do expect(cookbook_loader.metadata[:openldap].name.to_s).to eq("openldap") expect(cookbook_loader.metadata[:openldap]).to be_a_kind_of(Chef::Cookbook::Metadata) end end # referencing cookbook files end # loading all cookbooks context "loading all cookbooks when one has invalid metadata" do let(:repo_paths) do [ File.join(CHEF_SPEC_DATA, "kitchen"), File.join(CHEF_SPEC_DATA, "cookbooks"), File.join(CHEF_SPEC_DATA, "invalid-metadata-chef-repo"), ] end it "does not squelch the exception" do expect { cookbook_loader.load_cookbooks }.to raise_error("THIS METADATA HAS A BUG") end end describe "loading only one cookbook" do let(:openldap_cookbook) { cookbook_loader["openldap"] } let(:cookbook_as_hash) { Chef::CookbookManifest.new(openldap_cookbook).to_hash } before(:each) do cookbook_loader.load_cookbook("openldap") end it "should have loaded the correct cookbook" do seen = Hash.new cookbook_loader.each do |cookbook_name, cookbook| seen[cookbook_name] = true end expect(seen).to have_key("openldap") end it "should not duplicate keys when serialized to JSON" do # Chef JSON serialization will generate duplicate keys if given # a Hash containing matching string and symbol keys. See CHEF-4571. expect(cookbook_as_hash["metadata"].recipes.keys).not_to include(:openldap) expect(cookbook_as_hash["metadata"].recipes.keys).to include("openldap") expected_desc = "Main Open LDAP configuration" expect(cookbook_as_hash["metadata"].recipes["openldap"]).to eq(expected_desc) raw = Chef::JSONCompat.to_json(cookbook_as_hash["metadata"].recipes) search_str = "\"openldap\":\"" key_idx = raw.index(search_str) expect(key_idx).to be > 0 dup_idx = raw[(key_idx + 1)..-1].index(search_str) expect(dup_idx).to be_nil end it "should not load the cookbook again when accessed" do expect(cookbook_loader).not_to receive("load_cookbook") cookbook_loader["openldap"] end it "should not load the other cookbooks" do seen = Hash.new cookbook_loader.each do |cookbook_name, cookbook| seen[cookbook_name] = true end expect(seen).not_to have_key("apache2") end it "should load another cookbook lazily with []" do expect(cookbook_loader["apache2"]).to be_a_kind_of(Chef::CookbookVersion) end context "when an unrelated cookbook has invalid metadata" do let(:repo_paths) do [ File.join(CHEF_SPEC_DATA, "kitchen"), File.join(CHEF_SPEC_DATA, "cookbooks"), File.join(CHEF_SPEC_DATA, "invalid-metadata-chef-repo"), ] end it "ignores the invalid cookbook" do expect { cookbook_loader["openldap"] }.to_not raise_error end it "surfaces the exception if the cookbook is loaded later" do expect { cookbook_loader["invalid-metadata"] }.to raise_error("THIS METADATA HAS A BUG") end end describe "loading all cookbooks after loading only one cookbook" do before(:each) do expect(Chef::Log).to receive(:deprecation).with(/The cookbook\(s\): openldap exist in multiple places in your cookbook_path./) cookbook_loader.load_cookbooks end it "should load all cookbooks" do seen = Hash.new cookbook_loader.each do |cookbook_name, cookbook| seen[cookbook_name] = true end expect(seen).to have_key("openldap") expect(seen).to have_key("apache2") end end end # loading only one cookbook describe "loading a single cookbook with a different name than basename" do before(:each) do cookbook_loader.load_cookbook("name-mismatch") end it "loads the correct cookbook" do cookbook_version = cookbook_loader["name-mismatch"] expect(cookbook_version).to be_a_kind_of(Chef::CookbookVersion) expect(cookbook_version.name).to eq(:"name-mismatch") end end end chef-12.14.60/spec/unit/cookbook_manifest_spec.rb000066400000000000000000000175321276456504500216440ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "chef/cookbook_manifest" require "chef/digester" require "pathname" describe Chef::CookbookManifest do let(:version) { "1.2.3" } let(:identifier) { "9e10455ce2b4a4e29424b7064b1d67a1a25c9d3b" } let(:metadata) do Chef::Cookbook::Metadata.new.tap do |m| m.version(version) end end let(:cookbook_root) { "/tmp/blah" } let(:cookbook_version) do Chef::CookbookVersion.new("tatft", cookbook_root).tap do |c| c.metadata = metadata c.identifier = identifier end end let(:policy_mode) { false } subject(:cookbook_manifest) { Chef::CookbookManifest.new(cookbook_version, policy_mode: policy_mode) } context "when policy mode is not specified" do subject(:cookbook_manifest) { Chef::CookbookManifest.new(cookbook_version) } it "defaults to policies disabled" do expect(cookbook_manifest.policy_mode?).to be(false) end end describe "collecting cookbook data from the cookbook version object" do it "delegates `name' to cookbook_version" do expect(cookbook_manifest.name).to eq("tatft") end it "delegates `root_paths' to cookbook_version" do expect(cookbook_manifest.root_paths).to eq(["/tmp/blah"]) end it "delegates `metadata' to cookbook_version" do expect(cookbook_manifest.metadata).to eq(metadata) end it "delegates `full_name' to cookbook_version" do expect(cookbook_manifest.full_name).to eq("tatft-1.2.3") end it "delegates `version' to cookbook_version" do expect(cookbook_manifest.version).to eq(version) end it "delegates `frozen_version?' to cookbook_version" do expect(cookbook_manifest.frozen_version?).to be(false) end it "delegates `segment_filenames' to cookbook_version" do expect(cookbook_version).to receive(:segment_filenames).with(:recipes).and_return([]) expect(cookbook_manifest.segment_filenames(:recipes)).to eq([]) end end context "when given an empty cookbook" do let(:expected_hash) do { "chef_type" => "cookbook_version", "name" => "tatft-1.2.3", "version" => "1.2.3", "cookbook_name" => "tatft", "metadata" => metadata, "frozen?" => false, "recipes" => [], "definitions" => [], "libraries" => [], "attributes" => [], "files" => [], "templates" => [], "resources" => [], "providers" => [], "root_files" => [], } end it "converts the CookbookVersion to a ruby Hash representation" do expect(cookbook_manifest.to_hash).to eq(expected_hash) end end context "when given a cookbook with files" do let(:cookbook_root) { File.join(CHEF_SPEC_DATA, "cb_version_cookbooks", "tatft") } let(:attribute_filenames) { Dir[File.join(cookbook_root, "attributes", "**", "*.rb")] } let(:definition_filenames) { Dir[File.join(cookbook_root, "definitions", "**", "*.rb")] } let(:file_filenames) { Dir[File.join(cookbook_root, "files", "**", "*.tgz")] } let(:recipe_filenames) { Dir[File.join(cookbook_root, "recipes", "**", "*.rb")] } let(:template_filenames) { Dir[File.join(cookbook_root, "templates", "**", "*.erb")] } let(:library_filenames) { Dir[File.join(cookbook_root, "libraries", "**", "*.rb")] } let(:resource_filenames) { Dir[File.join(cookbook_root, "resources", "**", "*.rb")] } let(:provider_filenames) { Dir[File.join(cookbook_root, "providers", "**", "*.rb")] } let(:root_filenames) { Array(File.join(cookbook_root, "README.rdoc")) } let(:metadata_filenames) { Array(File.join(cookbook_root, "metadata.json")) } let(:match_md5) { /[0-9a-f]{32}/ } def map_to_file_specs(paths) paths.map do |path| relative_path = Pathname.new(path).relative_path_from(Pathname.new(cookbook_root)).to_s { "name" => File.basename(path), "path" => relative_path, "checksum" => Chef::Digester.generate_md5_checksum_for_file(path), "specificity" => "default", } end end let(:expected_hash) do { "chef_type" => "cookbook_version", "name" => "tatft-1.2.3", "version" => "1.2.3", "cookbook_name" => "tatft", "metadata" => metadata, "frozen?" => false, "recipes" => map_to_file_specs(recipe_filenames), "definitions" => map_to_file_specs(definition_filenames), "libraries" => map_to_file_specs(library_filenames), "attributes" => map_to_file_specs(attribute_filenames), "files" => map_to_file_specs(file_filenames), "templates" => map_to_file_specs(template_filenames), "resources" => map_to_file_specs(resource_filenames), "providers" => map_to_file_specs(provider_filenames), "root_files" => map_to_file_specs(root_filenames), } end before do cookbook_version.attribute_filenames = attribute_filenames cookbook_version.definition_filenames = definition_filenames cookbook_version.file_filenames = file_filenames cookbook_version.recipe_filenames = recipe_filenames cookbook_version.template_filenames = template_filenames cookbook_version.library_filenames = library_filenames cookbook_version.resource_filenames = resource_filenames cookbook_version.provider_filenames = provider_filenames cookbook_version.root_filenames = root_filenames cookbook_version.metadata_filenames = metadata_filenames end it "converts the CookbookVersion to a ruby Hash representation" do cookbook_manifest_hash = cookbook_manifest.to_hash expect(cookbook_manifest_hash.keys).to match_array(expected_hash.keys) cookbook_manifest_hash.each do |key, value| expect(cookbook_manifest_hash[key]).to eq(expected_hash[key]) end end end describe "providing upstream URLs for save" do context "and policy mode is disabled" do it "gives the save URL" do expect(cookbook_manifest.save_url).to eq("cookbooks/tatft/1.2.3") end it "gives the force save URL" do expect(cookbook_manifest.force_save_url).to eq("cookbooks/tatft/1.2.3?force=true") end end context "and policy mode is enabled" do let(:policy_mode) { true } let(:cookbook_manifest_hash) { cookbook_manifest.to_hash } it "sets the identifier in the manifest data" do expect(cookbook_manifest_hash["identifier"]).to eq("9e10455ce2b4a4e29424b7064b1d67a1a25c9d3b") end it "sets the name to just the name" do expect(cookbook_manifest_hash["name"]).to eq("tatft") end it "does not set a 'cookbook_name' field" do expect(cookbook_manifest_hash).to_not have_key("cookbook_name") end it "gives the save URL" do expect(cookbook_manifest.save_url).to eq("cookbook_artifacts/tatft/9e10455ce2b4a4e29424b7064b1d67a1a25c9d3b") end it "gives the force save URL" do expect(cookbook_manifest.force_save_url).to eq("cookbook_artifacts/tatft/9e10455ce2b4a4e29424b7064b1d67a1a25c9d3b?force=true") end end end end chef-12.14.60/spec/unit/cookbook_site_streaming_uploader_spec.rb000066400000000000000000000154541276456504500247470ustar00rootroot00000000000000# # Author:: Xabier de Zuazo (xabier@onddo.com) # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/cookbook_site_streaming_uploader" class FakeTempfile def initialize(basename) @basename = basename end def close end def path "#{@basename}.ZZZ" end end describe Chef::CookbookSiteStreamingUploader do describe "create_build_dir" do before(:each) do @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) @loader = Chef::CookbookLoader.new(@cookbook_repo) @loader.load_cookbooks allow(File).to receive(:unlink) end it "should create the cookbook tmp dir" do cookbook = @loader[:openldap] files_count = Dir.glob(File.join(@cookbook_repo, cookbook.name.to_s, "**", "*"), File::FNM_DOTMATCH).count { |file| File.file?(file) } # The fixture cookbook contains a spec/spec_helper.rb file, which is not # a part of any cookbook segment, so it is not uploaded. files_count -= 1 expect(Tempfile).to receive(:new).with("chef-#{cookbook.name}-build").and_return(FakeTempfile.new("chef-#{cookbook.name}-build")) expect(FileUtils).to receive(:mkdir_p).exactly(files_count + 1).times expect(FileUtils).to receive(:cp).exactly(files_count).times Chef::CookbookSiteStreamingUploader.create_build_dir(cookbook) end end # create_build_dir describe "make_request" do before(:each) do @uri = "http://cookbooks.dummy.com/api/v1/cookbooks" @secret_filename = File.join(CHEF_SPEC_DATA, "ssl/private_key.pem") @rsa_key = File.read(@secret_filename) response = Net::HTTPResponse.new("1.0", "200", "OK") allow_any_instance_of(Net::HTTP).to receive(:request).and_return(response) end it "should send an http request" do expect_any_instance_of(Net::HTTP).to receive(:request) Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, "bill", @secret_filename) end it "should read the private key file" do expect(File).to receive(:read).with(@secret_filename).and_return(@rsa_key) Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, "bill", @secret_filename) end it "should add the authentication signed header" do expect_any_instance_of(Mixlib::Authentication::SigningObject).to receive(:sign).and_return({}) Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, "bill", @secret_filename) end it "should be able to send post requests" do post = Net::HTTP::Post.new(@uri, {}) expect(Net::HTTP::Post).to receive(:new).once.and_return(post) expect(Net::HTTP::Put).not_to receive(:new) expect(Net::HTTP::Get).not_to receive(:new) Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, "bill", @secret_filename) end it "should be able to send put requests" do put = Net::HTTP::Put.new(@uri, {}) expect(Net::HTTP::Post).not_to receive(:new) expect(Net::HTTP::Put).to receive(:new).once.and_return(put) expect(Net::HTTP::Get).not_to receive(:new) Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, "bill", @secret_filename) end it "should be able to receive files to attach as argument" do Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, "bill", @secret_filename, { :myfile => File.new(File.join(CHEF_SPEC_DATA, "config.rb")), # a dummy file }) end it "should be able to receive strings to attach as argument" do Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, "bill", @secret_filename, { :mystring => "Lorem ipsum", }) end it "should be able to receive strings and files as argument at the same time" do Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, "bill", @secret_filename, { :myfile1 => File.new(File.join(CHEF_SPEC_DATA, "config.rb")), :mystring1 => "Lorem ipsum", :myfile2 => File.new(File.join(CHEF_SPEC_DATA, "config.rb")), :mystring2 => "Dummy text", }) end end # make_request describe "StreamPart" do before(:each) do @file = File.new(File.join(CHEF_SPEC_DATA, "config.rb")) @stream_part = Chef::CookbookSiteStreamingUploader::StreamPart.new(@file, File.size(@file)) end it "should create a StreamPart" do expect(@stream_part).to be_instance_of(Chef::CookbookSiteStreamingUploader::StreamPart) end it "should expose its size" do expect(@stream_part.size).to eql(File.size(@file)) end it "should read with offset and how_much" do content = @file.read(4) @file.rewind expect(@stream_part.read(0, 4)).to eql(content) end end # StreamPart describe "StringPart" do before(:each) do @str = "What a boring string" @string_part = Chef::CookbookSiteStreamingUploader::StringPart.new(@str) end it "should create a StringPart" do expect(@string_part).to be_instance_of(Chef::CookbookSiteStreamingUploader::StringPart) end it "should expose its size" do expect(@string_part.size).to eql(@str.size) end it "should read with offset and how_much" do expect(@string_part.read(2, 4)).to eql(@str[2, 4]) end end # StringPart describe "MultipartStream" do before(:each) do @string1 = "stream1" @string2 = "stream2" @stream1 = Chef::CookbookSiteStreamingUploader::StringPart.new(@string1) @stream2 = Chef::CookbookSiteStreamingUploader::StringPart.new(@string2) @parts = [ @stream1, @stream2 ] @multipart_stream = Chef::CookbookSiteStreamingUploader::MultipartStream.new(@parts) end it "should create a MultipartStream" do expect(@multipart_stream).to be_instance_of(Chef::CookbookSiteStreamingUploader::MultipartStream) end it "should expose its size" do expect(@multipart_stream.size).to eql(@stream1.size + @stream2.size) end it "should read with how_much" do expect(@multipart_stream.read(10)).to eql("#{@string1}#{@string2}"[0, 10]) end it "should read receiving destination buffer as second argument (CHEF-4456: Ruby 2 compat)" do dst_buf = "" @multipart_stream.read(10, dst_buf) expect(dst_buf).to eql("#{@string1}#{@string2}"[0, 10]) end end # MultipartStream end chef-12.14.60/spec/unit/cookbook_spec.rb000066400000000000000000000060521276456504500177510ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::CookbookVersion do # COOKBOOK_PATH = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks", "openldap")) before(:each) do @cookbook_repo = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks")) cl = Chef::CookbookLoader.new(@cookbook_repo) cl.load_cookbooks @cookbook_collection = Chef::CookbookCollection.new(cl) @cookbook = @cookbook_collection[:openldap] @node = Chef::Node.new @node.name "JuliaChild" @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) end it "should have a name" do expect(@cookbook.name).to eq(:openldap) end it "should allow you to set the list of attribute files and create the mapping from short names to paths" do @cookbook.attribute_filenames = [ "attributes/one.rb", "attributes/two.rb" ] expect(@cookbook.attribute_filenames).to eq([ "attributes/one.rb", "attributes/two.rb" ]) expect(@cookbook.attribute_filenames_by_short_filename.keys.sort).to eql(%w{one two}) expect(@cookbook.attribute_filenames_by_short_filename["one"]).to eq("attributes/one.rb") expect(@cookbook.attribute_filenames_by_short_filename["two"]).to eq("attributes/two.rb") end it "should allow you to set the list of recipe files and create the mapping of recipe short name to filename" do @cookbook.recipe_filenames = [ "recipes/one.rb", "recipes/two.rb" ] expect(@cookbook.recipe_filenames).to eq([ "recipes/one.rb", "recipes/two.rb" ]) expect(@cookbook.recipe_filenames_by_name.keys.sort).to eql(%w{one two}) expect(@cookbook.recipe_filenames_by_name["one"]).to eq("recipes/one.rb") expect(@cookbook.recipe_filenames_by_name["two"]).to eq("recipes/two.rb") end it "should generate a list of recipes by fully-qualified name" do @cookbook.recipe_filenames = [ "recipes/one.rb", "/recipes/two.rb", "three.rb" ] expect(@cookbook.fully_qualified_recipe_names.include?("openldap::one")).to eq(true) expect(@cookbook.fully_qualified_recipe_names.include?("openldap::two")).to eq(true) expect(@cookbook.fully_qualified_recipe_names.include?("openldap::three")).to eq(true) end it "should raise an ArgumentException if you try to load a bad recipe name" do expect { @cookbook.load_recipe("doesnt_exist", @node) }.to raise_error(ArgumentError) end end chef-12.14.60/spec/unit/cookbook_uploader_spec.rb000066400000000000000000000133001276456504500216360ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::CookbookUploader do let(:http_client) { double("Chef::ServerAPI") } let(:cookbook_loader) do loader = Chef::CookbookLoader.new(File.join(CHEF_SPEC_DATA, "cookbooks")) loader.load_cookbooks loader.cookbooks_by_name["apache2"].identifier = apache2_identifier loader.cookbooks_by_name["java"].identifier = java_identifier loader end let(:apache2_identifier) { "6644e6cb2ade90b8aff2ebb44728958fbc939ebf" } let(:apache2_cookbook) { cookbook_loader.cookbooks_by_name["apache2"] } let(:java_identifier) { "edd40c30c4e0ebb3658abde4620597597d2e9c17" } let(:java_cookbook) { cookbook_loader.cookbooks_by_name["java"] } let(:cookbooks_to_upload) { [apache2_cookbook, java_cookbook] } let(:checksums_of_cookbook_files) { apache2_cookbook.checksums.merge(java_cookbook.checksums) } let(:checksums_set) do checksums_of_cookbook_files.keys.inject({}) do |set, cksum| set[cksum] = nil set end end let(:sandbox_commit_uri) { "https://chef.example.org/sandboxes/abc123" } let(:policy_mode) { false } let(:uploader) { described_class.new(cookbooks_to_upload, rest: http_client, policy_mode: policy_mode) } it "defaults to not enabling policy mode" do expect(described_class.new(cookbooks_to_upload, rest: http_client).policy_mode?).to be(false) end it "has a list of cookbooks to upload" do expect(uploader.cookbooks).to eq(cookbooks_to_upload) end it "creates an HTTP client with default configuration when not initialized with one" do default_http_client = double("Chef::ServerAPI") expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(default_http_client) uploader = described_class.new(cookbooks_to_upload) expect(uploader.rest).to eq(default_http_client) end describe "uploading cookbooks" do def url_for(cksum) "https://storage.example.com/#{cksum}" end let(:sandbox_response) do sandbox_checksums = cksums_not_on_remote.inject({}) do |cksum_map, cksum| cksum_map[cksum] = { "needs_upload" => true, "url" => url_for(cksum) } cksum_map end { "checksums" => sandbox_checksums, "uri" => sandbox_commit_uri } end def expect_sandbox_create expect(http_client).to receive(:post). with("sandboxes", { :checksums => checksums_set }). and_return(sandbox_response) end def expect_checksum_upload checksums_of_cookbook_files.each do |md5, file_path| next unless cksums_not_on_remote.include?(md5) upload_headers = { "content-type" => "application/x-binary", "content-md5" => an_instance_of(String), "accept" => "application/json", } expect(http_client).to receive(:put). with(url_for(md5), IO.binread(file_path), upload_headers) end end def expected_save_url(cookbook) "cookbooks/#{cookbook.name}/#{cookbook.version}" end def expect_sandbox_commit expect(http_client).to receive(:put).with(sandbox_commit_uri, { :is_completed => true }) end def expect_cookbook_create cookbooks_to_upload.each do |cookbook| expect(http_client).to receive(:put). with(expected_save_url(cookbook), cookbook) end end context "when no files exist on the server" do let(:cksums_not_on_remote) do checksums_of_cookbook_files.keys end it "uploads all files in a sandbox transaction, then creates cookbooks on the server" do expect_sandbox_create expect_checksum_upload expect_sandbox_commit expect_cookbook_create uploader.upload_cookbooks end end context "when some files exist on the server" do let(:cksums_not_on_remote) do checksums_of_cookbook_files.keys[0, 1] end it "uploads all files in a sandbox transaction, then creates cookbooks on the server" do expect_sandbox_create expect_checksum_upload expect_sandbox_commit expect_cookbook_create uploader.upload_cookbooks end end context "when all files already exist on the server" do let(:cksums_not_on_remote) { [] } it "uploads all files in a sandbox transaction, then creates cookbooks on the server" do expect_sandbox_create expect_checksum_upload expect_sandbox_commit expect_cookbook_create uploader.upload_cookbooks end end context "when policy_mode is specified" do let(:cksums_not_on_remote) do checksums_of_cookbook_files.keys end let(:policy_mode) { true } def expected_save_url(cookbook) "cookbook_artifacts/#{cookbook.name}/#{cookbook.identifier}" end it "uploads all files in a sandbox transaction, then creates cookbooks on the server using cookbook_artifacts API" do expect_sandbox_create expect_checksum_upload expect_sandbox_commit expect_cookbook_create uploader.upload_cookbooks end end end end chef-12.14.60/spec/unit/cookbook_version_file_specificity_spec.rb000066400000000000000000000566451276456504500251250ustar00rootroot00000000000000# # Author:: Tim Hinderliter () # Author:: Christopher Walters () # Copyright:: Copyright 2010-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::CookbookVersion, "file specificity" do before(:each) do @cookbook = Chef::CookbookVersion.new("test-cookbook", "/cookbook-folder") @cookbook.manifest = { "files" => [ # afile.rb { :name => "afile.rb", :path => "files/host-examplehost.example.org/afile.rb", :checksum => "csum-host", :specificity => "host-examplehost.example.org", }, { :name => "afile.rb", :path => "files/ubuntu-9.10/afile.rb", :checksum => "csum-platver-full", :specificity => "ubuntu-9.10", }, { :name => "afile.rb", :path => "files/newubuntu-9/afile.rb", :checksum => "csum-platver-partial", :specificity => "newubuntu-9", }, { :name => "afile.rb", :path => "files/ubuntu/afile.rb", :checksum => "csum-plat", :specificity => "ubuntu", }, { :name => "afile.rb", :path => "files/default/afile.rb", :checksum => "csum-default", :specificity => "default", }, # for different/odd platform_versions { :name => "bfile.rb", :path => "files/fakeos-2.0.rc.1/bfile.rb", :checksum => "csum2-platver-full", :specificity => "fakeos-2.0.rc.1", }, { :name => "bfile.rb", :path => "files/newfakeos-2.0.rc/bfile.rb", :checksum => "csum2-platver-partial", :specificity => "newfakeos-2.0.rc", }, { :name => "bfile.rb", :path => "files/fakeos-maple tree/bfile.rb", :checksum => "csum3-platver-full", :specificity => "maple tree", }, { :name => "bfile.rb", :path => "files/fakeos-1/bfile.rb", :checksum => "csum4-platver-full", :specificity => "fakeos-1", }, # directory adirectory { :name => "anotherfile1.rb", :path => "files/host-examplehost.example.org/adirectory/anotherfile1.rb.host", :checksum => "csum-host-1", :specificity => "host-examplehost.example.org", }, { :name => "anotherfile2.rb", :path => "files/host-examplehost.example.org/adirectory/anotherfile2.rb.host", :checksum => "csum-host-2", :specificity => "host-examplehost.example.org", }, { :name => "anotherfile1.rb", :path => "files/ubuntu-9.10/adirectory/anotherfile1.rb.platform-full-version", :checksum => "csum-platver-full-1", :specificity => "ubuntu-9.10", }, { :name => "anotherfile2.rb", :path => "files/ubuntu-9.10/adirectory/anotherfile2.rb.platform-full-version", :checksum => "csum-platver-full-2", :specificity => "ubuntu-9.10", }, { :name => "anotherfile1.rb", :path => "files/newubuntu-9/adirectory/anotherfile1.rb.platform-partial-version", :checksum => "csum-platver-partial-1", :specificity => "newubuntu-9", }, { :name => "anotherfile2.rb", :path => "files/newubuntu-9/adirectory/anotherfile2.rb.platform-partial-version", :checksum => "csum-platver-partial-2", :specificity => "nweubuntu-9", }, { :name => "anotherfile1.rb", :path => "files/ubuntu/adirectory/anotherfile1.rb.platform", :checksum => "csum-plat-1", :specificity => "ubuntu", }, { :name => "anotherfile2.rb", :path => "files/ubuntu/adirectory/anotherfile2.rb.platform", :checksum => "csum-plat-2", :specificity => "ubuntu", }, { :name => "anotherfile1.rb", :path => "files/default/adirectory/anotherfile1.rb.default", :checksum => "csum-default-1", :specificity => "default", }, { :name => "anotherfile2.rb", :path => "files/default/adirectory/anotherfile2.rb.default", :checksum => "csum-default-2", :specificity => "default", }, # for different/odd platform_versions { :name => "anotherfile1.rb", :path => "files/fakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-full-version", :checksum => "csum2-platver-full-1", :specificity => "fakeos-2.0.rc.1", }, { :name => "anotherfile2.rb", :path => "files/fakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-full-version", :checksum => "csum2-platver-full-2", :specificity => "fakeos-2.0.rc.1", }, { :name => "anotherfile1.rb", :path => "files/newfakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-partial-version", :checksum => "csum2-platver-partial-1", :specificity => "newfakeos-2.0.rc", }, { :name => "anotherfile2.rb", :path => "files/newfakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-partial-version", :checksum => "csum2-platver-partial-2", :specificity => "newfakeos-2.0.rc", }, { :name => "anotherfile1.rb", :path => "files/fakeos-maple tree/adirectory/anotherfile1.rb.platform-full-version", :checksum => "csum3-platver-full-1", :specificity => "fakeos-maple tree", }, { :name => "anotherfile2.rb", :path => "files/fakeos-maple tree/adirectory/anotherfile2.rb.platform-full-version", :checksum => "csum3-platver-full-2", :specificity => "fakeos-maple tree", }, { :name => "anotherfile1.rb", :path => "files/fakeos-1/adirectory/anotherfile1.rb.platform-full-version", :checksum => "csum4-platver-full-1", :specificity => "fakeos-1", }, { :name => "anotherfile2.rb", :path => "files/fakeos-1/adirectory/anotherfile2.rb.platform-full-version", :checksum => "csum4-platver-full-2", :specificity => "fakeos-1", }, ], } end it "should return a manifest record based on priority preference: host" do node = Chef::Node.new node.automatic_attrs[:platform] = "ubuntu" node.automatic_attrs[:platform_version] = "9.10" node.automatic_attrs[:fqdn] = "examplehost.example.org" manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") expect(manifest_record).not_to be_nil expect(manifest_record[:checksum]).to eq("csum-host") end it "should return a manifest record based on priority preference: platform & full version" do node = Chef::Node.new node.automatic_attrs[:platform] = "ubuntu" node.automatic_attrs[:platform_version] = "9.10" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") expect(manifest_record).not_to be_nil expect(manifest_record[:checksum]).to eq("csum-platver-full") end it "should return a manifest record based on priority preference: platform & partial version" do node = Chef::Node.new node.automatic_attrs[:platform] = "newubuntu" node.automatic_attrs[:platform_version] = "9.10" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") expect(manifest_record).not_to be_nil expect(manifest_record[:checksum]).to eq("csum-platver-partial") end it "should return a manifest record based on priority preference: platform only" do node = Chef::Node.new node.automatic_attrs[:platform] = "ubuntu" node.automatic_attrs[:platform_version] = "1.0" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") expect(manifest_record).not_to be_nil expect(manifest_record[:checksum]).to eq("csum-plat") end it "should return a manifest record based on priority preference: default" do node = Chef::Node.new node.automatic_attrs[:platform] = "notubuntu" node.automatic_attrs[:platform_version] = "1.0" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb") expect(manifest_record).not_to be_nil expect(manifest_record[:checksum]).to eq("csum-default") end it "should return a manifest record based on priority preference: platform & full version - platform_version variant 1" do node = Chef::Node.new node.automatic_attrs[:platform] = "fakeos" node.automatic_attrs[:platform_version] = "2.0.rc.1" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") expect(manifest_record).not_to be_nil expect(manifest_record[:checksum]).to eq("csum2-platver-full") end it "should return a manifest record based on priority preference: platform & partial version - platform_version variant 1" do node = Chef::Node.new node.automatic_attrs[:platform] = "newfakeos" node.automatic_attrs[:platform_version] = "2.0.rc.1" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") expect(manifest_record).not_to be_nil expect(manifest_record[:checksum]).to eq("csum2-platver-partial") end it "should return a manifest record based on priority preference: platform & full version - platform_version variant 2" do node = Chef::Node.new node.automatic_attrs[:platform] = "fakeos" node.automatic_attrs[:platform_version] = "maple tree" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") expect(manifest_record).not_to be_nil expect(manifest_record[:checksum]).to eq("csum3-platver-full") end it "should return a manifest record based on priority preference: platform & full version - platform_version variant 3" do node = Chef::Node.new node.automatic_attrs[:platform] = "fakeos" node.automatic_attrs[:platform_version] = "1" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb") expect(manifest_record).not_to be_nil expect(manifest_record[:checksum]).to eq("csum4-platver-full") end it "should raise a FileNotFound exception without match" do node = Chef::Node.new expect do @cookbook.preferred_manifest_record(node, :files, "doesn't_exist.rb") end.to raise_error(Chef::Exceptions::FileNotFound) end it "should raise a FileNotFound exception consistently without match" do node = Chef::Node.new expect do @cookbook.preferred_manifest_record(node, :files, "doesn't_exist.rb") end.to raise_error(Chef::Exceptions::FileNotFound) expect do @cookbook.preferred_manifest_record(node, :files, "doesn't_exist.rb") end.to raise_error(Chef::Exceptions::FileNotFound) expect do @cookbook.preferred_manifest_record(node, :files, "doesn't_exist.rb") end.to raise_error(Chef::Exceptions::FileNotFound) end describe "when fetching the contents of a directory by file specificity" do it "should return a directory of manifest records based on priority preference: host" do node = Chef::Node.new node.automatic_attrs[:platform] = "ubuntu" node.automatic_attrs[:platform_version] = "9.10" node.automatic_attrs[:fqdn] = "examplehost.example.org" manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") expect(manifest_records).not_to be_nil expect(manifest_records.size).to eq(2) checksums = manifest_records.map { |manifest_record| manifest_record[:checksum] } expect(checksums.sort).to eq(["csum-host-1", "csum-host-2"]) end it "should return a directory of manifest records based on priority preference: platform & full version" do node = Chef::Node.new node.automatic_attrs[:platform] = "ubuntu" node.automatic_attrs[:platform_version] = "9.10" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") expect(manifest_records).not_to be_nil expect(manifest_records.size).to eq(2) checksums = manifest_records.map { |manifest_record| manifest_record[:checksum] } expect(checksums.sort).to eq(["csum-platver-full-1", "csum-platver-full-2"]) end it "should return a directory of manifest records based on priority preference: platform & partial version" do node = Chef::Node.new node.automatic_attrs[:platform] = "newubuntu" node.automatic_attrs[:platform_version] = "9.10" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") expect(manifest_records).not_to be_nil expect(manifest_records.size).to eq(2) checksums = manifest_records.map { |manifest_record| manifest_record[:checksum] } expect(checksums.sort).to eq(["csum-platver-partial-1", "csum-platver-partial-2"]) end it "should return a directory of manifest records based on priority preference: platform only" do node = Chef::Node.new node.automatic_attrs[:platform] = "ubuntu" node.automatic_attrs[:platform_version] = "1.0" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") expect(manifest_records).not_to be_nil expect(manifest_records.size).to eq(2) checksums = manifest_records.map { |manifest_record| manifest_record[:checksum] } expect(checksums.sort).to eq(["csum-plat-1", "csum-plat-2"]) end it "should return a directory of manifest records based on priority preference: default" do node = Chef::Node.new node.automatic_attrs[:platform] = "notubuntu" node.automatic_attrs[:platform_version] = "1.0" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") expect(manifest_records).not_to be_nil expect(manifest_records.size).to eq(2) checksums = manifest_records.map { |manifest_record| manifest_record[:checksum] } expect(checksums.sort).to eq(["csum-default-1", "csum-default-2"]) end it "should return a manifest record based on priority preference: platform & full version - platform_version variant 1" do node = Chef::Node.new node.automatic_attrs[:platform] = "fakeos" node.automatic_attrs[:platform_version] = "2.0.rc.1" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") expect(manifest_records).not_to be_nil expect(manifest_records.size).to eq(2) checksums = manifest_records.map { |manifest_record| manifest_record[:checksum] } expect(checksums.sort).to eq(["csum2-platver-full-1", "csum2-platver-full-2"]) end it "should return a manifest record based on priority preference: platform & partial version - platform_version variant 1" do node = Chef::Node.new node.automatic_attrs[:platform] = "newfakeos" node.automatic_attrs[:platform_version] = "2.0.rc.1" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") expect(manifest_records).not_to be_nil expect(manifest_records.size).to eq(2) checksums = manifest_records.map { |manifest_record| manifest_record[:checksum] } expect(checksums.sort).to eq(["csum2-platver-partial-1", "csum2-platver-partial-2"]) end it "should return a manifest record based on priority preference: platform & full version - platform_version variant 2" do node = Chef::Node.new node.automatic_attrs[:platform] = "fakeos" node.automatic_attrs[:platform_version] = "maple tree" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") expect(manifest_records).not_to be_nil expect(manifest_records.size).to eq(2) checksums = manifest_records.map { |manifest_record| manifest_record[:checksum] } expect(checksums.sort).to eq(["csum3-platver-full-1", "csum3-platver-full-2"]) end it "should return a manifest record based on priority preference: platform & full version - platform_version variant 3" do node = Chef::Node.new node.automatic_attrs[:platform] = "fakeos" node.automatic_attrs[:platform_version] = "1" node.automatic_attrs[:fqdn] = "differenthost.example.org" manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory") expect(manifest_records).not_to be_nil expect(manifest_records.size).to eq(2) checksums = manifest_records.map { |manifest_record| manifest_record[:checksum] } expect(checksums.sort).to eq(["csum4-platver-full-1", "csum4-platver-full-2"]) end end ## Globbing the relative paths out of the manifest records ## describe "when globbing for relative file paths based on filespecificity" do it "should return a list of relative paths based on priority preference: host" do node = Chef::Node.new node.automatic_attrs[:platform] = "ubuntu" node.automatic_attrs[:platform_version] = "9.10" node.automatic_attrs[:fqdn] = "examplehost.example.org" filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") expect(filenames).not_to be_nil expect(filenames.size).to eq(2) expect(filenames.sort).to eq(["anotherfile1.rb.host", "anotherfile2.rb.host"]) end it "should return a list of relative paths based on priority preference: platform & full version" do node = Chef::Node.new node.automatic_attrs[:platform] = "ubuntu" node.automatic_attrs[:platform_version] = "9.10" node.automatic_attrs[:fqdn] = "differenthost.example.org" filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") expect(filenames).not_to be_nil expect(filenames.size).to eq(2) expect(filenames.sort).to eq(["anotherfile1.rb.platform-full-version", "anotherfile2.rb.platform-full-version"]) end it "should return a list of relative paths based on priority preference: platform & partial version" do node = Chef::Node.new node.automatic_attrs[:platform] = "newubuntu" node.automatic_attrs[:platform_version] = "9.10" node.automatic_attrs[:fqdn] = "differenthost.example.org" filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") expect(filenames).not_to be_nil expect(filenames.size).to eq(2) expect(filenames.sort).to eq(["anotherfile1.rb.platform-partial-version", "anotherfile2.rb.platform-partial-version"]) end it "should return a list of relative paths based on priority preference: platform only" do node = Chef::Node.new node.automatic_attrs[:platform] = "ubuntu" node.automatic_attrs[:platform_version] = "1.0" node.automatic_attrs[:fqdn] = "differenthost.example.org" filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") expect(filenames).not_to be_nil expect(filenames.size).to eq(2) expect(filenames.sort).to eq(["anotherfile1.rb.platform", "anotherfile2.rb.platform"]) end it "should return a list of relative paths based on priority preference: default" do node = Chef::Node.new node.automatic_attrs[:platform] = "notubuntu" node.automatic_attrs[:platform_version] = "1.0" node.automatic_attrs[:fqdn] = "differenthost.example.org" filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") expect(filenames).not_to be_nil expect(filenames.size).to eq(2) expect(filenames.sort).to eq(["anotherfile1.rb.default", "anotherfile2.rb.default"]) end it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 1" do node = Chef::Node.new node.automatic_attrs[:platform] = "fakeos" node.automatic_attrs[:platform_version] = "2.0.rc.1" node.automatic_attrs[:fqdn] = "differenthost.example.org" filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") expect(filenames).not_to be_nil expect(filenames.size).to eq(2) expect(filenames.sort).to eq(["anotherfile1.rb.platform-full-version", "anotherfile2.rb.platform-full-version"]) end it "should return a list of relative paths based on priority preference: platform & partial version - platform_version variant 1" do node = Chef::Node.new node.automatic_attrs[:platform] = "newfakeos" node.automatic_attrs[:platform_version] = "2.0.rc.1" node.automatic_attrs[:fqdn] = "differenthost.example.org" filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") expect(filenames).not_to be_nil expect(filenames.size).to eq(2) expect(filenames.sort).to eq(["anotherfile1.rb.platform-partial-version", "anotherfile2.rb.platform-partial-version"]) end it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 2" do node = Chef::Node.new node.automatic_attrs[:platform] = "fakeos" node.automatic_attrs[:platform_version] = "maple tree" node.automatic_attrs[:fqdn] = "differenthost.example.org" filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") expect(filenames).not_to be_nil expect(filenames.size).to eq(2) expect(filenames.sort).to eq(["anotherfile1.rb.platform-full-version", "anotherfile2.rb.platform-full-version"]) end it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 3" do node = Chef::Node.new node.automatic_attrs[:platform] = "fakeos" node.automatic_attrs[:platform_version] = "1" node.automatic_attrs[:fqdn] = "differenthost.example.org" filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory") expect(filenames).not_to be_nil expect(filenames.size).to eq(2) expect(filenames.sort).to eq(["anotherfile1.rb.platform-full-version", "anotherfile2.rb.platform-full-version"]) end end end chef-12.14.60/spec/unit/cookbook_version_spec.rb000066400000000000000000000325631276456504500215240ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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::CookbookVersion do describe "when first created" do let(:cookbook_version) { Chef::CookbookVersion.new("tatft", "/tmp/blah") } it "has a name" do expect(cookbook_version.name).to eq("tatft") end it "has no attribute files" do expect(cookbook_version.attribute_filenames).to be_empty end it "has no resource definition files" do expect(cookbook_version.definition_filenames).to be_empty end it "has no cookbook files" do expect(cookbook_version.file_filenames).to be_empty end it "has no recipe files" do expect(cookbook_version.recipe_filenames).to be_empty end it "has no library files" do expect(cookbook_version.library_filenames).to be_empty end it "has no LWRP resource files" do expect(cookbook_version.resource_filenames).to be_empty end it "has no LWRP provider files" do expect(cookbook_version.provider_filenames).to be_empty end it "has no metadata files" do expect(cookbook_version.metadata_filenames).to be_empty end it "has an empty set of all_files" do expect(cookbook_version.all_files).to be_empty end it "is not frozen" do expect(cookbook_version).not_to be_frozen_version end it "can be frozen" do cookbook_version.freeze_version expect(cookbook_version).to be_frozen_version end it "has empty metadata" do expect(cookbook_version.metadata).to eq(Chef::Cookbook::Metadata.new) end end describe "with a cookbook directory named tatft" do MD5 = /[0-9a-f]{32}/ let(:cookbook_paths_by_type) do { # Dunno if the paths here are representitive of what is set by CookbookLoader... all_files: Dir[File.join(cookbook_root, "**", "*.rb")], attribute_filenames: Dir[File.join(cookbook_root, "attributes", "**", "*.rb")], definition_filenames: Dir[File.join(cookbook_root, "definitions", "**", "*.rb")], file_filenames: Dir[File.join(cookbook_root, "files", "**", "*.tgz")], recipe_filenames: Dir[File.join(cookbook_root, "recipes", "**", "*.rb")], template_filenames: Dir[File.join(cookbook_root, "templates", "**", "*.erb")], library_filenames: Dir[File.join(cookbook_root, "libraries", "**", "*.rb")], resource_filenames: Dir[File.join(cookbook_root, "resources", "**", "*.rb")], provider_filenames: Dir[File.join(cookbook_root, "providers", "**", "*.rb")], root_filenames: Array(File.join(cookbook_root, "README.rdoc")), metadata_filenames: Array(File.join(cookbook_root, "metadata.json")), } end let(:cookbook_root) { File.join(CHEF_SPEC_DATA, "cb_version_cookbooks", "tatft") } describe "and a cookbook with the same name" do let(:cookbook_version) do Chef::CookbookVersion.new("tatft", cookbook_root).tap do |c| # Currently the cookbook loader finds all the files then tells CookbookVersion # where they are. c.attribute_filenames = cookbook_paths_by_type[:attribute_filenames] c.definition_filenames = cookbook_paths_by_type[:definition_filenames] c.recipe_filenames = cookbook_paths_by_type[:recipe_filenames] c.template_filenames = cookbook_paths_by_type[:template_filenames] c.file_filenames = cookbook_paths_by_type[:file_filenames] c.library_filenames = cookbook_paths_by_type[:library_filenames] c.resource_filenames = cookbook_paths_by_type[:resource_filenames] c.provider_filenames = cookbook_paths_by_type[:provider_filenames] c.root_filenames = cookbook_paths_by_type[:root_filenames] c.metadata_filenames = cookbook_paths_by_type[:metadata_filenames] end end # Used to test file-specificity related file lookups let(:node) do Chef::Node.new.tap do |n| n.normal[:platform] = "ubuntu" n.normal[:platform_version] = "13.04" n.name("testing") end end it "determines whether a template is available for a given node" do expect(cookbook_version).to have_template_for_node(node, "configuration.erb") expect(cookbook_version).not_to have_template_for_node(node, "missing.erb") end it "determines whether a cookbook_file is available for a given node" do expect(cookbook_version).to have_cookbook_file_for_node(node, "giant_blob.tgz") expect(cookbook_version).not_to have_cookbook_file_for_node(node, "missing.txt") end describe "raises an error when attempting to load a missing cookbook_file and" do let(:node) do Chef::Node.new.tap do |n| n.name("sample.node") n.automatic_attrs[:fqdn] = "sample.example.com" n.automatic_attrs[:platform] = "ubuntu" n.automatic_attrs[:platform_version] = "10.04" end end def attempt_to_load_file cookbook_version.preferred_manifest_record(node, :files, "no-such-thing.txt") end it "describes the cookbook and version" do useful_explanation = Regexp.new(Regexp.escape("Cookbook 'tatft' (0.0.0) does not contain")) expect { attempt_to_load_file }.to raise_error(Chef::Exceptions::FileNotFound, useful_explanation) end it "lists suggested places to look" do useful_explanation = Regexp.new(Regexp.escape("files/default/no-such-thing.txt")) expect { attempt_to_load_file }.to raise_error(Chef::Exceptions::FileNotFound, useful_explanation) end end end end describe "with a cookbook directory named cookbook2 that has unscoped files" do let(:cookbook_paths_by_type) do { # Dunno if the paths here are representitive of what is set by CookbookLoader... all_files: Dir[File.join(cookbook_root, "**", "*.rb")], attribute_filenames: Dir[File.join(cookbook_root, "attributes", "**", "*.rb")], definition_filenames: Dir[File.join(cookbook_root, "definitions", "**", "*.rb")], file_filenames: Dir[File.join(cookbook_root, "files", "**", "*.*")], recipe_filenames: Dir[File.join(cookbook_root, "recipes", "**", "*.rb")], template_filenames: Dir[File.join(cookbook_root, "templates", "**", "*.*")], library_filenames: Dir[File.join(cookbook_root, "libraries", "**", "*.rb")], resource_filenames: Dir[File.join(cookbook_root, "resources", "**", "*.rb")], provider_filenames: Dir[File.join(cookbook_root, "providers", "**", "*.rb")], root_filenames: Array(File.join(cookbook_root, "README.rdoc")), metadata_filenames: Array(File.join(cookbook_root, "metadata.json")), } end let(:cookbook_root) { File.join(CHEF_SPEC_DATA, "cb_version_cookbooks", "cookbook2") } let(:cookbook_version) do Chef::CookbookVersion.new("cookbook2", cookbook_root).tap do |c| c.attribute_filenames = cookbook_paths_by_type[:attribute_filenames] c.definition_filenames = cookbook_paths_by_type[:definition_filenames] c.recipe_filenames = cookbook_paths_by_type[:recipe_filenames] c.template_filenames = cookbook_paths_by_type[:template_filenames] c.file_filenames = cookbook_paths_by_type[:file_filenames] c.library_filenames = cookbook_paths_by_type[:library_filenames] c.resource_filenames = cookbook_paths_by_type[:resource_filenames] c.provider_filenames = cookbook_paths_by_type[:provider_filenames] c.root_filenames = cookbook_paths_by_type[:root_filenames] c.metadata_filenames = cookbook_paths_by_type[:metadata_filenames] end end # Used to test file-specificity related file lookups let(:node) do Chef::Node.new.tap do |n| n.normal[:platform] = "ubuntu" n.normal[:platform_version] = "13.04" n.name("testing") end end it "should see a template" do expect(cookbook_version).to have_template_for_node(node, "test.erb") end it "should see a template using an array lookup" do expect(cookbook_version).to have_template_for_node(node, ["test.erb"]) end it "should see a template using an array lookup with non-existent elements" do expect(cookbook_version).to have_template_for_node(node, ["missing.txt", "test.erb"]) end it "should see a file" do expect(cookbook_version).to have_cookbook_file_for_node(node, "test.txt") end it "should see a file using an array lookup" do expect(cookbook_version).to have_cookbook_file_for_node(node, ["test.txt"]) end it "should see a file using an array lookup with non-existent elements" do expect(cookbook_version).to have_cookbook_file_for_node(node, ["missing.txt", "test.txt"]) end it "should not see a non-existent template" do expect(cookbook_version).not_to have_template_for_node(node, "missing.erb") end it "should not see a non-existent template using an array lookup" do expect(cookbook_version).not_to have_template_for_node(node, ["missing.erb"]) end it "should not see a non-existent file" do expect(cookbook_version).not_to have_cookbook_file_for_node(node, "missing.txt") end it "should not see a non-existent file using an array lookup" do expect(cookbook_version).not_to have_cookbook_file_for_node(node, ["missing.txt"]) end end describe "<=>" do it "should sort based on the version number" do examples = [ # smaller, larger ["1.0", "2.0"], ["1.2.3", "1.2.4"], ["1.2.3", "1.3.0"], ["1.2.3", "1.3"], ["1.2.3", "2.1.1"], ["1.2.3", "2.1"], ["1.2", "1.2.4"], ["1.2", "1.3.0"], ["1.2", "1.3"], ["1.2", "2.1.1"], ["1.2", "2.1"], ] examples.each do |smaller, larger| sm = Chef::CookbookVersion.new("foo", "/tmp/blah") lg = Chef::CookbookVersion.new("foo", "/tmp/blah") sm.version = smaller lg.version = larger expect(sm).to be < lg expect(lg).to be > sm expect(sm).not_to eq(lg) end end it "should equate versions 1.2 and 1.2.0" do a = Chef::CookbookVersion.new("foo", "/tmp/blah") b = Chef::CookbookVersion.new("foo", "/tmp/blah") a.version = "1.2" b.version = "1.2.0" expect(a).to eq(b) end it "should not allow you to sort cookbooks with different names" do apt = Chef::CookbookVersion.new "apt", "/tmp/blah" apt.version = "1.0" god = Chef::CookbookVersion.new "god", "/tmp/blah" god.version = "2.0" expect { apt <=> god }.to raise_error(Chef::Exceptions::CookbookVersionNameMismatch) end end describe "when you set a version" do subject(:cbv) { Chef::CookbookVersion.new("version validation", "/tmp/blah") } it "should accept valid cookbook versions" do good_versions = %w{1.2 1.2.3 1000.80.50000 0.300.25} good_versions.each do |v| cbv.version = v end end it "should raise InvalidVersion for bad cookbook versions" do bad_versions = ["1.2.3.4", "1.2.a4", "1", "a", "1.2 3", "1.2 a", "1 2 3", "1-2-3", "1_2_3", "1.2_3", "1.2-3"] the_error = Chef::Exceptions::InvalidCookbookVersion bad_versions.each do |v| expect { cbv.version = v }.to raise_error(the_error) end end end describe "when deprecation warnings are errors" do subject(:cbv) { Chef::CookbookVersion.new("version validation", "/tmp/blah") } it "errors on #status and #status=" do expect { cbv.status = :wat }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) expect { cbv.status }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) end end describe "deprecated features" do subject(:cbv) { Chef::CookbookVersion.new("tatft", "/tmp/blah").tap { |c| c.version = "1.2.3" } } before do Chef::Config[:treat_deprecation_warnings_as_errors] = false end it "gives a save URL for the standard cookbook API" do expect(cbv.save_url).to eq("cookbooks/tatft/1.2.3") end it "gives a force save URL for the standard cookbook API" do expect(cbv.force_save_url).to eq("cookbooks/tatft/1.2.3?force=true") end it "is \"ready\"" do # WTF is this? what are the valid states? and why aren't they set with encapsulating methods? # [Dan 15-Jul-2010] expect(cbv.status).to eq(:ready) end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { Chef::CookbookVersion.new("tatft", "/tmp/blah") } end end end chef-12.14.60/spec/unit/daemon_spec.rb000066400000000000000000000126641276456504500174140ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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" require "ostruct" describe Chef::Daemon do before do if windows? mock_struct = #Struct::Passwd.new(nil, nil, 111, 111) mock_struct = OpenStruct.new(:uid => 2342, :gid => 2342) allow(Etc).to receive(:getpwnam).and_return mock_struct allow(Etc).to receive(:getgrnam).and_return mock_struct # mock unimplemented methods allow(Process).to receive(:initgroups).and_return nil allow(Process::GID).to receive(:change_privilege).and_return 11 allow(Process::UID).to receive(:change_privilege).and_return 11 end end describe ".pid_file" do describe "when the pid_file option has been set" do before do Chef::Config[:pid_file] = "/var/run/chef/chef-client.pid" end it "should return the supplied value" do expect(Chef::Daemon.pid_file).to eql("/var/run/chef/chef-client.pid") end end describe "without the pid_file option set" do before do Chef::Daemon.name = "chef-client" end it "should return a valued based on @name" do expect(Chef::Daemon.pid_file).to eql("/tmp/chef-client.pid") end end end describe ".pid_from_file" do before do Chef::Config[:pid_file] = "/var/run/chef/chef-client.pid" end it "should suck the pid out of pid_file" do expect(File).to receive(:read).with("/var/run/chef/chef-client.pid").and_return("1337") Chef::Daemon.pid_from_file end end describe ".change_privilege" do before do allow(Chef::Application).to receive(:fatal!).and_return(true) Chef::Config[:user] = "aj" allow(Dir).to receive(:chdir) end it "changes the working directory to root" do expect(Dir).to receive(:chdir).with("/").and_return(0) Chef::Daemon.change_privilege end describe "when the user and group options are supplied" do before do Chef::Config[:group] = "staff" end it "should log an appropriate info message" do expect(Chef::Log).to receive(:info).with("About to change privilege to aj:staff") Chef::Daemon.change_privilege end it "should call _change_privilege with the user and group" do expect(Chef::Daemon).to receive(:_change_privilege).with("aj", "staff") Chef::Daemon.change_privilege end end describe "when just the user option is supplied" do it "should log an appropriate info message" do expect(Chef::Log).to receive(:info).with("About to change privilege to aj") Chef::Daemon.change_privilege end it "should call _change_privilege with just the user" do expect(Chef::Daemon).to receive(:_change_privilege).with("aj") Chef::Daemon.change_privilege end end end describe "._change_privilege" do before do allow(Process).to receive(:euid).and_return(0) allow(Process).to receive(:egid).and_return(0) allow(Process::UID).to receive(:change_privilege).and_return(nil) allow(Process::GID).to receive(:change_privilege).and_return(nil) @pw_user = double("Struct::Passwd", :uid => 501) @pw_group = double("Struct::Group", :gid => 20) allow(Process).to receive(:initgroups).and_return(true) allow(Etc).to receive(:getpwnam).and_return(@pw_user) allow(Etc).to receive(:getgrnam).and_return(@pw_group) end describe "with sufficient privileges" do before do allow(Process).to receive(:euid).and_return(0) allow(Process).to receive(:egid).and_return(0) end it "should initialize the supplemental group list" do expect(Process).to receive(:initgroups).with("aj", 20) Chef::Daemon._change_privilege("aj") end it "should attempt to change the process GID" do expect(Process::GID).to receive(:change_privilege).with(20).and_return(20) Chef::Daemon._change_privilege("aj") end it "should attempt to change the process UID" do expect(Process::UID).to receive(:change_privilege).with(501).and_return(501) Chef::Daemon._change_privilege("aj") end end describe "with insufficient privileges" do before do allow(Process).to receive(:euid).and_return(999) allow(Process).to receive(:egid).and_return(999) end it "should log an appropriate error message and fail miserably" do allow(Process).to receive(:initgroups).and_raise(Errno::EPERM) error = "Operation not permitted" if RUBY_PLATFORM.match("solaris2") || RUBY_PLATFORM.match("aix") error = "Not owner" end expect(Chef::Application).to receive(:fatal!).with("Permission denied when trying to change 999:999 to 501:20. #{error}") Chef::Daemon._change_privilege("aj") end end end end chef-12.14.60/spec/unit/data_bag_item_spec.rb000066400000000000000000000306011276456504500207000ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "chef/data_bag_item" describe Chef::DataBagItem do let(:data_bag_item) { Chef::DataBagItem.new } describe "initialize" do it "should be a Chef::DataBagItem" do expect(data_bag_item).to be_a_kind_of(Chef::DataBagItem) end end describe "data_bag" do it "should let you set the data_bag to a string" do expect(data_bag_item.data_bag("clowns")).to eq("clowns") end it "should return the current data_bag type" do data_bag_item.data_bag "clowns" expect(data_bag_item.data_bag).to eq("clowns") end it "should not accept spaces" do expect { data_bag_item.data_bag "clown masters" }.to raise_error(ArgumentError) end it "should throw an ArgumentError if you feed it anything but a string" do expect { data_bag_item.data_bag Hash.new }.to raise_error(ArgumentError) end end describe "raw_data" do it "should let you set the raw_data with a hash" do expect { data_bag_item.raw_data = { "id" => "octahedron" } }.not_to raise_error end it "should let you set the raw_data from a mash" do expect { data_bag_item.raw_data = Mash.new({ "id" => "octahedron" }) }.not_to raise_error end it "should raise an exception if you set the raw data without a key" do expect { data_bag_item.raw_data = { "monkey" => "pants" } }.to raise_error(ArgumentError) end it "should raise an exception if you set the raw data to something other than a hash" do expect { data_bag_item.raw_data = "katie rules" }.to raise_error(ArgumentError) end it "should accept alphanum/-/_ for the id" do expect { data_bag_item.raw_data = { "id" => "h1-_" } }.not_to raise_error end it "should accept alphanum.alphanum for the id" do expect { data_bag_item.raw_data = { "id" => "foo.bar" } }.not_to raise_error end it "should accept .alphanum for the id" do expect { data_bag_item.raw_data = { "id" => ".bozo" } }.not_to raise_error end it "should raise an exception if the id contains anything but alphanum/-/_" do expect { data_bag_item.raw_data = { "id" => "!@#" } }.to raise_error(ArgumentError) end it "should return the raw data" do data_bag_item.raw_data = { "id" => "highway_of_emptiness" } expect(data_bag_item.raw_data).to eq({ "id" => "highway_of_emptiness" }) end it "should be a Mash by default" do expect(data_bag_item.raw_data).to be_a_kind_of(Mash) end end describe "object_name" do let(:data_bag_item) do data_bag_item = Chef::DataBagItem.new data_bag_item.data_bag("dreams") data_bag_item.raw_data = { "id" => "the_beatdown" } data_bag_item end it "should return an object name based on the bag name and the raw_data id" do expect(data_bag_item.object_name).to eq("data_bag_item_dreams_the_beatdown") end end describe "class method object_name" do it "should return an object name based based on the bag name and an id" do expect(Chef::DataBagItem.object_name("zen", "master")).to eq("data_bag_item_zen_master") end end describe "class method name" do let(:data_bag_item) do data_bag_item = Chef::DataBagItem.new data_bag_item.data_bag("dreams") data_bag_item.raw_data = { "id" => "the_beatdown", "name" => "Bruce" } data_bag_item end it "should return the object name" do expect(data_bag_item.name).to eq(data_bag_item.object_name) end it "should be distinct from raw_data 'name' key" do expect(data_bag_item["name"]).to eq("Bruce") expect(data_bag_item["name"]).not_to eq(data_bag_item.object_name) end end describe "when used like a Hash" do let(:data_bag_item) do data_bag_item = Chef::DataBagItem.new data_bag_item.raw_data = { "id" => "journey", "trials" => "been through" } data_bag_item end it "responds to keys" do expect(data_bag_item.keys).to include("id") expect(data_bag_item.keys).to include("trials") end it "supports element reference with []" do expect(data_bag_item["id"]).to eq("journey") end it "implements all the methods of Hash" do methods = [:rehash, :to_hash, :[], :fetch, :[]=, :store, :default, :default=, :default_proc, :index, :size, :length, :empty?, :each_value, :each_key, :each_pair, :each, :keys, :values, :values_at, :delete, :delete_if, :reject!, :clear, :invert, :update, :replace, :merge!, :merge, :has_key?, :has_value?, :key?, :value?] methods.each do |m| expect(data_bag_item).to respond_to(m) end end end describe "from_hash" do context "when hash contains raw_data" do let(:data_bag_item) do Chef::DataBagItem.from_hash({ "raw_data" => { "id" => "whoa", "name" => "Bruce", "i_know" => "kung_fu" } }) end it "should have the id key set" do expect(data_bag_item["id"]).to eq("whoa") end it "should have the name key set" do expect(data_bag_item["name"]).to eq("Bruce") end end context "when hash does not contain raw_data" do let(:data_bag_item) do Chef::DataBagItem.from_hash({ "id" => "whoa", "name" => "Bruce", "i_know" => "kung_fu" }) end it "should have the id key set" do expect(data_bag_item["id"]).to eq("whoa") end it "should have the name key set" do expect(data_bag_item["name"]).to eq("Bruce") end end end describe "to_hash" do let(:data_bag_item) do data_bag_item = Chef::DataBagItem.new data_bag_item.data_bag("still_lost") data_bag_item.raw_data = { "id" => "whoa", "name" => "Bruce", "i_know" => "kung_fu" } data_bag_item end let!(:original_data_bag_keys) { data_bag_item.keys } let(:to_hash) { data_bag_item.to_hash } it "should return a hash" do expect(to_hash).to be_a_kind_of(Hash) end it "should have the raw_data keys as top level keys" do expect(to_hash["id"]).to eq("whoa") expect(to_hash["name"]).to eq("Bruce") expect(to_hash["i_know"]).to eq("kung_fu") end it "should have the chef_type of data_bag_item" do expect(to_hash["chef_type"]).to eq("data_bag_item") end it "should have the data_bag set" do expect(to_hash["data_bag"]).to eq("still_lost") end it "should not mutate the data_bag_item" do data_bag_item.to_hash expect(data_bag_item.keys).to eq(original_data_bag_keys) end end describe "when deserializing from JSON" do let(:data_bag_item) do data_bag_item = Chef::DataBagItem.new data_bag_item.data_bag("mars_volta") data_bag_item.raw_data = { "id" => "octahedron", "name" => "Bruce", "snooze" => { "finally" => :world_will } } data_bag_item end let(:deserial) { Chef::DataBagItem.from_hash(Chef::JSONCompat.parse(Chef::JSONCompat.to_json(data_bag_item))) } it "should deserialize to a Chef::DataBagItem object" do expect(deserial).to be_a_kind_of(Chef::DataBagItem) end it "should have a matching 'data_bag' value" do expect(deserial.data_bag).to eq(data_bag_item.data_bag) end it "should have a matching 'id' key" do expect(deserial["id"]).to eq("octahedron") end it "should have a matching 'name' key" do expect(deserial["name"]).to eq("Bruce") end it "should have a matching 'snooze' key" do expect(deserial["snooze"]).to eq({ "finally" => "world_will" }) end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { data_bag_item } end end describe "when converting to a string" do it "converts to a string in the form data_bag_item[ID]" do data_bag_item["id"] = "heart of darkness" expect(data_bag_item.to_s).to eq("data_bag_item[heart of darkness]") end it "inspects as data_bag_item[BAG, ID, RAW_DATA]" do raw_data = { "id" => "heart_of_darkness", "author" => "Conrad" } data_bag_item.raw_data = raw_data data_bag_item.data_bag("books") expect(data_bag_item.inspect).to eq("data_bag_item[\"books\", \"heart_of_darkness\", #{raw_data.inspect}]") end end describe "save" do let(:server) { instance_double(Chef::ServerAPI) } let(:data_bag_item) do data_bag_item = Chef::DataBagItem.new data_bag_item["id"] = "heart of darkness" data_bag_item.raw_data = { "id" => "heart_of_darkness", "author" => "Conrad" } data_bag_item.data_bag("books") data_bag_item end before do expect(Chef::ServerAPI).to receive(:new).and_return(server) end it "should update the item when it already exists" do expect(server).to receive(:put).with("data/books/heart_of_darkness", data_bag_item) data_bag_item.save end it "should create if the item is not found" do exception = double("404 error", :code => "404") expect(server).to receive(:put).and_raise(Net::HTTPServerException.new("foo", exception)) expect(server).to receive(:post).with("data/books", data_bag_item) data_bag_item.save end describe "when whyrun mode is enabled" do before do Chef::Config[:why_run] = true end after do Chef::Config[:why_run] = false end it "should not save" do expect(server).not_to receive(:put) expect(server).not_to receive(:post) data_bag_item.data_bag("books") data_bag_item.save end end end describe "destroy" do let(:server) { instance_double(Chef::ServerAPI) } let(:data_bag_item) do data_bag_item = Chef::DataBagItem.new data_bag_item.data_bag("a_baggy_bag") data_bag_item.raw_data = { "id" => "some_id" } data_bag_item end it "should set default parameters" do expect(Chef::ServerAPI).to receive(:new).and_return(server) expect(server).to receive(:delete).with("data/a_baggy_bag/data_bag_item_a_baggy_bag_some_id") data_bag_item.destroy end end describe "when loading" do before do data_bag_item.raw_data = { "id" => "charlie", "shell" => "zsh", "ssh_keys" => %w{key1 key2} } data_bag_item.data_bag("users") end describe "from an API call" do let(:http_client) { double("Chef::ServerAPI") } before do allow(Chef::ServerAPI).to receive(:new).and_return(http_client) end it "converts raw data to a data bag item" do expect(http_client).to receive(:get).with("data/users/charlie").and_return(data_bag_item.to_hash) item = Chef::DataBagItem.load(:users, "charlie") expect(item).to be_a_kind_of(Chef::DataBagItem) expect(item).to eq(data_bag_item) end it "does not convert when a DataBagItem is returned from the API call" do expect(http_client).to receive(:get).with("data/users/charlie").and_return(data_bag_item) item = Chef::DataBagItem.load(:users, "charlie") expect(item).to be_a_kind_of(Chef::DataBagItem) expect(item).to equal(data_bag_item) end end describe "in solo mode" do before do Chef::Config[:solo_legacy_mode] = true end after do Chef::Config[:solo_legacy_mode] = false end it "converts the raw data to a data bag item" do expect(Chef::DataBag).to receive(:load).with("users").and_return({ "charlie" => data_bag_item.to_hash }) item = Chef::DataBagItem.load("users", "charlie") expect(item).to be_a_kind_of(Chef::DataBagItem) expect(item).to eq(data_bag_item) end it "raises an exception for unknown items" do expect(Chef::DataBag).to receive(:load).with("users").and_return({ "charlie" => data_bag_item.to_hash }) expect { Chef::DataBagItem.load("users", "wonka") }.to raise_error Chef::Exceptions::InvalidDataBagItemID end end end end chef-12.14.60/spec/unit/data_bag_spec.rb000066400000000000000000000224551276456504500176720ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "chef/data_bag" describe Chef::DataBag do before(:each) do @data_bag = Chef::DataBag.new allow(ChefConfig).to receive(:windows?) { false } end describe "initialize" do it "should be a Chef::DataBag" do expect(@data_bag).to be_a_kind_of(Chef::DataBag) end end describe "name" do it "should let you set the name to a string" do expect(@data_bag.name("clowns")).to eq("clowns") end it "should return the current name" do @data_bag.name "clowns" expect(@data_bag.name).to eq("clowns") end it "should not accept spaces" do expect { @data_bag.name "clown masters" }.to raise_error(ArgumentError) end it "should throw an ArgumentError if you feed it anything but a string" do expect { @data_bag.name Hash.new }.to raise_error(ArgumentError) end [ ".", "-", "_", "1"].each do |char| it "should allow a '#{char}' character in the data bag name" do expect(@data_bag.name("clown#{char}clown")).to eq("clown#{char}clown") end end end describe "deserialize" do before(:each) do @data_bag.name("mars_volta") @deserial = Chef::DataBag.from_hash(Chef::JSONCompat.parse(Chef::JSONCompat.to_json(@data_bag))) end it "should deserialize to a Chef::DataBag object" do expect(@deserial).to be_a_kind_of(Chef::DataBag) end %w{ name }.each do |t| it "should match '#{t}'" do expect(@deserial.send(t.to_sym)).to eq(@data_bag.send(t.to_sym)) end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @data_bag } end end end describe "when saving" do before do @data_bag.name("piggly_wiggly") @rest = double("Chef::ServerAPI") allow(Chef::ServerAPI).to receive(:new).and_return(@rest) end it "should silently proceed when the data bag already exists" do exception = double("409 error", :code => "409") expect(@rest).to receive(:post).and_raise(Net::HTTPServerException.new("foo", exception)) @data_bag.save end it "should create the data bag" do expect(@rest).to receive(:post).with("data", @data_bag) @data_bag.save end describe "when whyrun mode is enabled" do before do Chef::Config[:why_run] = true end after do Chef::Config[:why_run] = false end it "should not save" do expect(@rest).not_to receive(:post) @data_bag.save end end end describe "when loading" do describe "from an API call" do before do Chef::Config[:chef_server_url] = "https://myserver.example.com" @http_client = double("Chef::ServerAPI") end it "should get the data bag from the server" do expect(Chef::ServerAPI).to receive(:new).with("https://myserver.example.com").and_return(@http_client) expect(@http_client).to receive(:get).with("data/foo") Chef::DataBag.load("foo") end it "should return the data bag" do allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) expect(@http_client).to receive(:get).with("data/foo").and_return({ "bar" => "https://myserver.example.com/data/foo/bar" }) data_bag = Chef::DataBag.load("foo") expect(data_bag).to eq({ "bar" => "https://myserver.example.com/data/foo/bar" }) end end def file_dir_stub(path, returns = true) expect(File).to receive(:directory?).with(path).and_return(returns) end def dir_glob_stub(path, returns = []) expect(Dir).to receive(:glob).with(File.join(path, "foo/*.json")).and_return(returns) end shared_examples_for "data bag in solo mode" do |data_bag_path| before do Chef::Config[:solo_legacy_mode] = true Chef::Config[:data_bag_path] = data_bag_path @paths = Array(data_bag_path) end after do Chef::Config[:solo_legacy_mode] = false end it "should get the data bag from the data_bag_path" do @paths.each do |path| file_dir_stub(path) dir_glob_stub(path) end Chef::DataBag.load("foo") end it "should get the data bag from the data_bag_path by symbolic name" do @paths.each do |path| file_dir_stub(path) dir_glob_stub(path) end Chef::DataBag.load(:foo) end it "should return the data bag" do @paths.each do |path| file_dir_stub(path) if path == @paths.first dir_glob_stub(path, [File.join(path, "foo/bar.json"), File.join(path, "foo/baz.json")]) else dir_glob_stub(path) end end expect(IO).to receive(:read).with(File.join(@paths.first, "foo/bar.json")).and_return('{"id": "bar", "name": "Bob Bar" }') expect(IO).to receive(:read).with(File.join(@paths.first, "foo/baz.json")).and_return('{"id": "baz", "name": "John Baz" }') data_bag = Chef::DataBag.load("foo") expect(data_bag).to eq({ "bar" => { "id" => "bar", "name" => "Bob Bar" }, "baz" => { "id" => "baz", "name" => "John Baz" } }) end it "should raise if data bag has items with similar names but different content" do @paths.each do |path| file_dir_stub(path) item_with_different_content = "{\"id\": \"bar\", \"name\": \"Bob Bar\", \"path\": \"#{path}\"}" expect(IO).to receive(:read).with(File.join(path, "foo/bar.json")).and_return(item_with_different_content) if data_bag_path.is_a?(String) dir_glob_stub(path, [File.join(path, "foo/bar.json"), File.join(path, "foo/baz.json")]) item_2_with_different_content = '{"id": "bar", "name": "John Baz"}' expect(IO).to receive(:read).with(File.join(path, "foo/baz.json")).and_return(item_2_with_different_content) else dir_glob_stub(path, [File.join(path, "foo/bar.json")]) end end expect { Chef::DataBag.load("foo") }.to raise_error(Chef::Exceptions::DuplicateDataBagItem) end it "should return data bag if it has items with similar names and the same content" do @paths.each do |path| file_dir_stub(path) dir_glob_stub(path, [File.join(path, "foo/bar.json"), File.join(path, "foo/baz.json")]) item_with_same_content = '{"id": "bar", "name": "Bob Bar"}' expect(IO).to receive(:read).with(File.join(path, "foo/bar.json")).and_return(item_with_same_content) expect(IO).to receive(:read).with(File.join(path, "foo/baz.json")).and_return(item_with_same_content) end data_bag = Chef::DataBag.load("foo") test_data_bag = { "bar" => { "id" => "bar", "name" => "Bob Bar" } } expect(data_bag).to eq(test_data_bag) end it "should merge data bag items if there are no conflicts" do @paths.each_with_index do |path, index| file_dir_stub(path) dir_glob_stub(path, [File.join(path, "foo/bar.json"), File.join(path, "foo/baz.json")]) test_item_with_same_content = '{"id": "bar", "name": "Bob Bar"}' expect(IO).to receive(:read).with(File.join(path, "foo/bar.json")).and_return(test_item_with_same_content) test_uniq_item = "{\"id\": \"baz_#{index}\", \"name\": \"John Baz\", \"path\": \"#{path}\"}" expect(IO).to receive(:read).with(File.join(path, "foo/baz.json")).and_return(test_uniq_item) end data_bag = Chef::DataBag.load("foo") test_data_bag = { "bar" => { "id" => "bar", "name" => "Bob Bar" } } @paths.each_with_index do |path, index| test_data_bag["baz_#{index}"] = { "id" => "baz_#{index}", "name" => "John Baz", "path" => path } end expect(data_bag).to eq(test_data_bag) end it "should return the data bag list" do @paths.each do |path| file_dir_stub(path) expect(Dir).to receive(:glob).and_return([File.join(path, "foo"), File.join(path, "bar")]) end data_bag_list = Chef::DataBag.list expect(data_bag_list).to eq({ "bar" => "bar", "foo" => "foo" }) end it "should raise an error if the configured data_bag_path is invalid" do file_dir_stub(@paths.first, false) expect do Chef::DataBag.load("foo") end.to raise_error Chef::Exceptions::InvalidDataBagPath, "Data bag path '/var/chef/data_bags' is invalid" end end describe "data bag with string path" do it_should_behave_like "data bag in solo mode", "/var/chef/data_bags" end describe "data bag with array path" do it_should_behave_like "data bag in solo mode", ["/var/chef/data_bags", "/var/chef/data_bags_2"] end end end chef-12.14.60/spec/unit/data_collector/000077500000000000000000000000001276456504500175605ustar00rootroot00000000000000chef-12.14.60/spec/unit/data_collector/messages/000077500000000000000000000000001276456504500213675ustar00rootroot00000000000000chef-12.14.60/spec/unit/data_collector/messages/helpers_spec.rb000066400000000000000000000142771276456504500244030ustar00rootroot00000000000000# # Author:: Adam Leff ( "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 it "updates the file" do allow(TestMessage).to receive(:metadata_filename).and_return("fake_metadata_file.json") allow(TestMessage).to receive(:metadata).and_return({ "key" => "current_value" }) expect(Chef::FileCache).to receive(:store).with( "fake_metadata_file.json", '{"key":"updated_value"}', 0644 ) TestMessage.update_metadata("key", "updated_value") end end end chef-12.14.60/spec/unit/data_collector/messages_spec.rb000066400000000000000000000132671276456504500227370ustar00rootroot00000000000000# # Author:: Adam Leff ( "updated" }) } let(:report2) { double("report2", report_data: { "status" => "skipped" }) } let(:reporter_data) do { run_status: run_status, resources: [report1, report2], } end before do allow(run_status).to receive(:start_time).and_return(Time.now) allow(run_status).to receive(:end_time).and_return(Time.now) end it "includes a valid node object in the payload" do message = Chef::DataCollector::Messages.run_end_message(reporter_data) expect(message["node"]).to be_an_instance_of(Chef::Node) end it "returns a sane JSON representation of the node object" do node.chef_environment = "my_test_environment" node.run_list.add("recipe[my_test_cookbook::default]") message = FFI_Yajl::Parser.parse(Chef::DataCollector::Messages.run_end_message(reporter_data).to_json) expect(message["node"]["chef_environment"]).to eq("my_test_environment") expect(message["node"]["run_list"]).to eq(["recipe[my_test_cookbook::default]"]) end context "when the run was successful" do let(:required_fields) do %w{ chef_server_fqdn entity_uuid id end_time expanded_run_list message_type message_version node node_name organization_name resources run_id run_list source start_time status total_resource_count updated_resource_count } 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 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 end chef-12.14.60/spec/unit/data_collector_spec.rb000066400000000000000000000502321276456504500211210ustar00rootroot00000000000000# # Author:: Adam Leff () # # 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" require "chef/resource_builder" 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) } before do Chef::Config[:data_collector][:server_url] = "http://my-data-collector-server.mycompany.com" end 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 "#converge_start" do it "stashes the run_context for later use" do reporter.converge_start("test_context") expect(reporter.run_context).to eq("test_context") end end describe "#converge_complete" do it "detects and processes any unprocessed resources" do expect(reporter).to receive(:detect_unprocessed_resources) reporter.converge_complete end end describe "#converge_failed" do it "detects and processes any unprocessed resources" do expect(reporter).to receive(:detect_unprocessed_resources) reporter.converge_failed("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") } let(:resource_report) { double("resource_report") } 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 "creates the resource report and stores it as the current one" do allow(reporter).to receive(:nested_resource?).and_return(false) expect(reporter).to receive(:create_resource_report) .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(:create_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 "creates the resource report and stores it as the current one" do allow(reporter).to receive(:nested_resource?).and_return(false) expect(reporter).to receive(:create_resource_report) .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(:update_current_resource_report) allow(reporter).to receive(:add_resource_report) allow(reporter).to receive(:current_resource_report) allow(resource_report).to receive(:finish) end context "when there is no current resource report" do it "does not touch the current resource report" do allow(reporter).to receive(:current_resource_report).and_return(nil) expect(reporter).not_to receive(:update_current_resource_report) 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 mark the resource as finished" do allow(reporter).to receive(:nested_resource?).with(new_resource).and_return(true) expect(resource_report).not_to receive(:finish) 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 "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 "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, Errno::EHOSTDOWN ].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 describe "#validate_data_collector_server_url!" do context "when server_url is empty" do it "raises an exception" do Chef::Config[:data_collector][:server_url] = "" expect { reporter.send(:validate_data_collector_server_url!) }.to raise_error(Chef::Exceptions::ConfigurationError) end end context "when server_url is not empty" do context "when server_url is an invalid URI" do it "raises an exception" do Chef::Config[:data_collector][:server_url] = "this is not a URI" expect { reporter.send(:validate_data_collector_server_url!) }.to raise_error(Chef::Exceptions::ConfigurationError) end end context "when server_url is a valid URI" do context "when server_url is a URI with no host" do it "raises an exception" do Chef::Config[:data_collector][:server_url] = "/file/uri.txt" expect { reporter.send(:validate_data_collector_server_url!) }.to raise_error(Chef::Exceptions::ConfigurationError) end end context "when server_url is a URI with a valid host" do it "does not an exception" do Chef::Config[:data_collector][:server_url] = "http://www.google.com/data-collector" expect { reporter.send(:validate_data_collector_server_url!) }.not_to raise_error end end end end end end chef-12.14.60/spec/unit/decorator/000077500000000000000000000000001276456504500165635ustar00rootroot00000000000000chef-12.14.60/spec/unit/decorator/lazy_array_spec.rb000066400000000000000000000032261276456504500223020ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Decorator::LazyArray do def foo @foo ||= 1 end def bar @bar ||= 2 end let(:decorator) do Chef::Decorator::LazyArray.new { [ foo, bar ] } end it "behaves like an array" do expect(decorator[0]).to eql(1) expect(decorator[1]).to eql(2) end it "accessing the array elements is lazy" do expect(decorator[0].class).to eql(Chef::Decorator::Lazy) expect(decorator[1].class).to eql(Chef::Decorator::Lazy) expect(@foo).to be nil expect(@bar).to be nil end it "calling a method on the array element runs the proc (and both elements are autovivified)" do expect(decorator[0].nil?).to be false expect(@foo).to equal(1) expect(@bar).to equal(2) end it "if we loop over the elements and do nothing then its not lazy" do # we don't know how many elements there are unless we evaluate the proc decorator.each { |i| } expect(@foo).to equal(1) expect(@bar).to equal(2) end end chef-12.14.60/spec/unit/decorator/lazy_spec.rb000066400000000000000000000021151276456504500211000ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Decorator::Lazy do let(:decorator) do @a = 0 Chef::Decorator::Lazy.new { @a = @a + 1 } end it "decorates an object" do expect(decorator.even?).to be false end it "the proc runs and does work" do expect(decorator).to eql(1) end it "creating the decorator does not cause the proc to run" do decorator expect(@a).to eql(0) end end chef-12.14.60/spec/unit/decorator_spec.rb000066400000000000000000000075541276456504500201350ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2015-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" def impersonates_a(klass) it "#is_a?(#{klass}) is true" do expect(decorator.is_a?(klass)).to be true end it "#is_a?(Chef::Decorator) is true" do expect(decorator.is_a?(Chef::Decorator)).to be true end it "#kind_of?(#{klass}) is true" do expect(decorator.kind_of?(klass)).to be true end it "#kind_of?(Chef::Decorator) is true" do expect(decorator.kind_of?(Chef::Decorator)).to be true end it "#instance_of?(#{klass}) is false" do expect(decorator.instance_of?(klass)).to be false end it "#instance_of?(Chef::Decorator) is true" do expect(decorator.instance_of?(Chef::Decorator)).to be true end it "#class is Chef::Decorator" do expect(decorator.class).to eql(Chef::Decorator) end end describe Chef::Decorator do let(:obj) {} let(:decorator) { Chef::Decorator.new(obj) } context "when the obj is a string" do let(:obj) { "STRING" } impersonates_a(String) it "#nil? is false" do expect(decorator.nil?).to be false end it "!! is true" do expect(!!decorator).to be true end it "dup returns a decorator" do expect(decorator.dup.class).to be Chef::Decorator end it "dup dup's the underlying thing" do expect(decorator.dup.__getobj__).not_to equal(decorator.__getobj__) end end context "when the obj is a nil" do let(:obj) { nil } it "#nil? is true" do expect(decorator.nil?).to be true end it "!! is false" do expect(!!decorator).to be false end impersonates_a(NilClass) end context "when the obj is an empty Hash" do let(:obj) { {} } impersonates_a(Hash) it "formats it correctly through ffi-yajl and not the JSON gem" do # this relies on a quirk of pretty formatting whitespace between yajl and ruby's JSON expect(FFI_Yajl::Encoder.encode(decorator, pretty: true)).to eql("{\n\n}\n") end end context "whent he obj is a Hash with elements" do let(:obj) { { foo: "bar", baz: "qux" } } impersonates_a(Hash) it "dup is shallow on the Hash" do expect(decorator.dup[:baz]).to equal(decorator[:baz]) end it "deep mutating the dup'd hash mutates the origin" do decorator.dup[:baz] << "qux" expect(decorator[:baz]).to eql("quxqux") end end context "memoizing methods" do let(:obj) { {} } it "calls method_missing only once" do expect(decorator).to receive(:method_missing).once.and_call_original expect(decorator.keys).to eql([]) expect(decorator.keys).to eql([]) end it "switching a Hash to an Array responds to keys then does not" do expect(decorator.respond_to?(:keys)).to be true expect(decorator.keys).to eql([]) decorator.__setobj__([]) expect(decorator.respond_to?(:keys)).to be false expect { decorator.keys }.to raise_error(NoMethodError) end it "memoization of methods happens on the instances, not the classes" do decorator2 = Chef::Decorator.new([]) expect(decorator.respond_to?(:keys)).to be true expect(decorator.keys).to eql([]) expect(decorator2.respond_to?(:keys)).to be false expect { decorator2.keys }.to raise_error(NoMethodError) end end end chef-12.14.60/spec/unit/deprecation_spec.rb000066400000000000000000000113001276456504500204300ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-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/deprecation/warnings" describe Chef::Deprecation do # Support code for Chef::Deprecation def self.class_from_string(str) str.split("::").inject(Object) do |mod, class_name| mod.const_get(class_name) end end module DeprecatedMethods def deprecated_method(value) @value = value end def get_value @value end end class TestClass extend Chef::Deprecation::Warnings include DeprecatedMethods add_deprecation_warnings_for(DeprecatedMethods.instance_methods) end method_snapshot_file = File.join(CHEF_SPEC_DATA, "file-providers-method-snapshot-chef-11-4.json") method_snapshot = Chef::JSONCompat.parse(File.open(method_snapshot_file).read()) method_snapshot.each do |class_name, old_methods| class_object = class_from_string(class_name) current_methods = class_object.public_instance_methods.map(&:to_sym) it "defines all methods on #{class_object} that were available in 11.0" do old_methods.each do |old_method| expect(current_methods).to include(old_method.to_sym) end end end context "when Chef::Config[:treat_deprecation_warnings_as_errors] is off" do before do Chef::Config[:treat_deprecation_warnings_as_errors] = false end context "deprecation warning messages" do RSpec::Matchers.define_negated_matcher :a_non_empty_array, :be_empty it "should be enabled for deprecated methods" do expect(Chef::Log).to receive(:warn).with(a_non_empty_array) TestClass.new.deprecated_method(10) end it "should contain stack trace" do expect(Chef::Log).to receive(:warn).with(a_string_including(".rb")) TestClass.new.deprecated_method(10) end end it "deprecated methods should still be called" do test_instance = TestClass.new test_instance.deprecated_method(10) expect(test_instance.get_value).to eq(10) end end it "should raise when deprecation warnings are treated as errors" do # rspec should set this expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be true test_instance = TestClass.new expect { test_instance.deprecated_method(10) }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) end context "When a class has deprecated_attr, _reader and _writer" do before(:context) do class DeprecatedAttrTest extend Chef::Mixin::Deprecation def initialize @a = @r = @w = 1 end deprecated_attr :a, "a" deprecated_attr_reader :r, "r" deprecated_attr_writer :w, "w" end end it "The deprecated_attr emits warnings" do test = DeprecatedAttrTest.new expect { test.a = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) expect { test.a }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) end it "The deprecated_attr_writer emits warnings, and does not create a reader" do test = DeprecatedAttrTest.new expect { test.w = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) expect { test.w }.to raise_error(NoMethodError) end it "The deprecated_attr_reader emits warnings, and does not create a writer" do test = DeprecatedAttrTest.new expect { test.r = 10 }.to raise_error(NoMethodError) expect { test.r }.to raise_error(Chef::Exceptions::DeprecatedFeatureError) end context "With deprecation warnings not throwing exceptions" do before do Chef::Config[:treat_deprecation_warnings_as_errors] = false end it "The deprecated_attr can be written to and read from" do test = DeprecatedAttrTest.new test.a = 10 expect(test.a).to eq 10 end it "The deprecated_attr_reader can be read from" do test = DeprecatedAttrTest.new expect(test.r).to eq 1 end it "The deprecated_attr_writer can be written to" do test = DeprecatedAttrTest.new test.w = 10 expect(test.instance_eval { @w }).to eq 10 end end end end chef-12.14.60/spec/unit/digester_spec.rb000066400000000000000000000032571276456504500177550ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Chef Software Inc. # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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::Digester do before(:each) do @cache = Chef::Digester.instance end describe "when computing checksums of cookbook files and templates" do it "proxies the class method checksum_for_file to the instance" do expect(@cache).to receive(:checksum_for_file).with("a_file_or_a_fail") Chef::Digester.checksum_for_file("a_file_or_a_fail") end it "computes a checksum of a file" do fixture_file = CHEF_SPEC_DATA + "/checksum/random.txt" expected = "09ee9c8cc70501763563bcf9c218d71b2fbf4186bf8e1e0da07f0f42c80a3394" expect(@cache.checksum_for_file(fixture_file)).to eq(expected) end it "generates a checksum from a non-file IO object" do io = StringIO.new("riseofthemachines\nriseofthechefs\n") expected_md5 = "0e157ac1e2dd73191b76067fb6b4bceb" expect(@cache.generate_md5_checksum(io)).to eq(expected_md5) end end end chef-12.14.60/spec/unit/dsl/000077500000000000000000000000001276456504500153635ustar00rootroot00000000000000chef-12.14.60/spec/unit/dsl/audit_spec.rb000066400000000000000000000025121276456504500200300ustar00rootroot00000000000000 require "spec_helper" require "chef/dsl/audit" class AuditDSLTester < Chef::Recipe include Chef::DSL::Audit end class BadAuditDSLTester include Chef::DSL::Audit end describe Chef::DSL::Audit do let(:auditor) { AuditDSLTester.new("cookbook_name", "recipe_name", run_context) } let(:run_context) { instance_double(Chef::RunContext, :audits => audits, :cookbook_collection => cookbook_collection) } let(:audits) { {} } let(:cookbook_collection) { {} } it "raises an error when a block of audits is not provided" do expect { auditor.control_group "name" }.to raise_error(Chef::Exceptions::NoAuditsProvided) end it "raises an error when no audit name is given" do expect { auditor.control_group {} }.to raise_error(Chef::Exceptions::AuditNameMissing) end context "audits already populated" do let(:audits) { { "unique" => {} } } it "raises an error if the audit name is a duplicate" do expect { auditor.control_group("unique") {} }.to raise_error(Chef::Exceptions::AuditControlGroupDuplicate) end end context "included in a class without recipe DSL" do let(:auditor) { BadAuditDSLTester.new } it "fails because it relies on the recipe DSL existing" do expect { auditor.control_group("unique") {} }.to raise_error(NoMethodError, /undefined method `cookbook_name'/) end end end chef-12.14.60/spec/unit/dsl/data_query_spec.rb000066400000000000000000000066231276456504500210670ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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/dsl/data_query" class DataQueryDSLTester include Chef::DSL::DataQuery end describe Chef::DSL::DataQuery do let(:node) { Hash.new } let(:language) do language = DataQueryDSLTester.new allow(language).to receive(:node).and_return(@node) language end describe "::data_bag" do it "lists the items in a data bag" do allow(Chef::DataBag).to receive(:load) .with("bag_name") .and_return("item_1" => "http://url_for/item_1", "item_2" => "http://url_for/item_2") expect( language.data_bag("bag_name").sort ).to eql %w{item_1 item_2} end end shared_examples_for "a data bag item" do it "validates the name of the data bag you're trying to load an item from" do expect { language.send(method_name, " %%^& ", "item_name") }.to raise_error(Chef::Exceptions::InvalidDataBagName) end it "validates the id of the data bag item you're trying to load" do expect { language.send(method_name, "bag_name", " 987 (*&()") }.to raise_error(Chef::Exceptions::InvalidDataBagItemID) end it "validates that the id of the data bag item is not nil" do expect { language.send(method_name, "bag_name", nil) }.to raise_error(Chef::Exceptions::InvalidDataBagItemID) end end describe "::data_bag_item" do let(:bag_name) { "bag_name" } let(:item_name) { "item_name" } let(:raw_data) do { "id" => item_name, "greeting" => "hello", "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }, }, } end let(:item) do item = Chef::DataBagItem.new item.data_bag(bag_name) item.raw_data = raw_data item end it "fetches a data bag item" do allow( Chef::DataBagItem ).to receive(:load).with(bag_name, item_name).and_return(item) expect( language.data_bag_item(bag_name, item_name) ).to eql item end include_examples "a data bag item" do let(:method_name) { :data_bag_item } end context "when the item is encrypted" do let(:secret) { "abc123SECRET" } let(:enc_data_bag) { double("Chef::EncryptedDataBagItem") } before do allow( Chef::DataBagItem ).to receive(:load).with(bag_name, item_name).and_return(item) expect(language).to receive(:encrypted?).and_return(true) expect( Chef::EncryptedDataBagItem ).to receive(:load_secret).and_return(secret) end it "detects encrypted data bag" do expect( Chef::EncryptedDataBagItem ).to receive(:new).with(raw_data, secret).and_return(enc_data_bag) expect( Chef::Log ).to receive(:debug).with(/Data bag item looks encrypted/) expect(language.data_bag_item(bag_name, item_name)).to eq(enc_data_bag) end end end end chef-12.14.60/spec/unit/dsl/declare_resource_spec.rb000066400000000000000000000313221276456504500222310ustar00rootroot00000000000000# # 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::ResourceCollection do let(:run_context) do cookbook_repo = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "data", "cookbooks")) cookbook_loader = Chef::CookbookLoader.new(cookbook_repo) cookbook_loader.load_cookbooks node = Chef::Node.new cookbook_collection = Chef::CookbookCollection.new(cookbook_loader) events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, cookbook_collection, events) end let(:recipe) do Chef::Recipe.new("hjk", "test", run_context) end describe "#declare_resource" do before do recipe.declare_resource(:zen_master, "monkey") do something true end end it "inserts into the resource collection" do expect(run_context.resource_collection.first.to_s).to eql("zen_master[monkey]") end it "sets the property from the block" do expect(run_context.resource_collection.first.something).to be true end end describe "#edit_resource!" do it "raises if nothing is found" do expect do recipe.edit_resource!(:zen_master, "monkey") do something true end end.to raise_error(Chef::Exceptions::ResourceNotFound) end it "raises if nothing is found and no block is given" do expect do recipe.edit_resource!(:zen_master, "monkey") end.to raise_error(Chef::Exceptions::ResourceNotFound) end it "edits the resource if it finds one" do resource = recipe.declare_resource(:zen_master, "monkey") do something false end expect( recipe.edit_resource!(:zen_master, "monkey") do something true end ).to eql(resource) expect(run_context.resource_collection.all_resources.size).to eql(1) expect(run_context.resource_collection.first.something).to be true end it "acts like find_resource! if not given a block and the resource exists" do resource = recipe.declare_resource(:zen_master, "monkey") do something false end expect( recipe.edit_resource!(:zen_master, "monkey") ).to eql(resource) expect(run_context.resource_collection.all_resources.size).to eql(1) expect(run_context.resource_collection.first.something).to be false end end describe "#edit_resource" do it "inserts a resource if nothing is found" do resource = recipe.edit_resource(:zen_master, "monkey") do something true end expect(run_context.resource_collection.all_resources.size).to eql(1) expect(run_context.resource_collection.first).to eql(resource) expect(run_context.resource_collection.first.something).to be true end it "inserts a resource even if not given a block" do resource = recipe.edit_resource(:zen_master, "monkey") expect(run_context.resource_collection.all_resources.size).to eql(1) expect(run_context.resource_collection.first).to eql(resource) end it "edits the resource if it finds one" do resource = recipe.declare_resource(:zen_master, "monkey") do something false end expect( recipe.edit_resource(:zen_master, "monkey") do something true end ).to eql(resource) expect(run_context.resource_collection.all_resources.size).to eql(1) expect(run_context.resource_collection.first.something).to be true end it "acts like find_resource if not given a block and the resource exists" do resource = recipe.declare_resource(:zen_master, "monkey") do something false end expect( recipe.edit_resource(:zen_master, "monkey") ).to eql(resource) expect(run_context.resource_collection.all_resources.size).to eql(1) expect(run_context.resource_collection.first.something).to be false end end describe "#find_resource!" do it "raises if nothing is found" do expect do recipe.find_resource!(:zen_master, "monkey") end.to raise_error(Chef::Exceptions::ResourceNotFound) end it "raises if given a block" do resource = recipe.declare_resource(:zen_master, "monkey") do something false end expect do recipe.find_resource!(:zen_master, "monkey") do something false end end.to raise_error(ArgumentError) end it "returns the resource if it finds one" do resource = recipe.declare_resource(:zen_master, "monkey") do something false end expect( recipe.find_resource!(:zen_master, "monkey") ).to eql(resource) expect(run_context.resource_collection.all_resources.size).to eql(1) expect(run_context.resource_collection.first.something).to be false end end describe "#find_resource without block" do it "returns nil if nothing is found" do expect(recipe.find_resource(:zen_master, "monkey")).to be nil expect(run_context.resource_collection.all_resources.size).to eql(0) end it "returns the resource if it finds one" do resource = recipe.declare_resource(:zen_master, "monkey") do something false end expect( recipe.find_resource(:zen_master, "monkey") ).to eql(resource) expect(run_context.resource_collection.all_resources.size).to eql(1) expect(run_context.resource_collection.first.something).to be false end end describe "#find_resource with block" do it "inserts a resource if nothing is found" do resource = recipe.find_resource(:zen_master, "monkey") do something true end expect(run_context.resource_collection.all_resources.size).to eql(1) expect(run_context.resource_collection.first).to eql(resource) expect(run_context.resource_collection.first.something).to be true end it "returns the resource if it finds one" do resource = recipe.declare_resource(:zen_master, "monkey") do something false end expect( recipe.find_resource(:zen_master, "monkey") do something true end ).to eql(resource) expect(run_context.resource_collection.all_resources.size).to eql(1) expect(run_context.resource_collection.first.something).to be false end end describe "#delete_resource" do it "returns nil if nothing is found" do expect( recipe.delete_resource(:zen_master, "monkey") ).to be nil end it "deletes and returns the resource if it finds one" do resource = recipe.declare_resource(:zen_master, "monkey") do something false end expect( recipe.delete_resource(:zen_master, "monkey") ).to eql(resource) expect(run_context.resource_collection.all_resources.size).to eql(0) end end describe "#delete_resource!" do it "raises if nothing is found" do expect do recipe.delete_resource!(:zen_master, "monkey") end.to raise_error(Chef::Exceptions::ResourceNotFound) end it "deletes and returns the resource if it finds one" do resource = recipe.declare_resource(:zen_master, "monkey") do something false end expect( recipe.delete_resource!(:zen_master, "monkey") ).to eql(resource) expect(run_context.resource_collection.all_resources.size).to eql(0) end it "removes pending delayed notifications" do recipe.declare_resource(:zen_master, "one") recipe.declare_resource(:zen_master, "two") do notifies :win, "zen_master[one]" end recipe.delete_resource(:zen_master, "two") resource = recipe.declare_resource(:zen_master, "two") expect(resource.delayed_notifications).to eql([]) end it "removes pending immediate notifications" do recipe.declare_resource(:zen_master, "one") recipe.declare_resource(:zen_master, "two") do notifies :win, "zen_master[one]", :immediate end recipe.delete_resource(:zen_master, "two") resource = recipe.declare_resource(:zen_master, "two") expect(resource.immediate_notifications).to eql([]) end it "removes pending before notifications" do recipe.declare_resource(:zen_master, "one") recipe.declare_resource(:zen_master, "two") do notifies :win, "zen_master[one]", :before end recipe.delete_resource(:zen_master, "two") resource = recipe.declare_resource(:zen_master, "two") expect(resource.before_notifications).to eql([]) end end describe "run_context helpers" do let(:parent_run_context) do run_context.create_child end let(:child_run_context) do parent_run_context.create_child end let(:parent_recipe) do Chef::Recipe.new("hjk", "parent", parent_run_context) end let(:child_recipe) do Chef::Recipe.new("hjk", "child", child_run_context) end before do # wire up our outer run context to the root Chef.run_context allow(Chef).to receive(:run_context).and_return(run_context) end it "our tests have correct separation" do child_resource = child_recipe.declare_resource(:zen_master, "child") do something false end parent_resource = parent_recipe.declare_resource(:zen_master, "parent") do something false end root_resource = recipe.declare_resource(:zen_master, "root") do something false end expect(run_context.resource_collection.first).to eql(root_resource) expect(run_context.resource_collection.first.to_s).to eql("zen_master[root]") expect(run_context.resource_collection.all_resources.size).to eql(1) expect(parent_run_context.resource_collection.first).to eql(parent_resource) expect(parent_run_context.resource_collection.first.to_s).to eql("zen_master[parent]") expect(parent_run_context.resource_collection.all_resources.size).to eql(1) expect(child_run_context.resource_collection.first).to eql(child_resource) expect(child_run_context.resource_collection.first.to_s).to eql("zen_master[child]") expect(child_run_context.resource_collection.all_resources.size).to eql(1) end it "with_run_context with :parent lets us build resources in the parent run_context from the child" do child_recipe.instance_eval do with_run_context(:parent) do declare_resource(:zen_master, "parent") do something false end end end expect(run_context.resource_collection.all_resources.size).to eql(0) expect(parent_run_context.resource_collection.all_resources.size).to eql(1) expect(parent_run_context.resource_collection.first.to_s).to eql("zen_master[parent]") expect(child_run_context.resource_collection.all_resources.size).to eql(0) end it "with_run_context with :root lets us build resources in the root run_context from the child" do child_recipe.instance_eval do with_run_context(:root) do declare_resource(:zen_master, "root") do something false end end end expect(run_context.resource_collection.first.to_s).to eql("zen_master[root]") expect(run_context.resource_collection.all_resources.size).to eql(1) expect(parent_run_context.resource_collection.all_resources.size).to eql(0) expect(child_run_context.resource_collection.all_resources.size).to eql(0) end it "with_run_context also takes a RunContext object as an argument" do child_recipe.instance_exec(parent_run_context) do |parent_run_context| with_run_context(parent_run_context) do declare_resource(:zen_master, "parent") do something false end end end expect(run_context.resource_collection.all_resources.size).to eql(0) expect(parent_run_context.resource_collection.all_resources.size).to eql(1) expect(parent_run_context.resource_collection.first.to_s).to eql("zen_master[parent]") expect(child_run_context.resource_collection.all_resources.size).to eql(0) end it "with_run_context returns the return value of the block" do child_recipe.instance_eval do ret = with_run_context(:root) do "return value" end raise "failed" unless ret == "return value" end end end end chef-12.14.60/spec/unit/dsl/platform_introspection_spec.rb000066400000000000000000000120261276456504500235270ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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/dsl/platform_introspection" class LanguageTester attr_reader :node def initialize(node) @node = node end include Chef::DSL::PlatformIntrospection end describe "PlatformIntrospection implementors" do let(:node) { Chef::Node.new } let(:platform_introspector) { LanguageTester.new(node) } it_behaves_like "a platform introspector" end describe Chef::DSL::PlatformIntrospection::PlatformDependentValue do before do platform_hash = { :openbsd => { :default => "free, functional, secure" }, [:redhat, :centos, :fedora, :scientific] => { :default => '"stable"' }, :ubuntu => { "10.04" => "using upstart more", :default => "using init more" }, :default => "bork da bork", } @platform_specific_value = Chef::DSL::PlatformIntrospection::PlatformDependentValue.new(platform_hash) end it "returns the default value when the platform doesn't match" do expect(@platform_specific_value.value_for_node(:platform => :dos)).to eq("bork da bork") end it "returns a value for a platform set as a group" do expect(@platform_specific_value.value_for_node(:platform => :centos)).to eq('"stable"') end it "returns a value for the platform when it was set as a symbol but fetched as a string" do expect(@platform_specific_value.value_for_node(:platform => "centos")).to eq('"stable"') end it "returns a value for a specific platform version" do node = { :platform => "ubuntu", :platform_version => "10.04" } expect(@platform_specific_value.value_for_node(node)).to eq("using upstart more") end it "returns a platform-default value if the platform version doesn't match an explicit one" do node = { :platform => "ubuntu", :platform_version => "9.10" } expect(@platform_specific_value.value_for_node(node)).to eq("using init more") end it "returns nil if there is no default and no platforms match" do # this matches the behavior in the original implementation. # whether or not it's correct is another matter. platform_specific_value = Chef::DSL::PlatformIntrospection::PlatformDependentValue.new({}) expect(platform_specific_value.value_for_node(:platform => "foo")).to be_nil end it "raises an argument error if the platform hash is not correctly structured" do bad_hash = { :ubuntu => :foo } # should be :ubuntu => {:default => 'foo'} expect { Chef::DSL::PlatformIntrospection::PlatformDependentValue.new(bad_hash) }.to raise_error(ArgumentError) end end describe Chef::DSL::PlatformIntrospection::PlatformFamilyDependentValue do before do @array_values = [:stop, :start, :reload] @platform_family_hash = { "debian" => "debian value", [:rhel, "fedora"] => "redhatty value", "suse" => @array_values, :gentoo => "gentoo value", :default => "default value", } @platform_family_value = Chef::DSL::PlatformIntrospection::PlatformFamilyDependentValue.new(@platform_family_hash) end it "returns the default value when the platform family doesn't match" do expect(@platform_family_value.value_for_node(:platform_family => :os2)).to eq("default value") end it "returns a value for the platform family when it was set as a string but fetched as a symbol" do expect(@platform_family_value.value_for_node(:platform_family => :debian)).to eq("debian value") end it "returns a value for the platform family when it was set as a symbol but fetched as a string" do expect(@platform_family_value.value_for_node(:platform_family => "gentoo")).to eq("gentoo value") end it "returns an array value stored for a platform family" do expect(@platform_family_value.value_for_node(:platform_family => "suse")).to eq(@array_values) end it "returns a value for the platform family when it was set within an array hash key as a symbol" do expect(@platform_family_value.value_for_node(:platform_family => :rhel)).to eq("redhatty value") end it "returns a value for the platform family when it was set within an array hash key as a string" do expect(@platform_family_value.value_for_node(:platform_family => "fedora")).to eq("redhatty value") end it "returns nil if there is no default and no platforms match" do platform_specific_value = Chef::DSL::PlatformIntrospection::PlatformFamilyDependentValue.new({}) expect(platform_specific_value.value_for_node(:platform_family => "foo")).to be_nil end end chef-12.14.60/spec/unit/dsl/reboot_pending_spec.rb000066400000000000000000000105221276456504500217200ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/dsl/reboot_pending" require "spec_helper" describe Chef::DSL::RebootPending do describe "reboot_pending?" do describe "in isolation" do let(:recipe) { Object.new.extend(Chef::DSL::RebootPending) } before do allow(recipe).to receive(:platform?).and_return(false) end context "platform is windows" do before do allow(recipe).to receive(:platform?).with("windows").and_return(true) allow(recipe).to receive(:registry_key_exists?).and_return(false) allow(recipe).to receive(:registry_value_exists?).and_return(false) end it 'should return true if "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations" exists' do allow(recipe).to receive(:registry_value_exists?).with('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => "PendingFileRenameOperations" }).and_return(true) expect(recipe.reboot_pending?).to be_truthy end it 'should return true if "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" exists' do allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired').and_return(true) expect(recipe.reboot_pending?).to be_truthy end it 'should return true if key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired" exists' do allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending').and_return(true) expect(recipe.reboot_pending?).to be_truthy end context "version is server 2003" do before do allow(Chef::Platform).to receive(:windows_server_2003?).and_return(true) end it 'should return true if value "HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile" contains specific data on 2k3' do allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(true) allow(recipe).to receive(:registry_get_values).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return( [{ :name => "Flags", :type => :dword, :data => 3 }]) expect(recipe.reboot_pending?).to be_truthy end end end context "platform is ubuntu" do before do allow(recipe).to receive(:platform?).with("ubuntu").and_return(true) end it "should return true if /var/run/reboot-required exists" do allow(File).to receive(:exists?).with("/var/run/reboot-required").and_return(true) expect(recipe.reboot_pending?).to be_truthy end it "should return false if /var/run/reboot-required does not exist" do allow(File).to receive(:exists?).with("/var/run/reboot-required").and_return(false) expect(recipe.reboot_pending?).to be_falsey end end end # describe in isolation describe "in a recipe" do it "responds to reboot_pending?" do # Chef::Recipe.new(cookbook_name, recipe_name, run_context(node, cookbook_collection, events)) recipe = Chef::Recipe.new(nil, nil, Chef::RunContext.new(Chef::Node.new, {}, nil)) expect(recipe).to respond_to(:reboot_pending?) end end # describe in a recipe describe "in a resource" do it "responds to reboot_pending?" do resource = Chef::Resource.new("Crackerjack::Timing", nil) expect(resource).to respond_to(:reboot_pending?) end end # describe in a resource end end chef-12.14.60/spec/unit/dsl/recipe_spec.rb000066400000000000000000000053671276456504500202040ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/dsl/recipe" RecipeDSLExampleClass = Struct.new(:cookbook_name, :recipe_name) class RecipeDSLExampleClass include Chef::DSL::Recipe end FullRecipeDSLExampleClass = Struct.new(:cookbook_name, :recipe_name) class FullRecipeDSLExampleClass include Chef::DSL::Recipe::FullDSL end RecipeDSLBaseAPI = Struct.new(:cookbook_name, :recipe_name) class RecipeDSLExampleSubclass < RecipeDSLBaseAPI include Chef::DSL::Recipe end # TODO: most of DSL::Recipe's implementation is tested in Chef::Recipe's tests, # move those to here. describe Chef::DSL::Recipe do let(:cookbook_name) { "example_cb" } let(:recipe_name) { "example_recipe" } it "tracks when it is included via FullDSL" do expect(Chef::DSL::Recipe::FullDSL.descendants).to include(FullRecipeDSLExampleClass) end it "doesn't track what is included via only the recipe DSL" do expect(Chef::DSL::Recipe::FullDSL.descendants).not_to include(RecipeDSLExampleClass) end shared_examples_for "A Recipe DSL Implementation" do it "responds to cookbook_name" do expect(recipe.cookbook_name).to eq(cookbook_name) end it "responds to recipe_name" do expect(recipe.recipe_name).to eq(recipe_name) end it "responds to shell_out" do expect(recipe.respond_to?(:shell_out)).to be true end it "responds to shell_out" do expect(recipe.respond_to?(:shell_out!)).to be true end it "responds to shell_out" do expect(recipe.respond_to?(:shell_out_with_systems_locale)).to be true end end context "when included in a class that defines the required interface directly" do let(:recipe) { RecipeDSLExampleClass.new(cookbook_name, recipe_name) } include_examples "A Recipe DSL Implementation" end # This is the situation that occurs when the Recipe DSL gets mixed in to a # resource, for example. context "when included in a class that defines the required interface in a superclass" do let(:recipe) { RecipeDSLExampleSubclass.new(cookbook_name, recipe_name) } include_examples "A Recipe DSL Implementation" end end chef-12.14.60/spec/unit/dsl/registry_helper_spec.rb000066400000000000000000000037661276456504500221450ustar00rootroot00000000000000# # Author:: Prajakta Purohit () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/dsl/registry_helper" require "spec_helper" describe Chef::Resource::RegistryKey do before (:all) do events = Chef::EventDispatch::Dispatcher.new node = Chef::Node.new node.consume_external_attrs(OHAI_SYSTEM.data, {}) run_context = Chef::RunContext.new(node, {}, events) @resource = Chef::Resource.new("foo", run_context) end context "tests registry dsl" do it "resource can access registry_helper method registry_key_exists" do expect(@resource.respond_to?("registry_key_exists?")).to eq(true) end it "resource can access registry_helper method registry_get_values" do expect(@resource.respond_to?("registry_get_values")).to eq(true) end it "resource can access registry_helper method registry_has_subkey" do expect(@resource.respond_to?("registry_has_subkeys?")).to eq(true) end it "resource can access registry_helper method registry_get_subkeys" do expect(@resource.respond_to?("registry_get_subkeys")).to eq(true) end it "resource can access registry_helper method registry_value_exists" do expect(@resource.respond_to?("registry_value_exists?")).to eq(true) end it "resource can access registry_helper method data_value_exists" do expect(@resource.respond_to?("registry_data_exists?")).to eq(true) end end end chef-12.14.60/spec/unit/dsl/resources_spec.rb000066400000000000000000000044351276456504500207420ustar00rootroot00000000000000# # Author:: Noah Kantrowitz () # Copyright:: Copyright 2015-2016, Noah Kantrowitz # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/dsl/resources" describe Chef::DSL::Resources do let(:declared_resources) { [] } let(:test_class) do r = declared_resources Class.new do include Chef::DSL::Resources define_method(:declare_resource) do |dsl_name, name, _created_at| r << [dsl_name, name] end end end subject { declared_resources } after do # Always clean up after ourselves. described_class.remove_resource_dsl(:test_resource) end context "with a resource added" do before do Chef::DSL::Resources.add_resource_dsl(:test_resource) test_class.new.instance_eval do test_resource "test_name" do end end end it { is_expected.to eq [[:test_resource, "test_name"]] } end context "with no resource added" do subject do test_class.new.instance_eval do test_resource "test_name" do end end end it { expect { subject }.to raise_error NoMethodError } end context "with a resource added and removed" do before do Chef::DSL::Resources.add_resource_dsl(:test_resource) Chef::DSL::Resources.remove_resource_dsl(:test_resource) end subject do test_class.new.instance_eval do test_resource "test_name" do end end end it { expect { subject }.to raise_error NoMethodError } end context "with a nameless resource" do before do Chef::DSL::Resources.add_resource_dsl(:test_resource) test_class.new.instance_eval do test_resource {} end end it { is_expected.to eq [[:test_resource, nil]] } end end chef-12.14.60/spec/unit/encrypted_data_bag_item/000077500000000000000000000000001276456504500214165ustar00rootroot00000000000000chef-12.14.60/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb000066400000000000000000000061401276456504500261100ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2010-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/encrypted_data_bag_item/check_encrypted" class CheckEncryptedTester include Chef::EncryptedDataBagItem::CheckEncrypted end describe Chef::EncryptedDataBagItem::CheckEncrypted do let(:tester) { CheckEncryptedTester.new } it "detects the item is not encrypted when the data is empty" do expect(tester.encrypted?({})).to eq(false) end it "detects the item is not encrypted when the data only contains an id" do expect(tester.encrypted?({ id: "foo" })).to eq(false) end context "when the item is encrypted" do let(:default_secret) { "abc123SECRET" } let(:item_name) { "item_name" } let(:raw_data) do { "id" => item_name, "greeting" => "hello", "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }, }, } end let(:version) { 1 } let(:encoded_data) do Chef::Config[:data_bag_encrypt_version] = version Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, default_secret) end it "does not detect encryption when the item version is unknown" do # It shouldn't be possible for someone to normally encrypt an item with an unknown version - they would have to # do something funky like encrypting it and then manually changing the version modified_encoded_data = encoded_data modified_encoded_data["greeting"]["version"] = 4 expect(tester.encrypted?(modified_encoded_data)).to eq(false) end shared_examples_for "encryption detected" do it "detects encrypted data bag" do expect( encryptor ).to receive(:encryptor_keys).at_least(:once).and_call_original expect(tester.encrypted?(encoded_data)).to eq(true) end end context "when encryption version is 1" do include_examples "encryption detected" do let(:version) { 1 } let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor } end end context "when encryption version is 2" do include_examples "encryption detected" do let(:version) { 2 } let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor } end end context "when encryption version is 3", :aes_256_gcm_only, ruby: "~> 2.0.0" do include_examples "encryption detected" do let(:version) { 3 } let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor } end end end end chef-12.14.60/spec/unit/encrypted_data_bag_item_spec.rb000066400000000000000000000360411276456504500227610ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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/encrypted_data_bag_item" module Version0Encryptor def self.encrypt_value(plaintext_data, key) data = plaintext_data.to_yaml cipher = OpenSSL::Cipher.new("aes-256-cbc") cipher.encrypt cipher.pkcs5_keyivgen(key) encrypted_bytes = cipher.update(data) encrypted_bytes << cipher.final Base64.encode64(encrypted_bytes) end end describe Chef::EncryptedDataBagItem::Encryptor do subject(:encryptor) { described_class.new(plaintext_data, key) } let(:plaintext_data) { { "foo" => "bar" } } let(:key) { "passwd" } it "encrypts to format version 1 by default" do expect(encryptor).to be_a_instance_of(Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor) end describe "generating a random IV" do it "generates a new IV for each encryption pass" do encryptor2 = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, key) # No API in ruby OpenSSL to get the iv is used for the encryption back # out. Instead we test if the encrypted data is the same. If it *is* the # same, we assume the IV was the same each time. expect(encryptor.encrypted_data).not_to eq encryptor2.encrypted_data end end describe "when encrypting a non-hash non-array value" do let(:plaintext_data) { 5 } it "serializes the value in a de-serializable way" do expect(Chef::JSONCompat.from_json(encryptor.serialized_data)["json_wrapper"]).to eq 5 end end describe "wrapping secret values in an envelope" do it "wraps the encrypted data in an envelope with the iv and version" do final_data = encryptor.for_encrypted_item expect(final_data["encrypted_data"]).to eq encryptor.encrypted_data expect(final_data["iv"]).to eq Base64.encode64(encryptor.iv) expect(final_data["version"]).to eq 1 expect(final_data["cipher"]).to eq "aes-256-cbc" end end describe "when using version 2 format" do before do Chef::Config[:data_bag_encrypt_version] = 2 end it "creates a version 2 encryptor" do expect(encryptor).to be_a_instance_of(Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor) end it "generates an hmac based on ciphertext with different iv" do encryptor2 = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, key) expect(encryptor.hmac).not_to eq(encryptor2.hmac) end it "includes the hmac in the envelope" do final_data = encryptor.for_encrypted_item expect(final_data["hmac"]).to eq(encryptor.hmac) end end describe "when using version 3 format" do before do Chef::Config[:data_bag_encrypt_version] = 3 end context "on supported platforms", :aes_256_gcm_only, ruby: "~> 2.0.0" do it "creates a version 3 encryptor" do expect(encryptor).to be_a_instance_of(Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor) end it "generates different authentication tags" do encryptor3 = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, key) encryptor.for_encrypted_item # required to generate the auth_tag encryptor3.for_encrypted_item expect(encryptor.auth_tag).not_to eq(encryptor3.auth_tag) end it "includes the auth_tag in the envelope" do final_data = encryptor.for_encrypted_item expect(final_data["auth_tag"]).to eq(Base64.encode64(encryptor.auth_tag)) end it "throws an error if auth tag is read before encrypting the data" do expect { encryptor.auth_tag }.to raise_error(Chef::EncryptedDataBagItem::EncryptionFailure) end end # context on supported platforms context "on unsupported platforms" do let(:aead_algorithm) { Chef::EncryptedDataBagItem::AEAD_ALGORITHM } it "throws an error warning about the OpenSSL version if it has no GCM support" do # Force Ruby with AEAD support allow(OpenSSL::Cipher).to receive(:method_defined?).with(:auth_data=).and_return(true) # OpenSSL without AEAD support expect(OpenSSL::Cipher).to receive(:ciphers).and_return([]) expect { encryptor }.to raise_error(Chef::EncryptedDataBagItem::EncryptedDataBagRequirementsFailure, /requires an OpenSSL/) end context "on platforms with old OpenSSL", :openssl_lt_101 do it "throws an error warning about the OpenSSL version" do expect { encryptor }.to raise_error(Chef::EncryptedDataBagItem::EncryptedDataBagRequirementsFailure, /requires an OpenSSL/) end end # context on platforms with old OpenSSL end # context on unsupported platforms end # when using version 3 format end describe Chef::EncryptedDataBagItem::Decryptor do subject(:decryptor) { described_class.for(encrypted_value, decryption_key) } let(:plaintext_data) { { "foo" => "bar" } } let(:encryption_key) { "passwd" } let(:decryption_key) { encryption_key } let(:json_wrapped_data) { Chef::JSONCompat.to_json({ "json_wrapper" => plaintext_data }) } shared_examples "decryption examples" do it "decrypts the encrypted value" do expect(decryptor.decrypted_data).to eq(json_wrapped_data) end it "unwraps the encrypted data and returns it" do expect(decryptor.for_decrypted_item).to eq plaintext_data end end context "when decrypting a version 3 (JSON+aes-256-gcm+random iv+auth tag) encrypted value" do context "on supported platforms", :aes_256_gcm_only, ruby: "~> 2.0.0" do let(:encrypted_value) do Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.new(plaintext_data, encryption_key).for_encrypted_item end let(:bogus_auth_tag) { "bogus_auth_tag" } include_examples "decryption examples" it "rejects the data if the authentication tag is wrong" do encrypted_value["auth_tag"] = bogus_auth_tag expect { decryptor.for_decrypted_item }.to raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) end it "rejects the data if the authentication tag is missing" do encrypted_value.delete("auth_tag") expect { decryptor.for_decrypted_item }.to raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) end end # context on supported platforms context "on unsupported platforms" do let(:encrypted_value) do { "encrypted_data" => "", "iv" => "", "version" => 3, "cipher" => "aes-256-cbc", } end context "on platforms with old OpenSSL", :openssl_lt_101 do it "throws an error warning about the OpenSSL version" do expect { decryptor }.to raise_error(Chef::EncryptedDataBagItem::EncryptedDataBagRequirementsFailure, /requires an OpenSSL/) end end # context on unsupported platforms end # context on platforms with old OpenSSL end # context when decrypting a version 3 context "when decrypting a version 2 (JSON+aes-256-cbc+hmac-sha256+random iv) encrypted value" do let(:encrypted_value) do Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.new(plaintext_data, encryption_key).for_encrypted_item end let(:bogus_hmac) do digest = OpenSSL::Digest.new("sha256") raw_hmac = OpenSSL::HMAC.digest(digest, "WRONG", encrypted_value["encrypted_data"]) Base64.encode64(raw_hmac) end include_examples "decryption examples" it "rejects the data if the hmac is wrong" do encrypted_value["hmac"] = bogus_hmac expect { decryptor.for_decrypted_item }.to raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) end it "rejects the data if the hmac is missing" do encrypted_value.delete("hmac") expect { decryptor.for_decrypted_item }.to raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) end end context "when decrypting a version 1 (JSON+aes-256-cbc+random iv) encrypted value" do let(:encrypted_value) do Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, encryption_key).for_encrypted_item end it "selects the correct strategy for version 1" do expect(decryptor).to be_a_instance_of Chef::EncryptedDataBagItem::Decryptor::Version1Decryptor end include_examples "decryption examples" describe "and the decryption step returns invalid data" do it "raises a decryption failure error" do # Over a large number of tests on a variety of systems, we occasionally # see the decryption step "succeed" but return invalid data (e.g., not # the original plain text) [CHEF-3858] expect(decryptor).to receive(:decrypted_data).and_return("lksajdf") expect { decryptor.for_decrypted_item }.to raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) end end context "and the provided key is incorrect" do let(:decryption_key) { "wrong-passwd" } it "raises a sensible error" do expect { decryptor.for_decrypted_item }.to raise_error(Chef::EncryptedDataBagItem::DecryptionFailure) end end context "and the cipher is not supported" do let(:encrypted_value) do ev = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, encryption_key).for_encrypted_item ev["cipher"] = "aes-256-foo" ev end it "raises a sensible error" do expect { decryptor.for_decrypted_item }.to raise_error(Chef::EncryptedDataBagItem::UnsupportedCipher) end end context "and version 2 format is required" do before do Chef::Config[:data_bag_decrypt_minimum_version] = 2 end it "raises an error attempting to decrypt" do expect { decryptor }.to raise_error(Chef::EncryptedDataBagItem::UnacceptableEncryptedDataBagItemFormat) end end end context "when decrypting a version 0 (YAML+aes-256-cbc+no iv) encrypted value", :not_supported_under_fips do let(:encrypted_value) do Version0Encryptor.encrypt_value(plaintext_data, encryption_key) end it "selects the correct strategy for version 0" do expect(decryptor).to be_a_instance_of(Chef::EncryptedDataBagItem::Decryptor::Version0Decryptor) end it "decrypts the encrypted value" do expect(decryptor.for_decrypted_item).to eq plaintext_data end context "and version 1 format is required" do before do Chef::Config[:data_bag_decrypt_minimum_version] = 1 end it "raises an error attempting to decrypt" do expect { decryptor }.to raise_error(Chef::EncryptedDataBagItem::UnacceptableEncryptedDataBagItemFormat) end end end end describe Chef::EncryptedDataBagItem do subject { described_class } let(:encrypted_data_bag_item) { subject.new(encoded_data, secret) } let(:plaintext_data) do { "id" => "item_name", "greeting" => "hello", "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true } }, } end let(:secret) { "abc123SECRET" } let(:encoded_data) { subject.encrypt_data_bag_item(plaintext_data, secret) } describe "encrypting" do it "doesn't encrypt the 'id' key" do expect(encoded_data["id"]).to eq "item_name" end it "encrypts non-collection objects" do expect(encoded_data["greeting"]["version"]).to eq 1 expect(encoded_data["greeting"]).to have_key("iv") iv = encoded_data["greeting"]["iv"] encryptor = Chef::EncryptedDataBagItem::Encryptor.new("hello", secret, iv) expect(encoded_data["greeting"]["encrypted_data"]).to eq(encryptor.for_encrypted_item["encrypted_data"]) end it "encrypts nested values" do expect(encoded_data["nested"]["version"]).to eq 1 expect(encoded_data["nested"]).to have_key("iv") iv = encoded_data["nested"]["iv"] encryptor = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data["nested"], secret, iv) expect(encoded_data["nested"]["encrypted_data"]).to eq(encryptor.for_encrypted_item["encrypted_data"]) end end describe "decrypting" do it "doesn't try to decrypt 'id'" do expect(encrypted_data_bag_item["id"]).to eq(plaintext_data["id"]) end it "decrypts 'greeting'" do expect(encrypted_data_bag_item["greeting"]).to eq(plaintext_data["greeting"]) end it "decrypts 'nested'" do expect(encrypted_data_bag_item["nested"]).to eq(plaintext_data["nested"]) end it "decrypts everyting via to_hash" do expect(encrypted_data_bag_item.to_hash).to eq(plaintext_data) end it "handles missing keys gracefully" do expect(encrypted_data_bag_item["no-such-key"]).to be_nil end end describe "loading" do it "should defer to Chef::DataBagItem.load" do allow(Chef::DataBagItem).to receive(:load).with(:the_bag, "my_codes").and_return(encoded_data) edbi = Chef::EncryptedDataBagItem.load(:the_bag, "my_codes", secret) expect(edbi["greeting"]).to eq(plaintext_data["greeting"]) end end describe ".load_secret" do let(:secret) { "opensesame" } context "when /var/mysecret exists" do before do allow(::File).to receive(:exist?).with("/var/mysecret").and_return(true) allow(IO).to receive(:read).with("/var/mysecret").and_return(secret) end it "load_secret('/var/mysecret') reads the secret" do expect(Chef::EncryptedDataBagItem.load_secret("/var/mysecret")).to eq secret end end context "when /etc/chef/encrypted_data_bag_secret exists" do before do path = Chef::Config.platform_specific_path("/etc/chef/encrypted_data_bag_secret") allow(::File).to receive(:exist?).with(path).and_return(true) allow(IO).to receive(:read).with(path).and_return(secret) end it "load_secret(nil) reads the secret" do expect(Chef::EncryptedDataBagItem.load_secret(nil)).to eq secret end end context "when /etc/chef/encrypted_data_bag_secret does not exist" do before do path = Chef::Config.platform_specific_path("/etc/chef/encrypted_data_bag_secret") allow(::File).to receive(:exist?).with(path).and_return(false) end it "load_secret(nil) emits a reasonable error message" do expect { Chef::EncryptedDataBagItem.load_secret(nil) }.to raise_error(ArgumentError, /No secret specified and no secret found at #{Chef::Config[:encrypted_data_bag_secret]}/) end end context "path argument is a URL" do before do allow(Kernel).to receive(:open).with("http://www.opscode.com/").and_return(StringIO.new(secret)) end it "reads from the URL" do expect(Chef::EncryptedDataBagItem.load_secret("http://www.opscode.com/")).to eq secret end end end end chef-12.14.60/spec/unit/environment_spec.rb000066400000000000000000000445151276456504500205150ustar00rootroot00000000000000# # Author:: Stephen Delano () # Author:: Seth Falcon () # Author:: John Keiser () # Author:: Kyle Goodwin () # Copyright:: Copyright 2010-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/environment" describe Chef::Environment do before(:each) do @environment = Chef::Environment.new end describe "initialize" do it "should be a Chef::Environment" do expect(@environment).to be_a_kind_of(Chef::Environment) end end describe "name" do it "should let you set the name to a string" do expect(@environment.name("production")).to eq("production") end it "should return the current name" do @environment.name("production") expect(@environment.name).to eq("production") end it "should not accept spaces" do expect { @environment.name("production environment") }.to raise_error(ArgumentError) end it "should not accept anything but strings" do expect { @environment.name(Array.new) }.to raise_error(ArgumentError) expect { @environment.name(Hash.new) }.to raise_error(ArgumentError) expect { @environment.name(2) }.to raise_error(ArgumentError) end end describe "description" do it "should let you set the description to a string" do expect(@environment.description("this is my test environment")).to eq("this is my test environment") end it "should return the correct description" do @environment.description("I like running tests") expect(@environment.description).to eq("I like running tests") end it "should not accept anything but strings" do expect { @environment.description(Array.new) }.to raise_error(ArgumentError) expect { @environment.description(Hash.new) }.to raise_error(ArgumentError) expect { @environment.description(42) }.to raise_error(ArgumentError) end end describe "default attributes" do it "should let you set the attributes hash explicitly" do expect(@environment.default_attributes({ :one => "two" })).to eq({ :one => "two" }) end it "should let you return the attributes hash" do @environment.default_attributes({ :one => "two" }) expect(@environment.default_attributes).to eq({ :one => "two" }) end it "should throw an ArgumentError if we aren't a kind of hash" do expect { @environment.default_attributes(Array.new) }.to raise_error(ArgumentError) end end describe "override attributes" do it "should let you set the attributes hash explicitly" do expect(@environment.override_attributes({ :one => "two" })).to eq({ :one => "two" }) end it "should let you return the attributes hash" do @environment.override_attributes({ :one => "two" }) expect(@environment.override_attributes).to eq({ :one => "two" }) end it "should throw an ArgumentError if we aren't a kind of hash" do expect { @environment.override_attributes(Array.new) }.to raise_error(ArgumentError) end end describe "cookbook_versions" do before(:each) do @cookbook_versions = { "apt" => "= 1.0.0", "god" => "= 2.0.0", "apache2" => "= 4.2.0", } end it "should let you set the cookbook versions in a hash" do expect(@environment.cookbook_versions(@cookbook_versions)).to eq(@cookbook_versions) end it "should return the cookbook versions" do @environment.cookbook_versions(@cookbook_versions) expect(@environment.cookbook_versions).to eq(@cookbook_versions) end it "should not accept anything but a hash" do expect { @environment.cookbook_versions("I am a string!") }.to raise_error(ArgumentError) expect { @environment.cookbook_versions(Array.new) }.to raise_error(ArgumentError) expect { @environment.cookbook_versions(42) }.to raise_error(ArgumentError) end it "should validate the hash" do expect(Chef::Environment).to receive(:validate_cookbook_versions).with(@cookbook_versions).and_return true @environment.cookbook_versions(@cookbook_versions) end end describe "cookbook" do it "should set the version of the cookbook in the cookbook_versions hash" do @environment.cookbook("apt", "~> 1.2.3") expect(@environment.cookbook_versions["apt"]).to eq("~> 1.2.3") end it "should validate the cookbook version it is passed" do expect(Chef::Environment).to receive(:validate_cookbook_version).with(">= 1.2.3").and_return true @environment.cookbook("apt", ">= 1.2.3") end end describe "update_from!" do before(:each) do @environment.name("prod") @environment.description("this is prod") @environment.cookbook_versions({ "apt" => "= 1.2.3" }) @example = Chef::Environment.new @example.name("notevenprod") @example.description("this is pre-prod") @example.cookbook_versions({ "apt" => "= 2.3.4" }) end it "should update everything but name" do @environment.update_from!(@example) expect(@environment.name).to eq("prod") expect(@environment.description).to eq(@example.description) expect(@environment.cookbook_versions).to eq(@example.cookbook_versions) end end describe "to_hash" do before(:each) do @environment.name("spec") @environment.description("Where we run the spec tests") @environment.cookbook_versions({ :apt => "= 1.2.3" }) @hash = @environment.to_hash end %w{name description cookbook_versions}.each do |t| it "should include '#{t}'" do expect(@hash[t]).to eq(@environment.send(t.to_sym)) end end it "should include 'json_class'" do expect(@hash["json_class"]).to eq("Chef::Environment") end it "should include 'chef_type'" do expect(@hash["chef_type"]).to eq("environment") end end describe "to_json" do before(:each) do @environment.name("spec") @environment.description("Where we run the spec tests") @environment.cookbook_versions({ :apt => "= 1.2.3" }) @json = @environment.to_json end %w{name description cookbook_versions}.each do |t| it "should include '#{t}'" do expect(@json).to match(/"#{t}":#{Regexp.escape(Chef::JSONCompat.to_json(@environment.send(t.to_sym)))}/) end end it "should include 'json_class'" do expect(@json).to match(/"json_class":"Chef::Environment"/) end it "should include 'chef_type'" do expect(@json).to match(/"chef_type":"environment"/) end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @environment } end end describe "from_json" do before(:each) do @data = { "name" => "production", "description" => "We are productive", "cookbook_versions" => { "apt" => "= 1.2.3", "god" => ">= 4.2.0", "apache2" => "= 2.0.0", }, "json_class" => "Chef::Environment", "chef_type" => "environment", } @environment = Chef::Environment.from_hash(Chef::JSONCompat.parse(Chef::JSONCompat.to_json(@data))) end it "should return a Chef::Environment" do expect(@environment).to be_a_kind_of(Chef::Environment) end %w{name description cookbook_versions}.each do |t| it "should match '#{t}'" do expect(@environment.send(t.to_sym)).to eq(@data[t]) end end end describe "self.validate_cookbook_versions" do before(:each) do @cookbook_versions = { "apt" => "= 1.0.0", "god" => "= 2.0.0", "apache2" => "= 4.2.0", } end it "should validate the version string of each cookbook" do @cookbook_versions.each do |cookbook, version| expect(Chef::Environment).to receive(:validate_cookbook_version).with(version).and_return true end Chef::Environment.validate_cookbook_versions(@cookbook_versions) end it "should return false if anything other than a hash is passed as the argument" do expect(Chef::Environment.validate_cookbook_versions(Array.new)).to eq(false) expect(Chef::Environment.validate_cookbook_versions(42)).to eq(false) expect(Chef::Environment.validate_cookbook_versions(Chef::CookbookVersion.new("meta"))).to eq(false) expect(Chef::Environment.validate_cookbook_versions("cookbook => 1.2.3")).to eq(false) end end describe "self.validate_cookbook_version" do it "should validate correct version numbers" do expect(Chef::Environment.validate_cookbook_version("= 1.2.3")).to eq(true) expect(Chef::Environment.validate_cookbook_version("=1.2.3")).to eq(true) expect(Chef::Environment.validate_cookbook_version(">= 0.0.3")).to eq(true) expect(Chef::Environment.validate_cookbook_version(">=0.0.3")).to eq(true) # A lone version is allowed, interpreted as implicit '=' expect(Chef::Environment.validate_cookbook_version("1.2.3")).to eq(true) end it "should return false when an invalid version is given" do expect(Chef::Environment.validate_cookbook_version(Chef::CookbookVersion.new("meta"))).to eq(false) expect(Chef::Environment.validate_cookbook_version("= 1.2.3a")).to eq(false) expect(Chef::Environment.validate_cookbook_version("=1.2.3a")).to eq(false) expect(Chef::Environment.validate_cookbook_version("= 1")).to eq(false) expect(Chef::Environment.validate_cookbook_version("=1")).to eq(false) expect(Chef::Environment.validate_cookbook_version("= a")).to eq(false) expect(Chef::Environment.validate_cookbook_version("=a")).to eq(false) expect(Chef::Environment.validate_cookbook_version("= 1.2.3.4")).to eq(false) expect(Chef::Environment.validate_cookbook_version("=1.2.3.4")).to eq(false) end describe "in solo mode" do before do Chef::Config[:solo_legacy_mode] = true end after do Chef::Config[:solo_legacy_mode] = false end it "should raise and exception" do expect do Chef::Environment.validate_cookbook_version("= 1.2.3.4") end.to raise_error Chef::Exceptions::IllegalVersionConstraint, "Environment cookbook version constraints not allowed in chef-solo" end end end describe "when updating from a parameter hash" do before do @environment = Chef::Environment.new end it "updates the name from parameters[:name]" do @environment.update_from_params(:name => "kurrupt") expect(@environment.name).to eq("kurrupt") end it "validates the name given in the params" do expect(@environment.update_from_params(:name => "@$%^&*()")).to be_falsey expect(@environment.invalid_fields[:name]).to eq(%q{Option name's value @$%^&*() does not match regular expression /^[\-[:alnum:]_]+$/}) end it "updates the description from parameters[:description]" do @environment.update_from_params(:description => "wow, writing your own object mapper is kinda painful") expect(@environment.description).to eq("wow, writing your own object mapper is kinda painful") end it "updates cookbook version constraints from the hash in parameters[:cookbook_version_constraints]" do # NOTE: I'm only choosing this (admittedly weird) structure for the hash b/c the better more obvious # one, i.e, {:cookbook_version_constraints => {COOKBOOK_NAME => CONSTRAINT}} is difficult to implement # the way merb does params params = { :name => "superbowl", :cookbook_version => { "0" => "apache2 ~> 1.0.0", "1" => "nginx < 2.0.0" } } @environment.update_from_params(params) expect(@environment.cookbook_versions).to eq({ "apache2" => "~> 1.0.0", "nginx" => "< 2.0.0" }) end it "validates the cookbook constraints" do params = { :cookbook_version => { "0" => "apache2 >>> 1.0.0" } } expect(@environment.update_from_params(params)).to be_falsey err_msg = @environment.invalid_fields[:cookbook_version]["0"] expect(err_msg).to eq("apache2 >>> 1.0.0 is not a valid cookbook constraint") end it "is not valid if the name is not present" do expect(@environment.validate_required_attrs_present).to be_falsey expect(@environment.invalid_fields[:name]).to eq("name cannot be empty") end it "is not valid after updating from params if the name is not present" do expect(@environment.update_from_params({})).to be_falsey expect(@environment.invalid_fields[:name]).to eq("name cannot be empty") end it "updates default attributes from a JSON string in params[:attributes]" do @environment.update_from_params(:name => "fuuu", :default_attributes => %q|{"fuuu":"RAGE"}|) expect(@environment.default_attributes).to eq({ "fuuu" => "RAGE" }) end it "updates override attributes from a JSON string in params[:attributes]" do @environment.update_from_params(:name => "fuuu", :override_attributes => %q|{"foo":"override"}|) expect(@environment.override_attributes).to eq({ "foo" => "override" }) end end describe "api model" do before(:each) do @rest = double("Chef::ServerAPI") allow(Chef::ServerAPI).to receive(:new).and_return(@rest) @query = double("Chef::Search::Query") allow(Chef::Search::Query).to receive(:new).and_return(@query) end describe "list" do describe "inflated" do it "should return a hash of environment names and objects" do e1 = double("Chef::Environment", :name => "one") expect(@query).to receive(:search).with(:environment).and_yield(e1) r = Chef::Environment.list(true) expect(r["one"]).to eq(e1) end end it "should return a hash of environment names and urls" do expect(@rest).to receive(:get).and_return({ "one" => "http://foo" }) r = Chef::Environment.list expect(r["one"]).to eq("http://foo") end end end describe "when loading" do describe "in solo mode" do before do Chef::Config[:solo_legacy_mode] = true Chef::Config[:environment_path] = "/var/chef/environments" end after do Chef::Config[:solo_legacy_mode] = false end it "should get the environment from the environment_path" do expect(File).to receive(:directory?).with(Chef::Config[:environment_path]).and_return(true) expect(File).to receive(:exists?).with(File.join(Chef::Config[:environment_path], "foo.json")).and_return(false) expect(File).to receive(:exists?).with(File.join(Chef::Config[:environment_path], "foo.rb")).exactly(2).times.and_return(true) expect(File).to receive(:readable?).with(File.join(Chef::Config[:environment_path], "foo.rb")).and_return(true) role_dsl = "name \"foo\"\ndescription \"desc\"\n" expect(IO).to receive(:read).with(File.join(Chef::Config[:environment_path], "foo.rb")).and_return(role_dsl) Chef::Environment.load("foo") end it "should return a Chef::Environment object from JSON" do expect(File).to receive(:directory?).with(Chef::Config[:environment_path]).and_return(true) expect(File).to receive(:exists?).with(File.join(Chef::Config[:environment_path], "foo.json")).and_return(true) environment_hash = { "name" => "foo", "default_attributes" => { "foo" => { "bar" => 1, }, }, "json_class" => "Chef::Environment", "description" => "desc", "chef_type" => "environment", } expect(IO).to receive(:read).with(File.join(Chef::Config[:environment_path], "foo.json")).and_return(Chef::JSONCompat.to_json(environment_hash)) environment = Chef::Environment.load("foo") expect(environment).to be_a_kind_of(Chef::Environment) expect(environment.name).to eq(environment_hash["name"]) expect(environment.description).to eq(environment_hash["description"]) expect(environment.default_attributes).to eq(environment_hash["default_attributes"]) end it "should return a Chef::Environment object from Ruby DSL" do expect(File).to receive(:directory?).with(Chef::Config[:environment_path]).and_return(true) expect(File).to receive(:exists?).with(File.join(Chef::Config[:environment_path], "foo.json")).and_return(false) expect(File).to receive(:exists?).with(File.join(Chef::Config[:environment_path], "foo.rb")).exactly(2).times.and_return(true) expect(File).to receive(:readable?).with(File.join(Chef::Config[:environment_path], "foo.rb")).and_return(true) role_dsl = "name \"foo\"\ndescription \"desc\"\n" expect(IO).to receive(:read).with(File.join(Chef::Config[:environment_path], "foo.rb")).and_return(role_dsl) environment = Chef::Environment.load("foo") expect(environment).to be_a_kind_of(Chef::Environment) expect(environment.name).to eq("foo") expect(environment.description).to eq("desc") end it "should raise an error if the configured environment_path is invalid" do expect(File).to receive(:directory?).with(Chef::Config[:environment_path]).and_return(false) expect do Chef::Environment.load("foo") end.to raise_error Chef::Exceptions::InvalidEnvironmentPath, "Environment path '/var/chef/environments' is invalid" end it "should raise an error if the file does not exist" do expect(File).to receive(:directory?).with(Chef::Config[:environment_path]).and_return(true) expect(File).to receive(:exists?).with(File.join(Chef::Config[:environment_path], "foo.json")).and_return(false) expect(File).to receive(:exists?).with(File.join(Chef::Config[:environment_path], "foo.rb")).and_return(false) expect do Chef::Environment.load("foo") end.to raise_error Chef::Exceptions::EnvironmentNotFound, "Environment 'foo' could not be loaded from disk" end end end end chef-12.14.60/spec/unit/event_dispatch/000077500000000000000000000000001276456504500176015ustar00rootroot00000000000000chef-12.14.60/spec/unit/event_dispatch/dispatcher_spec.rb000066400000000000000000000103741276456504500232730ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/event_dispatch/dispatcher" describe Chef::EventDispatch::Dispatcher do subject(:dispatcher) { Chef::EventDispatch::Dispatcher.new } let(:event_sink) { instance_double("Chef::EventDispatch::Base") } it "has no subscribers by default" do expect(dispatcher.subscribers).to be_empty end context "when an event sink is registered" do before do dispatcher.register(event_sink) end it "it has the event sink as a subscriber" do expect(dispatcher.subscribers.size).to eq(1) expect(dispatcher.subscribers.first).to eq(event_sink) end it "forwards events to the subscribed event sink" do # the events all have different arity and such so we just hit a few different events: expect(event_sink).to receive(:run_start).with("12.4.0") dispatcher.run_start("12.4.0") cookbook_version = double("cookbook_version") expect(event_sink).to receive(:synchronized_cookbook).with("apache2", cookbook_version) dispatcher.synchronized_cookbook("apache2", cookbook_version) exception = StandardError.new("foo") expect(event_sink).to receive(:recipe_file_load_failed).with("/path/to/file.rb", exception, "myrecipe") dispatcher.recipe_file_load_failed("/path/to/file.rb", exception, "myrecipe") end context "when an event sink has fewer arguments for an event" do # Can't use a double because they don't report arity correctly. let(:event_sink) do Class.new(Chef::EventDispatch::Base) do attr_reader :synchronized_cookbook_args def synchronized_cookbook(cookbook_name) @synchronized_cookbook_args = [cookbook_name] end end.new end it "trims the arugment list" do cookbook_version = double("cookbook_version") dispatcher.synchronized_cookbook("apache2", cookbook_version) expect(event_sink.synchronized_cookbook_args).to eq ["apache2"] end end end context "when two event sinks have different arguments for an event" do let(:event_sink_1) do Class.new(Chef::EventDispatch::Base) do attr_reader :synchronized_cookbook_args def synchronized_cookbook(cookbook_name) @synchronized_cookbook_args = [cookbook_name] end end.new end let(:event_sink_2) do Class.new(Chef::EventDispatch::Base) do attr_reader :synchronized_cookbook_args def synchronized_cookbook(cookbook_name, cookbook) @synchronized_cookbook_args = [cookbook_name, cookbook] end end.new end context "and the one with fewer arguments comes first" do before do dispatcher.register(event_sink_1) dispatcher.register(event_sink_2) end it "trims the arugment list" do cookbook_version = double("cookbook_version") dispatcher.synchronized_cookbook("apache2", cookbook_version) expect(event_sink_1.synchronized_cookbook_args).to eq ["apache2"] expect(event_sink_2.synchronized_cookbook_args).to eq ["apache2", cookbook_version] end end context "and the one with fewer arguments comes last" do before do dispatcher.register(event_sink_2) dispatcher.register(event_sink_1) end it "trims the arugment list" do cookbook_version = double("cookbook_version") dispatcher.synchronized_cookbook("apache2", cookbook_version) expect(event_sink_1.synchronized_cookbook_args).to eq ["apache2"] expect(event_sink_2.synchronized_cookbook_args).to eq ["apache2", cookbook_version] end end end end chef-12.14.60/spec/unit/event_dispatch/dsl_spec.rb000066400000000000000000000044121276456504500217230ustar00rootroot00000000000000# # Author:: Ranjib Dey () # # Copyright:: Copyright 2015-2016, Ranjib Dey # 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/event_dispatch/dsl" describe Chef::EventDispatch::DSL do let(:events) do Chef::EventDispatch::Dispatcher.new end let(:run_context) do Chef::RunContext.new(Chef::Node.new, nil, events) end before do Chef.set_run_context(run_context) end subject { described_class.new("test") } it "set handler name" do subject.on(:run_started) {} expect(events.subscribers.first.name).to eq("test") end it "raise error when invalid event type is supplied" do expect do subject.on(:foo_bar) {} end.to raise_error(Chef::Exceptions::InvalidEventType) end it "register user hooks against valid event type" do subject.on(:run_failed) { "testhook" } expect(events.subscribers.first.run_failed).to eq("testhook") end it "preserve state across event hooks" do calls = [] Chef.event_handler do on :resource_updated do calls << :updated end on :resource_action_start do calls << :started end end resource = Chef::Resource::RubyBlock.new("foo", run_context) resource.block {} resource.run_action(:run) expect(calls).to eq([:started, :updated]) end it "preserve instance variables across handler callbacks" do Chef.event_handler do on :resource_action_start do @ivar = [1] end on :resource_updated do @ivar << 2 end end resource = Chef::Resource::RubyBlock.new("foo", run_context) resource.block {} resource.run_action(:run) expect(events.subscribers.first.instance_variable_get(:@ivar)).to eq([1, 2]) end end chef-12.14.60/spec/unit/exceptions_spec.rb000066400000000000000000000124421276456504500203240ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Author:: Christopher Walters () # Author:: Kyle Goodwin () # Copyright:: Copyright 2010-2016, Thomas Bishop # Copyright:: Copyright 2010-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::Exceptions do exception_to_super_class = { Chef::Exceptions::Application => RuntimeError, Chef::Exceptions::Cron => RuntimeError, Chef::Exceptions::Env => RuntimeError, Chef::Exceptions::Exec => RuntimeError, Chef::Exceptions::FileNotFound => RuntimeError, Chef::Exceptions::Package => RuntimeError, Chef::Exceptions::Service => RuntimeError, Chef::Exceptions::Route => RuntimeError, Chef::Exceptions::SearchIndex => RuntimeError, Chef::Exceptions::Override => RuntimeError, Chef::Exceptions::UnsupportedAction => RuntimeError, Chef::Exceptions::MissingLibrary => RuntimeError, Chef::Exceptions::MissingRole => RuntimeError, Chef::Exceptions::CannotDetermineNodeName => RuntimeError, Chef::Exceptions::User => RuntimeError, Chef::Exceptions::Group => RuntimeError, Chef::Exceptions::Link => RuntimeError, Chef::Exceptions::Mount => RuntimeError, Chef::Exceptions::PrivateKeyMissing => RuntimeError, Chef::Exceptions::CannotWritePrivateKey => RuntimeError, Chef::Exceptions::RoleNotFound => RuntimeError, Chef::Exceptions::ValidationFailed => ArgumentError, Chef::Exceptions::InvalidPrivateKey => ArgumentError, Chef::Exceptions::ConfigurationError => ArgumentError, Chef::Exceptions::RedirectLimitExceeded => RuntimeError, Chef::Exceptions::AmbiguousRunlistSpecification => ArgumentError, Chef::Exceptions::CookbookNotFound => RuntimeError, Chef::Exceptions::AttributeNotFound => RuntimeError, Chef::Exceptions::InvalidCommandOption => RuntimeError, Chef::Exceptions::CommandTimeout => RuntimeError, Mixlib::ShellOut::ShellCommandFailed => RuntimeError, Chef::Exceptions::RequestedUIDUnavailable => RuntimeError, Chef::Exceptions::InvalidHomeDirectory => ArgumentError, Chef::Exceptions::DsclCommandFailed => RuntimeError, Chef::Exceptions::UserIDNotFound => ArgumentError, Chef::Exceptions::GroupIDNotFound => ArgumentError, Chef::Exceptions::InvalidResourceReference => RuntimeError, Chef::Exceptions::ResourceNotFound => RuntimeError, Chef::Exceptions::InvalidResourceSpecification => ArgumentError, Chef::Exceptions::SolrConnectionError => RuntimeError, Chef::Exceptions::InvalidDataBagPath => ArgumentError, Chef::Exceptions::InvalidEnvironmentPath => ArgumentError, Chef::Exceptions::EnvironmentNotFound => RuntimeError, Chef::Exceptions::InvalidVersionConstraint => ArgumentError, Chef::Exceptions::IllegalVersionConstraint => NotImplementedError, } exception_to_super_class.each do |exception, expected_super_class| it "should have an exception class of #{exception} which inherits from #{expected_super_class}" do expect { raise exception }.to raise_error(expected_super_class) end if exception.methods.include?(:to_json) include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { exception } end end end describe Chef::Exceptions::RunFailedWrappingError do shared_examples "RunFailedWrappingError expectations" do it "should initialize with a default message" do expect(e.message).to eq("Found #{num_errors} errors, they are stored in the backtrace") end it "should provide a modified backtrace when requested" do e.fill_backtrace expect(e.backtrace).to eq(backtrace) end end context "initialized with nothing" do let(:e) { Chef::Exceptions::RunFailedWrappingError.new } let(:num_errors) { 0 } let(:backtrace) { [] } include_examples "RunFailedWrappingError expectations" end context "initialized with nil" do let(:e) { Chef::Exceptions::RunFailedWrappingError.new(nil, nil) } let(:num_errors) { 0 } let(:backtrace) { [] } include_examples "RunFailedWrappingError expectations" end context "initialized with 1 error and nil" do let(:e) { Chef::Exceptions::RunFailedWrappingError.new(RuntimeError.new("foo"), nil) } let(:num_errors) { 1 } let(:backtrace) { ["1) RuntimeError - foo"] } include_examples "RunFailedWrappingError expectations" end context "initialized with 2 errors" do let(:e) { Chef::Exceptions::RunFailedWrappingError.new(RuntimeError.new("foo"), RuntimeError.new("bar")) } let(:num_errors) { 2 } let(:backtrace) { ["1) RuntimeError - foo", "", "2) RuntimeError - bar"] } include_examples "RunFailedWrappingError expectations" end end end chef-12.14.60/spec/unit/file_access_control_spec.rb000066400000000000000000000301151276456504500221400ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 "ostruct" describe Chef::FileAccessControl do describe "Unix" do before do platform_mock :unix do # we have to re-load the file so the proper # platform specific module is mixed in @node = Chef::Node.new load File.join(File.dirname(__FILE__), "..", "..", "lib", "chef", "file_access_control.rb") @resource = Chef::Resource::File.new("/tmp/a_file.txt") @resource.owner("toor") @resource.group("wheel") @resource.mode("0400") @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @current_resource = Chef::Resource::File.new("/tmp/different_file.txt") @provider_requirements = Chef::Provider::ResourceRequirements.new(@resource, @run_context) @provider = double("File provider", :requirements => @provider_requirements, :manage_symlink_access? => false) @fac = Chef::FileAccessControl.new(@current_resource, @resource, @provider) end end describe "class methods" do it "responds to #writable?" do expect(Chef::FileAccessControl).to respond_to(:writable?) end end it "has a resource" do expect(@fac.resource).to equal(@resource) end it "has a file to manage" do expect(@fac.file).to eq("/tmp/different_file.txt") end it "is not modified yet" do expect(@fac).not_to be_modified end it "determines the uid of the owner specified by the resource" do expect(Etc).to receive(:getpwnam).with("toor").and_return(OpenStruct.new(:uid => 2342)) expect(@fac.target_uid).to eq(2342) end it "raises a Chef::Exceptions::UserIDNotFound error when Etc can't find the user's name" do expect(Etc).to receive(:getpwnam).with("toor").and_raise(ArgumentError) expect { @fac.target_uid; @provider_requirements.run(:create) }.to raise_error(Chef::Exceptions::UserIDNotFound, "cannot determine user id for 'toor', does the user exist on this system?") end it "does not attempt to resolve the uid if the user is not specified" do resource = Chef::Resource::File.new("a file") fac = Chef::FileAccessControl.new(@current_resource, resource, @provider) expect(fac.target_uid).to be_nil end it "does not want to update the owner if none is specified" do resource = Chef::Resource::File.new("a file") fac = Chef::FileAccessControl.new(@current_resource, resource, @provider) expect(fac.should_update_owner?).to be_falsey end it "raises an ArgumentError if the resource's owner is set to something wack" do @resource.instance_variable_set(:@owner, :diaf) expect { @fac.target_uid; @provider_requirements.run(:create) }.to raise_error(ArgumentError) end it "uses the resource's uid for the target uid when the resource's owner is specified by an integer" do @resource.owner(2342) expect(@fac.target_uid).to eq(2342) end it "wraps uids to their negative complements to correctly handle negative uids" do # More: Mac OS X (at least) has negative UIDs for 'nobody' and some other # users. Ruby doesn't believe in negative UIDs so you get the diminished radix # complement (i.e., it wraps around the maximum size of C unsigned int) of these # uids. So we have to get ruby and negative uids to smoke the peace pipe # with each other. @resource.owner("nobody") expect(Etc).to receive(:getpwnam).with("nobody").and_return(OpenStruct.new(:uid => (4294967294))) expect(@fac.target_uid).to eq(-2) end it "does not wrap uids to their negative complements beyond -9" do # More: when OSX userIDs are created by ActiveDirectory sync, it tends to use huge numbers # which had been incorrectly wrapped. It does not look like the OSX IDs go below -2 @resource.owner("bigdude") expect(Etc).to receive(:getpwnam).with("bigdude").and_return(OpenStruct.new(:uid => (4294967286))) expect(@fac.target_uid).to eq(4294967286) end it "wants to update the owner when the current owner is nil (creating a file)" do @current_resource.owner(nil) @resource.owner(2342) expect(@fac.should_update_owner?).to be_truthy end it "wants to update the owner when the current owner doesn't match desired" do @current_resource.owner(3224) @resource.owner(2342) expect(@fac.should_update_owner?).to be_truthy end it "includes updating ownership in its list of desired changes" do resource = Chef::Resource::File.new("a file") resource.owner(2342) @current_resource.owner(100) fac = Chef::FileAccessControl.new(@current_resource, resource, @provider) expect(fac.describe_changes).to eq(["change owner from '100' to '2342'"]) end it "sets the file's owner as specified in the resource when the current owner is incorrect" do @resource.owner(2342) expect(File).to receive(:chown).with(2342, nil, "/tmp/different_file.txt") @fac.set_owner expect(@fac).to be_modified end it "doesn't set the file's owner if it already matches" do @resource.owner(2342) @current_resource.owner(2342) expect(File).not_to receive(:chown) @fac.set_owner expect(@fac).not_to be_modified end it "doesn't want to update a file's owner when it's already correct" do @resource.owner(2342) @current_resource.owner(2342) expect(@fac.should_update_owner?).to be_falsey end it "determines the gid of the group specified by the resource" do expect(Etc).to receive(:getgrnam).with("wheel").and_return(OpenStruct.new(:gid => 2342)) expect(@fac.target_gid).to eq(2342) end it "uses a user specified gid as the gid" do @resource.group(2342) expect(@fac.target_gid).to eq(2342) end it "raises a Chef::Exceptions::GroupIDNotFound error when Etc can't find the user's name" do expect(Etc).to receive(:getgrnam).with("wheel").and_raise(ArgumentError) expect { @fac.target_gid; @provider_requirements.run(:create) }.to raise_error(Chef::Exceptions::GroupIDNotFound, "cannot determine group id for 'wheel', does the group exist on this system?") end it "does not attempt to resolve a gid when none is supplied" do resource = Chef::Resource::File.new("crab") fac = Chef::FileAccessControl.new(@current_resource, resource, @provider) expect(fac.target_gid).to be_nil end it "does not want to update the group when no target group is specified" do resource = Chef::Resource::File.new("crab") fac = Chef::FileAccessControl.new(@current_resource, resource, @provider) expect(fac.should_update_group?).to be_falsey end it "raises an error when the supplied group name is an alien" do @resource.instance_variable_set(:@group, :failburger) expect { @fac.target_gid; @provider_requirements.run(:create) }.to raise_error(ArgumentError) end it "wants to update the group when the current group is nil (creating a file)" do @resource.group(2342) @current_resource.group(nil) expect(@fac.should_update_group?).to be_truthy end it "wants to update the group when the current group doesn't match the target group" do @resource.group(2342) @current_resource.group(815) expect(@fac.should_update_group?).to be_truthy end it "includes updating the group in the list of changes" do resource = Chef::Resource::File.new("crab") resource.group(2342) @current_resource.group(815) fac = Chef::FileAccessControl.new(@current_resource, resource, @provider) expect(fac.describe_changes).to eq(["change group from '815' to '2342'"]) end it "sets the file's group as specified in the resource when the group is not correct" do @resource.group(2342) @current_resource.group(815) expect(File).to receive(:chown).with(nil, 2342, "/tmp/different_file.txt") @fac.set_group expect(@fac).to be_modified end it "doesn't want to modify the file's group when the current group is correct" do @resource.group(2342) @current_resource.group(2342) expect(@fac.should_update_group?).to be_falsey end it "doesnt set the file's group if it is already correct" do @resource.group(2342) @current_resource.group(2342) # @fac.stub(:stat).and_return(OpenStruct.new(:gid => 2342)) expect(File).not_to receive(:chown) @fac.set_group expect(@fac).not_to be_modified end it "uses the supplied mode as octal when it's a string" do @resource.mode("444") expect(@fac.target_mode).to eq(292) # octal 444 => decimal 292 end it "uses the supplied mode verbatim when it's an integer" do @resource.mode(00444) expect(@fac.target_mode).to eq(292) end it "does not try to determine the mode when none is given" do resource = Chef::Resource::File.new("blahblah") fac = Chef::FileAccessControl.new(@current_resource, resource, @provider) expect(fac.target_mode).to be_nil end it "doesn't want to update the mode when no target mode is given" do resource = Chef::Resource::File.new("blahblah") fac = Chef::FileAccessControl.new(@current_resource, resource, @provider) expect(fac.should_update_mode?).to be_falsey end it "wants to update the mode when the current mode is nil (creating a file)" do @resource.mode("0400") @current_resource.mode(nil) expect(@fac.should_update_mode?).to be_truthy end it "wants to update the mode when the desired mode does not match the current mode" do @resource.mode("0400") @current_resource.mode("0644") expect(@fac.should_update_mode?).to be_truthy end it "includes changing the mode in the list of desired changes" do resource = Chef::Resource::File.new("blahblah") resource.mode("0750") @current_resource.mode("0444") fac = Chef::FileAccessControl.new(@current_resource, resource, @provider) expect(fac.describe_changes).to eq(["change mode from '0444' to '0750'"]) end it "sets the file's mode as specified in the resource when the current modes are incorrect" do # stat returns modes like 0100644 (octal) => 33188 (decimal) #@fac.stub(:stat).and_return(OpenStruct.new(:mode => 33188)) @current_resource.mode("0644") expect(File).to receive(:chmod).with(256, "/tmp/different_file.txt") @fac.set_mode expect(@fac).to be_modified end it "does not want to update the mode when the current mode is correct" do @current_resource.mode("0400") expect(@fac.should_update_mode?).to be_falsey end it "does not set the file's mode when the current modes are correct" do #@fac.stub(:stat).and_return(OpenStruct.new(:mode => 0100400)) @current_resource.mode("0400") expect(File).not_to receive(:chmod) @fac.set_mode expect(@fac).not_to be_modified end it "sets all access controls on a file" do allow(@fac).to receive(:stat).and_return(OpenStruct.new(:owner => 99, :group => 99, :mode => 0100444)) @resource.mode(0400) @resource.owner(0) @resource.group(0) expect(File).to receive(:chmod).with(0400, "/tmp/different_file.txt") expect(File).to receive(:chown).with(0, nil, "/tmp/different_file.txt") expect(File).to receive(:chown).with(nil, 0, "/tmp/different_file.txt") @fac.set_all expect(@fac).to be_modified end end end chef-12.14.60/spec/unit/file_cache_spec.rb000066400000000000000000000072351276456504500202110ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::FileCache do before do @file_cache_path = Dir.mktmpdir Chef::Config[:file_cache_path] = @file_cache_path @io = StringIO.new end after do FileUtils.rm_rf(Chef::Config[:file_cache_path]) end describe "when the relative path to the cache file doesn't exist" do it "creates intermediate directories as needed" do Chef::FileCache.store("whiz/bang", "I found a poop") expect(File).to exist(File.join(@file_cache_path, "whiz")) end it "creates the cached file at the correct relative path" do expect(File).to receive(:open).with(File.join(@file_cache_path, "whiz", "bang"), "w", 416).and_yield(@io) Chef::FileCache.store("whiz/bang", "borkborkbork") end end describe "when storing a file" do before do allow(File).to receive(:open).and_yield(@io) end it "should print the contents to the file" do Chef::FileCache.store("whiz/bang", "borkborkbork") expect(@io.string).to eq("borkborkbork") end end describe "when loading cached files" do it "finds and reads the cached file" do FileUtils.mkdir_p(File.join(@file_cache_path, "whiz")) File.open(File.join(@file_cache_path, "whiz", "bang"), "w") { |f| f.print("borkborkbork") } expect(Chef::FileCache.load("whiz/bang")).to eq("borkborkbork") end it "should raise a Chef::Exceptions::FileNotFound if the file doesn't exist" do expect { Chef::FileCache.load("whiz/bang") }.to raise_error(Chef::Exceptions::FileNotFound) end end describe "when deleting cached files" do before(:each) do FileUtils.mkdir_p(File.join(@file_cache_path, "whiz")) File.open(File.join(@file_cache_path, "whiz", "bang"), "w") { |f| f.print("borkborkbork") } end it "unlinks the file" do Chef::FileCache.delete("whiz/bang") expect(File).not_to exist(File.join(@file_cache_path, "whiz", "bang")) end end describe "when listing files in the cache" do before(:each) do FileUtils.mkdir_p(File.join(@file_cache_path, "whiz")) FileUtils.touch(File.join(@file_cache_path, "whiz", "bang")) FileUtils.mkdir_p(File.join(@file_cache_path, "snappy")) FileUtils.touch(File.join(@file_cache_path, "snappy", "patter")) end it "should return the relative paths" do expect(Chef::FileCache.list.sort).to eq(%w{snappy/patter whiz/bang}) end it "searches for cached files by globbing" do expect(Chef::FileCache.find("snappy/**/*")).to eq(%w{snappy/patter}) end end describe "when checking for the existence of a file" do before do FileUtils.mkdir_p(File.join(@file_cache_path, "whiz")) end it "has a key if the corresponding cache file exists" do FileUtils.touch(File.join(@file_cache_path, "whiz", "bang")) expect(Chef::FileCache).to have_key("whiz/bang") end it "doesn't have a key if the corresponding cache file doesn't exist" do expect(Chef::FileCache).not_to have_key("whiz/bang") end end end chef-12.14.60/spec/unit/file_content_management/000077500000000000000000000000001276456504500214465ustar00rootroot00000000000000chef-12.14.60/spec/unit/file_content_management/deploy/000077500000000000000000000000001276456504500227425ustar00rootroot00000000000000chef-12.14.60/spec/unit/file_content_management/deploy/cp_spec.rb000066400000000000000000000025461276456504500247120ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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::FileContentManagement::Deploy::Cp do let(:content_deployer) { described_class.new } let(:target_file_path) { "/etc/my_app.conf" } describe "creating the file" do it "touches the file to create it" do expect(FileUtils).to receive(:touch).with(target_file_path) content_deployer.create(target_file_path) end end describe "updating the file" do let(:staging_file_path) { "/tmp/random-dir/staging-file.tmp" } it "copies the staging file's content" do expect(FileUtils).to receive(:cp).with(staging_file_path, target_file_path) content_deployer.deploy(staging_file_path, target_file_path) end end end chef-12.14.60/spec/unit/file_content_management/deploy/mv_unix_spec.rb000066400000000000000000000073241276456504500257740ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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::FileContentManagement::Deploy::MvUnix do let(:content_deployer) { described_class.new } let(:target_file_path) { "/etc/my_app.conf" } describe "creating the file" do it "touches the file to create it" do expect(FileUtils).to receive(:touch).with(target_file_path) content_deployer.create(target_file_path) end end describe "updating the file" do let(:staging_file_path) { "/tmp/random-dir/staging-file.tmp" } let(:target_file_mode) { 0644 } let(:target_file_stat) do double "File::Stat struct for target file", :mode => target_file_mode, :uid => target_file_uid, :gid => target_file_gid end before do expect(File).to receive(:stat).with(target_file_path).and_return(target_file_stat) expect(File).to receive(:chmod).with(target_file_mode, staging_file_path).and_return(1) expect(FileUtils).to receive(:mv).with(staging_file_path, target_file_path) end # This context represents the case where: # * Chef runs as root # * The owner and group of the target file match the owner and group of the # staging file. context "when the user has permissions to set file ownership" do # For the purposes of this test, the uid/gid can be anything. These # values are just chosen because (assuming chef-client's euid == 1001 and # egid == 1001), the `chown` call is allowed by the OS. See the # description of `EPERM` in `man 2 chown` for reference. let(:target_file_uid) { 1001 } let(:target_file_gid) { 1001 } before do expect(File).to receive(:chown).with(target_file_uid, nil, staging_file_path).and_return(1) expect(File).to receive(:chown).with(nil, target_file_gid, staging_file_path).and_return(1) end it "fixes up permissions and moves the file into place" do content_deployer.deploy(staging_file_path, target_file_path) end end context "when the user does not have permissions to set file ownership" do # The test code does not care what these values are. These values are # chosen because they're representitive of the case that chef-client is # running as non-root and is managing a file that got ownership set to # root somehow. In this example, gid==20 is something like "staff" which # the user running chef-client is a member of (but it's not that user's # primary group). let(:target_file_uid) { 0 } let(:target_file_gid) { 20 } before do expect(File).to receive(:chown).with(target_file_uid, nil, staging_file_path).and_raise(Errno::EPERM) expect(File).to receive(:chown).with(nil, target_file_gid, staging_file_path).and_raise(Errno::EPERM) expect(Chef::Log).to receive(:warn).with(/^Could not set uid/) expect(Chef::Log).to receive(:warn).with(/^Could not set gid/) end it "fixes up permissions and moves the file into place" do content_deployer.deploy(staging_file_path, target_file_path) end end end end chef-12.14.60/spec/unit/file_content_management/deploy/mv_windows_spec.rb000066400000000000000000000213271276456504500265020ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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" unless Chef::Platform.windows? class Chef module ReservedNames module Win32 module Security ACL = Object.new SecurableObject = Object.new end end end end end require "chef/file_content_management/deploy/mv_windows" describe Chef::FileContentManagement::Deploy::MvWindows do let(:content_deployer) { described_class.new } let(:target_file_path) { "/etc/my_app.conf" } describe "creating the file" do it "touches the file to create it" do expect(FileUtils).to receive(:touch).with(target_file_path) content_deployer.create(target_file_path) end end describe "updating the file" do let(:staging_file_path) { "/tmp/random-dir/staging-file.tmp" } let(:target_file_security_object) do double "Securable Object for target file" end let(:updated_target_security_object) do double "Securable Object for target file after staging file deploy" end before do allow(Chef::ReservedNames::Win32::Security::SecurableObject). to receive(:new). with(target_file_path). and_return(target_file_security_object, updated_target_security_object) end context "when run without adminstrator privileges" do before do expect(target_file_security_object).to receive(:security_descriptor).and_raise(Chef::Exceptions::Win32APIError) end it "errors out with a WindowsNotAdmin error" do expect { content_deployer.deploy(staging_file_path, target_file_path) }.to raise_error(Chef::Exceptions::WindowsNotAdmin) end end context "when run with administrator privileges" do let(:original_target_file_owner) { double("original target file owner") } let(:original_target_file_group) { double("original target file group") } let(:target_file_security_descriptor) do double "security descriptor for target file", :group => original_target_file_group, :owner => original_target_file_owner end let(:updated_target_security_descriptor) do double "security descriptor for target file" end before do allow(target_file_security_object).to receive(:security_descriptor).and_return(target_file_security_descriptor) expect(FileUtils).to receive(:mv).with(staging_file_path, target_file_path) expect(updated_target_security_object).to receive(:group=).with(original_target_file_group) expect(updated_target_security_object).to receive(:owner=).with(original_target_file_owner) end context "and the target file has no dacl or sacl" do before do allow(target_file_security_descriptor).to receive(:dacl_present?).and_return(false) allow(target_file_security_descriptor).to receive(:sacl_present?).and_return(false) end it "fixes up permissions and moves the file into place" do content_deployer.deploy(staging_file_path, target_file_path) end end context "and the target file has null dacl and sacl" do before do allow(target_file_security_descriptor).to receive(:dacl_present?).and_return(true) allow(target_file_security_descriptor).to receive(:dacl).and_return(nil) allow(target_file_security_descriptor).to receive(:dacl_inherits?).and_return(false) allow(target_file_security_descriptor).to receive(:sacl_present?).and_return(true) allow(target_file_security_descriptor).to receive(:sacl).and_return(nil) allow(target_file_security_descriptor).to receive(:sacl_inherits?).and_return(false) expect(updated_target_security_object).to receive(:set_dacl).with(nil, false) expect(updated_target_security_object).to receive(:set_sacl).with(nil, false) end it "fixes up permissions and moves the file into place" do content_deployer.deploy(staging_file_path, target_file_path) end end context "and the target has an empty dacl and sacl" do let(:original_target_file_dacl) { [] } let(:original_target_file_sacl) { [] } let(:empty_dacl) { double("Windows ACL with no dacl ACEs") } let(:empty_sacl) { double("Windows ACL with no sacl ACEs") } before do allow(target_file_security_descriptor).to receive(:dacl_present?).and_return(true) allow(target_file_security_descriptor).to receive(:dacl_inherits?).and_return(false) allow(target_file_security_descriptor).to receive(:dacl).and_return(original_target_file_dacl) expect(Chef::ReservedNames::Win32::Security::ACL). to receive(:create). with([]). and_return(empty_dacl) allow(target_file_security_descriptor).to receive(:sacl_present?).and_return(true) allow(target_file_security_descriptor).to receive(:sacl_inherits?).and_return(false) allow(target_file_security_descriptor).to receive(:sacl).and_return(original_target_file_sacl) expect(Chef::ReservedNames::Win32::Security::ACL). to receive(:create). with([]). and_return(empty_sacl) expect(updated_target_security_object).to receive(:set_dacl).with(empty_dacl, false) expect(updated_target_security_object).to receive(:set_sacl).with(empty_sacl, false) end it "fixes up permissions and moves the file into place" do content_deployer.deploy(staging_file_path, target_file_path) end end context "and the target has a dacl and sacl" do let(:inherited_dacl_ace) { double("Windows dacl ace (inherited)", :inherited? => true) } let(:not_inherited_dacl_ace) { double("Windows dacl ace (not inherited)", :inherited? => false) } let(:original_target_file_dacl) { [inherited_dacl_ace, not_inherited_dacl_ace] } let(:inherited_sacl_ace) { double("Windows sacl ace (inherited)", :inherited? => true) } let(:not_inherited_sacl_ace) { double("Windows sacl ace (not inherited)", :inherited? => false) } let(:original_target_file_sacl) { [inherited_sacl_ace, not_inherited_sacl_ace] } let(:custom_dacl) { double("Windows ACL for non-inherited dacl aces") } let(:custom_sacl) { double("Windows ACL for non-inherited sacl aces") } before do allow(target_file_security_descriptor).to receive(:dacl_present?).and_return(true) allow(target_file_security_descriptor).to receive(:dacl_inherits?).and_return(dacl_inherits?) allow(target_file_security_descriptor).to receive(:dacl).and_return(original_target_file_dacl) expect(Chef::ReservedNames::Win32::Security::ACL). to receive(:create). with([not_inherited_dacl_ace]). and_return(custom_dacl) allow(target_file_security_descriptor).to receive(:sacl_present?).and_return(true) allow(target_file_security_descriptor).to receive(:sacl_inherits?).and_return(sacl_inherits?) allow(target_file_security_descriptor).to receive(:sacl).and_return(original_target_file_sacl) expect(Chef::ReservedNames::Win32::Security::ACL). to receive(:create). with([not_inherited_sacl_ace]). and_return(custom_sacl) expect(updated_target_security_object).to receive(:set_dacl).with(custom_dacl, dacl_inherits?) expect(updated_target_security_object).to receive(:set_sacl).with(custom_sacl, sacl_inherits?) end context "and the dacl and sacl don't inherit" do let(:dacl_inherits?) { false } let(:sacl_inherits?) { false } it "fixes up permissions and moves the file into place" do content_deployer.deploy(staging_file_path, target_file_path) end end context "and the dacl and sacl inherit" do let(:dacl_inherits?) { true } let(:sacl_inherits?) { true } it "fixes up permissions and moves the file into place" do content_deployer.deploy(staging_file_path, target_file_path) end end end end end end chef-12.14.60/spec/unit/file_content_management/tempfile_spec.rb000066400000000000000000000054311276456504500246150ustar00rootroot00000000000000# # Author:: Thom May () # 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 "spec_helper" describe Chef::FileContentManagement::Tempfile do let(:resource) do r = Chef::Resource::File.new("new_file") r.path "/foo/bar/new_file" r end subject { described_class.new(resource) } describe "#tempfile_basename" do it "should return a dotfile", :unix_only do expect(subject.send(:tempfile_basename)).to eql(".chef-new_file") end it "should return a file", :windows_only do expect(subject.send(:tempfile_basename)).to eql("chef-new_file") end end describe "#tempfile_dirnames" do it "should select a temp dir" do Chef::Config[:file_staging_uses_destdir] = false expect(Dir).to receive(:tmpdir).and_return("/tmp/dir") expect(subject.send(:tempfile_dirnames)).to eql(%w{ /tmp/dir }) end it "should select the destdir" do Chef::Config[:file_staging_uses_destdir] = true expect(subject.send(:tempfile_dirnames)).to eql(%w{ /foo/bar }) end it "should select the destdir and a temp dir" do Chef::Config[:file_staging_uses_destdir] = :auto expect(Dir).to receive(:tmpdir).and_return("/tmp/dir") expect(subject.send(:tempfile_dirnames)).to eql(%w{ /foo/bar /tmp/dir }) end end describe "#tempfile_open" do let(:tempfile) { instance_double("Tempfile") } let(:tempname) { windows? ? "chef-new_file" : ".chef-new_file" } before do Chef::Config[:file_staging_uses_destdir] = :auto allow(tempfile).to receive(:binmode).and_return(true) end it "should create a temporary file" do expect(subject.send(:tempfile_open)).to be_a(Tempfile) end it "should pick the destdir preferrentially" do expect(Tempfile).to receive(:open).with(tempname, "/foo/bar").and_return(tempfile) subject.send(:tempfile_open) end it "should use ENV['TMP'] otherwise" do expect(Dir).to receive(:tmpdir).and_return("/tmp/dir") expect(Tempfile).to receive(:open).with(tempname, "/foo/bar").and_raise(SystemCallError, "foo") expect(Tempfile).to receive(:open).with(tempname, "/tmp/dir").and_return(tempfile) subject.send(:tempfile_open) end end end chef-12.14.60/spec/unit/formatters/000077500000000000000000000000001276456504500167675ustar00rootroot00000000000000chef-12.14.60/spec/unit/formatters/base_spec.rb000066400000000000000000000047131276456504500212450ustar00rootroot00000000000000# # Author:: Lamont Granquist () # # 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" describe Chef::Formatters::Base do let(:out) { StringIO.new } let(:err) { StringIO.new } let(:formatter) { Chef::Formatters::Base.new(out, err) } it "starts with an indentation of zero" do expect(formatter.output.indent).to eql(0) end it "increments it to two correctly" do formatter.indent_by(2) expect(formatter.output.indent).to eql(2) end it "increments it and then decrements it corectly" do formatter.indent_by(2) formatter.indent_by(-2) expect(formatter.output.indent).to eql(0) end it "does not allow negative indentation" do formatter.indent_by(-2) expect(formatter.output.indent).to eql(0) end it "humanizes EOFError exceptions for #registration_failed" do formatter.registration_failed("foo.example.com", EOFError.new, double("Chef::Config")) expect(out.string).to match(/Received an EOF on transport socket/) end it "humanizes EOFError exceptions for #node_load_failed" do formatter.node_load_failed("foo.example.com", EOFError.new, double("Chef::Config")) expect(out.string).to match(/Received an EOF on transport socket/) end it "humanizes EOFError exceptions for #run_list_expand_failed" do formatter.run_list_expand_failed(double("Chef::Node"), EOFError.new) expect(out.string).to match(/Received an EOF on transport socket/) end it "humanizes EOFError exceptions for #cookbook_resolution_failed" do formatter.run_list_expand_failed(double("Expanded Run List"), EOFError.new) expect(out.string).to match(/Received an EOF on transport socket/) end it "humanizes EOFError exceptions for #cookbook_sync_failed" do formatter.cookbook_sync_failed("foo.example.com", EOFError.new) expect(out.string).to match(/Received an EOF on transport socket/) end end chef-12.14.60/spec/unit/formatters/doc_spec.rb000066400000000000000000000066221276456504500211010ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Formatters::Base do let(:out) { StringIO.new } let(:err) { StringIO.new } subject(:formatter) { Chef::Formatters::Doc.new(out, err) } it "prints a policyfile's name and revision ID" do minimal_policyfile = { "revision_id" => "613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073", "name" => "jenkins", "run_list" => [ "recipe[apt::default]", "recipe[java::default]", "recipe[jenkins::master]", "recipe[policyfile_demo::default]", ], "cookbook_locks" => {}, } formatter.policyfile_loaded(minimal_policyfile) expect(out.string).to include("Using policy 'jenkins' at revision '613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073'") end it "prints cookbook name and version" do cookbook_version = double(name: "apache2", version: "1.2.3") formatter.synchronized_cookbook("apache2", cookbook_version) expect(out.string).to include("- apache2 (1.2.3") end it "prints only seconds when elapsed time is less than 60 seconds" do @now = Time.now allow(Time).to receive(:now).and_return(@now, @now + 10.0) formatter.run_completed(nil) expect(formatter.elapsed_time).to eql(10.0) expect(formatter.pretty_elapsed_time).to include("10 seconds") expect(formatter.pretty_elapsed_time).not_to include("minutes") expect(formatter.pretty_elapsed_time).not_to include("hours") end it "prints minutes and seconds when elapsed time is more than 60 seconds" do @now = Time.now allow(Time).to receive(:now).and_return(@now, @now + 610.0) formatter.run_completed(nil) expect(formatter.elapsed_time).to eql(610.0) expect(formatter.pretty_elapsed_time).to include("10 minutes 10 seconds") expect(formatter.pretty_elapsed_time).not_to include("hours") end it "prints hours, minutes and seconds when elapsed time is more than 3600 seconds" do @now = Time.now allow(Time).to receive(:now).and_return(@now, @now + 36610.0) formatter.run_completed(nil) expect(formatter.elapsed_time).to eql(36610.0) expect(formatter.pretty_elapsed_time).to include("10 hours 10 minutes 10 seconds") end it "shows the percentage completion of an action" do res = Chef::Resource::RemoteFile.new("canteloupe") formatter.resource_update_progress(res, 35, 50, 10) expect(out.string).to include(" - Progress: 70%") end it "updates the percentage completion of an action" do res = Chef::Resource::RemoteFile.new("canteloupe") formatter.resource_update_progress(res, 70, 100, 10) expect(out.string).to include(" - Progress: 70%") formatter.resource_update_progress(res, 80, 100, 10) expect(out.string).to include(" - Progress: 80%") end end chef-12.14.60/spec/unit/formatters/error_description_spec.rb000066400000000000000000000050111276456504500240570ustar00rootroot00000000000000# # Author:: Jordan Running () # # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Formatters::ErrorDescription do let(:title) { "test title" } let(:out) { Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR) } let(:section_heading) { "test heading" } let(:section_text) { "test text" } subject { Chef::Formatters::ErrorDescription.new(title) } describe "#sections" do context "when no sections have been added" do it "should return an empty array" do expect(subject.sections).to eq [] end end context "when a section has been added" do before do subject.section(section_heading, section_text) end it "should return an array with the added section as a hash" do expect(subject.sections).to eq [ { section_heading => section_text } ] end end end describe "#display" do before do stub_const("RUBY_PLATFORM", "ruby-foo-9000") end context "when no sections have been added" do it "should output only the title and the Platform section" do subject.display(out) expect(out.out.string).to eq <<-END ================================================================================ test title ================================================================================ Platform: --------- ruby-foo-9000 END end end context "when a section has been added" do before do subject.section(section_heading, section_text) end it "should output the expected sections" do subject.display(out) expect(out.out.string).to eq <<-END ================================================================================ test title ================================================================================ test heading ------------ test text Platform: --------- ruby-foo-9000 END end end end end chef-12.14.60/spec/unit/formatters/error_inspectors/000077500000000000000000000000001276456504500223715ustar00rootroot00000000000000chef-12.14.60/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb000066400000000000000000000060111276456504500301420ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/formatters/error_inspectors/api_error_formatting" describe Chef::Formatters::APIErrorFormatting do let(:class_instance) { (Class.new { include Chef::Formatters::APIErrorFormatting }).new } let(:error_description) { instance_double(Chef::Formatters::ErrorDescription) } let(:response) { double("response") } before do allow(response).to receive(:body) end context "when describe_406_error is called" do context "when response['x-ops-server-api-version'] exists" do let(:min_version) { "2" } let(:max_version) { "5" } let(:request_version) { "30" } let(:return_hash) do { "min_version" => min_version, "max_version" => max_version, "request_version" => request_version, } end before do # mock out the header allow(response).to receive(:[]).with("x-ops-server-api-version").and_return(Chef::JSONCompat.to_json(return_hash)) end it "prints an error about client and server API version incompatibility with a min API version" do expect(error_description).to receive(:section).with("Incompatible server API version:", /a min API version of #{min_version}/) class_instance.describe_406_error(error_description, response) end it "prints an error about client and server API version incompatibility with a max API version" do expect(error_description).to receive(:section).with("Incompatible server API version:", /a max API version of #{max_version}/) class_instance.describe_406_error(error_description, response) end it "prints an error describing the request API version" do expect(error_description).to receive(:section).with("Incompatible server API version:", /a request with an API version of #{request_version}/) class_instance.describe_406_error(error_description, response) end end context "when response.body['error'] != 'invalid-x-ops-server-api-version'" do before do allow(response).to receive(:[]).with("x-ops-server-api-version").and_return(nil) end it "forwards the error_description to describe_http_error" do expect(class_instance).to receive(:describe_http_error).with(error_description) class_instance.describe_406_error(error_description, response) end end end end chef-12.14.60/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb000066400000000000000000000252571276456504500306720ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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" BAD_RECIPE = <<-E # # Cookbook Name:: syntax-err # Recipe:: default # # Copyright 2012-2016, YOUR_COMPANY_NAME # # All rights reserved - Do Not Redistribute # file "/tmp/explode-me" do mode 0655 owner "root" this_is_not_a_valid_method end E describe Chef::Formatters::ErrorInspectors::CompileErrorInspector do let(:node_name) { "test-node.example.com" } let(:description) { Chef::Formatters::ErrorDescription.new("Error Evaluating File:") } let(:exception) do e = NoMethodError.new("undefined method `this_is_not_a_valid_method' for Chef::Resource::File") e.set_backtrace(trace) e end # Change to $stdout to print error messages for manual inspection let(:stdout) { StringIO.new } let(:outputter) { Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR) } subject(:inspector) { described_class.new(path_to_failed_file, exception) } describe "finding the code responsible for the error" do context "when the stacktrace includes cookbook files" do let(:trace) do [ "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'", "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'", "/home/someuser/.multiruby/gems/chef/lib/chef/client.rb:123:in `run'", ] end let(:expected_filtered_trace) do [ "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'", "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'", ] end let(:path_to_failed_file) { "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb" } before do # Error inspector originally used file_cache_path which is incorrect on # chef-solo. Using cookbook_path should do the right thing for client and # solo. allow(Chef::Config).to receive(:cookbook_path).and_return([ "/home/someuser/dev-laptop/cookbooks" ]) end describe "when scrubbing backtraces" do it "shows backtrace lines from cookbook files" do expect(inspector.filtered_bt).to eq(expected_filtered_trace) end end describe "when explaining an error in the compile phase" do before do recipe_lines = BAD_RECIPE.split("\n").map { |l| l << "\n" } expect(IO).to receive(:readlines).with(path_to_failed_file).and_return(recipe_lines) inspector.add_explanation(description) end it "reports the error was not located within cookbooks" do expect(inspector.found_error_in_cookbooks?).to be(true) end it "finds the line number of the error from the stacktrace" do expect(inspector.culprit_line).to eq(14) end it "prints a pretty message" do description.display(outputter) end end end context "when the error is a RuntimeError about frozen object" do let(:exception) do e = RuntimeError.new("can't modify frozen Array") e.set_backtrace(trace) e end let(:path_to_failed_file) { "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb" } let(:trace) do [ "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb:2:in `block in from_file'", "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb:1:in `from_file'", ] end describe "when explaining a runtime error in the compile phase" do it "correctly detects RuntimeError for frozen objects" do expect(inspector.exception_message_modifying_frozen?).to be(true) end # could also test for description.section to be called, but would have # to adjust every other test to begin using a test double for description end end context "when the error does not contain any lines from cookbooks" do let(:trace) do [ "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'", "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'", "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `call'", "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `block (2 levels) in foreach_cookbook_load_segment'", "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `each'", "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `block in foreach_cookbook_load_segment'", "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `each'", "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `foreach_cookbook_load_segment'", "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:137:in `load_libraries'", ] end let(:exception) do e = Chef::Exceptions::RecipeNotFound.new("recipe nope:nope not found") e.set_backtrace(trace) e end let(:path_to_failed_file) { nil } it "gives a full, non-filtered trace" do expect(inspector.filtered_bt).to eq(trace) end it "does not error when displaying the error" do expect { description.display(outputter) }.to_not raise_error end it "reports the error was not located within cookbooks" do expect(inspector.found_error_in_cookbooks?).to be(false) end end end describe "when explaining an error on windows" do let(:trace_with_upcase_drive) do [ "C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb:14 in `from_file'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `call'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `block (2 levels) in foreach_cookbook_load_segment'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `each'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `block in foreach_cookbook_load_segment'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `each'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `foreach_cookbook_load_segment'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:137:in `load_libraries'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:62:in `load'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:198:in `setup_run_context'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:418:in `do_run'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:176:in `run'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:283:in `block in run_application'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:270:in `loop'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:270:in `run_application'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application.rb:70:in `run'", "C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/bin/chef-client:26:in `'", "C:/opscode/chef/bin/chef-client:19:in `load'", "C:/opscode/chef/bin/chef-client:19:in `

'", ] end let(:trace) { trace_with_upcase_drive } let(:path_to_failed_file) { "/var/cache/cookbooks/foo/recipes/default.rb" } before do allow(Chef::Config).to receive(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ]) recipe_lines = BAD_RECIPE.split("\n").map { |l| l << "\n" } expect(IO).to receive(:readlines).at_least(1).times.with(full_path_to_failed_file).and_return(recipe_lines) inspector.add_explanation(description) end context "when the drive letter in the path is uppercase" do let(:full_path_to_failed_file) { "C:/opscode/chef#{path_to_failed_file}" } it "reports the error was not located within cookbooks" do expect(inspector.found_error_in_cookbooks?).to be(true) end it "finds the culprit recipe name" do expect(inspector.culprit_file).to eq("C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb") end it "finds the line number of the error from the stack trace" do expect(inspector.culprit_line).to eq(14) end it "prints a pretty message" do description.display(outputter) end end context "when the drive letter in the path is lowercase" do let(:trace) do trace_with_upcase_drive.map { |line| line.gsub(/^C:/, "c:") } end let(:full_path_to_failed_file) { "c:/opscode/chef#{path_to_failed_file}" } it "reports the error was not located within cookbooks" do expect(inspector.found_error_in_cookbooks?).to be(true) end it "finds the culprit recipe name from the stacktrace" do expect(inspector.culprit_file).to eq("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb") end it "finds the line number of the error from the stack trace" do expect(inspector.culprit_line).to eq(14) end it "prints a pretty message" do description.display(outputter) end end end end chef-12.14.60/spec/unit/formatters/error_inspectors/cookbook_resolve_error_inspector_spec.rb000066400000000000000000000127671276456504500326110ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # 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" describe Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector do before do @expanded_run_list = Chef::RunList.new("recipe[annoyances]", "recipe[apache2]", "recipe[users]", "recipe[chef::client]") @description = Chef::Formatters::ErrorDescription.new("Error Resolving Cookbooks for Run List:") @outputter_output = StringIO.new @outputter = Chef::Formatters::IndentableOutputStream.new(@outputter_output, STDERR) # @outputter = Chef::Formatters::IndentableOutputStream.new(STDOUT, STDERR) end describe "when explaining a 403 error" do before do @response_body = %q({"error": [{"message": "gtfo"}]) @response = Net::HTTPForbidden.new("1.1", "403", "(response) forbidden") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) forbidden", @response) @inspector = Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector.new(@expanded_run_list, @exception) @inspector.add_explanation(@description) end it "prints a nice message" do expect { @description.display(@outputter) }.not_to raise_error end end describe "when explaining a PreconditionFailed (412) error with current error message style" do # Chef currently returns error messages with some fields as JSON strings, # which must be re-parsed to get the actual data. before do @response_body = "{\"error\":[\"{\\\"non_existent_cookbooks\\\":[\\\"apache2\\\"],\\\"cookbooks_with_no_versions\\\":[\\\"users\\\"],\\\"message\\\":\\\"Run list contains invalid items: no such cookbook nope.\\\"}\"]}" @response = Net::HTTPPreconditionFailed.new("1.1", "412", "(response) unauthorized") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) precondition failed", @response) @inspector = Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector.new(@expanded_run_list, @exception) @inspector.add_explanation(@description) end it "prints a pretty message" do @description.display(@outputter) @outputter_output.rewind observed_output = @outputter_output.read expect(observed_output).to include("apache2") expect(observed_output).to include("users") expect(observed_output).not_to include("Run list contains invalid items: no such cookbook nope.") end end describe "when explaining a PreconditionFailed (412) error with current error message style without cookbook details" do # Chef currently returns error messages with some fields as JSON strings, # which must be re-parsed to get the actual data. # In some cases the error message doesn't contain any cookbook # details. But we should still print a pretty error message. before do @response_body = "{\"error\":[{\"non_existent_cookbooks\":[],\"cookbooks_with_no_versions\":[],\"message\":\"unable to solve dependencies in alotted time.\"}]}" @response = Net::HTTPPreconditionFailed.new("1.1", "412", "(response) unauthorized") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) precondition failed", @response) @inspector = Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector.new(@expanded_run_list, @exception) @inspector.add_explanation(@description) end it "prints a pretty message" do @description.display(@outputter) @outputter_output.rewind expect(@outputter_output.read).to include("unable to solve dependencies in alotted time.") end end describe "when explaining a PreconditionFailed (412) error with single encoded JSON" do # Chef currently returns error messages with some fields as JSON strings, # which must be re-parsed to get the actual data. before do @response_body = "{\"error\":[{\"non_existent_cookbooks\":[\"apache2\"],\"cookbooks_with_no_versions\":[\"users\"],\"message\":\"Run list contains invalid items: no such cookbook nope.\"}]}" @response = Net::HTTPPreconditionFailed.new("1.1", "412", "(response) unauthorized") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) precondition failed", @response) @inspector = Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector.new(@expanded_run_list, @exception) @inspector.add_explanation(@description) end it "prints a pretty message" do @description.display(@outputter) @outputter_output.rewind observed_output = @outputter_output.read expect(observed_output).to include("apache2") expect(observed_output).to include("users") expect(observed_output).not_to include("Run list contains invalid items: no such cookbook nope.") end end end chef-12.14.60/spec/unit/formatters/error_inspectors/cookbook_sync_error_inspector_spec.rb000066400000000000000000000030701276456504500320710ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # 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" describe Chef::Formatters::ErrorInspectors::CookbookSyncErrorInspector do before do @description = Chef::Formatters::ErrorDescription.new("Error Expanding RunList:") @outputter = Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR) #@outputter = Chef::Formatters::IndentableOutputStream.new(STDOUT, STDERR) end describe "when explaining a 502 error" do before do @response_body = "sad trombone orchestra" @response = Net::HTTPBadGateway.new("1.1", "502", "(response) bad gateway") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPFatalError.new("(exception) bad gateway", @response) @inspector = described_class.new({}, @exception) @inspector.add_explanation(@description) end it "prints a nice message" do @description.display(@outputter) end end end chef-12.14.60/spec/unit/formatters/error_inspectors/node_load_error_inspector_spec.rb000066400000000000000000000016431276456504500311570ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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" # spec_helper loads the shared examples already. #require 'support/shared/unit/api_error_inspector_spec' describe Chef::Formatters::ErrorInspectors::NodeLoadErrorInspector do it_behaves_like "an api error inspector" end chef-12.14.60/spec/unit/formatters/error_inspectors/registration_error_inspector_spec.rb000066400000000000000000000016471276456504500317510ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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" # spec_helper loads the shared examples already. #require 'support/shared/unit/api_error_inspector_spec' describe Chef::Formatters::ErrorInspectors::RegistrationErrorInspector do it_behaves_like "an api error inspector" end chef-12.14.60/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb000066400000000000000000000165441276456504500313660ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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" describe Chef::Formatters::ErrorInspectors::ResourceFailureInspector do include Chef::DSL::Recipe def run_context node = Chef::Node.new node.automatic_attrs[:platform] = "ubuntu" node.automatic_attrs[:platform_version] = "10.04" Chef::RunContext.new(node, {}, nil) end def cookbook_name "rspec-example" end def recipe_name "rspec-example-recipe" end before do @description = Chef::Formatters::ErrorDescription.new("Error Converging Resource:") @stdout = StringIO.new @outputter = Chef::Formatters::IndentableOutputStream.new(@stdout, STDERR) #@outputter = Chef::Formatters::IndentableOutputStream.new(STDOUT, STDERR) allow(Chef::Config).to receive(:cookbook_path).and_return([ "/var/chef/cache" ]) end describe "when explaining an error converging a resource" do before do @resource = package("non-existing-package") do only_if do true end not_if("/bin/false") action :upgrade end @trace = [ "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'", "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'", "/usr/local/lib/ruby/gems/chef/lib/chef/client.rb:123:in `run'" # should not display ] @exception = Chef::Exceptions::Package.new("No such package 'non-existing-package'") @exception.set_backtrace(@trace) @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception) @inspector.add_explanation(@description) end it "filters chef core code from the backtrace" do @expected_filtered_trace = [ "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'", "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'", ] expect(@inspector.filtered_bt).to eq(@expected_filtered_trace) end it "prints a pretty message" do @description.display(@outputter) end describe "and the error is a template error" do before do @description = Chef::Formatters::ErrorDescription.new("Error Converging Resource:") @template_class = Class.new { include Chef::Mixin::Template } @template = @template_class.new @context = Chef::Mixin::Template::TemplateContext.new({}) @context[:chef] = "cool" @resource = template("/tmp/foo.txt") do mode "0644" end @error = begin @context.render_template_from_string("foo\nbar\nbaz\n<%= this_is_not_defined %>\nquin\nqunx\ndunno") rescue Chef::Mixin::Template::TemplateError => e e end @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @error) @inspector.add_explanation(@description) end it "includes contextual info from the template error in the output" do @description.display(@outputter) expect(@stdout.string).to include(@error.source_listing) end end describe "recipe_snippet" do before do # fake code to run through #recipe_snippet source_file = [ "if true", "var = non_existent", "end" ] allow(IO).to receive(:readlines).and_return(source_file) allow(File).to receive(:exists?).and_return(true) end it "parses a Windows path" do source_line = "C:/Users/btm/chef/chef/spec/unit/fake_file.rb:2: undefined local variable or method `non_existent' for main:Object (NameError)" @resource.source_line = source_line @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception) expect(@inspector.recipe_snippet).to match(/^# In C:\/Users\/btm/) end it "parses a Windows path" do source_line = "C:\\Windows\\Temp\\packer\\cookbooks\\fake_file.rb:2: undefined local variable or method `non_existent' for main:Object (NameError)" @resource.source_line = source_line @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception) expect(@inspector.recipe_snippet).to match(/^# In C:\\Windows\\Temp\\packer\\/) end it "parses a unix path" do source_line = "/home/btm/src/chef/chef/spec/unit/fake_file.rb:2: undefined local variable or method `non_existent' for main:Object (NameError)" @resource.source_line = source_line @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception) expect(@inspector.recipe_snippet).to match(/^# In \/home\/btm/) end context "when the recipe file does not exist" do before do allow(File).to receive(:exists?).and_return(false) allow(IO).to receive(:readlines).and_raise(Errno::ENOENT) end it "does not try to parse a recipe in chef-shell/irb (CHEF-3411)" do @resource.source_line = "(irb#1):1:in `irb_binding'" @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception) expect(@inspector.recipe_snippet).to be_nil end it "does not raise an exception trying to load a non-existent file (CHEF-3411)" do @resource.source_line = "/somewhere/in/space" @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception) expect { @inspector.recipe_snippet }.not_to raise_error end end end describe "when examining a resource that confuses the parser" do before do angry_bash_recipe = File.expand_path("cookbooks/angrybash/recipes/default.rb", CHEF_SPEC_DATA) source_line = "#{angry_bash_recipe}:1:in `
'" # source_line = caller(0)[0]; @resource = bash "go off the rails" do # code <<-END # for i in localhost 127.0.0.1 #{Socket.gethostname()} # do # echo "grant all on *.* to root@'$i' identified by 'a_password'; flush privileges;" | mysql -u root -h 127.0.0.1 # done # END # end @resource = eval(IO.read(angry_bash_recipe)) @resource.source_line = source_line @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception) @exception.set_backtrace(@trace) @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception) end it "does not generate an error" do expect { @inspector.add_explanation(@description) }.not_to raise_error @description.display(@outputter) end end end end chef-12.14.60/spec/unit/formatters/error_inspectors/run_list_expansion_error_inspector_spec.rb000066400000000000000000000066101276456504500331550ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # 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" describe Chef::Formatters::ErrorInspectors::RunListExpansionErrorInspector do before do @node = Chef::Node.new.tap do |n| n.name("unit-test.example.com") n.run_list("role[base]") end @description = Chef::Formatters::ErrorDescription.new("Error Expanding RunList:") @outputter = Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR) #@outputter = Chef::Formatters::IndentableOutputStream.new(STDOUT, STDERR) end describe "when explaining a missing role error" do before do @run_list_expansion = Chef::RunList::RunListExpansion.new("_default", @node.run_list) @run_list_expansion.missing_roles_with_including_role << [ "role[missing-role]", "role[base]" ] @run_list_expansion.missing_roles_with_including_role << [ "role[another-missing-role]", "role[base]" ] @exception = Chef::Exceptions::MissingRole.new(@run_list_expansion) @inspector = Chef::Formatters::ErrorInspectors::RunListExpansionErrorInspector.new(@node, @exception) @inspector.add_explanation(@description) end it "prints a pretty message" do @description.display(@outputter) end end describe "when explaining an HTTP 403 error" do before do @response_body = "forbidden" @response = Net::HTTPForbidden.new("1.1", "403", "(response) forbidden") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) forbidden", @response) @inspector = Chef::Formatters::ErrorInspectors::RunListExpansionErrorInspector.new(@node, @exception) allow(@inspector).to receive(:config).and_return(:node_name => "unit-test.example.com") @inspector.add_explanation(@description) end it "prints a pretty message" do @description.display(@outputter) end end describe "when explaining an HTTP 401 error" do before do @response_body = "check your key and node name" @response = Net::HTTPUnauthorized.new("1.1", "401", "(response) unauthorized") allow(@response).to receive(:body).and_return(@response_body) @exception = Net::HTTPServerException.new("(exception) unauthorized", @response) @inspector = Chef::Formatters::ErrorInspectors::RunListExpansionErrorInspector.new(@node, @exception) allow(@inspector).to receive(:config).and_return(:node_name => "unit-test.example.com", :client_key => "/etc/chef/client.pem", :chef_server_url => "http://chef.example.com") @inspector.add_explanation(@description) end it "prints a pretty message" do @description.display(@outputter) end end end chef-12.14.60/spec/unit/guard_interpreter/000077500000000000000000000000001276456504500203265ustar00rootroot00000000000000chef-12.14.60/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb000066400000000000000000000135751276456504500273340ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::GuardInterpreter::ResourceGuardInterpreter do let(:node) do node = Chef::Node.new node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s node.automatic[:os] = "windows" node end let(:run_context) { Chef::RunContext.new(node, nil, nil) } let(:parent_resource) do parent_resource = Chef::Resource.new("powershell_unit_test", run_context) allow(parent_resource).to receive(:run_action) allow(parent_resource).to receive(:updated).and_return(true) parent_resource end let(:guard_interpreter) { Chef::GuardInterpreter::ResourceGuardInterpreter.new(parent_resource, "echo hi", nil) } describe "get_interpreter_resource" do it "allows the guard interpreter to be set to Chef::Resource::Script" do parent_resource.guard_interpreter(:script) expect { guard_interpreter }.not_to raise_error end it "allows the guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do parent_resource.guard_interpreter(:powershell_script) expect { guard_interpreter }.not_to raise_error end it "raises an exception if guard_interpreter is set to a resource not derived from Chef::Resource::Script" do parent_resource.guard_interpreter(:file) expect { guard_interpreter }.to raise_error(ArgumentError, "Specified guard interpreter class Chef::Resource::File must be a kind of Chef::Resource::Execute resource") end context "when the resource cannot be found for the platform" do before do expect(Chef::Resource).to receive(:resource_for_node).with(:foobar, node).and_return(nil) end it "raises an exception" do parent_resource.guard_interpreter(:foobar) expect { guard_interpreter }.to raise_error(ArgumentError, "Specified guard_interpreter resource foobar unknown for this platform") end end it "fails when parent_resource is nil" do expect { Chef::GuardInterpreter::ResourceGuardInterpreter.new(nil, "echo hi", nil) }.to raise_error(ArgumentError, /Node for guard resource parent must not be nil/) end end describe "#evaluate" do let(:guard_interpreter) { Chef::GuardInterpreter::ResourceGuardInterpreter.new(parent_resource, "echo hi", {}) } let(:parent_resource) do parent_resource = Chef::Resource.new("execute resource", run_context) parent_resource.guard_interpreter(:execute) parent_resource end it "successfully evaluates the resource" do expect(guard_interpreter.evaluate).to eq(true) end it "does not corrupt the run_context of the node" do node_run_context_before_guard_execution = parent_resource.run_context expect(node_run_context_before_guard_execution.object_id).to eq(parent_resource.node.run_context.object_id) guard_interpreter.evaluate node_run_context_after_guard_execution = parent_resource.run_context expect(node_run_context_after_guard_execution.object_id).to eq(parent_resource.node.run_context.object_id) end describe "script command opts switch" do let(:command_opts) { {} } let(:guard_interpreter) { Chef::GuardInterpreter::ResourceGuardInterpreter.new(parent_resource, "exit 0", command_opts) } context "resource is a Script" do context "and guard_interpreter is a :script" do let(:parent_resource) do parent_resource = Chef::Resource::Script.new("resource", run_context) # Ruby scripts are cross platform to both Linux and Windows parent_resource.guard_interpreter(:ruby) parent_resource end let(:shell_out) do instance_double(Mixlib::ShellOut, :live_stream => true, :run_command => true, :error! => nil) end before do # TODO for some reason Windows is failing on executing a ruby script expect(Mixlib::ShellOut).to receive(:new) do |*args| expect(args[0]).to match(/^"ruby"/) shell_out end end it "merges to :code" do expect(command_opts).to receive(:merge).with({ :code => "exit 0" }).and_call_original expect(guard_interpreter.evaluate).to eq(true) end end context "and guard_interpreter is :execute" do let(:parent_resource) do parent_resource = Chef::Resource::Script.new("resource", run_context) parent_resource.guard_interpreter(:execute) parent_resource end it "merges to :code" do expect(command_opts).to receive(:merge).with({ :command => "exit 0" }).and_call_original expect(guard_interpreter.evaluate).to eq(true) end end end context "resource is not a Script" do let(:parent_resource) do parent_resource = Chef::Resource::Execute.new("resource", run_context) parent_resource.guard_interpreter(:execute) parent_resource end it "merges to :command" do expect(command_opts).to receive(:merge).with({ :command => "exit 0" }).and_call_original expect(guard_interpreter.evaluate).to eq(true) end end end end end chef-12.14.60/spec/unit/guard_interpreter_spec.rb000066400000000000000000000033161276456504500216700ustar00rootroot00000000000000# # Author:: Steven Danna (steve@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::GuardInterpreter do describe "#for_resource" do let (:resource) { Chef::Resource.new("foo") } it "returns a DefaultGuardInterpreter if the resource has guard_interpreter set to :default" do resource.guard_interpreter :default interpreter = Chef::GuardInterpreter.for_resource(resource, "", {}) expect(interpreter.class).to eq(Chef::GuardInterpreter::DefaultGuardInterpreter) end it "returns a ResourceGuardInterpreter if the resource has guard_interpreter set to !:default" do resource.guard_interpreter :foobar # Mock the resource guard interpreter to avoid having to set up a lot of state # currently we are only testing that we get the correct class of object back rgi = double("Chef::GuardInterpreter::ResourceGuardInterpreter") allow(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:new).and_return(rgi) interpreter = Chef::GuardInterpreter.for_resource(resource, "", {}) expect(interpreter).to eq(rgi) end end end chef-12.14.60/spec/unit/handler/000077500000000000000000000000001276456504500162165ustar00rootroot00000000000000chef-12.14.60/spec/unit/handler/json_file_spec.rb000066400000000000000000000044541276456504500215340ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2010-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::Handler::JsonFile do before(:each) do @handler = Chef::Handler::JsonFile.new(:the_sun => "will rise", :path => "/tmp/foobarbazqux") end it "accepts arbitrary config options" do expect(@handler.config[:the_sun]).to eq("will rise") end it "creates the directory where the reports will be saved" do expect(FileUtils).to receive(:mkdir_p).with("/tmp/foobarbazqux") expect(File).to receive(:chmod).with(00700, "/tmp/foobarbazqux") @handler.build_report_dir end describe "when reporting success" do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_status = Chef::RunStatus.new(@node, @events) @expected_time = Time.now allow(Time).to receive(:now).and_return(@expected_time, @expected_time + 5) @run_status.start_clock @run_status.stop_clock @run_context = Chef::RunContext.new(@node, {}, @events) @run_status.run_context = @run_context @run_status.exception = Exception.new("Boy howdy!") @file_mock = StringIO.new allow(File).to receive(:open).and_yield(@file_mock) end it "saves run status data to a file as JSON" do expect(@handler).to receive(:build_report_dir) @handler.run_report_unsafe(@run_status) reported_data = Chef::JSONCompat.parse(@file_mock.string) expect(reported_data["exception"]).to eq("Exception: Boy howdy!") expect(reported_data["start_time"]).to eq(@expected_time.to_s) expect(reported_data["end_time"]).to eq((@expected_time + 5).to_s) expect(reported_data["elapsed_time"]).to eq(5) end end end chef-12.14.60/spec/unit/handler_spec.rb000066400000000000000000000222161276456504500175600ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2010-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::Handler do before(:each) do @handler = Chef::Handler.new @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_status = Chef::RunStatus.new(@node, @events) @handler.instance_variable_set(:@run_status, @run_status) end describe "when accessing the run status" do before do @backtrace = caller @exception = Exception.new("epic_fail") @exception.set_backtrace(@backtrace) @run_status.exception = @exception @run_context = Chef::RunContext.new(@node, {}, @events) @all_resources = [Chef::Resource::Cat.new("lolz"), Chef::Resource::ZenMaster.new("tzu")] @all_resources.first.updated = true @run_context.resource_collection.all_resources.replace(@all_resources) @run_status.run_context = @run_context @start_time = Time.now @end_time = @start_time + 4.2 allow(Time).to receive(:now).and_return(@start_time, @end_time) @run_status.start_clock @run_status.stop_clock end it "has a shortcut for the exception" do expect(@handler.exception).to eq(@exception) end it "has a shortcut for the backtrace" do expect(@handler.backtrace).to eq(@backtrace) end it "has a shortcut for all resources" do expect(@handler.all_resources).to eq(@all_resources) end it "has a shortcut for just the updated resources" do expect(@handler.updated_resources).to eq([@all_resources.first]) end it "has a shortcut for the start time" do expect(@handler.start_time).to eq(@start_time) end it "has a shortcut for the end time" do expect(@handler.end_time).to eq(@end_time) end it "has a shortcut for the elapsed time" do expect(@handler.elapsed_time).to eq(4.2) end it "has a shortcut for the node" do expect(@handler.node).to eq(@node) end it "has a shortcut for the run context" do expect(@handler.run_context).to eq(@run_context) end it "has a shortcut for the success? and failed? predicates" do expect(@handler.success?).to be_falsey # because there's an exception expect(@handler.failed?).to be_truthy end it "has a shortcut to the hash representation of the run status" do expect(@handler.data).to eq(@run_status.to_hash) end end describe "when running the report" do it "does not fail if the report handler raises an exception" do $report_ran = false def @handler.report $report_ran = true raise Exception, "I died the deth" end expect { @handler.run_report_safely(@run_status) }.not_to raise_error expect($report_ran).to be_truthy end it "does not fail if the report handler does not raise an exception" do $report_ran = false def @handler.report $report_ran = true end expect { @handler.run_report_safely(@run_status) }.not_to raise_error expect($report_ran).to be_truthy end end # Hmm, no tests for report handlers, looks like describe "when running a report handler" do before do @run_context = Chef::RunContext.new(@node, {}, @events) @all_resources = [Chef::Resource::Cat.new("foo"), Chef::Resource::ZenMaster.new("moo")] @all_resources.first.updated = true @run_context.resource_collection.all_resources.replace(@all_resources) @run_status.run_context = @run_context @start_time = Time.now @end_time = @start_time + 4.2 allow(Time).to receive(:now).and_return(@start_time, @end_time) @run_status.start_clock @run_status.stop_clock end it "has a shortcut for all resources" do expect(@handler.all_resources).to eq(@all_resources) end it "has a shortcut for just the updated resources" do expect(@handler.updated_resources).to eq([@all_resources.first]) end it "has a shortcut for the start time" do expect(@handler.start_time).to eq(@start_time) end it "has a shortcut for the end time" do expect(@handler.end_time).to eq(@end_time) end it "has a shortcut for the elapsed time" do expect(@handler.elapsed_time).to eq(4.2) end it "has a shortcut for the node" do expect(@handler.node).to eq(@node) end it "has a shortcut for the run context" do expect(@handler.run_context).to eq(@run_context) end it "has a shortcut for the success? and failed? predicates" do expect(@handler.success?).to be_truthy expect(@handler.failed?).to be_falsey end it "has a shortcut to the hash representation of the run status" do expect(@handler.data).to eq(@run_status.to_hash) end end # and this would test the start handler describe "when running a start handler" do before do @start_time = Time.now allow(Time).to receive(:now).and_return(@start_time) @run_status.start_clock end it "should not have all resources" do expect(@handler.all_resources).to be_falsey end it "should not have updated resources" do expect(@handler.updated_resources).to be_falsey end it "has a shortcut for the start time" do expect(@handler.start_time).to eq(@start_time) end it "does not have a shortcut for the end time" do expect(@handler.end_time).to be_falsey end it "does not have a shortcut for the elapsed time" do expect(@handler.elapsed_time).to be_falsey end it "has a shortcut for the node" do expect(@handler.node).to eq(@node) end it "does not have a shortcut for the run context" do expect(@handler.run_context).to be_falsey end it "has a shortcut for the success? and failed? predicates" do expect(@handler.success?).to be_truthy # for some reason this is true expect(@handler.failed?).to be_falsey end it "has a shortcut to the hash representation of the run status" do expect(@handler.data).to eq(@run_status.to_hash) end end describe "library report handler" do before do # we need to lazily declare this after we have reset Chef::Config in the default rspec before handler class MyTestHandler < Chef::Handler handler_for :report, :exception, :start class << self attr_accessor :ran_report end def report self.class.ran_report = true end end end it "gets added to Chef::Config[:report_handlers]" do expect(Chef::Config[:report_handlers].include?(MyTestHandler)).to be true end it "gets added to Chef::Config[:exception_handlers]" do expect(Chef::Config[:exception_handlers].include?(MyTestHandler)).to be true end it "gets added to Chef::Config[:start_handlers]" do expect(Chef::Config[:start_handlers].include?(MyTestHandler)).to be true end it "runs the report handler" do Chef::Handler.run_report_handlers(@run_status) expect(MyTestHandler.ran_report).to be true end it "runs the exception handler" do Chef::Handler.run_exception_handlers(@run_status) expect(MyTestHandler.ran_report).to be true end it "runs the start handler" do Chef::Handler.run_start_handlers(@run_status) expect(MyTestHandler.ran_report).to be true end end describe "library singleton report handler" do before do # we need to lazily declare this after we have reset Chef::Config in the default rspec before handler class MyTestHandler < Chef::Handler handler_for :report, :exception, :start include Singleton attr_accessor :ran_report def report self.ran_report = true end end end it "gets added to Chef::Config[:report_handlers]" do expect(Chef::Config[:report_handlers].include?(MyTestHandler)).to be true end it "gets added to Chef::Config[:exception_handlers]" do expect(Chef::Config[:exception_handlers].include?(MyTestHandler)).to be true end it "gets added to Chef::Config[:start_handlers]" do expect(Chef::Config[:start_handlers].include?(MyTestHandler)).to be true end it "runs the report handler" do Chef::Handler.run_report_handlers(@run_status) expect(MyTestHandler.instance.ran_report).to be true end it "runs the exception handler" do Chef::Handler.run_exception_handlers(@run_status) expect(MyTestHandler.instance.ran_report).to be true end it "runs the start handler" do Chef::Handler.run_start_handlers(@run_status) expect(MyTestHandler.instance.ran_report).to be true end end end chef-12.14.60/spec/unit/http/000077500000000000000000000000001276456504500155605ustar00rootroot00000000000000chef-12.14.60/spec/unit/http/authenticator_spec.rb000066400000000000000000000055751276456504500220050ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/http/authenticator" describe Chef::HTTP::Authenticator do let(:class_instance) { Chef::HTTP::Authenticator.new } let(:method) { double("method") } let(:url) { double("url") } let(:headers) { Hash.new } let(:data) { double("data") } before do allow(class_instance).to receive(:authentication_headers).and_return({}) end context "when handle_request is called" do shared_examples_for "merging the server API version into the headers" do it "merges the default version of X-Ops-Server-API-Version into the headers" do # headers returned expect(class_instance.handle_request(method, url, headers, data)[2]). to include({ "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION }) end context "when api_version is set to something other than the default" do let(:class_instance) { Chef::HTTP::Authenticator.new({ :api_version => "-10" }) } it "merges the requested version of X-Ops-Server-API-Version into the headers" do expect(class_instance.handle_request(method, url, headers, data)[2]). to include({ "X-Ops-Server-API-Version" => "-10" }) end end end context "when !sign_requests?" do before do allow(class_instance).to receive(:sign_requests?).and_return(false) end it_behaves_like "merging the server API version into the headers" it "authentication_headers is not called" do expect(class_instance).to_not receive(:authentication_headers) class_instance.handle_request(method, url, headers, data) end end context "when sign_requests?" do before do allow(class_instance).to receive(:sign_requests?).and_return(true) end it_behaves_like "merging the server API version into the headers" it "calls authentication_headers with the proper input" do expect(class_instance).to receive(:authentication_headers).with( method, url, data, { "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION }).and_return({}) class_instance.handle_request(method, url, headers, data) end end end end chef-12.14.60/spec/unit/http/basic_client_spec.rb000066400000000000000000000046741276456504500215510ustar00rootroot00000000000000# # Author:: Cameron Cope () # 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/http/basic_client" describe "HTTP Connection" do let(:uri) { URI("https://example.com:4443") } subject(:basic_client) { Chef::HTTP::BasicClient.new(uri) } describe ".new" do it "creates an instance" do subject end end describe "#initialize" do it "calls .start when doing keepalives" do basic_client = Chef::HTTP::BasicClient.new(uri, keepalives: true) expect(basic_client).to receive(:configure_ssl) net_http_mock = instance_double(Net::HTTP, proxy_address: nil, "proxy_port=" => nil, "read_timeout=" => nil, "open_timeout=" => nil) expect(net_http_mock).to receive(:start).and_return(net_http_mock) expect(Net::HTTP).to receive(:new).and_return(net_http_mock) expect(basic_client.http_client).to eql(net_http_mock) end it "does not call .start when not doing keepalives" do basic_client = Chef::HTTP::BasicClient.new(uri) expect(basic_client).to receive(:configure_ssl) net_http_mock = instance_double(Net::HTTP, proxy_address: nil, "proxy_port=" => nil, "read_timeout=" => nil, "open_timeout=" => nil) expect(net_http_mock).not_to receive(:start) expect(Net::HTTP).to receive(:new).and_return(net_http_mock) expect(basic_client.http_client).to eql(net_http_mock) end end describe "#build_http_client" do it "should build an http client" do subject.build_http_client end it "should set an open timeout" do expect(subject.build_http_client.open_timeout).not_to be_nil end end describe "#proxy_uri" do subject(:proxy_uri) { basic_client.proxy_uri } it "uses ChefConfig's proxy_uri method" do expect(ChefConfig::Config).to receive(:proxy_uri).at_least(:once).with( uri.scheme, uri.host, uri.port ) proxy_uri end end end chef-12.14.60/spec/unit/http/http_request_spec.rb000066400000000000000000000057441276456504500216600ustar00rootroot00000000000000# # Author:: Klaas Jan Wierenga () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::HTTP::HTTPRequest do context "with HTTP url scheme" do it "should not include port 80 in Host header" do request = Chef::HTTP::HTTPRequest.new(:GET, URI("http://dummy.com"), "") expect(request.headers["Host"]).to eql("dummy.com") end it "should not include explicit port 80 in Host header" do request = Chef::HTTP::HTTPRequest.new(:GET, URI("http://dummy.com:80"), "") expect(request.headers["Host"]).to eql("dummy.com") end it "should include explicit port 8000 in Host header" do request = Chef::HTTP::HTTPRequest.new(:GET, URI("http://dummy.com:8000"), "") expect(request.headers["Host"]).to eql("dummy.com:8000") end it "should include explicit 443 port in Host header" do request = Chef::HTTP::HTTPRequest.new(:GET, URI("http://dummy.com:443"), "") expect(request.headers["Host"]).to eql("dummy.com:443") end it "should pass on explicit Host header unchanged" do request = Chef::HTTP::HTTPRequest.new(:GET, URI("http://dummy.com:8000"), "", { "Host" => "yourhost.com:8888" }) expect(request.headers["Host"]).to eql("yourhost.com:8888") end end context "with HTTPS url scheme" do it "should not include port 443 in Host header" do request = Chef::HTTP::HTTPRequest.new(:GET, URI("https://dummy.com"), "") expect(request.headers["Host"]).to eql("dummy.com") end it "should include explicit port 80 in Host header" do request = Chef::HTTP::HTTPRequest.new(:GET, URI("https://dummy.com:80"), "") expect(request.headers["Host"]).to eql("dummy.com:80") end it "should include explicit port 8000 in Host header" do request = Chef::HTTP::HTTPRequest.new(:GET, URI("https://dummy.com:8000"), "") expect(request.headers["Host"]).to eql("dummy.com:8000") end it "should not include explicit port 443 in Host header" do request = Chef::HTTP::HTTPRequest.new(:GET, URI("https://dummy.com:443"), "") expect(request.headers["Host"]).to eql("dummy.com") end end it "should pass on explicit Host header unchanged" do request = Chef::HTTP::HTTPRequest.new(:GET, URI("http://dummy.com:8000"), "", { "Host" => "myhost.com:80" }) expect(request.headers["Host"]).to eql("myhost.com:80") end end chef-12.14.60/spec/unit/http/json_input_spec.rb000066400000000000000000000077611276456504500213220ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/http/json_input" describe Chef::HTTP::JSONInput do let(:json_encoder) { described_class.new() } let(:url) { URI.parse("http://example.com") } let(:headers) { {} } def handle_request json_encoder.handle_request(http_method, url, headers, data) end it "passes the response unmodified" do http_response = double("Net::HTTPSuccess") request = double("Chef::HTTP::HTTPRequest") return_value = "response body" result = json_encoder.handle_response(http_response, request, return_value) expect(result).to eq([http_response, request, return_value]) end it "doesn't handle streaming responses" do http_response = double("Net::HTTPSuccess") expect(json_encoder.stream_response_handler(http_response)).to be nil end it "does nothing for stream completion" do http_response = double("Net::HTTPSuccess") request = double("Chef::HTTP::HTTPRequest") return_value = "response body" result = json_encoder.handle_response(http_response, request, return_value) expect(result).to eq([http_response, request, return_value]) end context "when handling a request with no body" do let(:http_method) { :get } let(:data) { nil } it "passes the request unmodified" do expect(handle_request).to eq([http_method, url, headers, data]) end end context "when the request should be serialized" do let(:http_method) { :put } let(:data) { { foo: "bar" } } let(:expected_data) { %q[{"foo":"bar"}] } context "and the request has a ruby object as the body and no explicit content-type" do it "serializes the body to json" do # Headers Hash get mutated, so we start by asserting it's empty: expect(headers).to be_empty expect(handle_request).to eq([http_method, url, headers, expected_data]) # Now the headers Hash should have json content type: expect(headers).to have_key("Content-Type") expect(headers["Content-Type"]).to eq("application/json") end end context "ant the request has an explicit content type of json" do it "serializes the body to json when content-type is all lowercase" do headers["content-type"] = "application/json" expect(handle_request).to eq([http_method, url, headers, expected_data]) # Content-Type header should be normalized: expect(headers.size).to eq(1) expect(headers).to have_key("Content-Type") expect(headers["Content-Type"]).to eq("application/json") end end end context "when handling a request with an explicit content type other than json" do let(:http_method) { :put } let(:data) { "some arbitrary bytes" } it "does not serialize the body to json when content type is given as lowercase" do headers["content-type"] = "application/x-binary" expect(handle_request).to eq([http_method, url, headers, data]) # not normalized expect(headers).to eq({ "content-type" => "application/x-binary" }) end it "does not serialize the body to json when content type is given in capitalized form" do headers["Content-Type"] = "application/x-binary" expect(handle_request).to eq([http_method, url, headers, data]) # not normalized expect(headers).to eq({ "Content-Type" => "application/x-binary" }) end end end chef-12.14.60/spec/unit/http/simple_spec.rb000066400000000000000000000023601276456504500204110ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::HTTP::Simple do it "should have content length validation middleware after compressor middleware" do client = Chef::HTTP::Simple.new("dummy.com") middlewares = client.instance_variable_get(:@middlewares) content_length = middlewares.find_index { |e| e.is_a? Chef::HTTP::ValidateContentLength } decompressor = middlewares.find_index { |e| e.is_a? Chef::HTTP::Decompressor } expect(content_length).not_to be_nil expect(decompressor).not_to be_nil expect(decompressor < content_length).to be_truthy end end chef-12.14.60/spec/unit/http/socketless_chef_zero_client_spec.rb000066400000000000000000000126371276456504500246710ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/http/socketless_chef_zero_client" describe Chef::HTTP::SocketlessChefZeroClient do let(:relative_url) { "" } let(:uri_str) { "chefzero://localhost:1/#{relative_url}" } let(:uri) { URI(uri_str) } subject(:zero_client) { Chef::HTTP::SocketlessChefZeroClient.new(uri) } it "has a host" do expect(zero_client.host).to eq("localhost") end it "has a port" do expect(zero_client.port).to eq(1) end describe "converting requests to rack format" do let(:expected_rack_req) do { "SCRIPT_NAME" => "", "SERVER_NAME" => "localhost", "REQUEST_METHOD" => method.to_s.upcase, "PATH_INFO" => uri.path, "QUERY_STRING" => uri.query, "SERVER_PORT" => uri.port, "HTTP_HOST" => "localhost:#{uri.port}", "rack.url_scheme" => "chefzero", } end context "when the request has no body" do let(:method) { :GET } let(:relative_url) { "clients" } let(:headers) { { "Accept" => "application/json" } } let(:body) { false } let(:expected_body_str) { "" } let(:rack_req) { zero_client.req_to_rack(method, uri, body, headers) } it "creates a rack request env" do # StringIO doesn't implement == in a way that we can compare, so we # check rack.input individually and then iterate over everything else expect(rack_req["rack.input"].string).to eq(expected_body_str) expected_rack_req.each do |key, value| expect(rack_req[key]).to eq(value) end end end context "when the request has a body" do let(:method) { :PUT } let(:relative_url) { "clients/foo" } let(:headers) { { "Accept" => "application/json" } } let(:body) { "bunch o' JSON" } let(:expected_body_str) { "bunch o' JSON" } let(:rack_req) { zero_client.req_to_rack(method, uri, body, headers) } it "creates a rack request env" do # StringIO doesn't implement == in a way that we can compare, so we # check rack.input individually and then iterate over everything else expect(rack_req["rack.input"].string).to eq(expected_body_str) expected_rack_req.each do |key, value| expect(rack_req[key]).to eq(value) end end end end describe "converting responses to Net::HTTP objects" do let(:net_http_response) { zero_client.to_net_http(code, headers, body) } context "when the request was successful (2XX)" do let(:code) { 200 } let(:headers) { { "Content-Type" => "Application/JSON" } } let(:body) { [ "bunch o' JSON" ] } it "creates a Net::HTTP success response object" do expect(net_http_response).to be_a_kind_of(Net::HTTPOK) expect(net_http_response.read_body).to eq("bunch o' JSON") expect(net_http_response["content-type"]).to eq("Application/JSON") end it "does not fail when calling read_body with a block" do expect(net_http_response.read_body { |chunk| chunk }).to eq("bunch o' JSON") end end context "when the requested object doesn't exist (404)" do let(:code) { 404 } let(:headers) { { "Content-Type" => "Application/JSON" } } let(:body) { [ "nope" ] } it "creates a Net::HTTPNotFound response object" do expect(net_http_response).to be_a_kind_of(Net::HTTPNotFound) end end end describe "request-response round trip" do let(:method) { :GET } let(:relative_url) { "clients" } let(:headers) { { "Accept" => "application/json" } } let(:body) { false } let(:expected_rack_req) do { "SCRIPT_NAME" => "", "SERVER_NAME" => "localhost", "REQUEST_METHOD" => method.to_s.upcase, "PATH_INFO" => uri.path, "QUERY_STRING" => uri.query, "SERVER_PORT" => uri.port, "HTTP_HOST" => "localhost:#{uri.port}", "rack.url_scheme" => "chefzero", "rack.input" => an_instance_of(StringIO), } end let(:response_code) { 200 } let(:response_headers) { { "Content-Type" => "Application/JSON" } } let(:response_body) { [ "bunch o' JSON" ] } let(:rack_response) { [ response_code, response_headers, response_body ] } let(:response) { zero_client.request(method, uri, body, headers) } before do expect(ChefZero::SocketlessServerMap).to receive(:request).with(1, expected_rack_req).and_return(rack_response) end it "makes a rack request to Chef Zero and returns the response as a Net::HTTP object" do _client, net_http_response = response expect(net_http_response).to be_a_kind_of(Net::HTTPOK) expect(net_http_response.code).to eq("200") expect(net_http_response.body).to eq("bunch o' JSON") end end end chef-12.14.60/spec/unit/http/ssl_policies_spec.rb000066400000000000000000000151741276456504500216170ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-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/http/ssl_policies" describe "HTTP SSL Policy" do before do Chef::Config[:ssl_client_cert] = nil Chef::Config[:ssl_client_key] = nil Chef::Config[:ssl_ca_path] = nil Chef::Config[:ssl_ca_file] = nil end let(:unconfigured_http_client) { Net::HTTP.new("example.com", 443) } let(:http_client) do unconfigured_http_client.use_ssl = true ssl_policy.apply unconfigured_http_client end describe Chef::HTTP::DefaultSSLPolicy do let(:ssl_policy) { Chef::HTTP::DefaultSSLPolicy.new(unconfigured_http_client) } describe "when configured with :ssl_verify_mode set to :verify peer" do before do Chef::Config[:ssl_verify_mode] = :verify_peer end it "configures the HTTP client to use SSL when given a URL with the https protocol" do expect(http_client.use_ssl?).to be_truthy end it "sets the OpenSSL verify mode to verify_peer" do expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) end it "raises a ConfigurationError if :ssl_ca_path is set to a path that doesn't exist" do Chef::Config[:ssl_ca_path] = "/dev/null/nothing_here" expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError) end it "should set the CA path if that is set in the configuration" do Chef::Config[:ssl_ca_path] = File.join(CHEF_SPEC_DATA, "ssl") expect(http_client.ca_path).to eq(File.join(CHEF_SPEC_DATA, "ssl")) end it "raises a ConfigurationError if :ssl_ca_file is set to a file that does not exist" do Chef::Config[:ssl_ca_file] = "/dev/null/nothing_here" expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError) end it "should set the CA file if that is set in the configuration" do Chef::Config[:ssl_ca_file] = CHEF_SPEC_DATA + "/ssl/5e707473.0" expect(http_client.ca_file).to eq(CHEF_SPEC_DATA + "/ssl/5e707473.0") end end describe "when configured with :ssl_verify_mode set to :verify peer" do before do @url = URI.parse("https://chef.example.com:4443/") Chef::Config[:ssl_verify_mode] = :verify_none end it "sets the OpenSSL verify mode to :verify_none" do expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE) end end describe "when configured with a client certificate" do before { @url = URI.parse("https://chef.example.com:4443/") } it "raises ConfigurationError if the certificate file doesn't exist" do Chef::Config[:ssl_client_cert] = "/dev/null/nothing_here" Chef::Config[:ssl_client_key] = CHEF_SPEC_DATA + "/ssl/chef-rspec.key" expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError) end it "raises ConfigurationError if the certificate file doesn't exist" do Chef::Config[:ssl_client_cert] = CHEF_SPEC_DATA + "/ssl/chef-rspec.cert" Chef::Config[:ssl_client_key] = "/dev/null/nothing_here" expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError) end it "raises a ConfigurationError if one of :ssl_client_cert and :ssl_client_key is set but not both" do Chef::Config[:ssl_client_cert] = "/dev/null/nothing_here" Chef::Config[:ssl_client_key] = nil expect { http_client }.to raise_error(Chef::Exceptions::ConfigurationError) end it "configures the HTTP client's cert and private key" do Chef::Config[:ssl_client_cert] = CHEF_SPEC_DATA + "/ssl/chef-rspec.cert" Chef::Config[:ssl_client_key] = CHEF_SPEC_DATA + "/ssl/chef-rspec.key" expect(http_client.cert.to_s).to eq(OpenSSL::X509::Certificate.new(IO.read(CHEF_SPEC_DATA + "/ssl/chef-rspec.cert")).to_s) expect(http_client.key.to_s).to eq(OpenSSL::PKey::RSA.new(IO.read(CHEF_SPEC_DATA + "/ssl/chef-rspec.key")).to_s) end end context "when additional certs are located in the trusted_certs dir" do let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") } let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) } let(:additional_pem_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "opscode.pem") } let(:additional_pem) { OpenSSL::X509::Certificate.new(File.read(additional_pem_path)) } before do Chef::Config.trusted_certs_dir = File.join(CHEF_SPEC_DATA, "trusted_certs") end it "enables verification of self-signed certificates" do expect(http_client.cert_store.verify(self_signed_crt)).to be_truthy end it "enables verification of cert chains" do # This cert is signed by DigiCert so it would be valid in normal SSL usage. # The chain goes: # trusted root -> intermediate -> opscode.pem # In this test, the intermediate has to be loaded and trusted in order # for verification to work correctly. # If the machine running the test doesn't have ruby SSL configured correctly, # then the root cert also has to be loaded for the test to succeed. # The system under test **SHOULD** do both of these things. expect(http_client.cert_store.verify(additional_pem)).to be_truthy end context "and some certs are duplicates" do it "skips duplicate certs" do # For whatever reason, OpenSSL errors out when adding a # cert you already have to the certificate store. ssl_policy.set_custom_certs ssl_policy.set_custom_certs #should not raise an error end end end end describe Chef::HTTP::APISSLPolicy do let(:ssl_policy) { Chef::HTTP::APISSLPolicy.new(unconfigured_http_client) } context "when verify_api_cert is set" do before do Chef::Config[:verify_api_cert] = true end it "sets the OpenSSL verify mode to verify_peer" do expect(http_client.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) end end end end chef-12.14.60/spec/unit/http/validate_content_length_spec.rb000066400000000000000000000136171276456504500240130ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "stringio" describe Chef::HTTP::ValidateContentLength do class TestClient < Chef::HTTP use Chef::HTTP::ValidateContentLength end let(:method) { "GET" } let(:url) { "http://dummy.com" } let(:headers) { {} } let(:data) { false } let(:request) {} let(:return_value) { "200" } # Test Variables let(:request_type) { :streaming } let(:content_length_value) { 23 } let(:streaming_length) { 23 } let(:response_body) { "Thanks for checking in." } let(:response_headers) do { "content-length" => content_length_value, } end let(:response) do m = double("HttpResponse", :body => response_body) allow(m).to receive(:[]) do |key| response_headers[key] end m end let(:middleware) do client = TestClient.new(url) client.middlewares[0] end def run_content_length_validation stream_handler = middleware.stream_response_handler(response) middleware.handle_request(method, url, headers, data) case request_type when :streaming # First stream the data data_length = streaming_length while data_length > 0 chunk_size = data_length > 10 ? 10 : data_length stream_handler.handle_chunk(double("Chunk", :bytesize => chunk_size)) data_length -= chunk_size end # Finally call stream complete middleware.handle_stream_complete(response, request, return_value) when :direct middleware.handle_response(response, request, return_value) else raise "Unknown request_type: #{request_type}" end end let(:debug_stream) { StringIO.new } let(:debug_output) { debug_stream.string } before(:each) do @original_log_level = Chef::Log.level Chef::Log.level = :debug allow(Chef::Log).to receive(:debug) do |message| debug_stream.puts message end end after(:each) do Chef::Log.level = @original_log_level end describe "without response body" do let(:request_type) { :direct } let(:response_body) { "Thanks for checking in." } it "shouldn't raise error" do expect { run_content_length_validation }.not_to raise_error end end describe "without Content-Length header" do let(:response_headers) { {} } %w{direct streaming}.each do |req_type| describe "when running #{req_type} request" do let(:request_type) { req_type.to_sym } it "should skip validation and log for debug" do run_content_length_validation expect(debug_output).to include("HTTP server did not include a Content-Length header in response") end end end end describe "with negative Content-Length header" do let(:content_length_value) { "-1" } %w{direct streaming}.each do |req_type| describe "when running #{req_type} request" do let(:request_type) { req_type.to_sym } it "should skip validation and log for debug" do run_content_length_validation expect(debug_output).to include("HTTP server responded with a negative Content-Length header (-1), cannot identify truncated downloads.") end end end end describe "with correct Content-Length header" do %w{direct streaming}.each do |req_type| describe "when running #{req_type} request" do let(:request_type) { req_type.to_sym } it "should validate correctly" do run_content_length_validation expect(debug_output).to include("Content-Length validated correctly.") end end end end describe "with wrong Content-Length header" do let(:content_length_value) { 25 } %w{direct streaming}.each do |req_type| describe "when running #{req_type} request" do let(:request_type) { req_type.to_sym } it "should raise ContentLengthMismatch error" do expect { run_content_length_validation }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end end end end describe "when download is interrupted" do let(:streaming_length) { 12 } it "should raise ContentLengthMismatch error" do expect { run_content_length_validation }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end end describe "when Transfer-Encoding & Content-Length is set" do let(:response_headers) do { "content-length" => content_length_value, "transfer-encoding" => "chunked", } end %w{direct streaming}.each do |req_type| describe "when running #{req_type} request" do let(:request_type) { req_type.to_sym } it "should skip validation and log for debug" do run_content_length_validation expect(debug_output).to include("Transfer-Encoding header is set, skipping Content-Length check.") end end end end describe "when client is being reused" do before do run_content_length_validation expect(debug_output).to include("Content-Length validated correctly.") end it "should reset internal counter" do expect(middleware.instance_variable_get(:@content_length_counter)).to be_nil end it "should validate correctly second time" do run_content_length_validation expect(debug_output).to include("Content-Length validated correctly.") end end end chef-12.14.60/spec/unit/http_spec.rb000066400000000000000000000160271276456504500171250ustar00rootroot00000000000000# # Author:: Xabier de Zuazo (xabier@onddo.com) # Copyright:: Copyright 2014-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/http" require "chef/http/basic_client" require "chef/http/socketless_chef_zero_client" class Chef::HTTP public :create_url end describe Chef::HTTP do let(:uri) { "https://chef.example/organizations/default/" } context "when given a chefzero:// URL" do let(:uri) { URI("chefzero://localhost:1") } subject(:http) { Chef::HTTP.new(uri) } it "uses the SocketlessChefZeroClient to handle requests" do expect(http.http_client).to be_a_kind_of(Chef::HTTP::SocketlessChefZeroClient) expect(http.http_client.url).to eq(uri) end end describe "#intialize" do it "accepts a keepalive option and passes it to the http_client" do http = Chef::HTTP.new(uri, keepalives: true) expect(Chef::HTTP::BasicClient).to receive(:new).with(uri, ssl_policy: Chef::HTTP::APISSLPolicy, keepalives: true).and_call_original expect(http.http_client).to be_a_kind_of(Chef::HTTP::BasicClient) end it "the default is not to use keepalives" do http = Chef::HTTP.new(uri) expect(Chef::HTTP::BasicClient).to receive(:new).with(uri, ssl_policy: Chef::HTTP::APISSLPolicy, keepalives: false).and_call_original expect(http.http_client).to be_a_kind_of(Chef::HTTP::BasicClient) end end describe "create_url" do it "should return a correctly formatted url 1/3 CHEF-5261" do http = Chef::HTTP.new("http://www.getchef.com") expect(http.create_url("api/endpoint")).to eql(URI.parse("http://www.getchef.com/api/endpoint")) end it "should return a correctly formatted url 2/3 CHEF-5261" do http = Chef::HTTP.new("http://www.getchef.com/") expect(http.create_url("/organization/org/api/endpoint/")).to eql(URI.parse("http://www.getchef.com/organization/org/api/endpoint/")) end it "should return a correctly formatted url 3/3 CHEF-5261" do http = Chef::HTTP.new("http://www.getchef.com/organization/org///") expect(http.create_url("///api/endpoint?url=http://foo.bar")).to eql(URI.parse("http://www.getchef.com/organization/org/api/endpoint?url=http://foo.bar")) end # As per: https://github.com/opscode/chef/issues/2500 it "should treat scheme part of the URI in a case-insensitive manner" do http = Chef::HTTP.allocate # Calling Chef::HTTP::new sets @url, don't want that. expect { http.create_url("HTTP://www1.chef.io/") }.not_to raise_error expect(http.create_url("HTTP://www2.chef.io/")).to eql(URI.parse("http://www2.chef.io/")) end end # create_url describe "#stream_to_tempfile" do it "should only close an existing Tempfile" do resp = Net::HTTPOK.new("1.1", 200, "OK") http = Chef::HTTP.new(uri) expect(Tempfile).to receive(:open).and_raise("TestError") expect_any_instance_of(Tempfile).not_to receive(:close!) expect { http.send(:stream_to_tempfile, uri, resp) }.to raise_error("TestError") end end describe "head" do it 'should return nil for a "200 Success" response (CHEF-4762)' do resp = Net::HTTPOK.new("1.1", 200, "OK") expect(resp).to receive(:read_body).and_return(nil) http = Chef::HTTP.new("") expect_any_instance_of(Chef::HTTP::BasicClient).to receive(:request).and_return(["request", resp]) expect(http.head("http://www.getchef.com/")).to eql(nil) end it 'should return false for a "304 Not Modified" response (CHEF-4762)' do resp = Net::HTTPNotModified.new("1.1", 304, "Not Modified") expect(resp).to receive(:read_body).and_return(nil) http = Chef::HTTP.new("") expect_any_instance_of(Chef::HTTP::BasicClient).to receive(:request).and_return(["request", resp]) expect(http.head("http://www.getchef.com/")).to eql(false) end end # head describe "retrying connection errors" do subject(:http) { Chef::HTTP.new(uri) } # http#http_client gets stubbed later, so eager create let!(:low_level_client) { http.http_client(URI(uri)) } let(:http_ok_response) do Net::HTTPOK.new("1.1", 200, "OK").tap do |r| allow(r).to receive(:read_body).and_return("") end end before do allow(http).to receive(:http_client).with(URI(uri)).and_return(low_level_client) end shared_examples_for "retriable_request_errors" do before do expect(low_level_client).to receive(:request).exactly(5).times.and_raise(exception) expect(http).to receive(:sleep).exactly(5).times.and_return(1) expect(low_level_client).to receive(:request).and_return([low_level_client, http_ok_response]) end it "retries the request 5 times" do http.get("/") end end shared_examples_for "errors_that_are_not_retried" do before do expect(low_level_client).to receive(:request).exactly(1).times.and_raise(exception) expect(http).to_not receive(:sleep) end it "raises the error without retrying or sleeping" do # We modify the strings to give addtional context, but the exception class should be the same expect { http.get("/") }.to raise_error(exception.class) end end context "when ECONNRESET is raised" do let(:exception) { Errno::ECONNRESET.new("example error") } include_examples "retriable_request_errors" end context "when SocketError is raised" do let(:exception) { SocketError.new("example error") } include_examples "retriable_request_errors" end context "when ETIMEDOUT is raised" do let(:exception) { Errno::ETIMEDOUT.new("example error") } include_examples "retriable_request_errors" end context "when ECONNREFUSED is raised" do let(:exception) { Errno::ECONNREFUSED.new("example error") } include_examples "retriable_request_errors" end context "when Timeout::Error is raised" do let(:exception) { Timeout::Error.new("example error") } include_examples "retriable_request_errors" end context "when OpenSSL::SSL::SSLError is raised" do let(:exception) { OpenSSL::SSL::SSLError.new("example error") } include_examples "retriable_request_errors" end context "when OpenSSL::SSL::SSLError is raised for certificate validation failure" do let(:exception) { OpenSSL::SSL::SSLError.new("ssl_connect returned=1 errno=0 state=sslv3 read server certificate b: certificate verify failed") } include_examples "errors_that_are_not_retried" end end end chef-12.14.60/spec/unit/json_compat_spec.rb000066400000000000000000000067461276456504500204710ustar00rootroot00000000000000# # Author:: Juanje Ojeda () # 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 File.expand_path("../../spec_helper", __FILE__) require "chef/json_compat" describe Chef::JSONCompat do before { Chef::Config[:treat_deprecation_warnings_as_errors] = false } describe "#from_json with JSON containing an existing class" do let(:json) { '{"json_class": "Chef::Role"}' } it "emits a deprecation warning" do Chef::Config[:treat_deprecation_warnings_as_errors] = true expect { Chef::JSONCompat.from_json(json) }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Auto inflation of JSON data is deprecated. Please use Chef::Role#from_hash/ end it "returns an instance of the class instead of a Hash" do expect(Chef::JSONCompat.from_json(json).class).to eq Chef::Role end end describe "#from_json with JSON containing comments" do let(:json) { %Q{{\n/* comment */\n// comment 2\n"json_class": "Chef::Role"}} } it "returns an instance of the class instead of a Hash" do expect(Chef::JSONCompat.from_json(json).class).to eq Chef::Role end end describe "#parse with JSON containing comments" do let(:json) { %Q{{\n/* comment */\n// comment 2\n"json_class": "Chef::Role"}} } it "returns a Hash" do expect(Chef::JSONCompat.parse(json).class).to eq Hash end end describe 'with JSON containing "Chef::Sandbox" as a json_class value' do require "chef/sandbox" # Only needed for this test let(:json) { '{"json_class": "Chef::Sandbox", "arbitrary": "data"}' } it "returns a Hash, because Chef::Sandbox is a dummy class" do expect(Chef::JSONCompat.from_json(json)).to eq({ "json_class" => "Chef::Sandbox", "arbitrary" => "data" }) end end describe "when pretty printing an object that defines #to_json" do class Foo def to_json(*a) Chef::JSONCompat.to_json({ "foo" => 1234, "bar" => { "baz" => 5678 } }, *a) end end it "should work" do f = Foo.new expect(Chef::JSONCompat.to_json_pretty(f)).to eql("{\n \"foo\": 1234,\n \"bar\": {\n \"baz\": 5678\n }\n}\n") end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { Foo.new } end end describe "with the file with 252 or less nested entries" do let(:json) { IO.read(File.join(CHEF_SPEC_DATA, "nested.json")) } let(:hash) { Chef::JSONCompat.from_json(json) } describe "when the 252 json file is loaded" do it "should create a Hash from the file" do expect(hash).to be_kind_of(Hash) end it "should has 'test' as a 252 nested value" do v = 252.times.inject(hash) do |memo, _| memo["key"] end expect(v).to eq("test") end end end it "should define .to_json on all classes" do class SomeClass; end expect(SomeClass.new.respond_to?(:to_json)).to eq(true) end end chef-12.14.60/spec/unit/key_spec.rb000066400000000000000000000516131276456504500167360ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/key" describe Chef::Key do # whether user or client irrelevent to these tests let(:key) { Chef::Key.new("original_actor", "user") } let(:public_key_string) do < "turtle", "name" => "key_name", "public_key" => public_key_string, "private_key" => "some_private_key", "expiration_date" => "infinity" } Chef::Key.from_json(o.to_json) end let(:key_with_create_key_field) do o = { "user" => "turtle", "create_key" => true } Chef::Key.from_json(o.to_json) end end end context "when deserializing a key for a client" do it_should_behave_like "a deserializable object" do let(:key) do o = { "client" => "turtle", "name" => "key_name", "public_key" => public_key_string, "private_key" => "some_private_key", "expiration_date" => "infinity" } Chef::Key.from_json(o.to_json) end let(:key_with_create_key_field) do o = { "client" => "turtle", "create_key" => true } Chef::Key.from_json(o.to_json) end end end end # when deserializing from JSON describe "API Interactions" do let(:rest) do Chef::Config[:chef_server_root] = "http://www.example.com" Chef::Config[:chef_server_url] = "http://www.example.com/organizations/test_org" r = double("rest") allow(Chef::ServerAPI).to receive(:new).and_return(r) r end let(:user_key) do o = Chef::Key.new("foobar", "user") o end let(:client_key) do o = Chef::Key.new("foobar", "client") o end describe "list" do context "when listing keys for a user" do let(:response) { [{ "uri" => "http://www.example.com/users/keys/foobar", "name" => "foobar", "expired" => false }] } let(:inflated_response) { { "foobar" => user_key } } it "lists all keys" do expect(rest).to receive(:get).with("users/#{user_key.actor}/keys").and_return(response) expect(Chef::Key.list_by_user("foobar")).to eq(response) end it "inflate all keys" do allow(Chef::Key).to receive(:load_by_user).with(user_key.actor, "foobar").and_return(user_key) expect(rest).to receive(:get).with("users/#{user_key.actor}/keys").and_return(response) expect(Chef::Key.list_by_user("foobar", true)).to eq(inflated_response) end end context "when listing keys for a client" do let(:response) { [{ "uri" => "http://www.example.com/users/keys/foobar", "name" => "foobar", "expired" => false }] } let(:inflated_response) { { "foobar" => client_key } } it "lists all keys" do expect(rest).to receive(:get).with("clients/#{client_key.actor}/keys").and_return(response) expect(Chef::Key.list_by_client("foobar")).to eq(response) end it "inflate all keys" do allow(Chef::Key).to receive(:load_by_client).with(client_key.actor, "foobar").and_return(client_key) expect(rest).to receive(:get).with("clients/#{user_key.actor}/keys").and_return(response) expect(Chef::Key.list_by_client("foobar", true)).to eq(inflated_response) end end end describe "create" do shared_examples_for "create key" do context "when a field is missing" do it "should raise a MissingKeyAttribute" do expect { key.create }.to raise_error(Chef::Exceptions::MissingKeyAttribute) end end context "when the name field is missing" do before do key.public_key public_key_string key.expiration_date "2020-12-24T21:00:00Z" end it "creates a new key via the API with the fingerprint as the name" do expect(rest).to receive(:post).with(url, { "name" => "12:3e:33:73:0b:f4:ec:72:dc:f0:4c:51:62:27:08:76:96:24:f4:4a", "public_key" => key.public_key, "expiration_date" => key.expiration_date }).and_return({}) key.create end end context "when every field is populated" do before do key.name "key_name" key.public_key public_key_string key.expiration_date "2020-12-24T21:00:00Z" key.create_key false end context "when create_key is false" do it "creates a new key via the API" do expect(rest).to receive(:post).with(url, { "name" => key.name, "public_key" => key.public_key, "expiration_date" => key.expiration_date }).and_return({}) key.create end end context "when create_key is true and public_key is nil" do before do key.delete_public_key key.create_key true $expected_output = { actor_type => "foobar", "name" => key.name, "create_key" => true, "expiration_date" => key.expiration_date, } $expected_input = { "name" => key.name, "create_key" => true, "expiration_date" => key.expiration_date, } end it "should create a new key via the API" do expect(rest).to receive(:post).with(url, $expected_input).and_return({}) key.create end context "when the server returns the private_key via key.create" do before do allow(rest).to receive(:post).with(url, $expected_input).and_return({ "private_key" => "this_private_key" }) end it "key.create returns the original key plus the private_key" do expect(key.create.to_hash).to eq($expected_output.merge({ "private_key" => "this_private_key" })) end end end context "when create_key is false and public_key is nil" do before do key.delete_public_key key.create_key false end it "should raise an InvalidKeyArgument" do expect { key.create }.to raise_error(Chef::Exceptions::MissingKeyAttribute) end end end end context "when creating a user key" do it_should_behave_like "create key" do let(:url) { "users/#{key.actor}/keys" } let(:key) { user_key } let(:actor_type) { "user" } end end context "when creating a client key" do it_should_behave_like "create key" do let(:url) { "clients/#{client_key.actor}/keys" } let(:key) { client_key } let(:actor_type) { "client" } end end end # create describe "update" do shared_examples_for "update key" do context "when name is missing and no argument was passed to update" do it "should raise an MissingKeyAttribute" do expect { key.update }.to raise_error(Chef::Exceptions::MissingKeyAttribute) end end context "when some fields are populated" do before do key.name "key_name" key.expiration_date "2020-12-24T21:00:00Z" end it "should update the key via the API" do expect(rest).to receive(:put).with(url, key.to_hash).and_return({}) key.update end end context "when @name is not nil and a arg is passed to update" do before do key.name "new_name" end it "passes @name in the body and the arg in the PUT URL" do expect(rest).to receive(:put).with(update_name_url, key.to_hash).and_return({}) key.update("old_name") end end context "when the server returns a public_key and create_key is true" do before do key.name "key_name" key.create_key true allow(rest).to receive(:put).with(url, key.to_hash).and_return({ "key" => "key_name", "public_key" => public_key_string, }) end it "returns a key with public_key populated" do new_key = key.update expect(new_key.public_key).to eq(public_key_string) end it "returns a key without create_key set" do new_key = key.update expect(new_key.create_key).to be_nil end end end context "when updating a user key" do it_should_behave_like "update key" do let(:url) { "users/#{key.actor}/keys/#{key.name}" } let(:update_name_url) { "users/#{key.actor}/keys/old_name" } let(:key) { user_key } end end context "when updating a client key" do it_should_behave_like "update key" do let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" } let(:update_name_url) { "clients/#{client_key.actor}/keys/old_name" } let(:key) { client_key } end end end #update describe "load" do shared_examples_for "load" do it "should load a named key from the API" do expect(rest).to receive(:get).with(url).and_return({ "user" => "foobar", "name" => "test_key_name", "public_key" => public_key_string, "expiration_date" => "infinity" }) key = Chef::Key.send(load_method, "foobar", "test_key_name") expect(key.actor).to eq("foobar") expect(key.name).to eq("test_key_name") expect(key.public_key).to eq(public_key_string) expect(key.expiration_date).to eq("infinity") end end describe "load_by_user" do it_should_behave_like "load" do let(:load_method) { :load_by_user } let(:url) { "users/foobar/keys/test_key_name" } end end describe "load_by_client" do it_should_behave_like "load" do let(:load_method) { :load_by_client } let(:url) { "clients/foobar/keys/test_key_name" } end end end #load describe "destroy" do shared_examples_for "destroy key" do context "when name is missing" do it "should raise an MissingKeyAttribute" do expect { Chef::Key.new("username", "user").destroy }.to raise_error(Chef::Exceptions::MissingKeyAttribute) end end before do key.name "key_name" end context "when name is not missing" do it "should delete the key via the API" do expect(rest).to receive(:delete).with(url).and_return({}) key.destroy end end end context "when destroying a user key" do it_should_behave_like "destroy key" do let(:url) { "users/#{key.actor}/keys/#{key.name}" } let(:key) { user_key } end end context "when destroying a client key" do it_should_behave_like "destroy key" do let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" } let(:key) { client_key } end end end end # API Interactions end chef-12.14.60/spec/unit/knife/000077500000000000000000000000001276456504500156755ustar00rootroot00000000000000chef-12.14.60/spec/unit/knife/bootstrap/000077500000000000000000000000001276456504500177125ustar00rootroot00000000000000chef-12.14.60/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb000066400000000000000000000161501276456504500250710ustar00rootroot00000000000000# # Author:: Lamont Granquist ) # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::Bootstrap::ChefVaultHandler do let(:stdout) { StringIO.new } let(:stderr) { StringIO.new } let(:stdin) { StringIO.new } let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) } let(:knife_config) { {} } let(:client) { Chef::ApiClient.new } let(:chef_vault_handler) do chef_vault_handler = Chef::Knife::Bootstrap::ChefVaultHandler.new(knife_config: knife_config, ui: ui) chef_vault_handler end context "when there's no vault option" do it "should report its not doing anything" do expect(chef_vault_handler.doing_chef_vault?).to be false end it "shouldn't do anything" do expect(chef_vault_handler).to_not receive(:sanity_check) expect(chef_vault_handler).to_not receive(:update_bootstrap_vault_json!) chef_vault_handler end end context "when setting chef vault items" do let(:bootstrap_vault_item) { double("ChefVault::Item") } before do expect(chef_vault_handler).to receive(:require_chef_vault!).at_least(:once) expect(bootstrap_vault_item).to receive(:clients).with(client).at_least(:once) expect(bootstrap_vault_item).to receive(:save).at_least(:once) end context "from knife_config[:bootstrap_vault_item]" do it "sets a single item as a scalar" do knife_config[:bootstrap_vault_item] = { "vault" => "item1" } expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end it "sets a single item as an array" do knife_config[:bootstrap_vault_item] = { "vault" => [ "item1" ] } expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end it "sets two items as an array" do knife_config[:bootstrap_vault_item] = { "vault" => %w{item1 item2} } expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end it "sets two vaults from different hash keys" do knife_config[:bootstrap_vault_item] = { "vault" => %w{item1 item2}, "vault2" => [ "item3" ] } expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end end context "from knife_config[:bootstrap_vault_json]" do it "sets a single item as a scalar" do knife_config[:bootstrap_vault_json] = '{ "vault": "item1" }' expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end it "sets a single item as an array" do knife_config[:bootstrap_vault_json] = '{ "vault": [ "item1" ] }' expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end it "sets two items as an array" do knife_config[:bootstrap_vault_json] = '{ "vault": [ "item1", "item2" ] }' expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end it "sets two vaults from different hash keys" do knife_config[:bootstrap_vault_json] = '{ "vault": [ "item1", "item2" ], "vault2": [ "item3" ] }' expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end end context "from knife_config[:bootstrap_vault_file]" do def setup_file_contents(json) stringio = StringIO.new(json) knife_config[:bootstrap_vault_file] = "/foo/bar/baz" expect(File).to receive(:read).with(knife_config[:bootstrap_vault_file]).and_return(stringio) end it "sets a single item as a scalar" do setup_file_contents('{ "vault": "item1" }') expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end it "sets a single item as an array" do setup_file_contents('{ "vault": [ "item1" ] }') expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end it "sets two items as an array" do setup_file_contents('{ "vault": [ "item1", "item2" ] }') expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end it "sets two vaults from different hash keys" do setup_file_contents('{ "vault": [ "item1", "item2" ], "vault2": [ "item3" ] }') expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item1").and_return(bootstrap_vault_item) expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault", "item2").and_return(bootstrap_vault_item) expect(chef_vault_handler).to receive(:load_chef_bootstrap_vault_item).with("vault2", "item3").and_return(bootstrap_vault_item) chef_vault_handler.run(client) end end end end chef-12.14.60/spec/unit/knife/bootstrap/client_builder_spec.rb000066400000000000000000000171201276456504500242360ustar00rootroot00000000000000# # Author:: Lamont Granquist ) # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::Bootstrap::ClientBuilder do let(:stdout) { StringIO.new } let(:stderr) { StringIO.new } let(:stdin) { StringIO.new } let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) } let(:knife_config) { {} } let(:chef_config) { {} } let(:node_name) { "bevell.wat" } let(:rest) { double("Chef::ServerAPI") } let(:client_builder) do client_builder = Chef::Knife::Bootstrap::ClientBuilder.new(knife_config: knife_config, chef_config: chef_config, ui: ui) allow(client_builder).to receive(:rest).and_return(rest) allow(client_builder).to receive(:node_name).and_return(node_name) client_builder end context "#sanity_check!" do let(:response_404) { OpenStruct.new(:code => "404") } let(:exception_404) { Net::HTTPServerException.new("404 not found", response_404) } context "in cases where the prompting fails" do before do # should fail early in #run expect(client_builder).to_not receive(:create_client!) expect(client_builder).to_not receive(:create_node!) end it "exits when the node exists and the user does not want to delete" do expect(rest).to receive(:get).with("nodes/#{node_name}") expect(ui.stdin).to receive(:readline).and_return("n") expect { client_builder.run }.to raise_error(SystemExit) end it "exits when the client exists and the user does not want to delete" do expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404) expect(rest).to receive(:get).with("clients/#{node_name}") expect(ui.stdin).to receive(:readline).and_return("n") expect { client_builder.run }.to raise_error(SystemExit) end end context "in cases where the prompting succeeds" do before do # mock out the rest of #run expect(client_builder).to receive(:create_client!) expect(client_builder).to receive(:create_node!) end it "when both the client and node do not exist it succeeds" do expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404) expect(rest).to receive(:get).with("clients/#{node_name}").and_raise(exception_404) expect { client_builder.run }.not_to raise_error end it "when we are allowed to delete an old node" do expect(rest).to receive(:get).with("nodes/#{node_name}") expect(ui.stdin).to receive(:readline).and_return("y") expect(rest).to receive(:get).with("clients/#{node_name}").and_raise(exception_404) expect(rest).to receive(:delete).with("nodes/#{node_name}") expect { client_builder.run }.not_to raise_error end it "when we are allowed to delete an old client" do expect(rest).to receive(:get).with("nodes/#{node_name}").and_raise(exception_404) expect(rest).to receive(:get).with("clients/#{node_name}") expect(ui.stdin).to receive(:readline).and_return("y") expect(rest).to receive(:delete).with("clients/#{node_name}") expect { client_builder.run }.not_to raise_error end it "when we are are allowed to delete both an old client and node" do expect(rest).to receive(:get).with("nodes/#{node_name}") expect(rest).to receive(:get).with("clients/#{node_name}") expect(ui.stdin).to receive(:readline).twice.and_return("y") expect(rest).to receive(:delete).with("nodes/#{node_name}") expect(rest).to receive(:delete).with("clients/#{node_name}") expect { client_builder.run }.not_to raise_error end end end context "#create_client!" do let(:client) { Chef::ApiClient.new } before do # mock out the rest of #run expect(client_builder).to receive(:sanity_check) expect(client_builder).to receive(:create_node!) end it "delegates everything to Chef::ApiClient::Registration and sets client" do reg_double = double("Chef::ApiClient::Registration") expect(Chef::ApiClient::Registration).to receive(:new).with(node_name, client_builder.client_path, http_api: rest).and_return(reg_double) expect(reg_double).to receive(:run).and_return(client) client_builder.run expect(client_builder.client).to eq(client) end end context "#client_path" do it "has a public API for the temporary client.pem file" do expect(client_builder.client_path).to match(/#{node_name}.pem/) end end context "#create_node!" do before do # mock out the rest of #run expect(client_builder).to receive(:sanity_check) expect(client_builder).to receive(:create_client!) # mock out default node building steps expect(client_builder).to receive(:client_rest).and_return(client_rest) expect(Chef::Node).to receive(:new).with(chef_server_rest: client_rest).and_return(node) expect(node).to receive(:name).with(node_name) expect(node).to receive(:save) end let(:client_rest) { double("Chef::ServerAPI (client)") } let(:node) { double("Chef::Node") } it "builds a node with a default run_list of []" do expect(node).to receive(:run_list).with([]) client_builder.run end it "does not add tags by default" do allow(node).to receive(:run_list).with([]) expect(node).to_not receive(:tags) client_builder.run end it "adds tags to the node when given" do tag_receiver = [] knife_config[:tags] = %w{foo bar} allow(node).to receive(:run_list).with([]) allow(node).to receive(:tags).and_return(tag_receiver) client_builder.run expect(tag_receiver).to eq %w{foo bar} end it "builds a node when the run_list is a string" do knife_config[:run_list] = "role[base],role[app]" expect(node).to receive(:run_list).with(["role[base]", "role[app]"]) client_builder.run end it "builds a node when the run_list is an Array" do knife_config[:run_list] = ["role[base]", "role[app]"] expect(node).to receive(:run_list).with(["role[base]", "role[app]"]) client_builder.run end it "builds a node with first_boot_attributes if they're given" do knife_config[:first_boot_attributes] = { :baz => :quux } expect(node).to receive(:normal_attrs=).with({ :baz => :quux }) expect(node).to receive(:run_list).with([]) client_builder.run end it "builds a node with an environment if its given" do knife_config[:environment] = "production" expect(node).to receive(:environment).with("production") expect(node).to receive(:run_list).with([]) client_builder.run end it "builds a node with policy_name and policy_group when given" do knife_config[:policy_name] = "my-app" knife_config[:policy_group] = "staging" expect(node).to receive(:run_list).with([]) expect(node).to receive(:policy_name=).with("my-app") expect(node).to receive(:policy_group=).with("staging") client_builder.run end end end chef-12.14.60/spec/unit/knife/bootstrap_spec.rb000066400000000000000000000730731276456504500212630ustar00rootroot00000000000000# # Author:: Ian Meyer () # Copyright:: Copyright 2010-2016, Ian Meyer # 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" Chef::Knife::Bootstrap.load_deps require "net/ssh" describe Chef::Knife::Bootstrap do before do allow(ChefConfig).to receive(:windows?) { false } end let(:knife) do Chef::Log.logger = Logger.new(StringIO.new) Chef::Config[:knife][:bootstrap_template] = bootstrap_template unless bootstrap_template.nil? k = Chef::Knife::Bootstrap.new(bootstrap_cli_options) k.merge_configs allow(k.ui).to receive(:stderr).and_return(stderr) allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) k end let(:stderr) { StringIO.new } let(:bootstrap_template) { nil } let(:bootstrap_cli_options) { [ ] } it "should use chef-full as default template" do expect(knife.bootstrap_template).to be_a_kind_of(String) expect(File.basename(knife.bootstrap_template)).to eq("chef-full") end context "with --bootstrap-vault-item" do let(:bootstrap_cli_options) { [ "--bootstrap-vault-item", "vault1:item1", "--bootstrap-vault-item", "vault1:item2", "--bootstrap-vault-item", "vault2:item1" ] } it "sets the knife config cli option correctly" do expect(knife.config[:bootstrap_vault_item]).to eq({ "vault1" => %w{item1 item2}, "vault2" => ["item1"] }) end end context "with :distro and :bootstrap_template cli options" do let(:bootstrap_cli_options) { [ "--bootstrap-template", "my-template", "--distro", "other-template" ] } it "should select bootstrap template" do expect(File.basename(knife.bootstrap_template)).to eq("my-template") end end context "with :distro and :template_file cli options" do let(:bootstrap_cli_options) { [ "--distro", "my-template", "--template-file", "other-template" ] } it "should select bootstrap template" do expect(File.basename(knife.bootstrap_template)).to eq("other-template") end end context "with :bootstrap_template and :template_file cli options" do let(:bootstrap_cli_options) { [ "--bootstrap-template", "my-template", "--template-file", "other-template" ] } it "should select bootstrap template" do expect(File.basename(knife.bootstrap_template)).to eq("my-template") end end context "when finding templates" do context "when :bootstrap_template config is set to a file" do context "that doesn't exist" do let(:bootstrap_template) { "/opt/blah/not/exists/template.erb" } it "raises an error" do expect { knife.find_template }.to raise_error(Errno::ENOENT) end end context "that exists" do let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) } it "loads the given file as the template" do expect(Chef::Log).to receive(:debug) expect(knife.find_template).to eq(File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb"))) end end end context "when :bootstrap_template config is set to a template name" do let(:bootstrap_template) { "example" } let(:builtin_template_path) { File.expand_path(File.join(File.dirname(__FILE__), "../../../lib/chef/knife/bootstrap/templates", "example.erb")) } let(:chef_config_dir_template_path) { "/knife/chef/config/bootstrap/example.erb" } let(:env_home_template_path) { "/env/home/.chef/bootstrap/example.erb" } let(:gem_files_template_path) { "/Users/schisamo/.rvm/gems/ruby-1.9.2-p180@chef-0.10/gems/knife-windows-0.5.4/lib/chef/knife/bootstrap/fake-bootstrap-template.erb" } def configure_chef_config_dir allow(Chef::Knife).to receive(:chef_config_dir).and_return("/knife/chef/config") end def configure_env_home allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_yield(env_home_template_path) end def configure_gem_files allow(Gem).to receive(:find_files).and_return([ gem_files_template_path ]) end before(:each) do expect(File).to receive(:exists?).with(bootstrap_template).and_return(false) end context "when file is available everywhere" do before do configure_chef_config_dir configure_env_home configure_gem_files expect(File).to receive(:exists?).with(builtin_template_path).and_return(true) end it "should load the template from built-in templates" do expect(knife.find_template).to eq(builtin_template_path) end end context "when file is available in chef_config_dir" do before do configure_chef_config_dir configure_env_home configure_gem_files expect(File).to receive(:exists?).with(builtin_template_path).and_return(false) expect(File).to receive(:exists?).with(chef_config_dir_template_path).and_return(true) it "should load the template from chef_config_dir" do knife.find_template.should eq(chef_config_dir_template_path) end end end context "when file is available in home directory" do before do configure_chef_config_dir configure_env_home configure_gem_files expect(File).to receive(:exists?).with(builtin_template_path).and_return(false) expect(File).to receive(:exists?).with(chef_config_dir_template_path).and_return(false) expect(File).to receive(:exists?).with(env_home_template_path).and_return(true) end it "should load the template from chef_config_dir" do expect(knife.find_template).to eq(env_home_template_path) end end context "when file is available in Gem files" do before do configure_chef_config_dir configure_env_home configure_gem_files expect(File).to receive(:exists?).with(builtin_template_path).and_return(false) expect(File).to receive(:exists?).with(chef_config_dir_template_path).and_return(false) expect(File).to receive(:exists?).with(env_home_template_path).and_return(false) expect(File).to receive(:exists?).with(gem_files_template_path).and_return(true) end it "should load the template from Gem files" do expect(knife.find_template).to eq(gem_files_template_path) end end context "when file is available in Gem files and home dir doesn't exist" do before do configure_chef_config_dir configure_gem_files allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_return(nil) expect(File).to receive(:exists?).with(builtin_template_path).and_return(false) expect(File).to receive(:exists?).with(chef_config_dir_template_path).and_return(false) expect(File).to receive(:exists?).with(gem_files_template_path).and_return(true) end it "should load the template from Gem files" do expect(knife.find_template).to eq(gem_files_template_path) end end end end ["-d", "--distro", "-t", "--bootstrap-template", "--template-file"].each do |t| context "when #{t} option is given in the command line" do it "sets the knife :bootstrap_template config" do knife.parse_options([t, "blahblah"]) knife.merge_configs expect(knife.bootstrap_template).to eq("blahblah") end end end context "with run_list template" do let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) } it "should return an empty run_list" do expect(knife.render_template).to eq('{"run_list":[]}') end it "should have role[base] in the run_list" do knife.parse_options(["-r", "role[base]"]) knife.merge_configs expect(knife.render_template).to eq('{"run_list":["role[base]"]}') end it "should have role[base] and recipe[cupcakes] in the run_list" do knife.parse_options(["-r", "role[base],recipe[cupcakes]"]) knife.merge_configs expect(knife.render_template).to eq('{"run_list":["role[base]","recipe[cupcakes]"]}') end context "with bootstrap_attribute options" do let(:jsonfile) do file = Tempfile.new (["node", ".json"]) File.open(file.path, "w") { |f| f.puts '{"foo":{"bar":"baz"}}' } file end it "should have foo => {bar => baz} in the first_boot from cli" do knife.parse_options(["-j", '{"foo":{"bar":"baz"}}']) knife.merge_configs expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}') actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template) expect(actual_hash).to eq(expected_hash) end it "should have foo => {bar => baz} in the first_boot from file" do knife.parse_options(["--json-attribute-file", jsonfile.path]) knife.merge_configs expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}') actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template) expect(actual_hash).to eq(expected_hash) jsonfile.close end context "when --json-attributes and --json-attribute-file were both passed" do it "raises a Chef::Exceptions::BootstrapCommandInputError with the proper error message" do knife.parse_options(["-j", '{"foo":{"bar":"baz"}}']) knife.parse_options(["--json-attribute-file", jsonfile.path]) knife.merge_configs expect { knife.run }.to raise_error(Chef::Exceptions::BootstrapCommandInputError) jsonfile.close end end end end context "with hints template" do let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test-hints.erb")) } it "should create a hint file when told to" do knife.parse_options(["--hint", "openstack"]) knife.merge_configs expect(knife.render_template).to match(/\/etc\/chef\/ohai\/hints\/openstack.json/) end it "should populate a hint file with JSON when given a file to read" do allow(::File).to receive(:read).and_return('{ "foo" : "bar" }') knife.parse_options(["--hint", "openstack=hints/openstack.json"]) knife.merge_configs expect(knife.render_template).to match(/\{\"foo\":\"bar\"\}/) end end describe "specifying no_proxy with various entries" do subject(:knife) do k = described_class.new Chef::Config[:knife][:bootstrap_template] = template_file k.parse_options(options) k.merge_configs k end let(:options) { ["--bootstrap-no-proxy", setting, "-s", "foo"] } let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) } let(:rendered_template) do knife.render_template end context "via --bootstrap-no-proxy" do let(:setting) { "api.opscode.com" } it "renders the client.rb with a single FQDN no_proxy entry" do expect(rendered_template).to match(%r{.*no_proxy\s*"api.opscode.com".*}) end end context "via --bootstrap-no-proxy multiple" do let(:setting) { "api.opscode.com,172.16.10.*" } it "renders the client.rb with comma-separated FQDN and wildcard IP address no_proxy entries" do expect(rendered_template).to match(%r{.*no_proxy\s*"api.opscode.com,172.16.10.\*".*}) end end context "via --ssl-verify-mode none" do let(:options) { ["--node-ssl-verify-mode", "none"] } it "renders the client.rb with ssl_verify_mode set to :verify_none" do expect(rendered_template).to match(/ssl_verify_mode :verify_none/) end end context "via --node-ssl-verify-mode peer" do let(:options) { ["--node-ssl-verify-mode", "peer"] } it "renders the client.rb with ssl_verify_mode set to :verify_peer" do expect(rendered_template).to match(/ssl_verify_mode :verify_peer/) end end context "via --node-ssl-verify-mode all" do let(:options) { ["--node-ssl-verify-mode", "all"] } it "raises error" do expect { rendered_template }.to raise_error(RuntimeError) end end context "via --node-verify-api-cert" do let(:options) { ["--node-verify-api-cert"] } it "renders the client.rb with verify_api_cert set to true" do expect(rendered_template).to match(/verify_api_cert true/) end end context "via --no-node-verify-api-cert" do let(:options) { ["--no-node-verify-api-cert"] } it "renders the client.rb with verify_api_cert set to false" do expect(rendered_template).to match(/verify_api_cert false/) end end end describe "specifying the encrypted data bag secret key" do let(:secret) { "supersekret" } let(:options) { [] } let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) } let(:rendered_template) do knife.parse_options(options) knife.merge_configs knife.render_template end it "creates a secret file" do expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) expect(knife).to receive(:read_secret).and_return(secret) expect(rendered_template).to match(%r{#{secret}}) end it "renders the client.rb with an encrypted_data_bag_secret entry" do expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) expect(knife).to receive(:read_secret).and_return(secret) expect(rendered_template).to match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) end end describe "when transferring trusted certificates" do let(:trusted_certs_dir) { Chef::Util::PathHelper.cleanpath(File.join(File.dirname(__FILE__), "../../data/trusted_certs")) } let(:rendered_template) do knife.merge_configs knife.render_template end before do Chef::Config[:trusted_certs_dir] = trusted_certs_dir allow(IO).to receive(:read).and_call_original allow(IO).to receive(:read).with(File.expand_path(Chef::Config[:validation_key])).and_return("") end def certificates Dir[File.join(trusted_certs_dir, "*.{crt,pem}")] end it "creates /etc/chef/trusted_certs" do expect(rendered_template).to match(%r{mkdir -p /etc/chef/trusted_certs}) end it "copies the certificates in the directory" do certificates.each do |cert| expect(IO).to receive(:read).with(File.expand_path(cert)) end certificates.each do |cert| expect(rendered_template).to match(%r{cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'}) end end it "doesn't create /etc/chef/trusted_certs if :trusted_certs_dir is empty" do expect(Dir).to receive(:glob).with(File.join(trusted_certs_dir, "*.{crt,pem}")).and_return([]) expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/trusted_certs}) end end context "when doing fips things" do let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) } let(:trusted_certs_dir) { Chef::Util::PathHelper.cleanpath(File.join(File.dirname(__FILE__), "../../data/trusted_certs")) } before do Chef::Config[:knife][:bootstrap_template] = template_file end let(:rendered_template) do knife.render_template end context "when knife is in fips mode" do before do Chef::Config[:fips] = true end it "renders 'fips true'" do Chef::Config[:fips] = true expect(rendered_template).to match("fips") end end context "when knife is not in fips mode" do before do # This is required because the chef-fips pipeline does # has a default value of true for fips Chef::Config[:fips] = false end it "does not render anything about fips" do expect(rendered_template).not_to match("fips") end end end describe "when transferring client.d" do let(:rendered_template) do knife.merge_configs knife.render_template end before do Chef::Config[:client_d_dir] = client_d_dir end context "when client_d_dir is nil" do let(:client_d_dir) { nil } it "does not create /etc/chef/client.d" do expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/client\.d}) end end context "when client_d_dir is set" do let(:client_d_dir) do Chef::Util::PathHelper.cleanpath( File.join(File.dirname(__FILE__), "../../data/client.d_00")) end it "creates /etc/chef/client.d" do expect(rendered_template).to match("mkdir -p /etc/chef/client\.d") end context "a flat directory structure" do it "creates a file 00-foo.rb" do expect(rendered_template).to match("cat > /etc/chef/client.d/00-foo.rb <<'EOP'") expect(rendered_template).to match("d6f9b976-289c-4149-baf7-81e6ffecf228") end it "creates a file bar" do expect(rendered_template).to match("cat > /etc/chef/client.d/bar <<'EOP'") expect(rendered_template).to match("1 / 0") end end context "a nested directory structure" do let(:client_d_dir) do Chef::Util::PathHelper.cleanpath( File.join(File.dirname(__FILE__), "../../data/client.d_01")) end it "creates a file foo/bar.rb" do expect(rendered_template).to match("cat > /etc/chef/client.d/foo/bar.rb <<'EOP'") expect(rendered_template).to match("1 / 0") end end end end describe "handling policyfile options" do context "when only policy_name is given" do let(:bootstrap_cli_options) { %w{ --policy-name my-app-server } } it "returns an error stating that policy_name and policy_group must be given together" do expect { knife.validate_options! }.to raise_error(SystemExit) expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together") end end context "when only policy_group is given" do let(:bootstrap_cli_options) { %w{ --policy-group staging } } it "returns an error stating that policy_name and policy_group must be given together" do expect { knife.validate_options! }.to raise_error(SystemExit) expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together") end end context "when both policy_name and policy_group are given, but run list is also given" do let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging --run-list cookbook } } it "returns an error stating that policyfile and run_list are exclusive" do expect { knife.validate_options! }.to raise_error(SystemExit) expect(stderr.string).to include("ERROR: Policyfile options and --run-list are exclusive") end end context "when policy_name and policy_group are given with no conflicting options" do let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging } } it "passes options validation" do expect { knife.validate_options! }.to_not raise_error end it "passes them into the bootstrap context" do expect(knife.bootstrap_context.first_boot).to have_key(:policy_name) expect(knife.bootstrap_context.first_boot).to have_key(:policy_group) end end # https://github.com/chef/chef/issues/4131 # Arguably a bug in the plugin: it shouldn't be setting this to nil, but it # worked before, so make it work now. context "when a plugin sets the run list option to nil" do before do knife.config[:run_list] = nil end it "passes options validation" do expect { knife.validate_options! }.to_not raise_error end end end describe "when configuring the underlying knife ssh command" do context "from the command line" do let(:knife_ssh) do knife.name_args = ["foo.example.com"] knife.config[:ssh_user] = "rooty" knife.config[:ssh_port] = "4001" knife.config[:ssh_password] = "open_sesame" Chef::Config[:knife][:ssh_user] = nil Chef::Config[:knife][:ssh_port] = nil knife.config[:forward_agent] = true knife.config[:ssh_identity_file] = "~/.ssh/me.rsa" allow(knife).to receive(:render_template).and_return("") knife.knife_ssh end it "configures the hostname" do expect(knife_ssh.name_args.first).to eq("foo.example.com") end it "configures the ssh user" do expect(knife_ssh.config[:ssh_user]).to eq("rooty") end it "configures the ssh password" do expect(knife_ssh.config[:ssh_password]).to eq("open_sesame") end it "configures the ssh port" do expect(knife_ssh.config[:ssh_port]).to eq("4001") end it "configures the ssh agent forwarding" do expect(knife_ssh.config[:forward_agent]).to eq(true) end it "configures the ssh identity file" do expect(knife_ssh.config[:ssh_identity_file]).to eq("~/.ssh/me.rsa") end end context "validating use_sudo_password" do before do knife.config[:ssh_password] = "password" allow(knife).to receive(:render_template).and_return("") end it "use_sudo_password contains description and long params for help" do expect(knife.options).to(have_key(:use_sudo_password)) \ && expect(knife.options[:use_sudo_password][:description].to_s).not_to(eq(""))\ && expect(knife.options[:use_sudo_password][:long].to_s).not_to(eq("")) end it "uses the password from --ssh-password for sudo when --use-sudo-password is set" do knife.config[:use_sudo] = true knife.config[:use_sudo_password] = true expect(knife.ssh_command).to include("echo \'#{knife.config[:ssh_password]}\' | sudo -S") end it "should not honor --use-sudo-password when --use-sudo is not set" do knife.config[:use_sudo] = false knife.config[:use_sudo_password] = true expect(knife.ssh_command).not_to include("echo #{knife.config[:ssh_password]} | sudo -S") end end context "from the knife config file" do let(:knife_ssh) do knife.name_args = ["config.example.com"] Chef::Config[:knife][:ssh_user] = "curiosity" Chef::Config[:knife][:ssh_port] = "2430" Chef::Config[:knife][:forward_agent] = true Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/you.rsa" Chef::Config[:knife][:ssh_gateway] = "towel.blinkenlights.nl" Chef::Config[:knife][:host_key_verify] = true allow(knife).to receive(:render_template).and_return("") knife.config = {} knife.merge_configs knife.knife_ssh end it "configures the ssh user" do expect(knife_ssh.config[:ssh_user]).to eq("curiosity") end it "configures the ssh port" do expect(knife_ssh.config[:ssh_port]).to eq("2430") end it "configures the ssh agent forwarding" do expect(knife_ssh.config[:forward_agent]).to eq(true) end it "configures the ssh identity file" do expect(knife_ssh.config[:ssh_identity_file]).to eq("~/.ssh/you.rsa") end it "configures the ssh gateway" do expect(knife_ssh.config[:ssh_gateway]).to eq("towel.blinkenlights.nl") end it "configures the host key verify mode" do expect(knife_ssh.config[:host_key_verify]).to eq(true) end end describe "when falling back to password auth when host key auth fails" do let(:knife_ssh_with_password_auth) do knife.name_args = ["foo.example.com"] knife.config[:ssh_user] = "rooty" knife.config[:ssh_identity_file] = "~/.ssh/me.rsa" allow(knife).to receive(:render_template).and_return("") k = knife.knife_ssh allow(k).to receive(:get_password).and_return("typed_in_password") allow(knife).to receive(:knife_ssh).and_return(k) knife.knife_ssh_with_password_auth end it "prompts the user for a password " do expect(knife_ssh_with_password_auth.config[:ssh_password]).to eq("typed_in_password") end it "configures knife not to use the identity file that didn't work previously" do expect(knife_ssh_with_password_auth.config[:ssh_identity_file]).to be_nil end end end it "verifies that a server to bootstrap was given as a command line arg" do knife.name_args = nil expect { knife.run }.to raise_error(SystemExit) expect(stderr.string).to match(/ERROR:.+FQDN or ip/) end describe "when running the bootstrap" do let(:knife_ssh) do knife.name_args = ["foo.example.com"] knife.config[:chef_node_name] = "foo.example.com" knife.config[:ssh_user] = "rooty" knife.config[:ssh_identity_file] = "~/.ssh/me.rsa" allow(knife).to receive(:render_template).and_return("") knife_ssh = knife.knife_ssh allow(knife).to receive(:knife_ssh).and_return(knife_ssh) knife_ssh end let(:client) { Chef::ApiClient.new } context "when running with a configured and present validation key" do before do # this tests runs the old code path where we have a validation key, so we need to pass that check allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true) end it "configures the underlying ssh command and then runs it" do expect(knife_ssh).to receive(:run) knife.run end it "falls back to password based auth when auth fails the first time" do allow(knife).to receive(:puts) fallback_knife_ssh = knife_ssh.dup expect(knife_ssh).to receive(:run).and_raise(Net::SSH::AuthenticationFailed.new("no ssh for you")) allow(knife).to receive(:knife_ssh_with_password_auth).and_return(fallback_knife_ssh) expect(fallback_knife_ssh).to receive(:run) knife.run end it "raises the exception if config[:ssh_password] is set and an authentication exception is raised" do knife.config[:ssh_password] = "password" expect(knife_ssh).to receive(:run).and_raise(Net::SSH::AuthenticationFailed) expect { knife.run }.to raise_error(Net::SSH::AuthenticationFailed) end it "creates the client and adds chef-vault items if vault_list is set" do knife.config[:bootstrap_vault_file] = "/not/our/responsibility/to/check/if/this/exists" expect(knife_ssh).to receive(:run) expect(knife.client_builder).to receive(:run) expect(knife.client_builder).to receive(:client).and_return(client) expect(knife.chef_vault_handler).to receive(:run).with(client) knife.run end it "creates the client and adds chef-vault items if vault_items is set" do knife.config[:bootstrap_vault_json] = '{ "vault" => "item" }' expect(knife_ssh).to receive(:run) expect(knife.client_builder).to receive(:run) expect(knife.client_builder).to receive(:client).and_return(client) expect(knife.chef_vault_handler).to receive(:run).with(client) knife.run end it "does old-style validation without creating a client key if vault_list+vault_items is not set" do expect(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true) expect(knife_ssh).to receive(:run) expect(knife.client_builder).not_to receive(:run) expect(knife.chef_vault_handler).not_to receive(:run) knife.run end it "raises an exception if the config[:chef_node_name] is not present" do knife.config[:chef_node_name] = nil expect { knife.run }.to raise_error(SystemExit) end end context "when the validation key is not present" do before do # this tests runs the old code path where we have a validation key, so we need to pass that check allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false) end it "creates the client (and possibly adds chef-vault items)" do expect(knife_ssh).to receive(:run) expect(knife.client_builder).to receive(:run) expect(knife.client_builder).to receive(:client).and_return(client) expect(knife.chef_vault_handler).to receive(:run).with(client) knife.run end it "raises an exception if the config[:chef_node_name] is not present" do knife.config[:chef_node_name] = nil expect { knife.run }.to raise_error(SystemExit) end end context "when the validation_key is nil" do before do # this tests runs the old code path where we have a validation key, so we need to pass that check for some plugins Chef::Config[:validation_key] = nil end it "creates the client and does not run client_builder or the chef_vault_handler" do expect(knife_ssh).to receive(:run) expect(knife.client_builder).not_to receive(:run) expect(knife.chef_vault_handler).not_to receive(:run) knife.run end end end describe "specifying ssl verification" do end end chef-12.14.60/spec/unit/knife/client_bulk_delete_spec.rb000066400000000000000000000116021276456504500230510ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::ClientBulkDelete do let(:stdout_io) { StringIO.new } let(:stdout) { stdout_io.string } let(:stderr_io) { StringIO.new } let(:stderr) { stderr_io.string } let(:knife) do k = Chef::Knife::ClientBulkDelete.new k.name_args = name_args k.config = option_args allow(k.ui).to receive(:stdout).and_return(stdout_io) allow(k.ui).to receive(:stderr).and_return(stderr_io) allow(k.ui).to receive(:confirm).and_return(knife_confirm) allow(k.ui).to receive(:confirm_without_exit).and_return(knife_confirm) k end let(:name_args) { [ "." ] } let(:option_args) { {} } let(:knife_confirm) { true } let(:nonvalidator_client_names) { %w{tim dan stephen} } let(:nonvalidator_clients) do clients = Hash.new nonvalidator_client_names.each do |client_name| client = Chef::ApiClientV1.new() client.name(client_name) allow(client).to receive(:destroy).and_return(true) clients[client_name] = client end clients end let(:validator_client_names) { %w{myorg-validator} } let(:validator_clients) do clients = Hash.new validator_client_names.each do |validator_client_name| validator_client = Chef::ApiClientV1.new() validator_client.name(validator_client_name) allow(validator_client).to receive(:validator).and_return(true) allow(validator_client).to receive(:destroy).and_return(true) clients[validator_client_name] = validator_client end clients end let(:client_names) { nonvalidator_client_names + validator_client_names } let(:clients) do nonvalidator_clients.merge(validator_clients) end before(:each) do allow(Chef::ApiClientV1).to receive(:list).and_return(clients) end describe "run" do describe "without a regex" do let(:name_args) { [ ] } it "should exit if the regex is not provided" do expect { knife.run }.to raise_error(SystemExit) end end describe "with any clients" do it "should get the list of the clients" do expect(Chef::ApiClientV1).to receive(:list) knife.run end it "should print the name of the clients" do knife.run client_names.each do |client_name| expect(stdout).to include(client_name) end end it "should confirm you really want to delete them" do expect(knife.ui).to receive(:confirm) knife.run end describe "without --delete-validators" do it "should mention that validator clients wont be deleted" do knife.run expect(stdout).to include("The following clients are validators and will not be deleted:") info = stdout.index "The following clients are validators and will not be deleted:" val = stdout.index "myorg-validator" expect(val > info).to be_truthy end it "should only delete nonvalidator clients" do nonvalidator_clients.each_value do |c| expect(c).to receive(:destroy) end validator_clients.each_value do |c| expect(c).not_to receive(:destroy) end knife.run end end describe "with --delete-validators" do let(:option_args) { { :delete_validators => true } } it "should mention that validator clients will be deleted" do knife.run expect(stdout).to include("The following validators will be deleted") end it "should confirm twice" do expect(knife.ui).to receive(:confirm).once expect(knife.ui).to receive(:confirm_without_exit).once knife.run end it "should delete all clients" do clients.each_value do |c| expect(c).to receive(:destroy) end knife.run end end end describe "with some clients" do let(:name_args) { [ "^ti" ] } it "should only delete clients that match the regex" do expect(clients["tim"]).to receive(:destroy) expect(clients["stephen"]).not_to receive(:destroy) expect(clients["dan"]).not_to receive(:destroy) expect(clients["myorg-validator"]).not_to receive(:destroy) knife.run end end end end chef-12.14.60/spec/unit/knife/client_create_spec.rb000066400000000000000000000116521276456504500220420ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" Chef::Knife::ClientCreate.load_deps describe Chef::Knife::ClientCreate do let(:stderr) { StringIO.new } let(:stdout) { StringIO.new } let(:default_client_hash) do { "name" => "adam", "validator" => false, "admin" => false, } end let(:client) do Chef::ApiClientV1.new end let(:knife) do k = Chef::Knife::ClientCreate.new k.name_args = [] allow(k).to receive(:client).and_return(client) allow(k).to receive(:edit_hash).with(client).and_return(client) allow(k.ui).to receive(:stderr).and_return(stderr) allow(k.ui).to receive(:stdout).and_return(stdout) k end before do allow(client).to receive(:to_s).and_return("client[adam]") allow(knife).to receive(:create_client).and_return(client) end before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" end describe "run" do context "when nothing is passed" do # from spec/support/shared/unit/knife_shared.rb it_should_behave_like "mandatory field missing" do let(:name_args) { [] } let(:fieldname) { "client name" } end end context "when clientname is passed" do before do knife.name_args = ["adam"] end context "when public_key and prevent_keygen are passed" do before do knife.config[:public_key] = "some_key" knife.config[:prevent_keygen] = true end it "prints the usage" do expect(knife).to receive(:show_usage) expect { knife.run }.to raise_error(SystemExit) end it "prints a relevant error message" do expect { knife.run }.to raise_error(SystemExit) expect(stderr.string).to match /You cannot pass --public-key and --prevent-keygen/ end end it "should create the ApiClient" do expect(knife).to receive(:create_client) knife.run end it "should print a message upon creation" do expect(knife).to receive(:create_client) knife.run expect(stderr.string).to match /Created client.*adam/i end it "should set the Client name" do knife.run expect(client.name).to eq("adam") end it "by default it is not an admin" do knife.run expect(client.admin).to be_falsey end it "by default it is not a validator" do knife.run expect(client.admin).to be_falsey end it "by default it should set create_key to true" do knife.run expect(client.create_key).to be_truthy end it "should allow you to edit the data" do expect(knife).to receive(:edit_hash).with(client).and_return(client) knife.run end describe "with -f or --file" do before do client.private_key "woot" end it "should write the private key to a file" do knife.config[:file] = "/tmp/monkeypants" filehandle = double("Filehandle") expect(filehandle).to receive(:print).with("woot") expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle) knife.run end end describe "with -a or --admin" do before do knife.config[:admin] = true end it "should create an admin client" do knife.run expect(client.admin).to be_truthy end end describe "with -p or --public-key" do before do knife.config[:public_key] = "some_key" allow(File).to receive(:read).and_return("some_key") allow(File).to receive(:expand_path) end it "sets the public key" do knife.run expect(client.public_key).to eq("some_key") end end describe "with -k or --prevent-keygen" do before do knife.config[:prevent_keygen] = true end it "does not set create_key" do knife.run expect(client.create_key).to be_falsey end end describe "with --validator" do before do knife.config[:validator] = true end it "should create an validator client" do knife.run expect(client.validator).to be_truthy end end end end end chef-12.14.60/spec/unit/knife/client_delete_spec.rb000066400000000000000000000051311276456504500220340ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::ClientDelete do before(:each) do @knife = Chef::Knife::ClientDelete.new # defaults @knife.config = { :delete_validators => false, } @knife.name_args = [ "adam" ] end describe "run" do it "should delete the client" do expect(@knife).to receive(:delete_object).with(Chef::ApiClientV1, "adam", "client") @knife.run end it "should print usage and exit when a client name is not provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end end describe "with a validator" do before(:each) do allow(Chef::Knife::UI).to receive(:confirm).and_return(true) allow(@knife).to receive(:confirm).and_return(true) @client = Chef::ApiClientV1.new expect(Chef::ApiClientV1).to receive(:load).and_return(@client) end it "should delete non-validator client if --delete-validators is not set" do @knife.config[:delete_validators] = false expect(@client).to receive(:destroy).and_return(@client) expect(@knife).to receive(:msg) @knife.run end it "should delete non-validator client if --delete-validators is set" do @knife.config[:delete_validators] = true expect(@client).to receive(:destroy).and_return(@client) expect(@knife).to receive(:msg) @knife.run end it "should not delete validator client if --delete-validators is not set" do @client.validator(true) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end it "should delete validator client if --delete-validators is set" do @knife.config[:delete_validators] = true expect(@client).to receive(:destroy).and_return(@client) expect(@knife).to receive(:msg) @knife.run end end end chef-12.14.60/spec/unit/knife/client_edit_spec.rb000066400000000000000000000031111276456504500215130ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/api_client_v1" describe Chef::Knife::ClientEdit do before(:each) do @knife = Chef::Knife::ClientEdit.new @knife.name_args = [ "adam" ] @knife.config[:disable_editing] = true end describe "run" do let(:data) do { "name" => "adam", "validator" => false, "admin" => false, "chef_type" => "client", "create_key" => true, } end it "should edit the client" do allow(Chef::ApiClientV1).to receive(:load).with("adam").and_return(data) expect(@knife).to receive(:edit_hash).with(data).and_return(data) @knife.run end it "should print usage and exit when a client name is not provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end end end chef-12.14.60/spec/unit/knife/client_list_spec.rb000066400000000000000000000020341276456504500215440ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::ClientList do before(:each) do @knife = Chef::Knife::ClientList.new @knife.name_args = [ "adam" ] end describe "run" do it "should list the clients" do expect(Chef::ApiClientV1).to receive(:list) expect(@knife).to receive(:format_list_for_display) @knife.run end end end chef-12.14.60/spec/unit/knife/client_reregister_spec.rb000066400000000000000000000040621276456504500227470ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::ClientReregister do before(:each) do @knife = Chef::Knife::ClientReregister.new @knife.name_args = [ "adam" ] @client_mock = double("client_mock", :private_key => "foo_key") @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end context "when no client name is given on the command line" do before do @knife.name_args = [] end it "should print usage and exit when a client name is not provided" do expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end end context "when not configured for file output" do it "reregisters the client and prints the key" do expect(Chef::ApiClientV1).to receive(:reregister).with("adam").and_return(@client_mock) @knife.run expect(@stdout.string).to match( /foo_key/ ) end end context "when configured for file output" do it "should write the private key to a file" do expect(Chef::ApiClientV1).to receive(:reregister).with("adam").and_return(@client_mock) @knife.config[:file] = "/tmp/monkeypants" filehandle = StringIO.new expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle) @knife.run expect(filehandle.string).to eq("foo_key") end end end chef-12.14.60/spec/unit/knife/client_show_spec.rb000066400000000000000000000034571276456504500215630ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::ClientShow do before(:each) do @knife = Chef::Knife::ClientShow.new @knife.name_args = [ "adam" ] @client_mock = double("client_mock") end describe "run" do it "should list the client" do expect(Chef::ApiClientV1).to receive(:load).with("adam").and_return(@client_mock) expect(@knife).to receive(:format_for_display).with(@client_mock) @knife.run end it "should pretty print json" do @knife.config[:format] = "json" @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) fake_client_contents = { "foo" => "bar", "baz" => "qux" } expect(Chef::ApiClientV1).to receive(:load).with("adam").and_return(fake_client_contents) @knife.run expect(@stdout.string).to eql("{\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n}\n") end it "should print usage and exit when a client name is not provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end end end chef-12.14.60/spec/unit/knife/configure_client_spec.rb000066400000000000000000000057441276456504500225650ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::ConfigureClient do before do @knife = Chef::Knife::ConfigureClient.new Chef::Config[:chef_server_url] = "https://chef.example.com" Chef::Config[:validation_client_name] = "chef-validator" Chef::Config[:validation_key] = "/etc/chef/validation.pem" @stderr = StringIO.new allow(@knife.ui).to receive(:stderr).and_return(@stderr) end describe "run" do it "should print usage and exit when a directory is not provided" do expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal).with(/must provide the directory/) expect do @knife.run end.to raise_error SystemExit end describe "when specifing a directory" do before do @knife.name_args = ["/home/bob/.chef"] @client_file = StringIO.new @validation_file = StringIO.new expect(File).to receive(:open).with("/home/bob/.chef/client.rb", "w"). and_yield(@client_file) expect(File).to receive(:open).with("/home/bob/.chef/validation.pem", "w"). and_yield(@validation_file) expect(IO).to receive(:read).and_return("foo_bar_baz") end it "should recursively create the directory" do expect(FileUtils).to receive(:mkdir_p).with("/home/bob/.chef") @knife.run end it "should write out the config file" do allow(FileUtils).to receive(:mkdir_p) @knife.run expect(@client_file.string).to match /log_level\s+\:info/ expect(@client_file.string).to match /log_location\s+STDOUT/ expect(@client_file.string).to match /chef_server_url\s+'https\:\/\/chef\.example\.com'/ expect(@client_file.string).to match /validation_client_name\s+'chef-validator'/ end it "should write out the validation.pem file" do allow(FileUtils).to receive(:mkdir_p) @knife.run expect(@validation_file.string).to match /foo_bar_baz/ end it "should print information on what is being configured" do allow(FileUtils).to receive(:mkdir_p) @knife.run expect(@stderr.string).to match /creating client configuration/i expect(@stderr.string).to match /writing client\.rb/i expect(@stderr.string).to match /writing validation\.pem/i end end end end chef-12.14.60/spec/unit/knife/configure_spec.rb000066400000000000000000000260271276456504500212240ustar00rootroot00000000000000require "spec_helper" describe Chef::Knife::Configure do before do Chef::Log.logger = Logger.new(StringIO.new) Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::Configure.new @rest_client = double("null rest client", :post => { :result => :true }) allow(@knife).to receive(:rest).and_return(@rest_client) @out = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@out) @knife.config[:config_file] = "/home/you/.chef/knife.rb" @in = StringIO.new("\n" * 7) allow(@knife.ui).to receive(:stdin).and_return(@in) @err = StringIO.new allow(@knife.ui).to receive(:stderr).and_return(@err) allow(Ohai::System).to receive(:new).and_return(ohai) end let(:fqdn) { "foo.example.org" } let(:ohai) do o = {} allow(o).to receive(:require_plugin) allow(o).to receive(:load_plugins) o[:fqdn] = fqdn o end let(:default_admin_key) { "/etc/chef-server/admin.pem" } let(:default_admin_key_win32) { File.expand_path(default_admin_key) } let(:default_validator_key) { "/etc/chef-server/chef-validator.pem" } let(:default_validator_key_win32) { File.expand_path(default_validator_key) } let(:default_server_url) { "https://#{fqdn}:443" } it "asks the user for the URL of the chef server" do @knife.ask_user_for_config expect(@out.string).to match(Regexp.escape("Please enter the chef server URL: [#{default_server_url}]")) expect(@knife.chef_server).to eq(default_server_url) end it "asks the user for the clientname they want for the new client if -i is specified" do @knife.config[:initial] = true allow(Etc).to receive(:getlogin).and_return("a-new-user") @knife.ask_user_for_config expect(@out.string).to match(Regexp.escape("Please enter a name for the new user: [a-new-user]")) expect(@knife.new_client_name).to eq(Etc.getlogin) end it "should not ask the user for the clientname they want for the new client if -i and --node_name are specified" do @knife.config[:initial] = true @knife.config[:node_name] = "testnode" allow(Etc).to receive(:getlogin).and_return("a-new-user") @knife.ask_user_for_config expect(@out.string).not_to match(Regexp.escape("Please enter a name for the new user")) expect(@knife.new_client_name).to eq("testnode") end it "asks the user for the existing API username or clientname if -i is not specified" do allow(Etc).to receive(:getlogin).and_return("a-new-user") @knife.ask_user_for_config expect(@out.string).to match(Regexp.escape("Please enter an existing username or clientname for the API: [a-new-user]")) expect(@knife.new_client_name).to eq(Etc.getlogin) end it "asks the user for the existing admin client's name if -i is specified" do @knife.config[:initial] = true @knife.ask_user_for_config expect(@out.string).to match(Regexp.escape("Please enter the existing admin name: [admin]")) expect(@knife.admin_client_name).to eq("admin") end it "should not ask the user for the existing admin client's name if -i and --admin-client_name are specified" do @knife.config[:initial] = true @knife.config[:admin_client_name] = "my-webui" @knife.ask_user_for_config expect(@out.string).not_to match(Regexp.escape("Please enter the existing admin:")) expect(@knife.admin_client_name).to eq("my-webui") end it "should not ask the user for the existing admin client's name if -i is not specified" do @knife.ask_user_for_config expect(@out.string).not_to match(Regexp.escape("Please enter the existing admin: [admin]")) expect(@knife.admin_client_name).not_to eq("admin") end it "asks the user for the location of the existing admin key if -i is specified" do @knife.config[:initial] = true @knife.ask_user_for_config expect(@out.string).to match(Regexp.escape("Please enter the location of the existing admin's private key: [#{default_admin_key}]")) if windows? expect(@knife.admin_client_key.capitalize).to eq(default_admin_key_win32.capitalize) else expect(@knife.admin_client_key).to eq(default_admin_key) end end it "should not ask the user for the location of the existing admin key if -i and --admin_client_key are specified" do @knife.config[:initial] = true @knife.config[:admin_client_key] = "/home/you/.chef/my-webui.pem" @knife.ask_user_for_config expect(@out.string).not_to match(Regexp.escape("Please enter the location of the existing admin client's private key:")) if windows? expect(@knife.admin_client_key).to match %r{^[A-Za-z]:/home/you/\.chef/my-webui\.pem$} else expect(@knife.admin_client_key).to eq("/home/you/.chef/my-webui.pem") end end it "should not ask the user for the location of the existing admin key if -i is not specified" do @knife.ask_user_for_config expect(@out.string).not_to match(Regexp.escape("Please enter the location of the existing admin client's private key: [#{default_admin_key}]")) if windows? expect(@knife.admin_client_key).not_to eq(default_admin_key_win32) else expect(@knife.admin_client_key).not_to eq(default_admin_key) end end it "asks the user for the location of a chef repo" do @knife.ask_user_for_config expect(@out.string).to match(Regexp.escape("Please enter the path to a chef repository (or leave blank):")) expect(@knife.chef_repo).to eq("") end it "asks the users for the name of the validation client" do @knife.ask_user_for_config expect(@out.string).to match(Regexp.escape("Please enter the validation clientname: [chef-validator]")) expect(@knife.validation_client_name).to eq("chef-validator") end it "should not ask the users for the name of the validation client if --validation_client_name is specified" do @knife.config[:validation_client_name] = "my-validator" @knife.ask_user_for_config expect(@out.string).not_to match(Regexp.escape("Please enter the validation clientname:")) expect(@knife.validation_client_name).to eq("my-validator") end it "asks the users for the location of the validation key" do @knife.ask_user_for_config expect(@out.string).to match(Regexp.escape("Please enter the location of the validation key: [#{default_validator_key}]")) if windows? expect(@knife.validation_key.capitalize).to eq(default_validator_key_win32.capitalize) else expect(@knife.validation_key).to eq(default_validator_key) end end it "should not ask the users for the location of the validation key if --validation_key is specified" do @knife.config[:validation_key] = "/home/you/.chef/my-validation.pem" @knife.ask_user_for_config expect(@out.string).not_to match(Regexp.escape("Please enter the location of the validation key:")) if windows? expect(@knife.validation_key).to match %r{^[A-Za-z]:/home/you/\.chef/my-validation\.pem$} else expect(@knife.validation_key).to eq("/home/you/.chef/my-validation.pem") end end it "should not ask the user for anything if -i and all other properties are specified" do @knife.config[:initial] = true @knife.config[:chef_server_url] = "http://localhost:5000" @knife.config[:node_name] = "testnode" @knife.config[:admin_client_name] = "my-webui" @knife.config[:admin_client_key] = "/home/you/.chef/my-webui.pem" @knife.config[:validation_client_name] = "my-validator" @knife.config[:validation_key] = "/home/you/.chef/my-validation.pem" @knife.config[:repository] = "" @knife.config[:client_key] = "/home/you/a-new-user.pem" allow(Etc).to receive(:getlogin).and_return("a-new-user") @knife.ask_user_for_config expect(@out.string).to match(/\s*/) expect(@knife.new_client_name).to eq("testnode") expect(@knife.chef_server).to eq("http://localhost:5000") expect(@knife.admin_client_name).to eq("my-webui") if windows? expect(@knife.admin_client_key).to match %r{^[A-Za-z]:/home/you/\.chef/my-webui\.pem$} expect(@knife.validation_key).to match %r{^[A-Za-z]:/home/you/\.chef/my-validation\.pem$} expect(@knife.new_client_key).to match %r{^[A-Za-z]:/home/you/a-new-user\.pem$} else expect(@knife.admin_client_key).to eq("/home/you/.chef/my-webui.pem") expect(@knife.validation_key).to eq("/home/you/.chef/my-validation.pem") expect(@knife.new_client_key).to eq("/home/you/a-new-user.pem") end expect(@knife.validation_client_name).to eq("my-validator") expect(@knife.chef_repo).to eq("") end it "writes the new data to a config file" do allow(File).to receive(:expand_path).with("/home/you/.chef/knife.rb").and_return("/home/you/.chef/knife.rb") allow(File).to receive(:expand_path).with("/home/you/.chef/#{Etc.getlogin}.pem").and_return("/home/you/.chef/#{Etc.getlogin}.pem") allow(File).to receive(:expand_path).with(default_validator_key).and_return(default_validator_key) allow(File).to receive(:expand_path).with(default_admin_key).and_return(default_admin_key) expect(FileUtils).to receive(:mkdir_p).with("/home/you/.chef") config_file = StringIO.new expect(::File).to receive(:open).with("/home/you/.chef/knife.rb", "w").and_yield config_file @knife.config[:repository] = "/home/you/chef-repo" @knife.run expect(config_file.string).to match(/^node_name[\s]+'#{Etc.getlogin}'$/) expect(config_file.string).to match(%r{^client_key[\s]+'/home/you/.chef/#{Etc.getlogin}.pem'$}) expect(config_file.string).to match(/^validation_client_name\s+'chef-validator'$/) expect(config_file.string).to match(%r{^validation_key\s+'#{default_validator_key}'$}) expect(config_file.string).to match(%r{^chef_server_url\s+'#{default_server_url}'$}) expect(config_file.string).to match(%r{cookbook_path\s+\[ '/home/you/chef-repo/cookbooks' \]}) end it "creates a new client when given the --initial option" do expect(File).to receive(:expand_path).with("/home/you/.chef/knife.rb").and_return("/home/you/.chef/knife.rb") expect(File).to receive(:expand_path).with("/home/you/.chef/a-new-user.pem").and_return("/home/you/.chef/a-new-user.pem") expect(File).to receive(:expand_path).with(default_validator_key).and_return(default_validator_key) expect(File).to receive(:expand_path).with(default_admin_key).and_return(default_admin_key) Chef::Config[:node_name] = "webmonkey.example.com" user_command = Chef::Knife::UserCreate.new expect(user_command).to receive(:run) allow(Etc).to receive(:getlogin).and_return("a-new-user") allow(Chef::Knife::UserCreate).to receive(:new).and_return(user_command) expect(FileUtils).to receive(:mkdir_p).with("/home/you/.chef") expect(::File).to receive(:open).with("/home/you/.chef/knife.rb", "w") @knife.config[:initial] = true @knife.config[:user_password] = "blah" @knife.run expect(user_command.name_args).to eq(Array("a-new-user")) expect(user_command.config[:user_password]).to eq("blah") expect(user_command.config[:admin]).to be_truthy expect(user_command.config[:file]).to eq("/home/you/.chef/a-new-user.pem") expect(user_command.config[:yes]).to be_truthy expect(user_command.config[:disable_editing]).to be_truthy end end chef-12.14.60/spec/unit/knife/cookbook_bulk_delete_spec.rb000066400000000000000000000066001276456504500234030ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::CookbookBulkDelete do before(:each) do Chef::Log.logger = Logger.new(StringIO.new) Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::CookbookBulkDelete.new @knife.config = { :print_after => nil } @knife.name_args = ["."] @stdout = StringIO.new @stderr = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) allow(@knife.ui).to receive(:stderr).and_return(@stderr) allow(@knife.ui).to receive(:confirm).and_return(true) @cookbooks = Hash.new %w{cheezburger pizza lasagna}.each do |cookbook_name| cookbook = Chef::CookbookVersion.new(cookbook_name) @cookbooks[cookbook_name] = cookbook end @rest = double("Chef::ServerAPI") allow(@rest).to receive(:get).and_return(@cookbooks) allow(@rest).to receive(:delete).and_return(true) allow(@knife).to receive(:rest).and_return(@rest) allow(Chef::CookbookVersion).to receive(:list).and_return(@cookbooks) end describe "when there are several cookbooks on the server" do before do @cheezburger = { "cheezburger" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-cheez", "version" => "1.0.0" }] } } allow(@rest).to receive(:get).with("cookbooks/cheezburger").and_return(@cheezburger) @pizza = { "pizza" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-pizza", "version" => "2.0.0" }] } } allow(@rest).to receive(:get).with("cookbooks/pizza").and_return(@pizza) @lasagna = { "lasagna" => { "url" => "file:///dev/null", "versions" => [{ "url" => "file:///dev/null-lasagna", "version" => "3.0.0" }] } } allow(@rest).to receive(:get).with("cookbooks/lasagna").and_return(@lasagna) end it "should print the cookbooks you are about to delete" do expected = @knife.ui.list(@cookbooks.keys.sort, :columns_down) @knife.run expect(@stdout.string).to match(/#{expected}/) end it "should confirm you really want to delete them" do expect(@knife.ui).to receive(:confirm) @knife.run end it "should delete each cookbook" do { "cheezburger" => "1.0.0", "pizza" => "2.0.0", "lasagna" => "3.0.0" }.each do |cookbook_name, version| expect(@rest).to receive(:delete).with("cookbooks/#{cookbook_name}/#{version}") end @knife.run end it "should only delete cookbooks that match the regex" do @knife.name_args = ["cheezburger"] expect(@rest).to receive(:delete).with("cookbooks/cheezburger/1.0.0") @knife.run end end it "should exit if the regex is not provided" do @knife.name_args = [] expect { @knife.run }.to raise_error(SystemExit) end end chef-12.14.60/spec/unit/knife/cookbook_create_spec.rb000066400000000000000000000275111276456504500223730ustar00rootroot00000000000000# # Author:: Nuo Yan () # Copyright:: Copyright 2010-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 "tmpdir" describe Chef::Knife::CookbookCreate do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" Chef::Config[:treat_deprecation_warnings_as_errors] = false @knife = Chef::Knife::CookbookCreate.new @knife.config = {} @knife.name_args = ["foobar"] @stdout = StringIO.new allow(@knife).to receive(:stdout).and_return(@stdout) end describe "run" do # Fixes CHEF-2579 it "should expand the path of the cookbook directory" do expect(File).to receive(:expand_path).with("~/tmp/monkeypants") @knife.config = { :cookbook_path => "~/tmp/monkeypants" } allow(@knife).to receive(:create_cookbook) allow(@knife).to receive(:create_readme) allow(@knife).to receive(:create_changelog) allow(@knife).to receive(:create_metadata) @knife.run end it "should create a new cookbook with default values to copyright name, email, readme format and license if those are not supplied" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir } expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "YOUR_COMPANY_NAME", "none") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "YOUR_COMPANY_NAME", "YOUR_EMAIL", "none", "md") @knife.run end it "should create a new cookbook with specified company name in the copyright section if one is specified" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "none") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "YOUR_EMAIL", "none", "md") @knife.run end it "should create a new cookbook with specified copyright name and email if they are specified" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "none") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "none", "md") @knife.run end it "should create a new cookbook with specified copyright name and email and license information (true) if they are specified" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", :cookbook_license => "apachev2", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "apachev2") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "apachev2", "md") @knife.run end it "should create a new cookbook with specified copyright name and email and license information (false) if they are specified" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", :cookbook_license => false, } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "none") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "none", "md") @knife.run end it "should create a new cookbook with specified copyright name and email and license information ('false' as string) if they are specified" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", :cookbook_license => "false", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "none") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "none", "md") @knife.run end it "should allow specifying a gpl2 license" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", :cookbook_license => "gplv2", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "gplv2") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "gplv2", "md") @knife.run end it "should allow specifying a gplv3 license" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", :cookbook_license => "gplv3", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "gplv3") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "gplv3", "md") @knife.run end it "should allow specifying the mit license" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", :cookbook_license => "mit", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "mit") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "md") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "mit", "md") @knife.run end it "should allow specifying the rdoc readme format" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", :cookbook_license => "mit", :readme_format => "rdoc", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "mit") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "rdoc") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "mit", "rdoc") @knife.run end it "should allow specifying the md readme format" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", :cookbook_license => "mit", :readme_format => "mkd", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "mit") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "mkd") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "mit", "mkd") @knife.run end it "should allow specifying the txt readme format" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", :cookbook_license => "mit", :readme_format => "txt", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "mit") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "txt") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "mit", "txt") @knife.run end it "should allow specifying an arbitrary readme format" do @dir = Dir.tmpdir @knife.config = { :cookbook_path => @dir, :cookbook_copyright => "Chef Software, Inc.", :cookbook_email => "test@chef.io", :cookbook_license => "mit", :readme_format => "foo", } @knife.name_args = ["foobar"] expect(@knife).to receive(:create_cookbook).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "mit") expect(@knife).to receive(:create_readme).with(@dir, @knife.name_args.first, "foo") expect(@knife).to receive(:create_changelog).with(@dir, @knife.name_args.first) expect(@knife).to receive(:create_metadata).with(@dir, @knife.name_args.first, "Chef Software, Inc.", "test@chef.io", "mit", "foo") @knife.run end context "when the cookbooks path is set to nil" do before do Chef::Config[:cookbook_path] = nil end it "should throw an argument error" do @dir = Dir.tmpdir expect { @knife.run }.to raise_error(ArgumentError) end end end end chef-12.14.60/spec/unit/knife/cookbook_delete_spec.rb000066400000000000000000000214611276456504500223700ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::CookbookDelete do before(:each) do @knife = Chef::Knife::CookbookDelete.new @knife.name_args = ["foobar"] @knife.cookbook_name = "foobar" @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) @stderr = StringIO.new allow(@knife.ui).to receive(:stderr).and_return(@stderr) end describe "run" do it "should print usage and exit when a cookbook name is not provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end describe "when specifying a cookbook name" do it "should delete the cookbook without a specific version" do expect(@knife).to receive(:delete_without_explicit_version) @knife.run end describe "and a version" do it "should delete the specific version of the cookbook" do @knife.name_args << "1.0.0" expect(@knife).to receive(:delete_explicit_version) @knife.run end end describe "with -a or --all" do it "should delete all versions of the cookbook" do @knife.config[:all] = true expect(@knife).to receive(:delete_all_versions) @knife.run end end describe "with -p or --purge" do it "should prompt to purge the files" do @knife.config[:purge] = true expect(@knife).to receive(:confirm). with(/.+Are you sure you want to purge files.+/) expect(@knife).to receive(:delete_without_explicit_version) @knife.run end end end end describe "delete_explicit_version" do it "should delete the specific cookbook version" do @knife.cookbook_name = "foobar" @knife.version = "1.0.0" expect(@knife).to receive(:delete_object).with(Chef::CookbookVersion, "foobar version 1.0.0", "cookbook").and_yield() expect(@knife).to receive(:delete_request).with("cookbooks/foobar/1.0.0") @knife.delete_explicit_version end end describe "delete_all_versions" do it "should prompt to delete all versions of the cookbook" do @knife.cookbook_name = "foobar" expect(@knife).to receive(:confirm).with("Do you really want to delete all versions of foobar") expect(@knife).to receive(:delete_all_without_confirmation) @knife.delete_all_versions end end describe "delete_all_without_confirmation" do it "should delete all versions without confirmation" do versions = ["1.0.0", "1.1.0"] expect(@knife).to receive(:available_versions).and_return(versions) versions.each do |v| expect(@knife).to receive(:delete_version_without_confirmation).with(v) end @knife.delete_all_without_confirmation end end describe "delete_without_explicit_version" do it "should exit if there are no available versions" do expect(@knife).to receive(:available_versions).and_return(nil) expect { @knife.delete_without_explicit_version }.to raise_error(SystemExit) end it "should delete the version if only one is found" do expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0"]) expect(@knife).to receive(:delete_explicit_version) @knife.delete_without_explicit_version end it "should ask which version(s) to delete if multiple are found" do expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0", "1.1.0"]) expect(@knife).to receive(:ask_which_versions_to_delete).and_return(["1.0.0", "1.1.0"]) expect(@knife).to receive(:delete_versions_without_confirmation).with(["1.0.0", "1.1.0"]) @knife.delete_without_explicit_version end end describe "available_versions" do before(:each) do @rest_mock = double("rest") expect(@knife).to receive(:rest).and_return(@rest_mock) @cookbook_data = { "foobar" => { "versions" => [{ "version" => "1.0.0" }, { "version" => "1.1.0" }, { "version" => "2.0.0" } ] }, } end it "should return the list of versions of the cookbook" do expect(@rest_mock).to receive(:get).with("cookbooks/foobar").and_return(@cookbook_data) expect(@knife.available_versions).to eq(["1.0.0", "1.1.0", "2.0.0"]) end it "should raise if an error other than HTTP 404 is returned" do exception = Net::HTTPServerException.new("500 Internal Server Error", "500") expect(@rest_mock).to receive(:get).and_raise(exception) expect { @knife.available_versions }.to raise_error Net::HTTPServerException end describe "if the cookbook can't be found" do before(:each) do expect(@rest_mock).to receive(:get). and_raise(Net::HTTPServerException.new("404 Not Found", "404")) end it "should print an error" do @knife.available_versions expect(@stderr.string).to match /error.+cannot find a cookbook named foobar/i end it "should return nil" do expect(@knife.available_versions).to eq(nil) end end end describe "ask_which_version_to_delete" do before(:each) do allow(@knife).to receive(:available_versions).and_return(["1.0.0", "1.1.0", "2.0.0"]) end it "should prompt the user to select a version" do prompt = /Which version\(s\) do you want to delete\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+4\. All versions.+/m expect(@knife).to receive(:ask_question).with(prompt).and_return("1") @knife.ask_which_versions_to_delete end it "should print an error and exit if a version wasn't specified" do expect(@knife).to receive(:ask_question).and_return("") expect(@knife.ui).to receive(:error).with(/no versions specified/i) expect { @knife.ask_which_versions_to_delete }.to raise_error(SystemExit) end it "should print an error if an invalid choice was selected" do expect(@knife).to receive(:ask_question).and_return("100") expect(@knife.ui).to receive(:error).with(/100 is not a valid choice/i) @knife.ask_which_versions_to_delete end it "should return the selected versions" do expect(@knife).to receive(:ask_question).and_return("1, 3") expect(@knife.ask_which_versions_to_delete).to eq(["1.0.0", "2.0.0"]) end it "should return all of the versions if 'all' was selected" do expect(@knife).to receive(:ask_question).and_return("4") expect(@knife.ask_which_versions_to_delete).to eq([:all]) end end describe "delete_version_without_confirmation" do it "should delete the cookbook version" do expect(@knife).to receive(:delete_request).with("cookbooks/foobar/1.0.0") @knife.delete_version_without_confirmation("1.0.0") end it "should output that the cookbook was deleted" do allow(@knife).to receive(:delete_request) @knife.delete_version_without_confirmation("1.0.0") expect(@stderr.string).to match /deleted cookbook\[foobar\]\[1.0.0\]/im end describe "with --print-after" do it "should display the cookbook data" do object = "" @knife.config[:print_after] = true allow(@knife).to receive(:delete_request).and_return(object) expect(@knife).to receive(:format_for_display).with(object) @knife.delete_version_without_confirmation("1.0.0") end end end describe "delete_versions_without_confirmation" do it "should delete each version without confirmation" do versions = ["1.0.0", "1.1.0"] versions.each do |v| expect(@knife).to receive(:delete_version_without_confirmation).with(v) end @knife.delete_versions_without_confirmation(versions) end describe "with -a or --all" do it "should delete all versions without confirmation" do versions = [:all] expect(@knife).to receive(:delete_all_without_confirmation) @knife.delete_versions_without_confirmation(versions) end end end end chef-12.14.60/spec/unit/knife/cookbook_download_spec.rb000066400000000000000000000222441276456504500227350ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::CookbookDownload do before(:each) do @knife = Chef::Knife::CookbookDownload.new @stderr = StringIO.new allow(@knife.ui).to receive(:stderr).and_return(@stderr) end describe "run" do it "should print usage and exit when a cookbook name is not provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal).with(/must specify a cookbook name/) expect { @knife.run }.to raise_error(SystemExit) end it "should exit with a fatal error when there is no cookbook on the server" do @knife.name_args = ["foobar", nil] expect(@knife).to receive(:determine_version).and_return(nil) expect(@knife.ui).to receive(:fatal).with("No such cookbook found") expect { @knife.run }.to raise_error(SystemExit) end describe "with a cookbook name" do before(:each) do @knife.name_args = ["foobar"] @knife.config[:download_directory] = "/var/tmp/chef" @rest_mock = double("rest") allow(@knife).to receive(:rest).and_return(@rest_mock) @manifest_data = { :recipes => [ { "path" => "recipes/foo.rb", "url" => "http://example.org/files/foo.rb" }, { "path" => "recipes/bar.rb", "url" => "http://example.org/files/bar.rb" }, ], :templates => [ { "path" => "templates/default/foo.erb", "url" => "http://example.org/files/foo.erb" }, { "path" => "templates/default/bar.erb", "url" => "http://example.org/files/bar.erb" }, ], :attributes => [ { "path" => "attributes/default.rb", "url" => "http://example.org/files/default.rb" }, ], } @cookbook_mock = double("cookbook") allow(@cookbook_mock).to receive(:version).and_return("1.0.0") allow(@cookbook_mock).to receive(:manifest).and_return(@manifest_data) expect(Chef::CookbookVersion).to receive(:load).with("foobar", "1.0.0"). and_return(@cookbook_mock) end it "should determine which version if one was not explicitly specified" do allow(@cookbook_mock).to receive(:manifest).and_return({}) expect(@knife).to receive(:determine_version).and_return("1.0.0") expect(File).to receive(:exists?).with("/var/tmp/chef/foobar-1.0.0").and_return(false) allow(Chef::CookbookVersion).to receive(:COOKBOOK_SEGEMENTS).and_return([]) @knife.run end describe "and a version" do before(:each) do @knife.name_args << "1.0.0" @files = @manifest_data.values.map { |v| v.map { |i| i["path"] } }.flatten.uniq @files_mocks = {} @files.map { |f| File.basename(f) }.flatten.uniq.each do |f| @files_mocks[f] = double("#{f}_mock") allow(@files_mocks[f]).to receive(:path).and_return("/var/tmp/#{f}") end end it "should print an error and exit if the cookbook download directory already exists" do expect(File).to receive(:exists?).with("/var/tmp/chef/foobar-1.0.0").and_return(true) expect(@knife.ui).to receive(:fatal).with(/\/var\/tmp\/chef\/foobar-1\.0\.0 exists/i) expect { @knife.run }.to raise_error(SystemExit) end describe "when downloading the cookbook" do before(:each) do @files.map { |f| File.dirname(f) }.flatten.uniq.each do |dir| expect(FileUtils).to receive(:mkdir_p).with("/var/tmp/chef/foobar-1.0.0/#{dir}"). at_least(:once) end @files_mocks.each_pair do |file, mock| expect(@rest_mock).to receive(:streaming_request).with("http://example.org/files/#{file}"). and_return(mock) end @files.each do |f| expect(FileUtils).to receive(:mv). with("/var/tmp/#{File.basename(f)}", "/var/tmp/chef/foobar-1.0.0/#{f}") end end it "should download the cookbook when the cookbook download directory doesn't exist" do expect(File).to receive(:exists?).with("/var/tmp/chef/foobar-1.0.0").and_return(false) @knife.run %w{attributes recipes templates}.each do |segment| expect(@stderr.string).to match /downloading #{segment}/im end expect(@stderr.string).to match /downloading foobar cookbook version 1\.0\.0/im expect(@stderr.string).to match /cookbook downloaded to \/var\/tmp\/chef\/foobar-1\.0\.0/im end describe "with -f or --force" do it "should remove the existing the cookbook download directory if it exists" do @knife.config[:force] = true expect(File).to receive(:exists?).with("/var/tmp/chef/foobar-1.0.0").and_return(true) expect(FileUtils).to receive(:rm_rf).with("/var/tmp/chef/foobar-1.0.0") @knife.run end end end end end end describe "determine_version" do it "should return nil if there are no versions" do expect(@knife).to receive(:available_versions).and_return(nil) expect(@knife.determine_version).to eq(nil) expect(@knife.version).to eq(nil) end it "should return and set the version if there is only one version" do expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0"]) expect(@knife.determine_version).to eq("1.0.0") expect(@knife.version).to eq("1.0.0") end it "should ask which version to download and return it if there is more than one" do expect(@knife).to receive(:available_versions).at_least(:once).and_return(["1.0.0", "2.0.0"]) expect(@knife).to receive(:ask_which_version).and_return("1.0.0") expect(@knife.determine_version).to eq("1.0.0") end describe "with -N or --latest" do it "should return and set the version to the latest version" do @knife.config[:latest] = true expect(@knife).to receive(:available_versions).at_least(:once). and_return(["1.0.0", "1.1.0", "2.0.0"]) @knife.determine_version expect(@knife.version.to_s).to eq("2.0.0") end end end describe "available_versions" do before(:each) do @knife.cookbook_name = "foobar" end it "should return nil if there are no versions" do expect(Chef::CookbookVersion).to receive(:available_versions). with("foobar"). and_return(nil) expect(@knife.available_versions).to eq(nil) end it "should return the available versions" do expect(Chef::CookbookVersion).to receive(:available_versions). with("foobar"). and_return(["1.1.0", "2.0.0", "1.0.0"]) expect(@knife.available_versions).to eq([Chef::Version.new("1.0.0"), Chef::Version.new("1.1.0"), Chef::Version.new("2.0.0")]) end it "should avoid multiple API calls to the server" do expect(Chef::CookbookVersion).to receive(:available_versions). once. with("foobar"). and_return(["1.1.0", "2.0.0", "1.0.0"]) @knife.available_versions @knife.available_versions end end describe "ask_which_version" do before(:each) do @knife.cookbook_name = "foobar" allow(@knife).to receive(:available_versions).and_return(["1.0.0", "1.1.0", "2.0.0"]) end it "should prompt the user to select a version" do prompt = /Which version do you want to download\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+/m expect(@knife).to receive(:ask_question).with(prompt).and_return("1") @knife.ask_which_version end it "should set the version to the user's selection" do expect(@knife).to receive(:ask_question).and_return("1") @knife.ask_which_version expect(@knife.version).to eq("1.0.0") end it "should print an error and exit if a version wasn't specified" do expect(@knife).to receive(:ask_question).and_return("") expect(@knife.ui).to receive(:error).with(/is not a valid value/i) expect { @knife.ask_which_version }.to raise_error(SystemExit) end it "should print an error if an invalid choice was selected" do expect(@knife).to receive(:ask_question).and_return("100") expect(@knife.ui).to receive(:error).with(/'100' is not a valid value/i) expect { @knife.ask_which_version }.to raise_error(SystemExit) end end end chef-12.14.60/spec/unit/knife/cookbook_list_spec.rb000066400000000000000000000060111276456504500220730ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::CookbookList do before do @knife = Chef::Knife::CookbookList.new @rest_mock = double("rest") allow(@knife).to receive(:rest).and_return(@rest_mock) @cookbook_names = %w{apache2 mysql} @base_url = "https://server.example.com/cookbooks" @cookbook_data = {} @cookbook_names.each do |item| @cookbook_data[item] = { "url" => "#{@base_url}/#{item}", "versions" => [{ "version" => "1.0.1", "url" => "#{@base_url}/#{item}/1.0.1" }] } end @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do it "should display the latest version of the cookbooks" do expect(@rest_mock).to receive(:get).with("/cookbooks?num_versions=1"). and_return(@cookbook_data) @knife.run @cookbook_names.each do |item| expect(@stdout.string).to match /#{item}\s+1\.0\.1/ end end it "should query cookbooks for the configured environment" do @knife.config[:environment] = "production" expect(@rest_mock).to receive(:get). with("/environments/production/cookbooks?num_versions=1"). and_return(@cookbook_data) @knife.run end describe "with -w or --with-uri" do it "should display the cookbook uris" do @knife.config[:with_uri] = true allow(@rest_mock).to receive(:get).and_return(@cookbook_data) @knife.run @cookbook_names.each do |item| pattern = /#{Regexp.escape(@cookbook_data[item]['versions'].first['url'])}/ expect(@stdout.string).to match pattern end end end describe "with -a or --all" do before do @cookbook_names.each do |item| @cookbook_data[item]["versions"] << { "version" => "1.0.0", "url" => "#{@base_url}/#{item}/1.0.0" } end end it "should display all versions of the cookbooks" do @knife.config[:all_versions] = true expect(@rest_mock).to receive(:get).with("/cookbooks?num_versions=all"). and_return(@cookbook_data) @knife.run @cookbook_names.each do |item| expect(@stdout.string).to match /#{item}\s+1\.0\.1\s+1\.0\.0/ end end end end end chef-12.14.60/spec/unit/knife/cookbook_metadata_from_file_spec.rb000066400000000000000000000041271276456504500247300ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Matthew Kent () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::CookbookMetadataFromFile do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @src = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.rb")) @tgt = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.json")) @knife = Chef::Knife::CookbookMetadataFromFile.new @knife.name_args = [ @src ] allow(@knife).to receive(:to_json_pretty).and_return(true) @md = Chef::Cookbook::Metadata.new allow(Chef::Cookbook::Metadata).to receive(:new).and_return(@md) allow($stdout).to receive(:write) end after do if File.exists?(@tgt) File.unlink(@tgt) end end describe "run" do it "should determine cookbook name from path" do expect(@md).to receive(:name).with(no_args) expect(@md).to receive(:name).with("quick_start") @knife.run end it "should load the metadata source" do expect(@md).to receive(:from_file).with(@src) @knife.run end it "should write out the metadata to the correct location" do expect(File).to receive(:open).with(@tgt, "w") @knife.run end it "should generate json from the metadata" do expect(Chef::JSONCompat).to receive(:to_json_pretty).with(@md) @knife.run end end end chef-12.14.60/spec/unit/knife/cookbook_metadata_spec.rb000066400000000000000000000170431276456504500227070ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2011-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::CookbookMetadata do before(:each) do @knife = Chef::Knife::CookbookMetadata.new @knife.name_args = ["foobar"] @cookbook_dir = Dir.mktmpdir @json_data = '{ "version": "1.0.0" }' @stdout = StringIO.new @stderr = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) allow(@knife.ui).to receive(:stderr).and_return(@stderr) end describe "run" do it "should print an error and exit if a cookbook name was not provided" do @knife.name_args = [] expect(@knife.ui).to receive(:error).with(/you must specify the cookbook.+use the --all/i) expect { @knife.run }.to raise_error(SystemExit) end it "should print an error and exit if an empty cookbook name was provided" do @knife.name_args = [""] expect(@knife.ui).to receive(:error).with(/you must specify the cookbook.+use the --all/i) expect { @knife.run }.to raise_error(SystemExit) end it "should generate the metadata for the cookbook" do expect(@knife).to receive(:generate_metadata).with("foobar") @knife.run end describe "with -a or --all" do before(:each) do @knife.config[:all] = true @foo = Chef::CookbookVersion.new("foo", "/tmp/blah") @foo.version = "1.0.0" @bar = Chef::CookbookVersion.new("bar", "/tmp/blah") @bar.version = "2.0.0" @cookbook_loader = { "foo" => @foo, "bar" => @bar, } expect(@cookbook_loader).to receive(:load_cookbooks).and_return(@cookbook_loader) expect(@knife).to receive(:generate_metadata).with("foo") expect(@knife).to receive(:generate_metadata).with("bar") end it "should generate the metadata for each cookbook" do Chef::Config[:cookbook_path] = @cookbook_dir expect(Chef::CookbookLoader).to receive(:new).with(@cookbook_dir).and_return(@cookbook_loader) @knife.run end describe "and with -o or --cookbook-path" do it "should look in the provided path and generate cookbook metadata" do @knife.config[:cookbook_path] = "/opt/chef/cookbooks" expect(Chef::CookbookLoader).to receive(:new).with("/opt/chef/cookbooks").and_return(@cookbook_loader) @knife.run end end end end describe "generate_metadata" do before(:each) do @knife.config[:cookbook_path] = @cookbook_dir allow(File).to receive(:expand_path).with("#{@cookbook_dir}/foobar/metadata.rb"). and_return("#{@cookbook_dir}/foobar/metadata.rb") end it "should generate the metadata from metadata.rb if it exists" do expect(File).to receive(:exists?).with("#{@cookbook_dir}/foobar/metadata.rb"). and_return(true) expect(@knife).to receive(:generate_metadata_from_file).with("foobar", "#{@cookbook_dir}/foobar/metadata.rb") @knife.run end it "should validate the metadata json if metadata.rb does not exist" do expect(File).to receive(:exists?).with("#{@cookbook_dir}/foobar/metadata.rb"). and_return(false) expect(@knife).to receive(:validate_metadata_json).with(@cookbook_dir, "foobar") @knife.run end end describe "generate_metadata_from_file" do before(:each) do @metadata_mock = double("metadata") @json_file_mock = double("json_file") end it "should generate the metatdata json from metatdata.rb" do allow(Chef::Cookbook::Metadata).to receive(:new).and_return(@metadata_mock) expect(@metadata_mock).to receive(:name).with("foobar") expect(@metadata_mock).to receive(:from_file).with("#{@cookbook_dir}/foobar/metadata.rb") expect(File).to receive(:open).with("#{@cookbook_dir}/foobar/metadata.json", "w"). and_yield(@json_file_mock) expect(@json_file_mock).to receive(:write).with(@json_data) expect(Chef::JSONCompat).to receive(:to_json_pretty).with(@metadata_mock). and_return(@json_data) @knife.generate_metadata_from_file("foobar", "#{@cookbook_dir}/foobar/metadata.rb") expect(@stderr.string).to match /generating metadata for foobar from #{@cookbook_dir}\/foobar\/metadata\.rb/im end { Chef::Exceptions::ObsoleteDependencySyntax => "obsolote dependency", Chef::Exceptions::InvalidVersionConstraint => "invalid version constraint", }.each_pair do |klass, description| it "should print an error and exit when an #{description} syntax exception is encountered" do exception = klass.new("#{description} blah") allow(Chef::Cookbook::Metadata).to receive(:new).and_raise(exception) expect do @knife.generate_metadata_from_file("foobar", "#{@cookbook_dir}/foobar/metadata.rb") end.to raise_error(SystemExit) expect(@stderr.string).to match /error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im expect(@stderr.string).to match /in #{@cookbook_dir}\/foobar\/metadata\.rb/im expect(@stderr.string).to match /#{description} blah/im end end end describe "validate_metadata_json" do it "should validate the metadata json" do expect(File).to receive(:exist?).with("#{@cookbook_dir}/foobar/metadata.json"). and_return(true) expect(IO).to receive(:read).with("#{@cookbook_dir}/foobar/metadata.json"). and_return(@json_data) expect(Chef::Cookbook::Metadata).to receive(:validate_json).with(@json_data) @knife.validate_metadata_json(@cookbook_dir, "foobar") end it "should not try to validate the metadata json if the file does not exist" do expect(File).to receive(:exist?).with("#{@cookbook_dir}/foobar/metadata.json"). and_return(false) expect(IO).not_to receive(:read) expect(Chef::Cookbook::Metadata).not_to receive(:validate_json) @knife.validate_metadata_json(@cookbook_dir, "foobar") end { Chef::Exceptions::ObsoleteDependencySyntax => "obsolote dependency", Chef::Exceptions::InvalidVersionConstraint => "invalid version constraint", }.each_pair do |klass, description| it "should print an error and exit when an #{description} syntax exception is encountered" do expect(File).to receive(:exist?).with("#{@cookbook_dir}/foobar/metadata.json"). and_return(true) expect(IO).to receive(:read).with("#{@cookbook_dir}/foobar/metadata.json"). and_return(@json_data) exception = klass.new("#{description} blah") allow(Chef::Cookbook::Metadata).to receive(:validate_json).and_raise(exception) expect do @knife.validate_metadata_json(@cookbook_dir, "foobar") end.to raise_error(SystemExit) expect(@stderr.string).to match /error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im expect(@stderr.string).to match /in #{@cookbook_dir}\/foobar\/metadata\.json/im expect(@stderr.string).to match /#{description} blah/im end end end end chef-12.14.60/spec/unit/knife/cookbook_show_spec.rb000066400000000000000000000174331276456504500221120ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2008-2016, Chef Software Inc. # License:: Apache License, eersion 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. # # rename to cookbook not coookbook require "spec_helper" describe Chef::Knife::CookbookShow do before do Chef::Config[:node_name] = "webmonkey.example.com" allow(knife).to receive(:rest).and_return(rest) allow(knife).to receive(:pretty_print).and_return(true) allow(knife).to receive(:output).and_return(true) allow(Chef::CookbookVersion).to receive(:load).and_return(cb) end let (:knife) do knife = Chef::Knife::CookbookShow.new knife.config = {} knife.name_args = [ "cookbook_name" ] knife end let (:cb) do cb = Chef::CookbookVersion.new("cookbook_name") cb.manifest = manifest cb end let (:rest) { double(Chef::ServerAPI) } let (:content) { "Example recipe text" } let (:manifest) do { "recipes" => [ { :name => "default.rb", :path => "recipes/default.rb", :checksum => "1234", :url => "http://example.org/files/default.rb", }, ], } end describe "run" do describe "with 0 arguments: help" do it "should should print usage and exit when given no arguments" do knife.name_args = [] expect(knife).to receive(:show_usage) expect(knife.ui).to receive(:fatal) expect { knife.run }.to raise_error(SystemExit) end end describe "with 1 argument: versions" do let (:response) do { "cookbook_name" => { "url" => "http://url/cookbooks/cookbook_name", "versions" => [ { "version" => "0.10.0", "url" => "http://url/cookbooks/cookbook_name/0.10.0" }, { "version" => "0.9.0", "url" => "http://url/cookbookx/cookbook_name/0.9.0" }, { "version" => "0.8.0", "url" => "http://url/cookbooks/cookbook_name/0.8.0" }, ], }, } end it "should show the raw cookbook data" do expect(rest).to receive(:get).with("cookbooks/cookbook_name").and_return(response) expect(knife).to receive(:format_cookbook_list_for_display).with(response) knife.run end it "should respect the user-supplied environment" do knife.config[:environment] = "foo" expect(rest).to receive(:get).with("environments/foo/cookbooks/cookbook_name").and_return(response) expect(knife).to receive(:format_cookbook_list_for_display).with(response) knife.run end end describe "with 2 arguments: name and version" do before do knife.name_args << "0.1.0" end it "should show the specific part of a cookbook" do expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) expect(knife).to receive(:output).with(cb) knife.run end end describe "with 3 arguments: name, version, and segment" do before(:each) do knife.name_args = [ "cookbook_name", "0.1.0", "recipes" ] end it "should print the json of the part" do expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) expect(knife).to receive(:output).with(cb.manifest["recipes"]) knife.run end end describe "with 4 arguments: name, version, segment and filename" do before(:each) do knife.name_args = [ "cookbook_name", "0.1.0", "recipes", "default.rb" ] end it "should print the raw result of the request (likely a file!)" do expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) expect(rest).to receive(:streaming_request).with("http://example.org/files/default.rb").and_return(StringIO.new(content)) expect(knife).to receive(:pretty_print).with(content) knife.run end end describe "with 4 arguments: name, version, segment and filename -- with specificity" do before(:each) do knife.name_args = [ "cookbook_name", "0.1.0", "files", "afile.rb" ] cb.manifest = { "files" => [ { :name => "afile.rb", :path => "files/host-examplehost.example.org/afile.rb", :checksum => "1111", :specificity => "host-examplehost.example.org", :url => "http://example.org/files/1111", }, { :name => "afile.rb", :path => "files/ubuntu-9.10/afile.rb", :checksum => "2222", :specificity => "ubuntu-9.10", :url => "http://example.org/files/2222", }, { :name => "afile.rb", :path => "files/ubuntu/afile.rb", :checksum => "3333", :specificity => "ubuntu", :url => "http://example.org/files/3333", }, { :name => "afile.rb", :path => "files/default/afile.rb", :checksum => "4444", :specificity => "default", :url => "http://example.org/files/4444", }, ], } end describe "with --fqdn" do it "should pass the fqdn" do knife.config[:platform] = "example_platform" knife.config[:platform_version] = "1.0" knife.config[:fqdn] = "examplehost.example.org" expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) expect(rest).to receive(:streaming_request).with("http://example.org/files/1111").and_return(StringIO.new(content)) expect(knife).to receive(:pretty_print).with(content) knife.run end end describe "and --platform" do it "should pass the platform" do knife.config[:platform] = "ubuntu" knife.config[:platform_version] = "1.0" knife.config[:fqdn] = "differenthost.example.org" expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) expect(rest).to receive(:streaming_request).with("http://example.org/files/3333").and_return(StringIO.new(content)) expect(knife).to receive(:pretty_print).with(content) knife.run end end describe "and --platform-version" do it "should pass the platform" do knife.config[:platform] = "ubuntu" knife.config[:platform_version] = "9.10" knife.config[:fqdn] = "differenthost.example.org" expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) expect(rest).to receive(:streaming_request).with("http://example.org/files/2222").and_return(StringIO.new(content)) expect(knife).to receive(:pretty_print).with(content) knife.run end end describe "with none of the arguments, it should use the default" do it "should pass them all" do expect(Chef::CookbookVersion).to receive(:load).with("cookbook_name", "0.1.0").and_return(cb) expect(rest).to receive(:streaming_request).with("http://example.org/files/4444").and_return(StringIO.new(content)) expect(knife).to receive(:pretty_print).with(content) knife.run end end end end end chef-12.14.60/spec/unit/knife/cookbook_site_download_spec.rb000066400000000000000000000132701276456504500237600ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2012-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper") describe Chef::Knife::CookbookSiteDownload do describe "run" do before do @knife = Chef::Knife::CookbookSiteDownload.new @knife.name_args = ["apache2"] @noauth_rest = double("no auth rest") @stderr = StringIO.new @cookbook_api_url = "https://supermarket.chef.io/api/v1/cookbooks" @version = "1.0.2" @version_us = @version.tr ".", "_" @current_data = { "deprecated" => false, "latest_version" => "#{@cookbook_api_url}/apache2/versions/#{@version_us}", "replacement" => "other_apache2" } allow(@knife.ui).to receive(:stderr).and_return(@stderr) allow(@knife).to receive(:noauth_rest).and_return(@noauth_rest) expect(@noauth_rest).to receive(:get). with("#{@cookbook_api_url}/apache2"). and_return(@current_data) @knife.configure_chef end context "when the cookbook is deprecated and not forced" do before do @current_data["deprecated"] = true end it "should warn with info about the replacement" do expect(@knife.ui).to receive(:warn). with(/.+deprecated.+replaced by other_apache2.+/i) expect(@knife.ui).to receive(:warn). with(/use --force.+download.+/i) @knife.run end end context "when" do before do @cookbook_data = { "version" => @version, "file" => "http://example.com/apache2_#{@version_us}.tgz" } @temp_file = double( :path => "/tmp/apache2_#{@version_us}.tgz" ) @file = File.join(Dir.pwd, "apache2-#{@version}.tar.gz") end context "downloading the latest version" do before do expect(@noauth_rest).to receive(:get). with(@current_data["latest_version"]). and_return(@cookbook_data) expect(@noauth_rest).to receive(:streaming_request). with(@cookbook_data["file"]). and_return(@temp_file) end context "and it is deprecated and with --force" do before do @current_data["deprecated"] = true @knife.config[:force] = true end it "should download the latest version" do expect(@knife.ui).to receive(:warn). with(/.+deprecated.+replaced by other_apache2.+/i) expect(FileUtils).to receive(:cp).with(@temp_file.path, @file) @knife.run expect(@stderr.string).to match /downloading apache2.+version.+#{Regexp.escape(@version)}/i expect(@stderr.string).to match /cookbook save.+#{Regexp.escape(@file)}/i end end it "should download the latest version" do expect(FileUtils).to receive(:cp).with(@temp_file.path, @file) @knife.run expect(@stderr.string).to match /downloading apache2.+version.+#{Regexp.escape(@version)}/i expect(@stderr.string).to match /cookbook save.+#{Regexp.escape(@file)}/i end context "with -f or --file" do before do @file = "/opt/chef/cookbooks/apache2.tar.gz" @knife.config[:file] = @file expect(FileUtils).to receive(:cp).with(@temp_file.path, @file) end it "should download the cookbook to the desired file" do @knife.run expect(@stderr.string).to match /downloading apache2.+version.+#{Regexp.escape(@version)}/i expect(@stderr.string).to match /cookbook save.+#{Regexp.escape(@file)}/i end end it "should provide an accessor to the version" do allow(FileUtils).to receive(:cp).and_return(true) expect(@knife.version).to eq(@version) @knife.run end end context "downloading a cookbook of a specific version" do before do @version = "1.0.1" @version_us = @version.tr ".", "_" @cookbook_data = { "version" => @version, "file" => "http://example.com/apache2_#{@version_us}.tgz" } @temp_file = double(:path => "/tmp/apache2_#{@version_us}.tgz") @file = File.join(Dir.pwd, "apache2-#{@version}.tar.gz") @knife.name_args << @version end it "should download the desired version" do expect(@noauth_rest).to receive(:get). with("#{@cookbook_api_url}/apache2/versions/#{@version_us}"). and_return(@cookbook_data) expect(@noauth_rest).to receive(:streaming_request). with(@cookbook_data["file"]). and_return(@temp_file) expect(FileUtils).to receive(:cp).with(@temp_file.path, @file) @knife.run expect(@stderr.string).to match /downloading apache2.+version.+#{Regexp.escape(@version)}/i expect(@stderr.string).to match /cookbook save.+#{Regexp.escape(@file)}/i end end end end end chef-12.14.60/spec/unit/knife/cookbook_site_install_spec.rb000066400000000000000000000204721276456504500236210ustar00rootroot00000000000000# # Author:: Steven Danna () # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) describe Chef::Knife::CookbookSiteInstall do let(:knife) { Chef::Knife::CookbookSiteInstall.new } let(:stdout) { StringIO.new } let(:stderr) { StringIO.new } let(:downloader) { Hash.new } let(:archive) { double(Mixlib::Archive, extract: true) } let(:repo) do double(:sanity_check => true, :reset_to_default_state => true, :prepare_to_import => true, :finalize_updates_to => true, :merge_updates_from => true) end let(:install_path) do if Chef::Platform.windows? "C:/tmp/chef" else "/var/tmp/chef" end end before(:each) do require "chef/knife/core/cookbook_scm_repo" allow(knife.ui).to receive(:stdout).and_return(stdout) knife.config = {} knife.config[:cookbook_path] = [ install_path ] allow(knife).to receive(:stderr).and_return(stderr) allow(knife).to receive(:stdout).and_return(stdout) # Assume all external commands would have succeed. :( allow(File).to receive(:unlink) allow(File).to receive(:rmtree) allow(knife).to receive(:shell_out!).and_return(true) allow(Mixlib::Archive).to receive(:new).and_return(archive) # CookbookSiteDownload Stup allow(knife).to receive(:download_cookbook_to).and_return(downloader) allow(downloader).to receive(:version) do if knife.name_args.size == 2 knife.name_args[1] else "0.3.0" end end # Stubs for CookbookSCMRepo allow(Chef::Knife::CookbookSCMRepo).to receive(:new).and_return(repo) end describe "run" do it "raises an error if a cookbook name is not provided" do knife.name_args = [] expect(knife.ui).to receive(:error).with("Please specify a cookbook to download and install.") expect { knife.run }.to raise_error(SystemExit) end it "raises an error if more than two arguments are given" do knife.name_args = %w{foo bar baz} expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") expect { knife.run }.to raise_error(SystemExit) end it "raises an error if the second argument is not a version" do knife.name_args = ["getting-started", "1pass"] expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") expect { knife.run }.to raise_error(SystemExit) end it "raises an error if the second argument is a four-digit version" do knife.name_args = ["getting-started", "0.0.0.1"] expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") expect { knife.run }.to raise_error(SystemExit) end it "raises an error if the second argument is a one-digit version" do knife.name_args = ["getting-started", "1"] expect(knife.ui).to receive(:error).with("Installing multiple cookbooks at once is not supported.") expect { knife.run }.to raise_error(SystemExit) end it "installs the specified version if second argument is a three-digit version" do knife.name_args = ["getting-started", "0.1.0"] knife.config[:no_deps] = true upstream_file = File.join(install_path, "getting-started.tar.gz") expect(knife).to receive(:download_cookbook_to).with(upstream_file) expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1.0") expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1.0") knife.run end it "installs the specified version if second argument is a two-digit version" do knife.name_args = ["getting-started", "0.1"] knife.config[:no_deps] = true upstream_file = File.join(install_path, "getting-started.tar.gz") expect(knife).to receive(:download_cookbook_to).with(upstream_file) expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.1") expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) expect(repo).to receive(:merge_updates_from).with("getting-started", "0.1") knife.run end it "installs the latest version if only a cookbook name is given" do knife.name_args = ["getting-started"] knife.config[:no_deps] = true upstream_file = File.join(install_path, "getting-started.tar.gz") expect(knife).to receive(:download_cookbook_to).with(upstream_file) expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0") expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0") knife.run end it "does not create/reset git branches if use_current_branch is set" do knife.name_args = ["getting-started"] knife.config[:use_current_branch] = true knife.config[:no_deps] = true upstream_file = File.join(install_path, "getting-started.tar.gz") expect(repo).not_to receive(:prepare_to_import) expect(repo).not_to receive(:reset_to_default_state) knife.run end it "does not raise an error if cookbook_path is a string" do knife.config[:cookbook_path] = install_path knife.config[:no_deps] = true knife.name_args = ["getting-started"] upstream_file = File.join(install_path, "getting-started.tar.gz") expect(knife).to receive(:download_cookbook_to).with(upstream_file) expect(knife).to receive(:extract_cookbook).with(upstream_file, "0.3.0") expect(knife).to receive(:clear_existing_files).with(File.join(install_path, "getting-started")) expect(repo).to receive(:merge_updates_from).with("getting-started", "0.3.0") expect { knife.run }.not_to raise_error end end # end of run let(:metadata) { Chef::Cookbook::Metadata.new } let(:rb_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.rb") } let(:json_metadata_path) { File.join(install_path, "post-punk-kitchen", "metadata.json") } describe "preferred_metadata" do before do allow(Chef::Cookbook::Metadata).to receive(:new).and_return(metadata) allow(File).to receive(:exist?).and_return(false) knife.instance_variable_set(:@cookbook_name, "post-punk-kitchen") knife.instance_variable_set(:@install_path, install_path) end it "returns a populated Metadata object if metadata.rb exists" do allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true) expect(metadata).to receive(:from_file).with(rb_metadata_path) knife.preferred_metadata end it "returns a populated Metadata object if metadata.json exists" do allow(File).to receive(:exist?).with(json_metadata_path).and_return(true) #expect(IO).to receive(:read).with(json_metadata_path) allow(IO).to receive(:read) expect(metadata).to receive(:from_json) knife.preferred_metadata end it "prefers metadata.rb over metadata.json" do allow(File).to receive(:exist?).with(rb_metadata_path).and_return(true) allow(File).to receive(:exist?).with(json_metadata_path).and_return(true) allow(IO).to receive(:read) expect(metadata).to receive(:from_file).with(rb_metadata_path) expect(metadata).not_to receive(:from_json) knife.preferred_metadata end it "rasies an error if it finds no metadata file" do expect { knife.preferred_metadata }.to raise_error { |error| expect(error).to be_a(Chef::Exceptions::MetadataNotFound) expect(error.cookbook_name).to eq("post-punk-kitchen") expect(error.install_path).to eq(install_path) } end end end chef-12.14.60/spec/unit/knife/cookbook_site_share_spec.rb000066400000000000000000000205321276456504500232520ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-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/cookbook_uploader" require "chef/cookbook_site_streaming_uploader" describe Chef::Knife::CookbookSiteShare do before(:each) do @knife = Chef::Knife::CookbookSiteShare.new # Merge default settings in. @knife.merge_configs @knife.name_args = %w{cookbook_name AwesomeSausage} @cookbook = Chef::CookbookVersion.new("cookbook_name") @cookbook_loader = double("Chef::CookbookLoader") allow(@cookbook_loader).to receive(:cookbook_exists?).and_return(true) allow(@cookbook_loader).to receive(:[]).and_return(@cookbook) allow(Chef::CookbookLoader).to receive(:new).and_return(@cookbook_loader) @noauth_rest = double(Chef::ServerAPI) allow(@knife).to receive(:noauth_rest).and_return(@noauth_rest) @cookbook_uploader = Chef::CookbookUploader.new("herpderp", :rest => "norest") allow(Chef::CookbookUploader).to receive(:new).and_return(@cookbook_uploader) allow(@cookbook_uploader).to receive(:validate_cookbooks).and_return(true) allow(Chef::CookbookSiteStreamingUploader).to receive(:create_build_dir).and_return(Dir.mktmpdir) allow(@knife).to receive(:shell_out!).and_return(true) @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do before(:each) do allow(@knife).to receive(:do_upload).and_return(true) @category_response = { "name" => "cookbook_name", "category" => "Testing Category", } @bad_category_response = { "error_code" => "NOT_FOUND", "error_messages" => [ "Resource does not exist.", ], } end it "should set true to config[:dry_run] as default" do expect(@knife.config[:dry_run]).to be_falsey end it "should should print usage and exit when given no arguments" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end it "should not fail when given only 1 argument and can determine category" do @knife.name_args = ["cookbook_name"] expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name").and_return(@category_response) expect(@knife).to receive(:do_upload) @knife.run end it "should use a default category when given only 1 argument and cannot determine category" do @knife.name_args = ["cookbook_name"] expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Net::HTTPServerException.new("404 Not Found", OpenStruct.new(code: "404")) } expect(@knife).to receive(:do_upload) expect { @knife.run }.to_not raise_error end it "should print error and exit when given only 1 argument and Chef::ServerAPI throws an exception" do @knife.name_args = ["cookbook_name"] expect(@noauth_rest).to receive(:get).with("https://supermarket.chef.io/api/v1/cookbooks/cookbook_name") { raise Errno::ECONNREFUSED, "Connection refused" } expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end it "should check if the cookbook exists" do expect(@cookbook_loader).to receive(:cookbook_exists?) @knife.run end it "should exit and log to error if the cookbook doesn't exist" do allow(@cookbook_loader).to receive(:cookbook_exists?).and_return(false) expect(@knife.ui).to receive(:error) expect { @knife.run }.to raise_error(SystemExit) end if File.exists?("/usr/bin/gnutar") || File.exists?("/bin/gnutar") it "should use gnutar to make a tarball of the cookbook" do expect(@knife).to receive(:shell_out!) do |args| expect(args.to_s).to match(/gnutar -czf/) end @knife.run end else it "should make a tarball of the cookbook" do expect(@knife).to receive(:shell_out!) do |args| expect(args.to_s).to match(/tar -czf/) end @knife.run end end it "should exit and log to error when the tarball creation fails" do allow(@knife).to receive(:shell_out!).and_raise(Chef::Exceptions::Exec) expect(@knife.ui).to receive(:error) expect { @knife.run }.to raise_error(SystemExit) end it "should upload the cookbook and clean up the tarball" do expect(@knife).to receive(:do_upload) expect(FileUtils).to receive(:rm_rf) @knife.run end context "when the --dry-run flag is specified" do before do allow(Chef::CookbookSiteStreamingUploader).to receive(:create_build_dir).and_return("/var/tmp/dummy") @knife.config = { :dry_run => true } allow(@knife).to receive_message_chain(:shell_out!, :stdout).and_return("file") end it "should list files in the tarball" do allow(@knife).to receive(:tar_cmd).and_return("footar") expect(@knife).to receive(:shell_out!).with("footar -czf #{@cookbook.name}.tgz #{@cookbook.name}", { :cwd => "/var/tmp/dummy" }) expect(@knife).to receive(:shell_out!).with("footar -tzf #{@cookbook.name}.tgz", { :cwd => "/var/tmp/dummy" }) @knife.run end it "does not upload the cookbook" do allow(@knife).to receive(:shell_out!).and_return(true) expect(@knife).not_to receive(:do_upload) @knife.run end end end describe "do_upload" do before(:each) do @upload_response = double("Net::HTTPResponse") allow(Chef::CookbookSiteStreamingUploader).to receive(:post).and_return(@upload_response) @stdout = StringIO.new @stderr = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) allow(@knife.ui).to receive(:stderr).and_return(@stderr) allow(File).to receive(:open).and_return(true) end it 'should post the cookbook to "https://supermarket.chef.io"' do response_text = Chef::JSONCompat.to_json({ :uri => "https://supermarket.chef.io/cookbooks/cookbook_name" }) allow(@upload_response).to receive(:body).and_return(response_text) allow(@upload_response).to receive(:code).and_return(201) expect(Chef::CookbookSiteStreamingUploader).to receive(:post).with(/supermarket\.chef\.io/, anything(), anything(), anything()) @knife.run end it "should alert the user when a version already exists" do response_text = Chef::JSONCompat.to_json({ :error_messages => ["Version already exists"] }) allow(@upload_response).to receive(:body).and_return(response_text) allow(@upload_response).to receive(:code).and_return(409) expect { @knife.run }.to raise_error(SystemExit) expect(@stderr.string).to match(/ERROR(.+)cookbook already exists/) end it "should pass any errors on to the user" do response_text = Chef::JSONCompat.to_json({ :error_messages => ["You're holding it wrong"] }) allow(@upload_response).to receive(:body).and_return(response_text) allow(@upload_response).to receive(:code).and_return(403) expect { @knife.run }.to raise_error(SystemExit) expect(@stderr.string).to match("ERROR(.*)You're holding it wrong") end it "should print the body if no errors are exposed on failure" do response_text = Chef::JSONCompat.to_json({ :system_error => "Your call was dropped", :reason => "There's a map for that" }) allow(@upload_response).to receive(:body).and_return(response_text) allow(@upload_response).to receive(:code).and_return(500) expect(@knife.ui).to receive(:error).with(/#{Regexp.escape(response_text)}/) #.ordered expect(@knife.ui).to receive(:error).with(/Unknown error/) #.ordered expect { @knife.run }.to raise_error(SystemExit) end end end chef-12.14.60/spec/unit/knife/cookbook_site_unshare_spec.rb000066400000000000000000000047721276456504500236250ustar00rootroot00000000000000# # Author:: Stephen Delano () # Author:: Tim Hinderliter () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::CookbookSiteUnshare do before(:each) do @knife = Chef::Knife::CookbookSiteUnshare.new @knife.name_args = ["cookbook_name"] allow(@knife).to receive(:confirm).and_return(true) @rest = double("Chef::ServerAPI") allow(@rest).to receive(:delete).and_return(true) allow(@knife).to receive(:rest).and_return(@rest) @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do describe "with no cookbook argument" do it "should print the usage and exit" do @knife.name_args = [] expect(@knife.ui).to receive(:fatal) expect(@knife).to receive(:show_usage) expect { @knife.run }.to raise_error(SystemExit) end end it "should confirm you want to unshare the cookbook" do expect(@knife).to receive(:confirm) @knife.run end it "should send a delete request to the cookbook site" do expect(@rest).to receive(:delete) @knife.run end it "should log an error and exit when forbidden" do exception = double('403 "Forbidden"', :code => "403") allow(@rest).to receive(:delete).and_raise(Net::HTTPServerException.new('403 "Forbidden"', exception)) expect(@knife.ui).to receive(:error) expect { @knife.run }.to raise_error(SystemExit) end it "should re-raise any non-forbidden errors on delete" do exception = double('500 "Application Error"', :code => "500") allow(@rest).to receive(:delete).and_raise(Net::HTTPServerException.new('500 "Application Error"', exception)) expect { @knife.run }.to raise_error(Net::HTTPServerException) end it "should log a success message" do expect(@knife.ui).to receive(:info) @knife.run end end end chef-12.14.60/spec/unit/knife/cookbook_test_spec.rb000066400000000000000000000061471276456504500221110ustar00rootroot00000000000000# # Author:: Stephen Delano ()$ # Author:: Matthew Kent () # Copyright:: Copyright 2010-2016, Chef Software Inc.$ # Copyright:: Copyright 2010-2016, Matthew Kent # 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" Chef::Knife::CookbookTest.load_deps describe Chef::Knife::CookbookTest do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::CookbookTest.new @knife.config[:cookbook_path] = File.join(CHEF_SPEC_DATA, "cookbooks") allow(@knife.cookbook_loader).to receive(:cookbook_exists?).and_return(true) @cookbooks = [] %w{tats central_market jimmy_johns pho}.each do |cookbook_name| @cookbooks << Chef::CookbookVersion.new(cookbook_name) end @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do it "should test the cookbook" do allow(@knife).to receive(:test_cookbook).and_return(true) @knife.name_args = ["italian"] expect(@knife).to receive(:test_cookbook).with("italian") @knife.run end it "should test multiple cookbooks when provided" do allow(@knife).to receive(:test_cookbook).and_return(true) @knife.name_args = %w{tats jimmy_johns} expect(@knife).to receive(:test_cookbook).with("tats") expect(@knife).to receive(:test_cookbook).with("jimmy_johns") expect(@knife).not_to receive(:test_cookbook).with("central_market") expect(@knife).not_to receive(:test_cookbook).with("pho") @knife.run end it "should test both ruby and templates" do @knife.name_args = ["example"] expect(@knife.config[:cookbook_path]).not_to be_empty Array(@knife.config[:cookbook_path]).reverse_each do |path| expect(@knife).to receive(:test_ruby).with(an_instance_of(Chef::Cookbook::SyntaxCheck)) expect(@knife).to receive(:test_templates).with(an_instance_of(Chef::Cookbook::SyntaxCheck)) end @knife.run end describe "with -a or --all" do it "should test all of the cookbooks" do allow(@knife).to receive(:test_cookbook).and_return(true) @knife.config[:all] = true @loader = {} allow(@loader).to receive(:load_cookbooks).and_return(@loader) @cookbooks.each do |cookbook| @loader[cookbook.name] = cookbook end allow(@knife).to receive(:cookbook_loader).and_return(@loader) @loader.each do |key, cookbook| expect(@knife).to receive(:test_cookbook).with(cookbook.name) end @knife.run end end end end chef-12.14.60/spec/unit/knife/cookbook_upload_spec.rb000066400000000000000000000312511276456504500224100ustar00rootroot00000000000000# # Author:: Matthew Kent () # Author:: Steven Danna () # 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) require "chef/cookbook_uploader" require "timeout" describe Chef::Knife::CookbookUpload do let(:cookbook) { Chef::CookbookVersion.new("test_cookbook", "/tmp/blah.txt") } let(:cookbooks_by_name) do { cookbook.name => cookbook } end let(:cookbook_loader) do cookbook_loader = cookbooks_by_name.dup allow(cookbook_loader).to receive(:merged_cookbooks).and_return([]) allow(cookbook_loader).to receive(:load_cookbooks_without_shadow_warning).and_return(cookbook_loader) cookbook_loader end let(:cookbook_uploader) { double(:upload_cookbooks => nil) } let(:output) { StringIO.new } let(:name_args) { ["test_cookbook"] } let(:knife) do k = Chef::Knife::CookbookUpload.new k.name_args = name_args allow(k.ui).to receive(:stdout).and_return(output) allow(k.ui).to receive(:stderr).and_return(output) k end before(:each) do allow(Chef::CookbookLoader).to receive(:new).and_return(cookbook_loader) end describe "with --concurrency" do it "should upload cookbooks with predefined concurrency" do allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({}) knife.config[:concurrency] = 3 test_cookbook = Chef::CookbookVersion.new("test_cookbook", "/tmp/blah") allow(cookbook_loader).to receive(:each).and_yield("test_cookbook", test_cookbook) allow(cookbook_loader).to receive(:cookbook_names).and_return(["test_cookbook"]) expect(Chef::CookbookUploader).to receive(:new). with( kind_of(Array), { :force => nil, :concurrency => 3 }). and_return(double("Chef::CookbookUploader", :upload_cookbooks => true)) knife.run end end describe "run" do before(:each) do allow(Chef::CookbookUploader).to receive_messages(:new => cookbook_uploader) allow(Chef::CookbookVersion).to receive(:list_all_versions).and_return({}) end it "should print usage and exit when a cookbook name is not provided" do knife.name_args = [] expect(knife).to receive(:show_usage) expect(knife.ui).to receive(:fatal) expect { knife.run }.to raise_error(SystemExit) end describe "when specifying a cookbook name" do it "should upload the cookbook" do expect(knife).to receive(:upload).once knife.run end it "should report on success" do expect(knife).to receive(:upload).once expect(knife.ui).to receive(:info).with(/Uploaded 1 cookbook/) knife.run end end describe "when specifying the same cookbook name twice" do it "should upload the cookbook only once" do knife.name_args = %w{test_cookbook test_cookbook} expect(knife).to receive(:upload).once knife.run end end context "when uploading a cookbook that uses deprecated overlays" do before do allow(cookbook_loader).to receive(:merged_cookbooks).and_return(["test_cookbook"]) allow(cookbook_loader).to receive(:merged_cookbook_paths). and_return({ "test_cookbook" => %w{/path/one/test_cookbook /path/two/test_cookbook} }) end it "emits a warning" do knife.run expected_message = <<-E WARNING: The cookbooks: test_cookbook exist in multiple places in your cookbook_path. A composite version of these cookbooks has been compiled for uploading. IMPORTANT: In a future version of Chef, this behavior will be removed and you will no longer be able to have the same version of a cookbook in multiple places in your cookbook_path. WARNING: The affected cookbooks are located: test_cookbook: /path/one/test_cookbook /path/two/test_cookbook E expect(output.string).to include(expected_message) end end describe "when specifying a cookbook name among many" do let(:name_args) { ["test_cookbook1"] } let(:cookbooks_by_name) do { "test_cookbook1" => Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah"), "test_cookbook2" => Chef::CookbookVersion.new("test_cookbook2", "/tmp/blah"), "test_cookbook3" => Chef::CookbookVersion.new("test_cookbook3", "/tmp/blah"), } end it "should read only one cookbook" do expect(cookbook_loader).to receive(:[]).once.with("test_cookbook1").and_call_original knife.run end it "should not read all cookbooks" do expect(cookbook_loader).not_to receive(:load_cookbooks) expect(cookbook_loader).not_to receive(:load_cookbooks_without_shadow_warning) knife.run end it "should upload only one cookbook" do expect(knife).to receive(:upload).exactly(1).times knife.run end end # This is testing too much. We should break it up. describe "when specifying a cookbook name with dependencies" do let(:name_args) { ["test_cookbook2"] } let(:cookbooks_by_name) do { "test_cookbook1" => test_cookbook1, "test_cookbook2" => test_cookbook2, "test_cookbook3" => test_cookbook3 } end let(:test_cookbook1) { Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah") } let(:test_cookbook2) do c = Chef::CookbookVersion.new("test_cookbook2") c.metadata.depends("test_cookbook3") c end let(:test_cookbook3) do c = Chef::CookbookVersion.new("test_cookbook3") c.metadata.depends("test_cookbook1") c.metadata.depends("test_cookbook2") c end it "should upload all dependencies once" do knife.config[:depends] = true allow(knife).to receive(:cookbook_names).and_return(%w{test_cookbook1 test_cookbook2 test_cookbook3}) expect(knife).to receive(:upload).exactly(3).times expect do Timeout.timeout(5) do knife.run end end.not_to raise_error end end describe "when specifying a cookbook name with missing dependencies" do let(:cookbook_dependency) { Chef::CookbookVersion.new("dependency", "/tmp/blah") } before(:each) do cookbook.metadata.depends("dependency") allow(cookbook_loader).to receive(:[]) do |ckbk| { "test_cookbook" => cookbook, "dependency" => cookbook_dependency }[ckbk] end allow(knife).to receive(:cookbook_names).and_return(%w{cookbook_dependency test_cookbook}) @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {}) end it "should exit and not upload the cookbook" do expect(cookbook_loader).to receive(:[]).once.with("test_cookbook") expect(cookbook_loader).not_to receive(:load_cookbooks) expect(cookbook_loader).not_to receive(:load_cookbooks_without_shadow_warning) expect(cookbook_uploader).not_to receive(:upload_cookbooks) expect { knife.run }.to raise_error(SystemExit) end it "should output a message for a single missing dependency" do expect { knife.run }.to raise_error(SystemExit) expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently") expect(@stderr.string).to include("being uploaded and cannot be found on the server.") expect(@stderr.string).to include("The missing cookbook(s) are: 'dependency' version '>= 0.0.0'") end it "should output a message for a multiple missing dependencies which are concatenated" do cookbook_dependency2 = Chef::CookbookVersion.new("dependency2") cookbook.metadata.depends("dependency2") allow(cookbook_loader).to receive(:[]) do |ckbk| { "test_cookbook" => cookbook, "dependency" => cookbook_dependency, "dependency2" => cookbook_dependency2 }[ckbk] end allow(knife).to receive(:cookbook_names).and_return(%w{dependency dependency2 test_cookbook}) expect { knife.run }.to raise_error(SystemExit) expect(@stderr.string).to include("Cookbook test_cookbook depends on cookbooks which are not currently") expect(@stderr.string).to include("being uploaded and cannot be found on the server.") expect(@stderr.string).to include("The missing cookbook(s) are:") expect(@stderr.string).to include("'dependency' version '>= 0.0.0'") expect(@stderr.string).to include("'dependency2' version '>= 0.0.0'") end end it "should freeze the version of the cookbooks if --freeze is specified" do knife.config[:freeze] = true expect(cookbook).to receive(:freeze_version).once knife.run end describe "with -a or --all" do before(:each) do knife.config[:all] = true end context "when cookbooks exist in the cookbook path" do before(:each) do @test_cookbook1 = Chef::CookbookVersion.new("test_cookbook1", "/tmp/blah") @test_cookbook2 = Chef::CookbookVersion.new("test_cookbook2", "/tmp/blah") allow(cookbook_loader).to receive(:each).and_yield("test_cookbook1", @test_cookbook1).and_yield("test_cookbook2", @test_cookbook2) allow(cookbook_loader).to receive(:cookbook_names).and_return(%w{test_cookbook1 test_cookbook2}) end it "should upload all cookbooks" do expect(knife).to receive(:upload).once knife.run end it "should report on success" do expect(knife).to receive(:upload).once expect(knife.ui).to receive(:info).with(/Uploaded all cookbooks/) knife.run end it "should update the version constraints for an environment" do allow(knife).to receive(:assert_environment_valid!).and_return(true) knife.config[:environment] = "production" expect(knife).to receive(:update_version_constraints).once knife.run end end context "when no cookbooks exist in the cookbook path" do before(:each) do allow(cookbook_loader).to receive(:each) end it "should not upload any cookbooks" do expect(knife).to_not receive(:upload) knife.run end context "when cookbook path is an array" do it "should warn users that no cookbooks exist" do knife.config[:cookbook_path] = ["/chef-repo/cookbooks", "/home/user/cookbooks"] expect(knife.ui).to receive(:warn).with( /Could not find any cookbooks in your cookbook path: #{knife.config[:cookbook_path].join(', ')}\. Use --cookbook-path to specify the desired path\./) knife.run end end context "when cookbook path is a string" do it "should warn users that no cookbooks exist" do knife.config[:cookbook_path] = "/chef-repo/cookbooks" expect(knife.ui).to receive(:warn).with( /Could not find any cookbooks in your cookbook path: #{knife.config[:cookbook_path]}\. Use --cookbook-path to specify the desired path\./) knife.run end end end end describe "when a frozen cookbook exists on the server" do it "should fail to replace it" do exception = Chef::Exceptions::CookbookFrozen.new expect(cookbook_uploader).to receive(:upload_cookbooks). and_raise(exception) allow(knife.ui).to receive(:error) expect(knife.ui).to receive(:error).with(exception) expect { knife.run }.to raise_error(SystemExit) end it "should not update the version constraints for an environment" do allow(knife).to receive(:assert_environment_valid!).and_return(true) knife.config[:environment] = "production" allow(knife).to receive(:upload).and_raise(Chef::Exceptions::CookbookFrozen) expect(knife.ui).to receive(:error).with(/Failed to upload 1 cookbook/) expect(knife.ui).to receive(:warn).with(/Not updating version constraints/) expect(knife).not_to receive(:update_version_constraints) expect { knife.run }.to raise_error(SystemExit) end end end # run end chef-12.14.60/spec/unit/knife/core/000077500000000000000000000000001276456504500166255ustar00rootroot00000000000000chef-12.14.60/spec/unit/knife/core/bootstrap_context_spec.rb000066400000000000000000000211151276456504500237450ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/knife/core/bootstrap_context" describe Chef::Knife::Core::BootstrapContext do before do # This is required because the chef-fips pipeline does # has a default value of true for fips Chef::Config[:fips] = false end let(:config) { { :foo => :bar, :color => true } } let(:run_list) { Chef::RunList.new("recipe[tmux]", "role[base]") } let(:chef_config) do { :validation_key => File.join(CHEF_SPEC_DATA, "ssl", "private_key.pem"), :chef_server_url => "http://chef.example.com:4444", :validation_client_name => "chef-validator-testing", } end let(:secret) { nil } subject(:bootstrap_context) { described_class.new(config, run_list, chef_config, secret) } it "initializes with Chef 11 parameters" do expect { described_class.new(config, run_list, chef_config) }.not_to raise_error end it "runs chef with the first-boot.json with no environment specified" do expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json" end describe "when in verbosity mode" do let(:config) { { :verbosity => 2, :color => true } } it "adds '-l debug' when verbosity is >= 2" do expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json -l debug" end end describe "when no color value has been set in config" do let(:config) { { :color => false } } it "adds '--no-color' when color is false" do expect(bootstrap_context.start_chef).to eq "chef-client -j /etc/chef/first-boot.json --no-color" end end it "reads the validation key" do expect(bootstrap_context.validation_key).to eq IO.read(File.join(CHEF_SPEC_DATA, "ssl", "private_key.pem")) end it "generates the config file data" do expected = <<-EXPECTED log_location STDOUT chef_server_url "http://chef.example.com:4444" validation_client_name "chef-validator-testing" # Using default node name (fqdn) EXPECTED expect(bootstrap_context.config_content).to eq expected end it "does not set a default log_level" do expect(bootstrap_context.config_content).not_to match(/log_level/) end describe "alternate chef-client path" do let(:chef_config) { { :chef_client_path => "/usr/local/bin/chef-client" } } it "runs chef-client from another path when specified" do expect(bootstrap_context.start_chef).to eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json" end end describe "validation key path that contains a ~" do let(:chef_config) { { :validation_key => "~/my.key" } } it "reads the validation key when it contains a ~" do expect(File).to receive(:exist?).with(File.expand_path("my.key", ENV["HOME"])).and_return(true) expect(IO).to receive(:read).with(File.expand_path("my.key", ENV["HOME"])) bootstrap_context.validation_key end end describe "when an explicit node name is given" do let(:config) { { :chef_node_name => "foobar.example.com" } } it "sets the node name in the client.rb" do expect(bootstrap_context.config_content).to match(/node_name "foobar\.example\.com"/) end end describe "when bootstrapping into a specific environment" do let(:config) { { :environment => "prodtastic", :color => true } } it "starts chef in the configured environment" do expect(bootstrap_context.start_chef).to eq("chef-client -j /etc/chef/first-boot.json -E prodtastic") end end describe "when tags are given" do let(:config) { { :tags => [ "unicorn" ] } } it "adds the attributes to first_boot" do expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ :run_list => run_list, :tags => ["unicorn"] })) end end describe "when JSON attributes are given" do let(:config) { { :first_boot_attributes => { :baz => :quux } } } it "adds the attributes to first_boot" do expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ :baz => :quux, :run_list => run_list })) end end describe "when JSON attributes are NOT given" do it "sets first_boot equal to run_list" do expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({ :run_list => run_list })) end end describe "when policy_name and policy_group are present in config" do let(:config) { { policy_name: "my_app_server", policy_group: "staging" } } it "includes them in the first_boot data and excludes run_list" do expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json(config)) end end describe "when an encrypted_data_bag_secret is provided" do let(:secret) { "supersekret" } it "reads the encrypted_data_bag_secret" do expect(bootstrap_context.encrypted_data_bag_secret).to eq "supersekret" end end describe "to support compatibility with existing templates" do it "sets the @config instance variable" do expect(bootstrap_context.instance_variable_get(:@config)).to eq config end it "sets the @run_list instance variable" do expect(bootstrap_context.instance_variable_get(:@run_list)).to eq run_list end end describe "when a bootstrap_version is specified" do let(:chef_config) do { :knife => { :bootstrap_version => "11.12.4" }, } end it "should send the full version to the installer" do expect(bootstrap_context.latest_current_chef_version_string).to eq("-v 11.12.4") end end describe "when a pre-release bootstrap_version is specified" do let(:chef_config) do { :knife => { :bootstrap_version => "11.12.4.rc.0" }, } end it "should send the full version to the installer and set the pre-release flag" do expect(bootstrap_context.latest_current_chef_version_string).to eq("-v 11.12.4.rc.0 -p") end end describe "when a bootstrap_version is not specified" do it "should send the latest current to the installer" do # Intentionally hard coded in order not to replicate the logic. expect(bootstrap_context.latest_current_chef_version_string).to eq("-v #{Chef::VERSION.to_i}") end end describe "ssl_verify_mode" do it "isn't set in the config_content by default" do expect(bootstrap_context.config_content).not_to include("ssl_verify_mode") end describe "when configured in config" do let(:chef_config) do { :knife => { :ssl_verify_mode => :verify_peer }, } end it "uses the config value" do expect(bootstrap_context.config_content).to include("ssl_verify_mode :verify_peer") end describe "when configured via CLI" do let(:config) { { :node_ssl_verify_mode => "none" } } it "uses CLI value" do expect(bootstrap_context.config_content).to include("ssl_verify_mode :verify_none") end end end end describe "verify_api_cert" do it "isn't set in the config_content by default" do expect(bootstrap_context.config_content).not_to include("verify_api_cert") end describe "when configured in config" do let(:chef_config) do { :knife => { :verify_api_cert => :false }, } end it "uses the config value" do expect(bootstrap_context.config_content).to include("verify_api_cert false") end describe "when configured via CLI" do let(:config) { { :node_verify_api_cert => true } } it "uses CLI value" do expect(bootstrap_context.config_content).to include("verify_api_cert true") end end end end describe "prerelease" do it "isn't set in the config_content by default" do expect(bootstrap_context.config_content).not_to include("prerelease") end describe "when configured via cli" do let(:config) { { :prerelease => true } } it "uses CLI value" do expect(bootstrap_context.latest_current_chef_version_string).to eq("-p") end end end end chef-12.14.60/spec/unit/knife/core/cookbook_scm_repo_spec.rb000066400000000000000000000172551276456504500236730ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/knife/core/cookbook_scm_repo" describe Chef::Knife::CookbookSCMRepo do before do @repo_path = File.join(CHEF_SPEC_DATA, "cookbooks") @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new @ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {}) @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, :default_branch => "master") @branch_list = Mixlib::ShellOut.new @branch_list.stdout.replace(<<-BRANCHES) chef-vendor-apache2 chef-vendor-build-essential chef-vendor-dynomite chef-vendor-ganglia chef-vendor-graphite chef-vendor-python chef-vendor-absent-new BRANCHES end it "has a path to the cookbook repo" do expect(@cookbook_repo.repo_path).to eq(@repo_path) end it "has a default branch" do expect(@cookbook_repo.default_branch).to eq("master") end describe "when sanity checking the repo" do it "exits when the directory does not exist" do expect(::File).to receive(:directory?).with(@repo_path).and_return(false) expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit) end describe "and the repo dir exists" do before do allow(::File).to receive(:directory?).with(@repo_path).and_return(true) end it "exits when there is no git repo" do allow(::File).to receive(:directory?).with(/.*\.git/).and_return(false) expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit) end describe "and the repo is a git repo" do before do allow(::File).to receive(:directory?).with(File.join(@repo_path, ".git")).and_return(true) end it "exits when the default branch doesn't exist" do @nobranches = Mixlib::ShellOut.new.tap { |s| s.stdout.replace "\n" } expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", :cwd => @repo_path).and_return(@nobranches) expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit) end describe "and the default branch exists" do before do @master_branch = Mixlib::ShellOut.new @master_branch.stdout.replace "* master\n" expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", :cwd => @repo_path).and_return(@master_branch) end it "exits when the git repo is dirty" do @dirty_status = Mixlib::ShellOut.new @dirty_status.stdout.replace(<<-DIRTY) M chef/lib/chef/knife/cookbook_site_vendor.rb DIRTY expect(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain", :cwd => @repo_path).and_return(@dirty_status) expect { @cookbook_repo.sanity_check }.to raise_error(SystemExit) end describe "and the repo is clean" do before do @clean_status = Mixlib::ShellOut.new.tap { |s| s.stdout.replace("\n") } allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain", :cwd => @repo_path).and_return(@clean_status) end it "passes the sanity check" do @cookbook_repo.sanity_check end end end end end end it "resets to default state by checking out the default branch" do expect(@cookbook_repo).to receive(:shell_out!).with("git checkout master", :cwd => @repo_path) @cookbook_repo.reset_to_default_state end it "determines if a the pristine copy branch exists" do expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", :cwd => @repo_path).and_return(@branch_list) expect(@cookbook_repo.branch_exists?("chef-vendor-apache2")).to be_truthy expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", :cwd => @repo_path).and_return(@branch_list) expect(@cookbook_repo.branch_exists?("chef-vendor-nginx")).to be_falsey end it "determines if a the branch not exists correctly without substring search" do expect(@cookbook_repo).to receive(:shell_out!).twice.with("git branch --no-color", :cwd => @repo_path).and_return(@branch_list) expect(@cookbook_repo).not_to be_branch_exists("chef-vendor-absent") expect(@cookbook_repo).to be_branch_exists("chef-vendor-absent-new") end describe "when the pristine copy branch does not exist" do it "prepares for import by creating the pristine copy branch" do expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", :cwd => @repo_path).and_return(@branch_list) expect(@cookbook_repo).to receive(:shell_out!).with("git checkout -b chef-vendor-nginx", :cwd => @repo_path) @cookbook_repo.prepare_to_import("nginx") end end describe "when the pristine copy branch does exist" do it "prepares for import by checking out the pristine copy branch" do expect(@cookbook_repo).to receive(:shell_out!).with("git branch --no-color", :cwd => @repo_path).and_return(@branch_list) expect(@cookbook_repo).to receive(:shell_out!).with("git checkout chef-vendor-apache2", :cwd => @repo_path) @cookbook_repo.prepare_to_import("apache2") end end describe "when the pristine copy branch was not updated by the changes" do before do @updates = Mixlib::ShellOut.new @updates.stdout.replace("\n") allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain -- apache2", :cwd => @repo_path).and_return(@updates) end it "shows no changes in the pristine copy" do expect(@cookbook_repo.updated?("apache2")).to be_falsey end it "does nothing to finalize the updates" do expect(@cookbook_repo.finalize_updates_to("apache2", "1.2.3")).to be_falsey end end describe "when the pristine copy branch was updated by the changes" do before do @updates = Mixlib::ShellOut.new @updates.stdout.replace(" M cookbooks/apache2/recipes/default.rb\n") allow(@cookbook_repo).to receive(:shell_out!).with("git status --porcelain -- apache2", :cwd => @repo_path).and_return(@updates) end it "shows changes in the pristine copy" do expect(@cookbook_repo.updated?("apache2")).to be_truthy end it "commits the changes to the repo and tags the commit" do expect(@cookbook_repo).to receive(:shell_out!).with("git add apache2", :cwd => @repo_path) expect(@cookbook_repo).to receive(:shell_out!).with("git commit -m \"Import apache2 version 1.2.3\" -- apache2", :cwd => @repo_path) expect(@cookbook_repo).to receive(:shell_out!).with("git tag -f cookbook-site-imported-apache2-1.2.3", :cwd => @repo_path) expect(@cookbook_repo.finalize_updates_to("apache2", "1.2.3")).to be_truthy end end describe "when a custom default branch is specified" do before do @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, :default_branch => "develop") end it "resets to default state by checking out the default branch" do expect(@cookbook_repo).to receive(:shell_out!).with("git checkout develop", :cwd => @repo_path) @cookbook_repo.reset_to_default_state end end end chef-12.14.60/spec/unit/knife/core/custom_manifest_loader_spec.rb000066400000000000000000000027171276456504500247210ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::SubcommandLoader::CustomManifestLoader do let(:ec2_server_create_plugin) { "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_server_create.rb" } let(:manifest_content) do { "plugins" => { "knife-ec2" => { "paths" => [ ec2_server_create_plugin, ], }, }, } end let(:loader) do Chef::Knife::SubcommandLoader::CustomManifestLoader.new(File.join(CHEF_SPEC_DATA, "knife-site-subcommands"), manifest_content) end it "uses paths from the manifest instead of searching gems" do expect(Gem::Specification).not_to receive(:latest_specs).and_call_original expect(loader.subcommand_files).to include(ec2_server_create_plugin) end end chef-12.14.60/spec/unit/knife/core/gem_glob_loader_spec.rb000066400000000000000000000250721276456504500232730ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::SubcommandLoader::GemGlobLoader do let(:loader) { Chef::Knife::SubcommandLoader::GemGlobLoader.new(File.join(CHEF_SPEC_DATA, "knife-site-subcommands")) } let(:home) { File.join(CHEF_SPEC_DATA, "knife-home") } let(:plugin_dir) { File.join(home, ".chef", "plugins", "knife") } before do allow(ChefConfig).to receive(:windows?) { false } Chef::Util::PathHelper.class_variable_set(:@@home_dir, home) end after do Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil) end it "builds a list of the core subcommand file require paths" do expect(loader.subcommand_files).not_to be_empty loader.subcommand_files.each do |require_path| expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/) end end it "finds files installed via rubygems" do expect(loader.find_subcommands_via_rubygems).to include("chef/knife/node_create") loader.find_subcommands_via_rubygems.each { |rel_path, abs_path| expect(abs_path).to match(%r{chef/knife/.+}) } end it "finds files from latest version of installed gems" do gems = [ double("knife-ec2-0.5.12") ] gem_files = [ "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb", "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb", ] expect($LOAD_PATH).to receive(:map).and_return([]) if Gem::Specification.respond_to? :latest_specs expect(Gem::Specification).to receive(:latest_specs).with(true).and_return(gems) expect(gems[0]).to receive(:matches_for_glob).with(/chef\/knife\/\*\.rb\{(.*),\.rb,(.*)\}/).and_return(gem_files) else expect(Gem.source_index).to receive(:latest_specs).with(true).and_return(gems) expect(gems[0]).to receive(:require_paths).twice.and_return(["lib"]) expect(gems[0]).to receive(:full_gem_path).and_return("/usr/lib/ruby/gems/knife-ec2-0.5.12") expect(Dir).to receive(:[]).with("/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb").and_return(gem_files) end expect(loader).to receive(:find_subcommands_via_dirglob).and_return({}) expect(loader.subcommand_files.select { |file| file =~ /knife-ec2/ }.sort).to eq(gem_files) end it "finds files using a dirglob when rubygems is not available" do expect(loader.find_subcommands_via_dirglob).to include("chef/knife/node_create") loader.find_subcommands_via_dirglob.each { |rel_path, abs_path| expect(abs_path).to match(%r{chef/knife/.+}) } end it "finds user-specific subcommands in the user's ~/.chef directory" do expected_command = File.join(home, ".chef", "plugins", "knife", "example_home_subcommand.rb") expect(loader.site_subcommands).to include(expected_command) end it "finds repo specific subcommands by searching for a .chef directory" do expected_command = File.join(CHEF_SPEC_DATA, "knife-site-subcommands", "plugins", "knife", "example_subcommand.rb") expect(loader.site_subcommands).to include(expected_command) end # https://github.com/opscode/chef-dk/issues/227 # # `knife` in ChefDK isn't from a gem install, it's directly run from a clone # of the source, but there can be one or more versions of chef also installed # as a gem. If the gem install contains a command that doesn't exist in the # source tree of the "primary" chef install, it can be loaded and cause an # error. We also want to ensure that we only load builtin commands from the # "primary" chef install. context "when a different version of chef is also installed as a gem" do let(:all_found_commands) do [ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb", "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb", "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb", # We use the fake version 1.0.0 because that version doesn't exist, # which ensures it won't ever equal "chef-#{Chef::VERSION}" "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/bootstrap.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_bulk_delete.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_create.rb", # Test that we don't accept a version number that is different only in # trailing characters, e.g. we are running Chef 12.0.0 but there is a # Chef 12.0.0.rc.0 gem also: "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb", # Test that we ignore the platform suffix when checking for different # gem versions. "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb", # ...but don't ignore the .rc / .dev parts in the case when we have # platform suffixes "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0-x86-mingw32/lib/chef/knife/invalid.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev-mswin32/lib/chef/knife/invalid-too.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev.0-x86-mingw64/lib/chef/knife/still-invalid.rb", # This command is "extra" compared to what's in the embedded/apps/chef install: "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb", # These are fake commands that have names designed to test that the # regex is strict enough "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb", # In a real scenario, we'd use rubygems APIs to only select the most # recent gem, but for this test we want to check that we're doing the # right thing both when the plugin version matches and does not match # the current chef version. Looking at # `SubcommandLoader::MATCHES_THIS_CHEF_GEM` and # `SubcommandLoader::MATCHES_CHEF_GEM` should make it clear why we want # to test these two cases. "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb", ] end let(:expected_valid_commands) do [ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb", "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb", "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb", "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb", ] end before do expect(loader).to receive(:find_files_latest_gems).with("chef/knife/*.rb").and_return(all_found_commands) expect(loader).to receive(:find_subcommands_via_dirglob).and_return({}) end it "ignores commands from the non-matching gem install" do expect(loader.find_subcommands_via_rubygems.values).to eq(expected_valid_commands) end end describe "finding 3rd party plugins" do let(:env_home) { "/home/alice" } let(:manifest_path) { env_home + "/.chef/plugin_manifest.json" } before do env_dup = ENV.to_hash allow(ENV).to receive(:[]) { |key| env_dup[key] } allow(ENV).to receive(:[]).with("HOME").and_return(env_home) end it "searches rubygems for plugins" do if Gem::Specification.respond_to?(:latest_specs) expect(Gem::Specification).to receive(:latest_specs).and_call_original else expect(Gem.source_index).to receive(:latest_specs).and_call_original end loader.subcommand_files.each do |require_path| expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/) end end context "and HOME environment variable is not set" do before do allow(ENV).to receive(:[]).with("HOME").and_return(nil) end it "searches rubygems for plugins" do if Gem::Specification.respond_to?(:latest_specs) expect(Gem::Specification).to receive(:latest_specs).and_call_original else expect(Gem.source_index).to receive(:latest_specs).and_call_original end loader.subcommand_files.each do |require_path| expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/) end end end end end chef-12.14.60/spec/unit/knife/core/hashed_command_loader_spec.rb000066400000000000000000000066171276456504500244560ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::SubcommandLoader::HashedCommandLoader do before do allow(ChefConfig).to receive(:windows?) { false } end let(:plugin_manifest) do { "_autogenerated_command_paths" => { "plugins_paths" => { "cool_a" => ["/file/for/plugin/a"], "cooler_b" => ["/file/for/plugin/b"], }, "plugins_by_category" => { "cool" => [ "cool_a", ], "cooler" => [ "cooler_b", ], }, }, } end let(:loader) do Chef::Knife::SubcommandLoader::HashedCommandLoader.new( File.join(CHEF_SPEC_DATA, "knife-site-subcommands"), plugin_manifest) end describe "#list_commands" do before do allow(File).to receive(:exists?).and_return(true) end it "lists all commands by category when no argument is given" do expect(loader.list_commands).to eq({ "cool" => ["cool_a"], "cooler" => ["cooler_b"] }) end it "lists only commands in the given category when a category is given" do expect(loader.list_commands("cool")).to eq({ "cool" => ["cool_a"] }) end context "when the plugin path is invalid" do before do expect(File).to receive(:exists?).with("/file/for/plugin/b").and_return(false) end it "lists all commands by category when no argument is given" do expect(Chef::Log).to receive(:error).with(/There are files specified in the manifest that are missing/) expect(Chef::Log).to receive(:error).with("Missing files:\n\t/file/for/plugin/b") expect(loader.list_commands).to eq({}) end end end describe "#subcommand_files" do it "lists all the files" do expect(loader.subcommand_files).to eq(["/file/for/plugin/a", "/file/for/plugin/b"]) end end describe "#load_commands" do before do allow(Kernel).to receive(:load).and_return(true) end it "returns false for non-existant commands" do expect(loader.load_command(["nothere"])).to eq(false) end it "loads the correct file and returns true if the command exists" do allow(File).to receive(:exists?).and_return(true) expect(Kernel).to receive(:load).with("/file/for/plugin/a").and_return(true) expect(loader.load_command(["cool_a"])).to eq(true) end end describe "#subcommand_for_args" do it "returns the subcommands for an exact match" do expect(loader.subcommand_for_args(["cooler_b"])).to eq("cooler_b") end it "finds the right subcommand even when _'s are elided" do expect(loader.subcommand_for_args(%w{cooler b})).to eq("cooler_b") end it "returns nil if the the subcommand isn't in our manifest" do expect(loader.subcommand_for_args(["cooler c"])).to eq(nil) end end end chef-12.14.60/spec/unit/knife/core/node_editor_spec.rb000066400000000000000000000167421276456504500224710ustar00rootroot00000000000000# # Author:: Jordan Running () # Copyright:: Copyright (c) 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/knife/core/node_editor" describe Chef::Knife::NodeEditor do let(:node_data) do { "name" => "test_node", "chef_environment" => "production", "automatic" => { "foo" => "bar" }, "default" => { "alpha" => { "bravo" => "charlie", "delta" => "echo" } }, "normal" => { "alpha" => { "bravo" => "hotel" }, "tags" => [] }, "override" => { "alpha" => { "bravo" => "foxtrot", "delta" => "golf" } }, "policy_name" => nil, "policy_group" => nil, "run_list" => %w{role[comedy] role[drama] recipe[mystery]}, } end let(:node) { Chef::Node.from_hash(node_data) } let(:ui) { double "ui" } let(:base_config) { { editor: "cat" } } let(:config) { base_config.merge(all_attributes: false) } subject { described_class.new(node, ui, config) } describe "#view" do it "returns a Hash with only the name, chef_environment, normal, " + "policy_name, policy_group, and run_list properties" do expected = node_data.select do |key,| %w{ name chef_environment normal policy_name policy_group run_list }.include?(key) end expect(subject.view).to eq(expected) end context "when config[:all_attributes] == true" do let(:config) { base_config.merge(all_attributes: true) } it 'returns a Hash with all of the node\'s properties' do expect(subject.view).to eq(node_data) end end end describe "#apply_updates" do context "when the node name is changed" do before(:each) do allow(ui).to receive(:warn) allow(ui).to receive(:confirm).and_return(true) end it "emits a warning and prompts for confirmation" do data = subject.view.merge("name" => "foo_new_name_node") updated_node = subject.apply_updates(data) expect(ui).to have_received(:warn) .with "Changing the name of a node results in a new node being " + "created, test_node will not be modified or removed." expect(ui).to have_received(:confirm) .with("Proceed with creation of new node") expect(updated_node).to be_a(Chef::Node) end end context "when config[:all_attributes] == false" do let(:config) { base_config.merge(all_attributes: false) } let(:updated_data) do subject.view.merge( "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] }, "policy_name" => "mypolicy", "policy_group" => "prod", "run_list" => %w{role[drama] recipe[mystery]} ) end it "returns a node with run_list and normal_attrs changed" do updated_node = subject.apply_updates(updated_data) expect(updated_node).to be_a(Chef::Node) # Expected to have been changed expect(updated_node.chef_environment).to eql(updated_data["chef_environment"]) expect(updated_node.normal_attrs).to eql(updated_data["normal"]) expect(updated_node.policy_name).to eql(updated_data["policy_name"]) expect(updated_node.policy_group).to eql(updated_data["policy_group"]) expect(updated_node.run_list.map(&:to_s)).to eql(updated_data["run_list"]) # Expected not to have changed expect(updated_node.default_attrs).to eql(node.default_attrs) expect(updated_node.override_attrs).to eql(node.override_attrs) expect(updated_node.automatic_attrs).to eql(node.automatic_attrs) end end context "when config[:all_attributes] == true" do let(:config) { base_config.merge(all_attributes: true) } let(:updated_data) do subject.view.merge( "default" => { "alpha" => { "bravo" => "charlie2", "delta" => "echo2" } }, "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] }, "override" => { "alpha" => { "bravo" => "foxtrot2", "delta" => "golf2" } }, "policy_name" => "mypolicy", "policy_group" => "prod", "run_list" => %w{role[drama] recipe[mystery]} ) end it "returns a node with all editable properties changed" do updated_node = subject.apply_updates(updated_data) expect(updated_node).to be_a(Chef::Node) expect(updated_node.chef_environment).to eql(updated_data["chef_environment"]) expect(updated_node.automatic_attrs).to eql(updated_data["automatic"]) expect(updated_node.normal_attrs).to eql(updated_data["normal"]) expect(updated_node.default_attrs).to eql(updated_data["default"]) expect(updated_node.override_attrs).to eql(updated_data["override"]) expect(updated_node.policy_name).to eql(updated_data["policy_name"]) expect(updated_node.policy_group).to eql(updated_data["policy_group"]) expect(updated_node.run_list.map(&:to_s)).to eql(updated_data["run_list"]) end end end describe "#updated?" do context "before the node has been edited" do it "returns false" do expect(subject.updated?).to be false end end context "after the node has been edited" do context "and changes were made" do let(:updated_data) do subject.view.merge( "default" => { "alpha" => { "bravo" => "charlie2", "delta" => "echo2" } }, "normal" => { "alpha" => { "bravo" => "hotel2" }, "tags" => [ "xyz" ] }, "override" => { "alpha" => { "bravo" => "foxtrot2", "delta" => "golf2" } }, "policy_name" => "mypolicy", "policy_group" => "prod", "run_list" => %w{role[drama] recipe[mystery]} ) end context "and changes affect only editable properties" do before(:each) do allow(ui).to receive(:edit_hash) .with(subject.view) .and_return(updated_data) subject.edit_node end it "returns an array of the changed property names" do expect(subject.updated?).to eql %w{ normal policy_name policy_group run_list } end end context "and the changes include non-editable properties" do before(:each) do data = updated_data.merge("bad_property" => "bad_value") allow(ui).to receive(:edit_hash) .with(subject.view) .and_return(data) subject.edit_node end it 'returns an array of property names that doesn\'t include ' + "the non-editable properties" do expect(subject.updated?).to eql %w{ normal policy_name policy_group run_list } end end end context "and changes were not made" do before(:each) do allow(ui).to receive(:edit_hash) .with(subject.view) .and_return(subject.view.dup) subject.edit_node end it { is_expected.not_to be_updated } end end end end chef-12.14.60/spec/unit/knife/core/object_loader_spec.rb000066400000000000000000000045271276456504500227700ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Juanje Ojeda () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/knife/core/object_loader" describe Chef::Knife::Core::ObjectLoader do before(:each) do @knife = Chef::Knife.new @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) Dir.chdir(File.join(CHEF_SPEC_DATA, "object_loader")) end shared_examples_for "Chef object" do |chef_class| it "should create a #{chef_class} object" do expect(@object).to be_a_kind_of(chef_class) end it "should has a attribute 'name'" do expect(@object.name).to eql("test") end end { "nodes" => Chef::Node, "roles" => Chef::Role, "environments" => Chef::Environment, }.each do |repo_location, chef_class| describe "when the file is a #{chef_class}" do before do @loader = Chef::Knife::Core::ObjectLoader.new(chef_class, @knife.ui) end describe "when the file is a Ruby" do before do @object = @loader.load_from(repo_location, "test.rb") end it_behaves_like "Chef object", chef_class end #NOTE: This is check for the bug described at CHEF-2352 describe "when the file is a JSON" do describe "and it has defined 'json_class'" do before do @object = @loader.load_from(repo_location, "test_json_class.json") end it_behaves_like "Chef object", chef_class end describe "and it has not defined 'json_class'" do before do @object = @loader.load_from(repo_location, "test.json") end it_behaves_like "Chef object", chef_class end end end end end chef-12.14.60/spec/unit/knife/core/subcommand_loader_spec.rb000066400000000000000000000055361276456504500236530ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::SubcommandLoader do let(:loader) { Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, "knife-site-subcommands")) } let(:home) { File.join(CHEF_SPEC_DATA, "knife-home") } let(:plugin_dir) { File.join(home, ".chef", "plugins", "knife") } before do allow(ChefConfig).to receive(:windows?) { false } Chef::Util::PathHelper.class_variable_set(:@@home_dir, home) end after do Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil) end let(:config_dir) { File.join(CHEF_SPEC_DATA, "knife-site-subcommands") } describe "#for_config" do context "when ~/.chef/plugin_manifest.json exists" do before do allow(File).to receive(:exist?).with(File.join(home, ".chef", "plugin_manifest.json")).and_return(true) end it "creates a HashedCommandLoader with the manifest has _autogenerated_command_paths" do allow(File).to receive(:read).with(File.join(home, ".chef", "plugin_manifest.json")).and_return("{ \"_autogenerated_command_paths\": {}}") expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::HashedCommandLoader end it "creates a CustomManifestLoader with then manifest has a key other than _autogenerated_command_paths" do Chef::Config[:treat_deprecation_warnings_as_errors] = false allow(File).to receive(:read).with(File.join(home, ".chef", "plugin_manifest.json")).and_return("{ \"plugins\": {}}") expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::CustomManifestLoader end end context "when ~/.chef/plugin_manifest.json does not exist" do before do allow(File).to receive(:exist?).with(File.join(home, ".chef", "plugin_manifest.json")).and_return(false) end it "creates a GemGlobLoader" do expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader end end end describe "#gem_glob_loader" do it "always creates a GemGlobLoader" do expect(Chef::Knife::SubcommandLoader.gem_glob_loader(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader end end end chef-12.14.60/spec/unit/knife/core/ui_spec.rb000066400000000000000000000450021276456504500206020ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tim Hinderliter () # Author:: Daniel DeLeo () # Author:: John Keiser () # 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::Knife::UI do before do @out, @err, @in = StringIO.new, StringIO.new, StringIO.new @config = { :verbosity => 0, :yes => nil, :format => "summary", } @ui = Chef::Knife::UI.new(@out, @err, @in, @config) Chef::Config[:treat_deprecation_warnings_as_errors] = false end describe "edit" do ruby_for_json = { "foo" => "bar" } json_from_ruby = "{\n \"foo\": \"bar\"\n}" json_from_editor = "{\n \"bar\": \"foo\"\n}" ruby_from_editor = { "bar" => "foo" } my_editor = "veeeye" temp_path = "/tmp/bar/baz" let(:subject) { @ui.edit_data(ruby_for_json, parse_output) } let(:parse_output) { false } context "when editing is disabled" do before do @ui.config[:disable_editing] = true stub_const("Tempfile", double) # Tempfiles should never be invoked end context "when parse_output is false" do it "returns pretty json string" do expect(subject).to eql(json_from_ruby) end end context "when parse_output is true" do let(:parse_output) { true } it "returns a ruby object" do expect(subject).to eql(ruby_for_json) end it "gives a deprecation error" do Chef::Config[:treat_deprecation_warnings_as_errors] = true expect { subject }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Auto inflation of JSON data is deprecated./ end end end context "when editing is enabled" do before do @ui.config[:disable_editing] = false @ui.config[:editor] = my_editor @mock = double("Tempfile") expect(@mock).to receive(:sync=).with(true) expect(@mock).to receive(:puts).with(json_from_ruby) expect(@mock).to receive(:close) expect(@mock).to receive(:path).at_least(:once).and_return(temp_path) expect(Tempfile).to receive(:open).with([ "knife-edit-", ".json" ]).and_yield(@mock) end context "and the editor works" do before do expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(true) expect(IO).to receive(:read).with(temp_path).and_return(json_from_editor) end context "when parse_output is false" do it "returns an edited pretty json string" do expect(subject).to eql(json_from_editor) end end context "when parse_output is true" do let(:parse_output) { true } it "returns an edited ruby object" do expect(subject).to eql(ruby_from_editor) end end end context "when running the editor fails with nil" do before do expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(nil) expect(IO).not_to receive(:read) end it "throws an exception" do expect { subject }.to raise_error(RuntimeError) end end context "when running the editor fails with false" do before do expect(@ui).to receive(:system).with("#{my_editor} #{temp_path}").and_return(false) expect(IO).not_to receive(:read) end it "throws an exception" do expect { subject }.to raise_error(RuntimeError) end end end context "when editing and not stubbing Tempfile (semi-functional test)" do before do @ui.config[:disable_editing] = false @ui.config[:editor] = my_editor @tempfile = Tempfile.new([ "knife-edit-", ".json" ]) expect(Tempfile).to receive(:open).with([ "knife-edit-", ".json" ]).and_yield(@tempfile) end context "and the editor works" do before do expect(@ui).to receive(:system).with("#{my_editor} #{@tempfile.path}").and_return(true) expect(IO).to receive(:read).with(@tempfile.path).and_return(json_from_editor) end context "when parse_output is false" do it "returns an edited pretty json string" do expect(subject).to eql(json_from_editor) end it "the tempfile should have mode 0600", :unix_only do # XXX: this looks odd because we're really testing Tempfile.new here expect(File.stat(@tempfile.path).mode & 0777).to eql(0600) expect(subject).to eql(json_from_editor) end end context "when parse_output is true" do let(:parse_output) { true } it "returns an edited ruby object" do expect(subject).to eql(ruby_from_editor) end it "the tempfile should have mode 0600", :unix_only do # XXX: this looks odd because we're really testing Tempfile.new here expect(File.stat(@tempfile.path).mode & 0777).to eql(0600) expect(subject).to eql(ruby_from_editor) end end end end end describe "format_list_for_display" do it "should print the full hash if --with-uri is true" do @ui.config[:with_uri] = true expect(@ui.format_list_for_display({ :marcy => :playground })).to eq({ :marcy => :playground }) end it "should print only the keys if --with-uri is false" do @ui.config[:with_uri] = false expect(@ui.format_list_for_display({ :marcy => :playground })).to eq([ :marcy ]) end end shared_examples "an output mehthod handling IO exceptions" do |method| it "should throw Errno::EIO exceptions" do allow(@out).to receive(:puts).and_raise(Errno::EIO) allow(@err).to receive(:puts).and_raise(Errno::EIO) expect { @ui.send(method, "hi") }.to raise_error(Errno::EIO) end it "should ignore Errno::EPIPE exceptions (CHEF-3516)" do allow(@out).to receive(:puts).and_raise(Errno::EPIPE) allow(@err).to receive(:puts).and_raise(Errno::EPIPE) expect { @ui.send(method, "hi") }.to raise_error(SystemExit) end it "should throw Errno::EPIPE exceptions with -VV (CHEF-3516)" do @config[:verbosity] = 2 allow(@out).to receive(:puts).and_raise(Errno::EPIPE) allow(@err).to receive(:puts).and_raise(Errno::EPIPE) expect { @ui.send(method, "hi") }.to raise_error(Errno::EPIPE) end end describe "output" do it_behaves_like "an output mehthod handling IO exceptions", :output it "formats strings appropriately" do @ui.output("hi") expect(@out.string).to eq("hi\n") end it "formats hashes appropriately" do @ui.output({ "hi" => "a", "lo" => "b" }) expect(@out.string).to eq < "b", "c" => "d" }, { "x" => "y" }, { "m" => "n", "o" => "p" }]) expect(@out.string).to eq < [], "b" => "c" }) expect(@out.string).to eq < [ "foo" ], "b" => "c" }) expect(@out.string).to eq < %w{foo bar}, "b" => "c" }) expect(@out.string).to eq < [ [ "foo" ] ], "b" => "c" }) expect(@out.string).to eq < [ %w{foo bar}, %w{baz bjork} ], "b" => "c" }) # XXX: using a HEREDOC at this point results in a line with required spaces which auto-whitespace removal settings # on editors will remove and will break this test. expect(@out.string).to eq("a:\n foo\n bar\n \n baz\n bjork\nb: c\n") end it "formats hashes with hash values appropriately" do @ui.output({ "a" => { "aa" => "bb", "cc" => "dd" }, "b" => "c" }) expect(@out.string).to eq < {}, "b" => "c" }) expect(@out.string).to eq < :go } expect(@ui.format_for_display(input)).to eq(input) end describe "with --attribute passed" do it "should return the deeply nested attribute" do input = { "gi" => { "go" => "ge" }, "id" => "sample-data-bag-item" } @ui.config[:attribute] = "gi.go" expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "gi.go" => "ge" } }) end it "should return multiple attributes" do input = { "gi" => "go", "hi" => "ho", "id" => "sample-data-bag-item" } @ui.config[:attribute] = %w{gi hi} expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "gi" => "go", "hi" => "ho" } }) end it "should handle attributes named the same as methods" do input = { "keys" => "values", "hi" => "ho", "id" => "sample-data-bag-item" } @ui.config[:attribute] = "keys" expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys" => "values" } }) end it "should handle nested attributes named the same as methods" do input = { "keys" => { "keys" => "values" }, "hi" => "ho", "id" => "sample-data-bag-item" } @ui.config[:attribute] = "keys.keys" expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys.keys" => "values" } }) end it "should return the name attribute" do allow_any_instance_of(Chef::Node).to receive(:name).and_return("chef.localdomain") input = Chef::Node.new @ui.config[:attribute] = "name" expect(@ui.format_for_display(input)).to eq( { "chef.localdomain" => { "name" => "chef.localdomain" } }) end it "returns nil when given an attribute path that isn't a name or attribute" do input = { "keys" => { "keys" => "values" }, "hi" => "ho", "id" => "sample-data-bag-item" } non_existing_path = "nope.nada.nothingtoseehere" @ui.config[:attribute] = non_existing_path expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { non_existing_path => nil } }) end end describe "with --run-list passed" do it "should return the run list" do input = Chef::Node.new input.name("sample-node") input.run_list("role[monkey]", "role[churchmouse]") @ui.config[:run_list] = true response = @ui.format_for_display(input) expect(response["sample-node"]["run_list"][0]).to eq("role[monkey]") expect(response["sample-node"]["run_list"][1]).to eq("role[churchmouse]") end end end describe "format_cookbook_list_for_display" do before(:each) do @item = { "cookbook_name" => { "url" => "http://url/cookbooks/cookbook", "versions" => [ { "version" => "3.0.0", "url" => "http://url/cookbooks/3.0.0" }, { "version" => "2.0.0", "url" => "http://url/cookbooks/2.0.0" }, { "version" => "1.0.0", "url" => "http://url/cookbooks/1.0.0" }, ], }, } end it "should return an array of the cookbooks with versions" do expected_response = [ "cookbook_name 3.0.0 2.0.0 1.0.0" ] response = @ui.format_cookbook_list_for_display(@item) expect(response).to eq(expected_response) end describe "with --with-uri" do it "should return the URIs" do response = { "cookbook_name" => { "1.0.0" => "http://url/cookbooks/1.0.0", "2.0.0" => "http://url/cookbooks/2.0.0", "3.0.0" => "http://url/cookbooks/3.0.0" }, } @ui.config[:with_uri] = true expect(@ui.format_cookbook_list_for_display(@item)).to eq(response) end end context "when running on Windows" do before(:each) do stdout = double("StringIO", :tty? => true) allow(@ui).to receive(:stdout).and_return(stdout) allow(ChefConfig).to receive(:windows?) { true } Chef::Config.reset end after(:each) do Chef::Config.reset end it "should have color set to true if knife config has color explicitly set to true" do Chef::Config[:color] = true @ui.config[:color] = true expect(@ui.color?).to eql(true) end it "should have color set to false if knife config has color explicitly set to false" do Chef::Config[:color] = false expect(@ui.color?).to eql(false) end it "should not have color set to false by default" do expect(@ui.color?).to eql(false) end end end describe "confirm" do let(:stdout) { StringIO.new } let(:output) { stdout.string } let(:question) { "monkeys rule" } let(:answer) { "y" } let(:default_choice) { nil } let(:append_instructions) { true } def run_confirm allow(@ui).to receive(:stdout).and_return(stdout) allow(@ui.stdin).to receive(:readline).and_return(answer) @ui.confirm(question, append_instructions, default_choice) end def run_confirm_without_exit allow(@ui).to receive(:stdout).and_return(stdout) allow(@ui.stdin).to receive(:readline).and_return(answer) @ui.confirm_without_exit(question, append_instructions, default_choice) end shared_examples_for "confirm with positive answer" do it "confirm should return true" do expect(run_confirm).to be_truthy end it "confirm_without_exit should return true" do expect(run_confirm_without_exit).to be_truthy end end shared_examples_for "confirm with negative answer" do it "confirm should exit 3" do expect do run_confirm end.to raise_error(SystemExit) { |e| expect(e.status).to eq(3) } end it "confirm_without_exit should return false" do expect(run_confirm_without_exit).to be_falsey end end describe "with default choice set to true" do let(:default_choice) { true } it "should show 'Y/n' in the instructions" do run_confirm expect(output).to include("Y/n") end describe "with empty answer" do let(:answer) { "" } it_behaves_like "confirm with positive answer" end describe "with answer N " do let(:answer) { "N" } it_behaves_like "confirm with negative answer" end end describe "with default choice set to false" do let(:default_choice) { false } it "should show 'y/N' in the instructions" do run_confirm expect(output).to include("y/N") end describe "with empty answer" do let(:answer) { "" } it_behaves_like "confirm with negative answer" end describe "with answer N " do let(:answer) { "Y" } it_behaves_like "confirm with positive answer" end end %w{Y y}.each do |answer| describe "with answer #{answer}" do let(:answer) { answer } it_behaves_like "confirm with positive answer" end end %w{N n}.each do |answer| describe "with answer #{answer}" do let(:answer) { answer } it_behaves_like "confirm with negative answer" end end describe "with --y or --yes passed" do it "should return true" do @ui.config[:yes] = true expect(run_confirm).to be_truthy expect(output).to eq("") end end end describe "when asking for free-form user input" do it "asks a question and returns the answer provided by the user" do out = StringIO.new allow(@ui).to receive(:stdout).and_return(out) allow(@ui).to receive(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n")) expect(@ui.ask_question("your chef server URL?")).to eq("http://mychefserver.example.com") expect(out.string).to eq("your chef server URL?") end it "suggests a default setting and returns the default when the user's response only contains whitespace" do out = StringIO.new allow(@ui).to receive(:stdout).and_return(out) allow(@ui).to receive(:stdin).and_return(StringIO.new(" \n")) expect(@ui.ask_question("your chef server URL? ", :default => "http://localhost:4000")).to eq("http://localhost:4000") expect(out.string).to eq("your chef server URL? [http://localhost:4000] ") end end end chef-12.14.60/spec/unit/knife/data_bag_create_spec.rb000066400000000000000000000065771276456504500223200ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Seth Falcon () # Copyright:: Copyright 2009-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 "tempfile" describe Chef::Knife::DataBagCreate do let(:knife) do k = Chef::Knife::DataBagCreate.new allow(k).to receive(:rest).and_return(rest) allow(k.ui).to receive(:stdout).and_return(stdout) k end let(:rest) { double("Chef::ServerAPI") } let(:stdout) { StringIO.new } let(:bag_name) { "sudoing_admins" } let(:item_name) { "ME" } let(:secret) { "abc123SECRET" } let(:raw_hash) { { "login_name" => "alphaomega", "id" => item_name } } let(:config) { {} } before do Chef::Config[:node_name] = "webmonkey.example.com" knife.name_args = [bag_name, item_name] allow(knife).to receive(:config).and_return(config) end it "tries to create a data bag with an invalid name when given one argument" do knife.name_args = ["invalid&char"] expect(Chef::DataBag).to receive(:validate_name!).with(knife.name_args[0]).and_raise(Chef::Exceptions::InvalidDataBagName) expect { knife.run }.to exit_with_code(1) end context "when given one argument" do before do knife.name_args = [bag_name] end it "creates a data bag" do expect(rest).to receive(:post).with("data", { "name" => bag_name }) expect(knife.ui).to receive(:info).with("Created data_bag[#{bag_name}]") knife.run end end context "no secret is specified for encryption" do let(:item) do item = Chef::DataBagItem.from_hash(raw_hash) item.data_bag(bag_name) item end it "creates a data bag item" do expect(knife).to receive(:create_object).and_yield(raw_hash) expect(knife).to receive(:encryption_secret_provided?).and_return(false) expect(rest).to receive(:post).with("data", { "name" => bag_name }).ordered expect(rest).to receive(:post).with("data/#{bag_name}", item).ordered knife.run end end context "a secret is specified for encryption" do let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) } let(:item) do item = Chef::DataBagItem.from_hash(encoded_data) item.data_bag(bag_name) item end it "creates an encrypted data bag item" do expect(knife).to receive(:create_object).and_yield(raw_hash) expect(knife).to receive(:encryption_secret_provided?).and_return(true) expect(knife).to receive(:read_secret).and_return(secret) expect(Chef::EncryptedDataBagItem) .to receive(:encrypt_data_bag_item) .with(raw_hash, secret) .and_return(encoded_data) expect(rest).to receive(:post).with("data", { "name" => bag_name }).ordered expect(rest).to receive(:post).with("data/#{bag_name}", item).ordered knife.run end end end chef-12.14.60/spec/unit/knife/data_bag_edit_spec.rb000066400000000000000000000106671276456504500217750ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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 "tempfile" describe Chef::Knife::DataBagEdit do before do Chef::Config[:node_name] = "webmonkey.example.com" knife.name_args = [bag_name, item_name] allow(knife).to receive(:config).and_return(config) end let(:knife) do k = Chef::Knife::DataBagEdit.new allow(k).to receive(:rest).and_return(rest) allow(k.ui).to receive(:stdout).and_return(stdout) k end let(:raw_hash) { { "login_name" => "alphaomega", "id" => "item_name" } } let(:db) { Chef::DataBagItem.from_hash(raw_hash) } let(:raw_edited_hash) { { "login_name" => "rho", "id" => "item_name", "new_key" => "new_value" } } let(:rest) { double("Chef::ServerAPI") } let(:stdout) { StringIO.new } let(:bag_name) { "sudoing_admins" } let(:item_name) { "ME" } let(:secret) { "abc123SECRET" } let(:config) { {} } let(:is_encrypted?) { false } let(:transmitted_hash) { raw_edited_hash } let(:data_to_edit) { db } shared_examples_for "editing a data bag" do it "correctly edits then uploads the data bag" do expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db) expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(is_encrypted?) expect(knife).to receive(:edit_hash).with(data_to_edit).and_return(raw_edited_hash) expect(rest).to receive(:put).with("data/#{bag_name}/#{item_name}", transmitted_hash).ordered knife.run end end it "requires data bag and item arguments" do knife.name_args = [] expect(stdout).to receive(:puts).twice.with(anything) expect { knife.run }.to exit_with_code(1) expect(stdout.string).to eq("") end context "when no secret is provided" do include_examples "editing a data bag" end context "when config[:print_after] is set" do let(:config) { { :print_after => true } } before do expect(knife.ui).to receive(:output).with(raw_edited_hash) end include_examples "editing a data bag" end context "when a secret is provided" do let!(:enc_raw_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) } let!(:enc_edited_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_edited_hash, secret) } let(:transmitted_hash) { enc_edited_hash } before(:each) do expect(knife).to receive(:read_secret).at_least(1).times.and_return(secret) expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(raw_edited_hash, secret).and_return(enc_edited_hash) end context "the data bag starts encrypted" do let(:is_encrypted?) { true } let(:db) { Chef::DataBagItem.from_hash(enc_raw_hash) } # If the data bag is encrypted, it gets passed to `edit` as a hash. Otherwise, it gets passed as a DataBag let (:data_to_edit) { raw_hash } before(:each) do expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) end include_examples "editing a data bag" end context "the data bag starts unencrypted" do before(:each) do expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).exactly(0).times expect(knife).to receive(:encryption_secret_provided?).and_return(true) end include_examples "editing a data bag" end end it "fails to edit an encrypted data bag if the secret is missing" do expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db) expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(true) expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) expect(knife.ui).to receive(:fatal).with("You cannot edit an encrypted data bag without providing the secret.") expect { knife.run }.to exit_with_code(1) end end chef-12.14.60/spec/unit/knife/data_bag_from_file_spec.rb000066400000000000000000000152641276456504500230100ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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_bag_item" require "chef/encrypted_data_bag_item" require "tempfile" Chef::Knife::DataBagFromFile.load_deps describe Chef::Knife::DataBagFromFile do before :each do allow(ChefConfig).to receive(:windows?) { false } Chef::Config[:node_name] = "webmonkey.example.com" FileUtils.mkdir_p([db_folder, db_folder2]) db_file.write(Chef::JSONCompat.to_json(plain_data)) db_file.flush allow(knife).to receive(:config).and_return(config) allow(Chef::Knife::Core::ObjectLoader).to receive(:new).and_return(loader) end # We have to explicitly clean up Tempfile on Windows because it said so. after :each do db_file.close db_file2.close db_file3.close FileUtils.rm_rf(db_folder) FileUtils.rm_rf(db_folder2) FileUtils.remove_entry_secure tmp_dir end let(:knife) do k = Chef::Knife::DataBagFromFile.new allow(k).to receive(:rest).and_return(rest) allow(k.ui).to receive(:stdout).and_return(stdout) k end let(:tmp_dir) { make_canonical_temp_directory } let(:db_folder) { File.join(tmp_dir, data_bags_path, bag_name) } let(:db_file) { Tempfile.new(["data_bag_from_file_test", ".json"], db_folder) } let(:db_file2) { Tempfile.new(["data_bag_from_file_test2", ".json"], db_folder) } let(:db_folder2) { File.join(tmp_dir, data_bags_path, bag_name2) } let(:db_file3) { Tempfile.new(["data_bag_from_file_test3", ".json"], db_folder2) } def new_bag_expects(b = bag_name, d = plain_data) data_bag = double expect(data_bag).to receive(:data_bag).with(b) expect(data_bag).to receive(:raw_data=).with(d) expect(data_bag).to receive(:save) expect(data_bag).to receive(:data_bag) expect(data_bag).to receive(:id) data_bag end let(:loader) { double("Knife::Core::ObjectLoader") } let(:data_bags_path) { "data_bags" } let(:plain_data) do { "id" => "item_name", "greeting" => "hello", "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true } }, } end let(:enc_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(plain_data, secret) } let(:rest) { double("Chef::ServerAPI") } let(:stdout) { StringIO.new } let(:bag_name) { "sudoing_admins" } let(:bag_name2) { "sudoing_admins2" } let(:item_name) { "ME" } let(:secret) { "abc123SECRET" } let(:config) { {} } it "loads from a file and saves" do knife.name_args = [bag_name, db_file.path] expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects) knife.run end it "loads all from multiple files and saves" do knife.name_args = [ bag_name, db_file.path, db_file2.path ] expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects) knife.run end it "loads all from a folder and saves" do knife.name_args = [ bag_name, db_folder ] expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects) knife.run end describe "loading all data bags" do it "loads all data bags when -a or --all options is provided" do knife.name_args = [] config[:all] = true expect(loader).to receive(:find_all_object_dirs).with("./#{data_bags_path}").and_return([bag_name, bag_name2]) expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name}").and_return([File.basename(db_file.path), File.basename(db_file2.path)]) expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)]) expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file.path)).and_return(plain_data) expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file2.path)).and_return(plain_data) expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).exactly(3).times.and_return(new_bag_expects, new_bag_expects, new_bag_expects(bag_name2)) knife.run end it "loads all data bags items when -a or --all options is provided" do knife.name_args = [bag_name2] config[:all] = true expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)]) expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name2)) knife.run end end describe "encrypted data bag items" do before(:each) do expect(knife).to receive(:encryption_secret_provided?).and_return(true) expect(knife).to receive(:read_secret).and_return(secret) expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(plain_data, secret).and_return(enc_data) end it "encrypts values when given --secret" do knife.name_args = [bag_name, db_file.path] expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name, enc_data)) knife.run end end describe "command line parsing" do it "prints help if given no arguments" do knife.name_args = [bag_name] expect { knife.run }.to exit_with_code(1) expect(stdout.string).to start_with("knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)") end end end chef-12.14.60/spec/unit/knife/data_bag_secret_options_spec.rb000066400000000000000000000142051276456504500241000ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2009-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/knife" require "chef/config" require "tempfile" class ExampleDataBagCommand < Chef::Knife include Chef::Knife::DataBagSecretOptions end describe Chef::Knife::DataBagSecretOptions do let(:example_db) do k = ExampleDataBagCommand.new allow(k.ui).to receive(:stdout).and_return(stdout) k end let(:stdout) { StringIO.new } let(:secret) { "abc123SECRET" } let(:secret_file) do sfile = Tempfile.new("encrypted_data_bag_secret") sfile.puts(secret) sfile.flush sfile end after do secret_file.close secret_file.unlink end describe "#validate_secrets" do it "throws an error when provided with both --secret and --secret-file on the CL" do Chef::Config[:knife][:cl_secret_file] = secret_file.path Chef::Config[:knife][:cl_secret] = secret expect(example_db).to receive(:exit).with(1) expect(example_db.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file") example_db.validate_secrets end it "throws an error when provided with `secret` and `secret_file` in knife.rb" do Chef::Config[:knife][:secret_file] = secret_file.path Chef::Config[:knife][:secret] = secret expect(example_db).to receive(:exit).with(1) expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config file") example_db.validate_secrets end end describe "#read_secret" do it "returns the secret first" do Chef::Config[:knife][:cl_secret] = secret expect(example_db).to receive(:config).and_return({ :secret => secret }) expect(example_db.read_secret).to eq(secret) end it "returns the secret_file only if secret does not exist" do Chef::Config[:knife][:cl_secret_file] = secret_file.path expect(example_db).to receive(:config).and_return({ :secret_file => secret_file.path }) expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") expect(example_db.read_secret).to eq("secret file contents") end it "returns the secret from the knife.rb config" do Chef::Config[:knife][:secret_file] = secret_file.path Chef::Config[:knife][:secret] = secret expect(example_db.read_secret).to eq(secret) end it "returns the secret_file from the knife.rb config only if the secret does not exist" do Chef::Config[:knife][:secret_file] = secret_file.path expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") expect(example_db.read_secret).to eq("secret file contents") end end describe "#encryption_secret_provided?" do it "returns true if the secret is passed on the CL" do Chef::Config[:knife][:cl_secret] = secret expect(example_db.encryption_secret_provided?).to eq(true) end it "returns true if the secret_file is passed on the CL" do Chef::Config[:knife][:cl_secret_file] = secret_file.path expect(example_db.encryption_secret_provided?).to eq(true) end it "returns true if --encrypt is passed on the CL and :secret is in config" do expect(example_db).to receive(:config).and_return({ :encrypt => true }) Chef::Config[:knife][:secret] = secret expect(example_db.encryption_secret_provided?).to eq(true) end it "returns true if --encrypt is passed on the CL and :secret_file is in config" do expect(example_db).to receive(:config).and_return({ :encrypt => true }) Chef::Config[:knife][:secret_file] = secret_file.path expect(example_db.encryption_secret_provided?).to eq(true) end it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the config" do expect(example_db).to receive(:config).and_return({ :encrypt => true }) expect(example_db).to receive(:exit).with(1) expect(example_db.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.") example_db.encryption_secret_provided? end it "returns false if no secret is passed" do expect(example_db).to receive(:config).and_return({}) expect(example_db.encryption_secret_provided?).to eq(false) end it "returns false if --encrypt is not provided and :secret is in the config" do expect(example_db).to receive(:config).and_return({}) Chef::Config[:knife][:secret] = secret expect(example_db.encryption_secret_provided?).to eq(false) end it "returns false if --encrypt is not provided and :secret_file is in the config" do expect(example_db).to receive(:config).and_return({}) Chef::Config[:knife][:secret_file] = secret_file.path expect(example_db.encryption_secret_provided?).to eq(false) end it "returns true if --encrypt is not provided, :secret is in the config and need_encrypt_flag is false" do Chef::Config[:knife][:secret] = secret expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true) end it "returns true if --encrypt is not provided, :secret_file is in the config and need_encrypt_flag is false" do Chef::Config[:knife][:secret_file] = secret_file.path expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true) end it "returns false if --encrypt is not provided and need_encrypt_flag is false" do expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(false) end end end chef-12.14.60/spec/unit/knife/data_bag_show_spec.rb000066400000000000000000000110031276456504500220110ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Falcon () # 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" require "chef/data_bag_item" require "chef/encrypted_data_bag_item" require "chef/json_compat" require "tempfile" describe Chef::Knife::DataBagShow do before do Chef::Config[:node_name] = "webmonkey.example.com" knife.name_args = [bag_name, item_name] allow(knife).to receive(:config).and_return(config) end let(:knife) do k = Chef::Knife::DataBagShow.new allow(k).to receive(:rest).and_return(rest) allow(k.ui).to receive(:stdout).and_return(stdout) k end let(:rest) { double("Chef::ServerAPI") } let(:stdout) { StringIO.new } let(:bag_name) { "sudoing_admins" } let(:item_name) { "ME" } let(:data_bag_contents) do { "id" => "id", "baz" => "http://localhost:4000/data/bag_o_data/baz", "qux" => "http://localhost:4000/data/bag_o_data/qux" } end let(:enc_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(data_bag_contents, secret) } let(:data_bag) { Chef::DataBagItem.from_hash(data_bag_contents) } let(:data_bag_with_encoded_hash) { Chef::DataBagItem.from_hash(enc_hash) } let(:enc_data_bag) { Chef::EncryptedDataBagItem.new(enc_hash, secret) } let(:secret) { "abc123SECRET" } # # let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }} # let(:config) { { format: "json" } } context "Data bag to show is encrypted" do before do allow(knife).to receive(:encrypted?).and_return(true) end it "decrypts and displays the encrypted data bag when the secret is provided" do expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) expect(knife).to receive(:read_secret).and_return(secret) expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) expect(knife.ui).to receive(:info).with("Encrypted data bag detected, decrypting with provided secret.") expect(Chef::EncryptedDataBagItem).to receive(:load).with(bag_name, item_name, secret).and_return(enc_data_bag) expected = %q{baz: http://localhost:4000/data/bag_o_data/baz id: id qux: http://localhost:4000/data/bag_o_data/qux} knife.run expect(stdout.string.strip).to eq(expected) end it "displays the encrypted data bag when the secret is not provided" do expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) expect(knife.ui).to receive(:warn).with("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.") knife.run expect(stdout.string.strip).to include("baz", "qux", "cipher") end end context "Data bag to show is not encrypted" do before do allow(knife).to receive(:encrypted?).and_return(false) expect(knife).to receive(:read_secret).exactly(0).times end it "displays the data bag" do expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag) expect(knife.ui).to receive(:warn).with("Unencrypted data bag detected, ignoring any provided secret options.") expected = %q{baz: http://localhost:4000/data/bag_o_data/baz id: id qux: http://localhost:4000/data/bag_o_data/qux} knife.run expect(stdout.string.strip).to eq(expected) end end it "displays the list of items in the data bag when only one @name_arg is provided" do knife.name_args = [bag_name] expect(Chef::DataBag).to receive(:load).with(bag_name).and_return({}) knife.run expect(stdout.string.strip).to eq("") end it "raises an error when no @name_args are provided" do knife.name_args = [] expect { knife.run }.to exit_with_code(1) expect(stdout.string).to start_with("knife data bag show BAG [ITEM] (options)") end end chef-12.14.60/spec/unit/knife/environment_compare_spec.rb000066400000000000000000000070131276456504500233070ustar00rootroot00000000000000# # Author:: Sander Botman () # Copyright:: Copyright 2013-2016, Sander Botman. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::EnvironmentCompare do before(:each) do @knife = Chef::Knife::EnvironmentCompare.new @environments = { "cita" => "http://localhost:4000/environments/cita", "citm" => "http://localhost:4000/environments/citm", } allow(@knife).to receive(:environment_list).and_return(@environments) @constraints = { "cita" => { "foo" => "= 1.0.1", "bar" => "= 0.0.4" }, "citm" => { "foo" => "= 1.0.1", "bar" => "= 0.0.2" }, } allow(@knife).to receive(:constraint_list).and_return(@constraints) @cookbooks = { "foo" => "= 1.0.1", "bar" => "= 0.0.1" } allow(@knife).to receive(:cookbook_list).and_return(@cookbooks) @rest_double = double("rest") allow(@knife).to receive(:rest).and_return(@rest_double) @cookbook_names = %w{apache2 mysql foo bar dummy chef_handler} @base_url = "https://server.example.com/cookbooks" @cookbook_data = {} @cookbook_names.each do |item| @cookbook_data[item] = { "url" => "#{@base_url}/#{item}", "versions" => [{ "version" => "1.0.1", "url" => "#{@base_url}/#{item}/1.0.1" }] } end allow(@rest_double).to receive(:get).with("/cookbooks?num_versions=1").and_return(@cookbook_data) @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do it "should display only cookbooks with version constraints" do @knife.config[:format] = "summary" @knife.run @environments.each do |item, url| expect(@stdout.string).to(match /#{item}/) && expect(@stdout.string.lines.count).to(be 4) end end it "should display 4 number of lines" do @knife.config[:format] = "summary" @knife.run expect(@stdout.string.lines.count).to be 4 end end describe "with -m or --mismatch" do it "should display only cookbooks that have mismatching version constraints" do @knife.config[:format] = "summary" @knife.config[:mismatch] = true @knife.run @constraints.each do |item, ver| expect(@stdout.string).to match /#{ver[1]}/ end end it "should display 3 number of lines" do @knife.config[:format] = "summary" @knife.config[:mismatch] = true @knife.run expect(@stdout.string.lines.count).to be 3 end end describe "with -a or --all" do it "should display all cookbooks" do @knife.config[:format] = "summary" @knife.config[:all] = true @knife.run @constraints.each do |item, ver| expect(@stdout.string).to match /#{ver[1]}/ end end it "should display 8 number of lines" do @knife.config[:format] = "summary" @knife.config[:all] = true @knife.run expect(@stdout.string.lines.count).to be 8 end end end chef-12.14.60/spec/unit/knife/environment_create_spec.rb000066400000000000000000000053221276456504500231250ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::EnvironmentCreate do before(:each) do @knife = Chef::Knife::EnvironmentCreate.new allow(@knife).to receive(:msg).and_return true allow(@knife).to receive(:output).and_return true allow(@knife).to receive(:show_usage).and_return true @knife.name_args = [ "production" ] @environment = Chef::Environment.new allow(@environment).to receive(:save) allow(Chef::Environment).to receive(:new).and_return @environment allow(@knife).to receive(:edit_data).and_return @environment end describe "run" do it "should create a new environment" do expect(Chef::Environment).to receive(:new) @knife.run end it "should set the environment name" do expect(@environment).to receive(:name).with("production") @knife.run end it "should not print the environment" do expect(@knife).not_to receive(:output) @knife.run end it "should prompt you to edit the data" do expect(@knife).to receive(:edit_data).with(@environment, object_class: Chef::Environment) @knife.run end it "should save the environment" do expect(@environment).to receive(:save) @knife.run end it "should show usage and exit when no environment name is provided" do @knife.name_args = [ ] expect(@knife.ui).to receive(:fatal) expect(@knife).to receive(:show_usage) expect { @knife.run }.to raise_error(SystemExit) end describe "with --description" do before(:each) do @knife.config[:description] = "This is production" end it "should set the description" do expect(@environment).to receive(:description).with("This is production") @knife.run end end describe "with --print-after" do before(:each) do @knife.config[:print_after] = true end it "should pretty print the environment, formatted for display" do expect(@knife).to receive(:output).with(@environment) @knife.run end end end end chef-12.14.60/spec/unit/knife/environment_delete_spec.rb000066400000000000000000000043641276456504500231310ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::EnvironmentDelete do before(:each) do @knife = Chef::Knife::EnvironmentDelete.new allow(@knife).to receive(:msg).and_return true allow(@knife).to receive(:output).and_return true allow(@knife).to receive(:show_usage).and_return true allow(@knife).to receive(:confirm).and_return true @knife.name_args = [ "production" ] @environment = Chef::Environment.new @environment.name("production") @environment.description("Please delete me") allow(@environment).to receive(:destroy).and_return true allow(Chef::Environment).to receive(:load).and_return @environment end it "should confirm that you want to delete" do expect(@knife).to receive(:confirm) @knife.run end it "should load the environment" do expect(Chef::Environment).to receive(:load).with("production") @knife.run end it "should delete the environment" do expect(@environment).to receive(:destroy) @knife.run end it "should not print the environment" do expect(@knife).not_to receive(:output) @knife.run end it "should show usage and exit when no environment name is provided" do @knife.name_args = [] expect(@knife.ui).to receive(:fatal) expect(@knife).to receive(:show_usage) expect { @knife.run }.to raise_error(SystemExit) end describe "with --print-after" do it "should pretty print the environment, formatted for display" do @knife.config[:print_after] = true expect(@knife).to receive(:output).with(@environment) @knife.run end end end chef-12.14.60/spec/unit/knife/environment_edit_spec.rb000066400000000000000000000051211276456504500226040ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::EnvironmentEdit do before(:each) do @knife = Chef::Knife::EnvironmentEdit.new allow(@knife.ui).to receive(:msg).and_return true allow(@knife.ui).to receive(:output).and_return true allow(@knife.ui).to receive(:show_usage).and_return true @knife.name_args = [ "production" ] @environment = Chef::Environment.new @environment.name("production") @environment.description("Please edit me") allow(@environment).to receive(:save).and_return true allow(Chef::Environment).to receive(:load).and_return @environment allow(@knife.ui).to receive(:edit_data).and_return @environment end it "should load the environment" do expect(Chef::Environment).to receive(:load).with("production") @knife.run end it "should let you edit the environment" do expect(@knife.ui).to receive(:edit_data).with(@environment, object_class: Chef::Environment) @knife.run end it "should save the edited environment data" do pansy = Chef::Environment.new @environment.name("new_environment_name") expect(@knife.ui).to receive(:edit_data).with(@environment, object_class: Chef::Environment).and_return(pansy) expect(pansy).to receive(:save) @knife.run end it "should not save the unedited environment data" do expect(@environment).not_to receive(:save) @knife.run end it "should not print the environment" do expect(@knife).not_to receive(:output) @knife.run end it "shoud show usage and exit when no environment name is provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect { @knife.run }.to raise_error(SystemExit) end describe "with --print-after" do it "should pretty print the environment, formatted for display" do @knife.config[:print_after] = true expect(@knife.ui).to receive(:output).with(@environment) @knife.run end end end chef-12.14.60/spec/unit/knife/environment_from_file_spec.rb000066400000000000000000000061451276456504500236300ustar00rootroot00000000000000# # Author:: Stephen Delano () # Author:: Seth Falcon () # Copyright:: Copyright 2010-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" Chef::Knife::EnvironmentFromFile.load_deps describe Chef::Knife::EnvironmentFromFile do before(:each) do allow(ChefConfig).to receive(:windows?) { false } @knife = Chef::Knife::EnvironmentFromFile.new @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) @knife.name_args = [ "spec.rb" ] @environment = Chef::Environment.new @environment.name("spec") @environment.description("runs the unit tests") @environment.cookbook_versions({ "apt" => "= 1.2.3" }) allow(@environment).to receive(:save).and_return true allow(@knife.loader).to receive(:load_from).and_return @environment end describe "run" do it "loads the environment data from a file and saves it" do expect(@knife.loader).to receive(:load_from).with("environments", "spec.rb").and_return(@environment) expect(@environment).to receive(:save) @knife.run end context "when handling multiple environments" do before(:each) do @env_apple = @environment.dup @env_apple.name("apple") allow(@knife.loader).to receive(:load_from).with("apple.rb").and_return @env_apple end it "loads multiple environments if given" do @knife.name_args = [ "spec.rb", "apple.rb" ] expect(@environment).to receive(:save).twice @knife.run end it "loads all environments with -a" do allow(File).to receive(:expand_path).with("./environments/").and_return("/tmp/environments") allow(Dir).to receive(:glob).with("/tmp/environments/*.{json,rb}").and_return(["spec.rb", "apple.rb"]) @knife.name_args = [] allow(@knife).to receive(:config).and_return({ :all => true }) expect(@environment).to receive(:save).twice @knife.run end end it "should not print the environment" do expect(@knife).not_to receive(:output) @knife.run end it "should show usage and exit if not filename is provided" do @knife.name_args = [] expect(@knife.ui).to receive(:fatal) expect(@knife).to receive(:show_usage) expect { @knife.run }.to raise_error(SystemExit) end describe "with --print-after" do it "should pretty print the environment, formatted for display" do @knife.config[:print_after] = true expect(@knife).to receive(:output) @knife.run end end end end chef-12.14.60/spec/unit/knife/environment_list_spec.rb000066400000000000000000000035131276456504500226350ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::EnvironmentList do before(:each) do @knife = Chef::Knife::EnvironmentList.new allow(@knife).to receive(:msg).and_return true allow(@knife).to receive(:output).and_return true allow(@knife).to receive(:show_usage).and_return true @environments = { "production" => "http://localhost:4000/environments/production", "development" => "http://localhost:4000/environments/development", "testing" => "http://localhost:4000/environments/testing", } allow(Chef::Environment).to receive(:list).and_return @environments end it "should make an api call to list the environments" do expect(Chef::Environment).to receive(:list) @knife.run end it "should print the environment names in a sorted list" do names = @environments.keys.sort { |a, b| a <=> b } expect(@knife).to receive(:output).with(names) @knife.run end describe "with --with-uri" do it "should print and unsorted list of the environments and their URIs" do @knife.config[:with_uri] = true expect(@knife).to receive(:output).with(@environments) @knife.run end end end chef-12.14.60/spec/unit/knife/environment_show_spec.rb000066400000000000000000000034111276456504500226370ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::EnvironmentShow do before(:each) do @knife = Chef::Knife::EnvironmentShow.new allow(@knife).to receive(:msg).and_return true allow(@knife).to receive(:output).and_return true allow(@knife).to receive(:show_usage).and_return true @knife.name_args = [ "production" ] @environment = Chef::Environment.new @environment.name("production") @environment.description("Look at me!") allow(Chef::Environment).to receive(:load).and_return @environment end it "should load the environment" do expect(Chef::Environment).to receive(:load).with("production") @knife.run end it "should pretty print the environment, formatted for display" do expect(@knife).to receive(:format_for_display).with(@environment) expect(@knife).to receive(:output) @knife.run end it "should show usage and exit when no environment name is provided" do @knife.name_args = [] expect(@knife.ui).to receive(:fatal) expect(@knife).to receive(:show_usage) expect { @knife.run }.to raise_error(SystemExit) end end chef-12.14.60/spec/unit/knife/index_rebuild_spec.rb000066400000000000000000000074301276456504500220550ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::IndexRebuild do let(:knife) { Chef::Knife::IndexRebuild.new } let(:rest_client) { double(Chef::ServerAPI) } let(:stub_rest!) do expect(knife).to receive(:rest).and_return(rest_client) end before :each do # This keeps the test output clean allow(knife.ui).to receive(:stdout).and_return(StringIO.new) end context "#grab_api_info" do let(:http_not_found_response) do e = Net::HTTPNotFound.new("1.1", 404, "blah") allow(e).to receive(:[]).with("x-ops-api-info").and_return(api_header_value) e end let(:http_server_exception) do Net::HTTPServerException.new("404: Not Found", http_not_found_response) end before(:each) do stub_rest! allow(rest_client).to receive(:get).and_raise(http_server_exception) end context "against a Chef 11 server" do let(:api_header_value) { "flavor=osc;version=11.0.0;erchef=1.2.3" } it "retrieves API information" do expect(knife.grab_api_info).to eq({ "flavor" => "osc", "version" => "11.0.0", "erchef" => "1.2.3" }) end end # Chef 11 context "against a Chef 10 server" do let(:api_header_value) { nil } it "finds no API information" do expect(knife.grab_api_info).to eq({}) end end # Chef 10 end # grab_api_info context "#unsupported_version?" do context "with Chef 11 API metadata" do it "is unsupported" do expect(knife.unsupported_version?({ "version" => "11.0.0", "flavor" => "osc", "erchef" => "1.2.3" })).to be_truthy end it "only truly relies on the version being non-nil" do expect(knife.unsupported_version?({ "version" => "1", "flavor" => "osc", "erchef" => "1.2.3" })).to be_truthy end end context "with Chef 10 API metadata" do it "is supported" do # Chef 10 will have no metadata expect(knife.unsupported_version?({})).to be_falsey end end end # unsupported_version? context "Simulating a 'knife index rebuild' run" do before :each do expect(knife).to receive(:grab_api_info).and_return(api_info) server_specific_stubs! end context "against a Chef 11 server" do let(:api_info) do { "flavor" => "osc", "version" => "11.0.0", "erchef" => "1.2.3", } end let(:server_specific_stubs!) do expect(knife).to receive(:unsupported_server_message).with(api_info) expect(knife).to receive(:exit).with(1) end it "should not be allowed" do knife.run end end context "against a Chef 10 server" do let(:api_info) { {} } let(:server_specific_stubs!) do stub_rest! expect(rest_client).to receive(:post).with("/search/reindex", {}).and_return("representative output") expect(knife).not_to receive(:unsupported_server_message) expect(knife).to receive(:deprecated_server_message) expect(knife).to receive(:nag) expect(knife).to receive(:output).with("representative output") end it "should be allowed" do knife.run end end end end chef-12.14.60/spec/unit/knife/key_create_spec.rb000066400000000000000000000167651276456504500213660ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/knife/user_key_create" require "chef/knife/client_key_create" require "chef/knife/key_create" require "chef/key" describe "key create commands that inherit knife" do shared_examples_for "a key create command" do let(:stderr) { StringIO.new } let(:params) { [] } let(:service_object) { instance_double(Chef::Knife::KeyCreate) } let(:command) do c = described_class.new([]) c.ui.config[:disable_editing] = true allow(c.ui).to receive(:stderr).and_return(stderr) allow(c.ui).to receive(:stdout).and_return(stderr) allow(c).to receive(:show_usage) c end context "after apply_params! is called with valid args" do let(:params) { ["charmander"] } before do command.apply_params!(params) end context "when the service object is called" do it "creates a new instance of Chef::Knife::KeyCreate with the correct args" do expect(Chef::Knife::KeyCreate).to receive(:new). with("charmander", command.actor_field_name, command.ui, command.config). and_return(service_object) command.service_object end end # when the service object is called end # after apply_params! is called with valid args end # a key create command describe Chef::Knife::UserKeyCreate do it_should_behave_like "a key create command" # defined in key_helper.rb it_should_behave_like "a knife key command" do let(:service_object) { instance_double(Chef::Knife::KeyCreate) } let(:params) { ["charmander"] } end end describe Chef::Knife::ClientKeyCreate do it_should_behave_like "a key create command" # defined in key_helper.rb it_should_behave_like "a knife key command" do let(:service_object) { instance_double(Chef::Knife::KeyCreate) } let(:params) { ["charmander"] } end end end describe Chef::Knife::KeyCreate do let(:public_key) do "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02 KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo 0wIDAQAB -----END PUBLIC KEY-----" end let(:config) { Hash.new } let(:actor) { "charmander" } let(:ui) { instance_double("Chef::Knife::UI") } shared_examples_for "key create run command" do let(:key_create_object) do described_class.new(actor, actor_field_name, ui, config) end context "when public_key and key_name weren't passed" do it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do expect { key_create_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_create_object.public_key_or_key_name_error_msg) end end context "when the command is run" do let(:expected_hash) do { actor_field_name => "charmander", } end before do allow(File).to receive(:read).and_return(public_key) allow(File).to receive(:expand_path) allow(key_create_object).to receive(:output_private_key_to_file) allow(key_create_object).to receive(:display_private_key) allow(key_create_object).to receive(:edit_hash).and_return(expected_hash) allow(key_create_object).to receive(:create_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) allow(key_create_object).to receive(:display_info) end context "when a valid hash is passed" do let(:key_name) { "charmander-key" } let(:valid_expiration_date) { "2020-12-24T21:00:00Z" } let(:expected_hash) do { actor_field_name => "charmander", "public_key" => public_key, "expiration_date" => valid_expiration_date, "key_name" => key_name, } end before do key_create_object.config[:public_key] = "public_key_path" key_create_object.config[:expiration_Date] = valid_expiration_date, key_create_object.config[:key_name] = key_name end it "creates the proper hash" do expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash) key_create_object.run end end context "when public_key is passed" do let(:expected_hash) do { actor_field_name => "charmander", "public_key" => public_key, } end before do key_create_object.config[:public_key] = "public_key_path" end it "calls File.expand_path with the public_key input" do expect(File).to receive(:expand_path).with("public_key_path") key_create_object.run end end # when public_key is passed context "when public_key isn't passed and key_name is" do let(:expected_hash) do { actor_field_name => "charmander", "name" => "charmander-key", "create_key" => true, } end before do key_create_object.config[:key_name] = "charmander-key" end it "should set create_key to true" do expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash) key_create_object.run end end context "when the server returns a private key" do let(:expected_hash) do { actor_field_name => "charmander", "public_key" => public_key, "private_key" => "super_private", } end before do key_create_object.config[:public_key] = "public_key_path" end context "when file is not passed" do it "calls display_private_key with the private_key" do expect(key_create_object).to receive(:display_private_key).with("super_private") key_create_object.run end end context "when file is passed" do before do key_create_object.config[:file] = "/fake/file" end it "calls output_private_key_to_file with the private_key" do expect(key_create_object).to receive(:output_private_key_to_file).with("super_private") key_create_object.run end end end # when the server returns a private key end # when the command is run end #key create run command" context "when actor_field_name is 'user'" do it_should_behave_like "key create run command" do let(:actor_field_name) { "user" } end end context "when actor_field_name is 'client'" do it_should_behave_like "key create run command" do let(:actor_field_name) { "client" } end end end chef-12.14.60/spec/unit/knife/key_delete_spec.rb000066400000000000000000000107111276456504500213460ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/knife/user_key_delete" require "chef/knife/client_key_delete" require "chef/knife/key_delete" require "chef/key" describe "key delete commands that inherit knife" do shared_examples_for "a key delete command" do let(:stderr) { StringIO.new } let(:params) { [] } let(:service_object) { instance_double(Chef::Knife::KeyDelete) } let(:command) do c = described_class.new([]) c.ui.config[:disable_editing] = true allow(c.ui).to receive(:stderr).and_return(stderr) allow(c.ui).to receive(:stdout).and_return(stderr) allow(c).to receive(:show_usage) c end context "after apply_params! is called with valid args" do let(:params) { ["charmander", "charmander-key"] } before do command.apply_params!(params) end context "when the service object is called" do it "creates a new instance of Chef::Knife::KeyDelete with the correct args" do expect(Chef::Knife::KeyDelete).to receive(:new). with("charmander-key", "charmander", command.actor_field_name, command.ui). and_return(service_object) command.service_object end end # when the service object is called end # after apply_params! is called with valid args end # a key delete command describe Chef::Knife::UserKeyDelete do it_should_behave_like "a key delete command" # defined in key_helpers.rb it_should_behave_like "a knife key command with a keyname as the second arg" it_should_behave_like "a knife key command" do let(:service_object) { instance_double(Chef::Knife::KeyDelete) } let(:params) { ["charmander", "charmander-key"] } end end describe Chef::Knife::ClientKeyDelete do it_should_behave_like "a key delete command" # defined in key_helpers.rb it_should_behave_like "a knife key command with a keyname as the second arg" it_should_behave_like "a knife key command" do let(:service_object) { instance_double(Chef::Knife::KeyDelete) } let(:params) { ["charmander", "charmander-key"] } end end end describe Chef::Knife::KeyDelete do let(:actor) { "charmander" } let(:keyname) { "charmander-key" } let(:ui) { instance_double("Chef::Knife::UI") } shared_examples_for "key delete run command" do let(:key_delete_object) do described_class.new(keyname, actor, actor_field_name, ui) end before do allow_any_instance_of(Chef::Key).to receive(:destroy) allow(key_delete_object).to receive(:print_destroyed) allow(key_delete_object).to receive(:confirm!) end context "when the command is run" do it "calls Chef::Key.new with the proper input" do expect(Chef::Key).to receive(:new).with(actor, actor_field_name).and_call_original key_delete_object.run end it "calls name on the Chef::Key instance with the proper input" do expect_any_instance_of(Chef::Key).to receive(:name).with(keyname) key_delete_object.run end it "calls destroy on the Chef::Key instance" do expect_any_instance_of(Chef::Key).to receive(:destroy).once key_delete_object.run end it "calls confirm!" do expect(key_delete_object).to receive(:confirm!) key_delete_object.run end it "calls print_destroyed" do expect(key_delete_object).to receive(:print_destroyed) key_delete_object.run end end # when the command is run end # key delete run command context "when actor_field_name is 'user'" do it_should_behave_like "key delete run command" do let(:actor_field_name) { "user" } end end context "when actor_field_name is 'client'" do it_should_behave_like "key delete run command" do let(:actor_field_name) { "client" } end end end chef-12.14.60/spec/unit/knife/key_edit_spec.rb000066400000000000000000000224031276456504500210320ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/knife/user_key_edit" require "chef/knife/client_key_edit" require "chef/knife/key_edit" require "chef/key" describe "key edit commands that inherit knife" do shared_examples_for "a key edit command" do let(:stderr) { StringIO.new } let(:params) { [] } let(:service_object) { instance_double(Chef::Knife::KeyEdit) } let(:command) do c = described_class.new([]) c.ui.config[:disable_editing] = true allow(c.ui).to receive(:stderr).and_return(stderr) allow(c.ui).to receive(:stdout).and_return(stderr) allow(c).to receive(:show_usage) c end context "after apply_params! is called with valid args" do let(:params) { ["charmander", "charmander-key"] } before do command.apply_params!(params) end context "when the service object is called" do it "creates a new instance of Chef::Knife::KeyEdit with the correct args" do expect(Chef::Knife::KeyEdit).to receive(:new). with("charmander-key", "charmander", command.actor_field_name, command.ui, command.config). and_return(service_object) command.service_object end end # when the service object is called end # after apply_params! is called with valid args end # a key edit command describe Chef::Knife::UserKeyEdit do it_should_behave_like "a key edit command" # defined in key_helpers.rb it_should_behave_like "a knife key command with a keyname as the second arg" it_should_behave_like "a knife key command" do let(:service_object) { instance_double(Chef::Knife::KeyEdit) } let(:params) { ["charmander", "charmander-key"] } end end describe Chef::Knife::ClientKeyEdit do it_should_behave_like "a key edit command" # defined in key_helpers.rb it_should_behave_like "a knife key command with a keyname as the second arg" it_should_behave_like "a knife key command" do let(:service_object) { instance_double(Chef::Knife::KeyEdit) } let(:params) { ["charmander", "charmander-key"] } end end end describe Chef::Knife::KeyEdit do let(:public_key) do "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02 KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo 0wIDAQAB -----END PUBLIC KEY-----" end let(:config) { Hash.new } let(:actor) { "charmander" } let(:keyname) { "charmander-key" } let(:ui) { instance_double("Chef::Knife::UI") } shared_examples_for "key edit run command" do let(:key_edit_object) do described_class.new(keyname, actor, actor_field_name, ui, config) end context "when the command is run" do let(:expected_hash) do { actor_field_name => "charmander", } end let(:new_keyname) { "charizard-key" } before do allow(File).to receive(:read).and_return(public_key) allow(File).to receive(:expand_path) allow(key_edit_object).to receive(:output_private_key_to_file) allow(key_edit_object).to receive(:display_private_key) allow(key_edit_object).to receive(:edit_hash).and_return(expected_hash) allow(key_edit_object).to receive(:display_info) end context "when public_key and create_key are passed" do before do key_edit_object.config[:public_key] = "public_key_path" key_edit_object.config[:create_key] = true end it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do expect { key_edit_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_edit_object.public_key_and_create_key_error_msg) end end context "when key_name is passed" do let(:expected_hash) do { actor_field_name => "charmander", "name" => new_keyname, } end before do key_edit_object.config[:key_name] = new_keyname allow_any_instance_of(Chef::Key).to receive(:update) end it "update_key_from_hash gets passed a hash with new key name" do expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash).and_return(Chef::Key.from_hash(expected_hash)) key_edit_object.run end it "Chef::Key.update is passed a string containing the original keyname" do expect_any_instance_of(Chef::Key).to receive(:update).with(/#{keyname}/).and_return(Chef::Key.from_hash(expected_hash)) key_edit_object.run end it "Chef::Key.update is not passed a string containing the new keyname" do expect_any_instance_of(Chef::Key).not_to receive(:update).with(/#{new_keyname}/) allow_any_instance_of(Chef::Key).to receive(:update).and_return(Chef::Key.from_hash(expected_hash)) key_edit_object.run end end context "when public_key, key_name, and expiration_date are passed" do let(:expected_hash) do { actor_field_name => "charmander", "public_key" => public_key, "name" => new_keyname, "expiration_date" => "infinity", } end before do key_edit_object.config[:public_key] = "this-public-key" key_edit_object.config[:key_name] = new_keyname key_edit_object.config[:expiration_date] = "infinity" allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) end it "passes the right hash to update_key_from_hash" do expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash) key_edit_object.run end end context "when create_key is passed" do let(:expected_hash) do { actor_field_name => "charmander", "create_key" => true, } end before do key_edit_object.config[:create_key] = true allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) end it "passes the right hash to update_key_from_hash" do expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash) key_edit_object.run end end context "when public_key is passed" do let(:expected_hash) do { actor_field_name => "charmander", "public_key" => public_key, } end before do allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) key_edit_object.config[:public_key] = "public_key_path" end it "calls File.expand_path with the public_key input" do expect(File).to receive(:expand_path).with("public_key_path") key_edit_object.run end end # when public_key is passed context "when the server returns a private key" do let(:expected_hash) do { actor_field_name => "charmander", "public_key" => public_key, "private_key" => "super_private", } end before do allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash)) key_edit_object.config[:public_key] = "public_key_path" end context "when file is not passed" do it "calls display_private_key with the private_key" do expect(key_edit_object).to receive(:display_private_key).with("super_private") key_edit_object.run end end context "when file is passed" do before do key_edit_object.config[:file] = "/fake/file" end it "calls output_private_key_to_file with the private_key" do expect(key_edit_object).to receive(:output_private_key_to_file).with("super_private") key_edit_object.run end end end # when the server returns a private key end # when the command is run end # key edit run command context "when actor_field_name is 'user'" do it_should_behave_like "key edit run command" do let(:actor_field_name) { "user" } end end context "when actor_field_name is 'client'" do it_should_behave_like "key edit run command" do let(:actor_field_name) { "client" } end end end chef-12.14.60/spec/unit/knife/key_helper.rb000066400000000000000000000044531276456504500203570ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" shared_examples_for "a knife key command" do let(:stderr) { StringIO.new } let(:params) { [] } let(:command) do c = described_class.new([]) c.ui.config[:disable_editing] = true allow(c.ui).to receive(:stderr).and_return(stderr) allow(c.ui).to receive(:stdout).and_return(stderr) allow(c).to receive(:show_usage) c end context "before apply_params! is called" do context "when apply_params! is called with invalid args" do it "shows the usage" do expect(command).to receive(:show_usage) expect { command.apply_params!(params) }.to exit_with_code(1) end it "outputs the proper error" do expect { command.apply_params!(params) }.to exit_with_code(1) expect(stderr.string).to include(command.actor_missing_error) end it "exits 1" do expect { command.apply_params!(params) }.to exit_with_code(1) end end end # before apply_params! is called context "after apply_params! is called with valid args" do let(:params) { ["charmander"] } before do command.apply_params!(params) end it "properly defines the actor" do expect(command.actor).to eq("charmander") end end # after apply_params! is called with valid args context "when the command is run" do before do allow(command).to receive(:service_object).and_return(service_object) allow(command).to receive(:name_args).and_return(["charmander"]) end context "when the command is successful" do before do expect(service_object).to receive(:run) end end end end # a knife key command chef-12.14.60/spec/unit/knife/key_list_spec.rb000066400000000000000000000171341276456504500210650ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/knife/user_key_list" require "chef/knife/client_key_list" require "chef/knife/key_list" require "chef/key" describe "key list commands that inherit knife" do shared_examples_for "a key list command" do let(:stderr) { StringIO.new } let(:params) { [] } let(:service_object) { instance_double(Chef::Knife::KeyList) } let(:command) do c = described_class.new([]) c.ui.config[:disable_editing] = true allow(c.ui).to receive(:stderr).and_return(stderr) allow(c.ui).to receive(:stdout).and_return(stderr) allow(c).to receive(:show_usage) c end context "after apply_params! is called with valid args" do let(:params) { ["charmander"] } before do command.apply_params!(params) end context "when the service object is called" do it "creates a new instance of Chef::Knife::KeyList with the correct args" do expect(Chef::Knife::KeyList).to receive(:new). with("charmander", command.list_method, command.ui, command.config). and_return(service_object) command.service_object end end # when the service object is called end # after apply_params! is called with valid args end # a key list command describe Chef::Knife::UserKeyList do it_should_behave_like "a key list command" # defined in key_helpers.rb it_should_behave_like "a knife key command" do let(:service_object) { instance_double(Chef::Knife::KeyList) } let(:params) { ["charmander"] } end end describe Chef::Knife::ClientKeyList do it_should_behave_like "a key list command" # defined in key_helpers.rb it_should_behave_like "a knife key command" do let(:service_object) { instance_double(Chef::Knife::KeyList) } let(:params) { ["charmander"] } end end end describe Chef::Knife::KeyList do let(:config) { Hash.new } let(:actor) { "charmander" } let(:ui) { instance_double("Chef::Knife::UI") } shared_examples_for "key list run command" do let(:key_list_object) do described_class.new(actor, list_method, ui, config) end before do allow(Chef::Key).to receive(list_method).and_return(http_response) allow(key_list_object).to receive(:display_info) # simply pass the string though that colorize takes in allow(key_list_object).to receive(:colorize).with(kind_of(String)) do |input| input end end context "when only_expired and only_non_expired were both passed" do before do key_list_object.config[:only_expired] = true key_list_object.config[:only_non_expired] = true end it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do expect { key_list_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_list_object.expired_and_non_expired_msg) end end context "when the command is run" do before do key_list_object.config[:only_expired] = false key_list_object.config[:only_non_expired] = false key_list_object.config[:with_details] = false end it "calls Chef::Key with the proper list command and input" do expect(Chef::Key).to receive(list_method).with(actor) key_list_object.run end it "displays all the keys" do expect(key_list_object).to receive(:display_info).with(/non-expired/).twice expect(key_list_object).to receive(:display_info).with(/out-of-date/).once key_list_object.run end context "when only_expired is called" do before do key_list_object.config[:only_expired] = true end it "excludes displaying non-expired keys" do expect(key_list_object).to receive(:display_info).with(/non-expired/).exactly(0).times key_list_object.run end it "displays the expired keys" do expect(key_list_object).to receive(:display_info).with(/out-of-date/).once key_list_object.run end end # when only_expired is called context "when only_non_expired is called" do before do key_list_object.config[:only_non_expired] = true end it "excludes displaying expired keys" do expect(key_list_object).to receive(:display_info).with(/out-of-date/).exactly(0).times key_list_object.run end it "displays the non-expired keys" do expect(key_list_object).to receive(:display_info).with(/non-expired/).twice key_list_object.run end end # when only_expired is called context "when with_details is false" do before do key_list_object.config[:with_details] = false end it "does not display the uri" do expect(key_list_object).to receive(:display_info).with(/https/).exactly(0).times key_list_object.run end it "does not display the expired status" do expect(key_list_object).to receive(:display_info).with(/\(expired\)/).exactly(0).times key_list_object.run end end # when with_details is false context "when with_details is true" do before do key_list_object.config[:with_details] = true end it "displays the uri" do expect(key_list_object).to receive(:display_info).with(/https/).exactly(3).times key_list_object.run end it "displays the expired status" do expect(key_list_object).to receive(:display_info).with(/\(expired\)/).once key_list_object.run end end # when with_details is true end # when the command is run end # key list run command context "when list_method is :list_by_user" do it_should_behave_like "key list run command" do let(:list_method) { :list_by_user } let(:http_response) do [ { "uri" => "https://api.opscode.piab/users/charmander/keys/non-expired1", "name" => "non-expired1", "expired" => false }, { "uri" => "https://api.opscode.piab/users/charmander/keys/non-expired2", "name" => "non-expired2", "expired" => false }, { "uri" => "https://api.opscode.piab/users/mary/keys/out-of-date", "name" => "out-of-date", "expired" => true }, ] end end end context "when list_method is :list_by_client" do it_should_behave_like "key list run command" do let(:list_method) { :list_by_client } let(:http_response) do [ { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired1", "name" => "non-expired1", "expired" => false }, { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired2", "name" => "non-expired2", "expired" => false }, { "uri" => "https://api.opscode.piab/organizations/pokemon/clients/mary/keys/out-of-date", "name" => "out-of-date", "expired" => true }, ] end end end end chef-12.14.60/spec/unit/knife/key_show_spec.rb000066400000000000000000000101561276456504500210670ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/knife/user_key_show" require "chef/knife/client_key_show" require "chef/knife/key_show" require "chef/key" describe "key show commands that inherit knife" do shared_examples_for "a key show command" do let(:stderr) { StringIO.new } let(:params) { [] } let(:service_object) { instance_double(Chef::Knife::KeyShow) } let(:command) do c = described_class.new([]) c.ui.config[:disable_editing] = true allow(c.ui).to receive(:stderr).and_return(stderr) allow(c.ui).to receive(:stdout).and_return(stderr) allow(c).to receive(:show_usage) c end context "after apply_params! is called with valid args" do let(:params) { ["charmander", "charmander-key"] } before do command.apply_params!(params) end context "when the service object is called" do it "creates a new instance of Chef::Knife::KeyShow with the correct args" do expect(Chef::Knife::KeyShow).to receive(:new). with("charmander-key", "charmander", command.load_method, command.ui). and_return(service_object) command.service_object end end # when the service object is called end # after apply_params! is called with valid args end # a key show command describe Chef::Knife::UserKeyShow do it_should_behave_like "a key show command" # defined in key_helpers.rb it_should_behave_like "a knife key command with a keyname as the second arg" it_should_behave_like "a knife key command" do let(:service_object) { instance_double(Chef::Knife::KeyShow) } let(:params) { ["charmander", "charmander-key"] } end end describe Chef::Knife::ClientKeyShow do it_should_behave_like "a key show command" # defined in key_helpers.rb it_should_behave_like "a knife key command with a keyname as the second arg" it_should_behave_like "a knife key command" do let(:service_object) { instance_double(Chef::Knife::KeyShow) } let(:params) { ["charmander", "charmander-key"] } end end end describe Chef::Knife::KeyShow do let(:actor) { "charmander" } let(:keyname) { "charmander" } let(:ui) { instance_double("Chef::Knife::UI") } let(:expected_hash) do { actor_field_name => "charmander", "name" => "charmander-key", "public_key" => "some-public-key", "expiration_date" => "infinity", } end shared_examples_for "key show run command" do let(:key_show_object) do described_class.new(keyname, actor, load_method, ui) end before do allow(key_show_object).to receive(:display_output) allow(Chef::Key).to receive(load_method).and_return(Chef::Key.from_hash(expected_hash)) end context "when the command is run" do it "loads the key using the proper method and args" do expect(Chef::Key).to receive(load_method).with(actor, keyname) key_show_object.run end it "displays the key" do expect(key_show_object).to receive(:display_output) key_show_object.run end end end context "when load_method is :load_by_user" do it_should_behave_like "key show run command" do let(:load_method) { :load_by_user } let(:actor_field_name) { "user" } end end context "when load_method is :load_by_client" do it_should_behave_like "key show run command" do let(:load_method) { :load_by_client } let(:actor_field_name) { "user" } end end end chef-12.14.60/spec/unit/knife/knife_help.rb000066400000000000000000000060601276456504500203300ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::Help do before(:each) do # Perilously use the build in list even though it is dynamic so we don't get warnings about the constant # HELP_TOPICS = [ "foo", "bar", "knife-kittens", "ceiling-cat", "shell" ] @knife = Chef::Knife::Help.new end it "should return a list of help topics" do expect(@knife.help_topics).to include("knife-status") end it "should run man for you" do @knife.name_args = [ "shell" ] expect(@knife).to receive(:exec).with(/^man \/.*\/shell.1$/) @knife.run end it "should suggest topics" do @knife.name_args = [ "list" ] allow(@knife.ui).to receive(:msg) expect(@knife.ui).to receive(:info).with("Available help topics are: ") expect(@knife.ui).to receive(:msg).with(/knife/) allow(@knife).to receive(:exec) expect(@knife).to receive(:exit).with(1) @knife.run end describe "find_manpage_path" do it "should find the man page in the gem" do expect(@knife.find_manpage_path("shell")).to match(/distro\/common\/man\/man1\/chef-shell.1$/) end it "should provide the man page name if not in the gem" do expect(@knife.find_manpage_path("foo")).to eq("foo") end end describe "find_manpages_for_query" do it "should error if it does not find a match" do allow(@knife.ui).to receive(:error) allow(@knife.ui).to receive(:info) allow(@knife.ui).to receive(:msg) expect(@knife).to receive(:exit).with(1) expect(@knife.ui).to receive(:error).with("No help found for 'chickens'") expect(@knife.ui).to receive(:msg).with(/knife/) @knife.find_manpages_for_query("chickens") end end describe "print_help_topics" do it "should print the known help topics" do allow(@knife.ui).to receive(:msg) allow(@knife.ui).to receive(:info) expect(@knife.ui).to receive(:msg).with(/knife/) @knife.print_help_topics end it "should shorten topics prefixed by knife-" do allow(@knife.ui).to receive(:msg) allow(@knife.ui).to receive(:info) expect(@knife.ui).to receive(:msg).with(/node/) @knife.print_help_topics end it "should not leave topics prefixed by knife-" do allow(@knife.ui).to receive(:msg) allow(@knife.ui).to receive(:info) expect(@knife.ui).not_to receive(:msg).with(/knife-node/) @knife.print_help_topics end end end chef-12.14.60/spec/unit/knife/node_bulk_delete_spec.rb000066400000000000000000000060151276456504500225220ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Knife::NodeBulkDelete do before(:each) do Chef::Log.logger = Logger.new(StringIO.new) Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::NodeBulkDelete.new @knife.name_args = ["."] @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) allow(@knife.ui).to receive(:confirm).and_return(true) @nodes = Hash.new %w{adam brent jacob}.each do |node_name| @nodes[node_name] = "http://localhost:4000/nodes/#{node_name}" end end describe "when creating the list of nodes" do it "fetches the node list" do expected = @nodes.inject({}) do |inflatedish, (name, uri)| inflatedish[name] = Chef::Node.new.tap { |n| n.name(name) } inflatedish end expect(Chef::Node).to receive(:list).and_return(@nodes) # I hate not having == defined for anything :( actual = @knife.all_nodes expect(actual.keys).to match_array(expected.keys) expect(actual.values.map { |n| n.name }).to match_array(%w{adam brent jacob}) end end describe "run" do before do @inflatedish_list = @nodes.keys.inject({}) do |nodes_by_name, name| node = Chef::Node.new() node.name(name) allow(node).to receive(:destroy).and_return(true) nodes_by_name[name] = node nodes_by_name end allow(@knife).to receive(:all_nodes).and_return(@inflatedish_list) end it "should print the nodes you are about to delete" do @knife.run expect(@stdout.string).to match(/#{@knife.ui.list(@nodes.keys.sort, :columns_down)}/) end it "should confirm you really want to delete them" do expect(@knife.ui).to receive(:confirm) @knife.run end it "should delete each node" do @inflatedish_list.each_value do |n| expect(n).to receive(:destroy) end @knife.run end it "should only delete nodes that match the regex" do @knife.name_args = ["adam"] expect(@inflatedish_list["adam"]).to receive(:destroy) expect(@inflatedish_list["brent"]).not_to receive(:destroy) expect(@inflatedish_list["jacob"]).not_to receive(:destroy) @knife.run end it "should exit if the regex is not provided" do @knife.name_args = [] expect { @knife.run }.to raise_error(SystemExit) end end end chef-12.14.60/spec/unit/knife/node_delete_spec.rb000066400000000000000000000041461276456504500215100ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Knife::NodeDelete do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::NodeDelete.new @knife.config = { :print_after => nil, } @knife.name_args = [ "adam" ] allow(@knife).to receive(:output).and_return(true) allow(@knife).to receive(:confirm).and_return(true) @node = Chef::Node.new() allow(@node).to receive(:destroy).and_return(true) allow(Chef::Node).to receive(:load).and_return(@node) @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do it "should confirm that you want to delete" do expect(@knife).to receive(:confirm) @knife.run end it "should load the node" do expect(Chef::Node).to receive(:load).with("adam").and_return(@node) @knife.run end it "should delete the node" do expect(@node).to receive(:destroy).and_return(@node) @knife.run end it "should not print the node" do expect(@knife).not_to receive(:output).with("poop") @knife.run end describe "with -p or --print-after" do it "should pretty print the node, formatted for display" do @knife.config[:print_after] = true expect(@knife).to receive(:format_for_display).with(@node).and_return("poop") expect(@knife).to receive(:output).with("poop") @knife.run end end end end chef-12.14.60/spec/unit/knife/node_edit_spec.rb000066400000000000000000000071261276456504500211740ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" Chef::Knife::NodeEdit.load_deps describe Chef::Knife::NodeEdit do # helper to convert the view from Chef objects into Ruby objects representing JSON def deserialized_json_view actual = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json_pretty(@knife.node_editor.send(:view))) end before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::NodeEdit.new @knife.config = { :editor => "cat", :attribute => nil, :print_after => nil, } @knife.name_args = [ "adam" ] @node = Chef::Node.new() end it "should load the node" do expect(Chef::Node).to receive(:load).with("adam").and_return(@node) @knife.node end describe "after loading the node" do before do allow(@knife).to receive(:node).and_return(@node) @node.automatic_attrs = { :go => :away } @node.default_attrs = { :hide => :me } @node.override_attrs = { :dont => :show } @node.normal_attrs = { :do_show => :these } @node.chef_environment("prod") @node.run_list("recipe[foo]") end it "creates a view of the node without attributes from roles or ohai" do actual = deserialized_json_view expect(actual).not_to have_key("automatic") expect(actual).not_to have_key("override") expect(actual).not_to have_key("default") expect(actual["normal"]).to eq({ "do_show" => "these" }) expect(actual["run_list"]).to eq(["recipe[foo]"]) expect(actual["chef_environment"]).to eq("prod") end it "shows the extra attributes when given the --all option" do @knife.config[:all_attributes] = true actual = deserialized_json_view expect(actual["automatic"]).to eq({ "go" => "away" }) expect(actual["override"]).to eq({ "dont" => "show" }) expect(actual["default"]).to eq({ "hide" => "me" }) expect(actual["normal"]).to eq({ "do_show" => "these" }) expect(actual["run_list"]).to eq(["recipe[foo]"]) expect(actual["chef_environment"]).to eq("prod") end it "does not consider unedited data updated" do view = deserialized_json_view @knife.node_editor.send(:apply_updates, view) expect(@knife.node_editor).not_to be_updated end it "considers edited data updated" do view = deserialized_json_view view["run_list"] << "role[fuuu]" @knife.node_editor.send(:apply_updates, view) expect(@knife.node_editor).to be_updated end end describe "edit_node" do before do allow(@knife).to receive(:node).and_return(@node) end let(:subject) { @knife.node_editor.edit_node } it "raises an exception when editing is disabled" do @knife.config[:disable_editing] = true expect { subject }.to raise_error(SystemExit) end it "raises an exception when the editor is not set" do @knife.config[:editor] = nil expect { subject }.to raise_error(SystemExit) end end end chef-12.14.60/spec/unit/knife/node_environment_set_spec.rb000066400000000000000000000032121276456504500234560ustar00rootroot00000000000000# # Author:: Jimmy McCrory () # Copyright:: Copyright 2014-2016, Jimmy McCrory # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::NodeEnvironmentSet do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::NodeEnvironmentSet.new @knife.name_args = %w{adam bar} allow(@knife).to receive(:output).and_return(true) @node = Chef::Node.new() @node.name("knifetest-node") @node.chef_environment << "foo" allow(@node).to receive(:save).and_return(true) allow(Chef::Node).to receive(:load).and_return(@node) end describe "run" do it "should load the node" do expect(Chef::Node).to receive(:load).with("adam") @knife.run end it "should update the environment" do @knife.run expect(@node.chef_environment).to eq("bar") end it "should save the node" do expect(@node).to receive(:save) @knife.run end it "should print the environment" do expect(@knife).to receive(:output).and_return(true) @knife.run end end end chef-12.14.60/spec/unit/knife/node_from_file_spec.rb000066400000000000000000000034401276456504500222040ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" Chef::Knife::NodeFromFile.load_deps describe Chef::Knife::NodeFromFile do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::NodeFromFile.new @knife.config = { :print_after => nil, } @knife.name_args = [ "adam.rb" ] allow(@knife).to receive(:output).and_return(true) allow(@knife).to receive(:confirm).and_return(true) @node = Chef::Node.new() allow(@node).to receive(:save) allow(@knife.loader).to receive(:load_from).and_return(@node) @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do it "should load from a file" do expect(@knife.loader).to receive(:load_from).with("nodes", "adam.rb").and_return(@node) @knife.run end it "should not print the Node" do expect(@knife).not_to receive(:output) @knife.run end describe "with -p or --print-after" do it "should print the Node" do @knife.config[:print_after] = true expect(@knife).to receive(:output) @knife.run end end end end chef-12.14.60/spec/unit/knife/node_list_spec.rb000066400000000000000000000041221276456504500212130ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Knife::NodeList do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" Chef::Config[:environment] = nil # reset this value each time, as it is not reloaded @knife = Chef::Knife::NodeList.new allow(@knife).to receive(:output).and_return(true) @list = { "foo" => "http://example.com/foo", "bar" => "http://example.com/foo", } allow(Chef::Node).to receive(:list).and_return(@list) allow(Chef::Node).to receive(:list_by_environment).and_return(@list) end describe "run" do it "should list all of the nodes if -E is not specified" do expect(Chef::Node).to receive(:list).and_return(@list) @knife.run end it "should pretty print the list" do expect(Chef::Node).to receive(:list).and_return(@list) expect(@knife).to receive(:output).with(%w{bar foo}) @knife.run end it "should list nodes in the specific environment if -E ENVIRONMENT is specified" do Chef::Config[:environment] = "prod" expect(Chef::Node).to receive(:list_by_environment).with("prod").and_return(@list) @knife.run end describe "with -w or --with-uri" do it "should pretty print the hash" do @knife.config[:with_uri] = true expect(Chef::Node).to receive(:list).and_return(@list) expect(@knife).to receive(:output).with(@list) @knife.run end end end end chef-12.14.60/spec/unit/knife/node_run_list_add_spec.rb000066400000000000000000000117531276456504500227170ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Knife::NodeRunListAdd do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::NodeRunListAdd.new @knife.config = { :after => nil, } @knife.name_args = [ "adam", "role[monkey]" ] allow(@knife).to receive(:output).and_return(true) @node = Chef::Node.new() allow(@node).to receive(:save).and_return(true) allow(Chef::Node).to receive(:load).and_return(@node) end describe "run" do it "should load the node" do expect(Chef::Node).to receive(:load).with("adam") @knife.run end it "should add to the run list" do @knife.run expect(@node.run_list[0]).to eq("role[monkey]") end it "should save the node" do expect(@node).to receive(:save) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.run end describe "with -a or --after specified" do it "should add to the run list after the specified entry" do @node.run_list << "role[acorns]" @node.run_list << "role[barn]" @knife.config[:after] = "role[acorns]" @knife.run expect(@node.run_list[0]).to eq("role[acorns]") expect(@node.run_list[1]).to eq("role[monkey]") expect(@node.run_list[2]).to eq("role[barn]") end end describe "with -b or --before specified" do it "should add to the run list before the specified entry" do @node.run_list << "role[acorns]" @node.run_list << "role[barn]" @knife.config[:before] = "role[acorns]" @knife.run expect(@node.run_list[0]).to eq("role[monkey]") expect(@node.run_list[1]).to eq("role[acorns]") expect(@node.run_list[2]).to eq("role[barn]") end end describe "with both --after and --before specified" do it "exits with an error" do @node.run_list << "role[acorns]" @node.run_list << "role[barn]" @knife.config[:before] = "role[acorns]" @knife.config[:after] = "role[acorns]" expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end end describe "with more than one role or recipe" do it "should add to the run list all the entries" do @knife.name_args = [ "adam", "role[monkey],role[duck]" ] @node.run_list << "role[acorns]" @knife.run expect(@node.run_list[0]).to eq("role[acorns]") expect(@node.run_list[1]).to eq("role[monkey]") expect(@node.run_list[2]).to eq("role[duck]") end end describe "with more than one role or recipe with space between items" do it "should add to the run list all the entries" do @knife.name_args = [ "adam", "role[monkey], role[duck]" ] @node.run_list << "role[acorns]" @knife.run expect(@node.run_list[0]).to eq("role[acorns]") expect(@node.run_list[1]).to eq("role[monkey]") expect(@node.run_list[2]).to eq("role[duck]") end end describe "with more than one role or recipe as different arguments" do it "should add to the run list all the entries" do @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ] @node.run_list << "role[acorns]" @knife.run expect(@node.run_list[0]).to eq("role[acorns]") expect(@node.run_list[1]).to eq("role[monkey]") expect(@node.run_list[2]).to eq("role[duck]") end end describe "with more than one role or recipe as different arguments and list separated by commas" do it "should add to the run list all the entries" do @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ] @node.run_list << "role[acorns]" @knife.run expect(@node.run_list[0]).to eq("role[acorns]") expect(@node.run_list[1]).to eq("role[monkey]") expect(@node.run_list[2]).to eq("role[duck]") end end describe "with one role or recipe but with an extraneous comma" do it "should add to the run list one item" do @knife.name_args = [ "adam", "role[monkey]," ] @node.run_list << "role[acorns]" @knife.run expect(@node.run_list[0]).to eq("role[acorns]") expect(@node.run_list[1]).to eq("role[monkey]") end end end end chef-12.14.60/spec/unit/knife/node_run_list_remove_spec.rb000066400000000000000000000075571276456504500234730ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Knife::NodeRunListRemove do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::NodeRunListRemove.new @knife.config[:print_after] = nil @knife.name_args = [ "adam", "role[monkey]" ] @node = Chef::Node.new() @node.name("knifetest-node") @node.run_list << "role[monkey]" allow(@node).to receive(:save).and_return(true) allow(@knife.ui).to receive(:output).and_return(true) allow(@knife.ui).to receive(:confirm).and_return(true) allow(Chef::Node).to receive(:load).and_return(@node) end describe "run" do it "should load the node" do expect(Chef::Node).to receive(:load).with("adam").and_return(@node) @knife.run end it "should remove the item from the run list" do @knife.run expect(@node.run_list[0]).not_to eq("role[monkey]") end it "should save the node" do expect(@node).to receive(:save).and_return(true) @knife.run end it "should print the run list" do @knife.config[:print_after] = true expect(@knife.ui).to receive(:output).with({ "knifetest-node" => { "run_list" => [] } }) @knife.run end describe "run with a list of roles and recipes" do it "should remove the items from the run list" do @node.run_list << "role[monkey]" @node.run_list << "recipe[duck::type]" @knife.name_args = [ "adam", "role[monkey],recipe[duck::type]" ] @knife.run expect(@node.run_list).not_to include("role[monkey]") expect(@node.run_list).not_to include("recipe[duck::type]") end it "should remove the items from the run list when name args contains whitespace" do @node.run_list << "role[monkey]" @node.run_list << "recipe[duck::type]" @knife.name_args = [ "adam", "role[monkey], recipe[duck::type]" ] @knife.run expect(@node.run_list).not_to include("role[monkey]") expect(@node.run_list).not_to include("recipe[duck::type]") end it "should remove the items from the run list when name args contains multiple run lists" do @node.run_list << "role[blah]" @node.run_list << "recipe[duck::type]" @knife.name_args = [ "adam", "role[monkey], recipe[duck::type]", "role[blah]" ] @knife.run expect(@node.run_list).not_to include("role[monkey]") expect(@node.run_list).not_to include("recipe[duck::type]") end it "should warn when the thing to remove is not in the runlist" do @node.run_list << "role[blah]" @node.run_list << "recipe[duck::type]" @knife.name_args = [ "adam", "role[blork]" ] expect(@knife.ui).to receive(:warn).with("role[blork] is not in the run list") @knife.run end it "should warn even more when the thing to remove is not in the runlist and unqualified" do @node.run_list << "role[blah]" @node.run_list << "recipe[duck::type]" @knife.name_args = %w{adam blork} expect(@knife.ui).to receive(:warn).with("blork is not in the run list") expect(@knife.ui).to receive(:warn).with(/did you forget recipe\[\] or role\[\]/) @knife.run end end end end chef-12.14.60/spec/unit/knife/node_run_list_set_spec.rb000066400000000000000000000075441276456504500227650ustar00rootroot00000000000000# # Author:: Mike Fiedler () # Copyright:: Copyright 2013-2016, Mike Fiedler # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::NodeRunListSet do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::NodeRunListSet.new @knife.config = {} @knife.name_args = [ "adam", "role[monkey]" ] allow(@knife).to receive(:output).and_return(true) @node = Chef::Node.new() allow(@node).to receive(:save).and_return(true) allow(Chef::Node).to receive(:load).and_return(@node) end describe "run" do it "should load the node" do expect(Chef::Node).to receive(:load).with("adam") @knife.run end it "should set the run list" do @knife.run expect(@node.run_list[0]).to eq("role[monkey]") end it "should save the node" do expect(@node).to receive(:save) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.run end describe "with more than one role or recipe" do it "should set the run list to all the entries" do @knife.name_args = [ "adam", "role[monkey],role[duck]" ] @knife.run expect(@node.run_list[0]).to eq("role[monkey]") expect(@node.run_list[1]).to eq("role[duck]") end end describe "with more than one role or recipe with space between items" do it "should set the run list to all the entries" do @knife.name_args = [ "adam", "role[monkey], role[duck]" ] @knife.run expect(@node.run_list[0]).to eq("role[monkey]") expect(@node.run_list[1]).to eq("role[duck]") end end describe "with more than one role or recipe as different arguments" do it "should set the run list to all the entries" do @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ] @knife.run expect(@node.run_list[0]).to eq("role[monkey]") expect(@node.run_list[1]).to eq("role[duck]") end end describe "with more than one role or recipe as different arguments and list separated by comas" do it "should add to the run list all the entries" do @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ] @knife.run expect(@node.run_list[0]).to eq("role[monkey]") expect(@node.run_list[1]).to eq("role[duck]") end end describe "with one role or recipe but with an extraneous comma" do it "should add to the run list one item" do @knife.name_args = [ "adam", "role[monkey]," ] @knife.run expect(@node.run_list[0]).to eq("role[monkey]") end end describe "with an existing run list" do it "should overwrite any existing run list items" do @node.run_list << "role[acorns]" @node.run_list << "role[zebras]" expect(@node.run_list[0]).to eq("role[acorns]") expect(@node.run_list[1]).to eq("role[zebras]") expect(@node.run_list.run_list_items.size).to eq(2) @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ] @knife.run expect(@node.run_list[0]).to eq("role[monkey]") expect(@node.run_list[1]).to eq("role[duck]") expect(@node.run_list.run_list_items.size).to eq(2) end end end end chef-12.14.60/spec/unit/knife/node_show_spec.rb000066400000000000000000000041321276456504500212210ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Knife::NodeShow do let(:node) do node = Chef::Node.new() node.name("adam") node.run_list = ["role[base]"] node end let(:knife) do knife = Chef::Knife::NodeShow.new knife.name_args = [ "adam" ] knife end before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" end describe "run" do it "should load the node" do expect(Chef::Node).to receive(:load).with("adam").and_return(node) allow(knife).to receive(:output).and_return(true) knife.run end it "should pretty print the node, formatted for display" do knife.config[:format] = nil stdout = StringIO.new allow(knife.ui).to receive(:stdout).and_return(stdout) allow(Chef::Node).to receive(:load).and_return(node) knife.run expect(stdout.string).to eql("Node Name: adam\nEnvironment: _default\nFQDN: \nIP: \nRun List: \nRoles: \nRecipes: \nPlatform: \nTags: \n") end it "should pretty print json" do knife.config[:format] = "json" stdout = StringIO.new allow(knife.ui).to receive(:stdout).and_return(stdout) expect(Chef::Node).to receive(:load).with("adam").and_return(node) knife.run expect(stdout.string).to eql("{\n \"name\": \"adam\",\n \"chef_environment\": \"_default\",\n \"run_list\": [\n\n]\n,\n \"normal\": {\n\n }\n}\n") end end end chef-12.14.60/spec/unit/knife/osc_user_create_spec.rb000066400000000000000000000061271276456504500224070ustar00rootroot00000000000000# # Author:: Steven Danna () # 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" Chef::Knife::OscUserCreate.load_deps # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_create_spec.rb. describe Chef::Knife::OscUserCreate do before(:each) do @knife = Chef::Knife::OscUserCreate.new @stdout = StringIO.new @stderr = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) allow(@knife.ui).to receive(:stderr).and_return(@stderr) @knife.name_args = [ "a_user" ] @knife.config[:user_password] = "foobar" @user = Chef::User.new @user.name "a_user" @user_with_private_key = Chef::User.new @user_with_private_key.name "a_user" @user_with_private_key.private_key "private_key" allow(@user).to receive(:create).and_return(@user_with_private_key) allow(Chef::User).to receive(:new).and_return(@user) allow(Chef::User).to receive(:from_hash).and_return(@user) allow(@knife).to receive(:edit_hash).and_return(@user.to_hash) end it "creates a new user" do expect(Chef::User).to receive(:new).and_return(@user) expect(@user).to receive(:create) @knife.run expect(@stderr.string).to match /created user.+a_user/i end it "sets the password" do @knife.config[:user_password] = "a_password" expect(@user).to receive(:password).with("a_password") @knife.run end it "exits with an error if password is blank" do @knife.config[:user_password] = "" expect { @knife.run }.to raise_error SystemExit expect(@stderr.string).to match /You must specify a non-blank password/ end it "sets the user name" do expect(@user).to receive(:name).with("a_user") @knife.run end it "sets the public key if given" do @knife.config[:user_key] = "/a/filename" allow(File).to receive(:read).with(File.expand_path("/a/filename")).and_return("a_key") expect(@user).to receive(:public_key).with("a_key") @knife.run end it "allows you to edit the data" do expect(@knife).to receive(:edit_hash).with(@user) @knife.run end it "writes the private key to a file when --file is specified" do @knife.config[:file] = "/tmp/a_file" filehandle = double("filehandle") expect(filehandle).to receive(:print).with("private_key") expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle) @knife.run end end chef-12.14.60/spec/unit/knife/osc_user_delete_spec.rb000066400000000000000000000027061276456504500224050ustar00rootroot00000000000000# # Author:: Steven Danna () # 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" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_delete_spec.rb. describe Chef::Knife::OscUserDelete do before(:each) do Chef::Knife::OscUserDelete.load_deps @knife = Chef::Knife::OscUserDelete.new @knife.name_args = [ "my_user" ] end it "deletes the user" do expect(@knife).to receive(:delete_object).with(Chef::User, "my_user") @knife.run end it "prints usage and exits when a user name is not provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end end chef-12.14.60/spec/unit/knife/osc_user_edit_spec.rb000066400000000000000000000034111276456504500220620ustar00rootroot00000000000000# # Author:: Steven Danna () # 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" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_edit_spec.rb. describe Chef::Knife::OscUserEdit do before(:each) do @stderr = StringIO.new @stdout = StringIO.new Chef::Knife::OscUserEdit.load_deps @knife = Chef::Knife::OscUserEdit.new allow(@knife.ui).to receive(:stderr).and_return(@stderr) allow(@knife.ui).to receive(:stdout).and_return(@stdout) @knife.name_args = [ "my_user" ] @knife.config[:disable_editing] = true end it "loads and edits the user" do data = { :name => "my_user" } allow(Chef::User).to receive(:load).with("my_user").and_return(data) expect(@knife).to receive(:edit_hash).with(data).and_return(data) @knife.run end it "prints usage and exits when a user name is not provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end end chef-12.14.60/spec/unit/knife/osc_user_list_spec.rb000066400000000000000000000022611276456504500221120ustar00rootroot00000000000000# # Author:: Steven Danna # 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" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_list_spec.rb. describe Chef::Knife::OscUserList do before(:each) do Chef::Knife::OscUserList.load_deps @knife = Chef::Knife::OscUserList.new end it "lists the users" do expect(Chef::User).to receive(:list) expect(@knife).to receive(:format_list_for_display) @knife.run end end chef-12.14.60/spec/unit/knife/osc_user_reregister_spec.rb000066400000000000000000000041441276456504500233140ustar00rootroot00000000000000# # Author:: Steven Danna () # 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" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_reregister_spec.rb. describe Chef::Knife::OscUserReregister do before(:each) do Chef::Knife::OscUserReregister.load_deps @knife = Chef::Knife::OscUserReregister.new @knife.name_args = [ "a_user" ] @user_mock = double("user_mock", :private_key => "private_key") allow(Chef::User).to receive(:load).and_return(@user_mock) @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end it "prints usage and exits when a user name is not provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end it "reregisters the user and prints the key" do expect(@user_mock).to receive(:reregister).and_return(@user_mock) @knife.run expect(@stdout.string).to match( /private_key/ ) end it "writes the private key to a file when --file is specified" do expect(@user_mock).to receive(:reregister).and_return(@user_mock) @knife.config[:file] = "/tmp/a_file" filehandle = StringIO.new expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle) @knife.run expect(filehandle.string).to eq("private_key") end end chef-12.14.60/spec/unit/knife/osc_user_show_spec.rb000066400000000000000000000030651276456504500221220ustar00rootroot00000000000000# # Author:: Steven Danna () # 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" # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur user_show_spec.rb. describe Chef::Knife::OscUserShow do before(:each) do Chef::Knife::OscUserShow.load_deps @knife = Chef::Knife::OscUserShow.new @knife.name_args = [ "my_user" ] @user_mock = double("user_mock") end it "loads and displays the user" do expect(Chef::User).to receive(:load).with("my_user").and_return(@user_mock) expect(@knife).to receive(:format_for_display).with(@user_mock) @knife.run end it "prints usage and exits when a user name is not provided" do @knife.name_args = [] expect(@knife).to receive(:show_usage) expect(@knife.ui).to receive(:fatal) expect { @knife.run }.to raise_error(SystemExit) end end chef-12.14.60/spec/unit/knife/raw_spec.rb000066400000000000000000000026251276456504500200320ustar00rootroot00000000000000# # Author:: Steven Danna () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" describe Chef::Knife::Raw do let(:rest) do r = double("Chef::Knife::Raw::RawInputServerAPI") allow(Chef::Knife::Raw::RawInputServerAPI).to receive(:new).and_return(r) r end let(:knife) do k = Chef::Knife::Raw.new k.config[:method] = "GET" k.name_args = [ "/nodes" ] k end describe "run" do it "should set the x-ops-request-source header when --proxy-auth is set" do knife.config[:proxy_auth] = true expect(rest).to receive(:request).with(:GET, "/nodes", { "Content-Type" => "application/json", "x-ops-request-source" => "web" }, false) knife.run end end end chef-12.14.60/spec/unit/knife/role_bulk_delete_spec.rb000066400000000000000000000046031276456504500225370ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::RoleBulkDelete do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::RoleBulkDelete.new @knife.config = { :print_after => nil, } @knife.name_args = ["."] @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) allow(@knife.ui).to receive(:confirm).and_return(true) @roles = Hash.new %w{dev staging production}.each do |role_name| role = Chef::Role.new() role.name(role_name) allow(role).to receive(:destroy).and_return(true) @roles[role_name] = role end allow(Chef::Role).to receive(:list).and_return(@roles) end describe "run" do it "should get the list of the roles" do expect(Chef::Role).to receive(:list).and_return(@roles) @knife.run end it "should print the roles you are about to delete" do @knife.run expect(@stdout.string).to match(/#{@knife.ui.list(@roles.keys.sort, :columns_down)}/) end it "should confirm you really want to delete them" do expect(@knife.ui).to receive(:confirm) @knife.run end it "should delete each role" do @roles.each_value do |r| expect(r).to receive(:destroy) end @knife.run end it "should only delete roles that match the regex" do @knife.name_args = ["dev"] expect(@roles["dev"]).to receive(:destroy) expect(@roles["staging"]).not_to receive(:destroy) expect(@roles["production"]).not_to receive(:destroy) @knife.run end it "should exit if the regex is not provided" do @knife.name_args = [] expect { @knife.run }.to raise_error(SystemExit) end end end chef-12.14.60/spec/unit/knife/role_create_spec.rb000066400000000000000000000045231276456504500215240ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Knife::RoleCreate do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::RoleCreate.new @knife.config = { :description => nil, } @knife.name_args = [ "adam" ] allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() allow(@role).to receive(:save) allow(Chef::Role).to receive(:new).and_return(@role) allow(@knife).to receive(:edit_data).and_return(@role) @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do it "should create a new role" do expect(Chef::Role).to receive(:new).and_return(@role) @knife.run end it "should set the role name" do expect(@role).to receive(:name).with("adam") @knife.run end it "should not print the role" do expect(@knife).not_to receive(:output) @knife.run end it "should allow you to edit the data" do expect(@knife).to receive(:edit_data).with(@role, object_class: Chef::Role) @knife.run end it "should save the role" do expect(@role).to receive(:save) @knife.run end describe "with -d or --description" do it "should set the description" do @knife.config[:description] = "All is bob" expect(@role).to receive(:description).with("All is bob") @knife.run end end describe "with -p or --print-after" do it "should pretty print the node, formatted for display" do @knife.config[:print_after] = true expect(@knife).to receive(:output).with(@role) @knife.run end end end end chef-12.14.60/spec/unit/knife/role_delete_spec.rb000066400000000000000000000037661276456504500215330ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Knife::RoleDelete do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::RoleDelete.new @knife.config = { :print_after => nil, } @knife.name_args = [ "adam" ] allow(@knife).to receive(:output).and_return(true) allow(@knife).to receive(:confirm).and_return(true) @role = Chef::Role.new() allow(@role).to receive(:destroy).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do it "should confirm that you want to delete" do expect(@knife).to receive(:confirm) @knife.run end it "should load the Role" do expect(Chef::Role).to receive(:load).with("adam").and_return(@role) @knife.run end it "should delete the Role" do expect(@role).to receive(:destroy).and_return(@role) @knife.run end it "should not print the Role" do expect(@knife).not_to receive(:output) @knife.run end describe "with -p or --print-after" do it "should pretty print the Role, formatted for display" do @knife.config[:print_after] = true expect(@knife).to receive(:output) @knife.run end end end end chef-12.14.60/spec/unit/knife/role_edit_spec.rb000066400000000000000000000045551276456504500212130ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Knife::RoleEdit do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::RoleEdit.new @knife.config[:print_after] = nil @knife.name_args = [ "adam" ] allow(@knife.ui).to receive(:output).and_return(true) @role = Chef::Role.new() allow(@role).to receive(:save) allow(Chef::Role).to receive(:load).and_return(@role) allow(@knife.ui).to receive(:edit_data).and_return(@role) allow(@knife.ui).to receive(:msg) end describe "run" do it "should load the role" do expect(Chef::Role).to receive(:load).with("adam").and_return(@role) @knife.run end it "should edit the role data" do expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role) @knife.run end it "should save the edited role data" do pansy = Chef::Role.new @role.name("new_role_name") expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role).and_return(pansy) expect(pansy).to receive(:save) @knife.run end it "should not save the unedited role data" do pansy = Chef::Role.new expect(@knife.ui).to receive(:edit_data).with(@role, object_class: Chef::Role).and_return(pansy) expect(pansy).not_to receive(:save) @knife.run end it "should not print the role" do expect(@knife.ui).not_to receive(:output) @knife.run end describe "with -p or --print-after" do it "should pretty print the role, formatted for display" do @knife.config[:print_after] = true expect(@knife.ui).to receive(:output).with(@role) @knife.run end end end end chef-12.14.60/spec/unit/knife/role_env_run_list_add_spec.rb000066400000000000000000000203771276456504500236050ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Will Albenzi () # 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::Knife::RoleEnvRunListAdd do before(:each) do # Chef::Config[:role_name] = "websimian" # Chef::Config[:env_name] = "QA" @knife = Chef::Knife::RoleEnvRunListAdd.new @knife.config = { :after => nil, } @knife.name_args = [ "will", "QA", "role[monkey]" ] allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() allow(@role).to receive(:save).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) end describe "run" do # it "should display all the things" do # @knife.run # @role.to_json.should == 'show all the things' # end it "should have an empty default run list" do @knife.run expect(@role.run_list[0]).to be_nil end it "should have a QA environment" do @knife.run expect(@role.active_run_list_for("QA")).to eq("QA") end it "should load the role named will" do expect(Chef::Role).to receive(:load).with("will") @knife.run end it "should be able to add an environment specific run list" do @knife.run expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") end it "should save the role" do expect(@role).to receive(:save) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.run end describe "with -a or --after specified" do it "should not create a change if the specified 'after' never comes" do @role.run_list_for("_default") << "role[acorns]" @role.run_list_for("_default") << "role[barn]" @knife.config[:after] = "role[acorns]" @knife.name_args = [ "will", "QA", "role[pad]" ] @knife.run expect(@role.run_list_for("QA")[0]).to be_nil expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to eq("role[barn]") expect(@role.run_list[2]).to be_nil end it "should add to the run list after the specified entries in the QA run list" do #Setup @role.run_list_for("_default") << "role[acorns]" @role.run_list_for("_default") << "role[barn]" @knife.run @role.run_list_for("QA") << "role[pencil]" @role.run_list_for("QA") << "role[pen]" #Configuration we are testing @knife.config[:after] = "role[pencil]" @knife.name_args = [ "will", "QA", "role[pad]", "role[whackadoo]" ] @knife.run #The actual tests expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") expect(@role.run_list_for("QA")[1]).to eq("role[pencil]") expect(@role.run_list_for("QA")[2]).to eq("role[pad]") expect(@role.run_list_for("QA")[3]).to eq("role[whackadoo]") expect(@role.run_list_for("QA")[4]).to eq("role[pen]") expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to eq("role[barn]") expect(@role.run_list[2]).to be_nil end end describe "with more than one role or recipe" do it "should add to the QA run list all the entries" do @knife.name_args = [ "will", "QA", "role[monkey],role[duck]" ] @role.run_list_for("_default") << "role[acorns]" @knife.run expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") expect(@role.run_list_for("QA")[1]).to eq("role[duck]") expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to be_nil end end describe "with more than one role or recipe with space between items" do it "should add to the run list all the entries" do @knife.name_args = [ "will", "QA", "role[monkey], role[duck]" ] @role.run_list_for("_default") << "role[acorns]" @knife.run expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") expect(@role.run_list_for("QA")[1]).to eq("role[duck]") expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to be_nil end end describe "with more than one role or recipe as different arguments" do it "should add to the run list all the entries" do @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck]" ] @role.run_list_for("_default") << "role[acorns]" @knife.run expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") expect(@role.run_list_for("QA")[1]).to eq("role[duck]") expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to be_nil end end describe "with more than one role or recipe as different arguments and list separated by comas" do it "should add to the run list all the entries" do @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck],recipe[bird::fly]" ] @role.run_list_for("_default") << "role[acorns]" @knife.run expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") expect(@role.run_list_for("QA")[1]).to eq("role[duck]") expect(@role.run_list_for("QA")[2]).to eq("recipe[bird::fly]") expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to be_nil end end describe "Recipe with version number is allowed" do it "should add to the run list all the entries including the versioned recipe" do @knife.name_args = [ "will", "QA", "role[monkey]", "role[duck],recipe[bird::fly@1.1.3]" ] @role.run_list_for("_default") << "role[acorns]" @knife.run expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") expect(@role.run_list_for("QA")[1]).to eq("role[duck]") expect(@role.run_list_for("QA")[2]).to eq("recipe[bird::fly@1.1.3]") expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to be_nil end end describe "with one role or recipe but with an extraneous comma" do it "should add to the run list one item" do @role.run_list_for("_default") << "role[acorns]" @knife.name_args = [ "will", "QA", "role[monkey]," ] @knife.run expect(@role.run_list_for("QA")[0]).to eq("role[monkey]") expect(@role.run_list_for("QA")[1]).to be_nil expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to be_nil end end describe "with more than one command" do it "should be able to the environment run list by running multiple knife commands" do @knife.name_args = [ "will", "QA", "role[blue]," ] @knife.run @knife.name_args = [ "will", "QA", "role[black]," ] @knife.run expect(@role.run_list_for("QA")[0]).to eq("role[blue]") expect(@role.run_list_for("QA")[1]).to eq("role[black]") expect(@role.run_list[0]).to be_nil end end describe "with more than one environment" do it "should add to the run list a second environment in the specific run list" do @role.run_list_for("_default") << "role[acorns]" @knife.name_args = [ "will", "QA", "role[blue]," ] @knife.run @role.run_list_for("QA") << "role[walnuts]" @knife.name_args = [ "will", "PRD", "role[ball]," ] @knife.run @role.run_list_for("PRD") << "role[pen]" expect(@role.run_list_for("QA")[0]).to eq("role[blue]") expect(@role.run_list_for("PRD")[0]).to eq("role[ball]") expect(@role.run_list_for("QA")[1]).to eq("role[walnuts]") expect(@role.run_list_for("PRD")[1]).to eq("role[pen]") expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to be_nil end end end end chef-12.14.60/spec/unit/knife/role_env_run_list_clear_spec.rb000066400000000000000000000062421276456504500241360ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Will Albenzi () # 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::Knife::RoleEnvRunListClear do before(:each) do Chef::Config[:role_name] = "will" Chef::Config[:env_name] = "QA" @setup = Chef::Knife::RoleEnvRunListAdd.new @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]" ] @knife = Chef::Knife::RoleEnvRunListClear.new @knife.config = { :print_after => nil, } @knife.name_args = %w{will QA} allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() @role.name("will") allow(@role).to receive(:save).and_return(true) allow(@knife.ui).to receive(:confirm).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) end describe "run" do # it "should display all the things" do # @knife.run # @role.to_json.should == 'show all the things' # end it "should load the node" do expect(Chef::Role).to receive(:load).with("will").and_return(@role) @knife.run end it "should remove the item from the run list" do @setup.run @knife.run expect(@role.run_list_for("QA")[0]).to be_nil expect(@role.run_list[0]).to be_nil end it "should save the node" do expect(@role).to receive(:save).and_return(true) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.config[:print_after] = true @setup.run @knife.run end describe "should clear an environmental run list of roles and recipes" do it "should remove the items from the run list" do @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @knife.name_args = %w{will QA} @knife.run expect(@role.run_list_for("QA")[0]).to be_nil expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]") expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]") expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]") expect(@role.run_list_for("PRD")[3]).to eq("role[person]") expect(@role.run_list_for("PRD")[4]).to eq("role[bird]") expect(@role.run_list_for("PRD")[5]).to eq("role[town]") end end end end chef-12.14.60/spec/unit/knife/role_env_run_list_remove_spec.rb000066400000000000000000000073131276456504500243450ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Will Albenzi () # 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::Knife::RoleEnvRunListRemove do before(:each) do Chef::Config[:role_name] = "will" Chef::Config[:env_name] = "QA" @setup = Chef::Knife::RoleEnvRunListAdd.new @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]" ] @knife = Chef::Knife::RoleEnvRunListRemove.new @knife.config = { :print_after => nil, } @knife.name_args = [ "will", "QA", "role[monkey]" ] allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() @role.name("will") allow(@role).to receive(:save).and_return(true) allow(@knife.ui).to receive(:confirm).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) end describe "run" do # it "should display all the things" do # @knife.run # @role.to_json.should == 'show all the things' # end it "should load the node" do expect(Chef::Role).to receive(:load).with("will").and_return(@role) @knife.run end it "should remove the item from the run list" do @setup.run @knife.run expect(@role.run_list_for("QA")[0]).not_to eq("role[monkey]") expect(@role.run_list_for("QA")[0]).to eq("role[person]") expect(@role.run_list[0]).to be_nil end it "should save the node" do expect(@role).to receive(:save).and_return(true) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.config[:print_after] = true @setup.run @knife.run end describe "run with a list of roles and recipes" do it "should remove the items from the run list" do @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @knife.name_args = [ "will", "QA", "role[monkey]" ] @knife.run @knife.name_args = [ "will", "QA", "recipe[duck::type]" ] @knife.run expect(@role.run_list_for("QA")).not_to include("role[monkey]") expect(@role.run_list_for("QA")).not_to include("recipe[duck::type]") expect(@role.run_list_for("QA")[0]).to eq("recipe[orange::chicken]") expect(@role.run_list_for("QA")[1]).to eq("role[person]") expect(@role.run_list_for("QA")[2]).to eq("role[bird]") expect(@role.run_list_for("QA")[3]).to eq("role[town]") expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]") expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]") expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]") expect(@role.run_list_for("PRD")[3]).to eq("role[person]") expect(@role.run_list_for("PRD")[4]).to eq("role[bird]") expect(@role.run_list_for("PRD")[5]).to eq("role[town]") end end end end chef-12.14.60/spec/unit/knife/role_env_run_list_replace_spec.rb000066400000000000000000000077111276456504500244650ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Will Albenzi () # 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::Knife::RoleEnvRunListReplace do before(:each) do Chef::Config[:role_name] = "will" Chef::Config[:env_name] = "QA" @setup = Chef::Knife::RoleEnvRunListAdd.new @setup.name_args = [ "will", "QA", "role[monkey]", "role[dude]", "role[fixer]" ] @knife = Chef::Knife::RoleEnvRunListReplace.new @knife.config = { :print_after => nil, } @knife.name_args = [ "will", "QA", "role[dude]", "role[person]" ] allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() @role.name("will") allow(@role).to receive(:save).and_return(true) allow(@knife.ui).to receive(:confirm).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) end describe "run" do # it "should display all the things" do # @knife.run # @role.to_json.should == 'show all the things' # end it "should load the node" do expect(Chef::Role).to receive(:load).with("will").and_return(@role) @knife.run end it "should remove the item from the run list" do @setup.run @knife.run expect(@role.run_list_for("QA")[1]).not_to eq("role[dude]") expect(@role.run_list_for("QA")[1]).to eq("role[person]") expect(@role.run_list[0]).to be_nil end it "should save the node" do expect(@role).to receive(:save).and_return(true) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.config[:print_after] = true @setup.run @knife.run end describe "run with a list of roles and recipes" do it "should replace the items from the run list" do @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @knife.name_args = [ "will", "QA", "role[monkey]", "role[gibbon]" ] @knife.run @knife.name_args = [ "will", "QA", "recipe[duck::type]", "recipe[duck::mallard]" ] @knife.run expect(@role.run_list_for("QA")).not_to include("role[monkey]") expect(@role.run_list_for("QA")).not_to include("recipe[duck::type]") expect(@role.run_list_for("QA")[0]).to eq("recipe[orange::chicken]") expect(@role.run_list_for("QA")[1]).to eq("role[gibbon]") expect(@role.run_list_for("QA")[2]).to eq("recipe[duck::mallard]") expect(@role.run_list_for("QA")[3]).to eq("role[person]") expect(@role.run_list_for("QA")[4]).to eq("role[bird]") expect(@role.run_list_for("QA")[5]).to eq("role[town]") expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]") expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]") expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]") expect(@role.run_list_for("PRD")[3]).to eq("role[person]") expect(@role.run_list_for("PRD")[4]).to eq("role[bird]") expect(@role.run_list_for("PRD")[5]).to eq("role[town]") expect(@role.run_list[0]).to be_nil end end end end chef-12.14.60/spec/unit/knife/role_env_run_list_set_spec.rb000066400000000000000000000070721276456504500236450ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Will Albenzi () # 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::Knife::RoleEnvRunListSet do before(:each) do Chef::Config[:role_name] = "will" Chef::Config[:env_name] = "QA" @setup = Chef::Knife::RoleEnvRunListAdd.new @setup.name_args = [ "will", "QA", "role[monkey]", "role[person]", "role[bucket]" ] @knife = Chef::Knife::RoleEnvRunListSet.new @knife.config = { :print_after => nil, } @knife.name_args = [ "will", "QA", "role[owen]", "role[mauntel]" ] allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() @role.name("will") allow(@role).to receive(:save).and_return(true) allow(@knife.ui).to receive(:confirm).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) end describe "run" do # it "should display all the things" do # @knife.run # @role.to_json.should == 'show all the things' # end it "should load the node" do expect(Chef::Role).to receive(:load).with("will").and_return(@role) @knife.run end it "should replace all the items in the runlist with what is specified" do @setup.run @knife.run expect(@role.run_list_for("QA")[0]).to eq("role[owen]") expect(@role.run_list_for("QA")[1]).to eq("role[mauntel]") expect(@role.run_list_for("QA")[2]).to be_nil expect(@role.run_list[0]).to be_nil end it "should save the node" do expect(@role).to receive(:save).and_return(true) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.config[:print_after] = true @setup.run @knife.run end describe "should clear an environmental run list of roles and recipes" do it "should remove the items from the run list" do @setup.name_args = [ "will", "QA", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @setup.name_args = [ "will", "PRD", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @knife.name_args = [ "will", "QA", "role[coke]", "role[pepsi]" ] @knife.run expect(@role.run_list_for("QA")[0]).to eq("role[coke]") expect(@role.run_list_for("QA")[1]).to eq("role[pepsi]") expect(@role.run_list_for("QA")[2]).to be_nil expect(@role.run_list_for("PRD")[0]).to eq("recipe[orange::chicken]") expect(@role.run_list_for("PRD")[1]).to eq("role[monkey]") expect(@role.run_list_for("PRD")[2]).to eq("recipe[duck::type]") expect(@role.run_list_for("PRD")[3]).to eq("role[person]") expect(@role.run_list_for("PRD")[4]).to eq("role[bird]") expect(@role.run_list_for("PRD")[5]).to eq("role[town]") expect(@role.run_list[0]).to be_nil end end end end chef-12.14.60/spec/unit/knife/role_from_file_spec.rb000066400000000000000000000041771276456504500222300ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" Chef::Knife::RoleFromFile.load_deps describe Chef::Knife::RoleFromFile do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::RoleFromFile.new @knife.config = { :print_after => nil, } @knife.name_args = [ "adam.rb" ] allow(@knife).to receive(:output).and_return(true) allow(@knife).to receive(:confirm).and_return(true) @role = Chef::Role.new() allow(@role).to receive(:save) allow(@knife.loader).to receive(:load_from).and_return(@role) @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do it "should load from a file" do expect(@knife.loader).to receive(:load_from).with("roles", "adam.rb").and_return(@role) @knife.run end it "should not print the role" do expect(@knife).not_to receive(:output) @knife.run end describe "with -p or --print-after" do it "should print the role" do @knife.config[:print_after] = true expect(@knife).to receive(:output) @knife.run end end end describe "run with multiple arguments" do it "should load each file" do @knife.name_args = [ "adam.rb", "caleb.rb" ] expect(@knife.loader).to receive(:load_from).with("roles", "adam.rb").and_return(@role) expect(@knife.loader).to receive(:load_from).with("roles", "caleb.rb").and_return(@role) @knife.run end end end chef-12.14.60/spec/unit/knife/role_list_spec.rb000066400000000000000000000032341276456504500212320ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Knife::RoleList do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::RoleList.new allow(@knife).to receive(:output).and_return(true) @list = { "foo" => "http://example.com/foo", "bar" => "http://example.com/foo", } allow(Chef::Role).to receive(:list).and_return(@list) end describe "run" do it "should list the roles" do expect(Chef::Role).to receive(:list).and_return(@list) @knife.run end it "should pretty print the list" do expect(Chef::Role).to receive(:list).and_return(@list) expect(@knife).to receive(:output).with(%w{bar foo}) @knife.run end describe "with -w or --with-uri" do it "should pretty print the hash" do @knife.config[:with_uri] = true expect(Chef::Role).to receive(:list).and_return(@list) expect(@knife).to receive(:output).with(@list) @knife.run end end end end chef-12.14.60/spec/unit/knife/role_run_list_add_spec.rb000066400000000000000000000150431276456504500227270ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Will Albenzi () # 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::Knife::RoleRunListAdd do before(:each) do # Chef::Config[:role_name] = "websimian" # Chef::Config[:env_name] = "QA" @knife = Chef::Knife::RoleRunListAdd.new @knife.config = { :after => nil, } @knife.name_args = [ "will", "role[monkey]" ] allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() allow(@role).to receive(:save).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) end describe "run" do # it "should display all the things" do # @knife.run # @role.to_json.should == 'show all the things' # end it "should have a run list with the monkey role" do @knife.run expect(@role.run_list[0]).to eq("role[monkey]") end it "should load the role named will" do expect(Chef::Role).to receive(:load).with("will") @knife.run end it "should save the role" do expect(@role).to receive(:save) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.run end describe "with -a or --after specified" do it "should not create a change if the specified 'after' never comes" do @role.run_list_for("_default") << "role[acorns]" @role.run_list_for("_default") << "role[barn]" @knife.config[:after] = "role[tree]" @knife.name_args = [ "will", "role[pad]" ] @knife.run expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to eq("role[barn]") expect(@role.run_list[2]).to be_nil end it "should add to the run list after the specified entries in the default run list" do #Setup @role.run_list_for("_default") << "role[acorns]" @role.run_list_for("_default") << "role[barn]" #Configuration we are testing @knife.config[:after] = "role[acorns]" @knife.name_args = [ "will", "role[pad]", "role[whackadoo]" ] @knife.run #The actual tests expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to eq("role[pad]") expect(@role.run_list[2]).to eq("role[whackadoo]") expect(@role.run_list[3]).to eq("role[barn]") expect(@role.run_list[4]).to be_nil end end describe "with more than one role or recipe" do it "should add to the QA run list all the entries" do @knife.name_args = [ "will", "role[monkey],role[duck]" ] @role.run_list_for("_default") << "role[acorns]" @knife.run expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to eq("role[monkey]") expect(@role.run_list[2]).to eq("role[duck]") expect(@role.run_list[3]).to be_nil end end describe "with more than one role or recipe with space between items" do it "should add to the run list all the entries" do @knife.name_args = [ "will", "role[monkey], role[duck]" ] @role.run_list_for("_default") << "role[acorns]" @knife.run expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to eq("role[monkey]") expect(@role.run_list[2]).to eq("role[duck]") expect(@role.run_list[3]).to be_nil end end describe "with more than one role or recipe as different arguments" do it "should add to the run list all the entries" do @knife.name_args = [ "will", "role[monkey]", "role[duck]" ] @role.run_list_for("_default") << "role[acorns]" @knife.run expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to eq("role[monkey]") expect(@role.run_list[2]).to eq("role[duck]") expect(@role.run_list[3]).to be_nil end end describe "with more than one role or recipe as different arguments and list separated by comas" do it "should add to the run list all the entries" do @knife.name_args = [ "will", "role[monkey]", "role[duck],recipe[bird::fly]" ] @role.run_list_for("_default") << "role[acorns]" @knife.run expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to eq("role[monkey]") expect(@role.run_list[2]).to eq("role[duck]") expect(@role.run_list[3]).to eq("recipe[bird::fly]") expect(@role.run_list[4]).to be_nil end end describe "Recipe with version number is allowed" do it "should add to the run list all the entries including the versioned recipe" do @knife.name_args = [ "will", "role[monkey]", "role[duck],recipe[bird::fly@1.1.3]" ] @role.run_list_for("_default") << "role[acorns]" @knife.run expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to eq("role[monkey]") expect(@role.run_list[2]).to eq("role[duck]") expect(@role.run_list[3]).to eq("recipe[bird::fly@1.1.3]") expect(@role.run_list[4]).to be_nil end end describe "with one role or recipe but with an extraneous comma" do it "should add to the run list one item" do @role.run_list_for("_default") << "role[acorns]" @knife.name_args = [ "will", "role[monkey]," ] @knife.run expect(@role.run_list[0]).to eq("role[acorns]") expect(@role.run_list[1]).to eq("role[monkey]") expect(@role.run_list[2]).to be_nil end end describe "with more than one command" do it "should be able to the environment run list by running multiple knife commands" do @knife.name_args = [ "will", "role[blue]," ] @knife.run @knife.name_args = [ "will", "role[black]," ] @knife.run expect(@role.run_list[0]).to eq("role[blue]") expect(@role.run_list[1]).to eq("role[black]") expect(@role.run_list[2]).to be_nil end end end end chef-12.14.60/spec/unit/knife/role_run_list_clear_spec.rb000066400000000000000000000047361276456504500232740ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Will Albenzi () # 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::Knife::RoleRunListClear do before(:each) do Chef::Config[:role_name] = "will" @setup = Chef::Knife::RoleRunListAdd.new @setup.name_args = [ "will", "role[monkey]", "role[person]" ] @knife = Chef::Knife::RoleRunListClear.new @knife.config = { :print_after => nil, } @knife.name_args = [ "will" ] allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() @role.name("will") allow(@role).to receive(:save).and_return(true) allow(@knife.ui).to receive(:confirm).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) end describe "run" do # it "should display all the things" do # @knife.run # @role.to_json.should == 'show all the things' # end it "should load the node" do expect(Chef::Role).to receive(:load).with("will").and_return(@role) @knife.run end it "should remove the item from the run list" do @setup.run @knife.run expect(@role.run_list[0]).to be_nil end it "should save the node" do expect(@role).to receive(:save).and_return(true) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.config[:print_after] = true @setup.run @knife.run end describe "should clear an environmental run list of roles and recipes" do it "should remove the items from the run list" do @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @knife.name_args = [ "will" ] @knife.run expect(@role.run_list[0]).to be_nil end end end end chef-12.14.60/spec/unit/knife/role_run_list_remove_spec.rb000066400000000000000000000056531276456504500235020ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Will Albenzi () # 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::Knife::RoleRunListRemove do before(:each) do Chef::Config[:role_name] = "will" @setup = Chef::Knife::RoleRunListAdd.new @setup.name_args = [ "will", "role[monkey]", "role[person]" ] @knife = Chef::Knife::RoleRunListRemove.new @knife.config = { :print_after => nil, } @knife.name_args = [ "will", "role[monkey]" ] allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() @role.name("will") allow(@role).to receive(:save).and_return(true) allow(@knife.ui).to receive(:confirm).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) end describe "run" do # it "should display all the things" do # @knife.run # @role.to_json.should == 'show all the things' # end it "should load the node" do expect(Chef::Role).to receive(:load).with("will").and_return(@role) @knife.run end it "should remove the item from the run list" do @setup.run @knife.run expect(@role.run_list[0]).to eq("role[person]") expect(@role.run_list[1]).to be_nil end it "should save the node" do expect(@role).to receive(:save).and_return(true) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.config[:print_after] = true @setup.run @knife.run end describe "run with a list of roles and recipes" do it "should remove the items from the run list" do @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @knife.name_args = [ "will", "role[monkey]" ] @knife.run @knife.name_args = [ "will", "recipe[duck::type]" ] @knife.run expect(@role.run_list).not_to include("role[monkey]") expect(@role.run_list).not_to include("recipe[duck::type]") expect(@role.run_list[0]).to eq("recipe[orange::chicken]") expect(@role.run_list[1]).to eq("role[person]") expect(@role.run_list[2]).to eq("role[bird]") expect(@role.run_list[3]).to eq("role[town]") end end end end chef-12.14.60/spec/unit/knife/role_run_list_replace_spec.rb000066400000000000000000000064721276456504500236200ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Will Albenzi () # 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::Knife::RoleRunListReplace do before(:each) do Chef::Config[:role_name] = "will" @setup = Chef::Knife::RoleRunListAdd.new @setup.name_args = [ "will", "role[monkey]", "role[dude]", "role[fixer]" ] @knife = Chef::Knife::RoleRunListReplace.new @knife.config = { :print_after => nil, } @knife.name_args = [ "will", "role[dude]", "role[person]" ] allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() @role.name("will") allow(@role).to receive(:save).and_return(true) allow(@knife.ui).to receive(:confirm).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) end describe "run" do # it "should display all the things" do # @knife.run # @role.to_json.should == 'show all the things' # end it "should load the node" do expect(Chef::Role).to receive(:load).with("will").and_return(@role) @knife.run end it "should remove the item from the run list" do @setup.run @knife.run expect(@role.run_list[0]).to eq("role[monkey]") expect(@role.run_list[1]).not_to eq("role[dude]") expect(@role.run_list[1]).to eq("role[person]") expect(@role.run_list[2]).to eq("role[fixer]") expect(@role.run_list[3]).to be_nil end it "should save the node" do expect(@role).to receive(:save).and_return(true) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.config[:print_after] = true @setup.run @knife.run end describe "run with a list of roles and recipes" do it "should replace the items from the run list" do @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @knife.name_args = [ "will", "role[monkey]", "role[gibbon]" ] @knife.run @knife.name_args = [ "will", "recipe[duck::type]", "recipe[duck::mallard]" ] @knife.run expect(@role.run_list).not_to include("role[monkey]") expect(@role.run_list).not_to include("recipe[duck::type]") expect(@role.run_list[0]).to eq("recipe[orange::chicken]") expect(@role.run_list[1]).to eq("role[gibbon]") expect(@role.run_list[2]).to eq("recipe[duck::mallard]") expect(@role.run_list[3]).to eq("role[person]") expect(@role.run_list[4]).to eq("role[bird]") expect(@role.run_list[5]).to eq("role[town]") expect(@role.run_list[6]).to be_nil end end end end chef-12.14.60/spec/unit/knife/role_run_list_set_spec.rb000066400000000000000000000055041276456504500227730ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Will Albenzi () # 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::Knife::RoleRunListSet do before(:each) do Chef::Config[:role_name] = "will" @setup = Chef::Knife::RoleRunListAdd.new @setup.name_args = [ "will", "role[monkey]", "role[person]", "role[bucket]" ] @knife = Chef::Knife::RoleRunListSet.new @knife.config = { :print_after => nil, } @knife.name_args = [ "will", "role[owen]", "role[mauntel]" ] allow(@knife).to receive(:output).and_return(true) @role = Chef::Role.new() @role.name("will") allow(@role).to receive(:save).and_return(true) allow(@knife.ui).to receive(:confirm).and_return(true) allow(Chef::Role).to receive(:load).and_return(@role) end describe "run" do # it "should display all the things" do # @knife.run # @role.to_json.should == 'show all the things' # end it "should load the node" do expect(Chef::Role).to receive(:load).with("will").and_return(@role) @knife.run end it "should replace all the items in the runlist with what is specified" do @setup.run @knife.run expect(@role.run_list[0]).to eq("role[owen]") expect(@role.run_list[1]).to eq("role[mauntel]") expect(@role.run_list[2]).to be_nil end it "should save the node" do expect(@role).to receive(:save).and_return(true) @knife.run end it "should print the run list" do expect(@knife).to receive(:output).and_return(true) @knife.config[:print_after] = true @setup.run @knife.run end describe "should clear an environmental run list of roles and recipes" do it "should remove the items from the run list" do @setup.name_args = [ "will", "recipe[orange::chicken]", "role[monkey]", "recipe[duck::type]", "role[person]", "role[bird]", "role[town]" ] @setup.run @knife.name_args = [ "will", "role[coke]", "role[pepsi]" ] @knife.run expect(@role.run_list[0]).to eq("role[coke]") expect(@role.run_list[1]).to eq("role[pepsi]") expect(@role.run_list[2]).to be_nil expect(@role.run_list[3]).to be_nil end end end end chef-12.14.60/spec/unit/knife/role_show_spec.rb000066400000000000000000000035201276456504500212350ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2014-2016, Lamont Granquist # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Knife::RoleShow do let(:role) { "base" } let(:knife) do knife = Chef::Knife::RoleShow.new knife.name_args = [ role ] knife end let(:role_mock) { double("role_mock") } describe "run" do it "should list the role" do expect(Chef::Role).to receive(:load).with("base").and_return(role_mock) expect(knife).to receive(:format_for_display).with(role_mock) knife.run end it "should pretty print json" do knife.config[:format] = "json" stdout = StringIO.new allow(knife.ui).to receive(:stdout).and_return(stdout) fake_role_contents = { "foo" => "bar", "baz" => "qux" } expect(Chef::Role).to receive(:load).with("base").and_return(fake_role_contents) knife.run expect(stdout.string).to eql("{\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n}\n") end context "without a role name" do let(:role) {} it "should print usage and exit when a role name is not provided" do expect(knife).to receive(:show_usage) expect(knife.ui).to receive(:fatal) expect { knife.run }.to raise_error(SystemExit) end end end end chef-12.14.60/spec/unit/knife/ssh_spec.rb000066400000000000000000000362201276456504500200340ustar00rootroot00000000000000# # Author:: Bryan McLellan # 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 "net/ssh" require "net/ssh/multi" describe Chef::Knife::Ssh do before(:each) do Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem" end before do @knife = Chef::Knife::Ssh.new @knife.merge_configs @node_foo = Chef::Node.new @node_foo.automatic_attrs[:fqdn] = "foo.example.org" @node_foo.automatic_attrs[:ipaddress] = "10.0.0.1" @node_bar = Chef::Node.new @node_bar.automatic_attrs[:fqdn] = "bar.example.org" @node_bar.automatic_attrs[:ipaddress] = "10.0.0.2" end describe "#configure_session" do context "manual is set to false (default)" do before do @knife.config[:manual] = false @query = Chef::Search::Query.new end def configure_query(node_array) allow(@query).to receive(:search).and_return([node_array]) allow(Chef::Search::Query).to receive(:new).and_return(@query) end def self.should_return_specified_attributes it "returns an array of the attributes specified on the command line OR config file, if only one is set" do @knife.config[:attribute] = "ipaddress" Chef::Config[:knife][:ssh_attribute] = "ipaddress" # this value will be in the config file configure_query([@node_foo, @node_bar]) expect(@knife).to receive(:session_from_list).with([["10.0.0.1", nil], ["10.0.0.2", nil]]) @knife.configure_session end it "returns an array of the attributes specified on the command line even when a config value is set" do Chef::Config[:knife][:ssh_attribute] = "config_file" # this value will be in the config file @knife.config[:attribute] = "ipaddress" # this is the value of the command line via #configure_attribute configure_query([@node_foo, @node_bar]) expect(@knife).to receive(:session_from_list).with([["10.0.0.1", nil], ["10.0.0.2", nil]]) @knife.configure_session end end it "searchs for and returns an array of fqdns" do configure_query([@node_foo, @node_bar]) expect(@knife).to receive(:session_from_list).with([ ["foo.example.org", nil], ["bar.example.org", nil], ]) @knife.configure_session end should_return_specified_attributes context "when cloud hostnames are available" do before do @node_foo.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-1.compute-1.amazonaws.com" @node_bar.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-2.compute-1.amazonaws.com" end it "returns an array of cloud public hostnames" do configure_query([@node_foo, @node_bar]) expect(@knife).to receive(:session_from_list).with([ ["ec2-10-0-0-1.compute-1.amazonaws.com", nil], ["ec2-10-0-0-2.compute-1.amazonaws.com", nil], ]) @knife.configure_session end should_return_specified_attributes end context "when cloud hostnames are available but empty" do before do @node_foo.automatic_attrs[:cloud][:public_hostname] = "" @node_bar.automatic_attrs[:cloud][:public_hostname] = "" end it "returns an array of fqdns" do configure_query([@node_foo, @node_bar]) expect(@knife).to receive(:session_from_list).with([ ["foo.example.org", nil], ["bar.example.org", nil], ]) @knife.configure_session end should_return_specified_attributes end it "should raise an error if no host are found" do configure_query([ ]) expect(@knife.ui).to receive(:fatal) expect(@knife).to receive(:exit).with(10) @knife.configure_session end context "when there are some hosts found but they do not have an attribute to connect with" do before do allow(@query).to receive(:search).and_return([[@node_foo, @node_bar]]) @node_foo.automatic_attrs[:fqdn] = nil @node_bar.automatic_attrs[:fqdn] = nil allow(Chef::Search::Query).to receive(:new).and_return(@query) end it "should raise a specific error (CHEF-3402)" do expect(@knife.ui).to receive(:fatal).with(/^2 nodes found/) expect(@knife).to receive(:exit).with(10) @knife.configure_session end end end context "manual is set to true" do before do @knife.config[:manual] = true end it "returns an array of provided values" do @knife.instance_variable_set(:@name_args, ["foo.example.org bar.example.org"]) expect(@knife).to receive(:session_from_list).with(["foo.example.org", "bar.example.org"]) @knife.configure_session end end end describe "#get_ssh_attribute" do # Order of precedence for ssh target # 1) command line attribute # 2) configuration file # 3) cloud attribute # 4) fqdn before do Chef::Config[:knife][:ssh_attribute] = nil @knife.config[:attribute] = nil @node_foo.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-1.compute-1.amazonaws.com" @node_bar.automatic_attrs[:cloud][:public_hostname] = "" end it "should return fqdn by default" do expect(@knife.get_ssh_attribute(Chef::Node.new)).to eq("fqdn") end it "should return cloud.public_hostname attribute if available" do expect(@knife.get_ssh_attribute(@node_foo)).to eq("cloud.public_hostname") end it "should favor to attribute_from_cli over config file and cloud" do @knife.config[:attribute] = "command_line" Chef::Config[:knife][:ssh_attribute] = "config_file" expect( @knife.get_ssh_attribute(@node_foo)).to eq("command_line") end it "should favor config file over cloud and default" do Chef::Config[:knife][:ssh_attribute] = "config_file" expect( @knife.get_ssh_attribute(@node_foo)).to eq("config_file") end it "should return fqdn if cloud.hostname is empty" do expect( @knife.get_ssh_attribute(@node_bar)).to eq("fqdn") end end describe "#session_from_list" do before :each do @knife.instance_variable_set(:@longest, 0) ssh_config = { :timeout => 50, :user => "locutus", :port => 23 } allow(Net::SSH).to receive(:configuration_for).with("the.b.org").and_return(ssh_config) end it "uses the port from an ssh config file" do @knife.session_from_list([["the.b.org", nil]]) expect(@knife.session.servers[0].port).to eq(23) end it "uses the port from a cloud attr" do @knife.session_from_list([["the.b.org", 123]]) expect(@knife.session.servers[0].port).to eq(123) end it "defaults to a timeout of 120 seconds" do @knife.session_from_list([["the.b.org", nil]]) expect(@knife.session.servers[0].options[:timeout]).to eq(120) end it "uses the timeout from Chef Config" do Chef::Config[:knife][:ssh_timeout] = 5 @knife.config[:ssh_timeout] = nil @knife.session_from_list([["the.b.org", nil]]) expect(@knife.session.servers[0].options[:timeout]).to eq(5) end it "uses the timeout from knife config" do @knife.config[:ssh_timeout] = 6 @knife.session_from_list([["the.b.org", nil]]) expect(@knife.session.servers[0].options[:timeout]).to eq(6) end it "uses the user from an ssh config file" do @knife.session_from_list([["the.b.org", 123]]) expect(@knife.session.servers[0].user).to eq("locutus") end end describe "#ssh_command" do let(:execution_channel) { double(:execution_channel, :on_data => nil) } let(:session_channel) { double(:session_channel, :request_pty => nil) } let(:execution_channel2) { double(:execution_channel, :on_data => nil) } let(:session_channel2) { double(:session_channel, :request_pty => nil) } let(:session) { double(:session, :loop => nil) } let(:command) { "false" } before do expect(execution_channel). to receive(:on_request). and_yield(nil, double(:data_stream, :read_long => exit_status)) expect(session_channel). to receive(:exec). with(command). and_yield(execution_channel, true) expect(execution_channel2). to receive(:on_request). and_yield(nil, double(:data_stream, :read_long => exit_status2)) expect(session_channel2). to receive(:exec). with(command). and_yield(execution_channel2, true) expect(session). to receive(:open_channel). and_yield(session_channel). and_yield(session_channel2) end context "both connections return 0" do let(:exit_status) { 0 } let(:exit_status2) { 0 } it "returns a 0 exit code" do expect(@knife.ssh_command(command, session)).to eq(0) end end context "the first connection returns 1 and the second returns 0" do let(:exit_status) { 1 } let(:exit_status2) { 0 } it "returns a non-zero exit code" do expect(@knife.ssh_command(command, session)).to eq(1) end end context "the first connection returns 1 and the second returns 2" do let(:exit_status) { 1 } let(:exit_status2) { 2 } it "returns a non-zero exit code" do expect(@knife.ssh_command(command, session)).to eq(2) end end end describe "#run" do before do @query = Chef::Search::Query.new expect(@query).to receive(:search).and_return([[@node_foo]]) allow(Chef::Search::Query).to receive(:new).and_return(@query) allow(@knife).to receive(:ssh_command).and_return(exit_code) @knife.name_args = ["*:*", "false"] end context "with an error" do let(:exit_code) { 1 } it "should exit with a non-zero exit code" do expect(@knife).to receive(:exit).with(exit_code) @knife.run end end context "with no error" do let(:exit_code) { 0 } it "should not exit" do expect(@knife).not_to receive(:exit) @knife.run end end end describe "#configure_password" do before do @knife.config.delete(:ssh_password_ng) @knife.config.delete(:ssh_password) end context "when setting ssh_password_ng from knife ssh" do # in this case ssh_password_ng exists, but ssh_password does not it "should prompt for a password when ssh_passsword_ng is nil" do @knife.config[:ssh_password_ng] = nil expect(@knife).to receive(:get_password).and_return("mysekretpassw0rd") @knife.configure_password expect(@knife.config[:ssh_password]).to eq("mysekretpassw0rd") end it "should set ssh_password to false if ssh_password_ng is false" do @knife.config[:ssh_password_ng] = false expect(@knife).not_to receive(:get_password) @knife.configure_password expect(@knife.config[:ssh_password]).to be_falsey end it "should set ssh_password to ssh_password_ng if we set a password" do @knife.config[:ssh_password_ng] = "mysekretpassw0rd" expect(@knife).not_to receive(:get_password) @knife.configure_password expect(@knife.config[:ssh_password]).to eq("mysekretpassw0rd") end end context "when setting ssh_password from knife bootstrap / knife * server create" do # in this case ssh_password exists, but ssh_password_ng does not it "should set ssh_password to nil when ssh_password is nil" do @knife.config[:ssh_password] = nil expect(@knife).not_to receive(:get_password) @knife.configure_password expect(@knife.config[:ssh_password]).to be_nil end it "should set ssh_password to false when ssh_password is false" do @knife.config[:ssh_password] = false expect(@knife).not_to receive(:get_password) @knife.configure_password expect(@knife.config[:ssh_password]).to be_falsey end it "should set ssh_password to ssh_password if we set a password" do @knife.config[:ssh_password] = "mysekretpassw0rd" expect(@knife).not_to receive(:get_password) @knife.configure_password expect(@knife.config[:ssh_password]).to eq("mysekretpassw0rd") end end context "when setting ssh_password in the config variable" do before(:each) do Chef::Config[:knife][:ssh_password] = "my_knife_passw0rd" end context "when setting ssh_password_ng from knife ssh" do # in this case ssh_password_ng exists, but ssh_password does not it "should prompt for a password when ssh_passsword_ng is nil" do @knife.config[:ssh_password_ng] = nil expect(@knife).to receive(:get_password).and_return("mysekretpassw0rd") @knife.configure_password expect(@knife.config[:ssh_password]).to eq("mysekretpassw0rd") end it "should set ssh_password to the configured knife.rb value if ssh_password_ng is false" do @knife.config[:ssh_password_ng] = false expect(@knife).not_to receive(:get_password) @knife.configure_password expect(@knife.config[:ssh_password]).to eq("my_knife_passw0rd") end it "should set ssh_password to ssh_password_ng if we set a password" do @knife.config[:ssh_password_ng] = "mysekretpassw0rd" expect(@knife).not_to receive(:get_password) @knife.configure_password expect(@knife.config[:ssh_password]).to eq("mysekretpassw0rd") end end context "when setting ssh_password from knife bootstrap / knife * server create" do # in this case ssh_password exists, but ssh_password_ng does not it "should set ssh_password to the configured knife.rb value when ssh_password is nil" do @knife.config[:ssh_password] = nil expect(@knife).not_to receive(:get_password) @knife.configure_password expect(@knife.config[:ssh_password]).to eq("my_knife_passw0rd") end it "should set ssh_password to the configured knife.rb value when ssh_password is false" do @knife.config[:ssh_password] = false expect(@knife).not_to receive(:get_password) @knife.configure_password expect(@knife.config[:ssh_password]).to eq("my_knife_passw0rd") end it "should set ssh_password to ssh_password if we set a password" do @knife.config[:ssh_password] = "mysekretpassw0rd" expect(@knife).not_to receive(:get_password) @knife.configure_password expect(@knife.config[:ssh_password]).to eq("mysekretpassw0rd") end end end end end chef-12.14.60/spec/unit/knife/ssl_check_spec.rb000066400000000000000000000216541276456504500212020ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "stringio" describe Chef::Knife::SslCheck do let(:name_args) { [] } let(:stdout_io) { StringIO.new } let(:stderr_io) { StringIO.new } def stderr stderr_io.string end def stdout stdout_io.string end subject(:ssl_check) do s = Chef::Knife::SslCheck.new allow(s.ui).to receive(:stdout).and_return(stdout_io) allow(s.ui).to receive(:stderr).and_return(stderr_io) s.name_args = name_args s end before do Chef::Config.chef_server_url = "https://example.com:8443/chef-server" end context "when no arguments are given" do it "uses the chef_server_url as the host to check" do expect(ssl_check.host).to eq("example.com") expect(ssl_check.port).to eq(8443) end end context "when a specific URI is given" do let(:name_args) { %w{https://example.test:10443/foo} } it "checks the SSL configuration against the given host" do expect(ssl_check.host).to eq("example.test") expect(ssl_check.port).to eq(10443) end end context "when an invalid URI is given" do let(:name_args) { %w{foo.test} } it "prints an error and exits" do expect { ssl_check.run }.to raise_error(SystemExit) expected_stdout = <<-E USAGE: knife ssl check [URL] (options) E expected_stderr = <<-E ERROR: Given URI: `foo.test' is invalid E expect(stdout_io.string).to eq(expected_stdout) expect(stderr_io.string).to eq(expected_stderr) end context "and its malformed enough to make URI.parse barf" do let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} } it "prints an error and exits" do expect { ssl_check.run }.to raise_error(SystemExit) expected_stdout = <<-E USAGE: knife ssl check [URL] (options) E expected_stderr = <<-E ERROR: Given URI: `#{name_args[0]}' is invalid E expect(stdout_io.string).to eq(expected_stdout) expect(stderr_io.string).to eq(expected_stderr) end end end describe "verifying trusted certificate X509 properties" do let(:name_args) { %w{https://foo.example.com:8443} } let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA, "trusted_certs") } let(:trusted_cert_file) { File.join(trusted_certs_dir, "example.crt") } let(:store) { OpenSSL::X509::Store.new } let(:certificate) { OpenSSL::X509::Certificate.new(IO.read(trusted_cert_file)) } before do Chef::Config[:trusted_certs_dir] = trusted_certs_dir allow(ssl_check).to receive(:trusted_certificates).and_return([trusted_cert_file]) allow(store).to receive(:add_cert).with(certificate) allow(OpenSSL::X509::Store).to receive(:new).and_return(store) allow(OpenSSL::X509::Certificate).to receive(:new).with(IO.read(trusted_cert_file)).and_return(certificate) allow(ssl_check).to receive(:verify_cert).and_return(true) allow(ssl_check).to receive(:verify_cert_host).and_return(true) end context "when the trusted certificates directory is not glob escaped", :windows_only do let(:trusted_certs_dir) { File.join(CHEF_SPEC_DATA.tr("/", "\\"), "trusted_certs") } before do allow(ssl_check).to receive(:trusted_certificates).and_call_original allow(store).to receive(:verify).with(certificate).and_return(true) end it "escpaes the trusted certificates directory" do expect(Dir).to receive(:glob) .with("#{ChefConfig::PathHelper.escape_glob_dir(trusted_certs_dir)}/*.{crt,pem}") .and_return([trusted_cert_file]) ssl_check.run end end context "when the trusted certificates have valid X509 properties" do before do allow(store).to receive(:verify).with(certificate).and_return(true) end it "does not generate any X509 warnings" do expect(ssl_check.ui).not_to receive(:warn).with(/There are invalid certificates in your trusted_certs_dir/) ssl_check.run end end context "when the trusted certificates have invalid X509 properties" do before do allow(store).to receive(:verify).with(certificate).and_return(false) allow(store).to receive(:error_string).and_return("unable to get local issuer certificate") end it "generates a warning message with invalid certificate file names" do expect(ssl_check.ui).to receive(:warn).with(/#{trusted_cert_file}: unable to get local issuer certificate/) ssl_check.run end end end describe "verifying the remote certificate" do let(:name_args) { %w{https://foo.example.com:8443} } let(:tcp_socket) { double(TCPSocket) } let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) } before do expect(ssl_check).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket) expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_check.verify_peer_ssl_context).and_return(ssl_socket) end def run ssl_check.run rescue Exception #puts "OUT: #{stdout_io.string}" #puts "ERR: #{stderr_io.string}" raise end context "when the remote host's certificate is valid" do before do expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs (no warn) expect(ssl_socket).to receive(:connect) # no error expect(ssl_socket).to receive(:post_connection_check).with("foo.example.com") # no error expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error end it "prints a success message" do ssl_check.run expect(stdout_io.string).to include("Successfully verified certificates from `foo.example.com'") end end describe "and the certificate is not valid" do let(:tcp_socket_for_debug) { double(TCPSocket) } let(:ssl_socket_for_debug) { double(OpenSSL::SSL::SSLSocket) } let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") } let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) } before do @old_signal = trap(:INT, "DEFAULT") expect(ssl_check).to receive(:proxified_socket). with("foo.example.com", 8443). and_return(tcp_socket_for_debug) expect(OpenSSL::SSL::SSLSocket).to receive(:new). with(tcp_socket_for_debug, ssl_check.noverify_peer_ssl_context). and_return(ssl_socket_for_debug) end after do trap(:INT, @old_signal) end context "when the certificate's CN does not match the hostname" do before do expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs expect(ssl_socket).to receive(:connect) # no error expect(ssl_socket).to receive(:post_connection_check). with("foo.example.com"). and_raise(OpenSSL::SSL::SSLError) expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error expect(ssl_socket_for_debug).to receive(:connect) expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt) end it "shows the CN used by the certificate and prints an error" do expect { run }.to raise_error(SystemExit) expect(stderr).to include("The SSL cert is signed by a trusted authority but is not valid for the given hostname") expect(stderr).to include("You are attempting to connect to: 'foo.example.com'") expect(stderr).to include("The server's certificate belongs to 'example.local'") end end context "when the cert is not signed by any trusted authority" do before do expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs expect(ssl_socket).to receive(:connect). and_raise(OpenSSL::SSL::SSLError) expect(ssl_socket).to receive(:hostname=). with("foo.example.com") # no error expect(ssl_socket_for_debug).to receive(:connect) expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt) end it "shows the CN used by the certificate and prints an error" do expect { run }.to raise_error(SystemExit) expect(stderr).to include("The SSL certificate of foo.example.com could not be verified") end end end end end chef-12.14.60/spec/unit/knife/ssl_fetch_spec.rb000066400000000000000000000130321276456504500212050ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/knife/ssl_fetch" describe Chef::Knife::SslFetch do let(:name_args) { [] } let(:stdout_io) { StringIO.new } let(:stderr_io) { StringIO.new } def stderr stderr_io.string end def stdout stdout_io.string end subject(:ssl_fetch) do s = Chef::Knife::SslFetch.new s.name_args = name_args allow(s.ui).to receive(:stdout).and_return(stdout_io) allow(s.ui).to receive(:stderr).and_return(stderr_io) s end context "when no arguments are given" do before do Chef::Config.chef_server_url = "https://example.com:8443/chef-server" end it "uses the chef_server_url as the host to fetch" do expect(ssl_fetch.host).to eq("example.com") expect(ssl_fetch.port).to eq(8443) end end context "when a specific URI is given" do let(:name_args) { %w{https://example.test:10443/foo} } it "fetchs the SSL configuration against the given host" do expect(ssl_fetch.host).to eq("example.test") expect(ssl_fetch.port).to eq(10443) end end context "when an invalid URI is given" do let(:name_args) { %w{foo.test} } it "prints an error and exits" do expect { ssl_fetch.run }.to raise_error(SystemExit) expected_stdout = <<-E USAGE: knife ssl fetch [URL] (options) E expected_stderr = <<-E ERROR: Given URI: `foo.test' is invalid E expect(stdout_io.string).to eq(expected_stdout) expect(stderr_io.string).to eq(expected_stderr) end context "and its malformed enough to make URI.parse barf" do let(:name_args) { %w{ftp://lkj\\blah:example.com/blah} } it "prints an error and exits" do expect { ssl_fetch.run }.to raise_error(SystemExit) expected_stdout = <<-E USAGE: knife ssl fetch [URL] (options) E expected_stderr = <<-E ERROR: Given URI: `#{name_args[0]}' is invalid E expect(stdout_io.string).to eq(expected_stdout) expect(stderr_io.string).to eq(expected_stderr) end end end describe "normalizing CNs for use as paths" do it "normalizes '*' to 'wildcard'" do expect(ssl_fetch.normalize_cn("*.example.com")).to eq("wildcard_example_com") end it "normalizes non-alnum and hyphen characters to underscores" do expect(ssl_fetch.normalize_cn("Billy-Bob's Super Awesome CA!")).to eq("Billy-Bob_s_Super_Awesome_CA_") end end describe "fetching the remote cert chain" do let(:name_args) { %w{https://foo.example.com:8443} } let(:tcp_socket) { double(TCPSocket) } let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) } let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") } let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) } let(:trusted_certs_dir) { Dir.mktmpdir } def run ssl_fetch.run rescue Exception puts "OUT: #{stdout_io.string}" puts "ERR: #{stderr_io.string}" raise end before do Chef::Config.trusted_certs_dir = trusted_certs_dir end after do FileUtils.rm_rf(trusted_certs_dir) end context "when the TLS connection is successful" do before do expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket) expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket) expect(ssl_socket).to receive(:connect) expect(ssl_socket).to receive(:peer_cert_chain).and_return([self_signed_crt]) end it "fetches the cert chain and writes the certs to the trusted_certs_dir" do run stored_cert_path = File.join(trusted_certs_dir, "example_local.crt") expect(File).to exist(stored_cert_path) expect(File.read(stored_cert_path)).to eq(File.read(self_signed_crt_path)) end end context "when connecting to a non-SSL service (like HTTP)" do let(:name_args) { %w{http://foo.example.com} } let(:unknown_protocol_error) { OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol") } before do expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 80).and_return(tcp_socket) expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket) expect(ssl_socket).to receive(:connect).and_raise(unknown_protocol_error) expect(ssl_fetch).to receive(:exit).with(1) end it "tells the user their URL is for a non-ssl service" do expected_error_text = <<-ERROR_TEXT ERROR: The service at the given URI (http://foo.example.com) does not accept SSL connections ERROR: Perhaps you meant to connect to 'https://foo.example.com'? ERROR_TEXT run expect(stderr).to include(expected_error_text) end end end end chef-12.14.60/spec/unit/knife/status_spec.rb000066400000000000000000000100741276456504500205610ustar00rootroot00000000000000# # Author:: Sahil Muthoo () # 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" describe Chef::Knife::Status do before(:each) do node = Chef::Node.new.tap do |n| n.automatic_attrs["fqdn"] = "foobar" n.automatic_attrs["ohai_time"] = 1343845969 end allow(Time).to receive(:now).and_return(Time.at(1428573420)) @query = double("Chef::Search::Query") allow(@query).to receive(:search).and_yield(node) allow(Chef::Search::Query).to receive(:new).and_return(@query) @knife = Chef::Knife::Status.new @stdout = StringIO.new allow(@knife.ui).to receive(:stdout).and_return(@stdout) end describe "run" do let(:opts) do { filter_result: { name: ["name"], ipaddress: ["ipaddress"], ohai_time: ["ohai_time"], ec2: ["ec2"], run_list: ["run_list"], platform: ["platform"], platform_version: ["platform_version"], chef_environment: ["chef_environment"] } } end it "should default to searching for everything" do expect(@query).to receive(:search).with(:node, "*:*", opts) @knife.run end it "should filter by nodes older than some mins" do @knife.config[:hide_by_mins] = 59 expect(@query).to receive(:search).with(:node, "NOT ohai_time:[1428569880 TO 1428573420]", opts) @knife.run end it "should filter by environment" do @knife.config[:environment] = "production" expect(@query).to receive(:search).with(:node, "chef_environment:production", opts) @knife.run end it "should filter by environment and nodes older than some mins" do @knife.config[:environment] = "production" @knife.config[:hide_by_mins] = 59 expect(@query).to receive(:search).with(:node, "chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts) @knife.run end it "should not use partial search with long output" do @knife.config[:long_output] = true expect(@query).to receive(:search).with(:node, "*:*", {}) @knife.run end context "with a custom query" do before :each do @knife.instance_variable_set(:@name_args, ["name:my_custom_name"]) end it "should allow a custom query to be specified" do expect(@query).to receive(:search).with(:node, "name:my_custom_name", opts) @knife.run end it "should filter by nodes older than some mins with nodename specified" do @knife.config[:hide_by_mins] = 59 expect(@query).to receive(:search).with(:node, "name:my_custom_name NOT ohai_time:[1428569880 TO 1428573420]", opts) @knife.run end it "should filter by environment with nodename specified" do @knife.config[:environment] = "production" expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production", opts) @knife.run end it "should filter by environment and nodes older than some mins with nodename specified" do @knife.config[:environment] = "production" @knife.config[:hide_by_mins] = 59 expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production NOT ohai_time:[1428569880 TO 1428573420]", opts) @knife.run end end it "should not colorize output unless it's writing to a tty" do @knife.run expect(@stdout.string.match(/foobar/)).not_to be_nil expect(@stdout.string.match(/\e.*ago/)).to be_nil end end end chef-12.14.60/spec/unit/knife/tag_create_spec.rb000066400000000000000000000012471276456504500213360ustar00rootroot00000000000000require "spec_helper" describe Chef::Knife::TagCreate do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::TagCreate.new @knife.name_args = [ Chef::Config[:node_name], "happytag" ] @node = Chef::Node.new allow(@node).to receive :save allow(Chef::Node).to receive(:load).and_return @node @stderr = StringIO.new allow(@knife.ui).to receive(:stderr).and_return(@stderr) end describe "run" do it "can create tags on a node" do @knife.run expect(@node.tags).to eq(["happytag"]) expect(@stderr.string).to match /created tags happytag.+node webmonkey.example.com/i end end end chef-12.14.60/spec/unit/knife/tag_delete_spec.rb000066400000000000000000000013401276456504500213270ustar00rootroot00000000000000require "spec_helper" describe Chef::Knife::TagDelete do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::TagDelete.new @knife.name_args = [ Chef::Config[:node_name], "sadtag" ] @node = Chef::Node.new allow(@node).to receive :save @node.tags << "sadtag" << "happytag" allow(Chef::Node).to receive(:load).and_return @node @stderr = StringIO.new allow(@knife.ui).to receive(:stderr).and_return(@stderr) end describe "run" do it "can delete tags on a node" do expect(@node.tags).to eq(%w{sadtag happytag}) @knife.run expect(@node.tags).to eq(["happytag"]) expect(@stderr.string).to match /deleted.+sadtag/i end end end chef-12.14.60/spec/unit/knife/tag_list_spec.rb000066400000000000000000000011561276456504500210450ustar00rootroot00000000000000require "spec_helper" describe Chef::Knife::TagList do before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" @knife = Chef::Knife::TagList.new @knife.name_args = [ Chef::Config[:node_name], "sadtag" ] @node = Chef::Node.new allow(@node).to receive :save @node.tags << "sadtag" << "happytag" allow(Chef::Node).to receive(:load).and_return @node end describe "run" do it "can list tags on a node" do expected = %w{sadtag happytag} expect(@node.tags).to eq(expected) expect(@knife).to receive(:output).with(expected) @knife.run end end end chef-12.14.60/spec/unit/knife/user_create_spec.rb000066400000000000000000000150741276456504500215440ustar00rootroot00000000000000# # Author:: Steven Danna () # Author:: Tyler Cloke () # 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" Chef::Knife::UserCreate.load_deps describe Chef::Knife::UserCreate do let(:knife) { Chef::Knife::UserCreate.new } let(:stderr) do StringIO.new end let(:stdout) do StringIO.new end before(:each) do allow(knife.ui).to receive(:stdout).and_return(stdout) allow(knife.ui).to receive(:stderr).and_return(stderr) allow(knife.ui).to receive(:warn) end # delete this once OSC11 support is gone context "when only one name_arg is passed" do before do knife.name_args = ["some_user"] allow(knife).to receive(:run_osc_11_user_create).and_raise(SystemExit) end it "displays the osc warning" do expect(knife.ui).to receive(:warn).with(knife.osc_11_warning) expect { knife.run }.to raise_error(SystemExit) end it "calls knife osc_user create" do expect(knife).to receive(:run_osc_11_user_create) expect { knife.run }.to raise_error(SystemExit) end end context "when USERNAME isn't specified" do # from spec/support/shared/unit/knife_shared.rb it_should_behave_like "mandatory field missing" do let(:name_args) { [] } let(:fieldname) { "username" } end end # uncomment once OSC11 support is gone, # pending doesn't work for shared_examples_for by default # # context "when DISPLAY_NAME isn't specified" do # # from spec/support/shared/unit/knife_shared.rb # it_should_behave_like "mandatory field missing" do # let(:name_args) { ['some_user'] } # let(:fieldname) { 'display name' } # end # end context "when FIRST_NAME isn't specified" do # from spec/support/shared/unit/knife_shared.rb it_should_behave_like "mandatory field missing" do let(:name_args) { %w{some_user some_display_name} } let(:fieldname) { "first name" } end end context "when LAST_NAME isn't specified" do # from spec/support/shared/unit/knife_shared.rb it_should_behave_like "mandatory field missing" do let(:name_args) { %w{some_user some_display_name some_first_name} } let(:fieldname) { "last name" } end end context "when EMAIL isn't specified" do # from spec/support/shared/unit/knife_shared.rb it_should_behave_like "mandatory field missing" do let(:name_args) { %w{some_user some_display_name some_first_name some_last_name} } let(:fieldname) { "email" } end end context "when PASSWORD isn't specified" do # from spec/support/shared/unit/knife_shared.rb it_should_behave_like "mandatory field missing" do let(:name_args) { %w{some_user some_display_name some_first_name some_last_name some_email} } let(:fieldname) { "password" } end end context "when all mandatory fields are validly specified" do before do knife.name_args = %w{some_user some_display_name some_first_name some_last_name some_email some_password} allow(knife).to receive(:edit_hash).and_return(knife.user.to_hash) allow(knife).to receive(:create_user_from_hash).and_return(knife.user) end before(:each) do # reset the user field every run knife.user_field = nil end it "sets all the mandatory fields" do knife.run expect(knife.user.username).to eq("some_user") expect(knife.user.display_name).to eq("some_display_name") expect(knife.user.first_name).to eq("some_first_name") expect(knife.user.last_name).to eq("some_last_name") expect(knife.user.email).to eq("some_email") expect(knife.user.password).to eq("some_password") end context "when user_key and prevent_keygen are passed" do before do knife.config[:user_key] = "some_key" knife.config[:prevent_keygen] = true end it "prints the usage" do expect(knife).to receive(:show_usage) expect { knife.run }.to raise_error(SystemExit) end it "prints a relevant error message" do expect { knife.run }.to raise_error(SystemExit) expect(stderr.string).to match /You cannot pass --user-key and --prevent-keygen/ end end context "when --prevent-keygen is passed" do before do knife.config[:prevent_keygen] = true end it "does not set user.create_key" do knife.run expect(knife.user.create_key).to be_falsey end end context "when --prevent-keygen is not passed" do it "sets user.create_key to true" do knife.run expect(knife.user.create_key).to be_truthy end end context "when --user-key is passed" do before do knife.config[:user_key] = "some_key" allow(File).to receive(:read).and_return("some_key") allow(File).to receive(:expand_path) end it "sets user.public_key" do knife.run expect(knife.user.public_key).to eq("some_key") end end context "when --user-key is not passed" do it "does not set user.public_key" do knife.run expect(knife.user.public_key).to be_nil end end context "when a private_key is returned" do before do allow(knife).to receive(:create_user_from_hash).and_return(Chef::UserV1.from_hash(knife.user.to_hash.merge({ "private_key" => "some_private_key" }))) end context "when --file is passed" do before do knife.config[:file] = "/some/path" end it "creates a new file of the path passed" do filehandle = double("filehandle") expect(filehandle).to receive(:print).with("some_private_key") expect(File).to receive(:open).with("/some/path", "w").and_yield(filehandle) knife.run end end context "when --file is not passed" do it "prints the private key to stdout" do expect(knife.ui).to receive(:msg).with("some_private_key") knife.run end end end end # when all mandatory fields are validly specified end chef-12.14.60/spec/unit/knife/user_delete_spec.rb000066400000000000000000000042771276456504500215460ustar00rootroot00000000000000# # Author:: Steven Danna () # 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" describe Chef::Knife::UserDelete do let(:knife) { Chef::Knife::UserDelete.new } let(:user) { double("user_object") } let(:stdout) { StringIO.new } before(:each) do Chef::Knife::UserDelete.load_deps knife.name_args = [ "my_user" ] allow(Chef::UserV1).to receive(:load).and_return(user) allow(user).to receive(:username).and_return("my_user") allow(knife.ui).to receive(:stderr).and_return(stdout) allow(knife.ui).to receive(:stdout).and_return(stdout) end # delete this once OSC11 support is gone context "when the username field is not supported by the server" do before do allow(knife).to receive(:run_osc_11_user_delete).and_raise(SystemExit) allow(user).to receive(:username).and_return(nil) end it "displays the osc warning" do expect(knife.ui).to receive(:warn).with(knife.osc_11_warning) expect { knife.run }.to raise_error(SystemExit) end it "forwards the command to knife osc_user edit" do expect(knife).to receive(:run_osc_11_user_delete) expect { knife.run }.to raise_error(SystemExit) end end it "deletes the user" do #expect(knife).to receive(:delete_object).with(Chef::UserV1, 'my_user') expect(knife).to receive(:delete_object).with("my_user") knife.run end it "prints usage and exits when a user name is not provided" do knife.name_args = [] expect(knife).to receive(:show_usage) expect(knife.ui).to receive(:fatal) expect { knife.run }.to raise_error(SystemExit) end end chef-12.14.60/spec/unit/knife/user_edit_spec.rb000066400000000000000000000042461276456504500212250ustar00rootroot00000000000000# # Author:: Steven Danna () # 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" describe Chef::Knife::UserEdit do let(:knife) { Chef::Knife::UserEdit.new } before(:each) do @stderr = StringIO.new @stdout = StringIO.new Chef::Knife::UserEdit.load_deps allow(knife.ui).to receive(:stderr).and_return(@stderr) allow(knife.ui).to receive(:stdout).and_return(@stdout) knife.name_args = [ "my_user" ] knife.config[:disable_editing] = true end # delete this once OSC11 support is gone context "when the username field is not supported by the server" do before do allow(knife).to receive(:run_osc_11_user_edit).and_raise(SystemExit) allow(Chef::UserV1).to receive(:load).and_return({ "username" => nil }) end it "displays the osc warning" do expect(knife.ui).to receive(:warn).with(knife.osc_11_warning) expect { knife.run }.to raise_error(SystemExit) end it "forwards the command to knife osc_user edit" do expect(knife).to receive(:run_osc_11_user_edit) expect { knife.run }.to raise_error(SystemExit) end end it "loads and edits the user" do data = { "username" => "my_user" } allow(Chef::UserV1).to receive(:load).with("my_user").and_return(data) expect(knife).to receive(:edit_hash).with(data).and_return(data) knife.run end it "prints usage and exits when a user name is not provided" do knife.name_args = [] expect(knife).to receive(:show_usage) expect(knife.ui).to receive(:fatal) expect { knife.run }.to raise_error(SystemExit) end end chef-12.14.60/spec/unit/knife/user_list_spec.rb000066400000000000000000000021531276456504500212460ustar00rootroot00000000000000# # Author:: Steven Danna # 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" describe Chef::Knife::UserList do let(:knife) { Chef::Knife::UserList.new } let(:stdout) { StringIO.new } before(:each) do Chef::Knife::UserList.load_deps allow(knife.ui).to receive(:stderr).and_return(stdout) allow(knife.ui).to receive(:stdout).and_return(stdout) end it "lists the users" do expect(Chef::UserV1).to receive(:list) expect(knife).to receive(:format_list_for_display) knife.run end end chef-12.14.60/spec/unit/knife/user_reregister_spec.rb000066400000000000000000000051511276456504500224470ustar00rootroot00000000000000# # Author:: Steven Danna () # 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" describe Chef::Knife::UserReregister do let(:knife) { Chef::Knife::UserReregister.new } let(:user_mock) { double("user_mock", :private_key => "private_key") } let(:stdout) { StringIO.new } before do Chef::Knife::UserReregister.load_deps knife.name_args = [ "a_user" ] allow(Chef::UserV1).to receive(:load).and_return(user_mock) allow(knife.ui).to receive(:stdout).and_return(stdout) allow(knife.ui).to receive(:stderr).and_return(stdout) allow(user_mock).to receive(:username).and_return("a_user") end # delete this once OSC11 support is gone context "when the username field is not supported by the server" do before do allow(knife).to receive(:run_osc_11_user_reregister).and_raise(SystemExit) allow(user_mock).to receive(:username).and_return(nil) end it "displays the osc warning" do expect(knife.ui).to receive(:warn).with(knife.osc_11_warning) expect { knife.run }.to raise_error(SystemExit) end it "forwards the command to knife osc_user edit" do expect(knife).to receive(:run_osc_11_user_reregister) expect { knife.run }.to raise_error(SystemExit) end end it "prints usage and exits when a user name is not provided" do knife.name_args = [] expect(knife).to receive(:show_usage) expect(knife.ui).to receive(:fatal) expect { knife.run }.to raise_error(SystemExit) end it "reregisters the user and prints the key" do expect(user_mock).to receive(:reregister).and_return(user_mock) knife.run expect(stdout.string).to match( /private_key/ ) end it "writes the private key to a file when --file is specified" do expect(user_mock).to receive(:reregister).and_return(user_mock) knife.config[:file] = "/tmp/a_file" filehandle = StringIO.new expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle) knife.run expect(filehandle.string).to eq("private_key") end end chef-12.14.60/spec/unit/knife/user_show_spec.rb000066400000000000000000000043471276456504500212620ustar00rootroot00000000000000# # Author:: Steven Danna () # 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" describe Chef::Knife::UserShow do let(:knife) { Chef::Knife::UserShow.new } let(:user_mock) { double("user_mock") } let(:stdout) { StringIO.new } before do Chef::Knife::UserShow.load_deps knife.name_args = [ "my_user" ] allow(user_mock).to receive(:username).and_return("my_user") allow(knife.ui).to receive(:stderr).and_return(stdout) allow(knife.ui).to receive(:stdout).and_return(stdout) end # delete this once OSC11 support is gone context "when the username field is not supported by the server" do before do allow(knife).to receive(:run_osc_11_user_show).and_raise(SystemExit) allow(Chef::UserV1).to receive(:load).with("my_user").and_return(user_mock) allow(user_mock).to receive(:username).and_return(nil) end it "displays the osc warning" do expect(knife.ui).to receive(:warn).with(knife.osc_11_warning) expect { knife.run }.to raise_error(SystemExit) end it "forwards the command to knife osc_user edit" do expect(knife).to receive(:run_osc_11_user_show) expect { knife.run }.to raise_error(SystemExit) end end it "loads and displays the user" do expect(Chef::UserV1).to receive(:load).with("my_user").and_return(user_mock) expect(knife).to receive(:format_for_display).with(user_mock) knife.run end it "prints usage and exits when a user name is not provided" do knife.name_args = [] expect(knife).to receive(:show_usage) expect(knife.ui).to receive(:fatal) expect { knife.run }.to raise_error(SystemExit) end end chef-12.14.60/spec/unit/knife_spec.rb000066400000000000000000000541331276456504500172420ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tim Hinderliter () # 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. # # Fixtures for subcommand loading live in this namespace module KnifeSpecs end require "spec_helper" require "uri" require "chef/knife/core/gem_glob_loader" describe Chef::Knife do let(:stderr) { StringIO.new } let(:knife) { Chef::Knife.new } let(:config_location) { File.expand_path("~/.chef/config.rb") } let(:config_loader) do instance_double("WorkstationConfigLoader", load: nil, no_config_found?: false, config_location: config_location, :chef_config_dir => "/etc/chef") end before(:each) do Chef::Log.logger = Logger.new(StringIO.new) Chef::Config[:node_name] = "webmonkey.example.com" allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader) allow(config_loader).to receive(:explicit_config_file=) # Prevent gratuitous code reloading: allow(Chef::Knife).to receive(:load_commands) allow(knife.ui).to receive(:puts) allow(knife.ui).to receive(:print) allow(Chef::Log).to receive(:init) allow(Chef::Log).to receive(:level) [:debug, :info, :warn, :error, :crit].each do |level_sym| allow(Chef::Log).to receive(level_sym) end allow(Chef::Knife).to receive(:puts) end after(:each) do Chef::Knife.reset_config_loader! end it "does not reset Chef::Config[:verbosity to nil if config[:verbosity] is nil" do Chef::Config[:verbosity] = 2 Chef::Knife.new expect(Chef::Config[:verbosity]).to eq(2) end describe "after loading a subcommand" do before do Chef::Knife.reset_subcommands! if KnifeSpecs.const_defined?(:TestNameMapping) KnifeSpecs.send(:remove_const, :TestNameMapping) end if KnifeSpecs.const_defined?(:TestExplicitCategory) KnifeSpecs.send(:remove_const, :TestExplicitCategory) end Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_name_mapping.rb")) Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_explicit_category.rb")) end it "has a category based on its name" do expect(KnifeSpecs::TestNameMapping.subcommand_category).to eq("test") end it "has an explicitly defined category if set" do expect(KnifeSpecs::TestExplicitCategory.subcommand_category).to eq("cookbook site") end it "can reference the subcommand by its snake cased name" do expect(Chef::Knife.subcommands["test_name_mapping"]).to equal(KnifeSpecs::TestNameMapping) end it "lists subcommands by category" do expect(Chef::Knife.subcommands_by_category["test"]).to include("test_name_mapping") end it "lists subcommands by category when the subcommands have explicit categories" do expect(Chef::Knife.subcommands_by_category["cookbook site"]).to include("test_explicit_category") end it "has empty dependency_loader list by default" do expect(KnifeSpecs::TestNameMapping.dependency_loaders).to be_empty end end describe "after loading all subcommands" do before do Chef::Knife.reset_subcommands! Chef::Knife.load_commands end it "references a subcommand class by its snake cased name" do class SuperAwesomeCommand < Chef::Knife end Chef::Knife.load_commands expect(Chef::Knife.subcommands).to have_key("super_awesome_command") expect(Chef::Knife.subcommands["super_awesome_command"]).to eq(SuperAwesomeCommand) end it "records the location of ChefFS-based commands correctly" do class AwesomeCheffsCommand < Chef::ChefFS::Knife end Chef::Knife.load_commands expect(Chef::Knife.subcommand_files["awesome_cheffs_command"]).to eq([__FILE__]) end it "guesses a category from a given ARGV" do Chef::Knife.subcommands_by_category["cookbook"] << :cookbook Chef::Knife.subcommands_by_category["cookbook site"] << :cookbook_site expect(Chef::Knife.guess_category(%w{cookbook foo bar baz})).to eq("cookbook") expect(Chef::Knife.guess_category(%w{cookbook site foo bar baz})).to eq("cookbook site") expect(Chef::Knife.guess_category(%w{cookbook site --help})).to eq("cookbook site") end it "finds a subcommand class based on ARGV" do Chef::Knife.subcommands["cookbook_site_vendor"] = :CookbookSiteVendor Chef::Knife.subcommands["cookbook"] = :Cookbook expect(Chef::Knife.subcommand_class_from(%w{cookbook site vendor --help foo bar baz})).to eq(:CookbookSiteVendor) end it "special case sets the subcommand_loader to GemGlobLoader when running rehash" do Chef::Knife.subcommands["rehash"] = :Rehash expect(Chef::Knife.subcommand_class_from(%w{rehash })).to eq(:Rehash) expect(Chef::Knife.subcommand_loader).to be_a(Chef::Knife::SubcommandLoader::GemGlobLoader) end end describe "the headers include X-Remote-Request-Id" do let(:headers) do { "Accept" => "application/json", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Chef-Version" => Chef::VERSION, "Host" => "api.opscode.piab", "X-REMOTE-REQUEST-ID" => request_id, } end let(:request_id) { "1234" } let(:request_mock) { {} } let(:rest) do allow(Net::HTTP).to receive(:new).and_return(http_client) allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id) allow(Chef::Config).to receive(:chef_server_url).and_return("https://api.opscode.piab") command = Chef::Knife.run(%w{test yourself}) rest = command.noauth_rest rest end let!(:http_client) do http_client = Net::HTTP.new(url.host, url.port) allow(http_client).to receive(:request).and_yield(http_response).and_return(http_response) http_client end let(:url) { URI.parse("https://api.opscode.piab") } let(:http_response) do http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req") allow(http_response).to receive(:read_body) allow(http_response).to receive(:body).and_return(body) http_response["Content-Length"] = body.bytesize.to_s http_response end let(:body) { "ninja" } before(:each) do Chef::Config[:chef_server_url] = "https://api.opscode.piab" if KnifeSpecs.const_defined?(:TestYourself) KnifeSpecs.send :remove_const, :TestYourself end Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_yourself.rb")) Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.kind_of?(Class) } end it "confirms that the headers include X-Remote-Request-Id" do expect(Net::HTTP::Get).to receive(:new).with("/monkey", headers).and_return(request_mock) rest.get("monkey") end end describe "when running a command" do before(:each) do if KnifeSpecs.const_defined?(:TestYourself) KnifeSpecs.send :remove_const, :TestYourself end Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_yourself.rb")) Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.kind_of?(Class) } end it "merges the global knife CLI options" do extra_opts = {} extra_opts[:editor] = { :long => "--editor EDITOR", :description => "Set the editor to use for interactive commands", :short => "-e EDITOR", :default => "/usr/bin/vim" } # there is special hackery to return the subcommand instance going on here. command = Chef::Knife.run(%w{test yourself}, extra_opts) editor_opts = command.options[:editor] expect(editor_opts[:long]).to eq("--editor EDITOR") expect(editor_opts[:description]).to eq("Set the editor to use for interactive commands") expect(editor_opts[:short]).to eq("-e EDITOR") expect(editor_opts[:default]).to eq("/usr/bin/vim") end it "creates an instance of the subcommand and runs it" do command = Chef::Knife.run(%w{test yourself}) expect(command).to be_an_instance_of(KnifeSpecs::TestYourself) expect(command.ran).to be_truthy end it "passes the command specific args to the subcommand" do command = Chef::Knife.run(%w{test yourself with some args}) expect(command.name_args).to eq(%w{with some args}) end it "excludes the command name from the name args when parts are joined with underscores" do command = Chef::Knife.run(%w{test_yourself with some args}) expect(command.name_args).to eq(%w{with some args}) end it "exits if no subcommand matches the CLI args" do stdout = StringIO.new allow(Chef::Knife.ui).to receive(:stderr).and_return(stderr) allow(Chef::Knife.ui).to receive(:stdout).and_return(stdout) expect(Chef::Knife.ui).to receive(:fatal) expect { Chef::Knife.run(%w{fuuu uuuu fuuuu}) }.to raise_error(SystemExit) { |e| expect(e.status).not_to eq(0) } end it "loads lazy dependencies" do Chef::Knife.run(%w{test yourself}) expect(KnifeSpecs::TestYourself.test_deps_loaded).to be_truthy end it "loads lazy dependencies from multiple deps calls" do other_deps_loaded = false KnifeSpecs::TestYourself.class_eval do deps { other_deps_loaded = true } end Chef::Knife.run(%w{test yourself}) expect(KnifeSpecs::TestYourself.test_deps_loaded).to be_truthy expect(other_deps_loaded).to be_truthy end describe "merging configuration options" do before do KnifeSpecs::TestYourself.option(:opt_with_default, :short => "-D VALUE", :default => "default-value") end it "sets the default log_location to STDERR for Chef::Log warnings" do knife_command = KnifeSpecs::TestYourself.new([]) knife_command.configure_chef expect(Chef::Config[:log_location]).to eq(STDERR) end it "sets the default log_level to warn so we can issue Chef::Log.warn" do knife_command = KnifeSpecs::TestYourself.new([]) knife_command.configure_chef expect(Chef::Config[:log_level]).to eql(:warn) end it "prefers the default value if no config or command line value is present" do knife_command = KnifeSpecs::TestYourself.new([]) #empty argv knife_command.configure_chef expect(knife_command.config[:opt_with_default]).to eq("default-value") end it "prefers a value in Chef::Config[:knife] to the default" do Chef::Config[:knife][:opt_with_default] = "from-knife-config" knife_command = KnifeSpecs::TestYourself.new([]) #empty argv knife_command.configure_chef expect(knife_command.config[:opt_with_default]).to eq("from-knife-config") end it "prefers a value from command line over Chef::Config and the default" do Chef::Config[:knife][:opt_with_default] = "from-knife-config" knife_command = KnifeSpecs::TestYourself.new(["-D", "from-cli"]) knife_command.configure_chef expect(knife_command.config[:opt_with_default]).to eq("from-cli") end it "merges `listen` config to Chef::Config" do Chef::Knife.run(%w{test yourself --no-listen}, Chef::Application::Knife.options) expect(Chef::Config[:listen]).to be(false) end context "verbosity is one" do let(:fake_config) { "/does/not/exist/knife.rb" } before do knife.config[:verbosity] = 1 knife.config[:config_file] = fake_config config_loader = double("Chef::WorkstationConfigLoader", :load => true, :no_config_found? => false, :chef_config_dir => "/etc/chef", :config_location => fake_config) allow(config_loader).to receive(:explicit_config_file=).with(fake_config).and_return(fake_config) allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader) end it "prints the path to the configuration file used" do stdout, stderr, stdin = StringIO.new, StringIO.new, StringIO.new knife.ui = Chef::Knife::UI.new(stdout, stderr, stdin, {}) expect(Chef::Log).to receive(:info).with("Using configuration from #{fake_config}") knife.configure_chef end end it "does not humanize the exception if Chef::Config[:verbosity] is two" do Chef::Config[:verbosity] = 2 allow(knife).to receive(:run).and_raise(Exception) expect(knife).not_to receive(:humanize_exception) expect { knife.run_with_pretty_exceptions }.to raise_error(Exception) end end end describe "when first created" do let(:knife) { KnifeSpecs::TestYourself.new(%w{with some args -s scrogramming}) } before do unless KnifeSpecs.const_defined?(:TestYourself) Kernel.load(File.join(CHEF_SPEC_DATA, "knife_subcommand", "test_yourself.rb")) end end it "it parses the options passed to it" do expect(knife.config[:scro]).to eq("scrogramming") end it "extracts its command specific args from the full arg list" do expect(knife.name_args).to eq(%w{with some args}) end it "does not have lazy dependencies loaded" do expect(knife.class.test_deps_loaded).not_to be_truthy end end describe "when formatting exceptions" do let(:stdout) { StringIO.new } let(:stderr) { StringIO.new } let(:stdin) { StringIO.new } let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) } before do knife.ui = ui expect(knife).to receive(:exit).with(100) end it "formats 401s nicely" do response = Net::HTTPUnauthorized.new("1.1", "401", "Unauthorized") response.instance_variable_set(:@read, true) # I hate you, net/http. allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "y u no syncronize your clock?")) allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("401 Unauthorized", response)) knife.run_with_pretty_exceptions expect(stderr.string).to match(/ERROR: Failed to authenticate to/) expect(stderr.string).to match(/Response: y u no syncronize your clock\?/) end it "formats 403s nicely" do response = Net::HTTPForbidden.new("1.1", "403", "Forbidden") response.instance_variable_set(:@read, true) # I hate you, net/http. allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "y u no administrator")) allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("403 Forbidden", response)) allow(knife).to receive(:username).and_return("sadpanda") knife.run_with_pretty_exceptions expect(stderr.string).to match(%r{ERROR: You authenticated successfully to http.+ as sadpanda but you are not authorized for this action}) expect(stderr.string).to match(%r{Response: y u no administrator}) end it "formats 400s nicely" do response = Net::HTTPBadRequest.new("1.1", "400", "Bad Request") response.instance_variable_set(:@read, true) # I hate you, net/http. allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "y u search wrong")) allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("400 Bad Request", response)) knife.run_with_pretty_exceptions expect(stderr.string).to match(%r{ERROR: The data in your request was invalid}) expect(stderr.string).to match(%r{Response: y u search wrong}) end it "formats 404s nicely" do response = Net::HTTPNotFound.new("1.1", "404", "Not Found") response.instance_variable_set(:@read, true) # I hate you, net/http. allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "nothing to see here")) allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("404 Not Found", response)) knife.run_with_pretty_exceptions expect(stderr.string).to match(%r{ERROR: The object you are looking for could not be found}) expect(stderr.string).to match(%r{Response: nothing to see here}) end it "formats 406s (non-supported API version error) nicely" do response = Net::HTTPNotAcceptable.new("1.1", "406", "Not Acceptable") response.instance_variable_set(:@read, true) # I hate you, net/http. # set the header response["x-ops-server-api-version"] = Chef::JSONCompat.to_json(:min_version => "0", :max_version => "1", :request_version => "10000000") allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "sad trombone")) allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("406 Not Acceptable", response)) knife.run_with_pretty_exceptions expect(stderr.string).to include("The request that Knife sent was using API version 10000000") expect(stderr.string).to include("The Chef server you sent the request to supports a min API verson of 0 and a max API version of 1") expect(stderr.string).to include("Please either update your Chef client or server to be a compatible set") end it "formats 500s nicely" do response = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error") response.instance_variable_set(:@read, true) # I hate you, net/http. allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "sad trombone")) allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("500 Internal Server Error", response)) knife.run_with_pretty_exceptions expect(stderr.string).to match(%r{ERROR: internal server error}) expect(stderr.string).to match(%r{Response: sad trombone}) end it "formats 502s nicely" do response = Net::HTTPBadGateway.new("1.1", "502", "Bad Gateway") response.instance_variable_set(:@read, true) # I hate you, net/http. allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "sadder trombone")) allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("502 Bad Gateway", response)) knife.run_with_pretty_exceptions expect(stderr.string).to match(%r{ERROR: bad gateway}) expect(stderr.string).to match(%r{Response: sadder trombone}) end it "formats 503s nicely" do response = Net::HTTPServiceUnavailable.new("1.1", "503", "Service Unavailable") response.instance_variable_set(:@read, true) # I hate you, net/http. allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "saddest trombone")) allow(knife).to receive(:run).and_raise(Net::HTTPFatalError.new("503 Service Unavailable", response)) knife.run_with_pretty_exceptions expect(stderr.string).to match(%r{ERROR: Service temporarily unavailable}) expect(stderr.string).to match(%r{Response: saddest trombone}) end it "formats other HTTP errors nicely" do response = Net::HTTPPaymentRequired.new("1.1", "402", "Payment Required") response.instance_variable_set(:@read, true) # I hate you, net/http. allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "nobugfixtillyoubuy")) allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("402 Payment Required", response)) knife.run_with_pretty_exceptions expect(stderr.string).to match(%r{ERROR: Payment Required}) expect(stderr.string).to match(%r{Response: nobugfixtillyoubuy}) end it "formats NameError and NoMethodError nicely" do allow(knife).to receive(:run).and_raise(NameError.new("Undefined constant FUUU")) knife.run_with_pretty_exceptions expect(stderr.string).to match(%r{ERROR: knife encountered an unexpected error}) expect(stderr.string).to match(%r{This may be a bug in the 'knife' knife command or plugin}) expect(stderr.string).to match(%r{Exception: NameError: Undefined constant FUUU}) end it "formats missing private key errors nicely" do allow(knife).to receive(:run).and_raise(Chef::Exceptions::PrivateKeyMissing.new("key not there")) allow(knife).to receive(:api_key).and_return("/home/root/.chef/no-key-here.pem") knife.run_with_pretty_exceptions expect(stderr.string).to match(%r{ERROR: Your private key could not be loaded from /home/root/.chef/no-key-here.pem}) expect(stderr.string).to match(%r{Check your configuration file and ensure that your private key is readable}) end it "formats connection refused errors nicely" do allow(knife).to receive(:run).and_raise(Errno::ECONNREFUSED.new("y u no shut up")) knife.run_with_pretty_exceptions # Errno::ECONNREFUSED message differs by platform # *nix = Errno::ECONNREFUSED: Connection refused # win32: Errno::ECONNREFUSED: No connection could be made because the target machine actively refused it. expect(stderr.string).to match(%r{ERROR: Network Error: .* - y u no shut up}) expect(stderr.string).to match(%r{Check your knife configuration and network settings}) end it "formats SSL errors nicely and suggests to use `knife ssl check` and `knife ssl fetch`" do error = OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed") allow(knife).to receive(:run).and_raise(error) knife.run_with_pretty_exceptions expected_message = <<-MSG ERROR: Could not establish a secure connection to the server. Use `knife ssl check` to troubleshoot your SSL configuration. If your Chef Server uses a self-signed certificate, you can use `knife ssl fetch` to make knife trust the server's certificates. MSG expect(stderr.string).to include(expected_message) end end end chef-12.14.60/spec/unit/lib_backcompat_spec.rb000066400000000000000000000030611276456504500210720ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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 "lib-backcompat" do it "require 'chef/chef_fs/file_system/chef_server_root_dir' yields the proper class" do require "chef/chef_fs/file_system/chef_server_root_dir" expect(Chef::ChefFS::FileSystem::ChefServerRootDir).to eq(Chef::ChefFS::FileSystem::ChefServer::ChefServerRootDir) end it "require 'chef/chef_fs/file_system/chef_repository_file_system_root_dir' yields the proper class" do require "chef/chef_fs/file_system/chef_repository_file_system_root_dir" expect(Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir).to eq(Chef::ChefFS::FileSystem::Repository::ChefRepositoryFileSystemRootDir) end it "require 'chef/chef_fs/file_system/acl_entry' yields the proper class" do require "chef/chef_fs/file_system/acl_entry" expect(Chef::ChefFS::FileSystem::AclEntry).to eq(Chef::ChefFS::FileSystem::ChefServer::AclEntry) end end chef-12.14.60/spec/unit/log/000077500000000000000000000000001276456504500153625ustar00rootroot00000000000000chef-12.14.60/spec/unit/log/syslog_spec.rb000066400000000000000000000035001276456504500202370ustar00rootroot00000000000000# # Author:: SAWANOBORI Yukihiko () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef" describe "Chef::Log::Syslog", :unix_only => true do let(:syslog) { Chef::Log::Syslog.new } let(:app) { Chef::Application.new } before do Chef::Log.init(MonoLogger.new(syslog)) @old_log_level = Chef::Log.level Chef::Log.level = :info @old_loggers = Chef::Log.loggers Chef::Log.use_log_devices([syslog]) end after do Chef::Log.level = @old_log_level Chef::Log.use_log_devices(@old_loggers) end it "should send message with severity info to syslog." do expect(syslog).to receive(:info).with("*** Chef 12.4.0.dev.0 ***") Chef::Log.info("*** Chef 12.4.0.dev.0 ***") end it "should send message with severity warning to syslog." do expect(syslog).to receive(:warn).with("No config file found or specified on command line, using command line options.") Chef::Log.warn("No config file found or specified on command line, using command line options.") end it "should fallback into send message with severity info to syslog when wrong format." do expect(syslog).to receive(:info).with("chef message") syslog.write("chef message") end end chef-12.14.60/spec/unit/log/winevt_spec.rb000066400000000000000000000036431276456504500202430ustar00rootroot00000000000000# # Author:: Jay Mundrawala (jdm@chef.io) # Author:: SAWANOBORI Yukihiko () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Log::WinEvt do let(:evtlog) { instance_double("Win32::EventLog") } let(:winevt) { Chef::Log::WinEvt.new(evtlog) } let(:app) { Chef::Application.new } before do Chef::Log.init(MonoLogger.new(winevt)) @old_log_level = Chef::Log.level Chef::Log.level = :info @old_loggers = Chef::Log.loggers Chef::Log.use_log_devices([winevt]) end after do Chef::Log.level = @old_log_level Chef::Log.use_log_devices(@old_loggers) end it "should send message with severity info to Windows Event Log." do expect(winevt).to receive(:info).with("*** Chef 12.4.0.dev.0 ***") Chef::Log.info("*** Chef 12.4.0.dev.0 ***") end it "should send message with severity warning to Windows Event Log." do expect(winevt).to receive(:warn).with("No config file found or specified on command line, using command line options.") Chef::Log.warn("No config file found or specified on command line, using command line options.") end it "should fallback into send message with severity info to Windows Event Log when wrong format." do expect(winevt).to receive(:info).with("chef message") winevt.write("chef message") end end chef-12.14.60/spec/unit/log_spec.rb000066400000000000000000000014011276456504500167150ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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 "tempfile" require "logger" require "spec_helper" describe Chef::Log do end chef-12.14.60/spec/unit/lwrp_spec.rb000066400000000000000000000711321276456504500171300ustar00rootroot00000000000000# # Author:: Christopher Walters () # Copyright:: Copyright 2009-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 "tmpdir" require "fileutils" require "chef/mixin/convert_to_class_name" module LwrpConstScopingConflict end describe "LWRP" do include Chef::Mixin::ConvertToClassName before do @original_verbose = $VERBOSE $VERBOSE = nil Chef::Resource::LWRPBase.class_eval { @loaded_lwrps = {} } end after do $VERBOSE = @original_verbose end def get_lwrp(name) Chef::ResourceResolver.resolve(name) end def get_lwrp_provider(name) old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] Chef::Config[:treat_deprecation_warnings_as_errors] = false begin Chef::Provider.const_get(convert_to_class_name(name.to_s)) ensure Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors end end describe "when overriding an existing class" do before :each do allow($stderr).to receive(:write) end it "should not skip loading a resource when there's a top level symbol of the same name" do Object.const_set("LwrpFoo", Class.new) file = File.expand_path( "lwrp/resources/foo.rb", CHEF_SPEC_DATA) expect(Chef::Log).not_to receive(:info).with(/Skipping/) expect(Chef::Log).not_to receive(:debug).with(/anymore/) Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) Object.send(:remove_const, "LwrpFoo") end it "should not skip loading a provider when there's a top level symbol of the same name" do Object.const_set("LwrpBuckPasser", Class.new) file = File.expand_path( "lwrp/providers/buck_passer.rb", CHEF_SPEC_DATA) expect(Chef::Log).not_to receive(:info).with(/Skipping/) expect(Chef::Log).not_to receive(:debug).with(/anymore/) Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) Object.send(:remove_const, "LwrpBuckPasser") end # @todo: we need a before block to manually remove_const all of the LWRPs that we # load in these tests. we're threading state through these tests in LWRPs that # have already been loaded in prior tests, which probably renders some of them bogus it "should log if attempting to load resource of same name" do Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| expect(Chef::Log).to receive(:debug).with(/Skipping/) Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end end it "should log if attempting to load provider of same name" do Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file| Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) end Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file| expect(Chef::Log).to receive(:debug).with(/Skipping/) Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil) end end it "keeps the old LRWP resource class in the list of resource subclasses" do # This was originally CHEF-3432 regression test. But with Chef 12 we are # not replacing the original classes anymore. Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end first_lwr_foo_class = get_lwrp(:lwrp_foo) expect(Chef::Resource.resource_classes).to include(first_lwr_foo_class) Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end expect(Chef::Resource.resource_classes).to include(first_lwr_foo_class) end it "does not attempt to remove classes from higher up namespaces [CHEF-4117]" do conflicting_lwrp_file = File.expand_path( "lwrp_const_scoping/resources/conflict.rb", CHEF_SPEC_DATA) # The test is that this should not raise an error: Chef::Resource::LWRPBase.build_from_file("lwrp_const_scoping", conflicting_lwrp_file, nil) end end context "When an LWRP resource in cookbook l-w-r-p is loaded" do before do @tmpdir = Dir.mktmpdir("lwrp_test") resource_path = File.join(@tmpdir, "foo.rb") IO.write(resource_path, "default_action :create") provider_path = File.join(@tmpdir, "foo.rb") IO.write(provider_path, <<-EOM) action :create do raise "hi" end EOM end it "Can find the resource at l_w_r_p_foo" do end end context "When an LWRP resource lwrp_foo is loaded" do before do @tmpdir = Dir.mktmpdir("lwrp_test") @lwrp_path = File.join(@tmpdir, "foo.rb") content = IO.read(File.expand_path("../../data/lwrp/resources/foo.rb", __FILE__)) IO.write(@lwrp_path, content) Chef::Resource::LWRPBase.build_from_file("lwrp", @lwrp_path, nil) @original_resource = Chef::ResourceResolver.resolve(:lwrp_foo) end after do FileUtils.remove_entry @tmpdir end context "And the LWRP is asked to load again, this time with different code" do before do content = IO.read(File.expand_path("../../data/lwrp_override/resources/foo.rb", __FILE__)) IO.write(@lwrp_path, content) Chef::Resource::LWRPBase.build_from_file("lwrp", @lwrp_path, nil) end it "Should load the old content, and not the new" do resource = Chef::ResourceResolver.resolve(:lwrp_foo) expect(resource).to eq @original_resource expect(resource.default_action).to eq([:pass_buck]) expect(Chef.method_defined?(:method_created_by_override_lwrp_foo)).to be_falsey end end end describe "Lightweight Chef::Resource" do before do Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil) end end it "should be resolvable with Chef::ResourceResolver.resolve(:lwrp_foo)" do expect(Chef::ResourceResolver.resolve(:lwrp_foo, node: Chef::Node.new)).to eq(get_lwrp(:lwrp_foo)) end it "should set resource_name" do expect(get_lwrp(:lwrp_foo).new("blah").resource_name).to eql(:lwrp_foo) end it "should output the resource_name in .to_s" do expect(get_lwrp(:lwrp_foo).new("blah").to_s).to eq "lwrp_foo[blah]" end it "should have a class that outputs a reasonable string" do expect(get_lwrp(:lwrp_foo).to_s).to eq "Custom resource lwrp_foo from cookbook lwrp" end it "should add the specified actions to the allowed_actions array" do expect(get_lwrp(:lwrp_foo).new("blah").allowed_actions).to include(:pass_buck, :twiddle_thumbs) end it "should set the specified action as the default action" do expect(get_lwrp(:lwrp_foo).new("blah").action).to eq([:pass_buck]) end it "should create a method for each attribute" do expect(get_lwrp(:lwrp_foo).new("blah").methods.map { |m| m.to_sym }).to include(:monkey) end it "should build attribute methods that respect validation rules" do expect { get_lwrp(:lwrp_foo).new("blah").monkey(42) }.to raise_error(ArgumentError) end it "should have access to the run context and node during class definition" do node = Chef::Node.new node.normal[:penguin_name] = "jackass" run_context = Chef::RunContext.new(node, Chef::CookbookCollection.new, @events) Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources_with_default_attributes", "*"))].each do |file| Chef::Resource::LWRPBase.build_from_file("lwrp", file, run_context) end cls = get_lwrp(:lwrp_nodeattr) expect(cls.node).to be_kind_of(Chef::Node) expect(cls.run_context).to be_kind_of(Chef::RunContext) expect(cls.node[:penguin_name]).to eql("jackass") end context "resource_name" do let(:klass) { Class.new(Chef::Resource::LWRPBase) } it "returns nil when the resource_name is not set" do expect(klass.resource_name).to be_nil end it "allows to user to user the resource_name" do expect do klass.resource_name(:foo) end.to_not raise_error end it "returns the set value for the resource" do klass.resource_name(:foo) expect(klass.resource_name).to eq(:foo) end context "lazy default values" do let(:klass) do Class.new(Chef::Resource::LWRPBase) do self.resource_name = :sample_resource attribute :food, :default => lazy { "BACON!" * 3 } attribute :drink, :default => lazy { |r| "Drink after #{r.food}!" } end end let(:instance) { klass.new("kitchen") } it "evaluates the default value when requested" do expect(instance.food).to eq("BACON!BACON!BACON!") end it "evaluates yields self to the block" do expect(instance.drink).to eq("Drink after BACON!BACON!BACON!!") end end end describe "when #default_action is an array" do let(:lwrp) do Class.new(Chef::Resource::LWRPBase) do actions :eat, :sleep default_action [:eat, :sleep] end end it "returns the array of default actions" do expect(lwrp.default_action).to eq([:eat, :sleep]) end end describe "when inheriting from LWRPBase" do let(:parent) do Class.new(Chef::Resource::LWRPBase) do actions :eat, :sleep default_action :eat end end context "when the child does not define the methods" do let(:child) do Class.new(parent) end it "delegates #actions to the parent" do expect(child.actions).to eq([:nothing, :eat, :sleep]) end it "delegates #default_action to the parent" do expect(child.default_action).to eq([:eat]) end end context "when the child does define the methods" do let(:child) do Class.new(parent) do actions :dont_eat, :dont_sleep default_action :dont_eat end end it "does not delegate #actions to the parent" do expect(child.actions).to eq([:nothing, :dont_eat, :dont_sleep]) end it "does not delegate #default_action to the parent" do expect(child.default_action).to eq([:dont_eat]) end end context "when actions are already defined" do let(:child) do Class.new(parent) do actions :eat actions :sleep actions :drink end end def raise_if_deprecated! if Chef::VERSION.split(".").first.to_i > 12 raise "This test should be removed and the associated code should be removed!" end end it "amends actions when they are already defined" do raise_if_deprecated! expect(child.actions).to eq([:nothing, :eat, :sleep, :drink]) end end end describe "when actions is set to an array" do let(:resource_class) do Class.new(Chef::Resource::LWRPBase) do actions [ :eat, :sleep ] end end let(:resource) do resource_class.new("blah") end it "actions includes those actions" do expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ] end it "allowed_actions includes those actions" do expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ] end it "resource.allowed_actions includes those actions" do expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ] end end describe "when allowed_actions is set to an array" do let(:resource_class) do Class.new(Chef::Resource::LWRPBase) do allowed_actions [ :eat, :sleep ] end end let(:resource) do resource_class.new("blah") end it "actions includes those actions" do expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ] end it "allowed_actions includes those actions" do expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ] end it "resource.allowed_actions includes those actions" do expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ] end end end describe "Lightweight Chef::Provider" do let(:node) do Chef::Node.new.tap do |n| n.automatic[:platform] = :ubuntu n.automatic[:platform_version] = "8.10" end end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, Chef::CookbookCollection.new({}), events) } let(:runner) { Chef::Runner.new(run_context) } let(:lwrp_cookbok_name) { "lwrp" } before do Chef::Provider::LWRPBase.class_eval { @loaded_lwrps = {} } end before(:each) do Dir[File.expand_path(File.expand_path("../../data/lwrp/resources/*", __FILE__))].each do |file| Chef::Resource::LWRPBase.build_from_file(lwrp_cookbok_name, file, run_context) end Dir[File.expand_path(File.expand_path("../../data/lwrp/providers/*", __FILE__))].each do |file| Chef::Provider::LWRPBase.build_from_file(lwrp_cookbok_name, file, run_context) end end it "should properly handle a new_resource reference" do resource = get_lwrp(:lwrp_foo).new("morpheus", run_context) resource.monkey("bob") resource.provider(get_lwrp_provider(:lwrp_monkey_name_printer)) provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs) provider.action_twiddle_thumbs end context "provider class created" do before do @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] Chef::Config[:treat_deprecation_warnings_as_errors] = false end after do Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors end it "should load the provider into a properly-named class" do expect(Chef::Provider.const_get("LwrpBuckPasser")).to be_kind_of(Class) expect(Chef::Provider::LwrpBuckPasser <= Chef::Provider::LWRPBase).to be_truthy end it "should create a method for each action" do expect(get_lwrp_provider(:lwrp_buck_passer).instance_methods).to include(:action_pass_buck) expect(get_lwrp_provider(:lwrp_thumb_twiddler).instance_methods).to include(:action_twiddle_thumbs) end it "sets itself as a provider for a resource of the same name" do found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :lwrp_buck_passer) # we bypass the per-file loading to get the file to load each time, # which creates the LWRP class repeatedly. New things get prepended to # the list of providers. expect(found_providers.first).to eq(get_lwrp_provider(:lwrp_buck_passer)) end context "with a cookbook with an underscore in the name" do let(:lwrp_cookbok_name) { "l_w_r_p" } it "sets itself as a provider for a resource of the same name" do found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer) expect(found_providers.size).to eq(1) expect(found_providers.last).to eq(get_lwrp_provider(:l_w_r_p_buck_passer)) end end context "with a cookbook with a hypen in the name" do let(:lwrp_cookbok_name) { "l-w-r-p" } it "sets itself as a provider for a resource of the same name" do incorrect_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :'l-w-r-p_buck_passer') expect(incorrect_providers).to eq([]) found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer) expect(found_providers.first).to eq(get_lwrp_provider(:l_w_r_p_buck_passer)) end end end it "should insert resources embedded in the provider into the middle of the resource collection" do injector = get_lwrp(:lwrp_foo).new("morpheus", run_context) injector.action(:pass_buck) injector.provider(get_lwrp_provider(:lwrp_buck_passer)) dummy = Chef::Resource::ZenMaster.new("keanu reeves", run_context) dummy.provider(Chef::Provider::Easy) run_context.resource_collection.insert(injector) run_context.resource_collection.insert(dummy) Chef::Runner.new(run_context).converge expect(run_context.resource_collection[0]).to eql(injector) expect(run_context.resource_collection[1].name).to eql("prepared_thumbs") expect(run_context.resource_collection[2].name).to eql("twiddled_thumbs") expect(run_context.resource_collection[3]).to eql(dummy) end it "should insert embedded resources from multiple providers, including from the last position, properly into the resource collection" do injector = get_lwrp(:lwrp_foo).new("morpheus", run_context) injector.action(:pass_buck) injector.provider(get_lwrp_provider(:lwrp_buck_passer)) injector2 = get_lwrp(:lwrp_bar).new("tank", run_context) injector2.action(:pass_buck) injector2.provider(get_lwrp_provider(:lwrp_buck_passer_2)) dummy = Chef::Resource::ZenMaster.new("keanu reeves", run_context) dummy.provider(Chef::Provider::Easy) run_context.resource_collection.insert(injector) run_context.resource_collection.insert(dummy) run_context.resource_collection.insert(injector2) Chef::Runner.new(run_context).converge expect(run_context.resource_collection[0]).to eql(injector) expect(run_context.resource_collection[1].name).to eql("prepared_thumbs") expect(run_context.resource_collection[2].name).to eql("twiddled_thumbs") expect(run_context.resource_collection[3]).to eql(dummy) expect(run_context.resource_collection[4]).to eql(injector2) expect(run_context.resource_collection[5].name).to eql("prepared_eyes") expect(run_context.resource_collection[6].name).to eql("dried_paint_watched") end it "should properly handle a new_resource reference" do resource = get_lwrp(:lwrp_foo).new("morpheus", run_context) resource.monkey("bob") resource.provider(get_lwrp_provider(:lwrp_monkey_name_printer)) provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs) provider.action_twiddle_thumbs expect(provider.monkey_name).to eq("my monkey's name is 'bob'") end it "should properly handle an embedded Resource accessing the enclosing Provider's scope" do resource = get_lwrp(:lwrp_foo).new("morpheus", run_context) resource.monkey("bob") resource.provider(get_lwrp_provider(:lwrp_embedded_resource_accesses_providers_scope)) provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs) #provider = @runner.build_provider(resource) provider.action_twiddle_thumbs expect(provider.enclosed_resource.monkey).to eq("bob, the monkey") end describe "when using inline compilation" do before do # Behavior in these examples depends on implementation of fixture provider. # See spec/data/lwrp/providers/inline_compiler # Side effect of lwrp_inline_compiler provider for testing notifications. $interior_ruby_block_2 = nil # resource type doesn't matter, so make an existing resource type work with provider. @resource = get_lwrp(:lwrp_foo).new("morpheus", run_context) @resource.allowed_actions << :test @resource.action(:test) @resource.provider(get_lwrp_provider(:lwrp_inline_compiler)) end it "does not add interior resources to the exterior resource collection" do @resource.run_action(:test) expect(run_context.resource_collection).to be_empty end context "when interior resources are updated" do it "processes notifications within the LWRP provider's action" do @resource.run_action(:test) expect($interior_ruby_block_2).to eq("executed") end it "marks the parent resource updated" do @resource.run_action(:test) expect(@resource).to be_updated expect(@resource).to be_updated_by_last_action end end context "when interior resources are not updated" do it "does not mark the parent resource updated" do @resource.run_action(:no_updates) expect(@resource).not_to be_updated expect(@resource).not_to be_updated_by_last_action end end end end context "resource class created" do before(:context) do @tmpdir = Dir.mktmpdir("lwrp_test") resource_path = File.join(@tmpdir, "once.rb") IO.write(resource_path, "default_action :create") @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors] Chef::Config[:treat_deprecation_warnings_as_errors] = false Chef::Resource::LWRPBase.build_from_file("lwrp", resource_path, nil) end after(:context) do FileUtils.remove_entry @tmpdir Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors end it "should load the resource into a properly-named class" do expect(Chef::Resource::LwrpOnce).to be_kind_of(Class) expect(Chef::Resource::LwrpOnce <= Chef::Resource::LWRPBase).to be_truthy end it "get_lwrp(:lwrp_once).new is a Chef::Resource::LwrpOnce" do lwrp = get_lwrp(:lwrp_once).new("hi") expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy end it "Chef::Resource::LwrpOnce.new is a get_lwrp(:lwrp_once)" do lwrp = Chef::Resource::LwrpOnce.new("hi") expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy end it "works even if LwrpOnce exists in the top level" do module ::LwrpOnce end expect(Chef::Resource::LwrpOnce).not_to eq(::LwrpOnce) end it "allows monkey patching of the lwrp through Chef::Resource" do monkey = Module.new do def issue_3607 end end Chef::Resource::LwrpOnce.send(:include, monkey) expect { get_lwrp(:lwrp_once).new("blah").issue_3607 }.not_to raise_error end context "with a subclass of get_lwrp(:lwrp_once)" do let(:subclass) do Class.new(get_lwrp(:lwrp_once)) end it "subclass.new is a subclass" do lwrp = subclass.new("hi") expect(lwrp.kind_of?(subclass)).to be_truthy expect(lwrp.is_a?(subclass)).to be_truthy expect(subclass === lwrp).to be_truthy expect(lwrp.class === subclass) end it "subclass.new is a Chef::Resource::LwrpOnce" do lwrp = subclass.new("hi") expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy expect(lwrp.class === Chef::Resource::LwrpOnce) end it "subclass.new is a get_lwrp(:lwrp_once)" do lwrp = subclass.new("hi") expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy expect(lwrp.class === get_lwrp(:lwrp_once)) end it "Chef::Resource::LwrpOnce.new is *not* a subclass" do lwrp = Chef::Resource::LwrpOnce.new("hi") expect(lwrp.kind_of?(subclass)).to be_falsey expect(lwrp.is_a?(subclass)).to be_falsey expect(subclass === lwrp.class).to be_falsey expect(subclass === Chef::Resource::LwrpOnce).to be_falsey end it "get_lwrp(:lwrp_once).new is *not* a subclass" do lwrp = get_lwrp(:lwrp_once).new("hi") expect(lwrp.kind_of?(subclass)).to be_falsey expect(lwrp.is_a?(subclass)).to be_falsey expect(subclass === lwrp.class).to be_falsey expect(subclass === get_lwrp(:lwrp_once)).to be_falsey end end context "with a subclass of Chef::Resource::LwrpOnce" do let(:subclass) do Class.new(Chef::Resource::LwrpOnce) end it "subclass.new is a subclass" do lwrp = subclass.new("hi") expect(lwrp.kind_of?(subclass)).to be_truthy expect(lwrp.is_a?(subclass)).to be_truthy expect(subclass === lwrp).to be_truthy expect(lwrp.class === subclass) end it "subclass.new is a Chef::Resource::LwrpOnce" do lwrp = subclass.new("hi") expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy expect(lwrp.class === Chef::Resource::LwrpOnce) end it "subclass.new is a get_lwrp(:lwrp_once)" do lwrp = subclass.new("hi") expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy expect(lwrp.class === get_lwrp(:lwrp_once)) end it "Chef::Resource::LwrpOnce.new is *not* a subclass" do lwrp = Chef::Resource::LwrpOnce.new("hi") expect(lwrp.kind_of?(subclass)).to be_falsey expect(lwrp.is_a?(subclass)).to be_falsey expect(subclass === lwrp.class).to be_falsey expect(subclass === Chef::Resource::LwrpOnce).to be_falsey end it "get_lwrp(:lwrp_once).new is *not* a subclass" do lwrp = get_lwrp(:lwrp_once).new("hi") expect(lwrp.kind_of?(subclass)).to be_falsey expect(lwrp.is_a?(subclass)).to be_falsey expect(subclass === lwrp.class).to be_falsey expect(subclass === get_lwrp(:lwrp_once)).to be_falsey end end end describe "extending the DSL mixin" do module MyAwesomeDSLExensionClass def my_awesome_dsl_extension(argument) argument end end class MyAwesomeResource < Chef::Resource::LWRPBase provides :my_awesome_resource resource_name :my_awesome_resource default_action :create end class MyAwesomeProvider < Chef::Provider::LWRPBase use_inline_resources provides :my_awesome_resource action :create do my_awesome_dsl_extension("foo") end end let(:recipe) do cookbook_repo = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks")) cookbook_loader = Chef::CookbookLoader.new(cookbook_repo) cookbook_loader.load_cookbooks cookbook_collection = Chef::CookbookCollection.new(cookbook_loader) node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, cookbook_collection, events) Chef::Recipe.new("hjk", "test", run_context) end it "lets you extend the recipe DSL" do expect(Chef::Recipe).to receive(:include).with(MyAwesomeDSLExensionClass) expect(Chef::Resource::ActionClass).to receive(:include).with(MyAwesomeDSLExensionClass) Chef::DSL::Recipe.send(:include, MyAwesomeDSLExensionClass) end it "lets you call your DSL from a recipe" do Chef::DSL::Recipe.send(:include, MyAwesomeDSLExensionClass) expect(recipe.my_awesome_dsl_extension("foo")).to eql("foo") end it "lets you call your DSL from a provider" do Chef::DSL::Recipe.send(:include, MyAwesomeDSLExensionClass) resource = MyAwesomeResource.new("name", run_context) run_context.resource_collection << resource runner = Chef::Runner.new(run_context) expect_any_instance_of(MyAwesomeProvider).to receive(:my_awesome_dsl_extension).and_call_original runner.converge end end end chef-12.14.60/spec/unit/mash_spec.rb000066400000000000000000000031731276456504500170740ustar00rootroot00000000000000# # Author:: Matthew Kent () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mash" describe Mash do it "should duplicate a simple key/value mash to a new mash" do data = { :x => "one", :y => "two", :z => "three" } @orig = Mash.new(data) @copy = @orig.dup expect(@copy.to_hash).to eq(Mash.new(data).to_hash) @copy[:x] = "four" expect(@orig[:x]).to eq("one") end it "should duplicate a mash with an array to a new mash" do data = { :x => "one", :y => "two", :z => [1, 2, 3] } @orig = Mash.new(data) @copy = @orig.dup expect(@copy.to_hash).to eq(Mash.new(data).to_hash) @copy[:z] << 4 expect(@orig[:z]).to eq([1, 2, 3]) end it "should duplicate a nested mash to a new mash" do data = { :x => "one", :y => "two", :z => Mash.new({ :a => [1, 2, 3] }) } @orig = Mash.new(data) @copy = @orig.dup expect(@copy.to_hash).to eq(Mash.new(data).to_hash) @copy[:z][:a] << 4 expect(@orig[:z][:a]).to eq([1, 2, 3]) end # add more! end chef-12.14.60/spec/unit/mixin/000077500000000000000000000000001276456504500157255ustar00rootroot00000000000000chef-12.14.60/spec/unit/mixin/api_version_request_handling_spec.rb000066400000000000000000000116501276456504500252210ustar00rootroot00000000000000# # Author:: Tyler Cloke (tyler@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Mixin::ApiVersionRequestHandling do let(:dummy_class) { Class.new { include Chef::Mixin::ApiVersionRequestHandling } } let(:object) { dummy_class.new } describe ".server_client_api_version_intersection" do let(:default_supported_client_versions) { [0, 1, 2] } context "when the response code is not 406" do let(:response) { OpenStruct.new(:code => "405") } let(:exception) { Net::HTTPServerException.new("405 Something Else", response) } it "returns nil" do expect(object.server_client_api_version_intersection(exception, default_supported_client_versions)). to be_nil end end # when the response code is not 406 context "when the response code is 406" do let(:response) { OpenStruct.new(:code => "406") } let(:exception) { Net::HTTPServerException.new("406 Not Acceptable", response) } context "when x-ops-server-api-version header does not exist" do it "returns nil" do expect(object.server_client_api_version_intersection(exception, default_supported_client_versions)). to be_nil end end # when x-ops-server-api-version header does not exist context "when x-ops-server-api-version header exists" do let(:min_server_version) { 2 } let(:max_server_version) { 4 } let(:return_hash) do { "min_version" => min_server_version, "max_version" => max_server_version, } end before(:each) do allow(response).to receive(:[]).with("x-ops-server-api-version").and_return(Chef::JSONCompat.to_json(return_hash)) end context "when there is no intersection between client and server versions" do shared_examples_for "no intersection between client and server versions" do it "return an array" do expect(object.server_client_api_version_intersection(exception, supported_client_versions)). to be_a_kind_of(Array) end it "returns an empty array" do expect(object.server_client_api_version_intersection(exception, supported_client_versions).length). to eq(0) end end context "when all the versions are higher than the max" do it_should_behave_like "no intersection between client and server versions" do let(:supported_client_versions) { [5, 6, 7] } end end context "when all the versions are lower than the min" do it_should_behave_like "no intersection between client and server versions" do let(:supported_client_versions) { [0, 1] } end end end # when there is no intersection between client and server versions context "when there is an intersection between client and server versions" do context "when multiple versions intersect" do let(:supported_client_versions) { [1, 2, 3, 4, 5] } it "includes all of the intersection" do expect(object.server_client_api_version_intersection(exception, supported_client_versions)). to eq([2, 3, 4]) end end # when multiple versions intersect context "when only the min client version intersects" do let(:supported_client_versions) { [0, 1, 2] } it "includes the intersection" do expect(object.server_client_api_version_intersection(exception, supported_client_versions)). to eq([2]) end end # when only the min client version intersects context "when only the max client version intersects" do let(:supported_client_versions) { [4, 5, 6] } it "includes the intersection" do expect(object.server_client_api_version_intersection(exception, supported_client_versions)). to eq([4]) end end # when only the max client version intersects end # when there is an intersection between client and server versions end # when x-ops-server-api-version header exists end # when the response code is 406 end # .server_client_api_version_intersection end # Chef::Mixin::ApiVersionRequestHandling chef-12.14.60/spec/unit/mixin/checksum_spec.rb000066400000000000000000000024001276456504500210620ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-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/mixin/checksum" require "stringio" class Chef::CMCCheck include Chef::Mixin::Checksum end describe Chef::Mixin::Checksum do before(:each) do @checksum_user = Chef::CMCCheck.new @cache = Chef::Digester.instance @file = CHEF_SPEC_DATA + "/checksum/random.txt" @stat = double("File::Stat", { :mtime => Time.at(0) }) allow(File).to receive(:stat).and_return(@stat) end it "gets the checksum of a file" do expect(@checksum_user.checksum(@file)).to eq("09ee9c8cc70501763563bcf9c218d71b2fbf4186bf8e1e0da07f0f42c80a3394") end end chef-12.14.60/spec/unit/mixin/command_spec.rb000066400000000000000000000071011276456504500207010ustar00rootroot00000000000000# # Author:: Hongli Lai (hongli@phusion.nl) # Copyright:: Copyright 2009-2016, Phusion # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Mixin::Command, :volatile do if windows? skip("TODO MOVE: this is a platform specific integration test.") else describe "popen4" do include Chef::Mixin::Command it "should be possible to read the child process's stdout and stderr" do popen4("sh -c 'echo hello && echo world >&2'") do |pid, stdin, stdout, stderr| expect(stdout.read).to eq("hello\n") expect(stderr.read).to eq("world\n") end end it "should default all commands to be run in the POSIX standard C locale" do popen4("echo $LC_ALL") do |pid, stdin, stdout, stderr| expect(stdout.read.strip).to eq("C") end end it "should respect locale when specified explicitly" do popen4("echo $LC_ALL", :environment => { "LC_ALL" => "es" }) do |pid, stdin, stdout, stderr| expect(stdout.read.strip).to eq("es") end end it "should end when the child process reads from STDIN and a block is given" do expect do Timeout.timeout(10) do popen4("ruby -e 'while gets; end'", :waitlast => true) do |pid, stdin, stdout, stderr| (1..5).each { |i| stdin.puts "#{i}" } end end end.not_to raise_error end describe "when a process detaches but doesn't close STDOUT and STDERR [CHEF-584]" do it "returns immediately after the first child process exits" do expect do Timeout.timeout(10) do evil_forker = "exit if fork; 10.times { sleep 1}" popen4("ruby -e '#{evil_forker}'") do |pid, stdin, stdout, stderr| end end end.not_to raise_error end end end describe "run_command" do include Chef::Mixin::Command it "logs the command's stderr and stdout output if the command failed" do allow(Chef::Log).to receive(:level).and_return(:debug) begin run_command(:command => "sh -c 'echo hello; echo world >&2; false'") violated "Exception expected, but nothing raised." rescue => e expect(e.message).to match(/STDOUT: hello/) expect(e.message).to match(/STDERR: world/) end end describe "when a process detaches but doesn't close STDOUT and STDERR [CHEF-584]" do it "returns successfully" do # CHEF-2916 might have added a slight delay here, or our CI # infrastructure is burdened. Bumping timeout from 2 => 4 -- # btm # Serdar - During Solaris tests, we've seen that processes # are taking a long time to exit. Bumping timeout now to 10. expect do Timeout.timeout(10) do evil_forker = "exit if fork; 10.times { sleep 1}" run_command(:command => "ruby -e '#{evil_forker}'") end end.not_to raise_error end end end end end chef-12.14.60/spec/unit/mixin/convert_to_class_name_spec.rb000066400000000000000000000035531276456504500236410ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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" class ConvertToClassTestHarness include Chef::Mixin::ConvertToClassName end describe Chef::Mixin::ConvertToClassName do before do @convert = ConvertToClassTestHarness.new end it "converts a_snake_case_word to a CamelCaseWord" do expect(@convert.convert_to_class_name("now_camelized")).to eq("NowCamelized") end it "converts a CamelCaseWord to a snake_case_word" do expect(@convert.convert_to_snake_case("NowImASnake")).to eq("now_im_a_snake") end it "removes the base classes before snake casing" do expect(@convert.convert_to_snake_case("NameSpaced::Class::ThisIsWin", "NameSpaced::Class")).to eq("this_is_win") end it "removes the base classes without explicitly naming them and returns snake case" do expect(@convert.snake_case_basename("NameSpaced::Class::ExtraWin")).to eq("extra_win") end it "interprets non-alphanumeric characters in snake case as word boundaries" do expect(@convert.convert_to_class_name("now_camelized_without-hyphen")).to eq("NowCamelizedWithoutHyphen") end it "interprets underscore" do expect(@convert.convert_to_class_name("_remove_leading_underscore")).to eq("RemoveLeadingUnderscore") end end chef-12.14.60/spec/unit/mixin/deep_merge_spec.rb000066400000000000000000000370271276456504500213710ustar00rootroot00000000000000# # Author:: Matthew Kent () # Author:: Steve Midgley (http://www.misuse.org/science) # Copyright:: Copyright 2010-2016, Matthew Kent # Copyright:: Copyright 2008-2016, Steve Midgley # 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. # Notice: # This code is imported from deep_merge by Steve Midgley. deep_merge is # available under the MIT license from # http://trac.misuse.org/science/wiki/DeepMerge require "spec_helper" # Test coverage from the original author converted to rspec describe Chef::Mixin::DeepMerge, "deep_merge!" do before do @dm = Chef::Mixin::DeepMerge @field_ko_prefix = "!merge" end # deep_merge core tests - moving from basic to more complex it "tests merging an hash w/array into blank hash" do hash_src = { "id" => "2" } hash_dst = {} @dm.deep_merge!(hash_src.dup, hash_dst) expect(hash_dst).to eq(hash_src) end it "tests merging an hash w/array into blank hash" do hash_src = { "region" => { "id" => %w{227 2} } } hash_dst = {} @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq(hash_src) end it "tests merge from empty hash" do hash_src = {} hash_dst = { "property" => %w{2 4} } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => %w{2 4} }) end it "tests merge to empty hash" do hash_src = { "property" => %w{2 4} } hash_dst = {} @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => %w{2 4} }) end it "tests simple string overwrite" do hash_src = { "name" => "value" } hash_dst = { "name" => "value1" } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "name" => "value" }) end it "tests simple string overwrite of empty hash" do hash_src = { "name" => "value" } hash_dst = {} @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq(hash_src) end it "tests hashes holding array" do hash_src = { "property" => %w{1 3} } hash_dst = { "property" => %w{2 4} } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => %w{2 4 1 3} }) end it "tests hashes holding hashes holding arrays (array with duplicate elements is merged with dest then src" do hash_src = { "property" => { "bedroom_count" => %w{1 2}, "bathroom_count" => ["1", "4+"] } } hash_dst = { "property" => { "bedroom_count" => %w{3 2}, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => %w{3 2 1}, "bathroom_count" => ["2", "1", "4+"] } }) end it "tests hash holding hash holding array v string (string is overwritten by array)" do hash_src = { "property" => { "bedroom_count" => %w{1 2}, "bathroom_count" => ["1", "4+"] } } hash_dst = { "property" => { "bedroom_count" => "3", "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => %w{1 2}, "bathroom_count" => ["2", "1", "4+"] } }) end it "tests hash holding hash holding string v array (array is overwritten by string)" do hash_src = { "property" => { "bedroom_count" => "3", "bathroom_count" => ["1", "4+"] } } hash_dst = { "property" => { "bedroom_count" => %w{1 2}, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => "3", "bathroom_count" => ["2", "1", "4+"] } }) end it "tests hash holding hash holding hash v array (array is overwritten by hash)" do hash_src = { "property" => { "bedroom_count" => { "king_bed" => 3, "queen_bed" => 1 }, "bathroom_count" => ["1", "4+"] } } hash_dst = { "property" => { "bedroom_count" => %w{1 2}, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => { "king_bed" => 3, "queen_bed" => 1 }, "bathroom_count" => ["2", "1", "4+"] } }) end it "tests 3 hash layers holding integers (integers are overwritten by source)" do hash_src = { "property" => { "bedroom_count" => { "king_bed" => 3, "queen_bed" => 1 }, "bathroom_count" => ["1", "4+"] } } hash_dst = { "property" => { "bedroom_count" => { "king_bed" => 2, "queen_bed" => 4 }, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => { "king_bed" => 3, "queen_bed" => 1 }, "bathroom_count" => ["2", "1", "4+"] } }) end it "tests 3 hash layers holding arrays of int (arrays are merged)" do hash_src = { "property" => { "bedroom_count" => { "king_bed" => [3], "queen_bed" => [1] }, "bathroom_count" => ["1", "4+"] } } hash_dst = { "property" => { "bedroom_count" => { "king_bed" => [2], "queen_bed" => [4] }, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => { "king_bed" => [2, 3], "queen_bed" => [4, 1] }, "bathroom_count" => ["2", "1", "4+"] } }) end it "tests 1 hash overwriting 3 hash layers holding arrays of int" do hash_src = { "property" => "1" } hash_dst = { "property" => { "bedroom_count" => { "king_bed" => [2], "queen_bed" => [4] }, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => "1" }) end it "tests 3 hash layers holding arrays of int (arrays are merged) but second hash's array is overwritten" do hash_src = { "property" => { "bedroom_count" => { "king_bed" => [3], "queen_bed" => [1] }, "bathroom_count" => "1" } } hash_dst = { "property" => { "bedroom_count" => { "king_bed" => [2], "queen_bed" => [4] }, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => { "king_bed" => [2, 3], "queen_bed" => [4, 1] }, "bathroom_count" => "1" } }) end it "tests 3 hash layers holding arrays of int, but one holds int. This one overwrites, but the rest merge" do hash_src = { "property" => { "bedroom_count" => { "king_bed" => 3, "queen_bed" => [1] }, "bathroom_count" => ["1"] } } hash_dst = { "property" => { "bedroom_count" => { "king_bed" => [2], "queen_bed" => [4] }, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => { "king_bed" => 3, "queen_bed" => [4, 1] }, "bathroom_count" => %w{2 1} } }) end it "tests 3 hash layers holding arrays of int, but source is incomplete." do hash_src = { "property" => { "bedroom_count" => { "king_bed" => [3] }, "bathroom_count" => ["1"] } } hash_dst = { "property" => { "bedroom_count" => { "king_bed" => [2], "queen_bed" => [4] }, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => { "king_bed" => [2, 3], "queen_bed" => [4] }, "bathroom_count" => %w{2 1} } }) end it "tests 3 hash layers holding arrays of int, but source is shorter and has new 2nd level ints." do hash_src = { "property" => { "bedroom_count" => { 2 => 3, "king_bed" => [3] }, "bathroom_count" => ["1"] } } hash_dst = { "property" => { "bedroom_count" => { "king_bed" => [2], "queen_bed" => [4] }, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => { 2 => 3, "king_bed" => [2, 3], "queen_bed" => [4] }, "bathroom_count" => %w{2 1} } }) end it "tests 3 hash layers holding arrays of int, but source is empty" do hash_src = {} hash_dst = { "property" => { "bedroom_count" => { "king_bed" => [2], "queen_bed" => [4] }, "bathroom_count" => ["2"] } } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => { "king_bed" => [2], "queen_bed" => [4] }, "bathroom_count" => ["2"] } }) end it "tests 3 hash layers holding arrays of int, but dest is empty" do hash_src = { "property" => { "bedroom_count" => { 2 => 3, "king_bed" => [3] }, "bathroom_count" => ["1"] } } hash_dst = {} @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => { 2 => 3, "king_bed" => [3] }, "bathroom_count" => ["1"] } }) end it "tests hash holding arrays of arrays" do hash_src = { %w{1 2 3} => %w{1 2} } hash_dst = { %w{4 5} => ["3"] } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ %w{1 2 3} => %w{1 2}, %w{4 5} => ["3"] }) end it "tests merging of hash with blank hash, and make sure that source array split does not function when turned off" do hash_src = { "property" => { "bedroom_count" => ["1", "2,3"] } } hash_dst = {} @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "property" => { "bedroom_count" => ["1", "2,3"] } }) end it "tests merging into a blank hash" do hash_src = { "action" => "browse", "controller" => "results" } hash_dst = {} @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq(hash_src) end it "tests are unmerged hashes passed unmodified w/out :unpack_arrays?" do hash_src = { "amenity" => { "id" => ["26,27"] } } hash_dst = {} @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "amenity" => { "id" => ["26,27"] } }) end it "tests hash of array of hashes" do hash_src = { "item" => [{ "1" => "3" }, { "2" => "4" }] } hash_dst = { "item" => [{ "3" => "5" }] } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "item" => [{ "3" => "5" }, { "1" => "3" }, { "2" => "4" }] }) end # Additions since import it "should overwrite true with false when merging boolean values" do hash_src = { "valid" => false } hash_dst = { "valid" => true } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "valid" => false }) end it "should overwrite false with true when merging boolean values" do hash_src = { "valid" => true } hash_dst = { "valid" => false } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "valid" => true }) end it "should overwrite a string with an empty string when merging string values" do hash_src = { "item" => " " } hash_dst = { "item" => "orange" } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "item" => " " }) end it "should overwrite an empty string with a string when merging string values" do hash_src = { "item" => "orange" } hash_dst = { "item" => " " } @dm.deep_merge!(hash_src, hash_dst) expect(hash_dst).to eq({ "item" => "orange" }) end end # deep_merge! # Chef specific describe Chef::Mixin::DeepMerge do before do @dm = Chef::Mixin::DeepMerge end describe "merge" do it "should merge a hash into an empty hash" do hash_dst = {} hash_src = { "id" => "2" } expect(@dm.merge(hash_dst, hash_src)).to eq(hash_src) end it "should merge a nested hash into an empty hash" do hash_dst = {} hash_src = { "region" => { "id" => %w{227 2} } } expect(@dm.merge(hash_dst, hash_src)).to eq(hash_src) end it "should overwrite as string value when merging hashes" do hash_dst = { "name" => "value1" } hash_src = { "name" => "value" } expect(@dm.merge(hash_dst, hash_src)).to eq({ "name" => "value" }) end it "should merge arrays within hashes" do hash_dst = { "property" => %w{2 4} } hash_src = { "property" => %w{1 3} } expect(@dm.merge(hash_dst, hash_src)).to eq({ "property" => %w{2 4 1 3} }) end it "should merge deeply nested hashes" do hash_dst = { "property" => { "values" => { "are" => "falling", "can" => "change" } } } hash_src = { "property" => { "values" => { "are" => "stable", "may" => "rise" } } } expect(@dm.merge(hash_dst, hash_src)).to eq({ "property" => { "values" => { "are" => "stable", "can" => "change", "may" => "rise" } } }) end it "should not modify the source or destination during the merge" do hash_dst = { "property" => %w{1 2 3} } hash_src = { "property" => %w{4 5 6} } ret = @dm.merge(hash_dst, hash_src) expect(hash_dst).to eq({ "property" => %w{1 2 3} }) expect(hash_src).to eq({ "property" => %w{4 5 6} }) expect(ret).to eq({ "property" => %w{1 2 3 4 5 6} }) end it "should not error merging un-dupable objects" do @dm.deep_merge(nil, 4) end end describe "hash-only merging" do it "merges Hashes like normal deep merge" do merge_ee_hash = { "top_level_a" => { "1_deep_a" => "1-a-merge-ee", "1_deep_b" => "1-deep-b-merge-ee" }, "top_level_b" => "top-level-b-merge-ee" } merge_with_hash = { "top_level_a" => { "1_deep_b" => "1-deep-b-merged-onto", "1_deep_c" => "1-deep-c-merged-onto" }, "top_level_b" => "top-level-b-merged-onto" } merged_result = @dm.hash_only_merge(merge_ee_hash, merge_with_hash) expect(merged_result["top_level_b"]).to eq("top-level-b-merged-onto") expect(merged_result["top_level_a"]["1_deep_a"]).to eq("1-a-merge-ee") expect(merged_result["top_level_a"]["1_deep_b"]).to eq("1-deep-b-merged-onto") expect(merged_result["top_level_a"]["1_deep_c"]).to eq("1-deep-c-merged-onto") end it "replaces arrays rather than merging them" do merge_ee_hash = { "top_level_a" => { "1_deep_a" => "1-a-merge-ee", "1_deep_b" => %w{A A A} }, "top_level_b" => "top-level-b-merge-ee" } merge_with_hash = { "top_level_a" => { "1_deep_b" => %w{B B B}, "1_deep_c" => "1-deep-c-merged-onto" }, "top_level_b" => "top-level-b-merged-onto" } merged_result = @dm.hash_only_merge(merge_ee_hash, merge_with_hash) expect(merged_result["top_level_b"]).to eq("top-level-b-merged-onto") expect(merged_result["top_level_a"]["1_deep_a"]).to eq("1-a-merge-ee") expect(merged_result["top_level_a"]["1_deep_b"]).to eq(%w{B B B}) end it "replaces non-hash items with hashes when there's a conflict" do merge_ee_hash = { "top_level_a" => "top-level-a-mergee", "top_level_b" => "top-level-b-merge-ee" } merge_with_hash = { "top_level_a" => { "1_deep_b" => %w{B B B}, "1_deep_c" => "1-deep-c-merged-onto" }, "top_level_b" => "top-level-b-merged-onto" } merged_result = @dm.hash_only_merge(merge_ee_hash, merge_with_hash) expect(merged_result["top_level_a"]).to be_a(Hash) expect(merged_result["top_level_a"]["1_deep_a"]).to be_nil expect(merged_result["top_level_a"]["1_deep_b"]).to eq(%w{B B B}) end it "does not mutate deeply-nested original hashes by default" do merge_ee_hash = { "top_level_a" => { "1_deep_a" => { "2_deep_a" => { "3_deep_a" => "foo" } } } } merge_with_hash = { "top_level_a" => { "1_deep_a" => { "2_deep_a" => { "3_deep_b" => "bar" } } } } @dm.hash_only_merge(merge_ee_hash, merge_with_hash) expect(merge_ee_hash).to eq({ "top_level_a" => { "1_deep_a" => { "2_deep_a" => { "3_deep_a" => "foo" } } } }) expect(merge_with_hash).to eq({ "top_level_a" => { "1_deep_a" => { "2_deep_a" => { "3_deep_b" => "bar" } } } }) end it "does not error merging un-dupable items" do merge_ee_hash = { "top_level_a" => 1, "top_level_b" => false } merge_with_hash = { "top_level_a" => 2, "top_level_b" => true } @dm.hash_only_merge(merge_ee_hash, merge_with_hash) end end end chef-12.14.60/spec/unit/mixin/deprecation_spec.rb000066400000000000000000000035231276456504500215640ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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/mixin/deprecation" describe Chef::Mixin do describe "deprecating constants (Class/Module)" do before do Chef::Mixin.deprecate_constant(:DeprecatedClass, Chef::Node, "This is a test deprecation") @log_io = StringIO.new Chef::Log.init(@log_io) end it "has a list of deprecated constants" do expect(Chef::Mixin.deprecated_constants).to have_key(:DeprecatedClass) end it "returns the replacement when accessing the deprecated constant" do expect(Chef::Mixin::DeprecatedClass).to eq(Chef::Node) end it "warns when accessing the deprecated constant" do Chef::Mixin::DeprecatedClass # rubocop:disable Lint/Void expect(@log_io.string).to include("This is a test deprecation") end end end describe Chef::Mixin::Deprecation::DeprecatedInstanceVariable do before do Chef::Log.logger = Logger.new(StringIO.new) @deprecated_ivar = Chef::Mixin::Deprecation::DeprecatedInstanceVariable.new("value", "an_ivar") end it "forward method calls to the target object" do expect(@deprecated_ivar.length).to eq(5) expect(@deprecated_ivar.to_sym).to eq(:value) end end chef-12.14.60/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb000066400000000000000000000074101276456504500262620ustar00rootroot00000000000000# # Author:: Mark Mzyk () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "etc" require "ostruct" describe Chef::Mixin::EnforceOwnershipAndPermissions do before(:each) do @node = Chef::Node.new @node.name "make_believe" @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @tmpdir = Dir.mktmpdir @resource = Chef::Resource::File.new("#{@tmpdir}/madeup.txt") FileUtils.touch @resource.path @resource.owner "adam" @provider = Chef::Provider::File.new(@resource, @run_context) @provider.current_resource = @resource end after(:each) do FileUtils.rm_rf(@tmpdir) end it "should call set_all on the file access control object" do expect_any_instance_of(Chef::FileAccessControl).to receive(:set_all) @provider.enforce_ownership_and_permissions end context "when nothing was updated" do before do allow_any_instance_of(Chef::FileAccessControl).to receive(:uid_from_resource).and_return(0) allow_any_instance_of(Chef::FileAccessControl).to receive(:requires_changes?).and_return(false) allow_any_instance_of(Chef::FileAccessControl).to receive(:define_resource_requirements) allow_any_instance_of(Chef::FileAccessControl).to receive(:describe_changes) passwd_struct = OpenStruct.new(:name => "root", :passwd => "x", :uid => 0, :gid => 0, :dir => "/root", :shell => "/bin/bash") group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0) allow(Etc).to receive(:getpwuid).and_return(passwd_struct) allow(Etc).to receive(:getgrgid).and_return(group_struct) end it "does not set updated_by_last_action on the new resource" do expect(@provider.new_resource).not_to receive(:updated_by_last_action) allow_any_instance_of(Chef::FileAccessControl).to receive(:set_all) @provider.run_action(:create) end end context "when something was modified" do before do allow_any_instance_of(Chef::FileAccessControl).to receive(:requires_changes?).and_return(true) allow_any_instance_of(Chef::FileAccessControl).to receive(:uid_from_resource).and_return(0) allow_any_instance_of(Chef::FileAccessControl).to receive(:describe_changes) passwd_struct = OpenStruct.new(:name => "root", :passwd => "x", :uid => 0, :gid => 0, :dir => "/root", :shell => "/bin/bash") group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0) allow(Etc).to receive(:getpwuid).and_return(passwd_struct) allow(Etc).to receive(:getgrgid).and_return(group_struct) end it "sets updated_by_last_action on the new resource" do @provider.new_resource.owner(0) # CHEF-3557 hack - Set these because we don't for windows @provider.new_resource.group(0) # CHEF-3557 hack - Set these because we don't for windows expect(@provider.new_resource).to receive(:updated_by_last_action) allow_any_instance_of(Chef::FileAccessControl).to receive(:set_all) @provider.run_action(:create) end end end chef-12.14.60/spec/unit/mixin/homebrew_user_spec.rb000066400000000000000000000066131276456504500221400ustar00rootroot00000000000000# # Author:: Joshua Timberman () # # Copyright 2014-2016, Chef Software, Inc # # 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/mixin/homebrew_user" class ExampleHomebrewUser include Chef::Mixin::HomebrewUser end describe Chef::Mixin::HomebrewUser do before(:each) do node.default["homebrew"]["owner"] = nil end let(:homebrew_user) { ExampleHomebrewUser.new } let(:node) { Chef::Node.new } describe "when the homebrew user is provided" do let(:uid) { 1001 } let(:user) { "foo" } it "returns the homebrew user without looking at the file when uid is provided" do expect(File).to receive(:exist?).exactly(0).times expect(homebrew_user.find_homebrew_uid(uid)).to eq(uid) end it "returns the homebrew user without looking at the file when name is provided" do expect(File).to receive(:exist?).exactly(0).times allow(Etc).to receive_message_chain(:getpwnam, :uid).and_return(uid) expect(homebrew_user.find_homebrew_uid(user)).to eq(uid) end end shared_examples "successfully find executable" do let(:user) { nil } let(:brew_owner) { 2001 } let(:default_brew_path) { "/usr/local/bin/brew" } let(:stat_double) do d = double() expect(d).to receive(:uid).and_return(brew_owner) d end context "debug statement prints owner name" do before do expect(Etc).to receive(:getpwuid).with(brew_owner).and_return(OpenStruct.new(:name => "name")) end it "returns the owner of the brew executable when it is at a default location" do expect(File).to receive(:exist?).with(default_brew_path).and_return(true) expect(File).to receive(:stat).with(default_brew_path).and_return(stat_double) expect(homebrew_user.find_homebrew_uid(user)).to eq(brew_owner) end it "returns the owner of the brew executable when it is not at a default location" do expect(File).to receive(:exist?).with(default_brew_path).and_return(false) allow(homebrew_user).to receive_message_chain(:shell_out, :stdout, :strip).and_return("/foo") expect(File).to receive(:stat).with("/foo").and_return(stat_double) expect(homebrew_user.find_homebrew_uid(user)).to eq(brew_owner) end end end describe "when the homebrew user is not provided" do it "raises an error if no executable is found" do expect(File).to receive(:exist?).with(default_brew_path).and_return(false) allow(homebrew_user).to receive_message_chain(:shell_out, :stdout, :strip).and_return("") expect { homebrew_user.find_homebrew_uid(user) }.to raise_error(Chef::Exceptions::CannotDetermineHomebrewOwner) end include_examples "successfully find executable" context "the executable is owned by root" do include_examples "successfully find executable" do let(:brew_owner) { 0 } end end end end chef-12.14.60/spec/unit/mixin/lazy_module_include.rb000066400000000000000000000033301276456504500223000ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" module TestA extend Chef::Mixin::LazyModuleInclude end module TestB include TestA extend Chef::Mixin::LazyModuleInclude end class TestC include TestB end module Monkey def monkey "monkey" end end module Klowns def klowns "klowns" end end TestA.send(:include, Monkey) TestB.send(:include, Klowns) describe Chef::Mixin::LazyModuleInclude do it "tracks descendant classes of TestA" do expect(TestA.descendants).to include(TestB) expect(TestA.descendants).to include(TestC) end it "tracks descendent classes of TestB" do expect(TestB.descendants).to eql([TestC]) end it "including into A mixins in methods into B and C" do expect(TestA.instance_methods).to include(:monkey) expect(TestB.instance_methods).to include(:monkey) expect(TestC.instance_methods).to include(:monkey) end it "including into B only mixins in methods into C" do expect(TestA.instance_methods).not_to include(:klowns) expect(TestB.instance_methods).to include(:klowns) expect(TestC.instance_methods).to include(:klowns) end end chef-12.14.60/spec/unit/mixin/params_validate_spec.rb000066400000000000000000000251621276456504500224260ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" class TinyClass include Chef::Mixin::ParamsValidate attr_reader :name def music(is_good = true) is_good end end describe Chef::Mixin::ParamsValidate do before(:each) do @vo = TinyClass.new() end it "should allow a hash and a hash as arguments to validate" do expect { @vo.validate({ :one => "two" }, {}) }.not_to raise_error end it "should raise an argument error if validate is called incorrectly" do expect { @vo.validate("one", "two") }.to raise_error(ArgumentError) end it "should require validation map keys to be symbols or strings" do expect { @vo.validate({ :one => "two" }, { :one => true }) }.not_to raise_error expect { @vo.validate({ :one => "two" }, { "one" => true }) }.not_to raise_error expect { @vo.validate({ :one => "two" }, { Hash.new => true }) }.to raise_error(ArgumentError) end it "should allow options to be required with true" do expect { @vo.validate({ :one => "two" }, { :one => true }) }.not_to raise_error end it "should allow options to be optional with false" do expect { @vo.validate({}, { :one => false }) }.not_to raise_error end it "should allow you to check what kind_of? thing an argument is with kind_of" do expect do @vo.validate( { :one => "string" }, { :one => { :kind_of => String, }, } ) end.not_to raise_error expect do @vo.validate( { :one => "string" }, { :one => { :kind_of => Array, }, } ) end.to raise_error(ArgumentError) end it "should allow you to specify an argument is required with required" do expect do @vo.validate( { :one => "string" }, { :one => { :required => true, }, } ) end.not_to raise_error expect do @vo.validate( { :two => "string" }, { :one => { :required => true, }, } ) end.to raise_error(ArgumentError) expect do @vo.validate( { :two => "string" }, { :one => { :required => false, }, } ) end.not_to raise_error end it "should allow you to specify whether an object has a method with respond_to" do expect do @vo.validate( { :one => @vo }, { :one => { :respond_to => "validate", }, } ) end.not_to raise_error expect do @vo.validate( { :one => @vo }, { :one => { :respond_to => "monkey", }, } ) end.to raise_error(ArgumentError) end it "should allow you to specify whether an object has all the given methods with respond_to and an array" do expect do @vo.validate( { :one => @vo }, { :one => { :respond_to => %w{validate music}, }, } ) end.not_to raise_error expect do @vo.validate( { :one => @vo }, { :one => { :respond_to => %w{monkey validate}, }, } ) end.to raise_error(ArgumentError) end it "should let you set a default value with default => value" do arguments = Hash.new @vo.validate(arguments, { :one => { :default => "is the loneliest number", }, }) expect(arguments[:one]).to eq("is the loneliest number") end it "should let you check regular expressions" do expect do @vo.validate( { :one => "is good" }, { :one => { :regex => /^is good$/, }, } ) end.not_to raise_error expect do @vo.validate( { :one => "is good" }, { :one => { :regex => /^is bad$/, }, } ) end.to raise_error(ArgumentError) end it "should let you specify your own callbacks" do expect do @vo.validate( { :one => "is good" }, { :one => { :callbacks => { "should be equal to is good" => lambda do |a| a == "is good" end, }, }, } ) end.not_to raise_error expect do @vo.validate( { :one => "is bad" }, { :one => { :callbacks => { "should be equal to 'is good'" => lambda do |a| a == "is good" end, }, }, } ) end.to raise_error(ArgumentError) end it "should let you combine checks" do args = { :one => "is good", :two => "is bad" } expect do @vo.validate( args, { :one => { :kind_of => String, :respond_to => [ :to_s, :upcase ], :regex => /^is good/, :callbacks => { "should be your friend" => lambda do |a| a == "is good" end, }, :required => true, }, :two => { :kind_of => String, :required => false, }, :three => { :default => "neato mosquito" }, } ) end.not_to raise_error expect(args[:three]).to eq("neato mosquito") expect do @vo.validate( args, { :one => { :kind_of => String, :respond_to => [ :to_s, :upcase ], :regex => /^is good/, :callbacks => { "should be your friend" => lambda do |a| a == "is good" end, }, :required => true, }, :two => { :kind_of => Hash, :required => false, }, :three => { :default => "neato mosquito" }, } ) end.to raise_error(ArgumentError) end it "should raise an ArgumentError if the validation map has an unknown check" do expect do @vo.validate( { :one => "two" }, { :one => { :busted => "check", }, } ) end.to raise_error(ArgumentError) end it "should accept keys that are strings in the options" do expect do @vo.validate({ "one" => "two" }, { :one => { :regex => /^two$/ } }) end.not_to raise_error end it "should allow an array to kind_of" do expect do @vo.validate( { :one => "string" }, { :one => { :kind_of => [ String, Array ], }, } ) end.not_to raise_error expect do @vo.validate( { :one => ["string"] }, { :one => { :kind_of => [ String, Array ], }, } ) end.not_to raise_error expect do @vo.validate( { :one => Hash.new }, { :one => { :kind_of => [ String, Array ], }, } ) end.to raise_error(ArgumentError) end it "asserts that a value returns false from a predicate method" do expect do @vo.validate({ :not_blank => "should pass" }, { :not_blank => { :cannot_be => [ :nil, :empty ] } }) end.not_to raise_error expect do @vo.validate({ :not_blank => "" }, { :not_blank => { :cannot_be => [ :nil, :empty ] } }) end.to raise_error(Chef::Exceptions::ValidationFailed) end it "should set and return a value, then return the same value" do value = "meow" expect(@vo.set_or_return(:test, value, {}).object_id).to eq(value.object_id) expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id) end it "should set and return a default value when the argument is nil, then return the same value" do value = "meow" expect(@vo.set_or_return(:test, nil, { :default => value }).object_id).to eq(value.object_id) expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id) end it "should raise an ArgumentError when argument is nil and required is true" do expect do @vo.set_or_return(:test, nil, { :required => true }) end.to raise_error(ArgumentError) end it "should not raise an error when argument is nil and required is false" do expect do @vo.set_or_return(:test, nil, { :required => false }) end.not_to raise_error end it "should set and return @name, then return @name for foo when argument is nil" do value = "meow" expect(@vo.set_or_return(:name, value, {}).object_id).to eq(value.object_id) expect(@vo.set_or_return(:foo, nil, { :name_attribute => true }).object_id).to eq(value.object_id) end it "should allow DelayedEvaluator instance to be set for value regardless of restriction" do value = Chef::DelayedEvaluator.new { "test" } @vo.set_or_return(:test, value, { :kind_of => Numeric }) end it "should raise an error when delayed evaluated attribute is not valid" do value = Chef::DelayedEvaluator.new { "test" } @vo.set_or_return(:test, value, { :kind_of => Numeric }) expect do @vo.set_or_return(:test, nil, { :kind_of => Numeric }) end.to raise_error(Chef::Exceptions::ValidationFailed) end it "should create DelayedEvaluator instance when #lazy is used" do @vo.set_or_return(:delayed, @vo.lazy { "test" }, {}) expect(@vo.instance_variable_get(:@delayed)).to be_a(Chef::DelayedEvaluator) end it "should execute block on each call when DelayedEvaluator" do value = "fubar" @vo.set_or_return(:test, @vo.lazy { value }, {}) expect(@vo.set_or_return(:test, nil, {})).to eq("fubar") value = "foobar" expect(@vo.set_or_return(:test, nil, {})).to eq("foobar") value = "fauxbar" expect(@vo.set_or_return(:test, nil, {})).to eq("fauxbar") end it "should not evaluate non DelayedEvaluator instances" do value = lambda { "test" } @vo.set_or_return(:test, value, {}) expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id) expect(@vo.set_or_return(:test, nil, {})).to be_a(Proc) end end chef-12.14.60/spec/unit/mixin/path_sanity_spec.rb000066400000000000000000000071051276456504500216120ustar00rootroot00000000000000# # Author:: Seth Chisamore () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" class PathSanityTestHarness include Chef::Mixin::PathSanity end describe Chef::Mixin::PathSanity do before do @sanity = PathSanityTestHarness.new end describe "when enforcing path sanity" do before do Chef::Config[:enforce_path_sanity] = true @ruby_bindir = "/some/ruby/bin" @gem_bindir = "/some/gem/bin" allow(Gem).to receive(:bindir).and_return(@gem_bindir) allow(RbConfig::CONFIG).to receive(:[]).with("bindir").and_return(@ruby_bindir) allow(ChefConfig).to receive(:windows?).and_return(false) end it "adds all useful PATHs even if environment is an empty hash" do env = {} @sanity.enforce_path_sanity(env) expect(env["PATH"]).to eq("#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") end it "adds all useful PATHs that are not yet in PATH to PATH" do env = { "PATH" => "" } @sanity.enforce_path_sanity(env) expect(env["PATH"]).to eq("#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") end it "does not re-add paths that already exist in PATH" do env = { "PATH" => "/usr/bin:/sbin:/bin" } @sanity.enforce_path_sanity(env) expect(env["PATH"]).to eq("/usr/bin:/sbin:/bin:#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin") end it "creates path with utf-8 encoding" do env = { "PATH" => "/usr/bin:/sbin:/bin:/b#{0x81.chr}t".force_encoding("ISO-8859-1") } @sanity.enforce_path_sanity(env) expect(env["PATH"].encoding.to_s).to eq("UTF-8") end it "adds the current executing Ruby's bindir and Gem bindir to the PATH" do env = { "PATH" => "" } @sanity.enforce_path_sanity(env) expect(env["PATH"]).to eq("#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") end it "does not create entries for Ruby/Gem bindirs if they exist in SANE_PATH or PATH" do ruby_bindir = "/usr/bin" gem_bindir = "/yo/gabba/gabba" allow(Gem).to receive(:bindir).and_return(gem_bindir) allow(RbConfig::CONFIG).to receive(:[]).with("bindir").and_return(ruby_bindir) env = { "PATH" => gem_bindir } @sanity.enforce_path_sanity(env) expect(env["PATH"]).to eq("/yo/gabba/gabba:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") end it "builds a valid windows path" do ruby_bindir = 'C:\ruby\bin' gem_bindir = 'C:\gems\bin' allow(Gem).to receive(:bindir).and_return(gem_bindir) allow(RbConfig::CONFIG).to receive(:[]).with("bindir").and_return(ruby_bindir) allow(ChefConfig).to receive(:windows?).and_return(true) env = { "PATH" => 'C:\Windows\system32;C:\mr\softie' } @sanity.enforce_path_sanity(env) expect(env["PATH"]).to eq("C:\\Windows\\system32;C:\\mr\\softie;#{ruby_bindir};#{gem_bindir}") end end end chef-12.14.60/spec/unit/mixin/powershell_out_spec.rb000066400000000000000000000047221276456504500223440ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mixin/powershell_out" describe Chef::Mixin::PowershellOut do let(:shell_out_class) { Class.new { include Chef::Mixin::PowershellOut } } subject(:object) { shell_out_class.new } let(:architecture) { "something" } let(:flags) do "-NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -InputFormat None" end describe "#powershell_out" do it "runs a command and returns the shell_out object" do ret = double("Mixlib::ShellOut") expect(object).to receive(:shell_out).with( "powershell.exe #{flags} -Command \"Get-Process\"", {} ).and_return(ret) expect(object.powershell_out("Get-Process")).to eql(ret) end it "passes options" do ret = double("Mixlib::ShellOut") expect(object).to receive(:shell_out).with( "powershell.exe #{flags} -Command \"Get-Process\"", timeout: 600 ).and_return(ret) expect(object.powershell_out("Get-Process", timeout: 600)).to eql(ret) end end describe "#powershell_out!" do it "runs a command and returns the shell_out object" do mixlib_shellout = double("Mixlib::ShellOut") expect(object).to receive(:shell_out).with( "powershell.exe #{flags} -Command \"Get-Process\"", {} ).and_return(mixlib_shellout) expect(mixlib_shellout).to receive(:error!) expect(object.powershell_out!("Get-Process")).to eql(mixlib_shellout) end it "passes options" do mixlib_shellout = double("Mixlib::ShellOut") expect(object).to receive(:shell_out).with( "powershell.exe #{flags} -Command \"Get-Process\"", timeout: 600 ).and_return(mixlib_shellout) expect(mixlib_shellout).to receive(:error!) expect(object.powershell_out!("Get-Process", timeout: 600)).to eql(mixlib_shellout) end end end chef-12.14.60/spec/unit/mixin/powershell_type_coercions_spec.rb000066400000000000000000000056301276456504500245610ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mixin/powershell_type_coercions" require "base64" class Chef::PSTypeTester include Chef::Mixin::PowershellTypeCoercions end describe Chef::Mixin::PowershellTypeCoercions do let (:test_class) { Chef::PSTypeTester.new } describe "#translate_type" do it "single quotes a string" do expect(test_class.translate_type("foo")).to eq("'foo'") end ["'", '"', "#", "`"].each do |c| it "base64 encodes a string that contains #{c}" do expect(test_class.translate_type("#{c}")).to match(Base64.strict_encode64(c)) end end it "does not quote an integer" do expect(test_class.translate_type(123)).to eq("123") end it "does not quote a floating point number" do expect(test_class.translate_type(123.4)).to eq("123.4") end it "translates $false when an instance of FalseClass is provided" do expect(test_class.translate_type(false)).to eq("$false") end it "translates $true when an instance of TrueClass is provided" do expect(test_class.translate_type(true)).to eq("$true") end it "translates all members of a hash and wrap them in @{} separated by ;" do expect(test_class.translate_type({ "a" => 1, "b" => 1.2, "c" => false, "d" => true })).to eq("@{a=1;b=1.2;c=$false;d=$true}") end it "translates all members of an array and them by a ," do expect(test_class.translate_type([true, false])).to eq("@($true,$false)") end it "translates a Chef::Node::ImmutableMash like a hash" do test_mash = Chef::Node::ImmutableMash.new({ "a" => 1, "b" => 1.2, "c" => false, "d" => true }) expect(test_class.translate_type(test_mash)).to eq("@{a=1;b=1.2;c=$false;d=$true}") end it "translates a Chef::Node::ImmutableArray like an array" do test_array = Chef::Node::ImmutableArray.new([true, false]) expect(test_class.translate_type(test_array)).to eq("@($true,$false)") end it "falls back :to_psobject if we have not defined at explicit rule" do ps_obj = double("PSObject") expect(ps_obj).to receive(:to_psobject).and_return("$true") expect(test_class.translate_type(ps_obj)).to eq("($true)") end end end chef-12.14.60/spec/unit/mixin/properties_spec.rb000066400000000000000000000066341276456504500214710ustar00rootroot00000000000000require "support/shared/integration/integration_helper" require "chef/mixin/properties" module ChefMixinPropertiesSpec describe "Chef::Resource.property" do include IntegrationSupport context "with a base class A with properties a, ab, and ac" do class A include Chef::Mixin::Properties property :a, "a", default: "a" property :ab, %w{a b}, default: "a" property :ac, %w{a c}, default: "a" end context "and a module B with properties b, ab and bc" do module B include Chef::Mixin::Properties property :b, "b", default: "b" property :ab, default: "b" property :bc, %w{b c}, default: "c" end context "and a derived class C < A with properties c, ac and bc" do class C < A include B property :c, "c", default: "c" property :ac, default: "c" property :bc, default: "c" end it "A.properties has a, ab, and ac with types 'a', ['a', 'b'], and ['b', 'c']" do expect(A.properties.keys).to eq [ :a, :ab, :ac ] expect(A.properties[:a].validation_options[:is]).to eq "a" expect(A.properties[:ab].validation_options[:is]).to eq %w{a b} expect(A.properties[:ac].validation_options[:is]).to eq %w{a c} end it "B.properties has b, ab, and bc with types 'b', nil and ['b', 'c']" do expect(B.properties.keys).to eq [ :b, :ab, :bc ] expect(B.properties[:b].validation_options[:is]).to eq "b" expect(B.properties[:ab].validation_options[:is]).to be_nil expect(B.properties[:bc].validation_options[:is]).to eq %w{b c} end it "C.properties has a, b, c, ac and bc with merged types" do expect(C.properties.keys).to eq [ :a, :ab, :ac, :b, :bc, :c ] expect(C.properties[:a].validation_options[:is]).to eq "a" expect(C.properties[:b].validation_options[:is]).to eq "b" expect(C.properties[:c].validation_options[:is]).to eq "c" expect(C.properties[:ac].validation_options[:is]).to eq %w{a c} expect(C.properties[:bc].validation_options[:is]).to eq %w{b c} end it "C.properties has ab with a non-merged type (from B)" do expect(C.properties[:ab].validation_options[:is]).to be_nil end context "and an instance of C" do let(:c) { C.new } it "all properties can be retrieved and merged properties default to ab->b, ac->c, bc->c" do expect(c.a).to eq("a") expect(c.b).to eq("b") expect(c.c).to eq("c") expect(c.ab).to eq("b") expect(c.ac).to eq("c") expect(c.bc).to eq("c") end end end end end end context "with an Inner module" do module Inner include Chef::Mixin::Properties property :inner end context "and an Outer module including it" do module Outer include Inner property :outer end context "and an Outerest class including that" do class Outerest include Outer property :outerest end it "Outerest.properties.validation_options[:is] inner, outer, outerest" do expect(Outerest.properties.keys).to eq [:inner, :outer, :outerest] end end end end end chef-12.14.60/spec/unit/mixin/proxified_socket_spec.rb000066400000000000000000000054161276456504500226330ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mixin/proxified_socket" require "proxifier/proxy" class TestProxifiedSocket include Chef::Mixin::ProxifiedSocket end describe Chef::Mixin::ProxifiedSocket do before(:all) do @original_env = ENV.to_hash end after(:all) do ENV.clear ENV.update(@original_env) end let(:host) { "host" } let(:port) { 7979 } let(:test_instance) { TestProxifiedSocket.new } let(:socket_double) { instance_double(TCPSocket) } let(:proxifier_double) { instance_double(Proxifier::Proxy) } let(:http_uri) { "http://somehost:1" } let(:https_uri) { "https://somehost:1" } let(:no_proxy_spec) { nil } shared_examples "proxified socket" do it "wraps the Socket in a Proxifier::Proxy" do expect(Proxifier).to receive(:Proxy).with(proxy_uri).and_return(proxifier_double) expect(proxifier_double).to receive(:open).with(host, port).and_return(socket_double) expect(test_instance.proxified_socket(host, port)).to eq(socket_double) end end context "when no proxy is set" do it "returns a plain TCPSocket" do ENV["http_proxy"] = nil ENV["https_proxy"] = nil expect(TCPSocket).to receive(:new).with(host, port).and_return(socket_double) expect(test_instance.proxified_socket(host, port)).to eq(socket_double) end end context "when https_proxy is set" do before do # I'm purposefully setting both of these because we prefer the https # variable ENV["https_proxy"] = https_uri ENV["http_proxy"] = http_uri end let(:proxy_uri) { https_uri } include_examples "proxified socket" context "when no_proxy is set" do # This is testing that no_proxy is also provided to Proxified # when it is set before do ENV["no_proxy"] = no_proxy_spec end let(:no_proxy_spec) { "somehost1,somehost2" } include_examples "proxified socket" end end context "when http_proxy is set" do before do ENV["https_proxy"] = nil ENV["http_proxy"] = http_uri end let(:proxy_uri) { http_uri } include_examples "proxified socket" end end chef-12.14.60/spec/unit/mixin/securable_spec.rb000066400000000000000000000405541276456504500212410ustar00rootroot00000000000000# encoding: UTF-8 # # Author:: Mark Mzyk () # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Mixin::Securable do before(:each) do @securable = Object.new @securable.send(:extend, Chef::Mixin::Securable) @securable.send(:extend, Chef::Mixin::ParamsValidate) end it "should accept a group name or id for group" do expect { @securable.group "root" }.not_to raise_error expect { @securable.group 123 }.not_to raise_error expect { @securable.group "+bad:group" }.to raise_error(ArgumentError) end it "should accept a user name or id for owner" do expect { @securable.owner "root" }.not_to raise_error expect { @securable.owner 123 }.not_to raise_error expect { @securable.owner "+bad:owner" }.to raise_error(ArgumentError) end it "allows the owner to be specified as #user" do expect(@securable).to respond_to(:user) end describe "unix-specific behavior" do before(:each) do platform_mock :unix do load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "mixin", "securable.rb") @securable = Object.new @securable.send(:extend, Chef::Mixin::Securable) @securable.send(:extend, Chef::Mixin::ParamsValidate) end end it "should accept group/owner names with spaces and backslashes" do expect { @securable.group 'test\ group' }.not_to raise_error expect { @securable.owner 'test\ group' }.not_to raise_error end it "should accept group/owner names that are a single character or digit" do expect { @securable.group "v" }.not_to raise_error expect { @securable.group "1" }.not_to raise_error expect { @securable.owner "v" }.not_to raise_error expect { @securable.owner "1" }.not_to raise_error end it "should not accept group/owner names starting with '-', '+', or '~'" do expect { @securable.group "-test" }.to raise_error(ArgumentError) expect { @securable.group "+test" }.to raise_error(ArgumentError) expect { @securable.group "~test" }.to raise_error(ArgumentError) expect { @securable.group "te-st" }.not_to raise_error expect { @securable.group "te+st" }.not_to raise_error expect { @securable.group "te~st" }.not_to raise_error expect { @securable.owner "-test" }.to raise_error(ArgumentError) expect { @securable.owner "+test" }.to raise_error(ArgumentError) expect { @securable.owner "~test" }.to raise_error(ArgumentError) expect { @securable.owner "te-st" }.not_to raise_error expect { @securable.owner "te+st" }.not_to raise_error expect { @securable.owner "te~st" }.not_to raise_error end it "should not accept group/owner names containing ':', ',' or non-space whitespace" do expect { @securable.group ":test" }.to raise_error(ArgumentError) expect { @securable.group "te:st" }.to raise_error(ArgumentError) expect { @securable.group ",test" }.to raise_error(ArgumentError) expect { @securable.group "te,st" }.to raise_error(ArgumentError) expect { @securable.group "\ttest" }.to raise_error(ArgumentError) expect { @securable.group "te\tst" }.to raise_error(ArgumentError) expect { @securable.group "\rtest" }.to raise_error(ArgumentError) expect { @securable.group "te\rst" }.to raise_error(ArgumentError) expect { @securable.group "\ftest" }.to raise_error(ArgumentError) expect { @securable.group "te\fst" }.to raise_error(ArgumentError) expect { @securable.group "\0test" }.to raise_error(ArgumentError) expect { @securable.group "te\0st" }.to raise_error(ArgumentError) expect { @securable.owner ":test" }.to raise_error(ArgumentError) expect { @securable.owner "te:st" }.to raise_error(ArgumentError) expect { @securable.owner ",test" }.to raise_error(ArgumentError) expect { @securable.owner "te,st" }.to raise_error(ArgumentError) expect { @securable.owner "\ttest" }.to raise_error(ArgumentError) expect { @securable.owner "te\tst" }.to raise_error(ArgumentError) expect { @securable.owner "\rtest" }.to raise_error(ArgumentError) expect { @securable.owner "te\rst" }.to raise_error(ArgumentError) expect { @securable.owner "\ftest" }.to raise_error(ArgumentError) expect { @securable.owner "te\fst" }.to raise_error(ArgumentError) expect { @securable.owner "\0test" }.to raise_error(ArgumentError) expect { @securable.owner "te\0st" }.to raise_error(ArgumentError) end it "should accept Active Directory-style domain names pulled in via LDAP (on unix hosts)" do expect { @securable.owner "domain\@user" }.not_to raise_error expect { @securable.owner "domain\\user" }.not_to raise_error expect { @securable.group "domain\@group" }.not_to raise_error expect { @securable.group "domain\\group" }.not_to raise_error expect { @securable.group "domain\\group^name" }.not_to raise_error end it "should not accept group/owner names containing embedded carriage returns" do skip "XXX: params_validate needs to be extended to support multi-line regex" #lambda { @securable.group "\ntest" }.should raise_error(ArgumentError) #lambda { @securable.group "te\nst" }.should raise_error(ArgumentError) #lambda { @securable.owner "\ntest" }.should raise_error(ArgumentError) #lambda { @securable.owner "te\nst" }.should raise_error(ArgumentError) end it "should accept group/owner names in UTF-8" do expect { @securable.group "tëst" }.not_to raise_error expect { @securable.group "ë" }.not_to raise_error expect { @securable.owner "tëst" }.not_to raise_error expect { @securable.owner "ë" }.not_to raise_error end it "should accept a unix file mode in string form as an octal number" do expect { @securable.mode "0" }.not_to raise_error expect { @securable.mode "0000" }.not_to raise_error expect { @securable.mode "0111" }.not_to raise_error expect { @securable.mode "0444" }.not_to raise_error expect { @securable.mode "111" }.not_to raise_error expect { @securable.mode "444" }.not_to raise_error expect { @securable.mode "7777" }.not_to raise_error expect { @securable.mode "07777" }.not_to raise_error expect { @securable.mode "-01" }.to raise_error(ArgumentError) expect { @securable.mode "010000" }.to raise_error(ArgumentError) expect { @securable.mode "-1" }.to raise_error(ArgumentError) expect { @securable.mode "10000" }.to raise_error(ArgumentError) expect { @securable.mode "07778" }.to raise_error(ArgumentError) expect { @securable.mode "7778" }.to raise_error(ArgumentError) expect { @securable.mode "4095" }.to raise_error(ArgumentError) expect { @securable.mode "0foo1234" }.to raise_error(ArgumentError) expect { @securable.mode "foo1234" }.to raise_error(ArgumentError) end it "should accept a unix file mode in numeric form as a ruby-interpreted integer" do expect { @securable.mode(0) }.not_to raise_error expect { @securable.mode(0000) }.not_to raise_error expect { @securable.mode(444) }.not_to raise_error expect { @securable.mode(0444) }.not_to raise_error expect { @securable.mode(07777) }.not_to raise_error expect { @securable.mode(292) }.not_to raise_error expect { @securable.mode(4095) }.not_to raise_error expect { @securable.mode(0111) }.not_to raise_error expect { @securable.mode(73) }.not_to raise_error expect { @securable.mode(-01) }.to raise_error(ArgumentError) expect { @securable.mode(010000) }.to raise_error(ArgumentError) expect { @securable.mode(-1) }.to raise_error(ArgumentError) expect { @securable.mode(4096) }.to raise_error(ArgumentError) end end describe "windows-specific behavior" do before(:each) do platform_mock :windows do load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "mixin", "securable.rb") securable_class = Class.new do include Chef::Mixin::Securable include Chef::Mixin::ParamsValidate end @securable = securable_class.new end end it "should not accept a group name or id for group with spaces and multiple backslashes" do expect { @securable.group 'test\ \group' }.to raise_error(ArgumentError) end it "should accept a unix file mode in string form as an octal number" do expect { @securable.mode "0" }.not_to raise_error expect { @securable.mode "0000" }.not_to raise_error expect { @securable.mode "0111" }.not_to raise_error expect { @securable.mode "0444" }.not_to raise_error expect { @securable.mode "111" }.not_to raise_error expect { @securable.mode "444" }.not_to raise_error expect { @securable.mode "7777" }.to raise_error(ArgumentError) expect { @securable.mode "07777" }.to raise_error(ArgumentError) expect { @securable.mode "-01" }.to raise_error(ArgumentError) expect { @securable.mode "010000" }.to raise_error(ArgumentError) expect { @securable.mode "-1" }.to raise_error(ArgumentError) expect { @securable.mode "10000" }.to raise_error(ArgumentError) expect { @securable.mode "07778" }.to raise_error(ArgumentError) expect { @securable.mode "7778" }.to raise_error(ArgumentError) expect { @securable.mode "4095" }.to raise_error(ArgumentError) expect { @securable.mode "0foo1234" }.to raise_error(ArgumentError) expect { @securable.mode "foo1234" }.to raise_error(ArgumentError) end it "should accept a unix file mode in numeric form as a ruby-interpreted integer" do expect { @securable.mode 0 }.not_to raise_error expect { @securable.mode 0000 }.not_to raise_error expect { @securable.mode 444 }.not_to raise_error expect { @securable.mode 0444 }.not_to raise_error expect { @securable.mode 07777 }.to raise_error(ArgumentError) expect { @securable.mode 292 }.not_to raise_error expect { @securable.mode 4095 }.to raise_error(ArgumentError) expect { @securable.mode 0111 }.not_to raise_error expect { @securable.mode 73 }.not_to raise_error expect { @securable.mode(-01) }.to raise_error(ArgumentError) expect { @securable.mode 010000 }.to raise_error(ArgumentError) expect { @securable.mode(-1) }.to raise_error(ArgumentError) expect { @securable.mode 4096 }.to raise_error(ArgumentError) end it "should allow you to specify :full_control, :modify, :read_execute, :read, and :write rights" do expect { @securable.rights :full_control, "The Dude" }.not_to raise_error expect { @securable.rights :modify, "The Dude" }.not_to raise_error expect { @securable.rights :read_execute, "The Dude" }.not_to raise_error expect { @securable.rights :read, "The Dude" }.not_to raise_error expect { @securable.rights :write, "The Dude" }.not_to raise_error expect { @securable.rights :to_party, "The Dude" }.to raise_error(ArgumentError) end it "should allow you to specify :full_control, :modify, :read_execute, :read, and :write deny_rights" do expect { @securable.deny_rights :full_control, "The Dude" }.not_to raise_error expect { @securable.deny_rights :modify, "The Dude" }.not_to raise_error expect { @securable.deny_rights :read_execute, "The Dude" }.not_to raise_error expect { @securable.deny_rights :read, "The Dude" }.not_to raise_error expect { @securable.deny_rights :write, "The Dude" }.not_to raise_error expect { @securable.deny_rights :to_party, "The Dude" }.to raise_error(ArgumentError) end it "should accept a principal as a string or an array" do expect { @securable.rights :read, "The Dude" }.not_to raise_error expect { @securable.rights :read, ["The Dude", "Donny"] }.not_to raise_error expect { @securable.rights :read, 3 }.to raise_error(ArgumentError) end it "should allow you to specify whether the permissions applies_to_children with true/false/:containers_only/:objects_only" do expect { @securable.rights :read, "The Dude", :applies_to_children => false }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => true }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => :containers_only }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => :objects_only }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => "poop" }.to raise_error(ArgumentError) end it "should allow you to specify whether the permissions applies_to_self with true/false" do expect { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => false }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_self => true }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_self => "poop" }.to raise_error(ArgumentError) end it "should allow you to specify whether the permissions applies one_level_deep with true/false" do expect { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => false }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => true }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => "poop" }.to raise_error(ArgumentError) end it "should allow multiple rights and deny_rights declarations" do @securable.rights :read, "The Dude" @securable.deny_rights :full_control, "The Dude" @securable.rights :full_control, "The Dude" @securable.rights :write, "The Dude" @securable.deny_rights :read, "The Dude" expect(@securable.rights.size).to eq(3) expect(@securable.deny_rights.size).to eq(2) end it "should allow you to specify whether the permission applies_to_self only if you specified applies_to_children" do expect { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => true }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => false }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => false, :applies_to_self => true }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => false, :applies_to_self => false }.to raise_error(ArgumentError) expect { @securable.rights :read, "The Dude", :applies_to_self => true }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_self => false }.not_to raise_error end it "should allow you to specify whether the permission applies one_level_deep only if you specified applies_to_children" do expect { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => true }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => false }.not_to raise_error expect { @securable.rights :read, "The Dude", :applies_to_children => false, :one_level_deep => true }.to raise_error(ArgumentError) expect { @securable.rights :read, "The Dude", :applies_to_children => false, :one_level_deep => false }.not_to raise_error expect { @securable.rights :read, "The Dude", :one_level_deep => true }.not_to raise_error expect { @securable.rights :read, "The Dude", :one_level_deep => false }.not_to raise_error end it "should allow you to specify whether the permissions inherit with true/false" do expect { @securable.inherits true }.not_to raise_error expect { @securable.inherits false }.not_to raise_error expect { @securable.inherits "monkey" }.to raise_error(ArgumentError) end end end chef-12.14.60/spec/unit/mixin/shell_out_spec.rb000066400000000000000000000304151276456504500212650ustar00rootroot00000000000000# # Author:: Ho-Sheng Hsiao (hosh@chef.io) # Code derived from spec/unit/mixin/command_spec.rb # # Original header: # Author:: Hongli Lai (hongli@phusion.nl) # Copyright:: Copyright 2009-2016, Phusion # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Mixin::ShellOut do let(:shell_out_class) { Class.new { include Chef::Mixin::ShellOut } } subject(:shell_out_obj) { shell_out_class.new } describe "#run_command_compatible_options" do subject { shell_out_obj.run_command_compatible_options(command_args) } let(:command_args) { [ cmd, options ] } let(:cmd) { "echo '#{rand(1000)}'" } let(:output) { StringIO.new } let!(:capture_log_output) { Chef::Log.logger = Logger.new(output) } let(:assume_deprecation_log_level) { allow(Chef::Log).to receive(:level).and_return(:warn) } before do Chef::Config[:treat_deprecation_warnings_as_errors] = false end context "without options" do let(:command_args) { [ cmd ] } it "should not edit command args" do is_expected.to eql(command_args) end end context "without deprecated options" do let(:options) { { :environment => environment } } let(:environment) { { "LC_ALL" => "C", "LANG" => "C", "LANGUAGE" => "C" } } it "should not edit command args" do is_expected.to eql(command_args) end end def self.should_emit_deprecation_warning_about(old_option, new_option) it "should emit a deprecation warning" do assume_deprecation_log_level && capture_log_output subject expect(output.string).to match /DEPRECATION:/ expect(output.string).to match Regexp.escape(old_option.to_s) expect(output.string).to match Regexp.escape(new_option.to_s) end end context "with :command_log_level option" do let(:options) { { :command_log_level => command_log_level } } let(:command_log_level) { :warn } it "should convert :command_log_level to :log_level" do is_expected.to eql [ cmd, { :log_level => command_log_level } ] end should_emit_deprecation_warning_about :command_log_level, :log_level end context "with :command_log_prepend option" do let(:options) { { :command_log_prepend => command_log_prepend } } let(:command_log_prepend) { "PROVIDER:" } it "should convert :command_log_prepend to :log_tag" do is_expected.to eql [ cmd, { :log_tag => command_log_prepend } ] end should_emit_deprecation_warning_about :command_log_prepend, :log_tag end context "with 'command_log_level' option" do let(:options) { { "command_log_level" => command_log_level } } let(:command_log_level) { :warn } it "should convert 'command_log_level' to :log_level" do is_expected.to eql [ cmd, { :log_level => command_log_level } ] end should_emit_deprecation_warning_about :command_log_level, :log_level end context "with 'command_log_prepend' option" do let(:options) { { "command_log_prepend" => command_log_prepend } } let(:command_log_prepend) { "PROVIDER:" } it "should convert 'command_log_prepend' to :log_tag" do is_expected.to eql [ cmd, { :log_tag => command_log_prepend } ] end should_emit_deprecation_warning_about :command_log_prepend, :log_tag end end context "when testing individual methods" do before(:each) do @original_env = ENV.to_hash ENV.clear end after(:each) do ENV.clear ENV.update(@original_env) end let(:cmd) { "echo '#{rand(1000)}'" } describe "#shell_out" do describe "when the last argument is a Hash" do describe "and environment is an option" do it "should not change environment language settings when they are set to nil" do options = { :environment => { "LC_ALL" => nil, "LANGUAGE" => nil, "LANG" => nil } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out(cmd, options) end it "should not change environment language settings when they are set to non-nil" do options = { :environment => { "LC_ALL" => "en_US.UTF-8", "LANGUAGE" => "en_US.UTF-8", "LANG" => "en_US.UTF-8" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out(cmd, options) end it "should set environment language settings to the configured internal locale when they are not present" do options = { :environment => { "HOME" => "/Users/morty" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, { :environment => { "HOME" => "/Users/morty", "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], }, }).and_return(true) shell_out_obj.shell_out(cmd, options) end it "should not mutate the options hash when it adds language settings" do options = { :environment => { "HOME" => "/Users/morty" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, { :environment => { "HOME" => "/Users/morty", "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], }, }).and_return(true) shell_out_obj.shell_out(cmd, options) expect(options[:environment].has_key?("LC_ALL")).to be false end end describe "and env is an option" do it "should not change env when langauge options are set to nil" do options = { :env => { "LC_ALL" => nil, "LANG" => nil, "LANGUAGE" => nil } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out(cmd, options) end it "should not change env when language options are set to non-nil" do options = { :env => { "LC_ALL" => "de_DE.UTF-8", "LANG" => "de_DE.UTF-8", "LANGUAGE" => "de_DE.UTF-8" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out(cmd, options) end it "should set environment language settings to the configured internal locale when they are not present" do options = { :env => { "HOME" => "/Users/morty" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, { :env => { "HOME" => "/Users/morty", "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], }, }).and_return(true) shell_out_obj.shell_out(cmd, options) end it "should not mutate the options hash when it adds language settings" do options = { :env => { "HOME" => "/Users/morty" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, { :env => { "HOME" => "/Users/morty", "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], }, }).and_return(true) shell_out_obj.shell_out(cmd, options) expect(options[:env].has_key?("LC_ALL")).to be false end end describe "and no env/environment option is present" do it "should set environment language settings to the configured internal locale" do options = { :user => "morty" } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, { :user => "morty", :environment => { "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], }, }).and_return(true) shell_out_obj.shell_out(cmd, options) end end end describe "when the last argument is not a Hash" do it "should set environment language settings to the configured internal locale" do expect(shell_out_obj).to receive(:shell_out_command).with(cmd, { :environment => { "LC_ALL" => Chef::Config[:internal_locale], "LANG" => Chef::Config[:internal_locale], "LANGUAGE" => Chef::Config[:internal_locale], }, }).and_return(true) shell_out_obj.shell_out(cmd) end end end describe "#shell_out_with_systems_locale" do describe "when the last argument is a Hash" do describe "and environment is an option" do it "should not change environment['LC_ALL'] when set to nil" do options = { :environment => { "LC_ALL" => nil } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out_with_systems_locale(cmd, options) end it "should not change environment['LC_ALL'] when set to non-nil" do options = { :environment => { "LC_ALL" => "en_US.UTF-8" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out_with_systems_locale(cmd, options) end it "should no longer set environment['LC_ALL'] to nil when 'LC_ALL' not present" do options = { :environment => { "HOME" => "/Users/morty" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out_with_systems_locale(cmd, options) end end describe "and env is an option" do it "should not change env when set to nil" do options = { :env => { "LC_ALL" => nil } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out_with_systems_locale(cmd, options) end it "should not change env when set to non-nil" do options = { :env => { "LC_ALL" => "en_US.UTF-8" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out_with_systems_locale(cmd, options) end it "should no longer set env['LC_ALL'] to nil when 'LC_ALL' not present" do options = { :env => { "HOME" => "/Users/morty" } } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out_with_systems_locale(cmd, options) end end describe "and no env/environment option is present" do it "should no longer add environment option and set environment['LC_ALL'] to nil" do options = { :user => "morty" } expect(shell_out_obj).to receive(:shell_out_command).with(cmd, options).and_return(true) shell_out_obj.shell_out_with_systems_locale(cmd, options) end end end describe "when the last argument is not a Hash" do it "should no longer add environment options and set environment['LC_ALL'] to nil" do expect(shell_out_obj).to receive(:shell_out_command).with(cmd).and_return(true) shell_out_obj.shell_out_with_systems_locale(cmd) end end end end end chef-12.14.60/spec/unit/mixin/subclass_directive_spec.rb000066400000000000000000000024141276456504500231420ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" class SubclassDirectiveParent extend Chef::Mixin::SubclassDirective subclass_directive :behave_differently end class SubclassDirectiveChild < SubclassDirectiveParent behave_differently end class ChildWithoutDirective < SubclassDirectiveParent end describe Chef::Mixin::Uris do let (:child) { SubclassDirectiveChild.new } let (:other_child) { ChildWithoutDirective.new } it "the child instance has the directive set" do expect(child.behave_differently?).to be true end it "a child that does not declare it does not have it set" do expect(other_child.behave_differently?).to be false end end chef-12.14.60/spec/unit/mixin/template_spec.rb000066400000000000000000000242271276456504500211060ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "cgi" describe Chef::Mixin::Template, "render_template" do let(:sep) { Chef::Platform.windows? ? "\r\n" : "\n" } before :each do @context = Chef::Mixin::Template::TemplateContext.new({}) end it "should render the template evaluated in the given context" do @context[:foo] = "bar" output = @context.render_template_from_string("<%= @foo %>") expect(output).to eq("bar") end template_contents = [ "Fancy\r\nTemplate\r\n\r\n", "Fancy\nTemplate\n\n", "Fancy\r\nTemplate\n\r\n"] describe "when running on windows" do before do allow(ChefConfig).to receive(:windows?).and_return(true) end it "should render the templates with windows line endings" do template_contents.each do |template_content| output = @context.render_template_from_string(template_content) output.each_line do |line| expect(line).to end_with("\r\n") end end end end describe "when running on unix" do before do allow(ChefConfig).to receive(:windows?).and_return(false) end it "should render the templates with unix line endings" do template_contents.each do |template_content| output = @context.render_template_from_string(template_content) output.each_line do |line| expect(line).to end_with("\n") end end end end it "should provide a node method to access @node" do @context[:node] = "tehShizzle" output = @context.render_template_from_string("<%= @node %>") expect(output).to eq("tehShizzle") end describe "with a template resource" do before :each do @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) Chef::Cookbook::FileVendor.fetch_from_disk(@cookbook_repo) @node = Chef::Node.new cl = Chef::CookbookLoader.new(@cookbook_repo) cl.load_cookbooks @cookbook_collection = Chef::CookbookCollection.new(cl) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) @rendered_file_location = Dir.tmpdir + "/openldap_stuff.conf" @resource = Chef::Resource::Template.new(@rendered_file_location) @resource.cookbook_name = "openldap" @current_resource = @resource.dup @content_provider = Chef::Provider::Template::Content.new(@resource, @current_resource, @run_context) @template_context = Chef::Mixin::Template::TemplateContext.new({}) @template_context[:node] = @node @template_context[:template_finder] = Chef::Provider::TemplateFinder.new(@run_context, @resource.cookbook_name, @node) end it "should provide a render method" do output = @template_context.render_template_from_string("before {<%= render('test.erb').strip -%>} after") expect(output).to eq("before {We could be diving for pearls!} after") end it "should render local files" do begin tf = Tempfile.new("partial") tf.write "test" tf.rewind output = @template_context.render_template_from_string("before {<%= render '#{tf.path}', :local => true %>} after") expect(output).to eq("before {test} after") ensure tf.close end end it "should render partials from a different cookbook" do @template_context[:template_finder] = Chef::Provider::TemplateFinder.new(@run_context, "apache2", @node) output = @template_context.render_template_from_string("before {<%= render('test.erb', :cookbook => 'openldap').strip %>} after") expect(output).to eq("before {We could be diving for pearls!} after") end it "should render using the source argument if provided" do begin tf = Tempfile.new("partial") tf.write "test" tf.rewind output = @template_context.render_template_from_string("before {<%= render 'something', :local => true, :source => '#{tf.path}' %>} after") expect(output).to eq("before {test} after") ensure tf.close end end it "should pass the node to partials" do @node.normal[:slappiness] = "happiness" output = @template_context.render_template_from_string("before {<%= render 'openldap_stuff.conf.erb' %>} after") expect(output).to eq("before {slappiness is happiness} after") end it "should pass the original variables to partials" do @template_context[:secret] = "candy" output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb' %>} after") output == "before {super secret is candy} after" end it "should pass the template finder to the partials" do output = @template_context.render_template_from_string("before {<%= render 'nested_openldap_partials.erb', :variables => {:hello => 'Hello World!' } %>} after") output == "before {Hello World!} after" end it "should pass variables to partials" do output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {:secret => 'whatever' } %>} after") expect(output).to eq("before {super secret is whatever} after") end it "should pass variables to partials even if they are named the same" do @template_context[:secret] = "one" output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {:secret => 'two' } %>} after <%= @secret %>") expect(output).to eq("before {super secret is two} after one") end it "should pass nil for missing variables in partials" do output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {} %>} after") expect(output).to eq("before {super secret is } after") output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb' %>} after") expect(output).to eq("before {super secret is } after") end it "should render nested partials" do path = File.expand_path(File.join(CHEF_SPEC_DATA, "partial_one.erb")) output = @template_context.render_template_from_string("before {<%= render('#{path}', :local => true).strip %>} after") expect(output).to eq("before {partial one We could be diving for pearls! calling home} after") end describe "when customizing the template context" do it "extends the context to include modules" do mod = Module.new do def hello "ohai" end end @template_context._extend_modules([mod]) output = @template_context.render_template_from_string("<%=hello%>") expect(output).to eq("ohai") end it "emits a warning when overriding 'core' methods" do mod = Module.new do def render end def node end def render_template end def render_template_from_string end end %w{node render render_template render_template_from_string}.each do |method_name| expect(Chef::Log).to receive(:warn).with(/^Core template method `#{method_name}' overridden by extension module/) end @template_context._extend_modules([mod]) end end end describe "when an exception is raised in the template" do def do_raise @context.render_template_from_string("foo\nbar\nbaz\n<%= this_is_not_defined %>\nquin\nqunx\ndunno") end it "should catch and re-raise the exception as a TemplateError" do expect { do_raise }.to raise_error(Chef::Mixin::Template::TemplateError) end it "should raise an error if an attempt is made to access node but it is nil" do expect { @context.render_template_from_string("<%= node %>") { |r| r } }.to raise_error(Chef::Mixin::Template::TemplateError) end describe "the raised TemplateError" do before :each do begin do_raise rescue Chef::Mixin::Template::TemplateError => e @exception = e end end it "should have the original exception" do expect(@exception.original_exception).to be expect(@exception.original_exception.message).to match(/undefined local variable or method `this_is_not_defined'/) end it "should determine the line number of the exception" do expect(@exception.line_number).to eq(4) end it "should provide a source listing of the template around the exception" do expect(@exception.source_listing).to eq(" 2: bar\n 3: baz\n 4: <%= this_is_not_defined %>\n 5: quin\n 6: qunx") end it "should provide the evaluation context of the template" do expect(@exception.context).to eq(@context) end it "should defer the message to the original exception" do expect(@exception.message).to match(/undefined local variable or method `this_is_not_defined'/) end it "should provide a nice source location" do expect(@exception.source_location).to eq("on line #4") end it "should create a pretty output for the terminal" do expect(@exception.to_s).to match(/Chef::Mixin::Template::TemplateError/) expect(@exception.to_s).to match(/undefined local variable or method `this_is_not_defined'/) expect(@exception.to_s).to include(" 2: bar\n 3: baz\n 4: <%= this_is_not_defined %>\n 5: quin\n 6: qunx") expect(@exception.to_s).to include(@exception.original_exception.backtrace.first) end end end end chef-12.14.60/spec/unit/mixin/unformatter_spec.rb000066400000000000000000000034401276456504500216330ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software # 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/mixin/unformatter" class Chef::UnformatterTest include Chef::Mixin::Unformatter def foo end end describe Chef::Mixin::Unformatter do let (:unformatter) { Chef::UnformatterTest.new } let (:message) { "Test Message" } describe "#write" do context "with a timestamp" do it "sends foo to itself when the message is of severity foo" do expect(unformatter).to receive(:foo).with(message) unformatter.write("[time] foo: #{message}") end it "sends foo to itself when the message is of severity FOO" do expect(unformatter).to receive(:foo).with(message) unformatter.write("[time] FOO: #{message}") end end context "without a timestamp" do it "sends foo to itself when the message is of severity foo" do expect(unformatter).to receive(:foo).with(message) unformatter.write("foo: #{message}") end it "sends foo to itself when the message is of severity FOO" do expect(unformatter).to receive(:foo).with(message) unformatter.write("FOO: #{message}") end end end end chef-12.14.60/spec/unit/mixin/uris_spec.rb000066400000000000000000000031451276456504500202510ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mixin/uris" class Chef::UrisTest include Chef::Mixin::Uris end describe Chef::Mixin::Uris do let (:uris) { Chef::UrisTest.new } describe "#uri_scheme?" do it "matches 'scheme://foo.com'" do expect(uris.uri_scheme?("scheme://foo.com")).to eq(true) end it "does not match 'c:/foo.com'" do expect(uris.uri_scheme?("c:/foo.com")).to eq(false) end it "does not match '/usr/bin/foo.com'" do expect(uris.uri_scheme?("/usr/bin/foo.com")).to eq(false) end it "does not match 'c:/foo.com://bar.com'" do expect(uris.uri_scheme?("c:/foo.com://bar.com")).to eq(false) end end describe "#as_uri" do it "parses a file scheme uri with spaces" do expect { uris.as_uri("file:///c:/foo bar.txt") }.not_to raise_exception end it "returns a URI object" do expect( uris.as_uri("file:///c:/foo bar.txt") ).to be_a(URI) end end end chef-12.14.60/spec/unit/mixin/windows_architecture_helper_spec.rb000066400000000000000000000067061276456504500250700ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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/mixin/windows_architecture_helper" describe Chef::Mixin::WindowsArchitectureHelper do include Chef::Mixin::WindowsArchitectureHelper before do @valid_architectures = [ :i386, :x86_64 ] @invalid_architectures = [ "i386", "x86_64", :x64, :x86, :arm ] @node_i386 = Chef::Node.new @node_x86_64 = Chef::Node.new end it "returns true when valid architectures are passed to valid_windows_architecture?" do @valid_architectures.each do |architecture| expect(valid_windows_architecture?(architecture)).to eq(true) end end it "returns false when invalid architectures are passed to valid_windows_architecture?" do @invalid_architectures.each do |architecture| expect(valid_windows_architecture?(architecture)).to eq(false) end end it "does not raise an exception when a valid architecture is passed to assert_valid_windows_architecture!" do @valid_architectures.each do |architecture| assert_valid_windows_architecture!(architecture) end end it "raises an error if an invalid architecture is passed to assert_valid_windows_architecture!" do @invalid_architectures.each do |architecture| begin expect(assert_valid_windows_architecture!(architecture)).to raise_error Chef::Exceptions::Win32ArchitectureIncorrect rescue Chef::Exceptions::Win32ArchitectureIncorrect end end end it "returns true only for supported desired architecture passed to node_supports_windows_architecture" do with_node_architecture_combinations do |node, desired_arch| expect(node_supports_windows_architecture?(node, desired_arch)).to be true if node_windows_architecture(node) == :x86_64 || desired_arch == :i386 expect(node_supports_windows_architecture?(node, desired_arch)).to be false if node_windows_architecture(node) == :i386 && desired_arch == :x86_64 end end it "returns true only when forced_32bit_override_required? has 64-bit node architecture and 32-bit desired architecture" do with_node_architecture_combinations do |node, desired_arch| expect(forced_32bit_override_required?(node, desired_arch)).to be true if (node_windows_architecture(node) == :x86_64) && (desired_arch == :i386) && !is_i386_process_on_x86_64_windows? expect(forced_32bit_override_required?(node, desired_arch)).to be false if ! ((node_windows_architecture(node) == :x86_64) && (desired_arch == :i386)) end end def with_node_architecture_combinations @valid_architectures.each do |node_architecture| new_node = Chef::Node.new new_node.default["kernel"] = Hash.new new_node.default["kernel"][:machine] = node_architecture.to_s @valid_architectures.each do |architecture| yield new_node, architecture if block_given? end end end end chef-12.14.60/spec/unit/mixin/xml_escape_spec.rb000066400000000000000000000030631276456504500214060ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" class XMLEscapingTestHarness include Chef::Mixin::XMLEscape end describe Chef::Mixin::XMLEscape do before do @escaper = XMLEscapingTestHarness.new end it "escapes ampersands to '&'" do expect(@escaper.xml_escape("&")).to eq("&") end it "escapes angle brackets to < or >" do expect(@escaper.xml_escape("<")).to eq("<") expect(@escaper.xml_escape(">")).to eq(">") end it "does not modify ASCII strings" do expect(@escaper.xml_escape("foobarbaz!@\#$%^*()")).to eq("foobarbaz!@\#$%^*()") end it "converts invalid bytes to asterisks" do expect(@escaper.xml_escape("\x00")).to eq("*") end it "converts UTF-8 correctly" do expect(@escaper.xml_escape("\xC2\xA9")).to eq("©") end it "converts win 1252 characters correctly" do expect(@escaper.xml_escape("#{0x80.chr}")).to eq("€") end end chef-12.14.60/spec/unit/monkey_patches/000077500000000000000000000000001276456504500176125ustar00rootroot00000000000000chef-12.14.60/spec/unit/monkey_patches/uri_spec.rb000066400000000000000000000017551276456504500217600ustar00rootroot00000000000000#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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 URI do describe "when a URI contains an IPv6 literal" do let(:ipv6_uri) do URI.parse("https://[2a00:1450:4009:809::1008]:8443") end it "returns the hostname without brackets" do expect(ipv6_uri.hostname).to eq("2a00:1450:4009:809::1008") end end end chef-12.14.60/spec/unit/monologger_spec.rb000066400000000000000000000030441276456504500203110ustar00rootroot00000000000000# # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/monologger" require "tempfile" require "spec_helper" describe MonoLogger do it "should disable buffering when passed an IO stream" do STDOUT.sync = false MonoLogger.new(STDOUT) expect(STDOUT.sync).to eq(true) end describe "when given an object that responds to write and close e.g. IO" do it "should use the object directly" do stream = StringIO.new MonoLogger.new(stream).fatal("Houston, we've had a problem.") expect(stream.string).to match(/Houston, we've had a problem./) end end describe "when given an object that is stringable (to_str)" do it "should open a File object with the given path" do temp_file = Tempfile.new("rspec-monologger-log") temp_file.close MonoLogger.new(temp_file.path).fatal("Do, or do not. There is no try.") expect(File.read(temp_file.path)).to match(/Do, or do not. There is no try./) end end end chef-12.14.60/spec/unit/node/000077500000000000000000000000001276456504500155265ustar00rootroot00000000000000chef-12.14.60/spec/unit/node/attribute_spec.rb000066400000000000000000001274361276456504500211050ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: AJ Christensen () # 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" require "chef/node/attribute" describe Chef::Node::Attribute do before(:each) do @attribute_hash = { "dmi" => {}, "command" => { "ps" => "ps -ef" }, "platform_version" => "10.5.7", "platform" => "mac_os_x", "ipaddress" => "192.168.0.117", "network" => { "default_interface" => "en1", "interfaces" => { "vmnet1" => { "flags" => %w{UP BROADCAST SMART RUNNING SIMPLEX MULTICAST}, "number" => "1", "addresses" => { "00:50:56:c0:00:01" => { "family" => "lladdr" }, "192.168.110.1" => { "broadcast" => "192.168.110.255", "netmask" => "255.255.255.0", "family" => "inet" } }, "mtu" => "1500", "type" => "vmnet", "arp" => { "192.168.110.255" => "ff:ff:ff:ff:ff:ff" }, "encapsulation" => "Ethernet" }, "stf0" => { "flags" => [], "number" => "0", "addresses" => {}, "mtu" => "1280", "type" => "stf", "encapsulation" => "6to4" }, "lo0" => { "flags" => %w{UP LOOPBACK RUNNING MULTICAST}, "number" => "0", "addresses" => { "::1" => { "scope" => "Node", "prefixlen" => "128", "family" => "inet6" }, "127.0.0.1" => { "netmask" => "255.0.0.0", "family" => "inet" }, "fe80::1" => { "scope" => "Link", "prefixlen" => "64", "family" => "inet6" } }, "mtu" => "16384", "type" => "lo", "encapsulation" => "Loopback" }, "gif0" => { "flags" => %w{POINTOPOINT MULTICAST}, "number" => "0", "addresses" => {}, "mtu" => "1280", "type" => "gif", "encapsulation" => "IPIP" }, "vmnet8" => { "flags" => %w{UP BROADCAST SMART RUNNING SIMPLEX MULTICAST}, "number" => "8", "addresses" => { "192.168.4.1" => { "broadcast" => "192.168.4.255", "netmask" => "255.255.255.0", "family" => "inet" }, "00:50:56:c0:00:08" => { "family" => "lladdr" } }, "mtu" => "1500", "type" => "vmnet", "arp" => { "192.168.4.255" => "ff:ff:ff:ff:ff:ff" }, "encapsulation" => "Ethernet" }, "en0" => { "status" => "inactive", "flags" => %w{UP BROADCAST SMART RUNNING SIMPLEX MULTICAST}, "number" => "0", "addresses" => { "00:23:32:b0:32:f2" => { "family" => "lladdr" } }, "mtu" => "1500", "media" => { "supported" => { "autoselect" => { "options" => [] }, "none" => { "options" => [] }, "1000baseT" => { "options" => ["full-duplex", "flow-control", "hw-loopback"] }, "10baseT/UTP" => { "options" => ["half-duplex", "full-duplex", "flow-control", "hw-loopback"] }, "100baseTX" => { "options" => ["half-duplex", "full-duplex", "flow-control", "hw-loopback"] } }, "selected" => { "autoselect" => { "options" => [] } } }, "type" => "en", "encapsulation" => "Ethernet" }, "en1" => { "status" => "active", "flags" => %w{UP BROADCAST SMART RUNNING SIMPLEX MULTICAST}, "number" => "1", "addresses" => { "fe80::223:6cff:fe7f:676c" => { "scope" => "Link", "prefixlen" => "64", "family" => "inet6" }, "00:23:6c:7f:67:6c" => { "family" => "lladdr" }, "192.168.0.117" => { "broadcast" => "192.168.0.255", "netmask" => "255.255.255.0", "family" => "inet" } }, "mtu" => "1500", "media" => { "supported" => { "autoselect" => { "options" => [] } }, "selected" => { "autoselect" => { "options" => [] } } }, "type" => "en", "arp" => { "192.168.0.72" => "0:f:ea:39:fa:d5", "192.168.0.1" => "0:1c:fb:fc:6f:20", "192.168.0.255" => "ff:ff:ff:ff:ff:ff", "192.168.0.3" => "0:1f:33:ea:26:9b", "192.168.0.77" => "0:23:12:70:f8:cf", "192.168.0.152" => "0:26:8:7d:2:4c" }, "encapsulation" => "Ethernet" }, "en2" => { "status" => "active", "flags" => %w{UP BROADCAST SMART RUNNING SIMPLEX MULTICAST}, "number" => "2", "addresses" => { "169.254.206.152" => { "broadcast" => "169.254.255.255", "netmask" => "255.255.0.0", "family" => "inet" }, "00:1c:42:00:00:01" => { "family" => "lladdr" }, "fe80::21c:42ff:fe00:1" => { "scope" => "Link", "prefixlen" => "64", "family" => "inet6" } }, "mtu" => "1500", "media" => { "supported" => { "autoselect" => { "options" => [] } }, "selected" => { "autoselect" => { "options" => [] } } }, "type" => "en", "encapsulation" => "Ethernet" }, "fw0" => { "status" => "inactive", "flags" => %w{BROADCAST SIMPLEX MULTICAST}, "number" => "0", "addresses" => { "00:23:32:ff:fe:b0:32:f2" => { "family" => "lladdr" } }, "mtu" => "4078", "media" => { "supported" => { "autoselect" => { "options" => ["full-duplex"] } }, "selected" => { "autoselect" => { "options" => ["full-duplex"] } } }, "type" => "fw", "encapsulation" => "1394" }, "en3" => { "status" => "active", "flags" => %w{UP BROADCAST SMART RUNNING SIMPLEX MULTICAST}, "number" => "3", "addresses" => { "169.254.206.152" => { "broadcast" => "169.254.255.255", "netmask" => "255.255.0.0", "family" => "inet" }, "00:1c:42:00:00:00" => { "family" => "lladdr" }, "fe80::21c:42ff:fe00:0" => { "scope" => "Link", "prefixlen" => "64", "family" => "inet6" } }, "mtu" => "1500", "media" => { "supported" => { "autoselect" => { "options" => [] } }, "selected" => { "autoselect" => { "options" => [] } } }, "type" => "en", "encapsulation" => "Ethernet" } } }, "fqdn" => "latte.local", "ohai_time" => 1249065590.90391, "domain" => "local", "os" => "darwin", "platform_build" => "9J61", "os_version" => "9.7.0", "hostname" => "latte", "macaddress" => "00:23:6c:7f:67:6c", "music" => { "jimmy_eat_world" => "nice", "apophis" => false }, } @default_hash = { "domain" => "opscode.com", "hot" => { "day" => "saturday" }, "music" => { "jimmy_eat_world" => "is fun!", "mastodon" => "rocks", "mars_volta" => "is loud and nutty", "deeper" => { "gates_of_ishtar" => nil }, "this" => { "apparatus" => { "must" => "be unearthed" } }, }, } @override_hash = { "macaddress" => "00:00:00:00:00:00", "hot" => { "day" => "sunday" }, "fire" => "still burn", "music" => { "mars_volta" => "cicatriz", }, } @automatic_hash = { "week" => "friday" } @attributes = Chef::Node::Attribute.new(@attribute_hash, @default_hash, @override_hash, @automatic_hash) end describe "initialize" do it "should return a Chef::Node::Attribute" do expect(@attributes).to be_a_kind_of(Chef::Node::Attribute) end it "should take an Automatioc, Normal, Default and Override hash" do expect { Chef::Node::Attribute.new({}, {}, {}, {}) }.not_to raise_error end [ :normal, :default, :override, :automatic ].each do |accessor| it "should set #{accessor}" do na = Chef::Node::Attribute.new({ :normal => true }, { :default => true }, { :override => true }, { :automatic => true }) expect(na.send(accessor)).to eq({ accessor.to_s => true }) end end it "should be enumerable" do expect(@attributes).to be_is_a(Enumerable) end end describe "when printing attribute components" do it "does not cause a type error" do # See CHEF-3799; IO#puts implicitly calls #to_ary on its argument. This # is expected to raise a NoMethodError or return an Array. `to_ary` is # the "strict" conversion method that should only be implemented by # things that are truly Array-like, so NoMethodError is the right choice. # (cf. there is no Hash#to_ary). expect { @attributes.default.to_ary }.to raise_error(NoMethodError) end end describe "when debugging attributes" do before do @attributes.default[:foo][:bar] = "default" @attributes.env_default[:foo][:bar] = "env_default" @attributes.role_default[:foo][:bar] = "role_default" @attributes.force_default[:foo][:bar] = "force_default" @attributes.normal[:foo][:bar] = "normal" @attributes.override[:foo][:bar] = "override" @attributes.role_override[:foo][:bar] = "role_override" @attributes.env_override[:foo][:bar] = "env_override" @attributes.force_override[:foo][:bar] = "force_override" @attributes.automatic[:foo][:bar] = "automatic" end it "gives the value at each level of precedence for a path spec" do expected = [ %w{default default}, %w{env_default env_default}, %w{role_default role_default}, %w{force_default force_default}, %w{normal normal}, %w{override override}, %w{role_override role_override}, %w{env_override env_override}, %w{force_override force_override}, %w{automatic automatic}, ] expect(@attributes.debug_value(:foo, :bar)).to eq(expected) end end describe "when fetching values based on precedence" do before do @attributes.default["default"] = "cookbook default" @attributes.override["override"] = "cookbook override" end it "prefers 'forced default' over any other default" do @attributes.force_default["default"] = "force default" @attributes.role_default["default"] = "role default" @attributes.env_default["default"] = "environment default" expect(@attributes["default"]).to eq("force default") end it "prefers role_default over environment or cookbook default" do @attributes.role_default["default"] = "role default" @attributes.env_default["default"] = "environment default" expect(@attributes["default"]).to eq("role default") end it "prefers environment default over cookbook default" do @attributes.env_default["default"] = "environment default" expect(@attributes["default"]).to eq("environment default") end it "returns the cookbook default when no other default values are present" do expect(@attributes["default"]).to eq("cookbook default") end it "prefers 'forced overrides' over role or cookbook overrides" do @attributes.force_override["override"] = "force override" @attributes.env_override["override"] = "environment override" @attributes.role_override["override"] = "role override" expect(@attributes["override"]).to eq("force override") end it "prefers environment overrides over role or cookbook overrides" do @attributes.env_override["override"] = "environment override" @attributes.role_override["override"] = "role override" expect(@attributes["override"]).to eq("environment override") end it "prefers role overrides over cookbook overrides" do @attributes.role_override["override"] = "role override" expect(@attributes["override"]).to eq("role override") end it "returns cookbook overrides when no other overrides are present" do expect(@attributes["override"]).to eq("cookbook override") end it "merges arrays within the default precedence" do @attributes.role_default["array"] = %w{role} @attributes.env_default["array"] = %w{env} expect(@attributes["array"]).to eq(%w{env role}) end it "merges arrays within the override precedence" do @attributes.role_override["array"] = %w{role} @attributes.env_override["array"] = %w{env} expect(@attributes["array"]).to eq(%w{role env}) end it "does not merge arrays between default and normal" do @attributes.role_default["array"] = %w{role} @attributes.normal["array"] = %w{normal} expect(@attributes["array"]).to eq(%w{normal}) end it "does not merge arrays between normal and override" do @attributes.normal["array"] = %w{normal} @attributes.role_override["array"] = %w{role} expect(@attributes["array"]).to eq(%w{role}) end it "merges nested hashes between precedence levels" do @attributes = Chef::Node::Attribute.new({}, {}, {}, {}) @attributes.env_default = { "a" => { "b" => { "default" => "default" } } } @attributes.normal = { "a" => { "b" => { "normal" => "normal" } } } @attributes.override = { "a" => { "override" => "role" } } @attributes.automatic = { "a" => { "automatic" => "auto" } } expect(@attributes["a"]).to eq({ "b" => { "default" => "default", "normal" => "normal" }, "override" => "role", "automatic" => "auto" }) end end describe "when reading combined default or override values" do before do @attributes.default["cd"] = "cookbook default" @attributes.role_default["rd"] = "role default" @attributes.env_default["ed"] = "env default" @attributes.default!["fd"] = "force default" @attributes.override["co"] = "cookbook override" @attributes.role_override["ro"] = "role override" @attributes.env_override["eo"] = "env override" @attributes.override!["fo"] = "force override" end it "merges all types of overrides into a combined override" do expect(@attributes.combined_override["co"]).to eq("cookbook override") expect(@attributes.combined_override["ro"]).to eq("role override") expect(@attributes.combined_override["eo"]).to eq("env override") expect(@attributes.combined_override["fo"]).to eq("force override") end it "merges all types of defaults into a combined default" do expect(@attributes.combined_default["cd"]).to eq("cookbook default") expect(@attributes.combined_default["rd"]).to eq("role default") expect(@attributes.combined_default["ed"]).to eq("env default") expect(@attributes.combined_default["fd"]).to eq("force default") end end describe "[]" do it "should return override data if it exists" do expect(@attributes["macaddress"]).to eq("00:00:00:00:00:00") end it "should return attribute data if it is not overridden" do expect(@attributes["platform"]).to eq("mac_os_x") end it "should return data that doesn't have corresponding keys in every hash" do expect(@attributes["command"]["ps"]).to eq("ps -ef") end it "should return default data if it is not overriden or in attribute data" do expect(@attributes["music"]["mastodon"]).to eq("rocks") end it "should prefer the override data over an available default" do expect(@attributes["music"]["mars_volta"]).to eq("cicatriz") end it "should prefer the attribute data over an available default" do expect(@attributes["music"]["jimmy_eat_world"]).to eq("nice") end it "should prefer override data over default data if there is no attribute data" do expect(@attributes["hot"]["day"]).to eq("sunday") end it "should return the merged hash if all three have values" do result = @attributes["music"] expect(result["mars_volta"]).to eq("cicatriz") expect(result["jimmy_eat_world"]).to eq("nice") expect(result["mastodon"]).to eq("rocks") end end describe "[]=" do it "should error out when the type of attribute to set has not been specified" do @attributes.normal["the_ghost"] = {} expect { @attributes["the_ghost"]["exterminate"] = false }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) end it "should let you set an attribute value when another hash has an intermediate value" do @attributes.normal["the_ghost"] = { "exterminate" => "the future" } expect { @attributes.normal["the_ghost"]["eviscerate"]["tomorrow"] = false }.not_to raise_error end it "should set the attribute value" do @attributes.normal["longboard"] = "surfing" expect(@attributes.normal["longboard"]).to eq("surfing") expect(@attributes.normal["longboard"]).to eq("surfing") end it "should set deeply nested attribute values when a precedence level is specified" do @attributes.normal["deftones"]["hunters"]["nap"] = "surfing" expect(@attributes.normal["deftones"]["hunters"]["nap"]).to eq("surfing") end it "should die if you try and do nested attributes that do not exist without read vivification" do expect { @attributes["foo"]["bar"] = :baz }.to raise_error(NoMethodError) end it "should let you set attributes manually without vivification" do @attributes.normal["foo"] = Mash.new @attributes.normal["foo"]["bar"] = :baz expect(@attributes.normal["foo"]["bar"]).to eq(:baz) end it "does not support ||= when setting" do # This is a limitation of auto-vivification. # Users who need this behavior can use set_unless and friends @attributes.normal["foo"] = Mash.new @attributes.normal["foo"]["bar"] ||= "stop the world" expect(@attributes.normal["foo"]["bar"]).to eq({}) end end describe "to_hash" do it "should convert to a hash" do expect(@attributes.to_hash.class).to eq(Hash) end it "should convert to a hash based on current state" do hash = @attributes["hot"].to_hash expect(hash.class).to eq(Hash) expect(hash["day"]).to eq("sunday") end it "should create a deep copy of the node attribute" do @attributes.default["foo"]["bar"]["baz"] = "fizz" hash = @attributes["foo"].to_hash expect(hash).to eql({ "bar" => { "baz" => "fizz" } }) hash["bar"]["baz"] = "buzz" expect(hash).to eql({ "bar" => { "baz" => "buzz" } }) expect(@attributes.default["foo"]).to eql({ "bar" => { "baz" => "fizz" } }) end it "should create a deep copy of arrays in the node attribute" do @attributes.default["foo"]["bar"] = ["fizz"] hash = @attributes["foo"].to_hash expect(hash).to eql({ "bar" => [ "fizz" ] }) hash["bar"].push("buzz") expect(hash).to eql({ "bar" => %w{fizz buzz} }) expect(@attributes.default["foo"]).to eql({ "bar" => [ "fizz" ] }) end it "mutating strings should not mutate the attributes" do pending "this is a bug that should be fixed" @attributes.default["foo"]["bar"]["baz"] = "fizz" hash = @attributes["foo"].to_hash expect(hash).to eql({ "bar" => { "baz" => "fizz" } }) hash["bar"]["baz"] << "buzz" expect(hash).to eql({ "bar" => { "baz" => "fizzbuzz" } }) expect(@attributes.default["foo"]).to eql({ "bar" => { "baz" => "fizz" } }) end end describe "dup" do it "array can be duped even if some elements can't" do @attributes.default[:foo] = %w{foo bar baz} + Array(1..3) + [nil, true, false, [ "el", 0, nil ] ] @attributes.default[:foo].dup end end describe "has_key?" do it "should return true if an attribute exists" do expect(@attributes.has_key?("music")).to eq(true) end it "should return false if an attribute does not exist" do expect(@attributes.has_key?("ninja")).to eq(false) end it "should return false if an attribute does not exist using dot notation" do expect(@attributes.has_key?("does_not_exist_at_all")).to eq(false) end it "should return true if an attribute exists but is set to nil using dot notation" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(@attributes.music.deeper.has_key?("gates_of_ishtar")).to eq(true) end it "should return true if an attribute exists but is set to false" do @attributes.has_key?("music") expect(@attributes["music"].has_key?("apophis")).to eq(true) end it "does not find keys above the current nesting level" do expect(@attributes["music"]["this"]["apparatus"]).not_to have_key("this") end it "does not find keys below the current nesting level" do expect(@attributes["music"]["this"]).not_to have_key("must") end [:include?, :key?, :member?].each do |method| it "should alias the method #{method} to itself" do expect(@attributes).to respond_to(method) end it "#{method} should behave like has_key?" do expect(@attributes.send(method, "music")).to eq(true) end end end describe "attribute?" do it "should return true if an attribute exists" do expect(@attributes.attribute?("music")).to eq(true) end it "should return false if an attribute does not exist" do expect(@attributes.attribute?("ninja")).to eq(false) end end describe "method_missing" do it "should behave like a [] lookup" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(@attributes.music.mastodon).to eq("rocks") end it "should allow the last method to set a value if it has an = sign on the end" do Chef::Config[:treat_deprecation_warnings_as_errors] = false @attributes.normal.music.mastodon = %w{dream still shining} expect(@attributes.normal.music.mastodon).to eq(%w{dream still shining}) end end describe "keys" do before(:each) do @attributes = Chef::Node::Attribute.new( { "one" => { "two" => "three" }, "hut" => { "two" => "three" }, "place" => {}, }, { "one" => { "four" => "five" }, "snakes" => "on a plane", }, { "one" => { "six" => "seven" }, "snack" => "cookies", }, {} ) end it "should yield each top level key" do collect = Array.new @attributes.keys.each do |k| collect << k end expect(collect.include?("one")).to eq(true) expect(collect.include?("hut")).to eq(true) expect(collect.include?("snakes")).to eq(true) expect(collect.include?("snack")).to eq(true) expect(collect.include?("place")).to eq(true) expect(collect.length).to eq(5) end it "should yield lower if we go deeper" do collect = Array.new @attributes["one"].keys.each do |k| collect << k end expect(collect.include?("two")).to eq(true) expect(collect.include?("four")).to eq(true) expect(collect.include?("six")).to eq(true) expect(collect.length).to eq(3) end it "should not raise an exception if one of the hashes has a nil value on a deep lookup" do expect { @attributes["place"].keys { |k| } }.not_to raise_error end end describe "each" do before(:each) do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) end it "should yield each top level key and value, post merge rules" do collect = Hash.new @attributes.each do |k, v| collect[k] = v end expect(collect["one"]).to eq("six") expect(collect["hut"]).to eq("three") expect(collect["snakes"]).to eq("on a plane") expect(collect["snack"]).to eq("cookies") end it "should yield as a two-element array" do @attributes.each do |a| expect(a).to be_an_instance_of(Array) end end end describe "each_key" do before do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) end it "should respond to each_key" do expect(@attributes).to respond_to(:each_key) end it "should yield each top level key, post merge rules" do collect = Array.new @attributes.each_key do |k| collect << k end expect(collect).to include("one") expect(collect).to include("snack") expect(collect).to include("hut") expect(collect).to include("snakes") end end describe "each_pair" do before do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) end it "should respond to each_pair" do expect(@attributes).to respond_to(:each_pair) end it "should yield each top level key and value pair, post merge rules" do collect = Hash.new @attributes.each_pair do |k, v| collect[k] = v end expect(collect["one"]).to eq("six") expect(collect["hut"]).to eq("three") expect(collect["snakes"]).to eq("on a plane") expect(collect["snack"]).to eq("cookies") end end describe "each_value" do before do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) end it "should respond to each_value" do expect(@attributes).to respond_to(:each_value) end it "should yield each value, post merge rules" do collect = Array.new @attributes.each_value do |v| collect << v end expect(collect).to include("cookies") expect(collect).to include("three") expect(collect).to include("on a plane") end it "should yield four elements" do collect = Array.new @attributes.each_value do |v| collect << v end expect(collect.length).to eq(4) end end describe "empty?" do before do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) @empty = Chef::Node::Attribute.new({}, {}, {}, {}) end it "should respond to empty?" do expect(@attributes).to respond_to(:empty?) end it "should return true when there are no keys" do expect(@empty.empty?).to eq(true) end it "should return false when there are keys" do expect(@attributes.empty?).to eq(false) end end describe "fetch" do before do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) end it "should respond to fetch" do expect(@attributes).to respond_to(:fetch) end describe "when the key exists" do it "should return the value of the key, post merge (same result as each)" do { "one" => "six", "hut" => "three", "snakes" => "on a plane", "snack" => "cookies", }.each do |k, v| expect(@attributes.fetch(k)).to eq(v) end end end describe "when the key does not exist" do describe "and no args are passed" do it "should raise an indexerror" do expect { @attributes.fetch("lololol") }.to raise_error(IndexError) end end describe "and a default arg is passed" do it "should return the value of the default arg" do expect(@attributes.fetch("lol", "blah")).to eq("blah") end end describe "and a block is passed" do it "should run the block and return its value" do expect(@attributes.fetch("lol") { |x| "#{x}, blah" }).to eq("lol, blah") end end end end describe "has_value?" do before do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) end it "should respond to has_value?" do expect(@attributes).to respond_to(:has_value?) end it "should return true if any key has the value supplied" do expect(@attributes.has_value?("cookies")).to eq(true) end it "should return false no key has the value supplied" do expect(@attributes.has_value?("lololol")).to eq(false) end it "should alias value?" do expect(@attributes).to respond_to(:value?) end end describe "index" do # Hash#index is deprecated and triggers warnings. def silence old_verbose = $VERBOSE $VERBOSE = nil yield ensure $VERBOSE = old_verbose end before do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) end it "should respond to index" do expect(@attributes).to respond_to(:index) end describe "when the value is indexed" do it "should return the index" do silence do expect(@attributes.index("six")).to eq("one") end end end describe "when the value is not indexed" do it "should return nil" do silence do expect(@attributes.index("lolol")).to eq(nil) end end end end describe "values" do before do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) end it "should respond to values" do expect(@attributes).to respond_to(:values) end it "should return an array of values" do expect(@attributes.values.length).to eq(4) end it "should match the values output from each" do expect(@attributes.values).to include("six") expect(@attributes.values).to include("cookies") expect(@attributes.values).to include("three") expect(@attributes.values).to include("on a plane") end end describe "select" do before do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) end it "should respond to select" do expect(@attributes).to respond_to(:select) end if RUBY_VERSION >= "1.8.7" it "should not raise a LocalJumpError if no block is given" do expect { @attributes.select }.not_to raise_error end else it "should raise a LocalJumpError if no block is given" do expect { @attributes.select }.to raise_error(LocalJumpError) end end it "should return an empty hash/array (ruby-version-dependent) for a block containing nil" do expect(@attributes.select { nil }).to eq({}.select { nil }) end # sorted for spec clarity it "should return a new array of k,v pairs for which the block returns true" do expect(@attributes.select { true }.sort).to eq( [ %w{hut three}, %w{one six}, %w{snack cookies}, ["snakes", "on a plane"], ] ) end end describe "size" do before do @attributes = Chef::Node::Attribute.new( { "one" => "two", "hut" => "three", }, { "one" => "four", "snakes" => "on a plane", }, { "one" => "six", "snack" => "cookies", }, {} ) @empty = Chef::Node::Attribute.new({}, {}, {}, {}) end it "should respond to size" do expect(@attributes).to respond_to(:size) end it "should alias length to size" do expect(@attributes).to respond_to(:length) end it "should return 0 for an empty attribute" do expect(@empty.size).to eq(0) end it "should return the number of pairs" do expect(@attributes.size).to eq(4) end end describe "kind_of?" do it "should falsely inform you that it is a Hash" do expect(@attributes).to be_a_kind_of(Hash) end it "should falsely inform you that it is a Mash" do expect(@attributes).to be_a_kind_of(Mash) end it "should inform you that it is a Chef::Node::Attribute" do expect(@attributes).to be_a_kind_of(Chef::Node::Attribute) end it "should inform you that it is anything else" do expect(@attributes).not_to be_a_kind_of(Chef::Node) end end describe "to_s" do it "should output simple attributes" do attributes = Chef::Node::Attribute.new(nil, nil, nil, nil) expect(attributes.to_s).to eq("{}") end it "should output merged attributes" do default_hash = { "a" => 1, "b" => 2, } override_hash = { "b" => 3, "c" => 4, } attributes = Chef::Node::Attribute.new(nil, default_hash, override_hash, nil) expect(attributes.to_s).to eq('{"a"=>1, "b"=>3, "c"=>4}') end end describe "inspect" do it "should be readable" do # NOTE: previous implementation hid the values, showing @automatic={...} # That is nice and compact, but hides a lot of info, which seems counter # to the point of calling #inspect... expect(@attributes.inspect).to match(/@automatic=\{.*\}/) expect(@attributes.inspect).to match(/@normal=\{.*\}/) end end describe "when not mutated" do it "does not reset the cache when dup'd [CHEF-3680]" do @attributes.default[:foo][:bar] = "set on original" subtree = @attributes[:foo] @attributes.default[:foo].dup[:bar] = "set on dup" expect(subtree[:bar]).to eq("set on original") end end describe "when setting a component attribute to a new value" do it "converts the input in to a VividMash tree (default)" do @attributes.default = {} @attributes.default.foo = "bar" expect(@attributes.merged_attributes[:foo]).to eq("bar") end it "converts the input in to a VividMash tree (normal)" do @attributes.normal = {} @attributes.normal.foo = "bar" expect(@attributes.merged_attributes[:foo]).to eq("bar") end it "converts the input in to a VividMash tree (override)" do @attributes.override = {} @attributes.override.foo = "bar" expect(@attributes.merged_attributes[:foo]).to eq("bar") end it "converts the input in to a VividMash tree (automatic)" do @attributes.automatic = {} @attributes.automatic.foo = "bar" expect(@attributes.merged_attributes[:foo]).to eq("bar") end end describe "when deep-merging between precedence levels" do it "correctly deep merges hashes and preserves the original contents" do @attributes.default = { "arglebargle" => { "foo" => "bar" } } @attributes.override = { "arglebargle" => { "fizz" => "buzz" } } expect(@attributes.merged_attributes[:arglebargle]).to eq({ "foo" => "bar", "fizz" => "buzz" }) expect(@attributes.default[:arglebargle]).to eq({ "foo" => "bar" }) expect(@attributes.override[:arglebargle]).to eq({ "fizz" => "buzz" }) end it "does not deep merge arrays, and preserves the original contents" do @attributes.default = { "arglebargle" => [ 1, 2, 3 ] } @attributes.override = { "arglebargle" => [ 4, 5, 6 ] } expect(@attributes.merged_attributes[:arglebargle]).to eq([ 4, 5, 6 ]) expect(@attributes.default[:arglebargle]).to eq([ 1, 2, 3 ]) expect(@attributes.override[:arglebargle]).to eq([ 4, 5, 6 ]) end it "correctly deep merges hashes and preserves the original contents when merging default and role_default" do @attributes.default = { "arglebargle" => { "foo" => "bar" } } @attributes.role_default = { "arglebargle" => { "fizz" => "buzz" } } expect(@attributes.merged_attributes[:arglebargle]).to eq({ "foo" => "bar", "fizz" => "buzz" }) expect(@attributes.default[:arglebargle]).to eq({ "foo" => "bar" }) expect(@attributes.role_default[:arglebargle]).to eq({ "fizz" => "buzz" }) end it "correctly deep merges arrays, and preserves the original contents when merging default and role_default" do @attributes.default = { "arglebargle" => [ 1, 2, 3 ] } @attributes.role_default = { "arglebargle" => [ 4, 5, 6 ] } expect(@attributes.merged_attributes[:arglebargle]).to eq([ 1, 2, 3, 4, 5, 6 ]) expect(@attributes.default[:arglebargle]).to eq([ 1, 2, 3 ]) expect(@attributes.role_default[:arglebargle]).to eq([ 4, 5, 6 ]) end end describe "when attemping to write without specifying precedence" do it "raises an error when using []=" do expect { @attributes[:new_key] = "new value" }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) end it "raises an error when using `attr=value`" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect { @attributes.new_key = "new value" }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) end end end chef-12.14.60/spec/unit/node/immutable_collections_spec.rb000066400000000000000000000123341276456504500234450ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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/node/immutable_collections" describe Chef::Node::ImmutableMash do before do @data_in = { :top => { :second_level => "some value" }, "top_level_2" => %w{array of values}, :top_level_3 => [{ :hash_array => 1, :hash_array_b => 2 }], :top_level_4 => { :level2 => { :key => "value" } }, } @immutable_mash = Chef::Node::ImmutableMash.new(@data_in) end it "element references like regular hash" do expect(@immutable_mash[:top][:second_level]).to eq("some value") end it "element references like a regular Mash" do expect(@immutable_mash[:top_level_2]).to eq(%w{array of values}) end it "converts Hash-like inputs into ImmutableMash's" do expect(@immutable_mash[:top]).to be_a(Chef::Node::ImmutableMash) end it "converts array inputs into ImmutableArray's" do expect(@immutable_mash[:top_level_2]).to be_a(Chef::Node::ImmutableArray) end it "converts arrays of hashes to ImmutableArray's of ImmutableMashes" do expect(@immutable_mash[:top_level_3].first).to be_a(Chef::Node::ImmutableMash) end it "converts nested hashes to ImmutableMashes" do expect(@immutable_mash[:top_level_4]).to be_a(Chef::Node::ImmutableMash) expect(@immutable_mash[:top_level_4][:level2]).to be_a(Chef::Node::ImmutableMash) end describe "to_hash" do before do @copy = @immutable_mash.to_hash end it "converts an immutable mash to a new mutable hash" do expect(@copy).to be_instance_of(Hash) end it "converts an immutable nested mash to a new mutable hash" do expect(@copy["top_level_4"]["level2"]).to be_instance_of(Hash) end it "converts an immutable nested array to a new mutable array" do expect(@copy["top_level_2"]).to be_instance_of(Array) end it "should create a mash with the same content" do expect(@copy).to eq(@immutable_mash) end it "should allow mutation" do expect { @copy["m"] = "m" }.not_to raise_error end end [ :[]=, :clear, :default=, :default_proc=, :delete, :delete_if, :keep_if, :merge!, :update, :reject!, :replace, :select!, :shift, :write, :write!, :unlink, :unlink!, ].each do |mutator| it "doesn't allow mutation via `#{mutator}'" do expect { @immutable_mash.send(mutator) }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) end end it "returns a mutable version of itself when duped" do mutable = @immutable_mash.dup mutable[:new_key] = :value expect(mutable[:new_key]).to eq(:value) end end describe Chef::Node::ImmutableArray do before do @immutable_array = Chef::Node::ImmutableArray.new(%w{foo bar baz} + Array(1..3) + [nil, true, false, [ "el", 0, nil ] ]) immutable_mash = Chef::Node::ImmutableMash.new({ :m => "m" }) @immutable_nested_array = Chef::Node::ImmutableArray.new(["level1", @immutable_array, immutable_mash]) end ## # Note: other behaviors, such as immutibilizing input data, are tested along # with ImmutableMash, above ### [ :<<, :[]=, :clear, :collect!, :compact!, :default=, :default_proc=, :delete, :delete_at, :delete_if, :fill, :flatten!, :insert, :keep_if, :map!, :merge!, :pop, :push, :update, :reject!, :reverse!, :replace, :select!, :shift, :slice!, :sort!, :sort_by!, :uniq!, :unshift, ].each do |mutator| it "does not allow mutation via `#{mutator}" do expect { @immutable_array.send(mutator) }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) end end it "can be duped even if some elements can't" do @immutable_array.dup end it "returns a mutable version of itself when duped" do mutable = @immutable_array.dup mutable[0] = :value expect(mutable[0]).to eq(:value) end describe "to_a" do before do @copy = @immutable_nested_array.to_a end it "converts an immutable array to a new mutable array" do expect(@copy).to be_instance_of(Array) end it "converts an immutable nested array to a new mutable array" do expect(@copy[1]).to be_instance_of(Array) end it "converts an immutable nested mash to a new mutable hash" do expect(@copy[2]).to be_instance_of(Hash) end it "should create an array with the same content" do expect(@copy).to eq(@immutable_nested_array) end it "should allow mutation" do expect { @copy << "m" }.not_to raise_error end end end chef-12.14.60/spec/unit/node/vivid_mash_spec.rb000066400000000000000000000345111276456504500212220ustar00rootroot00000000000000# # 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 "spec_helper" require "chef/node/attribute_collections" describe Chef::Node::VividMash do class Root attr_accessor :top_level_breadcrumb end let(:root) { Root.new } let(:vivid) do expect(root).to receive(:reset_cache).at_least(:once).with(nil) Chef::Node::VividMash.new(root, { "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil } ) end def with_breadcrumb(key) expect(root).to receive(:top_level_breadcrumb=).with(nil).at_least(:once).and_call_original expect(root).to receive(:top_level_breadcrumb=).with(key).at_least(:once).and_call_original end context "#read" do before do # vivify the vividmash, then we're read-only so the cache should never be cleared afterwards vivid expect(root).not_to receive(:reset_cache) end it "reads hashes deeply" do with_breadcrumb("one") expect(vivid.read("one", "two", "three")).to eql("four") end it "does not trainwreck when hitting hash keys that do not exist" do with_breadcrumb("one") expect(vivid.read("one", "five", "six")).to eql(nil) end it "does not trainwreck when hitting an array with an out of bounds index" do with_breadcrumb("array") expect(vivid.read("array", 5, "one")).to eql(nil) end it "does not trainwreck when hitting an array with a string key" do with_breadcrumb("array") expect(vivid.read("array", "one", "two")).to eql(nil) end it "does not trainwreck when traversing a nil" do with_breadcrumb("nil") expect(vivid.read("nil", "one", "two")).to eql(nil) end end context "#exist?" do before do # vivify the vividmash, then we're read-only so the cache should never be cleared afterwards vivid expect(root).not_to receive(:reset_cache) end it "true if there's a hash key there" do with_breadcrumb("one") expect(vivid.exist?("one", "two", "three")).to be true end it "true for intermediate hashes" do with_breadcrumb("one") expect(vivid.exist?("one")).to be true end it "true for arrays that exist" do with_breadcrumb("array") expect(vivid.exist?("array", 1)).to be true end it "true when the value of the key is nil" do with_breadcrumb("nil") expect(vivid.exist?("nil")).to be true end it "false when attributes don't exist" do with_breadcrumb("one") expect(vivid.exist?("one", "five", "six")).to be false end it "false when traversing a non-container" do with_breadcrumb("one") expect(vivid.exist?("one", "two", "three", "four")).to be false end it "false when an array index does not exist" do with_breadcrumb("array") expect(vivid.exist?("array", 3)).to be false end it "false when traversing a nil" do with_breadcrumb("nil") expect(vivid.exist?("nil", "foo", "bar")).to be false end end context "#read!" do before do # vivify the vividmash, then we're read-only so the cache should never be cleared afterwards vivid expect(root).not_to receive(:reset_cache) end it "reads hashes deeply" do with_breadcrumb("one") expect(vivid.read!("one", "two", "three")).to eql("four") end it "reads arrays deeply" do with_breadcrumb("array") expect(vivid.read!("array", 1)).to eql(1) end it "throws an exception when attributes do not exist" do with_breadcrumb("one") expect { vivid.read!("one", "five", "six") }.to raise_error(Chef::Exceptions::NoSuchAttribute) end it "throws an exception when traversing a non-container" do with_breadcrumb("one") expect { vivid.read!("one", "two", "three", "four") }.to raise_error(Chef::Exceptions::NoSuchAttribute) end it "throws an exception when an array element does not exist" do with_breadcrumb("array") expect { vivid.read!("array", 3) }.to raise_error(Chef::Exceptions::NoSuchAttribute) end end context "#write" do before do vivid expect(root).not_to receive(:reset_cache).with(nil) end it "should write into hashes" do with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write("one", "five", "six") expect(vivid["one"]["five"]).to eql("six") end it "should deeply autovivify" do with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write("one", "five", "six", "seven", "eight", "nine", "ten") expect(vivid["one"]["five"]["six"]["seven"]["eight"]["nine"]).to eql("ten") end it "should raise an exception if you overwrite an array with a hash" do with_breadcrumb("array") expect(root).to receive(:reset_cache).at_least(:once).with("array") vivid.write("array", "five", "six") expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => { "five" => "six" }, "nil" => nil }) end it "should raise an exception if you traverse through an array with a hash" do with_breadcrumb("array") expect(root).to receive(:reset_cache).at_least(:once).with("array") vivid.write("array", "five", "six", "seven") expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => { "five" => { "six" => "seven" } }, "nil" => nil }) end it "should raise an exception if you overwrite a string with a hash" do with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write("one", "two", "three", "four", "five") expect(vivid).to eql({ "one" => { "two" => { "three" => { "four" => "five" } } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you traverse through a string with a hash" do with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write("one", "two", "three", "four", "five", "six") expect(vivid).to eql({ "one" => { "two" => { "three" => { "four" => { "five" => "six" } } } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you overwrite a nil with a hash" do with_breadcrumb("nil") expect(root).to receive(:reset_cache).at_least(:once).with("nil") vivid.write("nil", "one", "two") expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => { "one" => "two" } }) end it "should raise an exception if you traverse through a nil with a hash" do with_breadcrumb("nil") expect(root).to receive(:reset_cache).at_least(:once).with("nil") vivid.write("nil", "one", "two", "three") expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => { "one" => { "two" => "three" } } }) end it "writes with a block" do with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write("one", "five") { "six" } expect(vivid["one"]["five"]).to eql("six") end end context "#write!" do before do vivid expect(root).not_to receive(:reset_cache).with(nil) end it "should write into hashes" do with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write!("one", "five", "six") expect(vivid["one"]["five"]).to eql("six") end it "should deeply autovivify" do with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write!("one", "five", "six", "seven", "eight", "nine", "ten") expect(vivid["one"]["five"]["six"]["seven"]["eight"]["nine"]).to eql("ten") end it "should raise an exception if you overwrite an array with a hash" do with_breadcrumb("array") expect(root).not_to receive(:reset_cache) expect { vivid.write!("array", "five", "six") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you traverse through an array with a hash" do with_breadcrumb("array") expect(root).not_to receive(:reset_cache) expect { vivid.write!("array", "five", "six", "seven") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you overwrite a string with a hash" do with_breadcrumb("one") expect(root).not_to receive(:reset_cache) expect { vivid.write!("one", "two", "three", "four", "five") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you traverse through a string with a hash" do with_breadcrumb("one") expect(root).not_to receive(:reset_cache) expect { vivid.write!("one", "two", "three", "four", "five", "six") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you overwrite a nil with a hash" do with_breadcrumb("nil") expect(root).not_to receive(:reset_cache) expect { vivid.write!("nil", "one", "two") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should raise an exception if you traverse through a nil with a hash" do with_breadcrumb("nil") expect(root).not_to receive(:reset_cache) expect { vivid.write!("nil", "one", "two", "three") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "writes with a block" do with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") vivid.write!("one", "five") { "six" } expect(vivid["one"]["five"]).to eql("six") end end context "#unlink" do before do vivid expect(root).not_to receive(:reset_cache).with(nil) end it "should return nil if the keys don't already exist" do expect(root).to receive(:top_level_breadcrumb=).with(nil).at_least(:once).and_call_original expect(root).not_to receive(:reset_cache) expect(vivid.unlink("five", "six", "seven", "eight")).to eql(nil) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should unlink hashes" do with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") expect( vivid.unlink("one") ).to eql({ "two" => { "three" => "four" } }) expect(vivid).to eql({ "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should unlink array elements" do with_breadcrumb("array") expect(root).to receive(:reset_cache).at_least(:once).with("array") expect(vivid.unlink("array", 2)).to eql(2) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1 ], "nil" => nil }) end it "should unlink nil" do with_breadcrumb("nil") expect(root).to receive(:reset_cache).at_least(:once).with("nil") expect(vivid.unlink("nil")).to eql(nil) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ] }) end it "should traverse a nil and safely do nothing" do with_breadcrumb("nil") expect(root).not_to receive(:reset_cache) expect(vivid.unlink("nil", "foo")).to eql(nil) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end end context "#unlink!" do before do vivid expect(root).not_to receive(:reset_cache).with(nil) end it "should raise an exception if the keys don't already exist" do expect(root).to receive(:top_level_breadcrumb=).with(nil).at_least(:once).and_call_original expect(root).not_to receive(:reset_cache) expect { vivid.unlink!("five", "six", "seven", "eight") }.to raise_error(Chef::Exceptions::NoSuchAttribute) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should unlink! hashes" do with_breadcrumb("one") expect(root).to receive(:reset_cache).at_least(:once).with("one") expect( vivid.unlink!("one") ).to eql({ "two" => { "three" => "four" } }) expect(vivid).to eql({ "array" => [ 0, 1, 2 ], "nil" => nil }) end it "should unlink! array elements" do with_breadcrumb("array") expect(root).to receive(:reset_cache).at_least(:once).with("array") expect(vivid.unlink!("array", 2)).to eql(2) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1 ], "nil" => nil }) end it "should unlink! nil" do with_breadcrumb("nil") expect(root).to receive(:reset_cache).at_least(:once).with("nil") expect(vivid.unlink!("nil")).to eql(nil) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ] }) end it "should raise an exception if it traverses a nil" do with_breadcrumb("nil") expect(root).not_to receive(:reset_cache) expect { vivid.unlink!("nil", "foo") }.to raise_error(Chef::Exceptions::NoSuchAttribute) expect(vivid).to eql({ "one" => { "two" => { "three" => "four" } }, "array" => [ 0, 1, 2 ], "nil" => nil }) end end end chef-12.14.60/spec/unit/node_map_spec.rb000066400000000000000000000134761276456504500177350ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/node_map" describe Chef::NodeMap do let(:node_map) { Chef::NodeMap.new } let(:node) { Chef::Node.new } describe "with a bad filter name" do it "should raise an error" do expect { node_map.set(node, :thing, on_platform_family: "rhel") }.to raise_error(ArgumentError) end end describe "when no matchers are set at all" do before do node_map.set(:thing, :foo) end it "returns the value" do expect(node_map.get(node, :thing)).to eql(:foo) end it "returns nil for keys that do not exist" do expect(node_map.get(node, :other_thing)).to eql(nil) end end describe "filtering by os" do before do node_map.set(:thing, :foo, os: ["windows"]) node_map.set(:thing, :bar, os: "linux") end it "returns the correct value for windows" do allow(node).to receive(:[]).with(:os).and_return("windows") expect(node_map.get(node, :thing)).to eql(:foo) end it "returns the correct value for linux" do allow(node).to receive(:[]).with(:os).and_return("linux") expect(node_map.get(node, :thing)).to eql(:bar) end it "returns nil for a non-matching os" do allow(node).to receive(:[]).with(:os).and_return("freebsd") expect(node_map.get(node, :thing)).to eql(nil) end end describe "rejecting an os" do before do node_map.set(:thing, :foo, os: "!windows") end it "returns nil for windows" do allow(node).to receive(:[]).with(:os).and_return("windows") expect(node_map.get(node, :thing)).to eql(nil) end it "returns the correct value for linux" do allow(node).to receive(:[]).with(:os).and_return("linux") expect(node_map.get(node, :thing)).to eql(:foo) end end describe "filtering by os and platform_family" do before do node_map.set(:thing, :bar, os: "linux", platform_family: "rhel") end it "returns the correct value when both match" do allow(node).to receive(:[]).with(:os).and_return("linux") allow(node).to receive(:[]).with(:platform_family).and_return("rhel") expect(node_map.get(node, :thing)).to eql(:bar) end it "returns nil for a non-matching os" do allow(node).to receive(:[]).with(:os).and_return("freebsd") expect(node_map.get(node, :thing)).to eql(nil) end it "returns nil when the platform_family does not match" do allow(node).to receive(:[]).with(:os).and_return("linux") allow(node).to receive(:[]).with(:platform_family).and_return("debian") expect(node_map.get(node, :thing)).to eql(nil) end end describe "with a block doing platform_version checks" do before do node_map.set(:thing, :foo, platform_family: "rhel") do |node| node[:platform_version].to_i >= 7 end end it "returns the value when the node matches" do allow(node).to receive(:[]).with(:platform_family).and_return("rhel") allow(node).to receive(:[]).with(:platform_version).and_return("7.0") expect(node_map.get(node, :thing)).to eql(:foo) end it "returns nil when the block does not match" do allow(node).to receive(:[]).with(:platform_family).and_return("rhel") allow(node).to receive(:[]).with(:platform_version).and_return("6.4") expect(node_map.get(node, :thing)).to eql(nil) end it "returns nil when the platform_family filter does not match" do allow(node).to receive(:[]).with(:platform_family).and_return("debian") allow(node).to receive(:[]).with(:platform_version).and_return("7.0") expect(node_map.get(node, :thing)).to eql(nil) end it "returns nil when both do not match" do allow(node).to receive(:[]).with(:platform_family).and_return("debian") allow(node).to receive(:[]).with(:platform_version).and_return("6.0") expect(node_map.get(node, :thing)).to eql(nil) end context "when there is a less specific definition" do before do node_map.set(:thing, :bar, platform_family: "rhel") end it "returns the value when the node matches" do allow(node).to receive(:[]).with(:platform_family).and_return("rhel") allow(node).to receive(:[]).with(:platform_version).and_return("7.0") expect(node_map.get(node, :thing)).to eql(:foo) end end end describe "resource back-compat testing" do before :each do Chef::Config[:treat_deprecation_warnings_as_errors] = false end it "should handle :on_platforms => :all" do node_map.set(:chef_gem, :foo, :on_platforms => :all) allow(node).to receive(:[]).with(:platform).and_return("windows") expect(node_map.get(node, :chef_gem)).to eql(:foo) end it "should handle :on_platforms => [ 'windows' ]" do node_map.set(:dsc_script, :foo, :on_platforms => [ "windows" ]) allow(node).to receive(:[]).with(:platform).and_return("windows") expect(node_map.get(node, :dsc_script)).to eql(:foo) end it "should handle :on_platform => :all" do node_map.set(:link, :foo, :on_platform => :all) allow(node).to receive(:[]).with(:platform).and_return("windows") expect(node_map.get(node, :link)).to eql(:foo) end end end chef-12.14.60/spec/unit/node_spec.rb000066400000000000000000001727571276456504500171100ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "ostruct" describe Chef::Node do let(:node) { Chef::Node.new() } let(:platform_introspector) { node } it_behaves_like "a platform introspector" it "creates a node and assigns it a name" do node = Chef::Node.build("solo-node") expect(node.name).to eq("solo-node") end it "should validate the name of the node" do expect { Chef::Node.build("solo node") }.to raise_error(Chef::Exceptions::ValidationFailed) end it "should be sortable" do n1 = Chef::Node.build("alpha") n2 = Chef::Node.build("beta") n3 = Chef::Node.build("omega") expect([n3, n1, n2].sort).to eq([n1, n2, n3]) end it "should share identity only with others of the same name" do n1 = Chef::Node.build("foo") n2 = Chef::Node.build("foo") n3 = Chef::Node.build("bar") expect(n1).to eq(n2) expect(n1).not_to eq(n3) end describe "when the node does not exist on the server" do before do response = OpenStruct.new(:code => "404") exception = Net::HTTPServerException.new("404 not found", response) allow(Chef::Node).to receive(:load).and_raise(exception) node.name("created-node") end it "creates a new node for find_or_create" do allow(Chef::Node).to receive(:new).and_return(node) expect(node).to receive(:create).and_return(node) node = Chef::Node.find_or_create("created-node") expect(node.name).to eq("created-node") expect(node).to equal(node) end end describe "when the node exists on the server" do before do node.name("existing-node") allow(Chef::Node).to receive(:load).and_return(node) end it "loads the node via the REST API for find_or_create" do expect(Chef::Node.find_or_create("existing-node")).to equal(node) end end describe "run_state" do it "is an empty hash" do expect(node.run_state).to respond_to(:keys) expect(node.run_state).to be_empty end end describe "initialize" do it "should default to the '_default' chef_environment" do n = Chef::Node.new expect(n.chef_environment).to eq("_default") end end describe "name" do it "should allow you to set a name with name(something)" do expect { node.name("latte") }.not_to raise_error end it "should return the name with name()" do node.name("latte") expect(node.name).to eql("latte") end it "should always have a string for name" do expect { node.name(Hash.new) }.to raise_error(ArgumentError) end it "cannot be blank" do expect { node.name("") }.to raise_error(Chef::Exceptions::ValidationFailed) end it "should not accept name doesn't match /^[\-[:alnum:]_:.]+$/" do expect { node.name("space in it") }.to raise_error(Chef::Exceptions::ValidationFailed) end end describe "chef_environment" do it "should set an environment with chef_environment(something)" do expect { node.chef_environment("latte") }.not_to raise_error end it "should return the chef_environment with chef_environment()" do node.chef_environment("latte") expect(node.chef_environment).to eq("latte") end it "should disallow non-strings" do expect { node.chef_environment(Hash.new) }.to raise_error(ArgumentError) expect { node.chef_environment(42) }.to raise_error(ArgumentError) end it "cannot be blank" do expect { node.chef_environment("") }.to raise_error(Chef::Exceptions::ValidationFailed) end end describe "policy_name" do it "defaults to nil" do expect(node.policy_name).to be_nil end it "sets policy_name with a regular setter" do node.policy_name = "example-policy" expect(node.policy_name).to eq("example-policy") end it "allows policy_name with every valid character" do expect { node.policy_name = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqurstuvwxyz0123456789-_:." }.to_not raise_error end it "sets policy_name when given an argument" do node.policy_name("example-policy") expect(node.policy_name).to eq("example-policy") end it "sets policy_name to nil when given nil" do node.policy_name = "example-policy" node.policy_name = nil expect(node.policy_name).to be_nil end it "disallows non-strings" do expect { node.policy_name(Hash.new) }.to raise_error(Chef::Exceptions::ValidationFailed) expect { node.policy_name(42) }.to raise_error(Chef::Exceptions::ValidationFailed) end it "cannot be blank" do expect { node.policy_name("") }.to raise_error(Chef::Exceptions::ValidationFailed) end end describe "policy_group" do it "defaults to nil" do expect(node.policy_group).to be_nil end it "sets policy_group with a regular setter" do node.policy_group = "staging" expect(node.policy_group).to eq("staging") end it "allows policy_group with every valid character" do expect { node.policy_group = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqurstuvwxyz0123456789-_:." }.to_not raise_error end it "sets an environment with chef_environment(something)" do node.policy_group("staging") expect(node.policy_group).to eq("staging") end it "sets policy_group to nil when given nil" do node.policy_group = "staging" node.policy_group = nil expect(node.policy_group).to be_nil end it "disallows non-strings" do expect { node.policy_group(Hash.new) }.to raise_error(Chef::Exceptions::ValidationFailed) expect { node.policy_group(42) }.to raise_error(Chef::Exceptions::ValidationFailed) end it "cannot be blank" do expect { node.policy_group("") }.to raise_error(Chef::Exceptions::ValidationFailed) end end describe "attributes" do it "should have attributes" do expect(node.attribute).to be_a_kind_of(Hash) end it "should allow attributes to be accessed by name or symbol directly on node[]" do node.default["locust"] = "something" expect(node[:locust]).to eql("something") expect(node["locust"]).to eql("something") end it "should return nil if it cannot find an attribute with node[]" do expect(node["secret"]).to eql(nil) end it "does not allow you to set an attribute via node[]=" do expect { node["secret"] = "shush" }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) end it "should allow you to query whether an attribute exists with attribute?" do node.default["locust"] = "something" expect(node.attribute?("locust")).to eql(true) expect(node.attribute?("no dice")).to eql(false) end it "should let you go deep with attribute?" do node.normal["battles"]["people"]["wonkey"] = true expect(node["battles"]["people"].attribute?("wonkey")).to eq(true) expect(node["battles"]["people"].attribute?("snozzberry")).to eq(false) end it "does not allow you to set an attribute via method_missing" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect { node.sunshine = "is bright" }.to raise_error(Chef::Exceptions::ImmutableAttributeModification) end it "should allow you get get an attribute via method_missing" do Chef::Config[:treat_deprecation_warnings_as_errors] = false node.default.sunshine = "is bright" expect(node.sunshine).to eql("is bright") end describe "normal attributes" do it "should allow you to set an attribute with set, without pre-declaring a hash" do node.normal[:snoopy][:is_a_puppy] = true expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "should allow you to set an attribute with set_unless with method_missing but emit a deprecation warning" do Chef::Config[:treat_deprecation_warnings_as_errors] = false node.normal_unless.snoopy.is_a_puppy = false expect(node[:snoopy][:is_a_puppy]).to eq(false) end it "should allow you to set an attribute with set_unless" do node.normal_unless[:snoopy][:is_a_puppy] = false expect(node[:snoopy][:is_a_puppy]).to eq(false) end it "should not allow you to set an attribute with set_unless if it already exists" do node.normal[:snoopy][:is_a_puppy] = true node.normal_unless[:snoopy][:is_a_puppy] = false expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "should allow you to set an attribute with set_unless if is a nil value" do node.attributes.normal = { snoopy: { is_a_puppy: nil } } node.normal_unless[:snoopy][:is_a_puppy] = false expect(node[:snoopy][:is_a_puppy]).to eq(false) end it "should allow you to set a value after a set_unless" do # this tests for set_unless_present state bleeding between statements CHEF-3806 node.normal_unless[:snoopy][:is_a_puppy] = false node.normal[:snoopy][:is_a_puppy] = true expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "should let you set a value after a 'dangling' set_unless" do # this tests for set_unless_present state bleeding between statements CHEF-3806 node.normal[:snoopy][:is_a_puppy] = "what" node.normal_unless[:snoopy][:is_a_puppy] node.normal[:snoopy][:is_a_puppy] = true expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "auto-vivifies attributes created via method syntax" do Chef::Config[:treat_deprecation_warnings_as_errors] = false node.normal.fuu.bahrr.baz = "qux" expect(node.fuu.bahrr.baz).to eq("qux") end it "should let you use tag as a convience method for the tags attribute" do node.normal["tags"] = %w{one two} node.tag("three", "four") expect(node["tags"]).to eq(%w{one two three four}) end it "set is a deprecated alias for normal" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(Chef).to receive(:log_deprecation).with(/set is deprecated/) node.set[:snoopy][:is_a_puppy] = true expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "set_unless is a deprecated alias for normal_unless" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(Chef).to receive(:log_deprecation).with(/set_unless is deprecated/) node.set_unless[:snoopy][:is_a_puppy] = false expect(node[:snoopy][:is_a_puppy]).to eq(false) end end describe "default attributes" do it "should be set with default, without pre-declaring a hash" do node.default[:snoopy][:is_a_puppy] = true expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "should allow you to set with default_unless without pre-declaring a hash" do node.default_unless[:snoopy][:is_a_puppy] = false expect(node[:snoopy][:is_a_puppy]).to eq(false) end it "should not allow you to set an attribute with default_unless if it already exists" do node.default[:snoopy][:is_a_puppy] = true node.default_unless[:snoopy][:is_a_puppy] = false expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "should allow you to set a value after a default_unless" do # this tests for set_unless_present state bleeding between statements CHEF-3806 node.default_unless[:snoopy][:is_a_puppy] = false node.default[:snoopy][:is_a_puppy] = true expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "should allow you to set a value after a 'dangling' default_unless" do # this tests for set_unless_present state bleeding between statements CHEF-3806 node.default[:snoopy][:is_a_puppy] = "what" node.default_unless[:snoopy][:is_a_puppy] node.default[:snoopy][:is_a_puppy] = true expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "does not exhibit chef/chef/issues/5005 bug" do node.env_default["a"]["r1"]["g"]["u"] = "u1" node.default_unless["a"]["r1"]["g"]["r"] = "r" expect(node["a"]["r1"]["g"]["u"]).to eql("u1") end it "auto-vivifies attributes created via method syntax" do Chef::Config[:treat_deprecation_warnings_as_errors] = false node.default.fuu.bahrr.baz = "qux" expect(node.fuu.bahrr.baz).to eq("qux") end it "default_unless correctly resets the deep merge cache" do node.normal["tags"] = [] # this sets our top-level breadcrumb node.default_unless["foo"]["bar"] = "NK-19V" expect(node["foo"]["bar"]).to eql("NK-19V") node.default_unless["foo"]["baz"] = "NK-33" expect(node["foo"]["baz"]).to eql("NK-33") end it "normal_unless correctly resets the deep merge cache" do node.normal["tags"] = [] # this sets our top-level breadcrumb node.normal_unless["foo"]["bar"] = "NK-19V" expect(node["foo"]["bar"]).to eql("NK-19V") node.normal_unless["foo"]["baz"] = "NK-33" expect(node["foo"]["baz"]).to eql("NK-33") end it "override_unless correctly resets the deep merge cache" do node.normal["tags"] = [] # this sets our top-level breadcrumb node.override_unless["foo"]["bar"] = "NK-19V" expect(node["foo"]["bar"]).to eql("NK-19V") node.override_unless["foo"]["baz"] = "NK-33" expect(node["foo"]["baz"]).to eql("NK-33") end end describe "override attributes" do it "should be set with override, without pre-declaring a hash" do node.override[:snoopy][:is_a_puppy] = true expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "should allow you to set with override_unless without pre-declaring a hash" do node.override_unless[:snoopy][:is_a_puppy] = false expect(node[:snoopy][:is_a_puppy]).to eq(false) end it "should not allow you to set an attribute with override_unless if it already exists" do node.override[:snoopy][:is_a_puppy] = true node.override_unless[:snoopy][:is_a_puppy] = false expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "should allow you to set a value after an override_unless" do # this tests for set_unless_present state bleeding between statements CHEF-3806 node.override_unless[:snoopy][:is_a_puppy] = false node.override[:snoopy][:is_a_puppy] = true expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "should allow you to set a value after a 'dangling' override_unless" do # this tests for set_unless_present state bleeding between statements CHEF-3806 node.override_unless[:snoopy][:is_a_puppy] = "what" node.override_unless[:snoopy][:is_a_puppy] node.override[:snoopy][:is_a_puppy] = true expect(node[:snoopy][:is_a_puppy]).to eq(true) end it "auto-vivifies attributes created via method syntax" do Chef::Config[:treat_deprecation_warnings_as_errors] = false node.override.fuu.bahrr.baz = "qux" expect(node.fuu.bahrr.baz).to eq("qux") end end describe "globally deleting attributes" do context "with hash values" do before do node.role_default["mysql"]["server"]["port"] = 1234 node.normal["mysql"]["server"]["port"] = 2345 node.override["mysql"]["server"]["port"] = 3456 end it "deletes all the values and returns the value with the highest precidence" do expect( node.rm("mysql", "server", "port") ).to eql(3456) expect( node["mysql"]["server"]["port"] ).to be_nil expect( node["mysql"]["server"] ).to eql({}) end it "deletes nested things correctly" do node.default["mysql"]["client"]["client_setting"] = "foo" expect( node.rm("mysql", "server") ).to eql( { "port" => 3456 } ) expect( node["mysql"] ).to eql( { "client" => { "client_setting" => "foo" } } ) end it "returns nil if the node attribute does not exist" do expect( node.rm("no", "such", "thing") ).to be_nil end it "can delete the entire tree" do expect( node.rm("mysql") ).to eql({ "server" => { "port" => 3456 } }) end end context "when trying to delete through a thing that isn't an array-like or hash-like object" do before do node.default["mysql"] = true end it "returns nil when you're two levels deeper" do expect( node.rm("mysql", "server", "port") ).to eql(nil) end it "returns nil when you're one level deeper" do expect( node.rm("mysql", "server") ).to eql(nil) end it "correctly deletes at the top level" do expect( node.rm("mysql") ).to eql(true) end end context "with array indexes" do before do node.role_default["mysql"]["server"][0]["port"] = 1234 node.normal["mysql"]["server"][0]["port"] = 2345 node.override["mysql"]["server"][0]["port"] = 3456 node.override["mysql"]["server"][1]["port"] = 3456 end it "deletes the array element" do expect( node.rm("mysql", "server", 0, "port") ).to eql(3456) expect( node["mysql"]["server"][0]["port"] ).to be_nil expect( node["mysql"]["server"][1]["port"] ).to eql(3456) end end context "with real arrays" do before do node.role_default["mysql"]["server"] = [ { "port" => 1234, } ] node.normal["mysql"]["server"] = [ { "port" => 2345, } ] node.override["mysql"]["server"] = [ { "port" => 3456, } ] end it "deletes the array element" do expect( node.rm("mysql", "server", 0, "port") ).to eql(3456) expect( node["mysql"]["server"][0]["port"] ).to be_nil end it "when mistaking arrays for hashes, it considers the value removed and does nothing" do node.rm("mysql", "server", "port") expect(node["mysql"]["server"][0]["port"]).to eql(3456) end end end describe "granular deleting attributes" do context "when only defaults exist" do before do node.role_default["mysql"]["server"]["port"] = 1234 node.default["mysql"]["server"]["port"] = 2345 node.force_default["mysql"]["server"]["port"] = 3456 end it "returns the deleted values" do expect( node.rm_default("mysql", "server", "port") ).to eql(3456) end it "returns nil for the combined attribues" do expect( node.rm_default("mysql", "server", "port") ).to eql(3456) expect( node["mysql"]["server"]["port"] ).to eql(nil) end it "returns an empty hash for the default attrs" do expect( node.rm_default("mysql", "server", "port") ).to eql(3456) # this auto-vivifies, should it? expect( node.default_attrs["mysql"]["server"]["port"] ).to eql({}) end it "returns an empty hash after the last key is deleted" do expect( node.rm_default("mysql", "server", "port") ).to eql(3456) expect( node["mysql"]["server"] ).to eql({}) end end context "when trying to delete through a thing that isn't an array-like or hash-like object" do before do node.default["mysql"] = true end it "returns nil when you're two levels deeper" do expect( node.rm_default("mysql", "server", "port") ).to eql(nil) end it "returns nil when you're one level deeper" do expect( node.rm_default("mysql", "server") ).to eql(nil) end it "correctly deletes at the top level" do expect( node.rm_default("mysql") ).to eql(true) end end context "when a higher precedence exists" do before do node.role_default["mysql"]["server"]["port"] = 1234 node.default["mysql"]["server"]["port"] = 2345 node.force_default["mysql"]["server"]["port"] = 3456 node.override["mysql"]["server"]["port"] = 9999 end it "returns the deleted values" do expect( node.rm_default("mysql", "server", "port") ).to eql(3456) end it "returns the higher precedence values after the delete" do expect( node.rm_default("mysql", "server", "port") ).to eql(3456) expect( node["mysql"]["server"]["port"] ).to eql(9999) end it "returns an empty has for the default attrs" do expect( node.rm_default("mysql", "server", "port") ).to eql(3456) # this auto-vivifies, should it? expect( node.default_attrs["mysql"]["server"]["port"] ).to eql({}) end end context "when a lower precedence exists" do before do node.default["mysql"]["server"]["port"] = 2345 node.override["mysql"]["server"]["port"] = 9999 node.role_override["mysql"]["server"]["port"] = 9876 node.force_override["mysql"]["server"]["port"] = 6669 end it "returns the deleted values" do expect( node.rm_override("mysql", "server", "port") ).to eql(6669) end it "returns the lower precedence levels after the delete" do expect( node.rm_override("mysql", "server", "port") ).to eql(6669) expect( node["mysql"]["server"]["port"] ).to eql(2345) end it "returns an empty has for the override attrs" do expect( node.rm_override("mysql", "server", "port") ).to eql(6669) # this auto-vivifies, should it? expect( node.override_attrs["mysql"]["server"]["port"] ).to eql({}) end end it "rm_default returns nil on deleting non-existent values" do expect( node.rm_default("no", "such", "thing") ).to be_nil end it "rm_normal returns nil on deleting non-existent values" do expect( node.rm_normal("no", "such", "thing") ).to be_nil end it "rm_override returns nil on deleting non-existent values" do expect( node.rm_override("no", "such", "thing") ).to be_nil end end describe "granular replacing attributes" do it "removes everything at the level of the last key" do node.default["mysql"]["server"]["port"] = 2345 node.default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql" } expect( node["mysql"]["server"] ).to eql({ "data_dir" => "/my_raid_volume/lib/mysql" }) end it "replaces a value at the cookbook sub-level of the atributes only" do node.default["mysql"]["server"]["port"] = 2345 node.default["mysql"]["server"]["service_name"] = "fancypants-sql" node.role_default["mysql"]["server"]["port"] = 1234 node.force_default["mysql"]["server"]["port"] = 3456 node.default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql" } expect( node["mysql"]["server"]["port"] ).to eql(3456) expect( node["mysql"]["server"]["service_name"] ).to be_nil expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql") expect( node["mysql"]["server"] ).to eql({ "port" => 3456, "data_dir" => "/my_raid_volume/lib/mysql" }) end it "higher precedence values aren't removed" do node.role_default["mysql"]["server"]["port"] = 1234 node.default["mysql"]["server"]["port"] = 2345 node.force_default["mysql"]["server"]["port"] = 3456 node.override["mysql"]["server"]["service_name"] = "fancypants-sql" node.default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql" } expect( node["mysql"]["server"]["port"] ).to eql(3456) expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql") expect( node["mysql"]["server"] ).to eql({ "service_name" => "fancypants-sql", "port" => 3456, "data_dir" => "/my_raid_volume/lib/mysql" }) end end describe "granular force replacing attributes" do it "removes everything at the level of the last key" do node.force_default["mysql"]["server"]["port"] = 2345 node.force_default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql", } expect( node["mysql"]["server"] ).to eql({ "data_dir" => "/my_raid_volume/lib/mysql", }) end it "removes all values from the precedence level when setting" do node.role_default["mysql"]["server"]["port"] = 1234 node.default["mysql"]["server"]["port"] = 2345 node.force_default["mysql"]["server"]["port"] = 3456 node.force_default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql", } expect( node["mysql"]["server"]["port"] ).to be_nil expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql") expect( node["mysql"]["server"] ).to eql({ "data_dir" => "/my_raid_volume/lib/mysql", }) end it "higher precedence levels are not removed" do node.role_default["mysql"]["server"]["port"] = 1234 node.default["mysql"]["server"]["port"] = 2345 node.force_default["mysql"]["server"]["port"] = 3456 node.override["mysql"]["server"]["service_name"] = "fancypants-sql" node.force_default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql", } expect( node["mysql"]["server"]["port"] ).to be_nil expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql") expect( node["mysql"]["server"] ).to eql({ "service_name" => "fancypants-sql", "data_dir" => "/my_raid_volume/lib/mysql", }) end it "will autovivify" do node.force_default!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql", } expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql") end it "lower precedence levels aren't removed" do node.role_override["mysql"]["server"]["port"] = 1234 node.override["mysql"]["server"]["port"] = 2345 node.force_override["mysql"]["server"]["port"] = 3456 node.default["mysql"]["server"]["service_name"] = "fancypants-sql" node.force_override!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql", } expect( node["mysql"]["server"]["port"] ).to be_nil expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql") expect( node["mysql"]["server"] ).to eql({ "service_name" => "fancypants-sql", "data_dir" => "/my_raid_volume/lib/mysql", }) end it "when overwriting a non-hash/array" do node.override["mysql"] = false node.force_override["mysql"] = true node.force_override!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql", } expect( node["mysql"]["server"]["data_dir"] ).to eql("/my_raid_volume/lib/mysql") end it "when overwriting an array with a hash" do node.force_override["mysql"][0] = true node.force_override!["mysql"]["server"] = { "data_dir" => "/my_raid_volume/lib/mysql", } expect( node["mysql"]["server"] ).to eql({ "data_dir" => "/my_raid_volume/lib/mysql", }) end end # In Chef-12.0 there is a deep_merge cache on the top level attribute which had a bug # where it cached node[:foo] separate from node['foo']. These tests exercise those edge conditions. # # https://github.com/opscode/chef/issues/2700 # https://github.com/opscode/chef/issues/2712 # https://github.com/opscode/chef/issues/2745 # describe "deep merge attribute cache edge conditions" do it "does not error with complicated attribute substitution" do Chef::Config[:treat_deprecation_warnings_as_errors] = false node.default["chef_attribute_hell"]["attr1"] = "attribute1" node.default["chef_attribute_hell"]["attr2"] = "#{node.chef_attribute_hell.attr1}/attr2" expect { node.default["chef_attribute_hell"]["attr3"] = "#{node.chef_attribute_hell.attr2}/attr3" }.not_to raise_error end it "caches both strings and symbols correctly" do node.force_default[:solr][:version] = "4.10.2" node.force_default[:solr][:data_dir] = "/opt/solr-#{node['solr'][:version]}/example/solr" node.force_default[:solr][:xms] = "512M" expect(node[:solr][:xms]).to eql("512M") expect(node["solr"][:xms]).to eql("512M") end it "method interpolation syntax also works" do Chef::Config[:treat_deprecation_warnings_as_errors] = false node.default["passenger"]["version"] = "4.0.57" node.default["passenger"]["root_path"] = "passenger-#{node['passenger']['version']}" node.default["passenger"]["root_path_2"] = "passenger-#{node.passenger['version']}" expect(node["passenger"]["root_path_2"]).to eql("passenger-4.0.57") expect(node[:passenger]["root_path_2"]).to eql("passenger-4.0.57") end end it "should raise an ArgumentError if you ask for an attribute that doesn't exist via method_missing" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect { node.sunshine }.to raise_error(NoMethodError) end it "should allow you to iterate over attributes with each_attribute" do node.default.sunshine = "is bright" node.default.canada = "is a nice place" seen_attributes = Hash.new node.each_attribute do |a, v| seen_attributes[a] = v end expect(seen_attributes).to have_key("sunshine") expect(seen_attributes).to have_key("canada") expect(seen_attributes["sunshine"]).to eq("is bright") expect(seen_attributes["canada"]).to eq("is a nice place") end describe "functional attribute API" do # deeper functional testing of this API is in the VividMash spec tests it "should have an exist? function" do node.default["foo"]["bar"] = "baz" expect(node.exist?("foo", "bar")).to be true expect(node.exist?("bar", "foo")).to be false end it "should have a read function" do node.override["foo"]["bar"] = "baz" expect(node.read("foo", "bar")).to eql("baz") expect(node.read("bar", "foo")).to eql(nil) end it "should have a read! function" do node.override["foo"]["bar"] = "baz" expect(node.read!("foo", "bar")).to eql("baz") expect { node.read!("bar", "foo") }.to raise_error(Chef::Exceptions::NoSuchAttribute) end it "delegates write(:level) to node.level.write()" do node.write(:default, "foo", "bar", "baz") expect(node.default["foo"]["bar"]).to eql("baz") end it "delegates write!(:level) to node.level.write!()" do node.write!(:default, "foo", "bar", "baz") expect(node.default["foo"]["bar"]).to eql("baz") node.default["bar"] = true expect { node.write!(:default, "bar", "foo", "baz") }.to raise_error(Chef::Exceptions::AttributeTypeMismatch) end it "delegates unlink(:level) to node.level.unlink()" do node.default["foo"]["bar"] = "baz" expect(node.unlink(:default, "foo", "bar")).to eql("baz") expect(node.unlink(:default, "bar", "foo")).to eql(nil) end it "delegates unlink!(:level) to node.level.unlink!()" do node.default["foo"]["bar"] = "baz" expect(node.unlink!(:default, "foo", "bar")).to eql("baz") expect { node.unlink!(:default, "bar", "foo") }.to raise_error(Chef::Exceptions::NoSuchAttribute) end end end describe "consuming json" do before do @ohai_data = { :platform => "foo", :platform_version => "bar" } end it "consumes the run list portion of a collection of attributes and returns the remainder" do attrs = { "run_list" => [ "role[base]", "recipe[chef::server]" ], "foo" => "bar" } expect(node.consume_run_list(attrs)).to eq({ "foo" => "bar" }) expect(node.run_list).to eq([ "role[base]", "recipe[chef::server]" ]) end it "sets the node chef_environment" do attrs = { "chef_environment" => "foo_environment", "bar" => "baz" } expect(node.consume_chef_environment(attrs)).to eq({ "bar" => "baz" }) expect(node.chef_environment).to eq("foo_environment") expect(node["chef_environment"]).to be nil end it "should overwrites the run list with the run list it consumes" do node.consume_run_list "recipes" => %w{one two} node.consume_run_list "recipes" => [ "three" ] expect(node.run_list).to eq([ "three" ]) end it "should not add duplicate recipes from the json attributes" do node.run_list << "one" node.consume_run_list "recipes" => %w{one two three} expect(node.run_list).to eq(%w{one two three}) end it "doesn't change the run list if no run_list is specified in the json" do node.run_list << "role[database]" node.consume_run_list "foo" => "bar" expect(node.run_list).to eq(["role[database]"]) end it "raises an exception if you provide both recipe and run_list attributes, since this is ambiguous" do expect { node.consume_run_list "recipes" => "stuff", "run_list" => "other_stuff" }.to raise_error(Chef::Exceptions::AmbiguousRunlistSpecification) end it "should add json attributes to the node" do node.consume_external_attrs(@ohai_data, { "one" => "two", "three" => "four" }) expect(node["one"]).to eql("two") expect(node["three"]).to eql("four") end it "should set the tags attribute to an empty array if it is not already defined" do node.consume_external_attrs(@ohai_data, {}) expect(node.tags).to eql([]) end it "should not set the tags attribute to an empty array if it is already defined" do node.tag("radiohead") node.consume_external_attrs(@ohai_data, {}) expect(node.tags).to eql([ "radiohead" ]) end it "should set the tags attribute to an empty array if it is nil" do node.attributes.normal = { "tags" => nil } node.consume_external_attrs(@ohai_data, {}) expect(node.tags).to eql([]) end it "should return an array if it is fed a string" do node.normal[:tags] = "string" node.consume_external_attrs(@ohai_data, {}) expect(node.tags).to eql(["string"]) end it "should return an array if it is fed a hash" do node.normal[:tags] = {} node.consume_external_attrs(@ohai_data, {}) expect(node.tags).to eql([]) end it "deep merges attributes instead of overwriting them" do node.consume_external_attrs(@ohai_data, "one" => { "two" => { "three" => "four" } }) expect(node["one"].to_hash).to eq({ "two" => { "three" => "four" } }) node.consume_external_attrs(@ohai_data, "one" => { "abc" => "123" }) node.consume_external_attrs(@ohai_data, "one" => { "two" => { "foo" => "bar" } }) expect(node["one"].to_hash).to eq({ "two" => { "three" => "four", "foo" => "bar" }, "abc" => "123" }) end it "gives attributes from JSON priority when deep merging" do node.consume_external_attrs(@ohai_data, "one" => { "two" => { "three" => "four" } }) expect(node["one"].to_hash).to eq({ "two" => { "three" => "four" } }) node.consume_external_attrs(@ohai_data, "one" => { "two" => { "three" => "forty-two" } }) expect(node["one"].to_hash).to eq({ "two" => { "three" => "forty-two" } }) end end describe "preparing for a chef client run" do before do @ohai_data = { :platform => "foobuntu", :platform_version => "23.42" } end it "sets its platform according to platform detection" do node.consume_external_attrs(@ohai_data, {}) expect(node.automatic_attrs[:platform]).to eq("foobuntu") expect(node.automatic_attrs[:platform_version]).to eq("23.42") end it "consumes the run list from provided json attributes" do node.consume_external_attrs(@ohai_data, { "run_list" => ["recipe[unicorn]"] }) expect(node.run_list).to eq(["recipe[unicorn]"]) end it "saves non-runlist json attrs for later" do expansion = Chef::RunList::RunListExpansion.new("_default", []) allow(node.run_list).to receive(:expand).and_return(expansion) node.consume_external_attrs(@ohai_data, { "foo" => "bar" }) node.expand! expect(node.normal_attrs).to eq({ "foo" => "bar", "tags" => [] }) end end describe "when expanding its run list and merging attributes" do before do @environment = Chef::Environment.new.tap do |e| e.name("rspec_env") e.default_attributes("env default key" => "env default value") e.override_attributes("env override key" => "env override value") end expect(Chef::Environment).to receive(:load).with("rspec_env").and_return(@environment) @expansion = Chef::RunList::RunListExpansion.new("rspec_env", []) node.chef_environment("rspec_env") allow(node.run_list).to receive(:expand).and_return(@expansion) end it "sets the 'recipes' automatic attribute to the recipes in the expanded run_list" do @expansion.recipes << "recipe[chef::client]" << "recipe[nginx::default]" node.expand! expect(node.automatic_attrs[:recipes]).to eq(["recipe[chef::client]", "recipe[nginx::default]"]) end it "sets the 'roles' automatic attribute to the expanded role list" do @expansion.instance_variable_set(:@applied_roles, { "arf" => nil, "countersnark" => nil }) node.expand! expect(node.automatic_attrs[:roles].sort).to eq(%w{arf countersnark}) end it "applies default attributes from the environment as environment defaults" do node.expand! expect(node.attributes.env_default["env default key"]).to eq("env default value") end it "applies override attributes from the environment as env overrides" do node.expand! expect(node.attributes.env_override["env override key"]).to eq("env override value") end it "applies default attributes from roles as role defaults" do @expansion.default_attrs["role default key"] = "role default value" node.expand! expect(node.attributes.role_default["role default key"]).to eq("role default value") end it "applies override attributes from roles as role overrides" do @expansion.override_attrs["role override key"] = "role override value" node.expand! expect(node.attributes.role_override["role override key"]).to eq("role override value") end end describe "loaded_recipe" do it "should not add a recipe that is already in the recipes list" do node.automatic_attrs[:recipes] = [ "nginx::module" ] node.loaded_recipe(:nginx, "module") expect(node.automatic_attrs[:recipes].length).to eq(1) end it "should add a recipe that is not already in the recipes list" do node.automatic_attrs[:recipes] = [ "nginx::other_module" ] node.loaded_recipe(:nginx, "module") expect(node.automatic_attrs[:recipes].length).to eq(2) expect(node.recipe?("nginx::module")).to be true expect(node.recipe?("nginx::other_module")).to be true end end describe "when querying for recipes in the run list" do context "when a recipe is in the top level run list" do before do node.run_list << "recipe[nginx::module]" end it "finds the recipe" do expect(node.recipe?("nginx::module")).to be true end it "does not find a recipe not in the run list" do expect(node.recipe?("nginx::other_module")).to be false end end context "when a recipe is in the expanded run list only" do before do node.run_list << "role[base]" node.automatic_attrs[:recipes] = [ "nginx::module" ] end it "finds a recipe in the expanded run list" do expect(node.recipe?("nginx::module")).to be true end it "does not find a recipe that's not in the run list" do expect(node.recipe?("nginx::other_module")).to be false end end end describe "when clearing computed state at the beginning of a run" do before do node.default[:foo] = "default" node.normal[:foo] = "normal" node.override[:foo] = "override" node.reset_defaults_and_overrides end it "removes default attributes" do expect(node.default).to be_empty end it "removes override attributes" do expect(node.override).to be_empty end it "leaves normal level attributes untouched" do expect(node[:foo]).to eq("normal") end end describe "when merging environment attributes" do before do node.chef_environment = "rspec" @expansion = Chef::RunList::RunListExpansion.new("rspec", []) @expansion.default_attrs.replace({ :default => "from role", :d_role => "role only" }) @expansion.override_attrs.replace({ :override => "from role", :o_role => "role only" }) @environment = Chef::Environment.new @environment.default_attributes = { :default => "from env", :d_env => "env only" } @environment.override_attributes = { :override => "from env", :o_env => "env only" } allow(Chef::Environment).to receive(:load).and_return(@environment) node.apply_expansion_attributes(@expansion) end it "does not nuke role-only default attrs" do expect(node[:d_role]).to eq("role only") end it "does not nuke role-only override attrs" do expect(node[:o_role]).to eq("role only") end it "does not nuke env-only default attrs" do expect(node[:o_env]).to eq("env only") end it "does not nuke role-only override attrs" do expect(node[:o_env]).to eq("env only") end it "gives role defaults precedence over env defaults" do expect(node[:default]).to eq("from role") end it "gives env overrides precedence over role overrides" do expect(node[:override]).to eq("from env") end end describe "when evaluating attributes files" do before do @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) @cookbook_loader = Chef::CookbookLoader.new(@cookbook_repo) @cookbook_loader.load_cookbooks @cookbook_collection = Chef::CookbookCollection.new(@cookbook_loader.cookbooks_by_name) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(node, @cookbook_collection, @events) node.include_attribute("openldap::default") node.include_attribute("openldap::smokey") end it "sets attributes from the files" do expect(node["ldap_server"]).to eql("ops1prod") expect(node["ldap_basedn"]).to eql("dc=hjksolutions,dc=com") expect(node["ldap_replication_password"]).to eql("forsure") expect(node["smokey"]).to eql("robinson") end it "gives a sensible error when attempting to load a missing attributes file" do expect { node.include_attribute("nope-this::doesnt-exist") }.to raise_error(Chef::Exceptions::CookbookNotFound) end end describe "roles" do it "should allow you to query whether or not it has a recipe applied with role?" do node.run_list << "role[sunrise]" expect(node.role?("sunrise")).to eql(true) expect(node.role?("not at home")).to eql(false) end it "should allow you to set roles with arguments" do node.run_list << "role[one]" node.run_list << "role[two]" expect(node.role?("one")).to eql(true) expect(node.role?("two")).to eql(true) end end describe "run_list" do it "should have a Chef::RunList of recipes and roles that should be applied" do expect(node.run_list).to be_a_kind_of(Chef::RunList) end it "should allow you to query the run list with arguments" do node.run_list "recipe[baz]" expect(node.run_list?("recipe[baz]")).to eql(true) end it "should allow you to set the run list with arguments" do node.run_list "recipe[baz]", "role[foo]" expect(node.run_list?("recipe[baz]")).to eql(true) expect(node.run_list?("role[foo]")).to eql(true) end end describe "from file" do it "should load a node from a ruby file" do node.from_file(File.expand_path(File.join(CHEF_SPEC_DATA, "nodes", "test.rb"))) expect(node.name).to eql("test.example.com-short") expect(node["sunshine"]).to eql("in") expect(node["something"]).to eql("else") expect(node.run_list).to eq(["operations-master", "operations-monitoring"]) end it "should raise an exception if the file cannot be found or read" do expect { node.from_file("/tmp/monkeydiving") }.to raise_error(IOError) end end describe "update_from!" do before(:each) do node.name("orig") node.chef_environment("dev") node.default_attrs = { "one" => { "two" => "three", "four" => "five", "eight" => "nine" } } node.override_attrs = { "one" => { "two" => "three", "four" => "six" } } node.normal_attrs = { "one" => { "two" => "seven" } } node.run_list << "role[marxist]" node.run_list << "role[leninist]" node.run_list << "recipe[stalinist]" @example = Chef::Node.new() @example.name("newname") @example.chef_environment("prod") @example.default_attrs = { "alpha" => { "bravo" => "charlie", "delta" => "echo" } } @example.override_attrs = { "alpha" => { "bravo" => "foxtrot", "delta" => "golf" } } @example.normal_attrs = { "alpha" => { "bravo" => "hotel" } } @example.run_list << "role[comedy]" @example.run_list << "role[drama]" @example.run_list << "recipe[mystery]" end it "allows update of everything except name" do node.update_from!(@example) expect(node.name).to eq("orig") expect(node.chef_environment).to eq(@example.chef_environment) expect(node.default_attrs).to eq(@example.default_attrs) expect(node.override_attrs).to eq(@example.override_attrs) expect(node.normal_attrs).to eq(@example.normal_attrs) expect(node.run_list).to eq(@example.run_list) end it "should not update the name of the node" do expect(node).not_to receive(:name).with(@example.name) node.update_from!(@example) end end describe "to_hash" do it "should serialize itself as a hash" do node.chef_environment("dev") node.default_attrs = { "one" => { "two" => "three", "four" => "five", "eight" => "nine" } } node.override_attrs = { "one" => { "two" => "three", "four" => "six" } } node.normal_attrs = { "one" => { "two" => "seven" } } node.run_list << "role[marxist]" node.run_list << "role[leninist]" node.run_list << "recipe[stalinist]" h = node.to_hash expect(h["one"]["two"]).to eq("three") expect(h["one"]["four"]).to eq("six") expect(h["one"]["eight"]).to eq("nine") expect(h["role"]).to be_include("marxist") expect(h["role"]).to be_include("leninist") expect(h["run_list"]).to be_include("role[marxist]") expect(h["run_list"]).to be_include("role[leninist]") expect(h["run_list"]).to be_include("recipe[stalinist]") expect(h["chef_environment"]).to eq("dev") end it "should return an empty array for empty run_list" do expect(node.to_hash["run_list"]).to eq([]) end end describe "converting to or from json" do it "should serialize itself as json", :json => true do node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA)) json = Chef::JSONCompat.to_json(node) expect(json).to match(/json_class/) expect(json).to match(/name/) expect(json).to match(/chef_environment/) expect(json).to match(/normal/) expect(json).to match(/default/) expect(json).to match(/override/) expect(json).to match(/run_list/) end it "should serialize valid json with a run list", :json => true do #This test came about because activesupport mucks with Chef json serialization #Test should pass with and without Activesupport node.run_list << { "type" => "role", "name" => "Cthulu" } node.run_list << { "type" => "role", "name" => "Hastur" } json = Chef::JSONCompat.to_json(node) expect(json).to match(/\"run_list\":\[\"role\[Cthulu\]\",\"role\[Hastur\]\"\]/) end it "should serialize the correct run list", :json => true do node.run_list << "role[marxist]" node.run_list << "role[leninist]" node.override_runlist << "role[stalinist]" expect(node.run_list).to be_include("role[stalinist]") json = Chef::JSONCompat.to_json(node) expect(json).to match(/\"run_list\":\[\"role\[marxist\]\",\"role\[leninist\]\"\]/) end it "merges the override components into a combined override object" do node.attributes.role_override["role override"] = "role override" node.attributes.env_override["env override"] = "env override" node_for_json = node.for_json expect(node_for_json["override"]["role override"]).to eq("role override") expect(node_for_json["override"]["env override"]).to eq("env override") end it "merges the default components into a combined default object" do node.attributes.role_default["role default"] = "role default" node.attributes.env_default["env default"] = "env default" node_for_json = node.for_json expect(node_for_json["default"]["role default"]).to eq("role default") expect(node_for_json["default"]["env default"]).to eq("env default") end it "should deserialize itself from json", :json => true do node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA)) json = Chef::JSONCompat.to_json(node) serialized_node = Chef::Node.from_hash(Chef::JSONCompat.parse(json)) expect(serialized_node).to be_a_kind_of(Chef::Node) expect(serialized_node.name).to eql(node.name) expect(serialized_node.chef_environment).to eql(node.chef_environment) node.each_attribute do |k, v| expect(serialized_node[k]).to eql(v) end expect(serialized_node.run_list).to eq(node.run_list) end context "when policyfile attributes are not present" do it "does not have a policy_name key in the json" do expect(node.for_json.keys).to_not include("policy_name") end it "does not have a policy_group key in the json" do expect(node.for_json.keys).to_not include("policy_name") end end context "when policyfile attributes are present" do before do node.policy_name = "my-application" node.policy_group = "staging" end it "includes policy_name key in the json" do expect(node.for_json).to have_key("policy_name") expect(node.for_json["policy_name"]).to eq("my-application") end it "includes a policy_group key in the json" do expect(node.for_json).to have_key("policy_group") expect(node.for_json["policy_group"]).to eq("staging") end it "parses policyfile attributes from JSON" do round_tripped_node = Chef::Node.from_hash(node.for_json) expect(round_tripped_node.policy_name).to eq("my-application") expect(round_tripped_node.policy_group).to eq("staging") end end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) do node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA)) node end end end describe "to_s" do it "should turn into a string like node[name]" do node.name("airplane") expect(node.to_s).to eql("node[airplane]") end end describe "api model" do before(:each) do @rest = double("Chef::ServerAPI") allow(Chef::ServerAPI).to receive(:new).and_return(@rest) @query = double("Chef::Search::Query") allow(Chef::Search::Query).to receive(:new).and_return(@query) end describe "list" do describe "inflated" do it "should return a hash of node names and objects" do n1 = double("Chef::Node", :name => "one") allow(n1).to receive(:kind_of?).with(Chef::Node) { true } expect(@query).to receive(:search).with(:node).and_yield(n1) r = Chef::Node.list(true) expect(r["one"]).to eq(n1) end end it "should return a hash of node names and urls" do expect(@rest).to receive(:get).and_return({ "one" => "http://foo" }) r = Chef::Node.list expect(r["one"]).to eq("http://foo") end end describe "load" do it "should load a node by name" do node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA)) json = Chef::JSONCompat.to_json(node) parsed = Chef::JSONCompat.parse(json) expect(@rest).to receive(:get).with("nodes/test.example.com").and_return(parsed) serialized_node = Chef::Node.load("test.example.com") expect(serialized_node).to be_a_kind_of(Chef::Node) expect(serialized_node.name).to eql(node.name) end end describe "destroy" do it "should destroy a node" do expect(@rest).to receive(:delete).with("nodes/monkey").and_return("foo") node.name("monkey") node.destroy end end describe "save" do it "should update a node if it already exists" do node.name("monkey") allow(node).to receive(:data_for_save).and_return({}) expect(@rest).to receive(:put).with("nodes/monkey", {}).and_return("foo") node.save end it "should not try and create if it can update" do node.name("monkey") allow(node).to receive(:data_for_save).and_return({}) expect(@rest).to receive(:put).with("nodes/monkey", {}).and_return("foo") expect(@rest).not_to receive(:post) node.save end it "should create if it cannot update" do node.name("monkey") allow(node).to receive(:data_for_save).and_return({}) exception = double("404 error", :code => "404") expect(@rest).to receive(:put).and_raise(Net::HTTPServerException.new("foo", exception)) expect(@rest).to receive(:post).with("nodes", {}) node.save end describe "when whyrun mode is enabled" do before do Chef::Config[:why_run] = true end after do Chef::Config[:why_run] = false end it "should not save" do node.name("monkey") expect(@rest).not_to receive(:put) expect(@rest).not_to receive(:post) node.save end end context "with whitelisted attributes configured" do it "should only save whitelisted attributes (and subattributes)" do Chef::Config[:automatic_attribute_whitelist] = [ ["filesystem", "/dev/disk0s2"], "network/interfaces/eth0", ] data = { "automatic" => { "filesystem" => { "/dev/disk0s2" => { "size" => "10mb" }, "map - autohome" => { "size" => "10mb" }, }, "network" => { "interfaces" => { "eth0" => {}, "eth1" => {}, }, }, }, "default" => {}, "normal" => {}, "override" => {} } selected_data = { "automatic" => { "filesystem" => { "/dev/disk0s2" => { "size" => "10mb" }, }, "network" => { "interfaces" => { "eth0" => {}, }, }, }, "default" => {}, "normal" => {}, "override" => {} } node.name("picky-monkey") allow(node).to receive(:for_json).and_return(data) expect(@rest).to receive(:put).with("nodes/picky-monkey", selected_data).and_return("foo") node.save end it "should save false-y whitelisted attributes" do Chef::Config[:default_attribute_whitelist] = [ "foo/bar/baz", ] data = { "default" => { "foo" => { "bar" => { "baz" => false, }, "other" => { "stuff" => true, }, }, }, } selected_data = { "default" => { "foo" => { "bar" => { "baz" => false, }, }, }, } node.name("falsey-monkey") allow(node).to receive(:for_json).and_return(data) expect(@rest).to receive(:put).with("nodes/falsey-monkey", selected_data).and_return("foo") node.save end it "should not save any attributes if the whitelist is empty" do Chef::Config[:automatic_attribute_whitelist] = [] data = { "automatic" => { "filesystem" => { "/dev/disk0s2" => { "size" => "10mb" }, "map - autohome" => { "size" => "10mb" }, }, }, "default" => {}, "normal" => {}, "override" => {} } selected_data = { "automatic" => {}, "default" => {}, "normal" => {}, "override" => {} } node.name("picky-monkey") allow(node).to receive(:for_json).and_return(data) expect(@rest).to receive(:put).with("nodes/picky-monkey", selected_data).and_return("foo") node.save end end context "when policyfile attributes are present" do before do node.name("example-node") node.policy_name = "my-application" node.policy_group = "staging" end context "and the server supports policyfile attributes in node JSON" do it "creates the object normally" do expect(@rest).to receive(:post).with("nodes", node.for_json) node.create end it "saves the node object normally" do expect(@rest).to receive(:put).with("nodes/example-node", node.for_json) node.save end end # Chef Server before 12.3 context "and the Chef Server does not support policyfile attributes in node JSON" do let(:response_body) { %q[{"error":["Invalid key policy_name in request body"]}] } let(:response) do Net::HTTPResponse.send(:response_class, "400").new("1.0", "400", "Bad Request").tap do |r| allow(r).to receive(:body).and_return(response_body) end end let(:http_exception) do begin response.error! rescue => e e end end let(:trimmed_node) do node.for_json.tap do |j| j.delete("policy_name") j.delete("policy_group") end end context "on Chef Client 13 and later" do # Though we normally attempt to provide compatibility with chef # server one major version back, policyfiles were beta when we # added the policyfile attributes to the node JSON, therefore # policyfile users need to be on 12.3 minimum when upgrading Chef # Client to 13+ it "lets the 400 pass through", chef: ">= 13" do expect { node.save }.to raise_error(http_exception) end end context "when the node exists" do it "falls back to saving without policyfile attributes" do expect(@rest).to receive(:put).with("nodes/example-node", node.for_json).and_raise(http_exception) expect(@rest).to receive(:put).with("nodes/example-node", trimmed_node).and_return(@node) expect { node.save }.to_not raise_error end end context "when the node doesn't exist" do let(:response_404) do Net::HTTPResponse.send(:response_class, "404").new("1.0", "404", "Not Found") end let(:http_exception_404) do begin response_404.error! rescue => e e end end it "falls back to saving without policyfile attributes" do expect(@rest).to receive(:put).with("nodes/example-node", node.for_json).and_raise(http_exception) expect(@rest).to receive(:put).with("nodes/example-node", trimmed_node).and_raise(http_exception_404) expect(@rest).to receive(:post).with("nodes", trimmed_node).and_return(@node) node.save end it "creates the node without policyfile attributes" do expect(@rest).to receive(:post).with("nodes", node.for_json).and_raise(http_exception) expect(@rest).to receive(:post).with("nodes", trimmed_node).and_return(@node) node.create end end end end end end describe "method_missing handling" do it "should have an #empty? method via Chef::Node::Attribute" do node.default["foo"] = "bar" expect(node.empty?).to be false end it "it should correctly implement #respond_to?" do expect(node.respond_to?(:empty?)).to be true end it "it should correctly retrieve the method with #method" do expect(node.method(:empty?)).to be_kind_of(Method) end end end chef-12.14.60/spec/unit/org_spec.rb000066400000000000000000000131501276456504500167270ustar00rootroot00000000000000# # Author:: Steven Danna (steve@chef.io) # Copyright:: Copyright 2014-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/org" require "tempfile" describe Chef::Org do let(:org) { Chef::Org.new("an_org") } describe "initialize" do it "is a Chef::Org" do expect(org).to be_a_kind_of(Chef::Org) end end describe "name" do it "lets you set the name to a string" do org.name "sg1" expect(org.name).to eq("sg1") end # It is not feasible to check all invalid characters. Here are a few # that we probably care about. it "raises on invalid characters" do # capital letters expect { org.name "Bar" }.to raise_error(ArgumentError) # slashes expect { org.name "foo/bar" }.to raise_error(ArgumentError) # ? expect { org.name "foo?" }.to raise_error(ArgumentError) # & expect { org.name "foo&" }.to raise_error(ArgumentError) # spaces expect { org.name "foo " }.to raise_error(ArgumentError) end it "raises an ArgumentError if you feed it anything but a string" do expect { org.name Hash.new }.to raise_error(ArgumentError) end end describe "full_name" do it "lets you set the full name" do org.full_name "foo" expect(org.full_name).to eq("foo") end it "raises an ArgumentError if you feed it anything but a string" do expect { org.name Hash.new }.to raise_error(ArgumentError) end end describe "private_key" do it "returns the private key" do org.private_key("super private") expect(org.private_key).to eq("super private") end it "raises an ArgumentError if you feed it something lame" do expect { org.private_key Hash.new }.to raise_error(ArgumentError) end end describe "when serializing to JSON" do let(:json) do org.name("black") org.full_name("black crowes") org.to_json end it "serializes as a JSON object" do expect(json).to match(/^\{.+\}$/) end it "includes the name value" do expect(json).to include(%q{"name":"black"}) end it "includes the full name value" do expect(json).to include(%q{"full_name":"black crowes"}) end it "includes the private key when present" do org.private_key("monkeypants") expect(org.to_json).to include(%q{"private_key":"monkeypants"}) end it "does not include the private key if not present" do expect(json).to_not include("private_key") end end describe "when deserializing from JSON" do let(:org) do o = { "name" => "turtle", "full_name" => "turtle_club", "private_key" => "pandas" } Chef::Org.from_json(o.to_json) end it "deserializes to a Chef::Org object" do expect(org).to be_a_kind_of(Chef::Org) end it "preserves the name" do expect(org.name).to eq("turtle") end it "preserves the full_name" do expect(org.full_name).to eq("turtle_club") end it "includes the private key if present" do expect(org.private_key).to eq("pandas") end end describe "API Interactions" do let(:rest) do Chef::Config[:chef_server_root] = "http://www.example.com" r = double("rest") allow(Chef::ServerAPI).to receive(:new).and_return(r) r end let(:org) do o = Chef::Org.new("foobar") o.full_name "foo bar bat" o end describe "list" do let(:response) { { "foobar" => "http://www.example.com/organizations/foobar" } } let(:inflated_response) { { "foobar" => org } } it "lists all orgs" do expect(rest).to receive(:get).with("organizations").and_return(response) expect(Chef::Org.list).to eq(response) end it "inflate all orgs" do allow(Chef::Org).to receive(:load).with("foobar").and_return(org) expect(rest).to receive(:get).with("organizations").and_return(response) expect(Chef::Org.list(true)).to eq(inflated_response) end end describe "create" do it "creates a new org via the API" do expect(rest).to receive(:post).with("organizations", { :name => "foobar", :full_name => "foo bar bat" }).and_return({}) org.create end end describe "read" do it "loads a named org from the API" do expect(rest).to receive(:get).with("organizations/foobar").and_return({ "name" => "foobar", "full_name" => "foo bar bat", "private_key" => "private" }) org = Chef::Org.load("foobar") expect(org.name).to eq("foobar") expect(org.full_name).to eq("foo bar bat") expect(org.private_key).to eq("private") end end describe "update" do it "updates an existing org on via the API" do expect(rest).to receive(:put).with("organizations/foobar", { :name => "foobar", :full_name => "foo bar bat" }).and_return({}) org.update end end describe "destroy" do it "deletes the specified org via the API" do expect(rest).to receive(:delete).with("organizations/foobar") org.destroy end end end end chef-12.14.60/spec/unit/platform/000077500000000000000000000000001276456504500164255ustar00rootroot00000000000000chef-12.14.60/spec/unit/platform/query_helpers_spec.rb000066400000000000000000000205421276456504500226560ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe "Chef::Platform#windows_server_2003?" do it "returns false early when not on windows" do allow(ChefConfig).to receive(:windows?).and_return(false) expect(Chef::Platform).not_to receive(:require) expect(Chef::Platform.windows_server_2003?).to be_falsey end # CHEF-4888: Need to call WIN32OLE.ole_initialize in new threads it "does not raise an exception" do expect { Thread.fork { Chef::Platform.windows_server_2003? }.join }.not_to raise_error end end describe "Chef::Platform#windows_nano_server?" do include_context "Win32" let(:key) { "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels" } let(:key_query_value) { 0x0001 } let(:access) { key_query_value | 0x0100 } let(:hive) { double("Win32::Registry::HKEY_LOCAL_MACHINE") } let(:registry) { double("Win32::Registry") } before(:all) do Win32::Registry = Class.new Win32::Registry::Error = Class.new(RuntimeError) end before do Win32::Registry::HKEY_LOCAL_MACHINE = hive Win32::Registry::KEY_QUERY_VALUE = key_query_value end after do Win32::Registry.send(:remove_const, "HKEY_LOCAL_MACHINE") if defined?(Win32::Registry::HKEY_LOCAL_MACHINE) Win32::Registry.send(:remove_const, "KEY_QUERY_VALUE") if defined?(Win32::Registry::KEY_QUERY_VALUE) end it "returns false early when not on windows" do allow(ChefConfig).to receive(:windows?).and_return(false) expect(Chef::Platform).to_not receive(:require) expect(Chef::Platform.windows_nano_server?).to be false end it "returns true when the registry value is 1" do allow(ChefConfig).to receive(:windows?).and_return(true) allow(Chef::Platform).to receive(:require).with("win32/registry") expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). with(key, access). and_yield(registry) expect(registry).to receive(:[]).with("NanoServer").and_return(1) expect(Chef::Platform.windows_nano_server?).to be true end it "returns false when the registry value is not 1" do allow(ChefConfig).to receive(:windows?).and_return(true) allow(Chef::Platform).to receive(:require).with("win32/registry") expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). with(key, access). and_yield(registry) expect(registry).to receive(:[]).with("NanoServer").and_return(0) expect(Chef::Platform.windows_nano_server?).to be false end it "returns false when the registry value does not exist" do allow(ChefConfig).to receive(:windows?).and_return(true) allow(Chef::Platform).to receive(:require).with("win32/registry") expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). with(key, access). and_yield(registry) expect(registry).to receive(:[]).with("NanoServer"). and_raise(Win32::Registry::Error, "The system cannot find the file specified.") expect(Chef::Platform.windows_nano_server?).to be false end it "returns false when the registry key does not exist" do allow(ChefConfig).to receive(:windows?).and_return(true) allow(Chef::Platform).to receive(:require).with("win32/registry") expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). with(key, access). and_raise(Win32::Registry::Error, "The system cannot find the file specified.") expect(Chef::Platform.windows_nano_server?).to be false end end describe "Chef::Platform#supports_msi?" do include_context "Win32" # clear and restore Win32:: namespace let(:key) { "System\\CurrentControlSet\\Services\\msiserver" } let(:key_query_value) { 0x0001 } let(:access) { key_query_value } let(:hive) { double("Win32::Registry::HKEY_LOCAL_MACHINE") } let(:registry) { double("Win32::Registry") } before(:all) do Win32::Registry = Class.new Win32::Registry::Error = Class.new(RuntimeError) end before do Win32::Registry::HKEY_LOCAL_MACHINE = hive Win32::Registry::KEY_QUERY_VALUE = key_query_value end after do Win32::Registry.send(:remove_const, "HKEY_LOCAL_MACHINE") if defined?(Win32::Registry::HKEY_LOCAL_MACHINE) Win32::Registry.send(:remove_const, "KEY_QUERY_VALUE") if defined?(Win32::Registry::KEY_QUERY_VALUE) end it "returns false early when not on windows" do allow(ChefConfig).to receive(:windows?).and_return(false) expect(Chef::Platform).to_not receive(:require) expect(Chef::Platform.supports_msi?).to be false end it "returns true when the registry key exists" do allow(ChefConfig).to receive(:windows?).and_return(true) allow(Chef::Platform).to receive(:require).with("win32/registry") expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). with(key, access). and_yield(registry) expect(Chef::Platform.supports_msi?).to be true end it "returns false when the registry key does not exist" do allow(ChefConfig).to receive(:windows?).and_return(true) allow(Chef::Platform).to receive(:require).with("win32/registry") expect(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open). with(key, access). and_raise(Win32::Registry::Error, "The system cannot find the file specified.") expect(Chef::Platform.supports_msi?).to be false end end describe "Chef::Platform#supports_dsc?" do it "returns false if powershell is not present" do node = Chef::Node.new expect(Chef::Platform.supports_dsc?(node)).to be_falsey end ["1.0", "2.0", "3.0"].each do |version| it "returns false for Powershell #{version}" do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = version expect(Chef::Platform.supports_dsc?(node)).to be_falsey end end ["4.0", "5.0"].each do |version| it "returns true for Powershell #{version}" do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = version expect(Chef::Platform.supports_dsc?(node)).to be_truthy end end end describe "Chef::Platform#supports_dsc_invoke_resource?" do it "returns false if powershell is not present" do node = Chef::Node.new expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_falsey end ["1.0", "2.0", "3.0", "4.0", "5.0.10017.9"].each do |version| it "returns false for Powershell #{version}" do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = version expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_falsey end end it "returns true for Powershell 5.0.10018.0" do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = "5.0.10018.0" expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_truthy end end describe "Chef::Platform#dsc_refresh_mode_disabled?" do let(:node) { instance_double("Chef::Node") } let(:cmdlet) { instance_double("Chef::Util::Powershell::Cmdlet") } let(:cmdlet_result) { instance_double("Chef::Util::Powershell::CmdletResult") } it "returns true when RefreshMode is Disabled" do expect(Chef::Util::Powershell::Cmdlet).to receive(:new). with(node, "Get-DscLocalConfigurationManager", :object). and_return(cmdlet) expect(cmdlet).to receive(:run!).and_return(cmdlet_result) expect(cmdlet_result).to receive(:return_value).and_return({ "RefreshMode" => "Disabled" }) expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be true end it "returns false when RefreshMode is not Disabled" do expect(Chef::Util::Powershell::Cmdlet).to receive(:new). with(node, "Get-DscLocalConfigurationManager", :object). and_return(cmdlet) expect(cmdlet).to receive(:run!).and_return(cmdlet_result) expect(cmdlet_result).to receive(:return_value).and_return({ "RefreshMode" => "LaLaLa" }) expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be false end end chef-12.14.60/spec/unit/platform_spec.rb000066400000000000000000000213131276456504500177640ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Platform do context "while testing with fake data" do before :all do @original_platform_map = Chef::Platform.platforms end after :all do || Chef::Platform.platforms = @original_platform_map end before(:each) do Chef::Platform.platforms = { :darwin => { ">= 10.11" => { :file => "new_darwinian", }, "9.2.2" => { :file => "darwinian", :else => "thing", }, :default => { :file => "old school", :snicker => "snack", }, }, :mars_volta => { }, :default => { :file => Chef::Provider::File, :pax => "brittania", :cat => "nice", }, } @events = Chef::EventDispatch::Dispatcher.new end it "should allow you to look up a platform by name and version, returning the provider map for it" do pmap = Chef::Platform.find("Darwin", "9.2.2") expect(pmap).to be_a_kind_of(Hash) expect(pmap[:file]).to eql("darwinian") end it "should allow you to look up a platform by name and version using \"greater than\" style operators" do pmap = Chef::Platform.find("Darwin", "11.1.0") expect(pmap).to be_a_kind_of(Hash) expect(pmap[:file]).to eql("new_darwinian") end it "should use the default providers for an os if the specific version does not exist" do pmap = Chef::Platform.find("Darwin", "1") expect(pmap).to be_a_kind_of(Hash) expect(pmap[:file]).to eql("old school") end it "should use the default providers if the os doesn't give me a default, but does exist" do pmap = Chef::Platform.find("mars_volta", "1") expect(pmap).to be_a_kind_of(Hash) expect(pmap[:file]).to eql(Chef::Provider::File) end it "should use the default provider if the os does not exist" do pmap = Chef::Platform.find("AIX", "1") expect(pmap).to be_a_kind_of(Hash) expect(pmap[:file]).to eql(Chef::Provider::File) end it "should merge the defaults for an os with the specific version" do pmap = Chef::Platform.find("Darwin", "9.2.2") expect(pmap[:file]).to eql("darwinian") expect(pmap[:snicker]).to eql("snack") end it "should merge the defaults for an os with the universal defaults" do pmap = Chef::Platform.find("Darwin", "9.2.2") expect(pmap[:file]).to eql("darwinian") expect(pmap[:pax]).to eql("brittania") end it "should allow you to look up a provider for a platform directly by symbol" do expect(Chef::Platform.find_provider("Darwin", "9.2.2", :file)).to eql("darwinian") end it "should raise an exception if a provider cannot be found for a resource type" do expect { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.to raise_error(Chef::Exceptions::ProviderNotFound) end it "should look up a provider for a resource with a Chef::Resource object" do kitty = Chef::Resource::Cat.new("loulou") expect(Chef::Platform.find_provider("Darwin", "9.2.2", kitty)).to eql("nice") end it "should look up a provider with a node and a Chef::Resource object" do kitty = Chef::Resource::Cat.new("loulou") node = Chef::Node.new node.name("Intel") node.automatic_attrs[:platform] = "mac_os_x" node.automatic_attrs[:platform_version] = "9.2.2" expect(Chef::Platform.find_provider_for_node(node, kitty)).to eql("nice") end it "should not throw an exception when the platform version has an unknown format" do expect(Chef::Platform.find_provider(:darwin, "bad-version", :file)).to eql("old school") end it "should prefer an explicit provider" do kitty = Chef::Resource::Cat.new("loulou") allow(kitty).to receive(:provider).and_return(Chef::Provider::File) node = Chef::Node.new node.name("Intel") node.automatic_attrs[:platform] = "mac_os_x" node.automatic_attrs[:platform_version] = "9.2.2" expect(Chef::Platform.find_provider_for_node(node, kitty)).to eql(Chef::Provider::File) end it "should look up a provider based on the resource name if nothing else matches" do kitty = Chef::Resource::Cat.new("loulou") class Chef::Provider::Cat < Chef::Provider; end Chef::Platform.platforms[:default].delete(:cat) node = Chef::Node.new node.name("Intel") node.automatic_attrs[:platform] = "mac_os_x" node.automatic_attrs[:platform_version] = "8.5" expect(Chef::Platform.find_provider_for_node(node, kitty)).to eql(Chef::Provider::Cat) end def setup_file_resource node = Chef::Node.new node.automatic_attrs[:platform] = "mac_os_x" node.automatic_attrs[:platform_version] = "9.2.2" run_context = Chef::RunContext.new(node, {}, @events) [ Chef::Resource::File.new("whateva", run_context), run_context ] end it "returns a provider object given a Chef::Resource object which has a valid run context and an action" do file, run_context = setup_file_resource provider = Chef::Platform.provider_for_resource(file, :foo) expect(provider).to be_an_instance_of(Chef::Provider::File) expect(provider.new_resource).to equal(file) expect(provider.run_context).to equal(run_context) end it "returns a provider object given a Chef::Resource object which has a valid run context without an action" do file, run_context = setup_file_resource provider = Chef::Platform.provider_for_resource(file) expect(provider).to be_an_instance_of(Chef::Provider::File) expect(provider.new_resource).to equal(file) expect(provider.run_context).to equal(run_context) end it "raises an error when trying to find the provider for a resource with no run context" do file = Chef::Resource::File.new("whateva") expect { Chef::Platform.provider_for_resource(file) }.to raise_error(ArgumentError) end it "does not support finding a provider by resource and node -- a run context is required" do expect { Chef::Platform.provider_for_node("node", "resource") }.to raise_error(NotImplementedError) end it "should update the provider map with map" do Chef::Platform.set( :platform => :darwin, :version => "9.2.2", :resource => :file, :provider => "masterful" ) expect(Chef::Platform.platforms[:darwin]["9.2.2"][:file]).to eql("masterful") Chef::Platform.set( :platform => :darwin, :resource => :file, :provider => "masterful" ) expect(Chef::Platform.platforms[:darwin][:default][:file]).to eql("masterful") Chef::Platform.set( :resource => :file, :provider => "masterful" ) expect(Chef::Platform.platforms[:default][:file]).to eql("masterful") Chef::Platform.set( :platform => :hero, :version => "9.2.2", :resource => :file, :provider => "masterful" ) expect(Chef::Platform.platforms[:hero]["9.2.2"][:file]).to eql("masterful") Chef::Platform.set( :resource => :file, :provider => "masterful" ) expect(Chef::Platform.platforms[:default][:file]).to eql("masterful") Chef::Platform.platforms = {} Chef::Platform.set( :resource => :file, :provider => "masterful" ) expect(Chef::Platform.platforms[:default][:file]).to eql("masterful") Chef::Platform.platforms = { :neurosis => {} } Chef::Platform.set(:platform => :neurosis, :resource => :package, :provider => "masterful") expect(Chef::Platform.platforms[:neurosis][:default][:package]).to eql("masterful") end it "does not overwrite the platform map when using :default platform" do Chef::Platform.set( :resource => :file, :platform => :default, :provider => "new school" ) expect(Chef::Platform.platforms[:default][:file]).to eql("new school") expect(Chef::Platform.platforms[:default][:cat]).to eql("nice") end end end chef-12.14.60/spec/unit/policy_builder/000077500000000000000000000000001276456504500176065ustar00rootroot00000000000000chef-12.14.60/spec/unit/policy_builder/dynamic_spec.rb000066400000000000000000000202471276456504500225760ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/policy_builder" describe Chef::PolicyBuilder::Dynamic do let(:node_name) { "joe_node" } let(:ohai_data) { { "platform" => "ubuntu", "platform_version" => "13.04", "fqdn" => "joenode.example.com" } } let(:json_attribs) { { "custom_attr" => "custom_attr_value" } } let(:override_runlist) { nil } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:err_namespace) { Chef::PolicyBuilder::Policyfile } let(:base_node) do node = Chef::Node.new node.name(node_name) node end let(:node) { base_node } subject(:policy_builder) { Chef::PolicyBuilder::Dynamic.new(node_name, ohai_data, json_attribs, override_runlist, events) } describe "loading policy data" do describe "delegating PolicyBuilder API to the correct implementation" do let(:implementation) { instance_double("Chef::PolicyBuilder::Policyfile") } before do allow(policy_builder).to receive(:implementation).and_return(implementation) end # Dynamic should load_node, figure out the correct backend, then forward # messages to it after. That behavior is tested below. it "responds to #load_node" do expect(policy_builder).to respond_to(:load_node) end it "forwards #original_runlist" do expect(implementation).to receive(:original_runlist) policy_builder.original_runlist end it "forwards #run_context" do expect(implementation).to receive(:run_context) policy_builder.run_context end it "forwards #run_list_expansion" do expect(implementation).to receive(:run_list_expansion) policy_builder.run_list_expansion end it "forwards #build_node to the implementation object" do expect(implementation).to receive(:build_node) policy_builder.build_node end it "forwards #setup_run_context to the implementation object" do expect(implementation).to receive(:setup_run_context) policy_builder.setup_run_context arg = Object.new expect(implementation).to receive(:setup_run_context).with(arg) policy_builder.setup_run_context(arg) end it "forwards #expand_run_list to the implementation object" do expect(implementation).to receive(:expand_run_list) policy_builder.expand_run_list end it "forwards #sync_cookbooks to the implementation object" do expect(implementation).to receive(:sync_cookbooks) policy_builder.sync_cookbooks end it "forwards #temporary_policy? to the implementation object" do expect(implementation).to receive(:temporary_policy?) policy_builder.temporary_policy? end end describe "selecting a backend implementation" do let(:implementation) do policy_builder.select_implementation(node) policy_builder.implementation end context "when no policyfile attributes are present on the node" do context "and json_attribs are not given" do let(:json_attribs) { {} } it "uses the ExpandNodeObject implementation" do expect(implementation).to be_a(Chef::PolicyBuilder::ExpandNodeObject) end end context "and no policyfile attributes are present in json_attribs" do let(:json_attribs) { { "foo" => "bar" } } it "uses the ExpandNodeObject implementation" do expect(implementation).to be_a(Chef::PolicyBuilder::ExpandNodeObject) end end context "and :use_policyfile is set in Chef::Config" do before do Chef::Config[:use_policyfile] = true end it "uses the Policyfile implementation" do expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile) end end context "and policy_name and policy_group are set on Chef::Config" do before do Chef::Config[:policy_name] = "example-policy" Chef::Config[:policy_group] = "testing" end it "uses the Policyfile implementation" do expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile) end end context "and deployment_group and policy_document_native_api are set on Chef::Config" do before do Chef::Config[:deployment_group] = "example-policy-staging" Chef::Config[:policy_document_native_api] = false end it "uses the Policyfile implementation" do expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile) end end context "and policyfile attributes are present in json_attribs" do let(:json_attribs) { { "policy_name" => "example-policy", "policy_group" => "testing" } } it "uses the Policyfile implementation" do expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile) end end end context "when policyfile attributes are present on the node" do let(:node) do base_node.policy_name = "example-policy" base_node.policy_group = "staging" base_node end it "uses the Policyfile implementation" do expect(implementation).to be_a(Chef::PolicyBuilder::Policyfile) end end end describe "loading a node" do let(:implementation) { instance_double("Chef::PolicyBuilder::Policyfile") } before do allow(policy_builder).to receive(:implementation).and_return(implementation) end context "when not running chef solo" do context "when successful" do before do expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) expect(policy_builder).to receive(:select_implementation).with(node) expect(implementation).to receive(:finish_load_node).with(node) end it "selects the backend implementation and continues node loading" do policy_builder.load_node end end context "when an error occurs finding the node" do before do expect(Chef::Node).to receive(:find_or_create).with(node_name).and_raise("oops") end it "sends a node_load_failed event and re-raises" do expect(events).to receive(:node_load_failed) expect { policy_builder.load_node }.to raise_error("oops") end end context "when an error occurs in the implementation's finish_load_node call" do before do expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) expect(policy_builder).to receive(:select_implementation).with(node) expect(implementation).to receive(:finish_load_node).and_raise("oops") end it "sends a node_load_failed event and re-raises" do expect(events).to receive(:node_load_failed) expect { policy_builder.load_node }.to raise_error("oops") end end end context "when running chef solo" do before do Chef::Config[:solo_legacy_mode] = true expect(Chef::Node).to receive(:build).with(node_name).and_return(node) expect(policy_builder).to receive(:select_implementation).with(node) expect(implementation).to receive(:finish_load_node).with(node) end it "selects the backend implementation and continues node loading" do policy_builder.load_node end end end end end chef-12.14.60/spec/unit/policy_builder/expand_node_object_spec.rb000066400000000000000000000246501276456504500247660ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/policy_builder" describe Chef::PolicyBuilder::ExpandNodeObject do let(:node_name) { "joe_node" } let(:ohai_data) { { "platform" => "ubuntu", "platform_version" => "13.04", "fqdn" => "joenode.example.com" } } let(:json_attribs) { { "run_list" => [] } } let(:override_runlist) { "recipe[foo::default]" } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:policy_builder) { Chef::PolicyBuilder::ExpandNodeObject.new(node_name, ohai_data, json_attribs, override_runlist, events) } # All methods that Chef::Client calls on this class. describe "Public API" do it "implements a node method" do expect(policy_builder).to respond_to(:node) end it "implements a load_node method for backwards compatibility until Chef 13" do expect(policy_builder).to respond_to(:load_node) end it "has removed the deprecated #load_node method", chef: ">= 13" do expect(policy_builder).to_not respond_to(:load_node) end it "implements a finish_load_node method" do expect(policy_builder).to respond_to(:finish_load_node) end it "implements a build_node method" do expect(policy_builder).to respond_to(:build_node) end it "implements a setup_run_context method that accepts a list of recipe files to run" do expect(policy_builder).to respond_to(:setup_run_context) expect(policy_builder.method(:setup_run_context).arity).to eq(-1) #optional argument end it "implements a run_context method" do expect(policy_builder).to respond_to(:run_context) end it "implements an expand_run_list method" do expect(policy_builder).to respond_to(:expand_run_list) end it "implements a sync_cookbooks method" do expect(policy_builder).to respond_to(:sync_cookbooks) end it "implements a temporary_policy? method" do expect(policy_builder).to respond_to(:temporary_policy?) end describe "finishing loading the node" do let(:node) { Chef::Node.new.tap { |n| n.name(node_name) } } it "stores the node" do policy_builder.finish_load_node(node) expect(policy_builder.node).to eq(node) end end end # Implementation specific tests describe "when first created" do it "has a node_name" do expect(policy_builder.node_name).to eq(node_name) end it "has ohai data" do expect(policy_builder.ohai_data).to eq(ohai_data) end it "has a set of attributes from command line option" do expect(policy_builder.json_attribs).to eq(json_attribs) end it "has an override_runlist" do expect(policy_builder.override_runlist).to eq(override_runlist) end end context "deprecated #load_node method" do let(:node) do node = Chef::Node.new node.name(node_name) node.run_list(["recipe[a::default]", "recipe[b::server]"]) node end before do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node) policy_builder.load_node end it "loads the node" do expect(policy_builder.node).to eq(node) end end context "once the node has been loaded" do let(:node) do node = Chef::Node.new node.name(node_name) node.run_list(["recipe[a::default]", "recipe[b::server]"]) node end before do policy_builder.finish_load_node(node) end it "expands the run_list" do expect(policy_builder.expand_run_list).to be_a(Chef::RunList::RunListExpansion) expect(policy_builder.run_list_expansion).to be_a(Chef::RunList::RunListExpansion) expect(policy_builder.run_list_expansion.recipes).to eq(["a::default", "b::server"]) end end describe "building the node" do let(:configured_environment) { nil } let(:json_attribs) { nil } let(:override_runlist) { nil } let(:primary_runlist) { ["recipe[primary::default]"] } let(:original_default_attrs) { { "default_key" => "default_value" } } let(:original_override_attrs) { { "override_key" => "override_value" } } let(:node) do node = Chef::Node.new node.name(node_name) node.default_attrs = original_default_attrs node.override_attrs = original_override_attrs node.run_list(primary_runlist) node end before do Chef::Config[:environment] = configured_environment policy_builder.finish_load_node(node) policy_builder.build_node end it "sanity checks test setup" do expect(node.run_list).to eq(primary_runlist) end it "clears existing default and override attributes from the node" do expect(node["default_key"]).to be_nil expect(node["override_key"]).to be_nil end it "applies ohai data to the node" do expect(node["fqdn"]).to eq(ohai_data["fqdn"]) end it "reports that a temporary_policy is not being used" do expect(policy_builder.temporary_policy?).to be_falsey end describe "when the given run list is not in expanded form" do # NOTE: for chef-client, the behavior is always to expand the run list, # but this operation is a no-op when none of the run list items are # roles. Because of the amount of mocking required to make this work in # tests, this test is isolated from the others. let(:primary_runlist) { ["role[some_role]"] } let(:expansion) do recipe_list = Chef::RunList::VersionedRecipeList.new recipe_list.add_recipe("recipe[from_role::default", "1.0.2") double("RunListExpansion", :recipes => recipe_list) end let(:node) do node = Chef::Node.new node.name(node_name) node.default_attrs = original_default_attrs node.override_attrs = original_override_attrs node.run_list(primary_runlist) expect(node).to receive(:expand!).with("server") do node.run_list("recipe[from_role::default]") expansion end node end it "expands run list items via the server API" do expect(node.run_list).to eq(["recipe[from_role::default]"]) end end context "when JSON attributes are given on the command line" do let(:json_attribs) { { "run_list" => ["recipe[json_attribs::default]"], "json_attribs_key" => "json_attribs_value" } } it "sets the run list according to the given JSON" do expect(node.run_list).to eq(["recipe[json_attribs::default]"]) end it "sets node attributes according to the given JSON" do expect(node["json_attribs_key"]).to eq("json_attribs_value") end end context "when an override_runlist is given" do let(:override_runlist) { "recipe[foo::default]" } it "sets the override run_list on the node" do expect(node.run_list).to eq([override_runlist]) expect(node.primary_runlist).to eq(primary_runlist) end it "reports that a temporary policy is being used" do expect(policy_builder.temporary_policy?).to be_truthy end end context "when no environment is specified" do it "does not set the environment" do expect(node.chef_environment).to eq("_default") end end context "when a custom environment is configured" do let(:configured_environment) { environment.name } let(:environment) do environment = Chef::Environment.new.tap { |e| e.name("prod") } expect(Chef::Environment).to receive(:load).with("prod").and_return(environment) environment end it "sets the environment as configured" do expect(node.chef_environment).to eq(environment.name) end end end describe "configuring the run_context" do let(:json_attribs) { nil } let(:override_runlist) { nil } let(:node) do node = Chef::Node.new node.name(node_name) node.run_list("recipe[first::default]", "recipe[second::default]") node end let(:chef_http) { double("Chef::ServerAPI") } let(:cookbook_resolve_url) { "environments/#{node.chef_environment}/cookbook_versions" } let(:cookbook_resolve_post_data) { { :run_list => ["first::default", "second::default"] } } # cookbook_hash is just a hash, but since we're passing it between mock # objects, we get a little better test strictness by using a double (which # will have object equality rather than semantic equality #== semantics). let(:cookbook_hash) { double("cookbook hash") } let(:expanded_cookbook_hash) { double("expanded cookbook hash", :each => nil) } let(:cookbook_synchronizer) { double("CookbookSynchronizer") } before do allow(policy_builder).to receive(:api_service).and_return(chef_http) policy_builder.finish_load_node(node) policy_builder.build_node run_list_expansion = policy_builder.run_list_expansion expect(cookbook_hash).to receive(:inject).and_return(expanded_cookbook_hash) expect(chef_http).to receive(:post).with(cookbook_resolve_url, cookbook_resolve_post_data).and_return(cookbook_hash) expect(Chef::CookbookSynchronizer).to receive(:new).with(expanded_cookbook_hash, events).and_return(cookbook_synchronizer) expect(cookbook_synchronizer).to receive(:sync_cookbooks) expect_any_instance_of(Chef::RunContext).to receive(:load).with(run_list_expansion) policy_builder.setup_run_context end it "configures FileVendor to fetch files remotely" do manifest = double("cookbook manifest") expect(Chef::Cookbook::RemoteFileVendor).to receive(:new).with(manifest, chef_http) Chef::Cookbook::FileVendor.create_from_manifest(manifest) end it "triggers cookbook compilation in the run_context" do # Test condition already covered by `Chef::RunContext.any_instance.should_receive(:load).with(run_list_expansion)` end end end chef-12.14.60/spec/unit/policy_builder/policyfile_spec.rb000066400000000000000000000652111276456504500233110ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/policy_builder" describe Chef::PolicyBuilder::Policyfile do let(:node_name) { "joe_node" } let(:ohai_data) { { "platform" => "ubuntu", "platform_version" => "13.04", "fqdn" => "joenode.example.com" } } let(:json_attribs) { { "custom_attr" => "custom_attr_value" } } let(:override_runlist) { nil } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:policy_builder) { Chef::PolicyBuilder::Policyfile.new(node_name, ohai_data, json_attribs, override_runlist, events) } # Convert a SHA1 (160 bit) hex string into an x.y.z version number where the # maximum value is smaller than a postgres BIGINT (signed 64bit, so 63 usable # bits). This requires enterprise Chef or open source server 11.1.0+ (currently not released) # # The SHA1 is devided as follows: # * "major": first 14 chars (56 bits) # * "minor": next 14 chars (56 bits) # * "patch": last 12 chars (48 bits) def id_to_dotted(sha1_id) major = sha1_id[0...14] minor = sha1_id[14...28] patch = sha1_id[28..40] decimal_integers = [major, minor, patch].map { |hex| hex.to_i(16) } decimal_integers.join(".") end let(:example1_lock_data) do # based on https://github.com/danielsdeleo/chef-workflow2-prototype/blob/master/skeletons/basic_policy/Policyfile.lock.json { "identifier" => "168d2102fb11c9617cd8a981166c8adc30a6e915", "version" => "2.3.5", # NOTE: for compatibility mode we include the dotted id in the policyfile to enhance discoverability. "dotted_decimal_identifier" => id_to_dotted("168d2102fb11c9617cd8a981166c8adc30a6e915"), "source" => { "path" => "./cookbooks/demo" }, "scm_identifier" => { "vcs" => "git", "rev_id" => "9d5b09026470c322c3cb5ca8a4157c4d2f16cef3", "remote" => nil, }, } end let(:example2_lock_data) do { "identifier" => "feab40e1fca77c7360ccca1481bb8ba5f919ce3a", "version" => "4.2.0", # NOTE: for compatibility mode we include the dotted id in the policyfile to enhance discoverability. "dotted_decimal_identifier" => id_to_dotted("feab40e1fca77c7360ccca1481bb8ba5f919ce3a"), "source" => { "api" => "https://community.getchef.com/api/v1/cookbooks/example2" }, } end let(:policyfile_default_attributes) { { "policyfile_default_attr" => "policyfile_default_value" } } let(:policyfile_override_attributes) { { "policyfile_override_attr" => "policyfile_override_value" } } let(:policyfile_run_list) { ["recipe[example1::default]", "recipe[example2::server]"] } let(:basic_valid_policy_data) do { "name" => "example-policy", "revision_id" => "123abc", "run_list" => policyfile_run_list, "cookbook_locks" => { "example1" => example1_lock_data, "example2" => example2_lock_data, }, "default_attributes" => policyfile_default_attributes, "override_attributes" => policyfile_override_attributes, } end let(:parsed_policyfile_json) { basic_valid_policy_data } let(:err_namespace) { Chef::PolicyBuilder::Policyfile } it "configures a Chef HTTP API client" do http = double("Chef::ServerAPI") server_url = "https://api.opscode.com/organizations/example" Chef::Config[:chef_server_url] = server_url expect(Chef::ServerAPI).to receive(:new).with(server_url).and_return(http) expect(policy_builder.http_api).to eq(http) end describe "reporting unsupported features" do def initialize_pb Chef::PolicyBuilder::Policyfile.new(node_name, ohai_data, json_attribs, override_runlist, events) end it "always gives `false` for #temporary_policy?" do expect(initialize_pb.temporary_policy?).to be_falsey end context "chef-solo" do before { Chef::Config[:solo_legacy_mode] = true } it "errors on create" do expect { initialize_pb }.to raise_error(err_namespace::UnsupportedFeature) end end context "when given an override run_list" do let(:override_runlist) { "recipe[foo],recipe[bar]" } it "errors on create" do expect { initialize_pb }.to raise_error(err_namespace::UnsupportedFeature) end end context "when json_attribs contains a run_list" do let(:json_attribs) { { "run_list" => [] } } it "errors on create" do expect { initialize_pb }.to raise_error(err_namespace::UnsupportedFeature) end end context "when an environment is configured" do before { Chef::Config[:environment] = "blurch" } it "errors when an environment is configured" do expect { initialize_pb }.to raise_error(err_namespace::UnsupportedFeature) end end end describe "loading policy data" do let(:http_api) { double("Chef::ServerAPI") } let(:configured_environment) { nil } let(:override_runlist) { nil } let(:primary_runlist) { nil } let(:original_default_attrs) { { "default_key" => "default_value" } } let(:original_override_attrs) { { "override_key" => "override_value" } } let(:node) do node = Chef::Node.new node.name(node_name) node.default_attrs = original_default_attrs node.override_attrs = original_override_attrs node.run_list(primary_runlist) if primary_runlist node end before do Chef::Config[:policy_document_native_api] = false Chef::Config[:deployment_group] = "example-policy-stage" allow(policy_builder).to receive(:http_api).and_return(http_api) end describe "when using compatibility mode (policy_document_native_api == false)" do before do Chef::Config[:deployment_group] = "example-policy-stage" end context "when the deployment group cannot be loaded" do let(:error404) { Net::HTTPServerException.new("404 message", :body) } before do expect(http_api).to receive(:get). with("data/policyfiles/example-policy-stage"). and_raise(error404) end it "raises an error" do expect { policy_builder.finish_load_node(node) }.to raise_error(err_namespace::ConfigurationError) end end context "when the deployment_group is not configured" do before do Chef::Config[:deployment_group] = nil end it "errors while loading the node" do expect { policy_builder.finish_load_node(node) }.to raise_error(err_namespace::ConfigurationError) end end context "when deployment_group is correctly configured" do let(:policy_relative_url) { "data/policyfiles/example-policy-stage" } before do expect(http_api).to receive(:get).with(policy_relative_url).and_return(parsed_policyfile_json) end it "fetches the policy file from a data bag item" do expect(policy_builder.policy).to eq(parsed_policyfile_json) end it "extracts the run_list from the policyfile" do expect(policy_builder.run_list).to eq(policyfile_run_list) end end end context "and policy_document_native_api is configured" do before do Chef::Config[:policy_document_native_api] = true Chef::Config[:policy_group] = "policy-stage" Chef::Config[:policy_name] = "example" end context "and policy_name or policy_group are not configured" do it "raises a Configuration error for policy_group" do Chef::Config[:policy_group] = nil expect { policy_builder.policy }.to raise_error(err_namespace::ConfigurationError) end it "raises a Configuration error for policy_name" do Chef::Config[:policy_name] = nil expect { policy_builder.policy }.to raise_error(err_namespace::ConfigurationError) end end context "and policy_name and policy_group are configured" do let(:policy_relative_url) { "policy_groups/policy-stage/policies/example" } before do expect(http_api).to receive(:get).with(policy_relative_url).and_return(parsed_policyfile_json) end it "fetches the policy file from a data bag item" do expect(policy_builder.policy).to eq(parsed_policyfile_json) end it "extracts the run_list from the policyfile" do expect(policy_builder.run_list).to eq(policyfile_run_list) end end end describe "building policy from the policyfile" do before do allow(policy_builder).to receive(:policy).and_return(parsed_policyfile_json) end it "fetches the policy file from a data bag item" do expect(policy_builder.policy).to eq(parsed_policyfile_json) end it "extracts the run_list from the policyfile" do expect(policy_builder.run_list).to eq(policyfile_run_list) end it "extracts the cookbooks and versions for display from the policyfile" do expected = [ "example1::default@2.3.5 (168d210)", "example2::server@4.2.0 (feab40e)", ] expect(policy_builder.run_list_with_versions_for_display).to eq(expected) end it "generates a RunListExpansion-alike object for feeding to the CookbookCompiler" do expect(policy_builder.run_list_expansion_ish).to respond_to(:recipes) expect(policy_builder.run_list_expansion_ish.recipes).to eq(["example1::default", "example2::server"]) end it "implements #expand_run_list in a manner compatible with ExpandNodeObject" do policy_builder.finish_load_node(node) expect(policy_builder.expand_run_list).to respond_to(:recipes) expect(policy_builder.expand_run_list.recipes).to eq(["example1::default", "example2::server"]) expect(policy_builder.expand_run_list.roles).to eq([]) end describe "validating the Policyfile.lock" do it "errors if the policyfile json contains any non-recipe items" do parsed_policyfile_json["run_list"] = ["role[foo]"] expect { policy_builder.validate_policyfile }.to raise_error(err_namespace::PolicyfileError) end it "errors if the policyfile json contains non-fully qualified recipe items" do parsed_policyfile_json["run_list"] = ["recipe[foo]"] expect { policy_builder.validate_policyfile }.to raise_error(err_namespace::PolicyfileError) end it "errors if the policyfile doesn't have a run_list key" do parsed_policyfile_json.delete("run_list") expect { policy_builder.validate_policyfile }.to raise_error(err_namespace::PolicyfileError) end it "error if the policyfile doesn't have a cookbook_locks key" do parsed_policyfile_json.delete("cookbook_locks") expect { policy_builder.validate_policyfile }.to raise_error(err_namespace::PolicyfileError) end it "accepts a valid policyfile" do policy_builder.validate_policyfile end end describe "building the node object" do let(:extra_chef_config) { {} } before do # must be set before #build_node is called to have the proper effect extra_chef_config.each do |key, value| Chef::Config[key] = value end policy_builder.finish_load_node(node) policy_builder.build_node end # it sets policy_name and policy_group in the following priority order: # -j JSON > config file > node object describe "selecting policy_name and policy_group from the various sources" do context "when only set in node JSON" do let(:json_attribs) do { "policy_name" => "policy_name_from_node_json", "policy_group" => "policy_group_from_node_json", } end it "sets policy_name and policy_group on Chef::Config" do expect(Chef::Config[:policy_name]).to eq("policy_name_from_node_json") expect(Chef::Config[:policy_group]).to eq("policy_group_from_node_json") end it "sets policy_name and policy_group on the node object" do expect(node.policy_name).to eq("policy_name_from_node_json") expect(node.policy_group).to eq("policy_group_from_node_json") end end context "when only set in Chef::Config" do let(:extra_chef_config) do { policy_name: "policy_name_from_config", policy_group: "policy_group_from_config", } end it "sets policy_name and policy_group on the node object" do expect(node.policy_name).to eq("policy_name_from_config") expect(node.policy_group).to eq("policy_group_from_config") end end context "when only set on the node" do let(:node) do node = Chef::Node.new node.name(node_name) node.policy_name = "policy_name_from_node" node.policy_group = "policy_group_from_node" node end it "sets policy_name and policy_group on Chef::Config" do expect(Chef::Config[:policy_name]).to eq("policy_name_from_node") expect(Chef::Config[:policy_group]).to eq("policy_group_from_node") end end context "when set in Chef::Config and the fetched node" do let(:node) do node = Chef::Node.new node.name(node_name) node.policy_name = "policy_name_from_node" node.policy_group = "policy_group_from_node" node end let(:extra_chef_config) do { policy_name: "policy_name_from_config", policy_group: "policy_group_from_config", } end it "prefers the policy_name and policy_group from Chef::Config" do expect(node.policy_name).to eq("policy_name_from_config") expect(node.policy_group).to eq("policy_group_from_config") end end context "when set in node json and the fetched node" do let(:json_attribs) do { "policy_name" => "policy_name_from_node_json", "policy_group" => "policy_group_from_node_json", } end let(:node) do node = Chef::Node.new node.name(node_name) node.policy_name = "policy_name_from_node" node.policy_group = "policy_group_from_node" node end it "prefers the policy_name and policy_group from the node json" do expect(policy_builder.policy_name).to eq("policy_name_from_node_json") expect(policy_builder.policy_group).to eq("policy_group_from_node_json") expect(Chef::Config[:policy_name]).to eq("policy_name_from_node_json") expect(Chef::Config[:policy_group]).to eq("policy_group_from_node_json") expect(node.policy_name).to eq("policy_name_from_node_json") expect(node.policy_group).to eq("policy_group_from_node_json") end end context "when set in all sources" do let(:json_attribs) do { "policy_name" => "policy_name_from_node_json", "policy_group" => "policy_group_from_node_json", } end let(:node) do node = Chef::Node.new node.name(node_name) node.policy_name = "policy_name_from_node" node.policy_group = "policy_group_from_node" node end let(:extra_chef_config) do { policy_name: "policy_name_from_config", policy_group: "policy_group_from_config", } end it "prefers the policy_name and group from node json" do expect(policy_builder.policy_name).to eq("policy_name_from_node_json") expect(policy_builder.policy_group).to eq("policy_group_from_node_json") expect(Chef::Config[:policy_name]).to eq("policy_name_from_node_json") expect(Chef::Config[:policy_group]).to eq("policy_group_from_node_json") expect(node.policy_name).to eq("policy_name_from_node_json") expect(node.policy_group).to eq("policy_group_from_node_json") end end end it "resets default and override data" do expect(node["default_key"]).to be_nil expect(node["override_key"]).to be_nil end describe "setting attribute values" do before do policy_builder.build_node end it "resets default and override data" do expect(node["default_key"]).to be_nil expect(node["override_key"]).to be_nil end it "applies ohai data" do expect(ohai_data).to_not be_empty # ensure test is testing something ohai_data.each do |key, value| expect(node.automatic_attrs[key]).to eq(value) end end it "applies attributes from json file" do expect(node["custom_attr"]).to eq("custom_attr_value") end it "applies attributes from the policyfile" do expect(node["policyfile_default_attr"]).to eq("policyfile_default_value") expect(node["policyfile_override_attr"]).to eq("policyfile_override_value") end it "sets the policyfile's run_list on the node object" do expect(node.run_list).to eq(policyfile_run_list) end it "creates node.automatic_attrs[:roles]" do expect(node.automatic_attrs[:roles]).to eq([]) end it "create node.automatic_attrs[:recipes]" do expect(node.automatic_attrs[:recipes]).to eq(["example1::default", "example2::server"]) end end context "when a named run_list is given" do before do Chef::Config[:named_run_list] = "deploy-app" end context "and the named run_list is not present in the policy" do it "raises a ConfigurationError" do err_class = Chef::PolicyBuilder::Policyfile::ConfigurationError err_text = "Policy 'example-policy' revision '123abc' does not have named_run_list 'deploy-app'(available named_run_lists: [])" expect { policy_builder.build_node }.to raise_error(err_class, err_text) end end context "and the named run_list is present in the policy" do let(:parsed_policyfile_json) do basic_valid_policy_data.dup.tap do |p| p["named_run_lists"] = { "deploy-app" => [ "recipe[example1::default]" ], } end end before do policy_builder.build_node end it "sets the run list to the desired named run list" do expect(policy_builder.run_list).to eq([ "recipe[example1::default]" ]) expected_expansion = Chef::PolicyBuilder::Policyfile::RunListExpansionIsh.new([ "example1::default" ], []) expect(policy_builder.run_list_expansion).to eq(expected_expansion) expect(policy_builder.run_list_with_versions_for_display).to eq(["example1::default@2.3.5 (168d210)"]) expect(node.run_list).to eq([ Chef::RunList::RunListItem.new("recipe[example1::default]") ]) expect(node[:roles]).to eq( [] ) expect(node[:recipes]).to eq( ["example1::default"] ) end it "disables the cookbook cache cleaner" do expect(Chef::CookbookCacheCleaner.instance.skip_removal).to be(true) end end end end describe "fetching the desired cookbook set" do let(:example1_cookbook_data) { double("CookbookVersion Hash for example1 cookbook") } let(:example2_cookbook_data) { double("CookbookVersion Hash for example2 cookbook") } let(:example1_cookbook_object) { double("Chef::CookbookVersion for example1 cookbook", version: "0.1.2") } let(:example2_cookbook_object) { double("Chef::CookbookVersion for example2 cookbook", version: "1.2.3") } let(:expected_cookbook_hash) do { "example1" => example1_cookbook_object, "example2" => example2_cookbook_object } end let(:example1_xyz_version) { example1_lock_data["dotted_decimal_identifier"] } let(:example2_xyz_version) { example2_lock_data["dotted_decimal_identifier"] } let(:example1_identifier) { example1_lock_data["identifier"] } let(:example2_identifier) { example2_lock_data["identifier"] } let(:cookbook_synchronizer) { double("Chef::CookbookSynchronizer") } shared_examples "fetching cookbooks when they don't exist" do context "and a cookbook is missing" do let(:error404) { Net::HTTPServerException.new("404 message", :body) } before do policy_builder.finish_load_node(node) policy_builder.build_node expect(http_api).to receive(:get).with(cookbook1_url). and_raise(error404) end it "raises an error indicating which cookbook is missing" do expect { policy_builder.cookbooks_to_sync }.to raise_error(Chef::Exceptions::CookbookNotFound) end end end shared_examples_for "fetching cookbooks when they exist" do context "and the cookbooks can be fetched" do before do Chef.reset! policy_builder.finish_load_node(node) policy_builder.build_node allow(Chef::CookbookSynchronizer).to receive(:new). with(expected_cookbook_hash, events). and_return(cookbook_synchronizer) end after do Chef.reset! end it "builds a Hash of the form 'cookbook_name' => Chef::CookbookVersion" do expect(policy_builder.cookbooks_to_sync).to eq(expected_cookbook_hash) end it "syncs the desired cookbooks via CookbookSynchronizer" do expect(cookbook_synchronizer).to receive(:sync_cookbooks) policy_builder.sync_cookbooks end it "builds a run context" do expect(cookbook_synchronizer).to receive(:sync_cookbooks) expect_any_instance_of(Chef::RunContext).to receive(:load).with(policy_builder.run_list_expansion_ish) expect_any_instance_of(Chef::CookbookCollection).to receive(:validate!) expect_any_instance_of(Chef::CookbookCollection).to receive(:install_gems) run_context = policy_builder.setup_run_context expect(run_context.node).to eq(node) expect(run_context.cookbook_collection.keys).to match_array(%w{example1 example2}) end it "makes the run context available via static method on Chef" do expect(cookbook_synchronizer).to receive(:sync_cookbooks) expect_any_instance_of(Chef::RunContext).to receive(:load).with(policy_builder.run_list_expansion_ish) expect_any_instance_of(Chef::CookbookCollection).to receive(:validate!) expect_any_instance_of(Chef::CookbookCollection).to receive(:install_gems) run_context = policy_builder.setup_run_context expect(Chef.run_context).to eq(run_context) end end end # shared_examples_for "fetching cookbooks" context "when using compatibility mode (policy_document_native_api == false)" do let(:cookbook1_url) { "cookbooks/example1/#{example1_xyz_version}" } let(:cookbook2_url) { "cookbooks/example2/#{example2_xyz_version}" } context "when the cookbooks don't exist on the server" do include_examples "fetching cookbooks when they don't exist" end context "when the cookbooks exist on the server" do before do expect(http_api).to receive(:get).with(cookbook1_url). and_return(example1_cookbook_data) expect(http_api).to receive(:get).with(cookbook2_url). and_return(example2_cookbook_data) expect(Chef::CookbookVersion).to receive(:from_cb_artifact_data).with(example1_cookbook_data). and_return(example1_cookbook_object) expect(Chef::CookbookVersion).to receive(:from_cb_artifact_data).with(example2_cookbook_data). and_return(example2_cookbook_object) end include_examples "fetching cookbooks when they exist" end end context "when using native API mode (policy_document_native_api == true)" do before do Chef::Config[:policy_document_native_api] = true Chef::Config[:policy_group] = "policy-stage" Chef::Config[:policy_name] = "example" end let(:cookbook1_url) { "cookbook_artifacts/example1/#{example1_identifier}" } let(:cookbook2_url) { "cookbook_artifacts/example2/#{example2_identifier}" } context "when the cookbooks don't exist on the server" do include_examples "fetching cookbooks when they don't exist" end context "when the cookbooks exist on the server" do before do expect(http_api).to receive(:get).with(cookbook1_url). and_return(example1_cookbook_data) expect(http_api).to receive(:get).with(cookbook2_url). and_return(example2_cookbook_data) expect(Chef::CookbookVersion).to receive(:from_cb_artifact_data).with(example1_cookbook_data). and_return(example1_cookbook_object) expect(Chef::CookbookVersion).to receive(:from_cb_artifact_data).with(example2_cookbook_data). and_return(example2_cookbook_object) end include_examples "fetching cookbooks when they exist" end end end end end end chef-12.14.60/spec/unit/policy_builder_spec.rb000066400000000000000000000014541276456504500211510ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/policy_builder" describe Chef::PolicyBuilder do # TODO: test the strategy method end chef-12.14.60/spec/unit/property/000077500000000000000000000000001276456504500164655ustar00rootroot00000000000000chef-12.14.60/spec/unit/property/state_spec.rb000066400000000000000000000435451276456504500211570ustar00rootroot00000000000000require "support/shared/integration/integration_helper" describe "Chef::Resource#identity and #state" do include IntegrationSupport class NewResourceNamer @i = 0 def self.next "chef_resource_property_spec_#{@i += 1}" end end def self.new_resource_name NewResourceNamer.next end let(:resource_class) do new_resource_name = self.class.new_resource_name Class.new(Chef::Resource) do resource_name new_resource_name end end let(:resource) do resource_class.new("blah") end def self.english_join(values) return "" if values.size == 0 return values[0].inspect if values.size == 1 "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}" end def self.with_property(*properties, &block) tags_index = properties.find_index { |p| !p.is_a?(String) } if tags_index properties, tags = properties[0..tags_index - 1], properties[tags_index..-1] else tags = [] end properties = properties.map { |property| "property #{property}" } context "With properties #{english_join(properties)}", *tags do before do properties.each do |property_str| resource_class.class_eval(property_str, __FILE__, __LINE__) end end instance_eval(&block) end end # identity context "Chef::Resource#identity_properties" do with_property ":x" do it "name is the default identity" do expect(resource_class.identity_properties).to eq [ Chef::Resource.properties[:name] ] expect(Chef::Resource.properties[:name].identity?).to be_falsey expect(resource.name).to eq "blah" expect(resource.identity).to eq "blah" end it "identity_properties :x changes the identity" do expect(resource_class.identity_properties :x).to eq [ resource_class.properties[:x] ] expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] expect(Chef::Resource.properties[:name].identity?).to be_falsey expect(resource_class.properties[:x].identity?).to be_truthy expect(resource.x "woo").to eq "woo" expect(resource.x).to eq "woo" expect(resource.name).to eq "blah" expect(resource.identity).to eq "woo" end with_property ":y, identity: true" do context "and identity_properties :x" do before do resource_class.class_eval do identity_properties :x end end it "only returns :x as identity" do resource.x "foo" resource.y "bar" expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] expect(resource.identity).to eq "foo" end it "does not flip y.desired_state off" do resource.x "foo" resource.y "bar" expect(resource_class.state_properties).to eq [ resource_class.properties[:x], resource_class.properties[:y], ] expect(resource.state_for_resource_reporter).to eq(x: "foo", y: "bar") end end end context "With a subclass" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) do subresource_class.new("sub") end it "name is the default identity on the subclass" do expect(subresource_class.identity_properties).to eq [ Chef::Resource.properties[:name] ] expect(Chef::Resource.properties[:name].identity?).to be_falsey expect(subresource.name).to eq "sub" expect(subresource.identity).to eq "sub" end context "With identity_properties :x on the superclass" do before do resource_class.class_eval do identity_properties :x end end it "The subclass inherits :x as identity" do expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x] ] expect(Chef::Resource.properties[:name].identity?).to be_falsey expect(subresource_class.properties[:x].identity?).to be_truthy subresource.x "foo" expect(subresource.identity).to eq "foo" end context "With property :y, identity: true on the subclass" do before do subresource_class.class_eval do property :y, identity: true end end it "The subclass's identity includes both x and y" do expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x], subresource_class.properties[:y], ] subresource.x "foo" subresource.y "bar" expect(subresource.identity).to eq(x: "foo", y: "bar") end end with_property ":y, String" do context "With identity_properties :y on the subclass" do before do subresource_class.class_eval do identity_properties :y end end it "y is part of state" do subresource.x "foo" subresource.y "bar" expect(subresource.state_for_resource_reporter).to eq(x: "foo", y: "bar") expect(subresource_class.state_properties).to eq [ subresource_class.properties[:x], subresource_class.properties[:y], ] end it "y is the identity" do expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:y] ] subresource.x "foo" subresource.y "bar" expect(subresource.identity).to eq "bar" end it "y still has validation" do expect { subresource.y 12 }.to raise_error Chef::Exceptions::ValidationFailed end end end end end end with_property ":string_only, String, identity: true", ":string_only2, String" do it "identity_properties does not change validation" do resource_class.identity_properties :string_only expect { resource.string_only 12 }.to raise_error Chef::Exceptions::ValidationFailed expect { resource.string_only2 12 }.to raise_error Chef::Exceptions::ValidationFailed end end with_property ":x, desired_state: false" do it "identity_properties does not change desired_state" do resource_class.identity_properties :x resource.x "hi" expect(resource.identity).to eq "hi" expect(resource_class.properties[:x].desired_state?).to be_falsey expect(resource_class.state_properties).to eq [] expect(resource.state_for_resource_reporter).to eq({}) end end context "With custom property custom_property defined only as methods, using different variables for storage" do before do resource_class.class_eval do def custom_property @blarghle ? @blarghle * 3 : nil end def custom_property=(x) @blarghle = x * 2 end end end context "And identity_properties :custom_property" do before do resource_class.class_eval do identity_properties :custom_property end end it "identity_properties comes back as :custom_property" do expect(resource_class.properties[:custom_property].identity?).to be_truthy expect(resource_class.identity_properties).to eq [ resource_class.properties[:custom_property] ] end it "custom_property becomes part of desired_state" do resource.custom_property = 1 expect(resource.state_for_resource_reporter).to eq(custom_property: 6) expect(resource_class.properties[:custom_property].desired_state?).to be_truthy expect(resource_class.state_properties).to eq [ resource_class.properties[:custom_property], ] end it "identity_properties does not change custom_property's getter or setter" do resource.custom_property = 1 expect(resource.custom_property).to eq 6 end it "custom_property is returned as the identity" do expect(resource.identity).to be_nil resource.custom_property = 1 expect(resource.identity).to eq 6 end end end end context "Property#identity" do with_property ":x, identity: true" do it "name is only part of the identity if an identity attribute is defined" do expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] resource.x "woo" expect(resource.identity).to eq "woo" end end with_property ":x, identity: true, default: 'xxx'", ":y, identity: true, default: 'yyy'", ":z, identity: true, default: 'zzz'" do it "identity_property raises an error if multiple identity values are defined" do expect { resource_class.identity_property }.to raise_error Chef::Exceptions::MultipleIdentityError end it "identity_attr raises an error if multiple identity values are defined" do expect { resource_class.identity_attr }.to raise_error Chef::Exceptions::MultipleIdentityError end it "identity returns all identity values in a hash if multiple are defined" do resource.x "foo" resource.y "bar" resource.z "baz" expect(resource.identity).to eq(x: "foo", y: "bar", z: "baz") end it "identity returns all values whether any value is set or not" do expect(resource.identity).to eq(x: "xxx", y: "yyy", z: "zzz") end it "identity_properties wipes out any other identity attributes if multiple are defined" do resource_class.identity_properties :y resource.x "foo" resource.y "bar" resource.z "baz" expect(resource.identity).to eq "bar" end end with_property ":x, identity: true, name_property: true" do it "identity when x is not defined returns the value of x" do expect(resource.identity).to eq "blah" end it "state when x is not defined returns the value of x" do expect(resource.state_for_resource_reporter).to eq(x: "blah") end end end # state_properties context "Chef::Resource#state_properties" do it "state_properties is empty by default" do expect(Chef::Resource.state_properties).to eq [] expect(resource.state_for_resource_reporter).to eq({}) end with_property ":x", ":y", ":z" do it "x, y and z are state attributes" do resource.x 1 resource.y 2 resource.z 3 expect(resource_class.state_properties).to eq [ resource_class.properties[:x], resource_class.properties[:y], resource_class.properties[:z], ] expect(resource.state_for_resource_reporter).to eq(x: 1, y: 2, z: 3) end it "values that are not set are not included in state" do resource.x 1 expect(resource.state_for_resource_reporter).to eq(x: 1) end it "when no values are set, nothing is included in state" do end end with_property ":x", ":y, desired_state: false", ":z, desired_state: true" do it "x and z are state attributes, and y is not" do resource.x 1 resource.y 2 resource.z 3 expect(resource_class.state_properties).to eq [ resource_class.properties[:x], resource_class.properties[:z], ] expect(resource.state_for_resource_reporter).to eq(x: 1, z: 3) end end with_property ":x, name_property: true" do # it "Unset values with name_property are included in state" do # expect(resource.state_for_resource_reporter).to eq({ x: 'blah' }) # end it "Set values with name_property are included in state" do resource.x 1 expect(resource.state_for_resource_reporter).to eq(x: 1) end end with_property ":x, default: 1" do it "Unset values with defaults are not included in state" do expect(resource.state_for_resource_reporter).to eq({}) end it "Set values with defaults are included in state" do resource.x 1 expect(resource.state_for_resource_reporter).to eq(x: 1) end end context "With a class with a normal getter and setter" do before do resource_class.class_eval do def x @blah * 3 end def x=(value) @blah = value * 2 end end end it "state_properties(:x) causes the value to be included in properties" do resource_class.state_properties(:x) resource.x = 1 expect(resource.x).to eq 6 expect(resource.state_for_resource_reporter).to eq(x: 6) end end context "When state_properties happens before properties are declared" do before do resource_class.class_eval do state_properties :x property :x end end it "the property works and is in state_properties" do expect(resource_class.state_properties).to include(resource_class.properties[:x]) resource.x = 1 expect(resource.x).to eq 1 expect(resource.state_for_resource_reporter).to eq(x: 1) end end with_property ":x, Integer, identity: true" do it "state_properties(:x) leaves the property in desired_state" do resource_class.state_properties(:x) resource.x 10 expect(resource_class.properties[:x].desired_state?).to be_truthy expect(resource_class.state_properties).to eq [ resource_class.properties[:x], ] expect(resource.state_for_resource_reporter).to eq(x: 10) end it "state_properties(:x) does not turn off validation" do resource_class.state_properties(:x) expect { resource.x "ouch" }.to raise_error Chef::Exceptions::ValidationFailed end it "state_properties(:x) does not turn off identity" do resource_class.state_properties(:x) resource.x 10 expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] expect(resource_class.properties[:x].identity?).to be_truthy expect(resource.identity).to eq 10 end end with_property ":x, Integer, identity: true, desired_state: false" do before do resource_class.class_eval do def y 20 end end end it "state_properties(:x) leaves x identical" do old_value = resource_class.properties[:y] resource_class.state_properties(:x) resource.x 10 expect(resource_class.properties[:y].object_id).to eq old_value.object_id expect(resource_class.properties[:x].desired_state?).to be_truthy expect(resource_class.properties[:x].identity?).to be_truthy expect(resource_class.identity_properties).to eq [ resource_class.properties[:x], ] expect(resource.identity).to eq(10) expect(resource_class.state_properties).to eq [ resource_class.properties[:x], ] expect(resource.state_for_resource_reporter).to eq(x: 10) end it "state_properties(:y) adds y to desired state" do old_value = resource_class.properties[:x] resource_class.state_properties(:y) resource.x 10 expect(resource_class.properties[:x].object_id).to eq old_value.object_id expect(resource_class.properties[:x].desired_state?).to be_falsey expect(resource_class.properties[:y].desired_state?).to be_truthy expect(resource_class.state_properties).to eq [ resource_class.properties[:y], ] expect(resource.state_for_resource_reporter).to eq(y: 20) end context "With a subclassed resource" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) do subresource_class.new("blah") end it "state_properties(:x) adds x to desired state" do old_value = resource_class.properties[:y] subresource_class.state_properties(:x) subresource.x 10 expect(subresource_class.properties[:y].object_id).to eq old_value.object_id expect(subresource_class.properties[:x].desired_state?).to be_truthy expect(subresource_class.properties[:x].identity?).to be_truthy expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x], ] expect(subresource.identity).to eq(10) expect(subresource_class.state_properties).to eq [ subresource_class.properties[:x], ] expect(subresource.state_for_resource_reporter).to eq(x: 10) end it "state_properties(:y) adds y to desired state" do old_value = resource_class.properties[:x] subresource_class.state_properties(:y) subresource.x 10 expect(subresource_class.properties[:x].object_id).to eq old_value.object_id expect(subresource_class.properties[:y].desired_state?).to be_truthy expect(subresource_class.state_properties).to eq [ subresource_class.properties[:y], ] expect(subresource.state_for_resource_reporter).to eq(y: 20) expect(subresource_class.properties[:x].identity?).to be_truthy expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x], ] expect(subresource.identity).to eq(10) end end end end end chef-12.14.60/spec/unit/property/validation_spec.rb000066400000000000000000000500021276456504500221530ustar00rootroot00000000000000require "support/shared/integration/integration_helper" describe "Chef::Resource.property validation" do include IntegrationSupport module Namer @i = 0 def self.next_resource_name "chef_resource_property_spec_#{@i += 1}" end def self.reset_index @current_index = 0 end def self.current_index @current_index end def self.next_index @current_index += 1 end end def lazy(&block) Chef::DelayedEvaluator.new(&block) end before do Namer.reset_index end def self.new_resource_name Namer.next_resource_name end let(:resource_class) do new_resource_name = self.class.new_resource_name Class.new(Chef::Resource) do resource_name new_resource_name def blah Namer.next_index end def self.blah "class#{Namer.next_index}" end end end let(:resource) do resource_class.new("blah") end def self.english_join(values) return "" if values.size == 0 return values[0].inspect if values.size == 1 "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}" end def self.with_property(*properties, &block) tags_index = properties.find_index { |p| !p.is_a?(String) } if tags_index properties, tags = properties[0..tags_index - 1], properties[tags_index..-1] else tags = [] end properties = properties.map { |property| "property #{property}" } context "With properties #{english_join(properties)}", *tags do before do properties.each do |property_str| resource_class.class_eval(property_str, __FILE__, __LINE__) end end instance_eval(&block) end end def self.validation_test(validation, success_values, failure_values, *tags) with_property ":x, #{validation}", *tags do it "gets nil when retrieving the initial (non-set) value" do expect(resource.x).to be_nil end success_values.each do |v| it "value #{v.inspect} is valid" do resource.instance_eval { @x = "default" } expect(resource.x v).to eq v expect(resource.x).to eq v end end failure_values.each do |v| it "value #{v.inspect} is invalid" do expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed resource.instance_eval { @x = "default" } expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed end end it "setting x to nil when it is already nil does not emit a warning" do expect(resource.x nil).to be_nil expect(resource.x).to be_nil end unless tags.include?(:nillable) it "changing x to nil warns that the get will change to a set in Chef 13 and does not change the value" do resource.instance_eval { @x = "default" } expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /An attempt was made to change x from "default" to nil by calling x\(nil\). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil./ Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(resource.x nil).to eq "default" expect(resource.x).to eq "default" end end end if tags.include?(:nil_is_valid) with_property ":x, #{validation}, default: nil" do it "setting x to nil when it is already nil does not emit a warning" do expect(resource.x nil).to be_nil expect(resource.x).to be_nil end it "changing x to nil warns that the get will change to a set in Chef 13 and does not change the value" do resource.instance_eval { @x = "default" } expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /An attempt was made to change x from "default" to nil by calling x\(nil\). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil./ Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(resource.x nil).to eq "default" expect(resource.x).to eq "default" end end elsif tags.include?(:nillable) with_property ":x, #{validation}, nillable: true" do it "changing x to nil with nillable true overwrites defaults and just works" do resource.instance_eval { @x = "default" } expect { resource.x nil }.not_to raise_error expect(resource.x nil).to eq nil expect(resource.x).to eq nil end end else it "property :x, #{validation}, default: nil warns that the default is invalid" do expect { resource_class.class_eval("property :x, #{validation}, default: nil", __FILE__, __LINE__) }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Default value nil is invalid for property x of resource chef_resource_property_spec_(\d+). Possible fixes: 1. Remove 'default: nil' if nil means 'undefined'. 2. Set a valid default value if there is a reasonable one. 3. Allow nil as a valid value of your property \(for example, 'property :x, \[ String, nil \], default: nil'\)./ end context "With property :x, #{validation}, default: nil" do before do Chef::Config[:treat_deprecation_warnings_as_errors] = false resource_class.class_eval("property :x, #{validation}, default: nil", __FILE__, __LINE__) Chef::Config[:treat_deprecation_warnings_as_errors] = true end it "changing x to nil emits a warning that the value is invalid and does not change the value" do resource.instance_eval { @x = "default" } expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /nil is an invalid value for x of resource chef_resource_property_spec_(\d+). In Chef 13, this warning will change to an error./ Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(resource.x nil).to eq "default" expect(resource.x).to eq "default" end end end end context "basic get, set, and nil set" do with_property ":x, kind_of: String" do context "when the variable already has a value" do before do resource.instance_eval { @x = "default" } end it "get succeeds" do expect(resource.x).to eq "default" end it "set to valid value succeeds" do expect(resource.x "str").to eq "str" expect(resource.x).to eq "str" end it "set to invalid value raises ValidationFailed" do expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed end it "set to nil emits a deprecation warning and does a get" do expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError Chef::Config[:treat_deprecation_warnings_as_errors] = false resource.x "str" expect(resource.x nil).to eq "str" expect(resource.x).to eq "str" end end context "when the variable does not have an initial value" do it "get succeeds" do expect(resource.x).to be_nil end it "set to valid value succeeds" do expect(resource.x "str").to eq "str" expect(resource.x).to eq "str" end it "set to invalid value raises ValidationFailed" do expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed end it "set to nil emits no warning because the value would not change" do expect(resource.x nil).to be_nil end end end with_property ":x, [ String, nil ]" do context "when the variable already has a value" do before do resource.instance_eval { @x = "default" } end it "get succeeds" do expect(resource.x).to eq "default" end it "set(nil) emits a warning that the value will be set, but does not set the value" do expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /An attempt was made to change x from "default" to nil by calling x\(nil\). In Chef 12, this does a get rather than a set. In Chef 13, this will change to set the value to nil./ Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(resource.x nil).to eq "default" expect(resource.x).to eq "default" end it "set to valid value succeeds" do expect(resource.x "str").to eq "str" expect(resource.x).to eq "str" end it "set to invalid value raises ValidationFailed" do expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed end end context "when the variable does not have an initial value" do it "get succeeds" do expect(resource.x).to be_nil end it "set(nil) sets the value" do expect(resource.x nil).to be_nil expect(resource.x).to be_nil end it "set to valid value succeeds" do expect(resource.x "str").to eq "str" expect(resource.x).to eq "str" end it "set to invalid value raises ValidationFailed" do expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed end end end end # Bare types context "bare types" do validation_test "String", [ "hi" ], [ 10 ] validation_test ":a", [ :a ], [ :b ] validation_test ":a, is: :b", [ :a, :b ], [ :c ] validation_test ":a, is: [ :b, :c ]", [ :a, :b, :c ], [ :d ] validation_test "[ :a, :b ], is: :c", [ :a, :b, :c ], [ :d ] validation_test "[ :a, :b ], is: [ :c, :d ]", [ :a, :b, :c, :d ], [ :e ] validation_test "nil", [ ], [ :a ], :nil_is_valid validation_test "[ nil ]", [ ], [ :a ], :nil_is_valid validation_test "[]", [], [ :a ] validation_test "[ String, nil ], nillable: true", [ nil, "thing" ], [ :nope, false ], :nillable end # is context "is" do # Class validation_test "is: String", [ "a", "" ], [ :a, 1 ] # Value validation_test "is: :a", [ :a ], [ :b ] validation_test "is: [ :a, :b ]", [ :a, :b ], [ [ :a, :b ] ] validation_test "is: [ [ :a, :b ] ]", [ [ :a, :b ] ], [ :a, :b ] # Regex validation_test "is: /abc/", %w{abc wowabcwow}, [ "", "abac" ] # Property validation_test "is: Chef::Property.new(is: :a)", [ :a ], [ :b ] # RSpec Matcher class Globalses extend RSpec::Matchers end validation_test "is: Globalses.eq(10)", [ 10 ], [ 1 ] # Proc validation_test "is: proc { |x| x }", [ true, 1 ], [ false ] validation_test "is: proc { |x| x > blah }", [ 10 ], [ -1 ] validation_test "is: nil", [ ], [ "a" ], :nil_is_valid validation_test "is: [ String, nil ]", [ "a" ], [ :b ], :nil_is_valid validation_test "is: []", [], [ :a ] end # Combination context "combination" do validation_test 'kind_of: String, equal_to: "a"', [ "a" ], [ "b" ], :nil_is_valid end # equal_to context "equal_to" do # Value validation_test "equal_to: :a", [ :a ], [ :b ], :nil_is_valid validation_test "equal_to: [ :a, :b ]", [ :a, :b ], [ [ :a, :b ] ], :nil_is_valid validation_test "equal_to: [ [ :a, :b ] ]", [ [ :a, :b ] ], [ :a, :b ], :nil_is_valid validation_test "equal_to: nil", [ ], [ "a" ], :nil_is_valid validation_test 'equal_to: [ "a", nil ]', [ "a" ], [ "b" ], :nil_is_valid validation_test 'equal_to: [ nil, "a" ]', [ "a" ], [ "b" ], :nil_is_valid validation_test "equal_to: []", [], [ :a ], :nil_is_valid end # kind_of context "kind_of" do validation_test "kind_of: String", [ "a" ], [ :b ], :nil_is_valid validation_test "kind_of: [ String, Symbol ]", [ "a", :b ], [ 1 ], :nil_is_valid validation_test "kind_of: [ Symbol, String ]", [ "a", :b ], [ 1 ], :nil_is_valid validation_test "kind_of: NilClass", [ ], [ "a" ], :nil_is_valid validation_test "kind_of: [ NilClass, String ]", [ "a" ], [ :a ], :nil_is_valid validation_test "kind_of: []", [], [ :a ], :nil_is_valid validation_test "kind_of: nil", [], [ :a ], :nil_is_valid end # regex context "regex" do validation_test "regex: /abc/", [ "xabcy" ], [ "gbh", 123 ], :nil_is_valid validation_test "regex: [ /abc/, /z/ ]", %w{xabcy aza}, [ "gbh", 123 ], :nil_is_valid validation_test "regex: [ /z/, /abc/ ]", %w{xabcy aza}, [ "gbh", 123 ], :nil_is_valid validation_test "regex: [ [ /z/, /abc/ ], [ /n/ ] ]", %w{xabcy aza ana}, [ "gbh", 123 ], :nil_is_valid validation_test "regex: []", [], [ :a ], :nil_is_valid validation_test "regex: nil", [], [ :a ], :nil_is_valid end # callbacks context "callbacks" do validation_test 'callbacks: { "a" => proc { |x| x > 10 }, "b" => proc { |x| x%2 == 0 } }', [ 12 ], [ 11, 4 ], :nil_is_valid validation_test 'callbacks: { "a" => proc { |x| x%2 == 0 }, "b" => proc { |x| x > 10 } }', [ 12 ], [ 11, 4 ], :nil_is_valid validation_test 'callbacks: { "a" => proc { |x| x.nil? } }', [ ], [ "a" ], :nil_is_valid validation_test "callbacks: {}", [ :a ], [], :nil_is_valid end # respond_to context "respond_to" do validation_test "respond_to: :split", [ "hi" ], [ 1 ], :nil_is_valid validation_test 'respond_to: "split"', [ "hi" ], [ 1 ], :nil_is_valid validation_test "respond_to: :to_s", [ :a ], [], :nil_is_valid validation_test "respond_to: [ :split, :to_s ]", [ "hi" ], [ 1 ], :nil_is_valid validation_test "respond_to: %w(split to_s)", [ "hi" ], [ 1 ], :nil_is_valid validation_test "respond_to: [ :to_s, :split ]", [ "hi" ], [ 1 ], :nil_is_valid validation_test "respond_to: []", [ :a ], [], :nil_is_valid validation_test "respond_to: nil", [ :a ], [], :nil_is_valid end context "cannot_be" do validation_test "cannot_be: :empty", [ 1, [1, 2], { a: 10 } ], [ [] ], :nil_is_valid validation_test 'cannot_be: "empty"', [ 1, [1, 2], { a: 10 } ], [ [] ], :nil_is_valid validation_test "cannot_be: [ :empty, :nil ]", [ 1, [1, 2], { a: 10 } ], [ [] ], :nil_is_valid validation_test 'cannot_be: [ "empty", "nil" ]', [ 1, [1, 2], { a: 10 } ], [ [] ], :nil_is_valid validation_test "cannot_be: [ :nil, :empty ]", [ 1, [1, 2], { a: 10 } ], [ [] ], :nil_is_valid validation_test "cannot_be: [ :empty, :nil, :blahblah ]", [ 1, [1, 2], { a: 10 } ], [ [] ], :nil_is_valid validation_test "cannot_be: []", [ :a ], [], :nil_is_valid validation_test "cannot_be: nil", [ :a ], [], :nil_is_valid end context "required" do with_property ":x, required: true" do it "if x is not specified, retrieval fails" do expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed end it "value 1 is valid" do expect(resource.x 1).to eq 1 expect(resource.x).to eq 1 end it "value nil emits a validation failed error because it must have a value" do expect { resource.x nil }.to raise_error Chef::Exceptions::ValidationFailed end context "and value is set to something other than nil" do before { resource.x 10 } it "value nil emits a deprecation warning and does a get" do expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError Chef::Config[:treat_deprecation_warnings_as_errors] = false resource.x 1 expect(resource.x nil).to eq 1 expect(resource.x).to eq 1 end end end with_property ":x, [String, nil], required: true" do it "if x is not specified, retrieval fails" do expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed end it "value nil is not valid (required means 'not nil')" do expect { resource.x nil }.to raise_error Chef::Exceptions::ValidationFailed end it "value '1' is valid" do expect(resource.x "1").to eq "1" expect(resource.x).to eq "1" end it "value 1 is invalid" do expect { resource.x 1 }.to raise_error Chef::Exceptions::ValidationFailed end end with_property ":x, name_property: true, required: true" do it "if x is not specified, the name property is returned" do expect(resource.x).to eq "blah" end it "value 1 is valid" do expect(resource.x 1).to eq 1 expect(resource.x).to eq 1 end it "value nil emits a deprecation warning and does a get" do expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError Chef::Config[:treat_deprecation_warnings_as_errors] = false resource.x 1 expect(resource.x nil).to eq 1 expect(resource.x).to eq 1 end end with_property ":x, default: 10, required: true" do it "if x is not specified, the default is returned" do expect(resource.x).to eq 10 end it "value 1 is valid" do expect(resource.x 1).to eq 1 expect(resource.x).to eq 1 end it "value nil is invalid" do expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError Chef::Config[:treat_deprecation_warnings_as_errors] = false resource.x 1 expect(resource.x nil).to eq 1 expect(resource.x).to eq 1 end end end context "custom validators (def _pv_blarghle)" do before do Chef::Config[:treat_deprecation_warnings_as_errors] = false end with_property ":x, blarghle: 1" do context "and a class that implements _pv_blarghle" do before do resource_class.class_eval do def _pv_blarghle(opts, key, value) if _pv_opts_lookup(opts, key) != value raise Chef::Exceptions::ValidationFailed, "ouch" end end end end it "value 1 is valid" do expect(resource.x 1).to eq 1 expect(resource.x).to eq 1 end it "value '1' is invalid" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect { resource.x "1" }.to raise_error Chef::Exceptions::ValidationFailed end it "value nil does a get" do Chef::Config[:treat_deprecation_warnings_as_errors] = false resource.x 1 resource.x nil expect(resource.x).to eq 1 end end end with_property ":x, blarghle: 1" do context "and a class that implements _pv_blarghle" do before do resource_class.class_eval do def _pv_blarghle(opts, key, value) if _pv_opts_lookup(opts, key) != value raise Chef::Exceptions::ValidationFailed, "ouch" end end end end it "value 1 is valid" do expect(resource.x 1).to eq 1 expect(resource.x).to eq 1 end it "value '1' is invalid" do expect { resource.x "1" }.to raise_error Chef::Exceptions::ValidationFailed end it "value nil does a get" do resource.x 1 resource.x nil expect(resource.x).to eq 1 end end end end end chef-12.14.60/spec/unit/property_spec.rb000066400000000000000000001305601276456504500200310ustar00rootroot00000000000000require "support/shared/integration/integration_helper" describe "Chef::Resource.property" do include IntegrationSupport module Namer @i = 0 def self.next_resource_name "chef_resource_property_spec_#{@i += 1}" end def self.reset_index @current_index = 0 end def self.current_index @current_index end def self.next_index @current_index += 1 end end def lazy(&block) Chef::DelayedEvaluator.new(&block) end before do Namer.reset_index end def self.new_resource_name Namer.next_resource_name end let(:resource_class) do new_resource_name = self.class.new_resource_name Class.new(Chef::Resource) do resource_name new_resource_name def next_index Namer.next_index end end end let(:resource) do resource_class.new("blah") end def self.english_join(values) return "" if values.size == 0 return values[0].inspect if values.size == 1 "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}" end def self.with_property(*properties, &block) tags_index = properties.find_index { |p| !p.is_a?(String) } if tags_index properties, tags = properties[0..tags_index - 1], properties[tags_index..-1] else tags = [] end if properties.size == 1 description = "With property #{properties.first}" else description = "With properties #{english_join(properties.map { |property| "#{property.inspect}" })}" end context description, *tags do before do properties.each do |property_str| resource_class.class_eval("property #{property_str}", __FILE__, __LINE__) end end instance_eval(&block) end end # Basic properties with_property ":bare_property" do it "can be set" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property).to eq 10 end it "emits a deprecation warning and does a get, if set to nil" do expect(resource.bare_property 10).to eq 10 expect { resource.bare_property nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(resource.bare_property nil).to eq 10 expect(resource.bare_property).to eq 10 end it "can be updated" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property 20).to eq 20 expect(resource.bare_property).to eq 20 end it "can be set with =" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property).to eq 10 end it "can be set to nil with =" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property = nil).to be_nil expect(resource.bare_property).to be_nil end it "can be updated with =" do expect(resource.bare_property 10).to eq 10 expect(resource.bare_property = 20).to eq 20 expect(resource.bare_property).to eq 20 end end with_property ":x, name_property: true" do context "and subclass" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) do subresource_class.new("blah") end context "with property :x on the subclass" do before do subresource_class.class_eval do property :x end end it "x is still name_property" do expect(subresource.x).to eq "blah" end end context "with property :x, name_attribute: false on the subclass" do before do subresource_class.class_eval do property :x, name_attribute: false end end it "x is no longer name_property" do expect(subresource.x).to be_nil end end context "with property :x, default: 10 on the subclass" do before do subresource_class.class_eval do property :x, default: 10 end end it "x is no longer name_property" do expect(subresource.x).to eq(10) end end end end with_property ":x, Integer" do context "and subclass" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) do subresource_class.new("blah") end it "x is inherited" do expect(subresource.x 10).to eq 10 expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 expect(subresource_class.properties[:x]).not_to be_nil end it "x's validation is inherited" do expect { subresource.x "ohno" }.to raise_error Chef::Exceptions::ValidationFailed end context "with property :y on the subclass" do before do subresource_class.class_eval do property :y end end it "x is still there" do expect(subresource.x 10).to eq 10 expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 expect(subresource_class.properties[:x]).not_to be_nil end it "y is there" do expect(subresource.y 10).to eq 10 expect(subresource.y).to eq 10 expect(subresource.y = 20).to eq 20 expect(subresource.y).to eq 20 expect(subresource_class.properties[:y]).not_to be_nil end it "y is not on the superclass" do expect { resource_class.y 10 }.to raise_error NoMethodError expect(resource_class.properties[:y]).to be_nil end end context "with property :x on the subclass" do before do subresource_class.class_eval do property :x end end it "x is still there" do expect(subresource.x 10).to eq 10 expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 expect(subresource_class.properties[:x]).not_to be_nil expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] end it "x's validation is inherited" do expect { subresource.x "ohno" }.to raise_error Chef::Exceptions::ValidationFailed end end context "with property :x, default: 80 on the subclass" do before do subresource_class.class_eval do property :x, default: 80 end end it "x is still there" do expect(subresource.x 10).to eq 10 expect(subresource.x).to eq 10 expect(subresource.x = 20).to eq 20 expect(subresource.x).to eq 20 expect(subresource_class.properties[:x]).not_to be_nil expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] end it "x defaults to 80" do expect(subresource.x).to eq 80 end it "x's validation is inherited" do expect { subresource.x "ohno" }.to raise_error Chef::Exceptions::ValidationFailed end end context "with property :x, String on the subclass" do before do subresource_class.class_eval do property :x, String end end it "x is still there" do expect(subresource.x "10").to eq "10" expect(subresource.x).to eq "10" expect(subresource.x = "20").to eq "20" expect(subresource.x).to eq "20" expect(subresource_class.properties[:x]).not_to be_nil expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x] end it "x's validation is overwritten" do expect { subresource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed expect(subresource.x "ohno").to eq "ohno" expect(subresource.x).to eq "ohno" end it "the superclass's validation for x is still there" do expect { resource.x "ohno" }.to raise_error Chef::Exceptions::ValidationFailed expect(resource.x 10).to eq 10 expect(resource.x).to eq 10 end end end end context "Chef::Resource::Property#reset_property" do it "when a resource is newly created, reset_property(:name) sets property to nil" do expect(resource.property_is_set?(:name)).to be_truthy resource.reset_property(:name) expect(resource.property_is_set?(:name)).to be_falsey expect(resource.name).to be_nil end it "when referencing an undefined property, reset_property(:x) raises an error" do expect { resource.reset_property(:x) }.to raise_error(ArgumentError) end with_property ":x" do it "when the resource is newly created, reset_property(:x) does nothing" do expect(resource.property_is_set?(:x)).to be_falsey resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to be_nil end it "when x is set, reset_property resets it" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to be_nil end end with_property ":x, Integer" do it "when the resource is newly created, reset_property(:x) does nothing" do expect(resource.property_is_set?(:x)).to be_falsey resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to be_nil end it "when x is set, reset_property resets it even though `nil` is technically invalid" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to be_nil end end with_property ":x, default: 10" do it "when the resource is newly created, reset_property(:x) does nothing" do expect(resource.property_is_set?(:x)).to be_falsey resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to eq 10 end it "when x is set, reset_property resets it and it returns the default" do resource.x 20 resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to eq 10 end end with_property ":x, default: lazy { 10 }" do it "when the resource is newly created, reset_property(:x) does nothing" do expect(resource.property_is_set?(:x)).to be_falsey resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to eq 10 end it "when x is set, reset_property resets it and it returns the default" do resource.x 20 resource.reset_property(:x) expect(resource.property_is_set?(:x)).to be_falsey expect(resource.x).to eq 10 end end end context "Chef::Resource::Property#property_is_set?" do it "when a resource is newly created, property_is_set?(:name) is true" do expect(resource.property_is_set?(:name)).to be_truthy end it "when referencing an undefined property, property_is_set?(:x) raises an error" do expect { resource.property_is_set?(:x) }.to raise_error(ArgumentError) end with_property ":x" do it "when the resource is newly created, property_is_set?(:x) is false" do expect(resource.property_is_set?(:x)).to be_falsey end it "when x is set, property_is_set?(:x) is true" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set with =, property_is_set?(:x) is true" do resource.x = 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set to a lazy value, property_is_set?(:x) is true" do resource.x lazy { 10 } expect(resource.property_is_set?(:x)).to be_truthy end it "when x is retrieved, property_is_set?(:x) is false" do resource.x expect(resource.property_is_set?(:x)).to be_falsey end end with_property ":x, default: 10" do it "when the resource is newly created, property_is_set?(:x) is false" do expect(resource.property_is_set?(:x)).to be_falsey end it "when x is set, property_is_set?(:x) is true" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set with =, property_is_set?(:x) is true" do resource.x = 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set to a lazy value, property_is_set?(:x) is true" do resource.x lazy { 10 } expect(resource.property_is_set?(:x)).to be_truthy end it "when x is retrieved, property_is_set?(:x) is false" do resource.x expect(resource.property_is_set?(:x)).to be_falsey end end with_property ":x, default: nil" do it "when the resource is newly created, property_is_set?(:x) is false" do expect(resource.property_is_set?(:x)).to be_falsey end it "when x is set, property_is_set?(:x) is true" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set with =, property_is_set?(:x) is true" do resource.x = 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set to a lazy value, property_is_set?(:x) is true" do resource.x lazy { 10 } expect(resource.property_is_set?(:x)).to be_truthy end it "when x is retrieved, property_is_set?(:x) is false" do resource.x expect(resource.property_is_set?(:x)).to be_falsey end end with_property ":x, default: lazy { 10 }" do it "when the resource is newly created, property_is_set?(:x) is false" do expect(resource.property_is_set?(:x)).to be_falsey end it "when x is set, property_is_set?(:x) is true" do resource.x 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is set with =, property_is_set?(:x) is true" do resource.x = 10 expect(resource.property_is_set?(:x)).to be_truthy end it "when x is retrieved, property_is_set?(:x) is false" do resource.x expect(resource.property_is_set?(:x)).to be_falsey end end end context "Chef::Resource::Property#default" do with_property ":x, default: 10" do it "when x is set, it returns its value" do expect(resource.x 20).to eq 20 expect(resource.property_is_set?(:x)).to be_truthy expect(resource.x).to eq 20 end it "when x is not set, it returns 10" do expect(resource.x).to eq 10 end it "when x is not set, it is not included in state" do expect(resource.state_for_resource_reporter).to eq({}) end it "when x is set to nil, it returns nil" do resource.instance_eval { @x = nil } expect(resource.x).to be_nil end context "With a subclass" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) { subresource_class.new("blah") } it "The default is inherited" do expect(subresource.x).to eq 10 end end end with_property ":x, default: 10, identity: true" do it "when x is not set, it is included in identity" do expect(resource.identity).to eq(10) end end with_property ":x, default: 1, identity: true", ":y, default: 2, identity: true" do it "when x is not set, it is still included in identity" do resource.y 20 expect(resource.identity).to eq(x: 1, y: 20) end end with_property ":x, default: nil" do it "when x is not set, it returns nil" do expect(resource.x).to be_nil end end with_property ":x" do it "when x is not set, it returns nil" do expect(resource.x).to be_nil end end context "hash default" do context "(deprecations allowed)" do before { Chef::Config[:treat_deprecation_warnings_as_errors] = false } with_property ":x, default: {}" do it "when x is not set, it returns {}" do expect(resource.x).to eq({}) end it "The same exact value is returned multiple times in a row" do value = resource.x expect(value).to eq({}) expect(resource.x.object_id).to eq(value.object_id) end it "Multiple instances of x receive the exact same value" do expect(resource.x.object_id).to eq(resource_class.new("blah2").x.object_id) end end end with_property ":x, default: lazy { {} }" do it "when x is not set, it returns {}" do expect(resource.x).to eq({}) end # it "The value is different each time it is called" do # value = resource.x # expect(value).to eq({}) # expect(resource.x.object_id).not_to eq(value.object_id) # end it "Multiple instances of x receive different values" do expect(resource.x.object_id).not_to eq(resource_class.new("blah2").x.object_id) end end end context "with a class with 'blah' as both class and instance methods" do before do resource_class.class_eval do def self.blah "class" end def blah "#{name}#{next_index}" end end end with_property ":x, default: lazy { blah }" do it "x is run in context of the instance" do expect(resource.x).to eq "blah1" end it "x is run in the context of each instance it is run in" do expect(resource.x).to eq "blah1" expect(resource_class.new("another").x).to eq "another2" # expect(resource.x).to eq "blah3" end end with_property ':x, default: lazy { |x| "#{blah}#{x.blah}" }' do it "x is run in context of the class (where it was defined) and passed the instance" do expect(resource.x).to eq "classblah1" end it "x is passed the value of each instance it is run in" do expect(resource.x).to eq "classblah1" expect(resource_class.new("another").x).to eq "classanother2" # expect(resource.x).to eq "classblah3" end end end context "validation of defaults" do it "When a class is declared with property :x, String, default: 10, a warning is emitted" do expect { resource_class.class_eval { property :x, String, default: 10 } }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Default value 10 is invalid for property x of resource chef_resource_property_spec_(\d+). In Chef 13 this will become an error: Property x must be one of: String! You passed 10./ end context "With property :x, String, default: 10" do before do Chef::Config[:treat_deprecation_warnings_as_errors] = false resource_class.class_eval { property :x, String, default: 10 } Chef::Config[:treat_deprecation_warnings_as_errors] = true end it "when x is set, no error is raised" do expect(resource.x "hi").to eq "hi" expect(resource.x).to eq "hi" end it "when x is retrieved, no validation error is raised" do expect(resource.x).to eq 10 end end with_property ":x, String, default: lazy { Namer.next_index }" do it "when the resource is created, no error is raised" do resource end it "when x is set, no error is raised" do expect(resource.x "hi").to eq "hi" expect(resource.x).to eq "hi" end it "when x is retrieved, an invalid default warning is emitted and the value is returned" do expect { resource.x }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Default value 1 is invalid for property x of resource chef_resource_property_spec_(\d+). In Chef 13 this will become an error: Property x must be one of: String! You passed 1./ expect(Namer.current_index).to eq 1 Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(resource.x).to eq 2 end end with_property ":x, default: lazy { Namer.next_index.to_s }, is: proc { |v| Namer.next_index; true }" do it "coercion and validation is only run the first time" do expect(resource.x).to eq "1" expect(Namer.current_index).to eq 2 expect(resource.x).to eq "1" expect(Namer.current_index).to eq 2 end end with_property ":x, default: lazy { Namer.next_index.to_s.freeze }, is: proc { |v| Namer.next_index; true }" do it "coercion and validation is run each time" do expect(resource.x).to eq "1" expect(Namer.current_index).to eq 2 expect(resource.x).to eq "3" expect(Namer.current_index).to eq 4 end end end context "coercion of defaults" do # Frozen default, non-frozen coerce with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do it "when the resource is created, the proc is not yet run" do resource expect(Namer.current_index).to eq 0 end it "when x is set, coercion is run" do expect(resource.x "hi").to eq "hi1" expect(resource.x).to eq "hi1" expect(Namer.current_index).to eq 1 end it "when x is retrieved, coercion is run exactly once" do expect(resource.x).to eq "101" expect(resource.x).to eq "101" expect(Namer.current_index).to eq 1 end end # Frozen default, frozen coerce with_property ':x, coerce: proc { |v| "#{v}#{next_index}".freeze }, default: 10' do it "when the resource is created, the proc is not yet run" do resource expect(Namer.current_index).to eq 0 end it "when x is set, coercion is run" do expect(resource.x "hi").to eq "hi1" expect(resource.x).to eq "hi1" expect(Namer.current_index).to eq 1 end it "when x is retrieved, coercion is run each time" do expect(resource.x).to eq "101" expect(resource.x).to eq "102" expect(Namer.current_index).to eq 2 end end # Frozen lazy default, non-frozen coerce with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "when the resource is created, the proc is not yet run" do resource expect(Namer.current_index).to eq 0 end it "when x is set, coercion is run" do expect(resource.x "hi").to eq "hi1" expect(resource.x).to eq "hi1" expect(Namer.current_index).to eq 1 end it "when x is retrieved, coercion is run exactly once" do expect(resource.x).to eq "101" expect(resource.x).to eq "101" expect(Namer.current_index).to eq 1 end end # Non-frozen lazy default, frozen coerce with_property ':x, coerce: proc { |v| "#{v}#{next_index}".freeze }, default: lazy { "10" }' do it "when the resource is created, the proc is not yet run" do resource expect(Namer.current_index).to eq 0 end it "when x is set, coercion is run" do expect(resource.x "hi").to eq "hi1" expect(resource.x).to eq "hi1" expect(Namer.current_index).to eq 1 end it "when x is retrieved, coercion is run each time" do expect(resource.x).to eq "101" expect(resource.x).to eq "102" expect(Namer.current_index).to eq 2 end end with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "coercion and validation is only run the first time x is retrieved" do expect(Namer.current_index).to eq 0 expect(resource.x).to eq "101" expect(Namer.current_index).to eq 2 expect(resource.x).to eq "101" expect(Namer.current_index).to eq 2 end end context "validation and coercion of defaults" do with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do it "when x is retrieved, it is coerced before validating and passes" do expect(resource.x).to eq "101" end end with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do it "when x is retrieved, it is coerced and emits an invalid default warning, but still returns the value" do expect { resource.x }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Default value 10 is invalid for property x of resource chef_resource_property_spec_(\d+). In Chef 13 this will become an error: Property x must be one of: Integer! You passed "101"./ Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(resource.x).to eq "102" end end with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "when x is retrieved, it is coerced before validating and passes" do expect(resource.x).to eq "101" end end with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "when x is retrieved, it is coerced and emits an invalid default warning; the value is still returned." do expect { resource.x }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Default value 10 is invalid for property x of resource chef_resource_property_spec_(\d+). In Chef 13 this will become an error: Property x must be one of: Integer! You passed "101"./ Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(resource.x).to eq "102" end end with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do it "coercion is only run the first time x is retrieved, and validation is run" do expect(Namer.current_index).to eq 0 expect(resource.x).to eq "101" expect(Namer.current_index).to eq 2 expect(resource.x).to eq "101" expect(Namer.current_index).to eq 2 end end end end end context "Chef::Resource#lazy" do with_property ":x" do it "setting x to a lazy value does not run it immediately" do resource.x lazy { Namer.next_index } expect(Namer.current_index).to eq 0 end it "you can set x to a lazy value in the instance" do resource.instance_eval do x lazy { Namer.next_index } end expect(resource.x).to eq 1 expect(Namer.current_index).to eq 1 end it "retrieving a lazy value pops it open" do resource.x lazy { Namer.next_index } expect(resource.x).to eq 1 expect(Namer.current_index).to eq 1 end it "retrieving a lazy value twice evaluates it twice" do resource.x lazy { Namer.next_index } expect(resource.x).to eq 1 expect(resource.x).to eq 2 expect(Namer.current_index).to eq 2 end it "setting the same lazy value on two different instances runs it on each instancee" do resource2 = resource_class.new("blah2") l = lazy { Namer.next_index } resource.x l resource2.x l expect(resource2.x).to eq 1 expect(resource.x).to eq 2 expect(resource2.x).to eq 3 end context "when the class has a class and instance method named blah" do before do resource_class.class_eval do def self.blah "class" end def blah "#{name}#{Namer.next_index}" end end end def blah "example" end # it "retrieving lazy { blah } gets the instance variable" do # resource.x lazy { blah } # expect(resource.x).to eq "blah1" # end # it "retrieving lazy { blah } from two different instances gets two different instance variables" do # resource2 = resource_class.new("another") # l = lazy { blah } # resource2.x l # resource.x l # expect(resource2.x).to eq "another1" # expect(resource.x).to eq "blah2" # expect(resource2.x).to eq "another3" # end it 'retrieving lazy { |x| "#{blah}#{x.blah}" } gets the example and instance variables' do resource.x lazy { |x| "#{blah}#{x.blah}" } expect(resource.x).to eq "exampleblah1" end it 'retrieving lazy { |x| "#{blah}#{x.blah}" } from two different instances gets two different instance variables' do resource2 = resource_class.new("another") l = lazy { |x| "#{blah}#{x.blah}" } resource2.x l resource.x l expect(resource2.x).to eq "exampleanother1" expect(resource.x).to eq "exampleblah2" expect(resource2.x).to eq "exampleanother3" end end end with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do it "lazy values are not coerced on set" do resource.x lazy { Namer.next_index } expect(Namer.current_index).to eq 0 end it "lazy values are coerced on get" do resource.x lazy { Namer.next_index } expect(resource.x).to eq "12" expect(Namer.current_index).to eq 2 end it "lazy values are coerced on each access" do resource.x lazy { Namer.next_index } expect(resource.x).to eq "12" expect(Namer.current_index).to eq 2 expect(resource.x).to eq "34" expect(Namer.current_index).to eq 4 end end with_property ":x, String" do it "lazy values are not validated on set" do resource.x lazy { Namer.next_index } expect(Namer.current_index).to eq 0 end it "lazy values are validated on get" do resource.x lazy { Namer.next_index } expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed expect(Namer.current_index).to eq 1 end end with_property ":x, is: proc { |v| Namer.next_index; true }" do it "lazy values are validated on each access" do resource.x lazy { Namer.next_index } expect(resource.x).to eq 1 expect(Namer.current_index).to eq 2 expect(resource.x).to eq 3 expect(Namer.current_index).to eq 4 end end with_property ':x, Integer, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do it "lazy values are not validated or coerced on set" do resource.x lazy { Namer.next_index } expect(Namer.current_index).to eq 0 end it "lazy values are coerced before being validated, which fails" do resource.x lazy { Namer.next_index } expect(Namer.current_index).to eq 0 expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed expect(Namer.current_index).to eq 2 end end with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }, is: proc { |v| Namer.next_index; true }' do it "lazy values are coerced and validated exactly once" do resource.x lazy { Namer.next_index } expect(resource.x).to eq "12" expect(Namer.current_index).to eq 3 expect(resource.x).to eq "45" expect(Namer.current_index).to eq 6 end end with_property ':x, String, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do it "lazy values are coerced before being validated, which succeeds" do resource.x lazy { Namer.next_index } expect(resource.x).to eq "12" expect(Namer.current_index).to eq 2 end end end context "Chef::Resource::Property#coerce" do with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do it "coercion runs on set" do expect(resource.x 10).to eq "101" expect(Namer.current_index).to eq 1 end it "does not emit a deprecation warning if set to nil" do # nil is never coerced expect(resource.x nil).to be_nil end it "coercion sets the value (and coercion does not run on get)" do expect(resource.x 10).to eq "101" expect(resource.x).to eq "101" expect(Namer.current_index).to eq 1 end it "coercion runs each time set happens" do expect(resource.x 10).to eq "101" expect(Namer.current_index).to eq 1 expect(resource.x 10).to eq "102" expect(Namer.current_index).to eq 2 end end with_property ":x, coerce: proc { |x| x }" do it "does not emit a deprecation warning if set to nil" do expect(resource.x nil).to be_nil end end with_property ':x, coerce: proc { |x| Namer.next_index; raise "hi" if x == 10; x }, is: proc { |x| Namer.next_index; x != 10 }' do it "failed coercion fails to set the value" do resource.x 20 expect(resource.x).to eq 20 expect(Namer.current_index).to eq 2 expect { resource.x 10 }.to raise_error "hi" expect(resource.x).to eq 20 expect(Namer.current_index).to eq 3 end it "validation does not run if coercion fails" do expect { resource.x 10 }.to raise_error "hi" expect(Namer.current_index).to eq 1 end end end context "Chef::Resource::Property validation" do with_property ":x, is: proc { |v| Namer.next_index; v.is_a?(Integer) }" do it "validation runs on set" do expect(resource.x 10).to eq 10 expect(Namer.current_index).to eq 1 end it "validation sets the value (and validation does not run on get)" do expect(resource.x 10).to eq 10 expect(resource.x).to eq 10 expect(Namer.current_index).to eq 1 end it "validation runs each time set happens" do expect(resource.x 10).to eq 10 expect(Namer.current_index).to eq 1 expect(resource.x 10).to eq 10 expect(Namer.current_index).to eq 2 end it "failed validation fails to set the value" do expect(resource.x 10).to eq 10 expect(Namer.current_index).to eq 1 expect { resource.x "blah" }.to raise_error Chef::Exceptions::ValidationFailed expect(resource.x).to eq 10 expect(Namer.current_index).to eq 2 end end end %w{name_attribute name_property}.each do |name| context "Chef::Resource::Property##{name}" do with_property ":x, #{name}: true" do it "defaults x to resource.name" do expect(resource.x).to eq "blah" end it "does not pick up resource.name if set" do expect(resource.x 10).to eq 10 expect(resource.x).to eq 10 end it "binds to the latest resource.name when run" do resource.name "foo" expect(resource.x).to eq "foo" end it "caches resource.name" do expect(resource.x).to eq "blah" resource.name "foo" expect(resource.x).to eq "blah" end end with_property ":x, #{name}: false" do it "defaults to nil" do expect(resource.x).to be_nil end end with_property ":x, #{name}: nil" do it "defaults to nil" do expect(resource.x).to be_nil end end context "default ordering deprecation warnings" do it "emits a deprecation warning for property :x, default: 10, #{name}: true" do expect { resource_class.property :x, :default => 10, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(default\) will be obeyed./ end it "emits a deprecation warning for property :x, default: nil, #{name}: true" do expect { resource_class.property :x, :default => nil, name.to_sym => true }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(name_property\) will be obeyed./ end it "emits a deprecation warning for property :x, #{name}: true, default: 10" do expect { resource_class.property :x, name.to_sym => true, :default => 10 }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(name_property\) will be obeyed./ end it "emits a deprecation warning for property :x, #{name}: true, default: nil" do expect { resource_class.property :x, name.to_sym => true, :default => nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Cannot specify both default and name_property together on property x of resource chef_resource_property_spec_(\d+). Only one \(name_property\) will be obeyed./ end end context "default ordering" do before { Chef::Config[:treat_deprecation_warnings_as_errors] = false } with_property ":x, default: 10, #{name}: true" do it "chooses default over #{name}" do expect(resource.x).to eq 10 end end with_property ":x, default: nil, #{name}: true" do it "chooses #{name} over default" do expect(resource.x).to eq "blah" end end with_property ":x, #{name}: true, default: 10" do it "chooses #{name} over default" do expect(resource.x).to eq "blah" end end with_property ":x, #{name}: true, default: nil" do it "chooses #{name} over default" do expect(resource.x).to eq "blah" end end end context "default ordering when #{name} is nil" do with_property ":x, #{name}: nil, default: 10" do it "chooses default" do expect(resource.x).to eq 10 end end with_property ":x, default: 10, #{name}: nil" do it "chooses default" do expect(resource.x).to eq 10 end end end context "default ordering when #{name} is false" do with_property ":x, #{name}: false, default: 10" do it "chooses default" do expect(resource.x).to eq 10 end end with_property ":x, default: 10, #{name}: nil" do it "chooses default" do expect(resource.x).to eq 10 end end end end end it "raises an error if both name_property and name_attribute are specified" do expect { resource_class.property :x, :name_property => false, :name_attribute => 1 }.to raise_error ArgumentError, /Cannot specify both name_property and name_attribute together on property x of resource chef_resource_property_spec_(\d+)./ expect { resource_class.property :x, :name_property => false, :name_attribute => nil }.to raise_error ArgumentError, /Cannot specify both name_property and name_attribute together on property x of resource chef_resource_property_spec_(\d+)./ expect { resource_class.property :x, :name_property => false, :name_attribute => false }.to raise_error ArgumentError, /Cannot specify both name_property and name_attribute together on property x of resource chef_resource_property_spec_(\d+)./ expect { resource_class.property :x, :name_property => true, :name_attribute => true }.to raise_error ArgumentError, /Cannot specify both name_property and name_attribute together on property x of resource chef_resource_property_spec_(\d+)./ end context "property_type" do it "property_types validate their defaults" do expect do module ::PropertySpecPropertyTypes include Chef::Mixin::Properties property_type(is: [:a, :b], default: :c) end end.to raise_error(Chef::Exceptions::DeprecatedFeatureError, /Default value :c is invalid for property ./) expect do module ::PropertySpecPropertyTypes include Chef::Mixin::Properties property_type(is: [:a, :b], default: :b) end end.not_to raise_error end context "With property_type ABType (is: [:a, :b]) and CDType (is: [:c, :d])" do before :all do module ::PropertySpecPropertyTypes include Chef::Mixin::Properties ABType = property_type(is: [:a, :b]) CDType = property_type(is: [:c, :d]) end end with_property ":x, [PropertySpecPropertyTypes::ABType, nil, PropertySpecPropertyTypes::CDType]" do it "The property can be set to nil without triggering a warning" do expect(resource.x nil).to be_nil expect(resource.x).to be_nil end it "The property can be set to :a" do expect(resource.x :a).to eq(:a) expect(resource.x).to eq(:a) end it "The property can be set to :c" do expect(resource.x :c).to eq(:c) expect(resource.x).to eq(:c) end it "The property cannot be set to :z" do expect { resource.x :z }.to raise_error(Chef::Exceptions::ValidationFailed, /Property x must be one of/) end end with_property ":x, [nil, PropertySpecPropertyTypes::ABType, PropertySpecPropertyTypes::CDType]" do it "The property can be set to nil without triggering a warning" do expect(resource.x nil).to be_nil expect(resource.x).to be_nil end it "The property can be set to :a" do expect(resource.x :a).to eq(:a) expect(resource.x).to eq(:a) end it "The property can be set to :c" do expect(resource.x :c).to eq(:c) expect(resource.x).to eq(:c) end it "The property cannot be set to :z" do expect { resource.x :z }.to raise_error(Chef::Exceptions::ValidationFailed, /Property x must be one of/) end end with_property ":x, [PropertySpecPropertyTypes::ABType, nil], default: nil" do it "The value defaults to nil" do expect(resource.x).to be_nil end end with_property ":x, [PropertySpecPropertyTypes::ABType, nil], default: lazy { nil }" do it "The value defaults to nil" do expect(resource.x).to be_nil end end end end context "with a custom property type" do class CustomPropertyType < Chef::Property end with_property ":x, CustomPropertyType.new" do it "creates x with the given type" do expect(resource_class.properties[:x]).to be_kind_of(CustomPropertyType) end context "and a subclass" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) do subresource_class.new("blah") end context "with property :x, default: 10 on the subclass" do before do subresource_class.class_eval do property :x, default: 10 end end it "x has the given type and default on the subclass" do expect(subresource_class.properties[:x]).to be_kind_of(CustomPropertyType) expect(subresource_class.properties[:x].default).to eq(10) end it "x does not have the default on the superclass" do expect(resource_class.properties[:x]).to be_kind_of(CustomPropertyType) expect(resource_class.properties[:x].default).to be_nil end end end end with_property ":x, CustomPropertyType.new, default: 10" do it "passes the default to the custom property type" do expect(resource_class.properties[:x]).to be_kind_of(CustomPropertyType) expect(resource_class.properties[:x].default).to eq(10) end end end end chef-12.14.60/spec/unit/provider/000077500000000000000000000000001276456504500164335ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/apt_repository_spec.rb000066400000000000000000000152371276456504500230650ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 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" APT_KEY_FINGER = <<-EOF /etc/apt/trusted.gpg -------------------- pub 1024D/437D05B5 2004-09-12 Key fingerprint = 6302 39CC 130E 1A7F D81A 27B1 4097 6EAF 437D 05B5 uid Ubuntu Archive Automatic Signing Key sub 2048g/79164387 2004-09-12 pub 1024D/FBB75451 2004-12-30 Key fingerprint = C598 6B4F 1257 FFA8 6632 CBA7 4618 1433 FBB7 5451 uid Ubuntu CD Image Automatic Signing Key pub 4096R/C0B21F32 2012-05-11 Key fingerprint = 790B C727 7767 219C 42C8 6F93 3B4F E6AC C0B2 1F32 uid Ubuntu Archive Automatic Signing Key (2012) pub 4096R/EFE21092 2012-05-11 Key fingerprint = 8439 38DF 228D 22F7 B374 2BC0 D94A A3F0 EFE2 1092 uid Ubuntu CD Image Automatic Signing Key (2012) EOF GPG_FINGER = <<-EOF pub 1024D/02A818DD 2009-04-22 Cloudera Apt Repository Key fingerprint = F36A 89E3 3CC1 BD0F 7107 9007 3275 74EE 02A8 18DD sub 2048g/D1CA74A1 2009-04-22 EOF describe Chef::Provider::AptRepository do let(:new_resource) { Chef::Resource::AptRepository.new("multiverse") } let(:shellout_env) { { env: { "LANG" => "en_US", "LANGUAGE" => "en_US" } } } let(:provider) do node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) Chef::Provider::AptRepository.new(new_resource, run_context) end let(:apt_key_finger) do r = double("Mixlib::ShellOut", stdout: APT_KEY_FINGER, exitstatus: 0, live_stream: true) allow(r).to receive(:run_command) r end let(:gpg_finger) do r = double("Mixlib::ShellOut", stdout: GPG_FINGER, exitstatus: 0, live_stream: true) allow(r).to receive(:run_command) r end let(:apt_fingerprints) do %w{630239CC130E1A7FD81A27B140976EAF437D05B5 C5986B4F1257FFA86632CBA746181433FBB75451 790BC7277767219C42C86F933B4FE6ACC0B21F32 843938DF228D22F7B3742BC0D94AA3F0EFE21092} end it "responds to load_current_resource" do expect(provider).to respond_to(:load_current_resource) end describe "#is_key_id?" do it "should detect a key" do expect(provider.is_key_id?("A4FF2279")).to be_truthy end it "should detect a key with a hex signifier" do expect(provider.is_key_id?("0xA4FF2279")).to be_truthy end it "should reject a key with the wrong length" do expect(provider.is_key_id?("4FF2279")).to be_falsey end it "should reject a key with non-hex characters" do expect(provider.is_key_id?("A4KF2279")).to be_falsey end end describe "#extract_fingerprints_from_cmd" do before do expect(Mixlib::ShellOut).to receive(:new).and_return(apt_key_finger) end it "should run the desired command" do expect(apt_key_finger).to receive(:run_command) provider.extract_fingerprints_from_cmd("apt-key finger") end it "should return a list of key fingerprints" do expect(provider.extract_fingerprints_from_cmd("apt-key finger")).to eql(apt_fingerprints) end end describe "#no_new_keys?" do before do allow(provider).to receive(:extract_fingerprints_from_cmd).with("apt-key finger").and_return(apt_fingerprints) end let(:file) { "/tmp/remote-gpg-keyfile" } it "should match a set of keys" do allow(provider).to receive(:extract_fingerprints_from_cmd).with("gpg --with-fingerprint #{file}").and_return(Array(apt_fingerprints.first)) expect(provider.no_new_keys?(file)).to be_truthy end it "should notice missing keys" do allow(provider).to receive(:extract_fingerprints_from_cmd).with("gpg --with-fingerprint #{file}").and_return(%w{ F36A89E33CC1BD0F71079007327574EE02A818DD }) expect(provider.no_new_keys?(file)).to be_falsey end end describe "#install_ppa_key" do let(:url) { "https://launchpad.net/api/1.0/~chef/+archive/main" } let(:key) { "C5986B4F1257FFA86632CBA746181433FBB75451" } it "should get a key" do simples = double("HTTP") allow(simples).to receive(:get).and_return("\"#{key}\"") expect(Chef::HTTP::Simple).to receive(:new).with(url).and_return(simples) expect(provider).to receive(:install_key_from_keyserver).with(key, "keyserver.ubuntu.com") provider.install_ppa_key("chef", "main") end end describe "#make_ppa_url" do it "should ignore non-ppa repositories" do expect(provider.make_ppa_url("some_string")).to be_nil end it "should create a URL" do expect(provider).to receive(:install_ppa_key).with("chef", "main").and_return(true) expect(provider.make_ppa_url("ppa:chef/main")).to eql("http://ppa.launchpad.net/chef/main/ubuntu") end end describe "#build_repo" do it "should create a repository string" do target = %Q{deb "http://test/uri" unstable main\n} expect(provider.build_repo("http://test/uri", "unstable", "main", false, nil)).to eql(target) end it "should create a repository string with no distribution" do target = %Q{deb "http://test/uri" main\n} expect(provider.build_repo("http://test/uri", nil, "main", false, nil)).to eql(target) end it "should create a repository string with source" do target = %Q{deb "http://test/uri" unstable main\ndeb-src "http://test/uri" unstable main\n} expect(provider.build_repo("http://test/uri", "unstable", "main", false, nil, true)).to eql(target) end it "should create a repository string with options" do target = %Q{deb [trusted=yes] "http://test/uri" unstable main\n} expect(provider.build_repo("http://test/uri", "unstable", "main", true, nil)).to eql(target) end it "should handle a ppa repo" do target = %Q{deb "http://ppa.launchpad.net/chef/main/ubuntu" unstable main\n} expect(provider).to receive(:make_ppa_url).with("ppa:chef/main").and_return("http://ppa.launchpad.net/chef/main/ubuntu") expect(provider.build_repo("ppa:chef/main", "unstable", "main", false, nil)).to eql(target) end end end chef-12.14.60/spec/unit/provider/apt_update_spec.rb000066400000000000000000000105011276456504500221150ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 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::AptUpdate do let(:new_resource) { Chef::Resource::AptUpdate.new("update") } let(:config_dir) { Dir.mktmpdir("apt_update_apt_conf_d") } let(:config_file) { File.join(config_dir, "15update-stamp") } let(:stamp_dir) { Dir.mktmpdir("apt_update_periodic") } before do stub_const("Chef::Provider::AptUpdate::APT_CONF_DIR", config_dir) stub_const("Chef::Provider::AptUpdate::STAMP_DIR", stamp_dir) end let(:provider) do node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) Chef::Provider::AptUpdate.new(new_resource, run_context) end it "responds to load_current_resource" do expect(provider).to respond_to(:load_current_resource) end context "when the apt config directory does not exist" do before do FileUtils.rmdir config_dir expect(File.exist?(config_dir)).to be false allow_any_instance_of(Chef::Provider::Execute).to receive(:shell_out!).with("apt-get -q update", anything()) end it "should create the directory" do provider.run_action(:update) expect(File.exist?(config_dir)).to be true expect(File.directory?(config_dir)).to be true end it "should create the config file" do provider.run_action(:update) expect(File.exist?(config_file)).to be true expect(File.read(config_file)).to match(/^APT::Update.*#{stamp_dir}/) end end describe "#action_update" do it "should update the apt cache" do provider.load_current_resource expect_any_instance_of(Chef::Provider::Execute).to receive(:shell_out!).with("apt-get -q update", anything()) provider.run_action(:update) expect(new_resource).to be_updated_by_last_action end end describe "#action_periodic" do before do allow(File).to receive(:exist?) allow(File).to receive(:exist?).with(Dir.tmpdir).and_return(true) expect(File).to receive(:exist?).with("#{stamp_dir}/update-success-stamp").and_return(true) end it "should run if the time stamp is old" do expect(File).to receive(:mtime).with("#{stamp_dir}/update-success-stamp").and_return(Time.now - 86_500) expect_any_instance_of(Chef::Provider::Execute).to receive(:shell_out!).with("apt-get -q update", anything()) provider.run_action(:periodic) expect(new_resource).to be_updated_by_last_action end it "should not run if the time stamp is new" do expect(File).to receive(:mtime).with("#{stamp_dir}/update-success-stamp").and_return(Time.now) expect_any_instance_of(Chef::Provider::Execute).not_to receive(:shell_out!).with("apt-get -q update", anything()) provider.run_action(:periodic) expect(new_resource).to_not be_updated_by_last_action end context "with a different frequency" do before do new_resource.frequency(400) end it "should run if the time stamp is old" do expect(File).to receive(:mtime).with("#{stamp_dir}/update-success-stamp").and_return(Time.now - 500) expect_any_instance_of(Chef::Provider::Execute).to receive(:shell_out!).with("apt-get -q update", anything()) provider.run_action(:periodic) expect(new_resource).to be_updated_by_last_action end it "should not run if the time stamp is new" do expect(File).to receive(:mtime).with("#{stamp_dir}/update-success-stamp").and_return(Time.now - 300) expect_any_instance_of(Chef::Provider::Execute).not_to receive(:shell_out!).with("apt-get -q update", anything()) provider.run_action(:periodic) expect(new_resource).to_not be_updated_by_last_action end end end end chef-12.14.60/spec/unit/provider/breakpoint_spec.rb000066400000000000000000000036011276456504500221300ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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::Breakpoint do before do @resource = Chef::Resource::Breakpoint.new @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @collection = double("resource collection") allow(@run_context).to receive(:resource_collection).and_return(@collection) @provider = Chef::Provider::Breakpoint.new(@resource, @run_context) end it "responds to load_current_resource" do expect(@provider).to respond_to(:load_current_resource) end it "gets the iterator from @collection and pauses it" do allow(Shell).to receive(:running?).and_return(true) @iterator = double("stepable_iterator") allow(@collection).to receive(:iterator).and_return(@iterator) expect(@iterator).to receive(:pause) @provider.action_break expect(@resource).to be_updated end it "doesn't pause the iterator if chef-shell isn't running" do allow(Shell).to receive(:running?).and_return(false) @iterator = double("stepable_iterator") allow(@collection).to receive(:iterator).and_return(@iterator) expect(@iterator).not_to receive(:pause) @provider.action_break end end chef-12.14.60/spec/unit/provider/cookbook_file/000077500000000000000000000000001276456504500212405ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/cookbook_file/content_spec.rb000066400000000000000000000027111276456504500242520ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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::CookbookFile::Content do let(:new_resource) { double("Chef::Resource::CookbookFile (new)", :cookbook_name => "apache2", :cookbook => "apache2") } let(:content) do @run_context = double("Chef::RunContext") @current_resource = double("Chef::Resource::CookbookFile (current)") Chef::Provider::CookbookFile::Content.new(new_resource, @current_resource, @run_context) end it "prefers the explicit cookbook name on the resource to the implicit one" do allow(new_resource).to receive(:cookbook).and_return("nginx") expect(content.send(:resource_cookbook)).to eq("nginx") end it "falls back to the implicit cookbook name on the resource" do expect(content.send(:resource_cookbook)).to eq("apache2") end end chef-12.14.60/spec/unit/provider/cookbook_file_spec.rb000066400000000000000000000034611276456504500226030ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Lamont Granquist () # Copyright:: Copyright 2009-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 "ostruct" require "support/shared/unit/provider/file" describe Chef::Provider::CookbookFile do let(:node) { double("Chef::Node") } let(:events) { double("Chef::Events").as_null_object } # mock all the methods let(:run_context) { double("Chef::RunContext", :node => node, :events => events) } let(:enclosing_directory) do canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates"))) end let(:resource_path) do canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt"))) end # Subject let(:provider) do provider = described_class.new(resource, run_context) allow(provider).to receive(:content).and_return(content) provider end let(:resource) do resource = Chef::Resource::CookbookFile.new("seattle", @run_context) resource.path(resource_path) resource.cookbook_name = "apache2" resource end let(:content) do content = double("Chef::Provider::CookbookFile::Content") end it_behaves_like Chef::Provider::File it_behaves_like "a file provider with source field" end chef-12.14.60/spec/unit/provider/cron/000077500000000000000000000000001276456504500173745ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/cron/unix_spec.rb000066400000000000000000000110661276456504500217220ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Author:: Toomas Pelberg (toomasp@gmx.net) # Copyright:: Copyright 2009-2016, Bryan McLellan # Copyright:: Copyright 2010-2016, Toomas Pelberg # 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::Cron::Unix do subject(:provider) { Chef::Provider::Cron::Unix.new(new_resource, run_context) } let(:username) { "root" } let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) do Chef::Resource::Cron.new("cronhole some stuff").tap do |resource| resource.user username resource.minute "30" resource.command "/bin/true" end end let(:status) { double("Process::Status", :exitstatus => exitstatus) } let(:exitstatus) { 0 } let(:shell_out) { double("Mixlib::ShellOut", :status => status, :stdout => stdout, :stderr => stderr) } it "is a Chef::Provider:Cron" do expect(provider).to be_a(Chef::Provider::Cron) end describe "read_crontab" do let(:stderr) { "" } let(:stdout) do String.new(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: something else * 5 * * * /bin/true # Another comment CRONTAB end before do allow(Chef::Log).to receive(:debug) allow(shell_out).to receive(:format_for_exception).and_return("formatted command output") allow(provider).to receive(:shell_out).with("/usr/bin/crontab -l", :user => username).and_return(shell_out) end it "should call crontab -l with the user" do provider.send(:read_crontab) expect(provider).to have_received(:shell_out).with("/usr/bin/crontab -l", :user => username) end it "should return the contents of the crontab" do crontab = provider.send(:read_crontab) expect(crontab).to eq(stdout) end context "when the user has no crontab" do let(:exitstatus) { 1 } it "should return nil if the user has no crontab" do expect(provider.send(:read_crontab)).to be_nil end it "logs the crontab output to debug" do provider.send(:read_crontab) expect(Chef::Log).to have_received(:debug).with("formatted command output") end end context "when any other error occurs" do let (:exitstatus) { 2 } it "should raise an exception if another error occurs" do expect do provider.send(:read_crontab) end.to raise_error(Chef::Exceptions::Cron, "Error determining state of #{new_resource.name}, exit: 2") end it "logs the crontab output to debug" do provider.send(:read_crontab) rescue nil expect(Chef::Log).to have_received(:debug).with("formatted command output") end end end describe "write_crontab" do let(:stdout) { "" } let(:stderr) { "" } let(:tempfile) { double("foo", :path => "/tmp/foo", :close => true) } before do expect(Tempfile).to receive(:new).and_return(tempfile) expect(tempfile).to receive(:flush) expect(tempfile).to receive(:chmod).with(420) expect(tempfile).to receive(:close!) allow(tempfile).to receive(:<<) allow(provider).to receive(:shell_out).with("/usr/bin/crontab #{tempfile.path}", :user => username).and_return(shell_out) end it "should call crontab for the user" do provider.send(:write_crontab, "Foo") expect(provider).to have_received(:shell_out).with("/usr/bin/crontab #{tempfile.path}", :user => username) end it "should call crontab with a file containing the crontab" do provider.send(:write_crontab, "Foo\n# wibble\n wah!!") expect(tempfile).to have_received(:<<).with("Foo\n# wibble\n wah!!") end context "when writing the crontab fails" do let(:exitstatus) { 1 } it "should raise an exception if the command returns non-zero" do expect do provider.send(:write_crontab, "Foo") end.to raise_error(Chef::Exceptions::Cron, /Error updating state of #{new_resource.name}, exit: 1/) end end end end chef-12.14.60/spec/unit/provider/cron_spec.rb000066400000000000000000000733061276456504500207440ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Copyright:: Copyright 2009-2016, Bryan McLellan # 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::Cron do describe "when with special time string" do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Cron.new("cronhole some stuff", @run_context) @new_resource.user "root" @new_resource.minute "30" @new_resource.command "/bin/true" @new_resource.time :reboot @provider = Chef::Provider::Cron.new(@new_resource, @run_context) end context "with a matching entry in the user's crontab" do before :each do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff @reboot /bin/true param1 param2 # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB end it "should set cron_exists" do @provider.load_current_resource expect(@provider.cron_exists).to eq(true) expect(@provider.cron_empty).to eq(false) end it "should pull the details out of the cron line" do cron = @provider.load_current_resource expect(cron.time).to eq(:reboot) expect(cron.command).to eq("/bin/true param1 param2") end it "should pull env vars out" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff MAILTO=foo@example.com SHELL=/bin/foosh PATH=/bin:/foo HOME=/home/foo @reboot /bin/true param1 param2 # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB cron = @provider.load_current_resource expect(cron.mailto).to eq("foo@example.com") expect(cron.shell).to eq("/bin/foosh") expect(cron.path).to eq("/bin:/foo") expect(cron.home).to eq("/home/foo") expect(cron.time).to eq(:reboot) expect(cron.command).to eq("/bin/true param1 param2") end it "should parse and load generic and standard environment variables from cron entry" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) # Chef Name: cronhole some stuff MAILTO=warn@example.com TEST=lol FLAG=1 @reboot /bin/true CRONTAB cron = @provider.load_current_resource expect(cron.mailto).to eq("warn@example.com") expect(cron.environment).to eq({ "TEST" => "lol", "FLAG" => "1" }) end it "should not break with variables that match the cron resource internals" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) # Chef Name: cronhole some stuff MINUTE=40 REBOOT=midnight TEST=lol ENVIRONMENT=production @reboot /bin/true CRONTAB cron = @provider.load_current_resource expect(cron.time).to eq(:reboot) expect(cron.environment).to eq({ "MINUTE" => "40", "REBOOT" => "midnight", "TEST" => "lol", "ENVIRONMENT" => "production" }) end it "should report the match" do expect(Chef::Log).to receive(:debug).with("Found cron '#{@new_resource.name}'") @provider.load_current_resource end describe "action_create" do before :each do allow(@provider).to receive(:write_crontab) allow(@provider).to receive(:read_crontab).and_return(nil) end context "when there is no existing crontab" do before :each do @provider.cron_exists = false @provider.cron_empty = true end it "should create a crontab with the entry" do expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) # Chef Name: cronhole some stuff @reboot /bin/true ENDCRON @provider.run_action(:create) end end end end end before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Cron.new("cronhole some stuff", @run_context) @new_resource.user "root" @new_resource.minute "30" @new_resource.command "/bin/true" @provider = Chef::Provider::Cron.new(@new_resource, @run_context) end describe "when examining the current system state" do context "with no crontab for the user" do before :each do allow(@provider).to receive(:read_crontab).and_return(nil) end it "should set cron_empty" do @provider.load_current_resource expect(@provider.cron_empty).to eq(true) expect(@provider.cron_exists).to eq(false) end it "should report an empty crontab" do expect(Chef::Log).to receive(:debug).with("Cron empty for '#{@new_resource.user}'") @provider.load_current_resource end end context "with no matching entry in the user's crontab" do before :each do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: something else * 5 * * * /bin/true # Another comment CRONTAB end it "should not set cron_exists or cron_empty" do @provider.load_current_resource expect(@provider.cron_exists).to eq(false) expect(@provider.cron_empty).to eq(false) end it "should report no entry found" do expect(Chef::Log).to receive(:debug).with("Cron '#{@new_resource.name}' not found") @provider.load_current_resource end it "should not fail if there's an existing cron with a numerical argument" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) # Chef Name: foo[bar] (baz) 21 */4 * * * some_prog 1234567 CRONTAB expect do @provider.load_current_resource end.not_to raise_error end end context "with a matching entry in the user's crontab" do before :each do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff * 5 * 1 * /bin/true param1 param2 # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB end it "should set cron_exists" do @provider.load_current_resource expect(@provider.cron_exists).to eq(true) expect(@provider.cron_empty).to eq(false) end it "should pull the details out of the cron line" do cron = @provider.load_current_resource expect(cron.minute).to eq("*") expect(cron.hour).to eq("5") expect(cron.day).to eq("*") expect(cron.month).to eq("1") expect(cron.weekday).to eq("*") expect(cron.time).to eq(nil) expect(cron.command).to eq("/bin/true param1 param2") end it "should pull env vars out" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff MAILTO=foo@example.com SHELL=/bin/foosh PATH=/bin:/foo HOME=/home/foo * 5 * 1 * /bin/true param1 param2 # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB cron = @provider.load_current_resource expect(cron.mailto).to eq("foo@example.com") expect(cron.shell).to eq("/bin/foosh") expect(cron.path).to eq("/bin:/foo") expect(cron.home).to eq("/home/foo") expect(cron.minute).to eq("*") expect(cron.hour).to eq("5") expect(cron.day).to eq("*") expect(cron.month).to eq("1") expect(cron.weekday).to eq("*") expect(cron.time).to eq(nil) expect(cron.command).to eq("/bin/true param1 param2") end it "should parse and load generic and standard environment variables from cron entry" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) # Chef Name: cronhole some stuff MAILTO=warn@example.com TEST=lol FLAG=1 * 5 * * * /bin/true CRONTAB cron = @provider.load_current_resource expect(cron.mailto).to eq("warn@example.com") expect(cron.environment).to eq({ "TEST" => "lol", "FLAG" => "1" }) end it "should not break with variabels that match the cron resource internals" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) # Chef Name: cronhole some stuff MINUTE=40 HOUR=midnight TEST=lol ENVIRONMENT=production * 5 * * * /bin/true CRONTAB cron = @provider.load_current_resource expect(cron.minute).to eq("*") expect(cron.hour).to eq("5") expect(cron.environment).to eq({ "MINUTE" => "40", "HOUR" => "midnight", "TEST" => "lol", "ENVIRONMENT" => "production" }) end it "should report the match" do expect(Chef::Log).to receive(:debug).with("Found cron '#{@new_resource.name}'") @provider.load_current_resource end end context "with a matching entry in the user's crontab using month names and weekday names (#CHEF-3178)" do before :each do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff * 5 * Jan Mon /bin/true param1 param2 # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB end it "should set cron_exists" do @provider.load_current_resource expect(@provider.cron_exists).to eq(true) expect(@provider.cron_empty).to eq(false) end it "should pull the details out of the cron line" do cron = @provider.load_current_resource expect(cron.minute).to eq("*") expect(cron.hour).to eq("5") expect(cron.day).to eq("*") expect(cron.month).to eq("Jan") expect(cron.weekday).to eq("Mon") expect(cron.command).to eq("/bin/true param1 param2") end it "should report the match" do expect(Chef::Log).to receive(:debug).with("Found cron '#{@new_resource.name}'") @provider.load_current_resource end end context "with a matching entry without a crontab line" do it "should set cron_exists and leave current_resource values at defaults" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff CRONTAB cron = @provider.load_current_resource expect(@provider.cron_exists).to eq(true) expect(cron.minute).to eq("*") expect(cron.hour).to eq("*") expect(cron.day).to eq("*") expect(cron.month).to eq("*") expect(cron.weekday).to eq("*") expect(cron.time).to eq(nil) expect(cron.command).to eq(nil) end it "should not pick up a commented out crontab line" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff #* 5 * 1 * /bin/true param1 param2 CRONTAB cron = @provider.load_current_resource expect(@provider.cron_exists).to eq(true) expect(cron.minute).to eq("*") expect(cron.hour).to eq("*") expect(cron.day).to eq("*") expect(cron.month).to eq("*") expect(cron.weekday).to eq("*") expect(cron.time).to eq(nil) expect(cron.command).to eq(nil) end it "should not pick up a later crontab entry" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff #* 5 * 1 * /bin/true param1 param2 # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB cron = @provider.load_current_resource expect(@provider.cron_exists).to eq(true) expect(cron.minute).to eq("*") expect(cron.hour).to eq("*") expect(cron.day).to eq("*") expect(cron.month).to eq("*") expect(cron.weekday).to eq("*") expect(cron.time).to eq(nil) expect(cron.command).to eq(nil) end end end describe "cron_different?" do before :each do @current_resource = Chef::Resource::Cron.new("cronhole some stuff") @current_resource.user "root" @current_resource.minute "30" @current_resource.command "/bin/true" @provider.current_resource = @current_resource end [:minute, :hour, :day, :month, :weekday, :command, :mailto, :path, :shell, :home].each do |attribute| it "should return true if #{attribute} doesn't match" do @new_resource.send(attribute, "something_else") expect(@provider.cron_different?).to eql(true) end end it "should return true if special time string doesn't match" do @new_resource.send(:time, :reboot) expect(@provider.cron_different?).to eql(true) end it "should return true if environment doesn't match" do @new_resource.environment "FOO" => "something_else" expect(@provider.cron_different?).to eql(true) end it "should return true if mailto doesn't match" do @current_resource.mailto "foo@bar.com" @new_resource.mailto(nil) expect(@provider.cron_different?).to eql(true) end it "should return false if the objects are identical" do expect(@provider.cron_different?).to eq(false) end end describe "action_create" do before :each do allow(@provider).to receive(:write_crontab) allow(@provider).to receive(:read_crontab).and_return(nil) end context "when there is no existing crontab" do before :each do @provider.cron_exists = false @provider.cron_empty = true end it "should create a crontab with the entry" do expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) # Chef Name: cronhole some stuff 30 * * * * /bin/true ENDCRON @provider.run_action(:create) end it "should include env variables that are set" do @new_resource.mailto "foo@example.com" @new_resource.path "/usr/bin:/my/custom/path" @new_resource.shell "/bin/foosh" @new_resource.home "/home/foo" @new_resource.environment "TEST" => "LOL" expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) # Chef Name: cronhole some stuff MAILTO="foo@example.com" PATH="/usr/bin:/my/custom/path" SHELL="/bin/foosh" HOME="/home/foo" TEST=LOL 30 * * * * /bin/true ENDCRON @provider.run_action(:create) end it "should mark the resource as updated" do @provider.run_action(:create) expect(@new_resource).to be_updated_by_last_action end it "should log the action" do expect(Chef::Log).to receive(:info).with("cron[cronhole some stuff] added crontab entry") @provider.run_action(:create) end end context "when there is a crontab with no matching section" do before :each do @provider.cron_exists = false allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB end it "should add the entry to the crontab" do expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command # Chef Name: something else 2 * 1 * * /bin/false # Another comment # Chef Name: cronhole some stuff 30 * * * * /bin/true ENDCRON @provider.run_action(:create) end it "should include env variables that are set" do @new_resource.mailto "foo@example.com" @new_resource.path "/usr/bin:/my/custom/path" @new_resource.shell "/bin/foosh" @new_resource.home "/home/foo" @new_resource.environment "TEST" => "LOL" expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command # Chef Name: something else 2 * 1 * * /bin/false # Another comment # Chef Name: cronhole some stuff MAILTO="foo@example.com" PATH="/usr/bin:/my/custom/path" SHELL="/bin/foosh" HOME="/home/foo" TEST=LOL 30 * * * * /bin/true ENDCRON @provider.run_action(:create) end it "should mark the resource as updated" do @provider.run_action(:create) expect(@new_resource).to be_updated_by_last_action end it "should log the action" do expect(Chef::Log).to receive(:info).with("cron[cronhole some stuff] added crontab entry") @provider.run_action(:create) end end context "when there is a crontab with a matching but different section" do before :each do @provider.cron_exists = true allow(@provider).to receive(:cron_different?).and_return(true) allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff 30 * * 3 * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB end it "should update the crontab entry" do expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff 30 * * * * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment ENDCRON @provider.run_action(:create) end it "should include env variables that are set" do @new_resource.mailto "foo@example.com" @new_resource.path "/usr/bin:/my/custom/path" @new_resource.shell "/bin/foosh" @new_resource.home "/home/foo" @new_resource.environment "TEST" => "LOL" expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff MAILTO="foo@example.com" PATH="/usr/bin:/my/custom/path" SHELL="/bin/foosh" HOME="/home/foo" TEST=LOL 30 * * * * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment ENDCRON @provider.run_action(:create) end it "should mark the resource as updated" do @provider.run_action(:create) expect(@new_resource).to be_updated_by_last_action end it "should log the action" do expect(Chef::Log).to receive(:info).with("cron[cronhole some stuff] updated crontab entry") @provider.run_action(:create) end end context "when there is a crontab with a matching section with no crontab line in it" do before :each do @provider.cron_exists = true allow(@provider).to receive(:cron_different?).and_return(true) end it "should add the crontab to the entry" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff CRONTAB expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff 30 * * * * /bin/true ENDCRON @provider.run_action(:create) end it "should not blat any following entries" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff #30 * * * * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff 30 * * * * /bin/true #30 * * * * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment ENDCRON @provider.run_action(:create) end it "should handle env vars with no crontab" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff MAILTO=bar@example.com PATH=/usr/bin:/my/custom/path SHELL=/bin/barsh HOME=/home/foo # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB @new_resource.mailto "foo@example.com" @new_resource.path "/usr/bin:/my/custom/path" @new_resource.shell "/bin/foosh" @new_resource.home "/home/foo" expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff MAILTO="foo@example.com" PATH="/usr/bin:/my/custom/path" SHELL="/bin/foosh" HOME="/home/foo" 30 * * * * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment ENDCRON @provider.run_action(:create) end end context "when there is a crontab with a matching and identical section" do before :each do @provider.cron_exists = true allow(@provider).to receive(:cron_different?).and_return(false) allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff * 5 * * * /bin/true # Another comment CRONTAB end it "should not update the crontab" do expect(@provider).not_to receive(:write_crontab) @provider.run_action(:create) end it "should not mark the resource as updated" do @provider.run_action(:create) expect(@new_resource).not_to be_updated_by_last_action end it "should log nothing changed" do expect(Chef::Log).to receive(:debug).with("Found cron '#{@new_resource.name}'") expect(Chef::Log).to receive(:debug).with("Skipping existing cron entry '#{@new_resource.name}'") @provider.run_action(:create) end end end describe "action_delete" do before :each do allow(@provider).to receive(:write_crontab) allow(@provider).to receive(:read_crontab).and_return(nil) end context "when the user's crontab has no matching section" do before :each do @provider.cron_exists = false end it "should do nothing" do expect(@provider).not_to receive(:write_crontab) expect(Chef::Log).not_to receive(:info) @provider.run_action(:delete) end it "should not mark the resource as updated" do @provider.run_action(:delete) expect(@new_resource).not_to be_updated_by_last_action end end context "when the user has a crontab with a matching section" do before :each do @provider.cron_exists = true allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff 30 * * 3 * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB end it "should remove the entry" do expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command # Chef Name: something else 2 * 1 * * /bin/false # Another comment ENDCRON @provider.run_action(:delete) end it "should remove any env vars with the entry" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff MAILTO=foo@example.com FOO=test 30 * * 3 * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command # Chef Name: something else 2 * 1 * * /bin/false # Another comment ENDCRON @provider.run_action(:delete) end it "should mark the resource as updated" do @provider.run_action(:delete) expect(@new_resource).to be_updated_by_last_action end it "should log the action" do expect(Chef::Log).to receive(:info).with("#{@new_resource} deleted crontab entry") @provider.run_action(:delete) end end context "when the crontab has a matching section with no crontab line" do before :each do @provider.cron_exists = true end it "should remove the section" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff CRONTAB expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command ENDCRON @provider.run_action(:delete) end it "should not blat following sections" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff #30 * * 3 * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command #30 * * 3 * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment ENDCRON @provider.run_action(:delete) end it "should remove any envvars with the section" do allow(@provider).to receive(:read_crontab).and_return(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: cronhole some stuff MAILTO=foo@example.com #30 * * 3 * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment CRONTAB expect(@provider).to receive(:write_crontab).with(<<-ENDCRON) 0 2 * * * /some/other/command #30 * * 3 * /bin/true # Chef Name: something else 2 * 1 * * /bin/false # Another comment ENDCRON @provider.run_action(:delete) end end end describe "read_crontab" do before :each do @status = double("Status", :exitstatus => 0) @stdout = StringIO.new(<<-CRONTAB) 0 2 * * * /some/other/command # Chef Name: something else * 5 * * * /bin/true # Another comment CRONTAB allow(@provider).to receive(:popen4).and_yield(1234, StringIO.new, @stdout, StringIO.new).and_return(@status) end it "should call crontab -l with the user" do expect(@provider).to receive(:popen4).with("crontab -l -u #{@new_resource.user}").and_return(@status) @provider.send(:read_crontab) end it "should return the contents of the crontab" do crontab = @provider.send(:read_crontab) expect(crontab).to eq <<-CRONTAB 0 2 * * * /some/other/command # Chef Name: something else * 5 * * * /bin/true # Another comment CRONTAB end it "should return nil if the user has no crontab" do status = double("Status", :exitstatus => 1) allow(@provider).to receive(:popen4).and_return(status) expect(@provider.send(:read_crontab)).to eq(nil) end it "should raise an exception if another error occurs" do status = double("Status", :exitstatus => 2) allow(@provider).to receive(:popen4).and_return(status) expect do @provider.send(:read_crontab) end.to raise_error(Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: 2") end end describe "write_crontab" do before :each do @status = double("Status", :exitstatus => 0) @stdin = StringIO.new allow(@provider).to receive(:popen4).and_yield(1234, @stdin, StringIO.new, StringIO.new).and_return(@status) end it "should call crontab for the user" do expect(@provider).to receive(:popen4).with("crontab -u #{@new_resource.user} -", :waitlast => true).and_return(@status) @provider.send(:write_crontab, "Foo") end it "should write the given string to the crontab command" do @provider.send(:write_crontab, "Foo\n# wibble\n wah!!") expect(@stdin.string).to eq("Foo\n# wibble\n wah!!") end it "should raise an exception if the command returns non-zero" do allow(@status).to receive(:exitstatus).and_return(1) expect do @provider.send(:write_crontab, "Foo") end.to raise_error(Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: 1") end it "should raise an exception if the command die's and parent tries to write" do class WriteErrPipe def write(str) raise Errno::EPIPE, "Test" end end allow(@status).to receive(:exitstatus).and_return(1) allow(@provider).to receive(:popen4).and_yield(1234, WriteErrPipe.new, StringIO.new, StringIO.new).and_return(@status) expect(Chef::Log).to receive(:debug).with("Broken pipe - Test") expect do @provider.send(:write_crontab, "Foo") end.to raise_error(Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: 1") end end describe "weekday_in_crontab" do context "when weekday is symbol" do it "should return weekday in crontab format" do @new_resource.weekday :wednesday expect(@provider.send(:weekday_in_crontab)).to eq("3") end it "should raise an error with an unknown weekday" do expect { @new_resource.weekday :caturday }.to raise_error(RangeError) end end context "when weekday is a number in a string" do it "should return the string" do @new_resource.weekday "3" expect(@provider.send(:weekday_in_crontab)).to eq("3") end it "should raise an error with an out of range number" do expect { @new_resource.weekday "-1" }.to raise_error(RangeError) end end context "when weekday is string with the name of the week" do it "should return the string" do @new_resource.weekday "mon" expect(@provider.send(:weekday_in_crontab)).to eq("mon") end end context "when weekday is an integer" do it "should return the integer" do @new_resource.weekday 1 expect(@provider.send(:weekday_in_crontab)).to eq("1") end it "should raise an error with an out of range integer" do expect { @new_resource.weekday 45 }.to raise_error(RangeError) end end end end chef-12.14.60/spec/unit/provider/deploy/000077500000000000000000000000001276456504500177275ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/deploy/revision_spec.rb000066400000000000000000000105211276456504500231230ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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::Deploy::Revision do before do allow(ChefConfig).to receive(:windows?) { false } @temp_dir = Dir.mktmpdir Chef::Config[:file_cache_path] = @temp_dir @resource = Chef::Resource::Deploy.new("/my/deploy/dir") @resource.revision("8a3195bf3efa246f743c5dfa83683201880f935c") @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @provider = Chef::Provider::Deploy::Revision.new(@resource, @run_context) @provider.load_current_resource @runner = double("runnah") allow(Chef::Runner).to receive(:new).and_return(@runner) @expected_release_dir = "/my/deploy/dir/releases/8a3195bf3efa246f743c5dfa83683201880f935c" end after do # Make sure we don't keep any state in our tests FileUtils.rm_rf @temp_dir if File.directory?( @temp_dir ) end it "uses the resolved revision from the SCM as the release slug" do allow(@provider.scm_provider).to receive(:revision_slug).and_return("uglySlugly") expect(@provider.send(:release_slug)).to eq("uglySlugly") end it "deploys to a dir named after the revision" do expect(@provider.release_path).to eq(@expected_release_dir) end it "stores the release dir in the file cache in the cleanup step" do allow(FileUtils).to receive(:mkdir_p) allow(FileUtils).to receive(:cp_r) @provider.cleanup! allow(@provider).to receive(:release_slug).and_return("73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2") @provider.load_current_resource @provider.cleanup! second_release = "/my/deploy/dir/releases/73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2" expect(@provider.all_releases).to eq([@expected_release_dir, second_release]) end it "removes a release from the file cache when it's used again in another release and append it to the end" do allow(FileUtils).to receive(:mkdir_p) allow(FileUtils).to receive(:cp_r) @provider.cleanup! allow(@provider).to receive(:release_slug).and_return("73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2") @provider.load_current_resource @provider.cleanup! second_release = "/my/deploy/dir/releases/73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2" expect(@provider.all_releases).to eq([@expected_release_dir, second_release]) @provider.cleanup! allow(@provider).to receive(:release_slug).and_return("8a3195bf3efa246f743c5dfa83683201880f935c") @provider.load_current_resource @provider.cleanup! expect(@provider.all_releases).to eq([second_release, @expected_release_dir]) end it "removes a release from the file cache when it's deleted by :cleanup!" do release_paths = %w{first second third fourth fifth}.map do |release_name| "/my/deploy/dir/releases/#{release_name}" end release_paths.each do |release_path| @provider.send(:release_created, release_path) end expect(@provider.all_releases).to eq(release_paths) allow(FileUtils).to receive(:rm_rf) @provider.cleanup! expected_release_paths = (%w{second third fourth fifth} << @resource.revision).map do |release_name| "/my/deploy/dir/releases/#{release_name}" end expect(@provider.all_releases).to eq(expected_release_paths) end it "regenerates the file cache if it's not available" do oldest = "/my/deploy/dir/releases/oldest" latest = "/my/deploy/dir/releases/latest" expect(Dir).to receive(:glob).with("/my/deploy/dir/releases/*").and_return([latest, oldest]) expect(::File).to receive(:ctime).with(oldest).and_return(Time.now - 10) expect(::File).to receive(:ctime).with(latest).and_return(Time.now - 1) expect(@provider.all_releases).to eq([oldest, latest]) end end chef-12.14.60/spec/unit/provider/deploy/timestamped_spec.rb000066400000000000000000000027061276456504500236070ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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::Deploy::Timestamped do before do @release_time = Time.utc( 2004, 8, 15, 16, 23, 42) allow(Time).to receive(:now).and_return(@release_time) @expected_release_dir = "/my/deploy/dir/releases/20040815162342" @resource = Chef::Resource::Deploy.new("/my/deploy/dir") @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @timestamped_deploy = Chef::Provider::Deploy::Timestamped.new(@resource, @run_context) @runner = double("runnah") allow(Chef::Runner).to receive(:new).and_return(@runner) end it "gives a timestamp for release_slug" do expect(@timestamped_deploy.send(:release_slug)).to eq("20040815162342") end end chef-12.14.60/spec/unit/provider/deploy_spec.rb000066400000000000000000000733711276456504500213010ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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::Deploy do before do allow(ChefConfig).to receive(:windows?) { false } @release_time = Time.utc( 2004, 8, 15, 16, 23, 42) allow(Time).to receive(:now).and_return(@release_time) @expected_release_dir = "/my/deploy/dir/releases/20040815162342" @resource = Chef::Resource::Deploy.new("/my/deploy/dir") @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @provider = Chef::Provider::Deploy.new(@resource, @run_context) allow(@provider).to receive(:release_slug) allow(@provider).to receive(:release_path).and_return(@expected_release_dir) end it "loads scm resource" do expect(@provider.scm_provider).to receive(:load_current_resource) @provider.load_current_resource end it "supports :deploy and :rollback actions" do expect(@provider).to respond_to(:action_deploy) expect(@provider).to respond_to(:action_rollback) end context "when the deploy resource has a timeout attribute" do let(:ten_seconds) { 10 } before { @resource.timeout(ten_seconds) } it "relays the timeout to the scm resource" do expect(@provider.scm_provider.new_resource.timeout).to eq(ten_seconds) end end context "when the deploy resource has no timeout attribute" do it "should not set a timeout on the scm resource" do expect(@provider.scm_provider.new_resource.timeout).to be_nil end end context "when the deploy_to dir does not exist yet" do before do expect(FileUtils).to receive(:mkdir_p).with(@resource.deploy_to).ordered expect(FileUtils).to receive(:mkdir_p).with(@resource.shared_path).ordered allow(::File).to receive(:directory?).and_return(false) allow(@provider).to receive(:symlink) allow(@provider).to receive(:migrate) allow(@provider).to receive(:copy_cached_repo) end it "creates deploy_to dir" do expect(::Dir).to receive(:chdir).with(@expected_release_dir).exactly(4).times expect(@provider).to receive(:enforce_ownership).twice allow(@provider).to receive(:update_cached_repo) @provider.deploy end end it "does not create deploy_to dir if it exists" do allow(::File).to receive(:directory?).and_return(true) expect(::Dir).to receive(:chdir).with(@expected_release_dir).exactly(4).times expect(FileUtils).not_to receive(:mkdir_p).with(@resource.deploy_to) expect(FileUtils).not_to receive(:mkdir_p).with(@resource.shared_path) expect(@provider).to receive(:enforce_ownership).twice allow(@provider).to receive(:copy_cached_repo) allow(@provider).to receive(:update_cached_repo) allow(@provider).to receive(:symlink) allow(@provider).to receive(:migrate) @provider.deploy end it "ensures the deploy_to dir ownership after the verfication that it exists" do expect(::Dir).to receive(:chdir).with(@expected_release_dir).exactly(4).times expect(@provider).to receive(:verify_directories_exist).ordered expect(@provider).to receive(:enforce_ownership).ordered allow(@provider).to receive(:copy_cached_repo) allow(@provider).to receive(:update_cached_repo) allow(@provider).to receive(:install_gems) expect(@provider).to receive(:enforce_ownership).ordered allow(@provider).to receive(:enforce_ownership) allow(@provider).to receive(:symlink) allow(@provider).to receive(:migrate) @provider.deploy end it "updates and copies the repo, then does a migrate, symlink, restart, restart, cleanup on deploy" do allow(FileUtils).to receive(:mkdir_p).with("/my/deploy/dir") allow(FileUtils).to receive(:mkdir_p).with("/my/deploy/dir/shared") expect(@provider).to receive(:enforce_ownership).twice expect(@provider).to receive(:update_cached_repo) expect(@provider).to receive(:copy_cached_repo) expect(@provider).to receive(:install_gems) expect(@provider).to receive(:callback).with(:before_migrate, nil) expect(@provider).to receive(:migrate) expect(@provider).to receive(:callback).with(:before_symlink, nil) expect(@provider).to receive(:symlink) expect(@provider).to receive(:callback).with(:before_restart, nil) expect(@provider).to receive(:restart) expect(@provider).to receive(:callback).with(:after_restart, nil) expect(@provider).to receive(:cleanup!) @provider.deploy end it "should not deploy if there is already a deploy at release_path, and it is the current release" do allow(@provider).to receive(:all_releases).and_return([@expected_release_dir]) allow(@provider).to receive(:current_release?).with(@expected_release_dir).and_return(true) expect(@provider).not_to receive(:deploy) @provider.run_action(:deploy) end it "should call action_rollback if there is already a deploy of this revision at release_path, and it is not the current release" do allow(@provider).to receive(:all_releases).and_return([@expected_release_dir, "102021"]) allow(@provider).to receive(:current_release?).with(@expected_release_dir).and_return(false) expect(@provider).to receive(:rollback_to).with(@expected_release_dir) expect(@provider).to receive(:current_release?) @provider.run_action(:deploy) end it "calls deploy when deploying a new release" do allow(@provider).to receive(:all_releases).and_return([]) expect(@provider).to receive(:deploy) @provider.run_action(:deploy) end it "runs action svn_force_export when new_resource.svn_force_export is true" do @resource.svn_force_export true expect(@provider.scm_provider).to receive(:run_action).with(:force_export) @provider.update_cached_repo end it "Removes the old release before deploying when force deploying over it" do allow(@provider).to receive(:all_releases).and_return([@expected_release_dir]) expect(FileUtils).to receive(:rm_rf).with(@expected_release_dir) expect(@provider).to receive(:deploy) @provider.run_action(:force_deploy) end it "deploys as normal when force deploying and there's no prior release at the same path" do allow(@provider).to receive(:all_releases).and_return([]) expect(@provider).to receive(:deploy) @provider.run_action(:force_deploy) end it "dont care by default if error happens on deploy" do allow(@provider).to receive(:all_releases).and_return(["previous_release"]) allow(@provider).to receive(:deploy) { raise "Unexpected error" } allow(@provider).to receive(:previous_release_path).and_return("previous_release") expect(@provider).not_to receive(:rollback) expect do @provider.run_action(:deploy) end.to raise_exception(RuntimeError, "Unexpected error") end it "rollbacks to previous release if error happens on deploy" do @resource.rollback_on_error true allow(@provider).to receive(:all_releases).and_return(["previous_release"]) allow(@provider).to receive(:deploy) { raise "Unexpected error" } allow(@provider).to receive(:previous_release_path).and_return("previous_release") expect(@provider).to receive(:rollback) expect do @provider.run_action(:deploy) end.to raise_exception(RuntimeError, "Unexpected error") end describe "on systems without broken Dir.glob results" do it "sets the release path to the penultimate release when one is not specified, symlinks, and rm's the last release on rollback" do allow(@provider).to receive(:release_path).and_return("/my/deploy/dir/releases/3") all_releases = ["/my/deploy/dir/releases/1", "/my/deploy/dir/releases/2", "/my/deploy/dir/releases/3", "/my/deploy/dir/releases/4", "/my/deploy/dir/releases/5"] allow(Dir).to receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) expect(@provider).to receive(:symlink) expect(FileUtils).to receive(:rm_rf).with("/my/deploy/dir/releases/4") expect(FileUtils).to receive(:rm_rf).with("/my/deploy/dir/releases/5") @provider.run_action(:rollback) expect(@provider.release_path).to eql("/my/deploy/dir/releases/3") expect(@provider.shared_path).to eql("/my/deploy/dir/shared") end it "sets the release path to the specified release, symlinks, and rm's any newer releases on rollback" do allow(@provider).to receive(:release_path).and_call_original all_releases = ["/my/deploy/dir/releases/20040815162342", "/my/deploy/dir/releases/20040700000000", "/my/deploy/dir/releases/20040600000000", "/my/deploy/dir/releases/20040500000000"].sort! allow(Dir).to receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) expect(@provider).to receive(:symlink) expect(FileUtils).to receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342") @provider.run_action(:rollback) expect(@provider.release_path).to eql("/my/deploy/dir/releases/20040700000000") expect(@provider.shared_path).to eql("/my/deploy/dir/shared") end it "sets the release path to the penultimate release, symlinks, and rm's the last release on rollback" do allow(@provider).to receive(:release_path).and_call_original all_releases = [ "/my/deploy/dir/releases/20040815162342", "/my/deploy/dir/releases/20040700000000", "/my/deploy/dir/releases/20040600000000", "/my/deploy/dir/releases/20040500000000"] allow(Dir).to receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) expect(@provider).to receive(:symlink) expect(FileUtils).to receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342") @provider.run_action(:rollback) expect(@provider.release_path).to eql("/my/deploy/dir/releases/20040700000000") expect(@provider.shared_path).to eql("/my/deploy/dir/shared") end describe "if there are no releases to fallback to" do it "an exception is raised when there is only 1 release" do #@provider.unstub(:release_path) -- unstub the release path on top to feed our own release path all_releases = [ "/my/deploy/dir/releases/20040815162342"] allow(Dir).to receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) #@provider.should_receive(:symlink) #FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342") #@provider.run_action(:rollback) #@provider.release_path.should eql(NIL) -- no check needed since assertions will fail expect do @provider.run_action(:rollback) end.to raise_exception(RuntimeError, "There is no release to rollback to!") end it "an exception is raised when there are no releases" do all_releases = [] allow(Dir).to receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) expect do @provider.run_action(:rollback) end.to raise_exception(RuntimeError, "There is no release to rollback to!") end end end describe "CHEF-628: on systems with broken Dir.glob results" do it "sets the release path to the penultimate release, symlinks, and rm's the last release on rollback" do allow(@provider).to receive(:release_path).and_call_original all_releases = [ "/my/deploy/dir/releases/20040500000000", "/my/deploy/dir/releases/20040600000000", "/my/deploy/dir/releases/20040700000000", "/my/deploy/dir/releases/20040815162342" ] allow(Dir).to receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) expect(@provider).to receive(:symlink) expect(FileUtils).to receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342") @provider.run_action(:rollback) expect(@provider.release_path).to eql("/my/deploy/dir/releases/20040700000000") expect(@provider.shared_path).to eql("/my/deploy/dir/shared") end end it "raises a runtime error when there's no release to rollback to" do all_releases = [] allow(Dir).to receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) expect { @provider.run_action(:rollback) }.to raise_error(RuntimeError) end it "runs the new resource collection in the runner during a callback" do @runner = double("Runner") allow(Chef::Runner).to receive(:new).and_return(@runner) expect(@runner).to receive(:converge) callback_code = Proc.new { :noop } @provider.callback(:whatevs, callback_code) end it "loads callback files from the release/ dir if the file exists" do foo_callback = @expected_release_dir + "/deploy/foo.rb" expect(::File).to receive(:exist?).with(foo_callback).once.and_return(true) expect(::Dir).to receive(:chdir).with(@expected_release_dir).and_yield expect(@provider).to receive(:from_file).with(foo_callback) @provider.callback(:foo, "deploy/foo.rb") end it "raises a runtime error if a callback file is explicitly specified but does not exist" do baz_callback = "/deploy/baz.rb" expect(::File).to receive(:exist?).with("#{@expected_release_dir}/#{baz_callback}").and_return(false) @resource.before_migrate baz_callback @provider.define_resource_requirements @provider.action = :deploy expect { @provider.process_resource_requirements }.to raise_error(RuntimeError) end it "runs a default callback if the callback code is nil" do bar_callback = @expected_release_dir + "/deploy/bar.rb" expect(::File).to receive(:exist?).with(bar_callback).and_return(true) expect(::Dir).to receive(:chdir).with(@expected_release_dir).and_yield expect(@provider).to receive(:from_file).with(bar_callback) @provider.callback(:bar, nil) end it "skips an eval callback if the file doesn't exist" do barbaz_callback = @expected_release_dir + "/deploy/barbaz.rb" expect(::File).to receive(:exist?).with(barbaz_callback).and_return(false) expect(::Dir).to receive(:chdir).with(@expected_release_dir).and_yield expect(@provider).not_to receive(:from_file) @provider.callback(:barbaz, nil) end # CHEF-3449 #converge_by is called in #recipe_eval and must happen in sequence # with the other calls to #converge_by to keep the train on the tracks it "evaluates a callback file before the corresponding step" do expect(@provider).to receive(:verify_directories_exist) expect(@provider).to receive(:update_cached_repo) expect(@provider).to receive(:enforce_ownership) expect(@provider).to receive(:copy_cached_repo) expect(@provider).to receive(:install_gems) expect(@provider).to receive(:enforce_ownership) expect(@provider).to receive(:converge_by).ordered # before_migrate expect(@provider).to receive(:migrate).ordered expect(@provider).to receive(:converge_by).ordered # before_symlink expect(@provider).to receive(:symlink).ordered expect(@provider).to receive(:converge_by).ordered # before_restart expect(@provider).to receive(:restart).ordered expect(@provider).to receive(:converge_by).ordered # after_restart expect(@provider).to receive(:cleanup!) @provider.deploy end it "gets a SCM provider as specified by its resource" do expect(@provider.scm_provider).to be_an_instance_of(Chef::Provider::Git) expect(@provider.scm_provider.new_resource.destination).to eql("/my/deploy/dir/shared/cached-copy") end it "syncs the cached copy of the repo" do expect(@provider.scm_provider).to receive(:run_action).with(:sync) @provider.update_cached_repo end it "makes a copy of the cached repo in releases dir" do expect(FileUtils).to receive(:mkdir_p).with("/my/deploy/dir/releases") expect(FileUtils).to receive(:cp_r).with("/my/deploy/dir/shared/cached-copy/.", @expected_release_dir, :preserve => true) @provider.copy_cached_repo end it "calls the internal callback :release_created when cleaning up the releases" do allow(FileUtils).to receive(:mkdir_p) allow(FileUtils).to receive(:cp_r) expect(@provider).to receive(:release_created) @provider.cleanup! end it "chowns the whole release dir to user and group specified in the resource" do @resource.user "foo" @resource.group "bar" expect(FileUtils).to receive(:chown_R).with("foo", "bar", "/my/deploy/dir", { :force => true }) @provider.enforce_ownership end it "skips the migration when resource.migrate => false but runs symlinks before migration" do @resource.migrate false expect(@provider).not_to receive :shell_out! expect(@provider).to receive :run_symlinks_before_migrate @provider.migrate end it "links the database.yml and runs resource.migration_command when resource.migrate #=> true" do @resource.migrate true @resource.migration_command "migration_foo" @resource.user "deployNinja" @resource.group "deployNinjas" @resource.environment "RAILS_ENV" => "production" expect(FileUtils).to receive(:ln_sf).with("/my/deploy/dir/shared/config/database.yml", @expected_release_dir + "/config/database.yml") expect(@provider).to receive(:enforce_ownership) allow(STDOUT).to receive(:tty?).and_return(true) allow(Chef::Log).to receive(:info?).and_return(true) expect(@provider).to receive(:shell_out!).with("migration_foo", :cwd => @expected_release_dir, :user => "deployNinja", :group => "deployNinjas", :log_level => :info, :live_stream => STDOUT, :log_tag => "deploy[/my/deploy/dir]", :environment => { "RAILS_ENV" => "production" }) @provider.migrate end it "purges the current release's /log /tmp/pids/ and /public/system directories" do expect(FileUtils).to receive(:rm_rf).with(@expected_release_dir + "/log") expect(FileUtils).to receive(:rm_rf).with(@expected_release_dir + "/tmp/pids") expect(FileUtils).to receive(:rm_rf).with(@expected_release_dir + "/public/system") @provider.purge_tempfiles_from_current_release end it "symlinks temporary files and logs from the shared dir into the current release" do allow(FileUtils).to receive(:mkdir_p).with(@resource.shared_path + "/system") allow(FileUtils).to receive(:mkdir_p).with(@resource.shared_path + "/pids") allow(FileUtils).to receive(:mkdir_p).with(@resource.shared_path + "/log") expect(FileUtils).to receive(:mkdir_p).with(@expected_release_dir + "/tmp") expect(FileUtils).to receive(:mkdir_p).with(@expected_release_dir + "/public") expect(FileUtils).to receive(:mkdir_p).with(@expected_release_dir + "/config") expect(FileUtils).to receive(:ln_sf).with("/my/deploy/dir/shared/system", @expected_release_dir + "/public/system") expect(FileUtils).to receive(:ln_sf).with("/my/deploy/dir/shared/pids", @expected_release_dir + "/tmp/pids") expect(FileUtils).to receive(:ln_sf).with("/my/deploy/dir/shared/log", @expected_release_dir + "/log") expect(FileUtils).to receive(:ln_sf).with("/my/deploy/dir/shared/config/database.yml", @expected_release_dir + "/config/database.yml") expect(@provider).to receive(:enforce_ownership) @provider.link_tempfiles_to_current_release end it "symlinks the current release dir into production" do expect(FileUtils).to receive(:rm_f).with("/my/deploy/dir/current") expect(FileUtils).to receive(:ln_sf).with(@expected_release_dir, "/my/deploy/dir/current") expect(@provider).to receive(:enforce_ownership) @provider.link_current_release_to_production end context "with a customized app layout" do before do @resource.purge_before_symlink(%w{foo bar}) @resource.create_dirs_before_symlink(%w{baz qux}) @resource.symlinks "foo/bar" => "foo/bar", "baz" => "qux/baz" @resource.symlink_before_migrate "radiohead/in_rainbows.yml" => "awesome" end it "purges the purge_before_symlink directories" do expect(FileUtils).to receive(:rm_rf).with(@expected_release_dir + "/foo") expect(FileUtils).to receive(:rm_rf).with(@expected_release_dir + "/bar") @provider.purge_tempfiles_from_current_release end it "symlinks files from the shared directory to the current release directory" do expect(FileUtils).to receive(:mkdir_p).with(@expected_release_dir + "/baz") expect(FileUtils).to receive(:mkdir_p).with(@expected_release_dir + "/qux") allow(FileUtils).to receive(:mkdir_p).with(@resource.shared_path + "/foo/bar") allow(FileUtils).to receive(:mkdir_p).with(@resource.shared_path + "/baz") expect(FileUtils).to receive(:ln_sf).with("/my/deploy/dir/shared/foo/bar", @expected_release_dir + "/foo/bar") expect(FileUtils).to receive(:ln_sf).with("/my/deploy/dir/shared/baz", @expected_release_dir + "/qux/baz") expect(FileUtils).to receive(:ln_sf).with("/my/deploy/dir/shared/radiohead/in_rainbows.yml", @expected_release_dir + "/awesome") expect(@provider).to receive(:enforce_ownership) @provider.link_tempfiles_to_current_release end end it "does nothing for restart if restart_command is empty" do expect(@provider).not_to receive(:shell_out!) @provider.restart end it "runs the restart command in the current application dir when the resource has a restart_command" do @resource.restart_command "restartcmd" expect(@provider).to receive(:shell_out!).with("restartcmd", :cwd => "/my/deploy/dir/current", :log_tag => "deploy[/my/deploy/dir]", :log_level => :debug) @provider.restart end it "lists all available releases" do all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000", "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000"].sort! expect(Dir).to receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) expect(@provider.all_releases).to eql(all_releases) end it "removes all but the 5 newest releases" do all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000", "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000", "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000", "/my/deploy/dir/20040200000000", "/my/deploy/dir/20040100000000"].sort! allow(@provider).to receive(:all_releases).and_return(all_releases) expect(FileUtils).to receive(:rm_rf).with("/my/deploy/dir/20040100000000") expect(FileUtils).to receive(:rm_rf).with("/my/deploy/dir/20040200000000") expect(FileUtils).to receive(:rm_rf).with("/my/deploy/dir/20040300000000") @provider.cleanup! end it "removes all but a certain number of releases when the resource has a keep_releases" do @resource.keep_releases 7 all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000", "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000", "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000", "/my/deploy/dir/20040200000000", "/my/deploy/dir/20040100000000"].sort! allow(@provider).to receive(:all_releases).and_return(all_releases) expect(FileUtils).to receive(:rm_rf).with("/my/deploy/dir/20040100000000") @provider.cleanup! end it "fires a callback for :release_deleted when deleting an old release" do all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000", "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000", "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000"].sort! allow(@provider).to receive(:all_releases).and_return(all_releases) allow(FileUtils).to receive(:rm_rf) expect(@provider).to receive(:release_deleted).with("/my/deploy/dir/20040300000000") @provider.cleanup! end it "puts resource.to_hash in @configuration for backwards compat with capistano-esque deploy hooks" do expect(@provider.instance_variable_get(:@configuration)).to eq(@resource.to_hash) end it "sets @configuration[:environment] to the value of RAILS_ENV for backwards compat reasons" do resource = Chef::Resource::Deploy.new("/my/deploy/dir") resource.environment "production" provider = Chef::Provider::Deploy.new(resource, @run_context) expect(provider.instance_variable_get(:@configuration)[:environment]).to eql("production") end it "shouldn't give a no method error on migrate if the environment is nil" do allow(@provider).to receive(:enforce_ownership) allow(@provider).to receive(:run_symlinks_before_migrate) allow(@provider).to receive(:shell_out!) @provider.migrate end context "using inline recipes for callbacks" do it "runs an inline recipe with the provided block for :callback_name == {:recipe => &block} " do snitch = nil recipe_code = Proc.new { snitch = 42 } #@provider.should_receive(:instance_eval).with(&recipe_code) @provider.callback(:whateverz, recipe_code) expect(snitch).to eq(42) end it "loads a recipe file from the specified path and from_file evals it" do expect(::File).to receive(:exist?).with(@expected_release_dir + "/chefz/foobar_callback.rb").once.and_return(true) expect(::Dir).to receive(:chdir).with(@expected_release_dir).and_yield expect(@provider).to receive(:from_file).with(@expected_release_dir + "/chefz/foobar_callback.rb") @provider.callback(:whateverz, "chefz/foobar_callback.rb") end it "instance_evals a block/proc for restart command" do snitch = nil restart_cmd = Proc.new { snitch = 42 } @resource.restart(&restart_cmd) @provider.restart expect(snitch).to eq(42) end end describe "API bridge to capistrano" do it "defines sudo as a forwarder to execute" do expect(@provider).to receive(:execute).with("the moon, fool") @provider.sudo("the moon, fool") end it "defines run as a forwarder to execute, setting the user, group, cwd and environment to new_resource.user" do mock_execution = double("Resource::Execute") expect(@provider).to receive(:execute).with("iGoToHell4this").and_return(mock_execution) @resource.user("notCoolMan") @resource.group("Ggroup") @resource.environment("APP_ENV" => "staging") @resource.deploy_to("/my/app") expect(mock_execution).to receive(:user).with("notCoolMan") expect(mock_execution).to receive(:group).with("Ggroup") expect(mock_execution).to receive(:cwd) {|*args| if args.empty? nil else expect(args.size).to eq(1) expect(args.first).to eq(@provider.release_path) end }.twice expect(mock_execution).to receive(:environment) { |*args| if args.empty? nil else expect(args.size).to eq(1) expect(args.first).to eq({ "APP_ENV" => "staging" }) end }.twice @provider.run("iGoToHell4this") end it "defines run as a forwarder to execute, setting cwd and environment but not override" do mock_execution = double("Resource::Execute") expect(@provider).to receive(:execute).with("iGoToHell4this").and_return(mock_execution) @resource.user("notCoolMan") expect(mock_execution).to receive(:user).with("notCoolMan") expect(mock_execution).to receive(:cwd).with(no_args()).and_return("/some/value") expect(mock_execution).to receive(:environment).with(no_args()).and_return({}) @provider.run("iGoToHell4this") end it "converts sudo and run to exec resources in hooks" do runner = double("tehRunner") allow(Chef::Runner).to receive(:new).and_return(runner) snitch = nil @resource.user("tehCat") callback_code = Proc.new do snitch = 42 temp_collection = self.resource_collection run("tehMice") snitch = temp_collection.lookup("execute[tehMice]") end expect(runner).to receive(:converge) # @provider.callback(:phony, callback_code) expect(snitch).to be_an_instance_of(Chef::Resource::Execute) expect(snitch.user).to eq("tehCat") end end describe "installing gems from a gems.yml" do before do allow(::File).to receive(:exist?).with("#{@expected_release_dir}/gems.yml").and_return(true) @gem_list = [{ :name => "eventmachine", :version => "0.12.9" }] end it "reads a gems.yml file, creating gem providers for each with action :upgrade" do expect(IO).to receive(:read).with("#{@expected_release_dir}/gems.yml").and_return("cookie") expect(YAML).to receive(:load).with("cookie").and_return(@gem_list) gems = @provider.send(:gem_packages) expect(gems.map { |g| g.action }).to eq([%i{install}]) expect(gems.map { |g| g.name }).to eq(%w{eventmachine}) expect(gems.map { |g| g.version }).to eq(%w{0.12.9}) end it "takes a list of gem providers converges them" do allow(IO).to receive(:read) allow(YAML).to receive(:load).and_return(@gem_list) expected_gem_resources = @provider.send(:gem_packages).map { |r| [r.name, r.version] } gem_runner = @provider.send(:gem_resource_collection_runner) # no one has heard of defining == to be meaningful so I have use this monstrosity actual = gem_runner.run_context.resource_collection.all_resources.map { |r| [r.name, r.version] } expect(actual).to eq(expected_gem_resources) end end end chef-12.14.60/spec/unit/provider/directory_spec.rb000066400000000000000000000242271276456504500220050ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "tmpdir" describe Chef::Provider::Directory do let(:tmp_dir) { Dir.mktmpdir } let(:new_resource) { Chef::Resource::Directory.new(tmp_dir) } let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:directory) { Chef::Provider::Directory.new(new_resource, run_context) } describe "#load_current_resource" do describe "scanning file security metadata" describe "on unix", unix_only: true do describe "when the directory exists" do let(:dir_stat) { File::Stat.new(tmp_dir) } let(:expected_uid) { dir_stat.uid } let(:expected_gid) { dir_stat.gid } let(:expected_mode) { "0%o" % ( dir_stat.mode & 007777 ) } let(:expected_pwnam) { Etc.getpwuid(expected_uid).name } let(:expected_grnam) { Etc.getgrgid(expected_gid).name } it "describes the access mode as a String of octal integers" do directory.load_current_resource expect(directory.current_resource.mode).to eq(expected_mode) end it "when the new_resource.owner is numeric, describes the owner as a numeric uid" do new_resource.owner(500) directory.load_current_resource expect(directory.current_resource.owner).to eql(expected_uid) end it "when the new_resource.group is numeric, describes the group as a numeric gid" do new_resource.group(500) directory.load_current_resource expect(directory.current_resource.group).to eql(expected_gid) end it "when the new_resource.owner is a string, describes the owner as a string" do new_resource.owner("foo") directory.load_current_resource expect(directory.current_resource.owner).to eql(expected_pwnam) end it "when the new_resource.group is a string, describes the group as a string" do new_resource.group("bar") directory.load_current_resource expect(directory.current_resource.group).to eql(expected_grnam) end end end describe "on windows", windows_only: true do describe "when the directory exists" do it "the mode is always nil" do directory.load_current_resource expect(directory.current_resource.mode).to be nil end it "the owner is always nil" do directory.load_current_resource expect(directory.current_resource.owner).to be nil end it "the group is always nil" do directory.load_current_resource expect(directory.current_resource.group).to be nil end it "rights are always nil (incorrectly)" do directory.load_current_resource expect(directory.current_resource.rights).to be nil end it "inherits is always nil (incorrectly)" do directory.load_current_resource expect(directory.current_resource.inherits).to be nil end end end describe "when the directory does not exist" do before do FileUtils.rmdir tmp_dir end it "sets the mode, group and owner to nil" do directory.load_current_resource expect(directory.current_resource.mode).to eq(nil) expect(directory.current_resource.group).to eq(nil) expect(directory.current_resource.owner).to eq(nil) end end end describe "#define_resource_requirements" do describe "on unix", unix_only: true do it "raises an exception if the user does not exist" do new_resource.owner("arglebargle_iv") expect(Etc).to receive(:getpwnam).with("arglebargle_iv").and_raise(ArgumentError) directory.action = :create directory.load_current_resource expect(directory.access_controls).to receive(:define_resource_requirements).and_call_original directory.define_resource_requirements expect { directory.process_resource_requirements }.to raise_error(ArgumentError) end it "raises an exception if the group does not exist" do new_resource.group("arglebargle_iv") expect(Etc).to receive(:getgrnam).with("arglebargle_iv").and_raise(ArgumentError) directory.action = :create directory.load_current_resource expect(directory.access_controls).to receive(:define_resource_requirements).and_call_original directory.define_resource_requirements expect { directory.process_resource_requirements }.to raise_error(ArgumentError) end end end describe "#run_action(:create)" do describe "when the directory exists" do it "does not create the directory" do expect(Dir).not_to receive(:mkdir).with(new_resource.path) directory.run_action(:create) end it "should not set the resource as updated" do directory.run_action(:create) expect(new_resource).not_to be_updated end end describe "when the directory does not exist" do before do FileUtils.rmdir tmp_dir end it "creates the directory" do directory.run_action(:create) expect(File.exist?(tmp_dir)).to be true end it "sets the new resource as updated" do directory.run_action(:create) expect(new_resource).to be_updated end end describe "when the parent directory does not exist" do before do new_resource.path "#{tmp_dir}/foobar" FileUtils.rmdir tmp_dir end it "raises an exception when recursive is false" do new_resource.recursive false expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist) end it "creates the directories when recursive is true" do new_resource.recursive true directory.run_action(:create) expect(new_resource).to be_updated expect(File.exist?("#{tmp_dir}/foobar")).to be true end it "raises an exception when the parent directory is a file and recursive is true" do FileUtils.touch tmp_dir new_resource.recursive true expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist) end end describe "on OS X" do before do allow(node).to receive(:[]).with("platform").and_return("mac_os_x") new_resource.path "/usr/bin/chef_test" new_resource.recursive false allow_any_instance_of(Chef::Provider::File).to receive(:do_selinux) end it "os x 10.10 can write to sip locations" do allow(node).to receive(:[]).with("platform_version").and_return("10.10") allow(Dir).to receive(:mkdir).and_return([true], []) allow(::File).to receive(:directory?).and_return(true) allow(Chef::FileAccessControl).to receive(:writable?).and_return(true) directory.run_action(:create) expect(new_resource).to be_updated end it "os x 10.11 cannot write to sip locations" do allow(node).to receive(:[]).with("platform_version").and_return("10.11") allow(::File).to receive(:directory?).and_return(true) allow(Chef::FileAccessControl).to receive(:writable?).and_return(false) expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::InsufficientPermissions) end it "os x 10.11 can write to sip exlcusions" do new_resource.path "/usr/local/chef_test" allow(node).to receive(:[]).with("platform_version").and_return("10.11") allow(::File).to receive(:directory?).and_return(true) allow(Dir).to receive(:mkdir).and_return([true], []) allow(Chef::FileAccessControl).to receive(:writable?).and_return(false) directory.run_action(:create) expect(new_resource).to be_updated end end end describe "#run_action(:delete)" do describe "when the directory exists" do it "deletes the directory" do directory.run_action(:delete) expect(File.exist?(tmp_dir)).to be false end it "sets the new resource as updated" 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 before do FileUtils.rmdir tmp_dir end it "does not delete the directory" do expect(Dir).not_to receive(:delete).with(new_resource.path) directory.run_action(:delete) end it "sets the new resource as updated" do directory.run_action(:delete) expect(new_resource).not_to be_updated end end describe "when the directory is not writable" do before do allow(Chef::FileAccessControl).to receive(:writable?).and_return(false) end it "cannot delete it and raises an exception" do expect { directory.run_action(:delete) }.to raise_error(RuntimeError) end end describe "when the target directory is a file" do before do FileUtils.rmdir tmp_dir FileUtils.touch tmp_dir end it "cannot delete it and raises an exception" do expect { directory.run_action(:delete) }.to raise_error(RuntimeError) end end end end chef-12.14.60/spec/unit/provider/dsc_resource_spec.rb000066400000000000000000000144071276456504500224600ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef" require "spec_helper" describe Chef::Provider::DscResource do let (:events) { Chef::EventDispatch::Dispatcher.new } let (:run_context) { Chef::RunContext.new(node, {}, events) } let (:resource) { Chef::Resource::DscResource.new("dscresource", run_context) } let (:provider) do Chef::Provider::DscResource.new(resource, run_context) end context "when Powershell does not support Invoke-DscResource" do let (:node) do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = "4.0" node end it "raises a ProviderNotFound exception" do expect(provider).not_to receive(:meta_configuration) expect { provider.run_action(:run) }.to raise_error( Chef::Exceptions::ProviderNotFound, /5\.0\.10018\.0/) end end context "when Powershell supports Invoke-DscResource" do context "when RefreshMode is not set to Disabled" do context "and the WMF 5 is a preview release" do let (:node) do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = "5.0.10018.0" node end it "raises an exception" do expect(provider).to receive(:dsc_refresh_mode_disabled?).and_return(false) expect { provider.run_action(:run) }.to raise_error( Chef::Exceptions::ProviderNotFound, /Disabled/) end end context "and the WMF is 5 RTM or newer" do let (:node) do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = "5.0.10586.0" node end it "does not raises an exception" do expect(provider).to receive(:test_resource) expect(provider).to receive(:set_resource) expect(provider).to receive(:reboot_if_required) expect { provider.run_action(:run) }.to_not raise_error end end end end context "when the LCM supports Invoke-DscResource" do let (:node) do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = "5.0.10018.0" node end let (:resource_result) { double("CmdletResult", return_value: { "InDesiredState" => true }, stream: "description") } let (:invoke_dsc_resource) { double("cmdlet", run!: resource_result) } let (:store) { double("ResourceStore", find: resource_records) } let (:resource_records) { [] } before do allow(Chef::Util::DSC::ResourceStore).to receive(:instance).and_return(store) allow(Chef::Util::Powershell::Cmdlet).to receive(:new).and_return(invoke_dsc_resource) allow(provider).to receive(:dsc_refresh_mode_disabled?).and_return(true) end it "does not update the resource if it is up to date" do expect(provider).to receive(:test_resource).and_return(true) provider.run_action(:run) expect(resource).not_to be_updated end it "converges the resource if it is not up to date" do expect(provider).to receive(:test_resource).and_return(false) expect(provider).to receive(:set_resource) provider.run_action(:run) expect(resource).to be_updated end it "flags the resource as reboot required when required" do expect(provider).to receive(:test_resource).and_return(false) expect(provider).to receive(:invoke_resource). and_return(double(:stdout => "", :return_value => nil)) expect(provider).to receive(:add_dsc_verbose_log) expect(provider).to receive(:return_dsc_resource_result).and_return(true) expect(provider).to receive(:create_reboot_resource) provider.run_action(:run) end it "does not flag the resource as reboot required when not required" do expect(provider).to receive(:test_resource).and_return(false) expect(provider).to receive(:invoke_resource). and_return(double(:stdout => "", :return_value => nil)) expect(provider).to receive(:add_dsc_verbose_log) expect(provider).to receive(:return_dsc_resource_result).and_return(false) expect(provider).to_not receive(:create_reboot_resource) provider.run_action(:run) end context "resource name cannot be found" do let (:resource_records) { [] } it "raises ResourceNotFound" do expect { provider.run_action(:run) }.to raise_error(Chef::Exceptions::ResourceNotFound) end end context "resource name is found" do context "no module name for resource found" do let (:resource_records) { [{}] } it "returns the default dsc resource module" do expect(Chef::Util::Powershell::Cmdlet).to receive(:new) do |node, cmdlet, format| expect(cmdlet).to match(/Module PSDesiredStateConfiguration /) end.and_return(invoke_dsc_resource) provider.run_action(:run) end end context "a module name for resource is found" do let (:resource_records) { [{ "Module" => { "Name" => "ModuleName" } }] } it "returns the default dsc resource module" do expect(Chef::Util::Powershell::Cmdlet).to receive(:new) do |node, cmdlet, format| expect(cmdlet).to match(/Module ModuleName /) end.and_return(invoke_dsc_resource) provider.run_action(:run) end end context "multiple resource are found" do let (:resource_records) do [ { "Module" => { "Name" => "ModuleName1" } }, { "Module" => { "Name" => "ModuleName2" } }, ] end it "raises MultipleDscResourcesFound" do expect { provider.run_action(:run) }.to raise_error(Chef::Exceptions::MultipleDscResourcesFound) end end end end end chef-12.14.60/spec/unit/provider/dsc_script_spec.rb000066400000000000000000000200121276456504500221220ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef" require "chef/util/dsc/resource_info" require "spec_helper" describe Chef::Provider::DscScript do context "when DSC is available" do let (:node) do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = "4.0" node end let (:events) { Chef::EventDispatch::Dispatcher.new } let (:run_context) { Chef::RunContext.new(node, {}, events) } let (:resource) { Chef::Resource::DscScript.new("script", run_context) } let (:provider) do Chef::Provider::DscScript.new(resource, run_context) end describe "#load_current_resource" do it "describes the resource as converged if there were 0 DSC resources" do allow(provider).to receive(:run_configuration).with(:test).and_return([]) provider.load_current_resource expect(provider.instance_variable_get("@resource_converged")).to be_truthy end it "describes the resource as not converged if there is 1 DSC resources that is converged" do dsc_resource_info = Chef::Util::DSC::ResourceInfo.new("resource", false, ["nothing will change something"]) allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) provider.load_current_resource expect(provider.instance_variable_get("@resource_converged")).to be_truthy end it "describes the resource as not converged if there is 1 DSC resources that is not converged" do dsc_resource_info = Chef::Util::DSC::ResourceInfo.new("resource", true, ["will change something"]) allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) provider.load_current_resource expect(provider.instance_variable_get("@resource_converged")).to be_falsey end it "describes the resource as not converged if there are any DSC resources that are not converged" do dsc_resource_info1 = Chef::Util::DSC::ResourceInfo.new("resource", true, ["will change something"]) dsc_resource_info2 = Chef::Util::DSC::ResourceInfo.new("resource", false, ["nothing will change something"]) allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info1, dsc_resource_info2]) provider.load_current_resource expect(provider.instance_variable_get("@resource_converged")).to be_falsey end it "describes the resource as converged if all DSC resources that are converged" do dsc_resource_info1 = Chef::Util::DSC::ResourceInfo.new("resource", false, ["nothing will change something"]) dsc_resource_info2 = Chef::Util::DSC::ResourceInfo.new("resource", false, ["nothing will change something"]) allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info1, dsc_resource_info2]) provider.load_current_resource expect(provider.instance_variable_get("@resource_converged")).to be_truthy end end describe "#generate_configuration_document" do # I think integration tests should cover these cases it "uses configuration_document_from_script_path when a dsc script file is given" do allow(provider).to receive(:load_current_resource) resource.command("path_to_script") generator = double("Chef::Util::DSC::ConfigurationGenerator") expect(generator).to receive(:configuration_document_from_script_path) allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) provider.send(:generate_configuration_document, "tmp", nil) end it "uses configuration_document_from_script_code when a the dsc resource is given" do allow(provider).to receive(:load_current_resource) resource.code("ImADSCResource{}") generator = double("Chef::Util::DSC::ConfigurationGenerator") expect(generator).to receive(:configuration_document_from_script_code) allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) provider.send(:generate_configuration_document, "tmp", nil) end it "should noop if neither code or command are provided" do allow(provider).to receive(:load_current_resource) generator = double("Chef::Util::DSC::ConfigurationGenerator") expect(generator).to receive(:configuration_document_from_script_code).with("", anything(), anything(), anything()) allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) provider.send(:generate_configuration_document, "tmp", nil) end end describe "action_run" do it "should converge the script if it is not converged" do dsc_resource_info = Chef::Util::DSC::ResourceInfo.new("resource", true, ["will change something"]) allow(provider).to receive(:run_configuration).with(:test).and_return([dsc_resource_info]) allow(provider).to receive(:run_configuration).with(:set) provider.run_action(:run) expect(resource).to be_updated end it "should not converge if the script is already converged" do allow(provider).to receive(:run_configuration).with(:test).and_return([]) provider.run_action(:run) expect(resource).not_to be_updated end end describe "#generate_description" do it "removes the resource name from the beginning of any log line from the LCM" do dsc_resource_info = Chef::Util::DSC::ResourceInfo.new("resourcename", true, ["resourcename doing something", "lastline"]) provider.instance_variable_set("@dsc_resources_info", [dsc_resource_info]) expect(provider.send(:generate_description)[1]).to match(/converge DSC resource resourcename by doing something/) end it "ignores the last line" do dsc_resource_info = Chef::Util::DSC::ResourceInfo.new("resourcename", true, ["resourcename doing something", "lastline"]) provider.instance_variable_set("@dsc_resources_info", [dsc_resource_info]) expect(provider.send(:generate_description)[1]).not_to match(/lastline/) end it "reports a dsc resource has not been changed if the LCM reported no change was required" do dsc_resource_info = Chef::Util::DSC::ResourceInfo.new("resourcename", false, ["resourcename does nothing", "lastline"]) provider.instance_variable_set("@dsc_resources_info", [dsc_resource_info]) expect(provider.send(:generate_description)[1]).to match(/converge DSC resource resourcename by doing nothing/) end end end context "when Dsc is not available" do let (:node) { Chef::Node.new } let (:events) { Chef::EventDispatch::Dispatcher.new } let (:run_context) { Chef::RunContext.new(node, {}, events) } let (:resource) { Chef::Resource::DscScript.new("script", run_context) } let (:provider) { Chef::Provider::DscScript.new(resource, run_context) } describe "action_run" do ["1.0", "2.0", "3.0"].each do |version| it "raises an exception for powershell version '#{version}'" do node.automatic[:languages][:powershell][:version] = version expect do provider.run_action(:run) end.to raise_error(Chef::Exceptions::ProviderNotFound) end end it "raises an exception if Powershell is not present" do expect do provider.run_action(:run) end.to raise_error(Chef::Exceptions::ProviderNotFound) end end end end chef-12.14.60/spec/unit/provider/env/000077500000000000000000000000001276456504500172235ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/env/windows_spec.rb000066400000000000000000000064251276456504500222630ustar00rootroot00000000000000# # Author:: Sander van Harmelen # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Provider::Env::Windows, :windows_only do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } context "when environment variable is not PATH" do let(:new_resource) do new_resource = Chef::Resource::Env.new("CHEF_WINDOWS_ENV_TEST") new_resource.value("foo") new_resource end let(:provider) do provider = Chef::Provider::Env::Windows.new(new_resource, run_context) allow(provider).to receive(:env_obj).and_return(double("null object").as_null_object) provider end describe "action_create" do before do ENV.delete("CHEF_WINDOWS_ENV_TEST") provider.key_exists = false end it "should update the ruby ENV object when it creates the key" do provider.action_create expect(ENV["CHEF_WINDOWS_ENV_TEST"]).to eql("foo") end end describe "action_modify" do before do ENV["CHEF_WINDOWS_ENV_TEST"] = "foo" end it "should update the ruby ENV object when it updates the value" do expect(provider).to receive(:requires_modify_or_create?).and_return(true) new_resource.value("foobar") provider.action_modify expect(ENV["CHEF_WINDOWS_ENV_TEST"]).to eql("foobar") end describe "action_delete" do before do ENV["CHEF_WINDOWS_ENV_TEST"] = "foo" end it "should update the ruby ENV object when it deletes the key" do provider.action_delete expect(ENV["CHEF_WINDOWS_ENV_TEST"]).to eql(nil) end end end end context "when environment is PATH" do describe "for PATH" do let(:system_root) { "%SystemRoot%" } let(:system_root_value) { 'D:\Windows' } let(:new_resource) do new_resource = Chef::Resource::Env.new("PATH") new_resource.value(system_root) new_resource end let(:provider) do provider = Chef::Provider::Env::Windows.new(new_resource, run_context) allow(provider).to receive(:env_obj).and_return(double("null object").as_null_object) provider end before do stub_const("ENV", { "PATH" => "" }) end it "replaces Windows system variables" do expect(provider).to receive(:requires_modify_or_create?).and_return(true) expect(provider).to receive(:expand_path).with(system_root).and_return(system_root_value) provider.action_modify expect(ENV["PATH"]).to eql(system_root_value) end end end end chef-12.14.60/spec/unit/provider/env_spec.rb000066400000000000000000000255621276456504500205740ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Provider::Env do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Env.new("FOO") @new_resource.value("bar") @provider = Chef::Provider::Env.new(@new_resource, @run_context) end it "assumes the key_name exists by default" do expect(@provider.key_exists).to be_truthy end describe "when loading the current status" do before do #@current_resource = @new_resource.clone #Chef::Resource::Env.stub(:new).and_return(@current_resource) @provider.current_resource = @current_resource allow(@provider).to receive(:env_value).with("FOO").and_return("bar") allow(@provider).to receive(:env_key_exists).and_return(true) end it "should create a current resource with the same name as the new resource" do @provider.load_current_resource expect(@provider.new_resource.name).to eq("FOO") end it "should set the key_name to the key name of the new resource" do @provider.load_current_resource expect(@provider.current_resource.key_name).to eq("FOO") end it "should check if the key_name exists" do expect(@provider).to receive(:env_key_exists).with("FOO").and_return(true) @provider.load_current_resource expect(@provider.key_exists).to be_truthy end it "should flip the value of exists if the key does not exist" do expect(@provider).to receive(:env_key_exists).with("FOO").and_return(false) @provider.load_current_resource expect(@provider.key_exists).to be_falsey end it "should return the current resource" do expect(@provider.load_current_resource).to be_a_kind_of(Chef::Resource::Env) end end describe "action_create" do before do @provider.key_exists = false allow(@provider).to receive(:create_env).and_return(true) allow(@provider).to receive(:modify_env).and_return(true) end it "should call create_env if the key does not exist" do expect(@provider).to receive(:create_env).and_return(true) @provider.action_create end it "should set the new_resources updated flag when it creates the key" do @provider.action_create expect(@new_resource).to be_updated end it "should check to see if the values are the same if the key exists" do @provider.key_exists = true expect(@provider).to receive(:requires_modify_or_create?).and_return(false) @provider.action_create end it "should call modify_env if the key exists and values are not equal" do @provider.key_exists = true allow(@provider).to receive(:requires_modify_or_create?).and_return(true) expect(@provider).to receive(:modify_env).and_return(true) @provider.action_create end it "should set the new_resources updated flag when it updates an existing value" do @provider.key_exists = true allow(@provider).to receive(:requires_modify_or_create?).and_return(true) allow(@provider).to receive(:modify_env).and_return(true) @provider.action_create expect(@new_resource).to be_updated end end describe "action_delete" do before(:each) do @provider.current_resource = @current_resource @provider.key_exists = false allow(@provider).to receive(:delete_element).and_return(false) allow(@provider).to receive(:delete_env).and_return(true) end it "should not call delete_env if the key does not exist" do expect(@provider).not_to receive(:delete_env) @provider.action_delete end it "should not call delete_element if the key does not exist" do expect(@provider).not_to receive(:delete_element) @provider.action_delete end it "should call delete_env if the key exists" do @provider.key_exists = true expect(@provider).to receive(:delete_env) @provider.action_delete end it "should set the new_resources updated flag to true if the key is deleted" do @provider.key_exists = true @provider.action_delete expect(@new_resource).to be_updated end end describe "action_modify" do before(:each) do @provider.current_resource = @current_resource @provider.key_exists = true allow(@provider).to receive(:modify_env).and_return(true) end it "should call modify_group if the key exists and values are not equal" do expect(@provider).to receive(:requires_modify_or_create?).and_return(true) expect(@provider).to receive(:modify_env).and_return(true) @provider.action_modify end it "should set the new resources updated flag to true if modify_env is called" do allow(@provider).to receive(:requires_modify_or_create?).and_return(true) allow(@provider).to receive(:modify_env).and_return(true) @provider.action_modify expect(@new_resource).to be_updated end it "should not call modify_env if the key exists but the values are equal" do expect(@provider).to receive(:requires_modify_or_create?).and_return(false) expect(@provider).not_to receive(:modify_env) @provider.action_modify end it "should raise a Chef::Exceptions::Env if the key doesn't exist" do @provider.key_exists = false expect { @provider.action_modify }.to raise_error(Chef::Exceptions::Env) end end describe "delete_element" do before(:each) do @current_resource = Chef::Resource::Env.new("FOO") @new_resource.delim ";" @new_resource.value "C:/bar/bin" @current_resource.value "C:/foo/bin;C:/bar/bin" @provider.current_resource = @current_resource end it "should return true if the element is not found" do allow(@new_resource).to receive(:value).and_return("C:/baz/bin") expect(@provider.delete_element).to eql(true) end it "should return false if the delim not defined" do allow(@new_resource).to receive(:delim).and_return(nil) expect(@provider.delete_element).to eql(false) end it "should return true if the element is deleted" do @new_resource.value("C:/foo/bin") expect(@provider).to receive(:create_env) expect(@provider.delete_element).to eql(true) expect(@new_resource).to be_updated end context "when new_resource's value contains the delimiter" do it "should return false if all the elements are deleted" do # This indicates that the entire key needs to be deleted @new_resource.value("C:/foo/bin;C:/bar/bin") expect(@provider.delete_element).to eql(false) expect(@new_resource).not_to be_updated # This will be updated in action_delete end it "should return true if any, but not all, of the elements are deleted" do @new_resource.value("C:/foo/bin;C:/notbaz/bin") expect(@provider).to receive(:create_env) expect(@provider.delete_element).to eql(true) expect(@new_resource).to be_updated end it "should return true if none of the elements are deleted" do @new_resource.value("C:/notfoo/bin;C:/notbaz/bin") expect(@provider.delete_element).to eql(true) expect(@new_resource).not_to be_updated end end end describe "requires_modify_or_create?" do before(:each) do @new_resource.value("C:/bar") @current_resource = @new_resource.clone @provider.current_resource = @current_resource end it "should return false if the values are equal" do expect(@provider.requires_modify_or_create?).to be_falsey end it "should return true if the values not are equal" do @new_resource.value("C:/elsewhere") expect(@provider.requires_modify_or_create?).to be_truthy end it "should return false if the current value contains the element" do @new_resource.delim(";") @current_resource.value("C:/bar;C:/foo;C:/baz") expect(@provider.requires_modify_or_create?).to be_falsey end it "should return true if the current value does not contain the element" do @new_resource.delim(";") @current_resource.value("C:/biz;C:/foo/bin;C:/baz") expect(@provider.requires_modify_or_create?).to be_truthy end context "when new_resource's value contains the delimiter" do it "should return false if all the current values are contained in specified order" do @new_resource.value("C:/biz;C:/baz") @new_resource.delim(";") @current_resource.value("C:/biz;C:/foo/bin;C:/baz") expect(@provider.requires_modify_or_create?).to be_falsey end it "should return true if any of the new values are not contained" do @new_resource.value("C:/biz;C:/baz;C:/bin") @new_resource.delim(";") @current_resource.value("C:/biz;C:/foo/bin;C:/baz") expect(@provider.requires_modify_or_create?).to be_truthy end it "should return true if values are contained in different order" do @new_resource.value("C:/biz;C:/baz") @new_resource.delim(";") @current_resource.value("C:/baz;C:/foo/bin;C:/biz") expect(@provider.requires_modify_or_create?).to be_truthy end end end describe "modify_env" do before(:each) do allow(@provider).to receive(:create_env).and_return(true) @new_resource.delim ";" @current_resource = Chef::Resource::Env.new("FOO") @current_resource.value "C:/foo/bin" @provider.current_resource = @current_resource end it "should not modify the variable passed to the resource" do new_value = "C:/bar/bin" passed_value = new_value.dup @new_resource.value(passed_value) @provider.modify_env expect(passed_value).to eq(new_value) end it "should only add values not already contained" do @new_resource.value("C:/foo;C:/bar;C:/baz") @current_resource.value("C:/bar;C:/baz;C:/foo/bar") @provider.modify_env expect(@new_resource.value).to eq("C:/foo;C:/bar;C:/baz;C:/foo/bar") end it "should reorder values to keep order which asked" do @new_resource.value("C:/foo;C:/bar;C:/baz") @current_resource.value("C:/foo/bar;C:/baz;C:/bar") @provider.modify_env expect(@new_resource.value).to eq("C:/foo;C:/bar;C:/baz;C:/foo/bar") end end end chef-12.14.60/spec/unit/provider/erl_call_spec.rb000066400000000000000000000053761276456504500215620ustar00rootroot00000000000000# # Author:: Joe Williams () # Copyright:: Copyright 2009-2016, Joe Williams # 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::ErlCall do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::ErlCall.new("test", @node) @new_resource.code("io:format(\"burritos\", []).") @new_resource.node_name("chef@localhost") @new_resource.name("test") @provider = Chef::Provider::ErlCall.new(@new_resource, @run_context) allow(@provider).to receive(:popen4).and_return(@status) @stdin = StringIO.new @stdout = StringIO.new("{ok, woohoo}") @stderr = StringIO.new @pid = 2342999 end it "should return a Chef::Provider::ErlCall object" do provider = Chef::Provider::ErlCall.new(@new_resource, @run_context) expect(provider).to be_a_kind_of(Chef::Provider::ErlCall) end it "should return true" do expect(@provider.load_current_resource).to eql(true) end describe "when running a distributed erl call resource" do before do @new_resource.cookie("nomnomnom") @new_resource.distributed(true) @new_resource.name_type("sname") end it "should write to stdin of the erl_call command" do expected_cmd = "erl_call -e -s -sname chef@localhost -c nomnomnom" expect(@provider).to receive(:popen4).with(expected_cmd, :waitlast => true).and_return([@pid, @stdin, @stdout, @stderr]) expect(Process).to receive(:wait).with(@pid) @provider.action_run expect(@stdin.string).to eq("#{@new_resource.code}\n") end end describe "when running a local erl call resource" do before do @new_resource.cookie(nil) @new_resource.distributed(false) @new_resource.name_type("name") end it "should write to stdin of the erl_call command" do expect(@provider).to receive(:popen4).with("erl_call -e -name chef@localhost ", :waitlast => true).and_return([@pid, @stdin, @stdout, @stderr]) expect(Process).to receive(:wait).with(@pid) @provider.action_run expect(@stdin.string).to eq("#{@new_resource.code}\n") end end end chef-12.14.60/spec/unit/provider/execute_spec.rb000066400000000000000000000232671276456504500214460ustar00rootroot00000000000000# # Author:: Prajakta Purohit () # 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::Execute do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:provider) { Chef::Provider::Execute.new(new_resource, run_context) } let(:current_resource) { Chef::Resource::Ifconfig.new("foo_resource", run_context) } # You will be the same object, I promise. @live_stream = Chef::EventDispatch::EventsOutputStream.new(run_context.events, :name => :execute) let(:opts) do { timeout: 3600, returns: 0, log_level: :info, log_tag: new_resource.to_s, } end let(:new_resource) { Chef::Resource::Execute.new("foo_resource", run_context) } before do allow(Chef::EventDispatch::EventsOutputStream).to receive(:new) { @live_stream } allow(ChefConfig).to receive(:windows?) { false } @original_log_level = Chef::Log.level Chef::Log.level = :info allow(STDOUT).to receive(:tty?).and_return(false) end after do Chef::Log.level = @original_log_level Chef::Config[:always_stream_execute] = false Chef::Config[:daemon] = false end describe "#initialize" do it "should return a Chef::Provider::Execute provider" do expect(provider.class).to eql(Chef::Provider::Execute) end end describe "#load_current_resource" do before do expect(Chef::Resource::Execute).to receive(:new).with(new_resource.name).and_return(current_resource) end it "should return the current resource" do expect(provider.load_current_resource).to eql(current_resource) end it "our timeout should default to 3600" do provider.load_current_resource expect(provider.timeout).to eql(3600) end end describe "#action_run" do it "runs shell_out with the default options" do expect(provider).to receive(:shell_out!).with(new_resource.name, opts) expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).to be_updated end it "if you pass a command attribute, it runs the command" do new_resource.command "/usr/argelbargle/bin/oogachacka 12345" expect(provider).to receive(:shell_out!).with(new_resource.command, opts) expect(provider).to receive(:converge_by).with("execute #{new_resource.command}").and_call_original expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).to be_updated end it "should honor sensitive attribute" do new_resource.sensitive true # Since the resource is sensitive, it should not have :live_stream set opts.delete(:live_stream) expect(provider).to receive(:shell_out!).with(new_resource.name, opts) expect(provider).to receive(:converge_by).with("execute sensitive resource").and_call_original expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).to be_updated end it "should do nothing if the sentinel file exists" do new_resource.creates "/foo_resource" expect(FileTest).to receive(:exist?).with(new_resource.creates).and_return(true) expect(provider).not_to receive(:shell_out!) expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).not_to be_updated end describe "when the user specifies a relative path without cwd" do before do expect(new_resource.cwd).to be_falsey new_resource.creates "foo_resource" end it "should warn in Chef-12", chef: "< 13" do expect(Chef::Log).to receive(:warn).with(/relative path/) expect(FileTest).to receive(:exist?).with(new_resource.creates).and_return(true) expect(provider).not_to receive(:shell_out!) provider.run_action(:run) expect(new_resource).not_to be_updated end it "should raise if user specified relative path without cwd for Chef-13", chef: ">= 13" do expect(Chef::Log).to receive(:warn).with(/relative path/) expect(FileTest).to receive(:exist?).with(new_resource.creates).and_return(true) expect(provider).not_to receive(:shell_out!) expect { provider.run_action(:run) }.to raise_error # @todo: add a real error for Chef-13 end end it "should respect cwd options for 'creates'" do new_resource.cwd "/tmp" new_resource.creates "foo_resource" expect(FileTest).not_to receive(:exist?).with(new_resource.creates) expect(FileTest).to receive(:exist?).with(File.join("/tmp", new_resource.creates)).and_return(true) expect(Chef::Log).not_to receive(:warn) expect(provider).not_to receive(:shell_out!) provider.run_action(:run) expect(new_resource).not_to be_updated end it "should not include stdout/stderr in failure exception for sensitive resource" do opts.delete(:live_stream) new_resource.sensitive true expect(provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) expect do provider.run_action(:run) end.to raise_error(Mixlib::ShellOut::ShellCommandFailed, /suppressed for sensitive resource/) end describe "streaming output" do it "should not set the live_stream if sensitive is on" do new_resource.sensitive true expect(provider).to receive(:shell_out!).with(new_resource.name, opts) expect(provider).to receive(:converge_by).with("execute sensitive resource").and_call_original expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).to be_updated end describe "with an output formatter listening" do let(:events) { d = Chef::EventDispatch::Dispatcher.new; d.register(Chef::Formatters::Doc.new(StringIO.new, StringIO.new)); d } before do Chef::Config[:stream_execute_output] = true end it "should set the live_stream if the log level is info or above" do nopts = opts nopts[:live_stream] = @live_stream expect(provider).to receive(:shell_out!).with(new_resource.name, nopts) expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).to be_updated end it "should set the live_stream if the resource requests live streaming" do Chef::Log.level = :warn new_resource.live_stream true nopts = opts nopts[:live_stream] = @live_stream expect(provider).to receive(:shell_out!).with(new_resource.name, nopts) expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).to be_updated end it "should not set the live_stream if the resource is sensitive" do new_resource.sensitive true expect(provider).to receive(:shell_out!).with(new_resource.name, opts) expect(provider).to receive(:converge_by).with("execute sensitive resource").and_call_original expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).to be_updated end end describe "with only logging enabled" do it "should set the live_stream to STDOUT if we are a TTY, not daemonized, not sensitive, and info is enabled" do nopts = opts nopts[:live_stream] = STDOUT allow(STDOUT).to receive(:tty?).and_return(true) expect(provider).to receive(:shell_out!).with(new_resource.name, nopts) expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).to be_updated end it "should not set the live_stream to STDOUT if we are a TTY, not daemonized, but sensitive" do new_resource.sensitive true allow(STDOUT).to receive(:tty?).and_return(true) expect(provider).to receive(:shell_out!).with(new_resource.name, opts) expect(provider).to receive(:converge_by).with("execute sensitive resource").and_call_original expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).to be_updated end it "should not set the live_stream to STDOUT if we are a TTY, but daemonized" do Chef::Config[:daemon] = true allow(STDOUT).to receive(:tty?).and_return(true) expect(provider).to receive(:shell_out!).with(new_resource.name, opts) expect(provider).to receive(:converge_by).with("execute foo_resource").and_call_original expect(Chef::Log).not_to receive(:warn) provider.run_action(:run) expect(new_resource).to be_updated end end end end end chef-12.14.60/spec/unit/provider/file/000077500000000000000000000000001276456504500173525ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/file/content_spec.rb000066400000000000000000000073521276456504500223720ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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::File::Content do # # mock setup # let(:current_resource) do double("Chef::Provider::File::Resource (current)") end let(:enclosing_directory) do canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates"))) end let(:resource_path) do canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt"))) end let(:new_resource) do double("Chef::Provider::File::Resource (new)", :name => "seattle.txt", :path => resource_path) end let(:run_context) do double("Chef::RunContext") end # # subject # let(:content) do Chef::Provider::File::Content.new(new_resource, current_resource, run_context) end describe "when the resource has a content attribute set" do before do allow(new_resource).to receive(:content).and_return("Do do do do, do do do do, do do do do, do do do do") end it "returns a tempfile" do expect(content.tempfile).to be_a_kind_of(Tempfile) end it "the tempfile contents should match the resource contents" do expect(IO.read(content.tempfile.path)).to eq(new_resource.content) end it "returns a tempfile in the tempdir when :file_staging_uses_destdir is not set" do Chef::Config[:file_staging_uses_destdir] = false expect(content.tempfile.path.start_with?(Dir.tmpdir)).to be_truthy expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be_falsey end it "returns a tempfile in the destdir when :file_deployment_uses_destdir is set" do Chef::Config[:file_staging_uses_destdir] = true expect(content.tempfile.path.start_with?(Dir.tmpdir)).to be_falsey expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be_truthy end context "when creating a tempfiles in destdir fails" do let(:enclosing_directory) do canonicalize_path("/nonexisting/path") end it "returns a tempfile in the tempdir when :file_deployment_uses_destdir is set to :auto" do Chef::Config[:file_staging_uses_destdir] = :auto expect(content.tempfile.path.start_with?(Dir.tmpdir)).to be_truthy expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be_falsey end it "fails when :file_desployment_uses_destdir is set" do Chef::Config[:file_staging_uses_destdir] = true expect { content.tempfile }.to raise_error(Chef::Exceptions::FileContentStagingError) end it "returns a tempfile in the tempdir when :file_desployment_uses_destdir is not set" do expect(content.tempfile.path.start_with?(Dir.tmpdir)).to be_truthy expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be_falsey end end end describe "when the resource does not have a content attribute set" do before do allow(new_resource).to receive(:content).and_return(nil) end it "should return nil instead of a tempfile" do expect(content.tempfile).to be_nil end end end chef-12.14.60/spec/unit/provider/file_spec.rb000066400000000000000000000034221276456504500207120ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Lamont Granquist () # 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 "support/shared/unit/provider/file" describe Chef::Provider::File do let(:resource) do # need to check for/against mutating state within the new_resource, so don't mock resource = Chef::Resource::File.new("seattle") resource.path(resource_path) resource end let(:content) do content = double("Chef::Provider::File::Content") end let(:node) { double("Chef::Node") } let(:events) { double("Chef::Events").as_null_object } # mock all the methods let(:run_context) { double("Chef::RunContext", :node => node, :events => events) } let(:enclosing_directory) do canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates"))) end let(:resource_path) do canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt"))) end # Subject let(:provider) do provider = described_class.new(resource, run_context) allow(provider).to receive(:content).and_return(content) provider end it_behaves_like Chef::Provider::File it_behaves_like "a file provider with content field" end chef-12.14.60/spec/unit/provider/git_spec.rb000066400000000000000000001136261276456504500205660ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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::Git do before(:each) do allow(STDOUT).to receive(:tty?).and_return(true) @original_log_level = Chef::Log.level Chef::Log.level = :info @current_resource = Chef::Resource::Git.new("web2.0 app") @current_resource.revision("d35af14d41ae22b19da05d7d03a0bafc321b244c") @resource = Chef::Resource::Git.new("web2.0 app") @resource.repository "git://github.com/opscode/chef.git" @resource.destination "/my/deploy/dir" @resource.revision "d35af14d41ae22b19da05d7d03a0bafc321b244c" @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @provider = Chef::Provider::Git.new(@resource, @run_context) @provider.current_resource = @current_resource end after(:each) do Chef::Log.level = @original_log_level end context "determining the revision of the currently deployed checkout" do before do @stdout = double("standard out") @stderr = double("standard error") @exitstatus = double("exitstatus") end it "sets the current revision to nil if the deploy dir does not exist" do expect(::File).to receive(:exist?).with("/my/deploy/dir/.git").and_return(false) expect(@provider.find_current_revision).to be_nil end it "determines the current revision when there is one" do expect(::File).to receive(:exist?).with("/my/deploy/dir/.git").and_return(true) @stdout = "9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13\n" expect(@provider).to receive(:shell_out!).with("git rev-parse HEAD", { :cwd => "/my/deploy/dir", :returns => [0, 128], :log_tag => "git[web2.0 app]" }).and_return(double("ShellOut result", :stdout => @stdout)) expect(@provider.find_current_revision).to eql("9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13") end it "gives the current revision as nil when there is no current revision" do expect(::File).to receive(:exist?).with("/my/deploy/dir/.git").and_return(true) @stderr = "fatal: Not a git repository (or any of the parent directories): .git" @stdout = "" expect(@provider).to receive(:shell_out!).with("git rev-parse HEAD", :cwd => "/my/deploy/dir", :returns => [0, 128], :log_tag => "git[web2.0 app]" ).and_return(double("ShellOut result", :stdout => "", :stderr => @stderr)) expect(@provider.find_current_revision).to be_nil end end it "creates a current_resource with the currently deployed revision when a clone exists in the destination dir" do allow(@provider).to receive(:find_current_revision).and_return("681c9802d1c62a45b490786c18f0b8216b309440") @provider.load_current_resource expect(@provider.current_resource.name).to eql(@resource.name) expect(@provider.current_resource.revision).to eql("681c9802d1c62a45b490786c18f0b8216b309440") end it "keeps the node and resource passed to it on initialize" do expect(@provider.node).to equal(@node) expect(@provider.new_resource).to equal(@resource) end context "resolving revisions to a SHA" do before do @git_ls_remote = "git ls-remote \"git://github.com/opscode/chef.git\" " end it "returns resource.revision as is if revision is already a full SHA" do expect(@provider.target_revision).to eql("d35af14d41ae22b19da05d7d03a0bafc321b244c") end it "converts resource.revision from a tag to a SHA" do @resource.revision "v1.0" @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") expect(@provider).to receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", { :log_tag => "git[web2.0 app]" }).and_return(double("ShellOut result", :stdout => @stdout)) expect(@provider.target_revision).to eql("503c22a5e41f5ae3193460cca044ed1435029f53") end it "converts resource.revision from an annotated tag to the tagged SHA (not SHA of tag)" do @resource.revision "v1.0" @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n" + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0^{}\n") expect(@provider).to receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", { :log_tag => "git[web2.0 app]" }).and_return(double("ShellOut result", :stdout => @stdout)) expect(@provider.target_revision).to eql("663c22a5e41f5ae3193460cca044ed1435029f53") end it "converts resource.revision from a tag to a SHA using an exact match" do @resource.revision "v1.0" @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/releases/v1.0\n" + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n") expect(@provider).to receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", { :log_tag => "git[web2.0 app]" }).and_return(double("ShellOut result", :stdout => @stdout)) expect(@provider.target_revision).to eql("503c22a5e41f5ae3193460cca044ed1435029f53") end it "converts resource.revision from a tag to a SHA, matching tags first, then heads" do @resource.revision "v1.0" @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n" + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") expect(@provider).to receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", { :log_tag => "git[web2.0 app]" }).and_return(double("ShellOut result", :stdout => @stdout)) expect(@provider.target_revision).to eql("663c22a5e41f5ae3193460cca044ed1435029f53") end it "converts resource.revision from a tag to a SHA, matching heads if no tags match" do @resource.revision "v1.0" @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.1\n" + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") expect(@provider).to receive(:shell_out!).with(@git_ls_remote + "\"v1.0*\"", { :log_tag => "git[web2.0 app]" }).and_return(double("ShellOut result", :stdout => @stdout)) expect(@provider.target_revision).to eql("503c22a5e41f5ae3193460cca044ed1435029f53") end it "converts resource.revision from a tag to a SHA, matching tags first, then heads, then revision" do @resource.revision "refs/pulls/v1.0" @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n" + "805c22a5e41f5ae3193460cca044ed1435029f53\trefs/pulls/v1.0\n" + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") expect(@provider).to receive(:shell_out!).with(@git_ls_remote + "\"refs/pulls/v1.0*\"", { :log_tag => "git[web2.0 app]" }).and_return(double("ShellOut result", :stdout => @stdout)) expect(@provider.target_revision).to eql("805c22a5e41f5ae3193460cca044ed1435029f53") end it "converts resource.revision from a tag to a SHA, using full path if provided" do @resource.revision "refs/heads/v1.0" @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" + "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/tags/v1.0\n" + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n") expect(@provider).to receive(:shell_out!).with(@git_ls_remote + "\"refs/heads/v1.0*\"", { :log_tag => "git[web2.0 app]" }).and_return(double("ShellOut result", :stdout => @stdout)) expect(@provider.target_revision).to eql("503c22a5e41f5ae3193460cca044ed1435029f53") end it "raises an invalid remote reference error if you try to deploy from ``origin'' and assertions are run" do @resource.revision "origin/" @provider.action = :checkout @provider.define_resource_requirements allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::InvalidRemoteGitReference) end it "raises an unresolvable git reference error if the revision can't be resolved to any revision and assertions are run" do @resource.revision "FAIL, that's the revision I want" @provider.action = :checkout expect(@provider).to receive(:shell_out!).and_return(double("ShellOut result", :stdout => "\n")) @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::UnresolvableGitReference) end it "does not raise an error if the revision can't be resolved when assertions are not run" do @resource.revision "FAIL, that's the revision I want" expect(@provider).to receive(:shell_out!).and_return(double("ShellOut result", :stdout => "\n")) expect(@provider.target_revision).to eq(nil) end it "does not raise an error when the revision is valid and assertions are run." do @resource.revision "0.8-alpha" @stdout = "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" expect(@provider).to receive(:shell_out!).with(@git_ls_remote + "\"0.8-alpha*\"", { :log_tag => "git[web2.0 app]" }).and_return(double("ShellOut result", :stdout => @stdout)) @provider.action = :checkout allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) @provider.define_resource_requirements expect { @provider.process_resource_requirements }.not_to raise_error end it "gives the latest HEAD revision SHA if nothing is specified" do @stdout = <<-SHAS 28af684d8460ba4793eda3e7ac238c864a5d029a\tHEAD 503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha 28af684d8460ba4793eda3e7ac238c864a5d029a\trefs/heads/master c44fe79bb5e36941ce799cee6b9de3a2ef89afee\trefs/tags/0.5.2 14534f0e0bf133dc9ff6dbe74f8a0c863ff3ac6d\trefs/tags/0.5.4 d36fddb4291341a1ff2ecc3c560494e398881354\trefs/tags/0.5.6 9e5ce9031cbee81015de680d010b603bce2dd15f\trefs/tags/0.6.0 9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13\trefs/tags/0.6.2 014a69af1cdce619de82afaf6cdb4e6ac658fede\trefs/tags/0.7.0 fa8097ff666af3ce64761d8e1f1c2aa292a11378\trefs/tags/0.7.2 44f9be0b33ba5c10027ddb030a5b2f0faa3eeb8d\trefs/tags/0.7.4 d7b9957f67236fa54e660cc3ab45ffecd6e0ba38\trefs/tags/0.7.8 b7d19519a1c15f1c1a324e2683bd728b6198ce5a\trefs/tags/0.7.8^{} ebc1b392fe7e8f0fbabc305c299b4d365d2b4d9b\trefs/tags/chef-server-package SHAS @resource.revision "" expect(@provider).to receive(:shell_out!).with(@git_ls_remote + "\"HEAD\"", { :log_tag => "git[web2.0 app]" }).and_return(double("ShellOut result", :stdout => @stdout)) expect(@provider.target_revision).to eql("28af684d8460ba4793eda3e7ac238c864a5d029a") end end it "responds to :revision_slug as an alias for target_revision" do expect(@provider).to respond_to(:revision_slug) end context "with an ssh wrapper" do let(:deploy_user) { "deployNinja" } let(:wrapper) { "do_it_this_way.sh" } let(:expected_cmd) { 'git clone "git://github.com/opscode/chef.git" "/my/deploy/dir"' } let(:default_options) do { :user => deploy_user, :environment => { "GIT_SSH" => wrapper, "HOME" => "/home/deployNinja" }, :log_tag => "git[web2.0 app]", } end before do @resource.user deploy_user @resource.ssh_wrapper wrapper allow(Etc).to receive(:getpwnam).and_return(double("Struct::Passwd", :name => @resource.user, :dir => "/home/deployNinja")) end context "without a timeout set" do it "clones a repo with default git options" do expect(@provider).to receive(:shell_out!).with(expected_cmd, default_options) @provider.clone end end context "with a timeout set" do let (:seconds) { 10 } before { @resource.timeout(seconds) } it "clones a repo with amended git options" do expect(@provider).to receive(:shell_out!).with(expected_cmd, default_options.merge(:timeout => seconds)) @provider.clone end end context "with a specific home" do let (:override_home) do { "HOME" => "/home/masterNinja" } end let(:overrided_options) do { :user => deploy_user, :environment => { "GIT_SSH" => wrapper, "HOME" => "/home/masterNinja" }, :log_tag => "git[web2.0 app]", } end before do @resource.environment(override_home) end before { @resource.environment(override_home) } it "clones a repo with amended git options with specific home" do expect(@provider).to receive(:shell_out!).with(expected_cmd, overrided_options) @provider.clone end end end context "with a user id" do let(:deploy_user) { 123 } let(:expected_cmd) { 'git clone "git://github.com/opscode/chef.git" "/my/deploy/dir"' } let(:default_options) do { :user => 123, :environment => { "HOME" => "/home/deployNinja" }, :log_tag => "git[web2.0 app]", } end before do @resource.user deploy_user allow(Etc).to receive(:getpwuid).and_return(double("Struct::Passwd", :name => @resource.user, :dir => "/home/deployNinja")) end context "with a specific home" do let (:override_home) do { "HOME" => "/home/masterNinja" } end let(:overrided_options) do { :user => 123, :environment => { "HOME" => "/home/masterNinja" }, :log_tag => "git[web2.0 app]", } end before do @resource.environment(override_home) end before { @resource.environment(override_home) } it "clones a repo with amended git options with specific home" do expect(@provider).to receive(:shell_out!).with(expected_cmd, hash_including(overrided_options)) @provider.clone end end end it "runs a clone command with escaped destination" do @resource.user "deployNinja" allow(Etc).to receive(:getpwnam).and_return(double("Struct::Passwd", :name => @resource.user, :dir => "/home/deployNinja")) @resource.destination "/Application Support/with/space" @resource.ssh_wrapper "do_it_this_way.sh" expected_cmd = "git clone \"git://github.com/opscode/chef.git\" \"/Application Support/with/space\"" expect(@provider).to receive(:shell_out!).with(expected_cmd, :user => "deployNinja", :log_tag => "git[web2.0 app]", :environment => { "HOME" => "/home/deployNinja", "GIT_SSH" => "do_it_this_way.sh" }) @provider.clone end it "compiles a clone command using --depth for shallow cloning" do @resource.depth 5 expected_cmd = "git clone --depth 5 \"git://github.com/opscode/chef.git\" \"/my/deploy/dir\"" version_response = double("shell_out") allow(version_response).to receive(:stdout) { "git version 1.7.9" } expect(@provider).to receive(:shell_out!).with("git --version", :log_tag => "git[web2.0 app]").and_return(version_response) expect(@provider).to receive(:shell_out!).with(expected_cmd, :log_tag => "git[web2.0 app]") @provider.clone end it "compiles a clone command using --no-single-branch for shallow cloning when git >= 1.7.10" do @resource.depth 5 expected_cmd = "git clone --depth 5 --no-single-branch \"git://github.com/opscode/chef.git\" \"/my/deploy/dir\"" version_response = double("shell_out") allow(version_response).to receive(:stdout) { "git version 1.7.10" } expect(@provider).to receive(:shell_out!).with("git --version", :log_tag => "git[web2.0 app]").and_return(version_response) expect(@provider).to receive(:shell_out!).with(expected_cmd, :log_tag => "git[web2.0 app]") @provider.clone end it "compiles a clone command with a remote other than ``origin''" do @resource.remote "opscode" expected_cmd = "git clone -o opscode \"git://github.com/opscode/chef.git\" \"/my/deploy/dir\"" expect(@provider).to receive(:shell_out!).with(expected_cmd, :log_tag => "git[web2.0 app]") @provider.clone end it "runs a checkout command with default options" do expect(@provider).to receive(:shell_out!).with("git branch -f deploy d35af14d41ae22b19da05d7d03a0bafc321b244c", :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]").ordered expect(@provider).to receive(:shell_out!).with("git checkout deploy", :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]").ordered @provider.checkout end it "runs an enable_submodule command" do @resource.enable_submodules true expected_cmd = "git submodule sync" expect(@provider).to receive(:shell_out!).with(expected_cmd, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") expected_cmd = "git submodule update --init --recursive" expect(@provider).to receive(:shell_out!).with(expected_cmd, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") @provider.enable_submodules end it "does nothing for enable_submodules if resource.enable_submodules #=> false" do expect(@provider).not_to receive(:shell_out!) @provider.enable_submodules end it "runs a sync command with default options" do expect(@provider).to receive(:setup_remote_tracking_branches).with(@resource.remote, @resource.repository) expected_cmd1 = "git fetch origin" expect(@provider).to receive(:shell_out!).with(expected_cmd1, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") expected_cmd2 = "git fetch origin --tags" expect(@provider).to receive(:shell_out!).with(expected_cmd2, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") expected_cmd3 = "git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c" expect(@provider).to receive(:shell_out!).with(expected_cmd3, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") @provider.fetch_updates end it "runs a sync command with the user and group specified in the resource" do @resource.user("whois") allow(Etc).to receive(:getpwnam).and_return(double("Struct::Passwd", :name => @resource.user, :dir => "/home/whois")) @resource.group("thisis") expect(@provider).to receive(:setup_remote_tracking_branches).with(@resource.remote, @resource.repository) expected_cmd1 = "git fetch origin" expect(@provider).to receive(:shell_out!).with(expected_cmd1, :cwd => "/my/deploy/dir", :user => "whois", :group => "thisis", :log_tag => "git[web2.0 app]", :environment => { "HOME" => "/home/whois" }) expected_cmd2 = "git fetch origin --tags" expect(@provider).to receive(:shell_out!).with(expected_cmd2, :cwd => "/my/deploy/dir", :user => "whois", :group => "thisis", :log_tag => "git[web2.0 app]", :environment => { "HOME" => "/home/whois" }) expected_cmd3 = "git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c" expect(@provider).to receive(:shell_out!).with(expected_cmd3, :cwd => "/my/deploy/dir", :user => "whois", :group => "thisis", :log_tag => "git[web2.0 app]", :environment => { "HOME" => "/home/whois" }) @provider.fetch_updates end it "configures remote tracking branches when remote is ``origin''" do @resource.remote "origin" expect(@provider).to receive(:setup_remote_tracking_branches).with(@resource.remote, @resource.repository) fetch_command1 = "git fetch origin" expect(@provider).to receive(:shell_out!).with(fetch_command1, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") fetch_command2 = "git fetch origin --tags" expect(@provider).to receive(:shell_out!).with(fetch_command2, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") fetch_command3 = "git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c" expect(@provider).to receive(:shell_out!).with(fetch_command3, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") @provider.fetch_updates end it "configures remote tracking branches when remote is not ``origin''" do @resource.remote "opscode" expect(@provider).to receive(:setup_remote_tracking_branches).with(@resource.remote, @resource.repository) fetch_command1 = "git fetch opscode" expect(@provider).to receive(:shell_out!).with(fetch_command1, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") fetch_command2 = "git fetch opscode --tags" expect(@provider).to receive(:shell_out!).with(fetch_command2, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") fetch_command3 = "git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c" expect(@provider).to receive(:shell_out!).with(fetch_command3, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") @provider.fetch_updates end context "configuring remote tracking branches" do it "checks if a remote with this name already exists" do command_response = double("shell_out") allow(command_response).to receive(:exitstatus) { 1 } expected_command = "git config --get remote.#{@resource.remote}.url" expect(@provider).to receive(:shell_out!).with(expected_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]", :returns => [0, 1, 2]).and_return(command_response) add_remote_command = "git remote add #{@resource.remote} #{@resource.repository}" expect(@provider).to receive(:shell_out!).with(add_remote_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository) end it "runs the config with the user and group specified in the resource" do @resource.user("whois") @resource.group("thisis") allow(Etc).to receive(:getpwnam).and_return(double("Struct::Passwd", :name => @resource.user, :dir => "/home/whois")) command_response = double("shell_out") allow(command_response).to receive(:exitstatus) { 1 } expected_command = "git config --get remote.#{@resource.remote}.url" expect(@provider).to receive(:shell_out!).with(expected_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]", :user => "whois", :group => "thisis", :environment => { "HOME" => "/home/whois" }, :returns => [0, 1, 2]).and_return(command_response) add_remote_command = "git remote add #{@resource.remote} #{@resource.repository}" expect(@provider).to receive(:shell_out!).with(add_remote_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]", :user => "whois", :group => "thisis", :environment => { "HOME" => "/home/whois" }) @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository) end describe "when a remote with a given name hasn't been configured yet" do it "adds a new remote " do command_response = double("shell_out") allow(command_response).to receive(:exitstatus) { 1 } check_remote_command = "git config --get remote.#{@resource.remote}.url" expect(@provider).to receive(:shell_out!).with(check_remote_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]", :returns => [0, 1, 2]).and_return(command_response) expected_command = "git remote add #{@resource.remote} #{@resource.repository}" expect(@provider).to receive(:shell_out!).with(expected_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository) end end describe "when a remote with a given name has already been configured" do it "updates remote url when the url is different" do command_response = double("shell_out") allow(command_response).to receive(:exitstatus) { 0 } allow(command_response).to receive(:stdout) { "some_other_url" } check_remote_command = "git config --get remote.#{@resource.remote}.url" expect(@provider).to receive(:shell_out!).with(check_remote_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]", :returns => [0, 1, 2]).and_return(command_response) expected_command = "git config --replace-all remote.#{@resource.remote}.url #{@resource.repository}" expect(@provider).to receive(:shell_out!).with(expected_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository) end it "doesn't update remote url when the url is the same" do command_response = double("shell_out") allow(command_response).to receive(:exitstatus) { 0 } allow(command_response).to receive(:stdout) { @resource.repository } check_remote_command = "git config --get remote.#{@resource.remote}.url" expect(@provider).to receive(:shell_out!).with(check_remote_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]", :returns => [0, 1, 2]).and_return(command_response) unexpected_command = "git config --replace-all remote.#{@resource.remote}.url #{@resource.repository}" expect(@provider).not_to receive(:shell_out!).with(unexpected_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository) end it "resets remote url when it has multiple values" do command_response = double("shell_out") allow(command_response).to receive(:exitstatus) { 2 } check_remote_command = "git config --get remote.#{@resource.remote}.url" expect(@provider).to receive(:shell_out!).with(check_remote_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]", :returns => [0, 1, 2]).and_return(command_response) expected_command = "git config --replace-all remote.#{@resource.remote}.url #{@resource.repository}" expect(@provider).to receive(:shell_out!).with(expected_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]") @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository) end end end it "raises an error if the git clone command would fail because the enclosing directory doesn't exist" do allow(@provider).to receive(:shell_out!) expect { @provider.run_action(:sync) }.to raise_error(Chef::Exceptions::MissingParentDirectory) end it "does a checkout by cloning the repo and then enabling submodules" do # will be invoked in load_current_resource allow(::File).to receive(:exist?).with("/my/deploy/dir/.git").and_return(false) allow(::File).to receive(:exist?).with("/my/deploy/dir").and_return(true) allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) allow(::Dir).to receive(:entries).with("/my/deploy/dir").and_return([".", ".."]) expect(@provider).to receive(:clone) expect(@provider).to receive(:checkout) expect(@provider).to receive(:enable_submodules) @provider.run_action(:checkout) # Even though an actual run will cause an update to occur, the fact that we've stubbed out # the actions above will prevent updates from registering # @resource.should be_updated end it "does not call checkout if enable_checkout is false" do # will be invoked in load_current_resource allow(::File).to receive(:exist?).with("/my/deploy/dir/.git").and_return(false) allow(::File).to receive(:exist?).with("/my/deploy/dir").and_return(true) allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) allow(::Dir).to receive(:entries).with("/my/deploy/dir").and_return([".", ".."]) @resource.enable_checkout false expect(@provider).to receive(:clone) expect(@provider).not_to receive(:checkout) expect(@provider).to receive(:enable_submodules) @provider.run_action(:checkout) end # REGRESSION TEST: on some OSes, the entries from an empty directory will be listed as # ['..', '.'] but this shouldn't change the behavior it "does a checkout by cloning the repo and then enabling submodules when the directory entries are listed as %w{.. .}" do allow(::File).to receive(:exist?).with("/my/deploy/dir/.git").and_return(false) allow(::File).to receive(:exist?).with("/my/deploy/dir").and_return(false) allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) allow(::Dir).to receive(:entries).with("/my/deploy/dir").and_return(["..", "."]) expect(@provider).to receive(:clone) expect(@provider).to receive(:checkout) expect(@provider).to receive(:enable_submodules) expect(@provider).to receive(:add_remotes) @provider.run_action(:checkout) # @resource.should be_updated end it "should not checkout if the destination exists or is a non empty directory" do # will be invoked in load_current_resource allow(::File).to receive(:exist?).with("/my/deploy/dir/.git").and_return(false) allow(::File).to receive(:exist?).with("/my/deploy/dir").and_return(true) allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) allow(::Dir).to receive(:entries).with("/my/deploy/dir").and_return([".", "..", "foo", "bar"]) expect(@provider).not_to receive(:clone) expect(@provider).not_to receive(:checkout) expect(@provider).not_to receive(:enable_submodules) expect(@provider).not_to receive(:add_remotes) @provider.run_action(:checkout) expect(@resource).not_to be_updated end it "syncs the code by updating the source when the repo has already been checked out" do expect(::File).to receive(:exist?).with("/my/deploy/dir/.git").and_return(true) allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) expect(@provider).to receive(:find_current_revision).exactly(1).and_return("d35af14d41ae22b19da05d7d03a0bafc321b244c") expect(@provider).not_to receive(:fetch_updates) expect(@provider).to receive(:add_remotes) @provider.run_action(:sync) expect(@resource).not_to be_updated end it "marks the resource as updated when the repo is updated and gets a new version" do expect(::File).to receive(:exist?).with("/my/deploy/dir/.git").and_return(true) allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) # invoked twice - first time from load_current_resource expect(@provider).to receive(:find_current_revision).exactly(1).and_return("d35af14d41ae22b19da05d7d03a0bafc321b244c") allow(@provider).to receive(:target_revision).and_return("28af684d8460ba4793eda3e7ac238c864a5d029a") expect(@provider).to receive(:fetch_updates) expect(@provider).to receive(:enable_submodules) expect(@provider).to receive(:add_remotes) @provider.run_action(:sync) # @resource.should be_updated end it "does not fetch any updates if the remote revision matches the current revision" do expect(::File).to receive(:exist?).with("/my/deploy/dir/.git").and_return(true) allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) allow(@provider).to receive(:find_current_revision).and_return("d35af14d41ae22b19da05d7d03a0bafc321b244c") allow(@provider).to receive(:target_revision).and_return("d35af14d41ae22b19da05d7d03a0bafc321b244c") expect(@provider).not_to receive(:fetch_updates) expect(@provider).to receive(:add_remotes) @provider.run_action(:sync) expect(@resource).not_to be_updated end it "clones the repo instead of fetching it if the deploy directory doesn't exist" do allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) expect(::File).to receive(:exist?).with("/my/deploy/dir/.git").exactly(2).and_return(false) expect(@provider).to receive(:action_checkout) expect(@provider).not_to receive(:shell_out!) @provider.run_action(:sync) # @resource.should be_updated end it "clones the repo instead of fetching updates if the deploy directory is empty" do expect(::File).to receive(:exist?).with("/my/deploy/dir/.git").exactly(2).and_return(false) allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) allow(::File).to receive(:directory?).with("/my/deploy/dir").and_return(true) allow(@provider).to receive(:sync_command).and_return("huzzah!") expect(@provider).to receive(:action_checkout) expect(@provider).not_to receive(:shell_out!).with("huzzah!", :cwd => "/my/deploy/dir") @provider.run_action(:sync) #@resource.should be_updated end it "does an export by cloning the repo then removing the .git directory" do expect(@provider).to receive(:action_checkout) expect(FileUtils).to receive(:rm_rf).with(@resource.destination + "/.git") @provider.run_action(:export) expect(@resource).to be_updated end describe "calling add_remotes" do it "adds a new remote for each entry in additional remotes hash" do @resource.additional_remotes({ :opscode => "opscode_repo_url", :another_repo => "some_other_repo_url" }) allow(STDOUT).to receive(:tty?).and_return(false) command_response = double("shell_out") allow(command_response).to receive(:exitstatus) { 0 } @resource.additional_remotes.each_pair do |remote_name, remote_url| expect(@provider).to receive(:setup_remote_tracking_branches).with(remote_name, remote_url) end @provider.add_remotes end end describe "calling multiple_remotes?" do before(:each) do @command_response = double("shell_out") end describe "when check remote command returns with status 2" do it "returns true" do allow(@command_response).to receive(:exitstatus) { 2 } expect(@provider.multiple_remotes?(@command_response)).to be_truthy end end describe "when check remote command returns with status 0" do it "returns false" do allow(@command_response).to receive(:exitstatus) { 0 } expect(@provider.multiple_remotes?(@command_response)).to be_falsey end end describe "when check remote command returns with status 0" do it "returns false" do allow(@command_response).to receive(:exitstatus) { 1 } expect(@provider.multiple_remotes?(@command_response)).to be_falsey end end end describe "calling remote_matches?" do before(:each) do @command_response = double("shell_out") end describe "when output of the check remote command matches the repository url" do it "returns true" do allow(@command_response).to receive(:exitstatus) { 0 } allow(@command_response).to receive(:stdout) { @resource.repository } expect(@provider.remote_matches?(@resource.repository, @command_response)).to be_truthy end end describe "when output of the check remote command doesn't match the repository url" do it "returns false" do allow(@command_response).to receive(:exitstatus) { 0 } allow(@command_response).to receive(:stdout) { @resource.repository + "test" } expect(@provider.remote_matches?(@resource.repository, @command_response)).to be_falsey end end end end chef-12.14.60/spec/unit/provider/group/000077500000000000000000000000001276456504500175675ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/group/dscl_spec.rb000066400000000000000000000276421276456504500220660ustar00rootroot00000000000000# # Author:: Dreamcat4 () # Copyright:: Copyright 2009-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::Group::Dscl do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("aj") @current_resource = Chef::Resource::Group.new("aj") @provider = Chef::Provider::Group::Dscl.new(@new_resource, @run_context) @provider.current_resource = @current_resource @status = double(:stdout => "\n", :stderr => "", :exitstatus => 0) allow(@provider).to receive(:shell_out).and_return(@status) end it "should run shell_out with the supplied array of arguments appended to the dscl command" do expect(@provider).to receive(:shell_out).with("dscl . -cmd /Path arg1 arg2") @provider.dscl("cmd", "/Path", "arg1", "arg2") end it "should return an array of four elements - cmd, status, stdout, stderr" do dscl_retval = @provider.dscl("cmd /Path args") expect(dscl_retval).to be_a_kind_of(Array) expect(dscl_retval).to eq(["dscl . -cmd /Path args", @status, "\n", ""]) end describe "safe_dscl" do before do @node = Chef::Node.new @provider = Chef::Provider::Group::Dscl.new(@node, @new_resource) allow(@provider).to receive(:dscl).and_return(["cmd", @status, "stdout", "stderr"]) end it "should run dscl with the supplied cmd /Path args" do expect(@provider).to receive(:dscl).with("cmd /Path args") @provider.safe_dscl("cmd /Path args") end describe "with the dscl command returning a non zero exit status for a delete" do before do @status = double("Process::Status", :exitstatus => 1) allow(@provider).to receive(:dscl).and_return(["cmd", @status, "stdout", "stderr"]) end it "should return an empty string of standard output for a delete" do safe_dscl_retval = @provider.safe_dscl("delete /Path args") expect(safe_dscl_retval).to be_a_kind_of(String) expect(safe_dscl_retval).to eq("") end it "should raise an exception for any other command" do expect { @provider.safe_dscl("cmd /Path arguments") }.to raise_error(Chef::Exceptions::Group) end end describe "with the dscl command returning no such key" do before do allow(@provider).to receive(:dscl).and_return(["cmd", @status, "No such key: ", "stderr"]) end it "should raise an exception" do expect { @provider.safe_dscl("cmd /Path arguments") }.to raise_error(Chef::Exceptions::Group) end end describe "with the dscl command returning a zero exit status" do it "should return the third array element, the string of standard output" do safe_dscl_retval = @provider.safe_dscl("cmd /Path args") expect(safe_dscl_retval).to be_a_kind_of(String) expect(safe_dscl_retval).to eq("stdout") end end end describe "get_free_gid" do before do @node = Chef::Node.new @provider = Chef::Provider::Group::Dscl.new(@node, @new_resource) allow(@provider).to receive(:safe_dscl).and_return("\naj 200\njt 201\n") end it "should run safe_dscl with list /Groups gid" do expect(@provider).to receive(:safe_dscl).with("list /Groups gid") @provider.get_free_gid end it "should return the first unused gid number on or above 200" do expect(@provider.get_free_gid).to equal(202) end it "should raise an exception when the search limit is exhausted" do search_limit = 1 expect { @provider.get_free_gid(search_limit) }.to raise_error(RuntimeError) end end describe "gid_used?" do before do @node = Chef::Node.new @provider = Chef::Provider::Group::Dscl.new(@node, @new_resource) allow(@provider).to receive(:safe_dscl).and_return("\naj 500\n") end it "should run safe_dscl with list /Groups gid" do expect(@provider).to receive(:safe_dscl).with("list /Groups gid") @provider.gid_used?(500) end it "should return true for a used gid number" do expect(@provider.gid_used?(500)).to be_truthy end it "should return false for an unused gid number" do expect(@provider.gid_used?(501)).to be_falsey end it "should return false if not given any valid gid number" do expect(@provider.gid_used?(nil)).to be_falsey end end describe "set_gid" do describe "with the new resource and a gid number which is already in use" do before do allow(@provider).to receive(:gid_used?).and_return(true) end it "should raise an exception if the new resources gid is already in use" do expect { @provider.set_gid }.to raise_error(Chef::Exceptions::Group) end end describe "with no gid number for the new resources" do it "should run get_free_gid and return a valid, unused gid number" do expect(@provider).to receive(:get_free_gid).and_return(501) @provider.set_gid end end describe "with blank gid number for the new resources" do before do @new_resource.instance_variable_set(:@gid, nil) allow(@new_resource).to receive(:safe_dscl) end it "should run get_free_gid and return a valid, unused gid number" do expect(@provider).to receive(:get_free_gid).and_return(501) @provider.set_gid end end describe "with a valid gid number which is not already in use" do it "should run safe_dscl with create /Groups/group PrimaryGroupID gid" do allow(@provider).to receive(:get_free_gid).and_return(50) expect(@provider).to receive(:safe_dscl).with("list /Groups gid") expect(@provider).to receive(:safe_dscl).with("create /Groups/aj PrimaryGroupID 50").and_return(true) @provider.set_gid end end end describe "set_members" do describe "with existing members in the current resource and append set to false in the new resource" do before do allow(@new_resource).to receive(:members).and_return([]) allow(@new_resource).to receive(:append).and_return(false) allow(@current_resource).to receive(:members).and_return(%w{all your base}) end it "should log an appropriate message" do expect(Chef::Log).to receive(:debug).with("group[aj] removing group members all your base") @provider.set_members end it "should run safe_dscl with create /Groups/group GroupMembership to clear the Group's UID list" do expect(@provider).to receive(:safe_dscl).with("create /Groups/aj GroupMembers ''").and_return(true) expect(@provider).to receive(:safe_dscl).with("create /Groups/aj GroupMembership ''").and_return(true) @provider.set_members end end describe "with supplied members in the new resource" do before do @new_resource.members(%w{all your base}) @current_resource.members([]) end it "should log an appropriate debug message" do expect(Chef::Log).to receive(:debug).with("group[aj] setting group members all, your, base") @provider.set_members end it "should run safe_dscl with append /Groups/group GroupMembership and group members all, your, base" do expect(@provider).to receive(:safe_dscl).with("create /Groups/aj GroupMembers ''").and_return(true) expect(@provider).to receive(:safe_dscl).with("append /Groups/aj GroupMembership all your base").and_return(true) expect(@provider).to receive(:safe_dscl).with("create /Groups/aj GroupMembership ''").and_return(true) @provider.set_members end end describe "with no members in the new resource" do before do @new_resource.append(true) @new_resource.members([]) end it "should not call safe_dscl" do expect(@provider).not_to receive(:safe_dscl) @provider.set_members end end end describe "when loading the current system state" do before (:each) do @provider.action = :create @provider.load_current_resource @provider.define_resource_requirements end it "raises an error if the required binary /usr/bin/dscl doesn't exist" do expect(File).to receive(:exists?).with("/usr/bin/dscl").and_return(false) expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Group) end it "doesn't raise an error if /usr/bin/dscl exists" do allow(File).to receive(:exists?).and_return(true) expect { @provider.process_resource_requirements }.not_to raise_error end end describe "when creating the group" do it "creates the group, password field, gid, and sets group membership" do expect(@provider).to receive(:set_gid).and_return(true) expect(@provider).to receive(:set_members).and_return(true) expect(@provider).to receive(:safe_dscl).with("create /Groups/aj Password '*'") expect(@provider).to receive(:safe_dscl).with("create /Groups/aj") @provider.create_group end end describe "managing the group" do it "should manage the group_name if it changed and the new resources group_name is not null" do @current_resource.group_name("oldval") @new_resource.group_name("newname") expect(@provider).to receive(:set_members).and_return(true) expect(@provider).to receive(:safe_dscl).with("create /Groups/newname") expect(@provider).to receive(:safe_dscl).with("create /Groups/newname Password '*'") @provider.manage_group end it "should manage the gid if it changed and the new resources gid is not null" do @current_resource.gid(23) @new_resource.gid(42) expect(@provider).to receive(:set_gid) @provider.manage_group end it "should manage the members if it changed and the new resources members is not null" do @current_resource.members(%{charlie root}) @new_resource.members(%{crab revenge}) expect(@provider).to receive(:set_members) @provider.manage_group end end describe "remove_group" do it "should run safe_dscl with delete /Groups/group and with the new resources group name" do expect(@provider).to receive(:safe_dscl).with("delete /Groups/aj").and_return(true) @provider.remove_group end end end describe "Test DSCL loading" do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("group name aj") @new_resource.group_name("aj") @provider = Chef::Provider::Group::Dscl.new(@new_resource, @run_context) @output = <<-EOF AppleMetaNodeLocation: /Local/Default Comment: Test Group GeneratedUID: AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA NestedGroups: AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAB Password: * PrimaryGroupID: 999 RealName: TestGroup RecordName: com.apple.aj RecordType: dsRecTypeStandard:Groups GroupMembership: waka bar EOF allow(@provider).to receive(:safe_dscl).with("read /Groups/aj").and_return(@output) @current_resource = @provider.load_current_resource end it "should parse gid properly" do allow(File).to receive(:exists?).and_return(true) expect(@current_resource.gid).to eq("999") end it "should parse members properly" do allow(File).to receive(:exists?).and_return(true) expect(@current_resource.members).to eq(%w{waka bar}) end end chef-12.14.60/spec/unit/provider/group/gpasswd_spec.rb000066400000000000000000000104041276456504500225750ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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::Group::Gpasswd, "modify_group_members" do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("wheel") @new_resource.members %w{lobster rage fist} @new_resource.append false @provider = Chef::Provider::Group::Gpasswd.new(@new_resource, @run_context) #@provider.stub(:run_command).and_return(true) end describe "when determining the current group state" do before (:each) do @provider.action = :create @provider.load_current_resource @provider.define_resource_requirements end # Checking for required binaries is already done in the spec # for Chef::Provider::Group - no need to repeat it here. We'll # include only what's specific to this provider. it "should raise an error if the required binary /usr/bin/gpasswd doesn't exist" do allow(File).to receive(:exists?).and_return(true) expect(File).to receive(:exists?).with("/usr/bin/gpasswd").and_return(false) expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Group) end it "shouldn't raise an error if the required binaries exist" do allow(File).to receive(:exists?).and_return(true) expect { @provider.process_resource_requirements }.not_to raise_error end end describe "after the group's current state is known" do before do @current_resource = @new_resource.dup @provider.current_resource = @new_resource end describe "when no group members are specified and append is not set" do before do @new_resource.append(false) @new_resource.members([]) end it "logs a message and sets group's members to 'none'" do expect(Chef::Log).to receive(:debug).with("group[wheel] setting group members to: none") expect(@provider).to receive(:shell_out!).with("gpasswd -M \"\" wheel") @provider.modify_group_members end end describe "when no group members are specified and append is set" do before do @new_resource.append(true) @new_resource.members([]) end it "does not modify group membership" do expect(@provider).not_to receive(:shell_out!) @provider.modify_group_members end end describe "when the resource specifies group members" do it "should log an appropriate debug message" do expect(Chef::Log).to receive(:debug).with("group[wheel] setting group members to: lobster, rage, fist") allow(@provider).to receive(:shell_out!) @provider.modify_group_members end it "should run gpasswd with the members joined by ',' followed by the target group" do expect(@provider).to receive(:shell_out!).with("gpasswd -M lobster,rage,fist wheel") @provider.modify_group_members end describe "when no user exists in the system" do before do current_resource = @new_resource.dup current_resource.members([ ]) @provider.current_resource = current_resource end it "should run gpasswd individually for each user when the append option is set" do @new_resource.append(true) expect(@provider).to receive(:shell_out!).with("gpasswd -a lobster wheel") expect(@provider).to receive(:shell_out!).with("gpasswd -a rage wheel") expect(@provider).to receive(:shell_out!).with("gpasswd -a fist wheel") @provider.modify_group_members end end end end end chef-12.14.60/spec/unit/provider/group/groupadd_spec.rb000066400000000000000000000151361276456504500227410ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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::Group::Groupadd, "set_options" do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("aj") @new_resource.gid(50) @new_resource.members(%w{root aj}) @new_resource.system false @new_resource.non_unique false @current_resource = Chef::Resource::Group.new("aj") @current_resource.gid(50) @current_resource.members(%w{root aj}) @current_resource.system false @current_resource.non_unique false @provider = Chef::Provider::Group::Groupadd.new(@new_resource, @run_context) @provider.current_resource = @current_resource end field_list = { :gid => "-g", } field_list.each do |attribute, option| it "should check for differences in #{attribute} between the current and new resources" do expect(@new_resource).to receive(attribute) expect(@current_resource).to receive(attribute) @provider.set_options end it "should set the option for #{attribute} if the new resources #{attribute} is not null" do allow(@new_resource).to receive(attribute).and_return("wowaweea") expect(@provider.set_options).to eql(" #{option} '#{@new_resource.send(attribute)}' #{@new_resource.group_name}") end end it "should combine all the possible options" do match_string = "" field_list.sort { |a, b| a[0] <=> b[0] }.each do |attribute, option| allow(@new_resource).to receive(attribute).and_return("hola") match_string << " #{option} 'hola'" end match_string << " aj" expect(@provider.set_options).to eql(match_string) end describe "when we want to create a system group" do it "should not set groupadd_options '-r' when system is false" do @new_resource.system(false) expect(@provider.groupadd_options).not_to match(/-r/) end it "should set groupadd -r if system is true" do @new_resource.system(true) expect(@provider.groupadd_options).to eq(" -r") end end describe "when we want to create a non_unique gid group" do it "should not set groupadd_options '-o' when non_unique is false" do @new_resource.non_unique(false) expect(@provider.groupadd_options).not_to match(/-o/) end it "should set groupadd -o if non_unique is true" do @new_resource.non_unique(true) expect(@provider.groupadd_options).to eq(" -o") end end end describe Chef::Provider::Group::Groupadd, "create_group" do before do @node = Chef::Node.new @new_resource = Chef::Resource::Group.new("aj") @provider = Chef::Provider::Group::Groupadd.new(@node, @new_resource) allow(@provider).to receive(:run_command).and_return(true) allow(@provider).to receive(:set_options).and_return(" monkey") allow(@provider).to receive(:groupadd_options).and_return("") allow(@provider).to receive(:modify_group_members).and_return(true) end it "should run groupadd with the return of set_options" do expect(@provider).to receive(:run_command).with({ :command => "groupadd monkey" }).and_return(true) @provider.create_group end it "should modify the group members" do expect(@provider).to receive(:modify_group_members).and_return(true) @provider.create_group end end describe Chef::Provider::Group::Groupadd do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("aj") @provider = Chef::Provider::Group::Groupadd.new(@new_resource, @run_context) allow(@provider).to receive(:run_command).and_return(true) allow(@provider).to receive(:set_options).and_return(" monkey") end describe "manage group" do it "should run groupmod with the return of set_options" do allow(@provider).to receive(:modify_group_members).and_return(true) expect(@provider).to receive(:run_command).with({ :command => "groupmod monkey" }).and_return(true) @provider.manage_group end it "should modify the group members" do expect(@provider).to receive(:modify_group_members).and_return(true) @provider.manage_group end end describe "remove_group" do it "should run groupdel with the new resources group name" do expect(@provider).to receive(:run_command).with({ :command => "groupdel aj" }).and_return(true) @provider.remove_group end end [:add_member, :remove_member, :set_members].each do |m| it "should raise an error when calling #{m}" do expect { @provider.send(m, [ ]) }.to raise_error(Chef::Exceptions::Group, "you must override #{m} in #{@provider}") end end describe "load_current_resource" do before do allow(File).to receive(:exists?).and_return(false) @provider.define_resource_requirements end it "should raise an error if the required binary /usr/sbin/groupadd doesn't exist" do expect(File).to receive(:exists?).with("/usr/sbin/groupadd").and_return(false) expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Group) end it "should raise an error if the required binary /usr/sbin/groupmod doesn't exist" do expect(File).to receive(:exists?).with("/usr/sbin/groupadd").and_return(true) expect(File).to receive(:exists?).with("/usr/sbin/groupmod").and_return(false) expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Group) end it "should raise an error if the required binary /usr/sbin/groupdel doesn't exist" do expect(File).to receive(:exists?).with("/usr/sbin/groupadd").and_return(true) expect(File).to receive(:exists?).with("/usr/sbin/groupmod").and_return(true) expect(File).to receive(:exists?).with("/usr/sbin/groupdel").and_return(false) expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Group) end end end chef-12.14.60/spec/unit/provider/group/groupmod_spec.rb000066400000000000000000000121141276456504500227610ustar00rootroot00000000000000# # Author:: Dan Crosta () # 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" describe Chef::Provider::Group::Groupmod do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("wheel") @new_resource.gid 123 @new_resource.members %w{lobster rage fist} @new_resource.append false @provider = Chef::Provider::Group::Groupmod.new(@new_resource, @run_context) end describe "manage_group" do describe "when determining the current group state" do it "should raise an error if the required binary /usr/sbin/group doesn't exist" do expect(File).to receive(:exists?).with("/usr/sbin/group").and_return(false) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Group) end it "should raise an error if the required binary /usr/sbin/user doesn't exist" do expect(File).to receive(:exists?).with("/usr/sbin/group").and_return(true) expect(File).to receive(:exists?).with("/usr/sbin/user").and_return(false) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Group) end it "shouldn't raise an error if the required binaries exist" do allow(File).to receive(:exists?).and_return(true) expect { @provider.load_current_resource }.not_to raise_error end end describe "after the group's current state is known" do before do @current_resource = @new_resource.dup @provider.current_resource = @current_resource end describe "when no group members are specified and append is not set" do before do @new_resource.append(false) @new_resource.members([]) end it "logs a message and sets group's members to 'none', then removes existing group members" do expect(Chef::Log).to receive(:debug).with("group[wheel] setting group members to: none") expect(@provider).to receive(:shell_out!).with("group mod -n wheel_bak wheel") expect(@provider).to receive(:shell_out!).with("group add -g '123' -o wheel") expect(@provider).to receive(:shell_out!).with("group del wheel_bak") @provider.manage_group end end describe "when no group members are specified and append is set" do before do @new_resource.append(true) @new_resource.members([]) end it "logs a message and does not modify group membership" do expect(Chef::Log).to receive(:debug).with("group[wheel] not changing group members, the group has no members to add") expect(@provider).not_to receive(:shell_out!) @provider.manage_group end end describe "when removing some group members" do before do @new_resource.append(false) @new_resource.members(%w{ lobster }) end it "updates group membership correctly" do allow(Chef::Log).to receive(:debug) expect(@provider).to receive(:shell_out!).with("group mod -n wheel_bak wheel") expect(@provider).to receive(:shell_out!).with("user mod -G wheel lobster") expect(@provider).to receive(:shell_out!).with("group add -g '123' -o wheel") expect(@provider).to receive(:shell_out!).with("group del wheel_bak") @provider.manage_group end end end end describe "create_group" do describe "when creating a new group" do before do @current_resource = Chef::Resource::Group.new("wheel") @provider.current_resource = @current_resource end it "should run a group add command and some user mod commands" do expect(@provider).to receive(:shell_out!).with("group add -g '123' wheel") expect(@provider).to receive(:shell_out!).with("user mod -G wheel lobster") expect(@provider).to receive(:shell_out!).with("user mod -G wheel rage") expect(@provider).to receive(:shell_out!).with("user mod -G wheel fist") @provider.create_group end end end describe "remove_group" do describe "when removing an existing group" do before do @current_resource = @new_resource.dup @provider.current_resource = @current_resource end it "should run a group del command" do expect(@provider).to receive(:shell_out!).with("group del wheel") @provider.remove_group end end end end chef-12.14.60/spec/unit/provider/group/pw_spec.rb000066400000000000000000000117301276456504500215560ustar00rootroot00000000000000# # Author:: Stephen Haynes () # Copyright:: Copyright 2009-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::Group::Pw do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("wheel") @new_resource.gid 50 @new_resource.members %w{root aj} @current_resource = Chef::Resource::Group.new("aj") @current_resource.gid 50 @current_resource.members %w{root aj} @provider = Chef::Provider::Group::Pw.new(@new_resource, @run_context) @provider.current_resource = @current_resource end describe "when setting options for the pw command" do it "does not set the gid option if gids match or are unmanaged" do expect(@provider.set_options).to eq(" wheel") end it "sets the option for gid if it is not nil" do @new_resource.gid(42) expect(@provider.set_options).to eql(" wheel -g '42'") end end describe "when creating a group" do it "should run pw groupadd with the return of set_options and set_members_option" do @new_resource.gid(23) expect(@provider).to receive(:run_command).with({ :command => "pw groupadd wheel -g '23' -M root,aj" }).and_return(true) @provider.create_group end end describe "when managing the group" do it "should run pw groupmod with the return of set_options" do @new_resource.gid(42) @new_resource.members(["someone"]) expect(@provider).to receive(:run_command).with({ :command => "pw groupmod wheel -g '42' -m someone" }).and_return(true) expect(@provider).to receive(:run_command).with({ :command => "pw groupmod wheel -g '42' -d root,aj" }).and_return(true) @provider.manage_group end end describe "when removing the group" do it "should run pw groupdel with the new resources group name" do expect(@provider).to receive(:run_command).with({ :command => "pw groupdel wheel" }).and_return(true) @provider.remove_group end end describe "when setting group membership" do describe "with an empty members array in both the new and current resource" do before do allow(@new_resource).to receive(:members).and_return([]) allow(@current_resource).to receive(:members).and_return([]) end it "should set no options" do expect(@provider.set_members_options).to eql([ ]) end end describe "with an empty members array in the new resource and existing members in the current resource" do before do allow(@new_resource).to receive(:members).and_return([]) allow(@current_resource).to receive(:members).and_return(%w{all your base}) end it "should log an appropriate message" do expect(Chef::Log).to receive(:debug).with("group[wheel] removing group members: all,your,base") @provider.set_members_options end it "should set the -d option with the members joined by ','" do expect(@provider.set_members_options).to eql([ " -d all,your,base" ]) end end describe "with supplied members array in the new resource and an empty members array in the current resource" do before do allow(@new_resource).to receive(:members).and_return(%w{all your base}) allow(@current_resource).to receive(:members).and_return([]) end it "should log an appropriate debug message" do expect(Chef::Log).to receive(:debug).with("group[wheel] adding group members: all,your,base") @provider.set_members_options end it "should set the -m option with the members joined by ','" do expect(@provider.set_members_options).to eql([ " -m all,your,base" ]) end end end describe "load_current_resource" do before (:each) do @provider.action = :create @provider.load_current_resource @provider.define_resource_requirements end it "should raise an error if the required binary /usr/sbin/pw doesn't exist" do expect(File).to receive(:exists?).with("/usr/sbin/pw").and_return(false) expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Group) end it "shouldn't raise an error if /usr/sbin/pw exists" do allow(File).to receive(:exists?).and_return(true) expect { @provider.process_resource_requirements }.not_to raise_error end end end chef-12.14.60/spec/unit/provider/group/usermod_spec.rb000066400000000000000000000107431276456504500226110ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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::Group::Usermod do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("wheel") @new_resource.members %w{all your base} @new_resource.excluded_members [ ] @provider = Chef::Provider::Group::Usermod.new(@new_resource, @run_context) allow(@provider).to receive(:run_command) end describe "modify_group_members" do describe "with an empty members array" do before do allow(@new_resource).to receive(:append).and_return(true) allow(@new_resource).to receive(:members).and_return([]) end it "should log an appropriate message" do expect(@provider).not_to receive(:shell_out!) @provider.modify_group_members end end describe "with supplied members" do platforms = { "openbsd" => "-G", "netbsd" => "-G", "solaris" => "-a -G", "suse" => "-a -G", "opensuse" => "-a -G", "smartos" => "-G", "omnios" => "-G", } before do allow(@new_resource).to receive(:members).and_return(%w{all your base}) allow(File).to receive(:exists?).and_return(true) end it "should raise an error when setting the entire group directly" do @provider.define_resource_requirements @provider.load_current_resource @provider.instance_variable_set("@group_exists", true) @provider.action = :modify expect { @provider.run_action(@provider.process_resource_requirements) }.to raise_error(Chef::Exceptions::Group, "setting group members directly is not supported by #{@provider}, must set append true in group") end it "should raise an error when excluded_members are set" do @provider.define_resource_requirements @provider.load_current_resource @provider.instance_variable_set("@group_exists", true) @provider.action = :modify allow(@new_resource).to receive(:append).and_return(true) allow(@new_resource).to receive(:excluded_members).and_return(["someone"]) expect { @provider.run_action(@provider.process_resource_requirements) }.to raise_error(Chef::Exceptions::Group, "excluded_members is not supported by #{@provider}") end platforms.each do |platform, flags| it "should usermod each user when the append option is set on #{platform}" do current_resource = @new_resource.dup current_resource.members([ ]) @provider.current_resource = current_resource @node.automatic_attrs[:platform] = platform allow(@new_resource).to receive(:append).and_return(true) expect(@provider).to receive(:shell_out!).with("usermod #{flags} wheel all") expect(@provider).to receive(:shell_out!).with("usermod #{flags} wheel your") expect(@provider).to receive(:shell_out!).with("usermod #{flags} wheel base") @provider.modify_group_members end end end end describe "when loading the current resource" do before(:each) do allow(File).to receive(:exists?).and_return(false) @provider.action = :create @provider.define_resource_requirements end it "should raise an error if the required binary /usr/sbin/usermod doesn't exist" do allow(File).to receive(:exists?).and_return(true) expect(File).to receive(:exists?).with("/usr/sbin/usermod").and_return(false) expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Group) end it "shouldn't raise an error if the required binaries exist" do allow(File).to receive(:exists?).and_return(true) expect { @provider.process_resource_requirements }.not_to raise_error end end end chef-12.14.60/spec/unit/provider/group/windows_spec.rb000066400000000000000000000074011276456504500226220ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" class Chef class Util class Windows class NetGroup end end end end describe Chef::Provider::Group::Windows do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("staff") @net_group = double("Chef::Util::Windows::NetGroup") allow(Chef::Util::Windows::NetGroup).to receive(:new).and_return(@net_group) @provider = Chef::Provider::Group::Windows.new(@new_resource, @run_context) end describe "when creating the group" do it "should call @net_group.local_add" do expect(@net_group).to receive(:local_set_members).with([]) expect(@net_group).to receive(:local_add) @provider.create_group end end describe "manage_group" do before do @new_resource.members([ "us" ]) @current_resource = Chef::Resource::Group.new("staff") @current_resource.members %w{all your base} @new_resource.excluded_members %w{all} allow(Chef::Util::Windows::NetGroup).to receive(:new).and_return(@net_group) allow(@net_group).to receive(:local_add_members) allow(@net_group).to receive(:local_set_members) allow(@provider).to receive(:lookup_account_name) allow(@provider).to receive(:validate_member!).and_return(true) @provider.current_resource = @current_resource end it "should call @net_group.local_set_members" do allow(@new_resource).to receive(:append).and_return(false) expect(@net_group).to receive(:local_set_members).with(@new_resource.members) @provider.manage_group end it "should call @net_group.local_add_members" do allow(@new_resource).to receive(:append).and_return(true) expect(@net_group).to receive(:local_add_members).with(@new_resource.members) @provider.manage_group end it "should call @net_group.local_delete_members" do allow(@new_resource).to receive(:append).and_return(true) allow(@provider).to receive(:lookup_account_name).with("all").and_return("all") expect(@net_group).to receive(:local_delete_members).with(@new_resource.excluded_members) @provider.manage_group end end describe "remove_group" do before do allow(Chef::Util::Windows::NetGroup).to receive(:new).and_return(@net_group) allow(@provider).to receive(:run_command).and_return(true) end it "should call @net_group.local_delete" do expect(@net_group).to receive(:local_delete) @provider.remove_group end end end describe Chef::Provider::Group::Windows, "NetGroup" do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("Creating a new group") @new_resource.group_name "Remote Desktop Users" end it "sets group_name correctly" do expect(Chef::Util::Windows::NetGroup).to receive(:new).with("Remote Desktop Users") Chef::Provider::Group::Windows.new(@new_resource, @run_context) end end chef-12.14.60/spec/unit/provider/group_spec.rb000066400000000000000000000260261276456504500211340ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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::User do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Group.new("wheel", @run_context) @new_resource.gid 500 @new_resource.members "aj" @provider = Chef::Provider::Group.new(@new_resource, @run_context) @current_resource = Chef::Resource::Group.new("aj", @run_context) @current_resource.gid 500 @current_resource.members "aj" @provider.current_resource = @current_resource @pw_group = double("Struct::Group", :name => "wheel", :gid => 20, :mem => %w{root aj} ) allow(Etc).to receive(:getgrnam).with("wheel").and_return(@pw_group) end it "assumes the group exists by default" do expect(@provider.group_exists).to be_truthy end describe "when establishing the current state of the group" do it "sets the group name of the current resource to the group name of the new resource" do @provider.load_current_resource expect(@provider.current_resource.group_name).to eq("wheel") end it "does not modify the desired gid if set" do @provider.load_current_resource expect(@new_resource.gid).to eq(500) end it "sets the desired gid to the current gid if none is set" do @new_resource.instance_variable_set(:@gid, nil) @provider.load_current_resource expect(@new_resource.gid).to eq(20) end it "looks up the group in /etc/group with getgrnam" do expect(Etc).to receive(:getgrnam).with(@new_resource.group_name).and_return(@pw_group) @provider.load_current_resource expect(@provider.current_resource.gid).to eq(20) expect(@provider.current_resource.members).to eq(%w{root aj}) end it "should flip the value of exists if it cannot be found in /etc/group" do allow(Etc).to receive(:getgrnam).and_raise(ArgumentError) @provider.load_current_resource expect(@provider.group_exists).to be_falsey end it "should return the current resource" do expect(@provider.load_current_resource).to equal(@provider.current_resource) end end describe "when determining if the system is already in the target state" do [ :gid, :members ].each do |attribute| it "should return true if #{attribute} doesn't match" do allow(@current_resource).to receive(attribute).and_return("looooooooooooooooooool") expect(@provider.compare_group).to be_truthy end end it "should return false if gid and members are equal" do expect(@provider.compare_group).to be_falsey end it "should coerce an integer to a string for comparison" do allow(@current_resource).to receive(:gid).and_return("500") expect(@provider.compare_group).to be_falsey end it "should return false if append is true and the group member(s) already exists" do @current_resource.members << "extra_user" allow(@new_resource).to receive(:append).and_return(true) expect(@provider.compare_group).to be_falsey end it "should return true if append is true and the group member(s) do not already exist" do @new_resource.members << "extra_user" allow(@new_resource).to receive(:append).and_return(true) expect(@provider.compare_group).to be_truthy end it "should return false if append is true and excluded_members include a non existing member" do @new_resource.excluded_members << "extra_user" allow(@new_resource).to receive(:append).and_return(true) expect(@provider.compare_group).to be_falsey end it "should return true if the append is true and excluded_members include an existing user" do @new_resource.members.each { |m| @new_resource.excluded_members << m } @new_resource.members.clear allow(@new_resource).to receive(:append).and_return(true) expect(@provider.compare_group).to be_truthy end end describe "when creating a group" do it "should call create_group if the group does not exist" do @provider.group_exists = false expect(@provider).to receive(:create_group).and_return(true) @provider.run_action(:create) end it "should set the new_resources updated flag when it creates the group" do @provider.group_exists = false allow(@provider).to receive(:create_group) @provider.run_action(:create) expect(@provider.new_resource).to be_updated end it "should check to see if the group has mismatched attributes if the group exists" do @provider.group_exists = true allow(@provider).to receive(:compare_group).and_return(false) allow(@provider).to receive(:change_desc).and_return([ ]) @provider.run_action(:create) expect(@provider.new_resource).not_to be_updated end it "should call manage_group if the group exists and has mismatched attributes" do @provider.group_exists = true allow(@provider).to receive(:compare_group).and_return(true) allow(@provider).to receive(:change_desc).and_return([ ]) expect(@provider).to receive(:manage_group).and_return(true) @provider.run_action(:create) end it "should set the new_resources updated flag when it creates the group if we call manage_group" do @provider.group_exists = true allow(@provider).to receive(:compare_group).and_return(true) allow(@provider).to receive(:change_desc).and_return(["Some changes are going to be done."]) allow(@provider).to receive(:manage_group).and_return(true) @provider.run_action(:create) expect(@new_resource).to be_updated end end describe "when removing a group" do it "should not call remove_group if the group does not exist" do @provider.group_exists = false expect(@provider).not_to receive(:remove_group) @provider.run_action(:remove) expect(@provider.new_resource).not_to be_updated end it "should call remove_group if the group exists" do @provider.group_exists = true expect(@provider).to receive(:remove_group) @provider.run_action(:remove) expect(@provider.new_resource).to be_updated end end describe "when updating a group" do before(:each) do @provider.group_exists = true allow(@provider).to receive(:manage_group).and_return(true) end it "should run manage_group if the group exists and has mismatched attributes" do expect(@provider).to receive(:compare_group).and_return(true) allow(@provider).to receive(:change_desc).and_return(["Some changes are going to be done."]) expect(@provider).to receive(:manage_group).and_return(true) @provider.run_action(:manage) end it "should set the new resources updated flag to true if manage_group is called" do allow(@provider).to receive(:compare_group).and_return(true) allow(@provider).to receive(:change_desc).and_return(["Some changes are going to be done."]) allow(@provider).to receive(:manage_group).and_return(true) @provider.run_action(:manage) expect(@new_resource).to be_updated end it "should not run manage_group if the group does not exist" do @provider.group_exists = false expect(@provider).not_to receive(:manage_group) @provider.run_action(:manage) end it "should not run manage_group if the group exists but has no differing attributes" do expect(@provider).to receive(:compare_group).and_return(false) allow(@provider).to receive(:change_desc).and_return(["Some changes are going to be done."]) expect(@provider).not_to receive(:manage_group) @provider.run_action(:manage) end end describe "when modifying the group" do before(:each) do @provider.group_exists = true allow(@provider).to receive(:manage_group).and_return(true) end it "should run manage_group if the group exists and has mismatched attributes" do expect(@provider).to receive(:compare_group).and_return(true) allow(@provider).to receive(:change_desc).and_return(["Some changes are going to be done."]) expect(@provider).to receive(:manage_group).and_return(true) @provider.run_action(:modify) end it "should set the new resources updated flag to true if manage_group is called" do allow(@provider).to receive(:compare_group).and_return(true) allow(@provider).to receive(:change_desc).and_return(["Some changes are going to be done."]) allow(@provider).to receive(:manage_group).and_return(true) @provider.run_action(:modify) expect(@new_resource).to be_updated end it "should not run manage_group if the group exists but has no differing attributes" do expect(@provider).to receive(:compare_group).and_return(false) allow(@provider).to receive(:change_desc).and_return(["Some changes are going to be done."]) expect(@provider).not_to receive(:manage_group) @provider.run_action(:modify) end it "should raise a Chef::Exceptions::Group if the group doesn't exist" do @provider.group_exists = false expect { @provider.run_action(:modify) }.to raise_error(Chef::Exceptions::Group) end end describe "when determining the reason for a change" do it "should report which group members are missing if members are missing and appending to the group" do @new_resource.members << "user1" @new_resource.members << "user2" allow(@new_resource).to receive(:append).and_return true expect(@provider.compare_group).to be_truthy expect(@provider.change_desc).to eq([ "add missing member(s): user1, user2" ]) end it "should report that the group members will be overwritten if not appending" do @new_resource.members << "user1" allow(@new_resource).to receive(:append).and_return false expect(@provider.compare_group).to be_truthy expect(@provider.change_desc).to eq([ "replace group members with new list of members" ]) end it "should report the gid will be changed when it does not match" do allow(@current_resource).to receive(:gid).and_return("BADF00D") expect(@provider.compare_group).to be_truthy expect(@provider.change_desc).to eq([ "change gid #{@current_resource.gid} to #{@new_resource.gid}" ]) end it "should report no change reason when no change is required" do expect(@provider.compare_group).to be_falsey expect(@provider.change_desc).to eq([ ]) end end end chef-12.14.60/spec/unit/provider/http_request_spec.rb000066400000000000000000000134441276456504500225270ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::HttpRequest do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::HttpRequest.new("adam") @new_resource.name "adam" @new_resource.url "http://www.opscode.com/" @new_resource.message "is cool" @provider = Chef::Provider::HttpRequest.new(@new_resource, @run_context) end describe "load_current_resource" do it "should set up a Chef::ServerAPI client, with no authentication" do expect(Chef::HTTP::Simple).to receive(:new).with(@new_resource.url) @provider.load_current_resource end end describe "when making REST calls" do before(:each) do # run_action(x) forces load_current_resource to run; # that would overwrite our supplied mock Chef::Rest # object allow(@provider).to receive(:load_current_resource).and_return(true) @http = double("Chef::ServerAPI") @provider.http = @http end describe "action_get" do it "should inflate a message block at runtime" do @new_resource.message { "return" } expect(@http).to receive(:get).with("http://www.opscode.com/", {}) @provider.run_action(:get) expect(@new_resource).to be_updated end it "should run a GET request" do expect(@http).to receive(:get).with("http://www.opscode.com/", {}) @provider.run_action(:get) expect(@new_resource).to be_updated end end describe "action_put" do it "should run a PUT request with the message as the payload" do expect(@http).to receive(:put).with("http://www.opscode.com/", @new_resource.message, {}) @provider.run_action(:put) expect(@new_resource).to be_updated end it "should inflate a message block at runtime" do allow(@new_resource).to receive(:message).and_return(lambda { "return" }) expect(@http).to receive(:put).with("http://www.opscode.com/", "return", {}) @provider.run_action(:put) expect(@new_resource).to be_updated end end describe "action_post" do it "should run a PUT request with the message as the payload" do expect(@http).to receive(:post).with("http://www.opscode.com/", @new_resource.message, {}) @provider.run_action(:post) expect(@new_resource).to be_updated end it "should inflate a message block at runtime" do @new_resource.message { "return" } expect(@http).to receive(:post).with("http://www.opscode.com/", "return", {}) @provider.run_action(:post) expect(@new_resource).to be_updated end end describe "action_delete" do it "should run a DELETE request" do expect(@http).to receive(:delete).with("http://www.opscode.com/", {}) @provider.run_action(:delete) expect(@new_resource).to be_updated end end # CHEF-4762: we expect a nil return value for a "200 Success" response # and false for a "304 Not Modified" response describe "action_head" do before do @provider.http = @http end it "should inflate a message block at runtime" do @new_resource.message { "return" } expect(@http).to receive(:head).with("http://www.opscode.com/", {}).and_return(nil) @provider.run_action(:head) expect(@new_resource).to be_updated end it "should run a HEAD request" do expect(@http).to receive(:head).with("http://www.opscode.com/", {}).and_return(nil) @provider.run_action(:head) expect(@new_resource).to be_updated end it "should update a HEAD request with empty string response body (CHEF-4762)" do expect(@http).to receive(:head).with("http://www.opscode.com/", {}).and_return("") @provider.run_action(:head) expect(@new_resource).to be_updated end it "should update a HEAD request with nil response body (CHEF-4762)" do expect(@http).to receive(:head).with("http://www.opscode.com/", {}).and_return(nil) @provider.run_action(:head) expect(@new_resource).to be_updated end it "should not update a HEAD request if a not modified response (CHEF-4762)" do if_modified_since = File.mtime(__FILE__).httpdate @new_resource.headers "If-Modified-Since" => if_modified_since expect(@http).to receive(:head).with("http://www.opscode.com/", { "If-Modified-Since" => if_modified_since }).and_return(false) @provider.run_action(:head) expect(@new_resource).not_to be_updated end it "should run a HEAD request with If-Modified-Since header" do @new_resource.headers "If-Modified-Since" => File.mtime(__FILE__).httpdate expect(@http).to receive(:head).with("http://www.opscode.com/", @new_resource.headers) @provider.run_action(:head) end it "doesn't call converge_by if HEAD does not return modified" do expect(@http).to receive(:head).and_return(false) expect(@provider).not_to receive(:converge_by) @provider.run_action(:head) end end end end chef-12.14.60/spec/unit/provider/ifconfig/000077500000000000000000000000001276456504500202175ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/ifconfig/aix_spec.rb000066400000000000000000000157461276456504500223540ustar00rootroot00000000000000# # Author:: Kaustubh Deorukhkar () # Copyright:: Copyright 2013-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/exceptions" describe Chef::Provider::Ifconfig::Aix do before(:all) do @ifconfig_output = <<-IFCONFIG en1: flags=1e080863,480 inet 10.153.11.59 netmask 0xffff0000 broadcast 10.153.255.255 tcp_sendspace 262144 tcp_recvspace 262144 rfc1323 1 en0: flags=1e080863,480 metric 1 inet 172.29.174.58 netmask 0xffffc000 broadcast 172.29.191.255 tcp_sendspace 262144 tcp_recvspace 262144 rfc1323 1 lo0: flags=e08084b,c0 inet 127.0.0.1 netmask 0xff000000 broadcast 127.255.255.255 inet6 ::1%1/0 IFCONFIG end before(:each) do @node = Chef::Node.new @cookbook_collection = Chef::CookbookCollection.new([]) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) @new_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context) @provider = Chef::Provider::Ifconfig::Aix.new(@new_resource, @run_context) end describe "#load_current_resource" do before do @status = double(:stdout => @ifconfig_output, :exitstatus => 0) allow(@provider).to receive(:shell_out).and_return(@status) @new_resource.device "en0" end it "should load given interface with attributes." do current_resource = @provider.load_current_resource expect(current_resource.inet_addr).to eq("172.29.174.58") expect(current_resource.target).to eq(@new_resource.target) expect(current_resource.mask).to eq("255.255.192.0") expect(current_resource.bcast).to eq("172.29.191.255") expect(current_resource.metric).to eq("1") end end describe "#action_add" do it "should add an interface if it does not exist" do @new_resource.device "en10" allow(@provider).to receive(:load_current_resource) do @provider.instance_variable_set("@status", double("Status", :exitstatus => 0)) @provider.instance_variable_set("@current_resource", Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)) end command = "chdev -l #{@new_resource.device} -a netaddr=#{@new_resource.name}" expect(@provider).to receive(:run_command).with(:command => command) @provider.run_action(:add) expect(@new_resource).to be_updated end it "should raise exception if metric attribute is set" do @new_resource.device "en0" @new_resource.metric "1" allow(@provider).to receive(:load_current_resource) do @provider.instance_variable_set("@status", double("Status", :exitstatus => 0)) @provider.instance_variable_set("@current_resource", Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)) end expect { @provider.run_action(:add) }.to raise_error(Chef::Exceptions::Ifconfig, "interface metric attribute cannot be set for :add action") end end describe "#action_enable" do it "should enable an interface if it does not exist" do @new_resource.device "en10" allow(@provider).to receive(:load_current_resource) do @provider.instance_variable_set("@status", double("Status", :exitstatus => 0)) @provider.instance_variable_set("@current_resource", Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)) end command = "ifconfig #{@new_resource.device} #{@new_resource.name}" expect(@provider).to receive(:run_command).with(:command => command) @provider.run_action(:enable) expect(@new_resource).to be_updated end end describe "#action_disable" do it "should not disable an interface if it does not exist" do @new_resource.device "en10" allow(@provider).to receive(:load_current_resource) do @provider.instance_variable_set("@status", double("Status", :exitstatus => 0)) @provider.instance_variable_set("@current_resource", Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)) end expect(@provider).not_to receive(:run_command) @provider.run_action(:disable) expect(@new_resource).not_to be_updated end context "interface exists" do before do @new_resource.device "en10" allow(@provider).to receive(:load_current_resource) do @provider.instance_variable_set("@status", double("Status", :exitstatus => 0)) current_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context) current_resource.device @new_resource.device @provider.instance_variable_set("@current_resource", current_resource) end end it "should disable an interface if it exists" do command = "ifconfig #{@new_resource.device} down" expect(@provider).to receive(:run_command).with(:command => command) @provider.run_action(:disable) expect(@new_resource).to be_updated end end end describe "#action_delete" do it "should not delete an interface if it does not exist" do @new_resource.device "en10" allow(@provider).to receive(:load_current_resource) do @provider.instance_variable_set("@status", double("Status", :exitstatus => 0)) @provider.instance_variable_set("@current_resource", Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)) end expect(@provider).not_to receive(:run_command) @provider.run_action(:delete) expect(@new_resource).not_to be_updated end context "interface exists" do before do @new_resource.device "en10" allow(@provider).to receive(:load_current_resource) do @provider.instance_variable_set("@status", double("Status", :exitstatus => 0)) current_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context) current_resource.device @new_resource.device @provider.instance_variable_set("@current_resource", current_resource) end end it "should delete an interface if it exists" do command = "chdev -l #{@new_resource.device} -a state=down" expect(@provider).to receive(:run_command).with(:command => command) @provider.run_action(:delete) expect(@new_resource).to be_updated end end end end chef-12.14.60/spec/unit/provider/ifconfig/debian_spec.rb000066400000000000000000000262031276456504500230030ustar00rootroot00000000000000# # Author:: Xabier de Zuazo (xabier@onddo.com) # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/exceptions" describe Chef::Provider::Ifconfig::Debian do let(:run_context) do node = Chef::Node.new cookbook_collection = Chef::CookbookCollection.new([]) events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, cookbook_collection, events) end let(:new_resource) do new_resource = Chef::Resource::Ifconfig.new("10.0.0.1", run_context) new_resource.mask "255.255.254.0" new_resource.metric "1" new_resource.mtu "1500" new_resource.device "eth0" new_resource end let(:current_resource) { Chef::Resource::Ifconfig.new("10.0.0.1", run_context) } let(:provider) do status = double("Status", :exitstatus => 0) provider = Chef::Provider::Ifconfig::Debian.new(new_resource, run_context) provider.instance_variable_set("@status", status) provider.current_resource = current_resource allow(provider).to receive(:load_current_resource) allow(provider).to receive(:run_command) provider end let(:config_filename_ifaces) { "/etc/network/interfaces" } let(:config_filename_ifcfg) { "/etc/network/interfaces.d/ifcfg-#{new_resource.device}" } describe "generate_config" do context "when writing a file" do let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" } before do stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) end it "should write a network-script" do provider.run_action(:add) expect(File.read(config_filename_ifcfg)).to match(/^iface eth0 inet static\s*$/) expect(File.read(config_filename_ifcfg)).to match(/^\s+address 10\.0\.0\.1\s*$/) expect(File.read(config_filename_ifcfg)).to match(/^\s+netmask 255\.255\.254\.0\s*$/) end context "when the interface_dot_d directory does not exist" do before do FileUtils.rmdir tempdir_path expect(File.exists?(tempdir_path)).to be_falsey end it "should create the /etc/network/interfaces.d directory" do provider.run_action(:add) expect(File.exists?(tempdir_path)).to be_truthy expect(File.directory?(tempdir_path)).to be_truthy end it "should mark the resource as updated" do provider.run_action(:add) expect(new_resource.updated_by_last_action?).to be_truthy end end context "when the interface_dot_d directory exists" do before do expect(File.exists?(tempdir_path)).to be_truthy end it "should still mark the resource as updated (we still write a file to it)" do provider.run_action(:add) expect(new_resource.updated_by_last_action?).to be_truthy end end end context "when the file is up-to-date" do let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" } before do stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) config_file_ifcfg = StringIO.new(<<-EOF iface eth0 inet static address 10.0.0.1 netmask 255.255.254.0 EOF ) expect(File.exists?(tempdir_path)).to be_truthy # since the file exists, the enclosing dir must also exist end context "when the /etc/network/interfaces file has the source line" do let(:expected_string) do <<-EOF a line source #{tempdir_path}/* another line EOF end before do tempfile.write(expected_string) tempfile.close expect(provider).not_to receive(:converge_by).with(/modifying #{tempfile.path} to source #{tempdir_path}/) end it "should preserve all the contents" do provider.run_action(:add) expect(IO.read(tempfile.path)).to eq(expected_string) end end context "when the /etc/network/interfaces file does not have the source line" do let(:expected_string) do <<-EOF a line another line source #{tempdir_path}/* EOF end before do tempfile.write("a line\nanother line\n") tempfile.close allow(provider).to receive(:converge_by).and_call_original expect(provider).to receive(:converge_by).with(/modifying #{tempfile.path} to source #{tempdir_path}/).and_call_original end it "should preserve the original contents and add the source line" do provider.run_action(:add) expect(IO.read(tempfile.path)).to eq(expected_string) end it "should mark the resource as updated" do provider.run_action(:add) expect(new_resource.updated_by_last_action?).to be_truthy end end end describe "when running under why run" do before do Chef::Config[:why_run] = true end after do Chef::Config[:why_run] = false end context "when writing a file" do let(:config_file_ifcfg) { StringIO.new } let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" } before do stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) expect(File).not_to receive(:new).with(config_filename_ifcfg, "w") end it "should write a network-script" do provider.run_action(:add) expect(config_file_ifcfg.string).not_to match(/^iface eth0 inet static\s*$/) expect(config_file_ifcfg.string).not_to match(/^\s+address 10\.0\.0\.1\s*$/) expect(config_file_ifcfg.string).not_to match(/^\s+netmask 255\.255\.254\.0\s*$/) end context "when the interface_dot_d directory does not exist" do before do FileUtils.rmdir tempdir_path expect(File.exists?(tempdir_path)).to be_falsey end it "should not create the /etc/network/interfaces.d directory" do provider.run_action(:add) expect(File.exists?(tempdir_path)).not_to be_truthy end it "should mark the resource as updated" do provider.run_action(:add) expect(new_resource.updated_by_last_action?).to be_truthy end end context "when the interface_dot_d directory exists" do before do expect(File.exists?(tempdir_path)).to be_truthy end it "should still mark the resource as updated (we still write a file to it)" do provider.run_action(:add) expect(new_resource.updated_by_last_action?).to be_truthy end end end context "when the file is up-to-date" do let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" } before do stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) config_file_ifcfg = StringIO.new(<<-EOF iface eth0 inet static address 10.0.0.1 netmask 255.255.254.0 EOF ) expect(File).not_to receive(:new).with(config_filename_ifcfg, "w") expect(File.exists?(tempdir_path)).to be_truthy # since the file exists, the enclosing dir must also exist end context "when the /etc/network/interfaces file has the source line" do let(:expected_string) do <<-EOF a line source #{tempdir_path}/* another line EOF end before do tempfile.write(expected_string) tempfile.close end it "should preserve all the contents" do provider.run_action(:add) expect(IO.read(tempfile.path)).to eq(expected_string) end end context "when the /etc/network/interfaces file does not have the source line" do let(:expected_string) do <<-EOF a line another line source #{tempdir_path}/* EOF end before do tempfile.write("a line\nanother line\n") tempfile.close end it "should preserve the original contents and not add the source line" do provider.run_action(:add) expect(IO.read(tempfile.path)).to eq("a line\nanother line\n") end it "should mark the resource as updated" do provider.run_action(:add) expect(new_resource.updated_by_last_action?).to be_truthy end end end end end describe "delete_config for action_delete" do let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" } before do stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) File.open(config_filename_ifcfg, "w") do |fh| fh.write "arbitrary text\n" fh.close end end after do Dir.rmdir(tempdir_path) end it "should delete network-script if it exists" do current_resource.device new_resource.device # belt and suspenders testing? expect_any_instance_of(Chef::Util::Backup).to receive(:do_backup).and_call_original # internal implementation detail of Ifconfig. expect_any_instance_of(Chef::Provider::File).to receive(:action_delete).and_call_original expect(File.exist?(config_filename_ifcfg)).to be_truthy provider.run_action(:delete) expect(File.exist?(config_filename_ifcfg)).to be_falsey end end end chef-12.14.60/spec/unit/provider/ifconfig/redhat_spec.rb000066400000000000000000000055341276456504500230340ustar00rootroot00000000000000# # Author:: Xabier de Zuazo (xabier@onddo.com) # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/exceptions" describe Chef::Provider::Ifconfig::Redhat do before do @node = Chef::Node.new @cookbook_collection = Chef::CookbookCollection.new([]) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) #This new_resource can be called anything --> it is not the same as in ifconfig.rb @new_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context) @new_resource.mask "255.255.254.0" @new_resource.metric "1" @new_resource.mtu "1500" @new_resource.device "eth0" @provider = Chef::Provider::Ifconfig::Redhat.new(@new_resource, @run_context) @current_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context) status = double("Status", :exitstatus => 0) @provider.instance_variable_set("@status", status) @provider.current_resource = @current_resource config_filename = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}" @config = double("chef-resource-file") expect(@provider).to receive(:resource_for_config).with(config_filename).and_return(@config) end describe "generate_config for action_add" do it "should write network-script for centos" do allow(@provider).to receive(:load_current_resource) allow(@provider).to receive(:run_command) expect(@config).to receive(:content) do |arg| expect(arg).to match(/^\s*DEVICE=eth0\s*$/) expect(arg).to match(/^\s*IPADDR=10\.0\.0\.1\s*$/) expect(arg).to match(/^\s*NETMASK=255\.255\.254\.0\s*$/) end expect(@config).to receive(:run_action).with(:create) expect(@config).to receive(:updated?).and_return(true) @provider.run_action(:add) end end describe "delete_config for action_delete" do it "should delete network-script if it exists for centos" do @current_resource.device @new_resource.device allow(@provider).to receive(:load_current_resource) allow(@provider).to receive(:run_command) expect(@config).to receive(:run_action).with(:delete) expect(@config).to receive(:updated?).and_return(true) @provider.run_action(:delete) end end end chef-12.14.60/spec/unit/provider/ifconfig_spec.rb000066400000000000000000000166761276456504500215760ustar00rootroot00000000000000# # Author:: Prajakta Purohit (prajakta@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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) require "spec_helper" require "chef/exceptions" describe Chef::Provider::Ifconfig do before do @node = Chef::Node.new @cookbook_collection = Chef::CookbookCollection.new([]) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) #This new_resource can be called anything --> it is not the same as in ifconfig.rb @new_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context) @new_resource.mask "255.255.254.0" @new_resource.metric "1" @new_resource.mtu "1500" @new_resource.device "eth0" @provider = Chef::Provider::Ifconfig.new(@new_resource, @run_context) @current_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context) status = double("Status", :exitstatus => 0) @provider.instance_variable_set("@status", status) @provider.current_resource = @current_resource end describe Chef::Provider::Ifconfig, "load_current_resource" do before do @status = double(:stdout => "", :exitstatus => 1) allow(@provider).to receive(:shell_out).and_return(@status) @provider.load_current_resource end it "should track state of ifconfig failure" do expect(@provider.instance_variable_get("@status").exitstatus).not_to eq(0) end it "should thrown an exception when ifconfig fails" do @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error Chef::Exceptions::Ifconfig end end describe Chef::Provider::Ifconfig, "action_add" do it "should add an interface if it does not exist" do #@provider.stub(:run_command).and_return(true) allow(@provider).to receive(:load_current_resource) @current_resource.inet_addr nil command = "ifconfig eth0 10.0.0.1 netmask 255.255.254.0 metric 1 mtu 1500" expect(@provider).to receive(:run_command).with(:command => command) expect(@provider).to receive(:generate_config) @provider.run_action(:add) expect(@new_resource).to be_updated end it "should set the address to target if specified" do allow(@provider).to receive(:load_current_resource) @new_resource.target "172.16.32.2" command = "ifconfig eth0 172.16.32.2 netmask 255.255.254.0 metric 1 mtu 1500" expect(@provider).to receive(:run_command).with(:command => command) @provider.run_action(:add) expect(@new_resource).to be_updated end it "should not add an interface if it already exists" do allow(@provider).to receive(:load_current_resource) expect(@provider).not_to receive(:run_command) @current_resource.inet_addr "10.0.0.1" expect(@provider).to receive(:generate_config) @provider.run_action(:add) expect(@new_resource).not_to be_updated end #We are not testing this case with the assumption that anyone writing the cookbook would not make a typo == lo #it "should add a blank command if the #{@new_resource.device} == lo" do #end end describe Chef::Provider::Ifconfig, "action_enable" do it "should enable interface if it does not exist" do allow(@provider).to receive(:load_current_resource) @current_resource.inet_addr nil command = "ifconfig eth0 10.0.0.1 netmask 255.255.254.0 metric 1 mtu 1500" expect(@provider).to receive(:run_command).with(:command => command) expect(@provider).not_to receive(:generate_config) @provider.run_action(:enable) expect(@new_resource).to be_updated end it "should set the address to target if specified" do allow(@provider).to receive(:load_current_resource) @new_resource.target "172.16.32.2" command = "ifconfig eth0 172.16.32.2 netmask 255.255.254.0 metric 1 mtu 1500" expect(@provider).to receive(:run_command).with(:command => command) @provider.run_action(:enable) expect(@new_resource).to be_updated end it "should not enable interface if it already exists" do allow(@provider).to receive(:load_current_resource) expect(@provider).not_to receive(:run_command) @current_resource.inet_addr "10.0.0.1" expect(@provider).not_to receive(:generate_config) @provider.run_action(:enable) expect(@new_resource).not_to be_updated end end describe Chef::Provider::Ifconfig, "action_delete" do it "should delete interface if it exists" do allow(@provider).to receive(:load_current_resource) @current_resource.device "eth0" command = "ifconfig #{@new_resource.device} down" expect(@provider).to receive(:run_command).with(:command => command) expect(@provider).to receive(:delete_config) @provider.run_action(:delete) expect(@new_resource).to be_updated end it "should not delete interface if it does not exist" do allow(@provider).to receive(:load_current_resource) expect(@provider).not_to receive(:run_command) expect(@provider).to receive(:delete_config) @provider.run_action(:delete) expect(@new_resource).not_to be_updated end end describe Chef::Provider::Ifconfig, "action_disable" do it "should disable interface if it exists" do allow(@provider).to receive(:load_current_resource) @current_resource.device "eth0" command = "ifconfig #{@new_resource.device} down" expect(@provider).to receive(:run_command).with(:command => command) expect(@provider).not_to receive(:delete_config) @provider.run_action(:disable) expect(@new_resource).to be_updated end it "should not delete interface if it does not exist" do allow(@provider).to receive(:load_current_resource) expect(@provider).not_to receive(:run_command) expect(@provider).not_to receive(:delete_config) @provider.run_action(:disable) expect(@new_resource).not_to be_updated end end describe Chef::Provider::Ifconfig, "action_delete" do it "should delete interface of it exists" do allow(@provider).to receive(:load_current_resource) @current_resource.device "eth0" command = "ifconfig #{@new_resource.device} down" expect(@provider).to receive(:run_command).with(:command => command) expect(@provider).to receive(:delete_config) @provider.run_action(:delete) expect(@new_resource).to be_updated end it "should not delete interface if it does not exist" do # This is so that our fake values do not get overwritten allow(@provider).to receive(:load_current_resource) # This is so that nothing actually runs expect(@provider).not_to receive(:run_command) expect(@provider).to receive(:delete_config) @provider.run_action(:delete) expect(@new_resource).not_to be_updated end end end chef-12.14.60/spec/unit/provider/launchd_spec.rb000066400000000000000000000137341276456504500214200ustar00rootroot00000000000000# # Author:: Mike Dodge () # Copyright:: Copyright (c) 2015 Facebook, 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::Launchd do context "When launchd manages call.mom.weekly" do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:provider) { Chef::Provider::Launchd.new(new_resource, run_context) } let(:label) { "call.mom.weekly" } let(:new_resource) { Chef::Resource::Launchd.new(label) } let!(:current_resource) { Chef::Resource::Launchd.new(label) } let(:test_plist) { String.new <<-XML } \tLabel \tcall.mom.weekly \tProgram \t/Library/scripts/call_mom.sh \tStartCalendarInterval \t \t\tHourly \t\t10 \t\tWeekday \t\t7 \t \tTimeOut \t300 XML let(:test_hash) do { "Label" => "call.mom.weekly", "Program" => "/Library/scripts/call_mom.sh", "StartCalendarInterval" => { "Hourly" => 10, "Weekday" => 7, }, "TimeOut" => 300, } end before(:each) do provider.load_current_resource end it "resource name and label should be call.mom.weekly" do expect(new_resource.name).to eql(label) expect(new_resource.label).to eql(label) end def run_resource_setup_for_action(action) new_resource.action(action) provider.action = action provider.load_current_resource provider.define_resource_requirements provider.process_resource_requirements end describe "with type is set to" do describe "agent" do it "path should be /Library/LaunchAgents/call.mom.weekly.plist" do new_resource.type "agent" expect(provider.gen_path_from_type). to eq("/Library/LaunchAgents/call.mom.weekly.plist") end end describe "daemon" do it "path should be /Library/LaunchDaemons/call.mom.weekly.plist" do expect(provider.gen_path_from_type). to eq("/Library/LaunchDaemons/call.mom.weekly.plist") end end end describe "with a :create action and" do describe "program is passed" do it "should produce the test_plist from properties" do new_resource.program "/Library/scripts/call_mom.sh" new_resource.time_out 300 new_resource.start_calendar_interval "Hourly" => 10, "Weekday" => 7 expect(provider.content?).to be_truthy expect(provider.content).to eql(test_plist) end end describe "hash is passed" do it "should produce the test_plist from the hash" do new_resource.hash test_hash expect(provider.content?).to be_truthy expect(provider.content).to eql(test_plist) end end end describe "with an :enable action" do describe "and the file has been updated" do before(:each) do allow(provider).to receive( :manage_plist).with(:create).and_return(true) allow(provider).to receive( :manage_service).with(:restart).and_return(true) end it "should call manage_service with a :restart action" do expect(provider.manage_service(:restart)).to be_truthy end it "works with action enable" do expect(run_resource_setup_for_action(:enable)).to be_truthy provider.action_enable end end describe "and the file has not been updated" do before(:each) do allow(provider).to receive( :manage_plist).with(:create).and_return(nil) allow(provider).to receive( :manage_service).with(:enable).and_return(true) end it "should call manage_service with a :enable action" do expect(provider.manage_service(:enable)).to be_truthy end it "works with action enable" do expect(run_resource_setup_for_action(:enable)).to be_truthy provider.action_enable end end end describe "with an :delete action" do describe "and the ld file is present" do before(:each) do allow(File).to receive(:exists?).and_return(true) allow(provider).to receive( :manage_service).with(:disable).and_return(true) allow(provider).to receive( :manage_plist).with(:delete).and_return(true) end it "should call manage_service with a :disable action" do expect(provider.manage_service(:disable)).to be_truthy end it "works with action :delete" do expect(run_resource_setup_for_action(:delete)).to be_truthy provider.action_delete end end describe "and the ld file is not present" do before(:each) do allow(File).to receive(:exists?).and_return(false) allow(provider).to receive( :manage_plist).with(:delete).and_return(true) end it "works with action :delete" do expect(run_resource_setup_for_action(:delete)).to be_truthy provider.action_delete end end end end end chef-12.14.60/spec/unit/provider/link_spec.rb000066400000000000000000000347771276456504500207510ustar00rootroot00000000000000# # Author:: AJ Christensen () # Author:: John Keiser () # 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 "ostruct" require "spec_helper" if Chef::Platform.windows? require "chef/win32/file" #probably need this in spec_helper end describe Chef::Resource::Link, :not_supported_on_win2k3 do let(:provider) do node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, @events) Chef::Provider::Link.new(new_resource, run_context) end let(:new_resource) do result = Chef::Resource::Link.new("#{CHEF_SPEC_DATA}/fofile-link") result.to "#{CHEF_SPEC_DATA}/fofile" result end def canonicalize(path) Chef::Platform.windows? ? path.tr("/", '\\') : path end describe "when the target is a symlink" do before(:each) do lstat = double("stats", :ino => 5) allow(lstat).to receive(:uid).and_return(501) allow(lstat).to receive(:gid).and_return(501) allow(lstat).to receive(:mode).and_return(0777) allow(File).to receive(:lstat).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(lstat) allow(provider.file_class).to receive(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true) allow(provider.file_class).to receive(:readlink).with("#{CHEF_SPEC_DATA}/fofile-link").and_return("#{CHEF_SPEC_DATA}/fofile") end describe "to a file that exists" do before do allow(File).to receive(:exist?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true) new_resource.owner 501 # only loaded in current_resource if present in new new_resource.group 501 provider.load_current_resource end it "should set the symlink target" do expect(provider.current_resource.target_file).to eq("#{CHEF_SPEC_DATA}/fofile-link") end it "should set the link type" do expect(provider.current_resource.link_type).to eq(:symbolic) end it "should update the source of the existing link with the links target" do expect(provider.current_resource.to).to eq(canonicalize("#{CHEF_SPEC_DATA}/fofile")) end it "should set the owner" do expect(provider.current_resource.owner).to eq(501) end it "should set the group" do expect(provider.current_resource.group).to eq(501) end # We test create in unit tests because there is no other way to ensure # it does no work. Other create and delete scenarios are covered in # the functional tests for links. context "when the desired state is identical" do let(:new_resource) do result = Chef::Resource::Link.new("#{CHEF_SPEC_DATA}/fofile-link") result.to "#{CHEF_SPEC_DATA}/fofile" result end it "create does no work" do expect(provider.access_controls).not_to receive(:set_all) provider.run_action(:create) end end end describe "to a file that doesn't exist" do before do allow(File).to receive(:exist?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false) allow(provider.file_class).to receive(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true) allow(provider.file_class).to receive(:readlink).with("#{CHEF_SPEC_DATA}/fofile-link").and_return("#{CHEF_SPEC_DATA}/fofile") new_resource.owner "501" # only loaded in current_resource if present in new new_resource.group "501" provider.load_current_resource end it "should set the symlink target" do expect(provider.current_resource.target_file).to eq("#{CHEF_SPEC_DATA}/fofile-link") end it "should set the link type" do expect(provider.current_resource.link_type).to eq(:symbolic) end it "should update the source of the existing link to the link's target" do expect(provider.current_resource.to).to eq(canonicalize("#{CHEF_SPEC_DATA}/fofile")) end it "should not set the owner" do expect(provider.current_resource.owner).to be_nil end it "should not set the group" do expect(provider.current_resource.group).to be_nil end end end describe "when the target doesn't exist" do before do allow(File).to receive(:exists?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false) allow(provider.file_class).to receive(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false) provider.load_current_resource end it "should set the symlink target" do expect(provider.current_resource.target_file).to eq("#{CHEF_SPEC_DATA}/fofile-link") end it "should update the source of the existing link to nil" do expect(provider.current_resource.to).to be_nil end it "should not set the owner" do expect(provider.current_resource.owner).to eq(nil) end it "should not set the group" do expect(provider.current_resource.group).to eq(nil) end end describe "when the target is a regular old file" do before do stat = double("stats", :ino => 5) allow(stat).to receive(:uid).and_return(501) allow(stat).to receive(:gid).and_return(501) allow(stat).to receive(:mode).and_return(0755) allow(provider.file_class).to receive(:stat).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(stat) allow(File).to receive(:exists?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true) allow(provider.file_class).to receive(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false) end describe "and the source does not exist" do before do allow(File).to receive(:exists?).with("#{CHEF_SPEC_DATA}/fofile").and_return(false) provider.load_current_resource end it "should set the symlink target" do expect(provider.current_resource.target_file).to eq("#{CHEF_SPEC_DATA}/fofile-link") end it "should update the current source of the existing link with an empty string" do expect(provider.current_resource.to).to eq("") end it "should not set the owner" do expect(provider.current_resource.owner).to eq(nil) end it "should not set the group" do expect(provider.current_resource.group).to eq(nil) end end describe "and the source exists" do before do stat = double("stats", :ino => 6) allow(stat).to receive(:uid).and_return(502) allow(stat).to receive(:gid).and_return(502) allow(stat).to receive(:mode).and_return(0644) allow(provider.file_class).to receive(:stat).with("#{CHEF_SPEC_DATA}/fofile").and_return(stat) allow(File).to receive(:exists?).with("#{CHEF_SPEC_DATA}/fofile").and_return(true) provider.load_current_resource end it "should set the symlink target" do expect(provider.current_resource.target_file).to eq("#{CHEF_SPEC_DATA}/fofile-link") end it "should update the current source of the existing link with an empty string" do expect(provider.current_resource.to).to eq("") end it "should not set the owner" do expect(provider.current_resource.owner).to eq(nil) end it "should not set the group" do expect(provider.current_resource.group).to eq(nil) end end describe "and is hardlinked to the source" do before do stat = double("stats", :ino => 5) allow(stat).to receive(:uid).and_return(502) allow(stat).to receive(:gid).and_return(502) allow(stat).to receive(:mode).and_return(0644) allow(provider.file_class).to receive(:stat).with("#{CHEF_SPEC_DATA}/fofile").and_return(stat) allow(File).to receive(:exists?).with("#{CHEF_SPEC_DATA}/fofile").and_return(true) provider.load_current_resource end it "should set the symlink target" do expect(provider.current_resource.target_file).to eq("#{CHEF_SPEC_DATA}/fofile-link") end it "should set the link type" do expect(provider.current_resource.link_type).to eq(:hard) end it "should update the source of the existing link to the link's target" do expect(provider.current_resource.to).to eq(canonicalize("#{CHEF_SPEC_DATA}/fofile")) end it "should not set the owner" do expect(provider.current_resource.owner).to eq(nil) end it "should not set the group" do expect(provider.current_resource.group).to eq(nil) end # We test create in unit tests because there is no other way to ensure # it does no work. Other create and delete scenarios are covered in # the functional tests for links. context "when the desired state is identical" do let(:new_resource) do result = Chef::Resource::Link.new("#{CHEF_SPEC_DATA}/fofile-link") result.to "#{CHEF_SPEC_DATA}/fofile" result.link_type :hard result end it "create does no work" do expect(provider.file_class).not_to receive(:symlink) expect(provider.file_class).not_to receive(:link) expect(provider.access_controls).not_to receive(:set_all) provider.run_action(:create) end end end end describe "action_delete" do before(:each) do stat = double("stats", :ino => 5) allow(stat).to receive(:uid).and_return(501) allow(stat).to receive(:gid).and_return(501) allow(stat).to receive(:mode).and_return(0755) allow(provider.file_class).to receive(:stat).with( "#{CHEF_SPEC_DATA}/fofile-link").and_return(stat) provider.load_current_resource end shared_context "delete link to directories on Windows" do before do allow(::File).to receive(:directory?).with( "#{CHEF_SPEC_DATA}/fofile-link").and_return(true) end it "invokes Dir.delete method to delete the link" do expect(::Dir).to receive(:delete).with(provider.new_resource.target_file) expect(Chef::Log).to receive(:info).with("#{provider.new_resource} deleted") provider.run_action(:delete) end end shared_context "delete link to directories on Linux" do before do allow(::File).to receive(:directory?).with( "#{CHEF_SPEC_DATA}/fofile-link").and_return(true) end it "invokes File.delete method to delete the link" do expect(::File).to receive(:delete).with(provider.new_resource.target_file) expect(Chef::Log).to receive(:info).with("#{provider.new_resource} deleted") provider.run_action(:delete) end end shared_context "delete link to files" do before do allow(::File).to receive(:directory?).with( "#{CHEF_SPEC_DATA}/fofile-link").and_return(false) end it "invokes File.delete method to delete the link" do expect(::File).to receive(:delete).with(provider.new_resource.target_file) expect(Chef::Log).to receive(:info).with("#{provider.new_resource} deleted") provider.run_action(:delete) end end shared_context "soft links prerequisites" do before(:each) do allow(provider.file_class).to receive(:symlink?).with( "#{CHEF_SPEC_DATA}/fofile-link").and_return(true) allow(provider.file_class).to receive(:readlink).with( "#{CHEF_SPEC_DATA}/fofile-link").and_return("#{CHEF_SPEC_DATA}/fofile") end end shared_context "hard links prerequisites" do let(:new_resource) do result = Chef::Resource::Link.new("#{CHEF_SPEC_DATA}/fofile-link") result.to "#{CHEF_SPEC_DATA}/fofile" result.link_type :hard result end before(:each) do stat = double("stats", :ino => 5) allow(stat).to receive(:uid).and_return(502) allow(stat).to receive(:gid).and_return(502) allow(stat).to receive(:mode).and_return(0644) allow(provider.file_class).to receive(:symlink?).with( "#{CHEF_SPEC_DATA}/fofile-link").and_return(false) allow(File).to receive(:exists?).with( "#{CHEF_SPEC_DATA}/fofile-link").and_return(true) allow(File).to receive(:exists?).with( "#{CHEF_SPEC_DATA}/fofile").and_return(true) allow(provider.file_class).to receive(:stat).with( "#{CHEF_SPEC_DATA}/fofile").and_return(stat) end end context "on Windows platform" do let(:resource_link) do Chef::Resource::Link.new(provider.new_resource.name) end before(:each) do allow(Chef::Resource::Link).to receive(:new).with( provider.new_resource.name).and_return(resource_link) allow(resource_link).to receive(:verify_links_supported!) allow(Chef::Platform).to receive(:windows?).and_return(true) end context "soft links" do include_context "soft links prerequisites" context "to directories" do include_context "delete link to directories on Windows" end context "to files" do include_context "delete link to files" end end context "hard links" do include_context "hard links prerequisites" context "to directories" do include_context "delete link to directories on Windows" end context "to files" do include_context "delete link to files" end end end context "on Linux platform" do before(:each) do allow(Chef::Platform).to receive(:windows?).and_return(false) end context "soft links" do include_context "soft links prerequisites" context "to directories" do include_context "delete link to directories on Linux" end context "to files" do include_context "delete link to files" end end context "hard links" do include_context "hard links prerequisites" context "to directories" do include_context "delete link to directories on Linux" end context "to files" do include_context "delete link to files" end end end end end chef-12.14.60/spec/unit/provider/log_spec.rb000066400000000000000000000047771276456504500205720ustar00rootroot00000000000000# # Author:: Cary Penniman () # 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::Log::ChefLog do let(:log_str) { "this is my test string to log" } let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) { Chef::Resource::Log.new(log_str) } let(:provider) { Chef::Provider::Log::ChefLog.new(new_resource, run_context) } it "should write the string to the Chef::Log object at default level (info)" do expect(Chef::Log).to receive(:info).with(log_str).and_return(true) provider.run_action(:write) end it "should write the string to the Chef::Log object at debug level" do new_resource.level :debug expect(Chef::Log).to receive(:debug).with(log_str).and_return(true) provider.run_action(:write) end it "should write the string to the Chef::Log object at info level" do new_resource.level :info expect(Chef::Log).to receive(:info).with(log_str).and_return(true) provider.run_action(:write) end it "should write the string to the Chef::Log object at warn level" do new_resource.level :warn expect(Chef::Log).to receive(:warn).with(log_str).and_return(true) provider.run_action(:write) end it "should write the string to the Chef::Log object at error level" do new_resource.level :error expect(Chef::Log).to receive(:error).with(log_str).and_return(true) provider.run_action(:write) end it "should write the string to the Chef::Log object at fatal level" do new_resource.level :fatal expect(Chef::Log).to receive(:fatal).with(log_str).and_return(true) provider.run_action(:write) end it "should print the string in why-run mode" do Chef::Config[:why_run] = true expect(Chef::Log).to receive(:info).with(log_str).and_return(true) provider.run_action(:write) end end chef-12.14.60/spec/unit/provider/mdadm_spec.rb000066400000000000000000000135351276456504500210630ustar00rootroot00000000000000# # Author:: Joe Williams () # Copyright:: Copyright 2009-2016, Joe Williams # 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 "ostruct" describe Chef::Provider::Mdadm do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Mdadm.new("/dev/md1") @new_resource.devices ["/dev/sdz1", "/dev/sdz2", "/dev/sdz3"] @provider = Chef::Provider::Mdadm.new(@new_resource, @run_context) end describe "when determining the current metadevice status" do it "should set the current resources mount point to the new resources mount point" do allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(:status => 0)) @provider.load_current_resource expect(@provider.current_resource.name).to eq("/dev/md1") expect(@provider.current_resource.raid_device).to eq("/dev/md1") end it "determines that the metadevice exists when mdadm exit code is zero" do allow(@provider).to receive(:shell_out!).with("mdadm --detail --test /dev/md1", :returns => [0, 4]).and_return(OpenStruct.new(:status => 0)) @provider.load_current_resource expect(@provider.current_resource.exists).to be_truthy end it "determines that the metadevice does not exist when mdadm exit code is 4" do allow(@provider).to receive(:shell_out!).with("mdadm --detail --test /dev/md1", :returns => [0, 4]).and_return(OpenStruct.new(:status => 4)) @provider.load_current_resource expect(@provider.current_resource.exists).to be_falsey end end describe "after the metadevice status is known" do before(:each) do @current_resource = Chef::Resource::Mdadm.new("/dev/md1") @new_resource.level 5 allow(@provider).to receive(:load_current_resource).and_return(true) @provider.current_resource = @current_resource end describe "when creating the metadevice" do it "should create the raid device if it doesnt exist" do @current_resource.exists(false) expected_command = "yes | mdadm --create /dev/md1 --level 5 --chunk=16 --metadata=0.90 --raid-devices 3 /dev/sdz1 /dev/sdz2 /dev/sdz3" expect(@provider).to receive(:shell_out!).with(expected_command) @provider.run_action(:create) end it "should specify a bitmap only if set" do @current_resource.exists(false) @new_resource.bitmap("grow") expected_command = "yes | mdadm --create /dev/md1 --level 5 --chunk=16 --metadata=0.90 --bitmap=grow --raid-devices 3 /dev/sdz1 /dev/sdz2 /dev/sdz3" expect(@provider).to receive(:shell_out!).with(expected_command) @provider.run_action(:create) expect(@new_resource).to be_updated_by_last_action end it "should specify a layout only if set" do @current_resource.exists(false) @new_resource.layout("rs") expected_command = "yes | mdadm --create /dev/md1 --level 5 --chunk=16 --metadata=0.90 --layout=rs --raid-devices 3 /dev/sdz1 /dev/sdz2 /dev/sdz3" expect(@provider).to receive(:shell_out!).with(expected_command) @provider.run_action(:create) expect(@new_resource).to be_updated_by_last_action end it "should not specify a chunksize if raid level 1" do @current_resource.exists(false) @new_resource.level 1 expected_command = "yes | mdadm --create /dev/md1 --level 1 --metadata=0.90 --raid-devices 3 /dev/sdz1 /dev/sdz2 /dev/sdz3" expect(@provider).to receive(:shell_out!).with(expected_command) @provider.run_action(:create) expect(@new_resource).to be_updated_by_last_action end it "should not create the raid device if it does exist" do @current_resource.exists(true) expect(@provider).not_to receive(:shell_out!) @provider.run_action(:create) expect(@new_resource).not_to be_updated_by_last_action end end describe "when asembling the metadevice" do it "should assemble the raid device if it doesnt exist" do @current_resource.exists(false) expected_mdadm_cmd = "yes | mdadm --assemble /dev/md1 /dev/sdz1 /dev/sdz2 /dev/sdz3" expect(@provider).to receive(:shell_out!).with(expected_mdadm_cmd) @provider.run_action(:assemble) expect(@new_resource).to be_updated_by_last_action end it "should not assemble the raid device if it doesnt exist" do @current_resource.exists(true) expect(@provider).not_to receive(:shell_out!) @provider.run_action(:assemble) expect(@new_resource).not_to be_updated_by_last_action end end describe "when stopping the metadevice" do it "should stop the raid device if it exists" do @current_resource.exists(true) expected_mdadm_cmd = "yes | mdadm --stop /dev/md1" expect(@provider).to receive(:shell_out!).with(expected_mdadm_cmd) @provider.run_action(:stop) expect(@new_resource).to be_updated_by_last_action end it "should not attempt to stop the raid device if it does not exist" do @current_resource.exists(false) expect(@provider).not_to receive(:shell_out!) @provider.run_action(:stop) expect(@new_resource).not_to be_updated_by_last_action end end end end chef-12.14.60/spec/unit/provider/mount/000077500000000000000000000000001276456504500175755ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/mount/aix_spec.rb000066400000000000000000000200671276456504500217220ustar00rootroot00000000000000# # Author:: Kaustubh Deorukhkar () # Copyright:: Copyright 2013-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 "ostruct" describe Chef::Provider::Mount::Aix do before(:all) do @mounted_output = <<-MOUNT node mounted mounted over vfs date options -------- --------------- --------------- ------ ------------ --------------- /dev/sdz1 /tmp/foo jfs2 Jul 17 13:22 rw,log=/dev/hd8 MOUNT @unmounted_output = <<-UNMOUNTED node mounted mounted over vfs date options -------- --------------- --------------- ------ ------------ --------------- /dev/sdz2 / jfs2 Jul 17 13:22 rw,log=/dev/hd8 UNMOUNTED @conflict_mounted_output = <<-MOUNT node mounted mounted over vfs date options -------- --------------- --------------- ------ ------------ --------------- /dev/sdz3 /tmp/foo jfs2 Jul 17 13:22 rw,log=/dev/hd8 MOUNT @enabled_output = <<-ENABLED #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct /tmp/foo:/dev/sdz1:jfs2::bootfs:10485760:rw:yes:no ENABLED end before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Mount.new("/tmp/foo") @new_resource.device "/dev/sdz1" @new_resource.device_type :device @new_resource.fstype "jfs2" @new_resource.supports :remount => false @provider = Chef::Provider::Mount::Aix.new(@new_resource, @run_context) allow(::File).to receive(:exists?).with("/dev/sdz1").and_return true allow(::File).to receive(:exists?).with("/tmp/foo").and_return true end def stub_mounted(provider, mounted_output) response = double("Mixlib::ShellOut command", :exitstatus => 0, :stdout => mounted_output, :stderr => "") expect(provider).to receive(:shell_out!).with("mount").and_return(response) end def stub_enabled(provider, enabled_output) response = double("Mixlib::ShellOut command", :exitstatus => 0, :stdout => enabled_output, :stderr => "") expect(provider).to receive(:shell_out).with("lsfs -c #{@new_resource.mount_point}").and_return(response) end def stub_mounted_enabled(provider, mounted_output, enabled_output) stub_mounted(provider, mounted_output) stub_enabled(provider, enabled_output) end describe "when discovering the current fs state" do it "should set current_resource.mounted to true if device is already mounted" do stub_mounted_enabled(@provider, @mounted_output, "") @provider.load_current_resource expect(@provider.current_resource.mounted).to be_truthy end it "should set current_resource.mounted to false if device is not mounted" do stub_mounted_enabled(@provider, @unmounted_output, "") @provider.load_current_resource expect(@provider.current_resource.mounted).to be_falsey end it "should set current_resource.mounted to false if the mount point is used for another device" do stub_mounted_enabled(@provider, @conflict_mounted_output, "") @provider.load_current_resource expect(@provider.current_resource.mounted).to be_falsey end end # tests for #enabled? it "should load current_resource with properties if device is already mounted and enabled" do stub_mounted_enabled(@provider, @mounted_output, @enabled_output) @provider.load_current_resource expect(@provider.current_resource.enabled).to be_truthy expect(@provider.current_resource.mounted).to be_truthy expect(@provider.current_resource.mount_point).to eql(@new_resource.mount_point) expect(@provider.current_resource.fstype).to eql("jfs2") expect(@provider.current_resource.options).to eql(["rw"]) end describe "mount_fs" do it "should mount resource if it is not mounted" do stub_mounted_enabled(@provider, @unmounted_output, "") expect(@provider).to receive(:shell_out!).with("mount -v #{@new_resource.fstype} #{@new_resource.device} #{@new_resource.mount_point}") @provider.run_action(:mount) end it "should not mount resource if it is already mounted" do stub_mounted_enabled(@provider, @mounted_output, "") expect(@provider).not_to receive(:mount_fs) @provider.run_action(:mount) end end describe "umount_fs" do it "should umount resource if it is already mounted" do stub_mounted_enabled(@provider, @mounted_output, "") expect(@provider).to receive(:shell_out!).with("umount #{@new_resource.mount_point}") @provider.run_action(:umount) end it "should not umount resource if it is not mounted" do stub_mounted_enabled(@provider, @unmounted_output, "") expect(@provider).not_to receive(:umount_fs) @provider.run_action(:umount) end end describe "remount_fs" do it "should remount resource if it is already mounted and it supports remounting" do @new_resource.supports({ :remount => true }) stub_mounted_enabled(@provider, @mounted_output, "") expect(@provider).to receive(:shell_out!).with("mount -o remount #{@new_resource.device} #{@new_resource.mount_point}") @provider.run_action(:remount) end it "should remount with new mount options if it is already mounted and it supports remounting" do @new_resource.supports({ :remount => true }) @new_resource.options("nodev,rw") stub_mounted_enabled(@provider, @mounted_output, "") expect(@provider).to receive(:shell_out!).with("mount -o remount,nodev,rw #{@new_resource.device} #{@new_resource.mount_point}") @provider.run_action(:remount) end end describe "enable_fs" do it "should enable mount if it is mounted and not enabled" do @new_resource.options("nodev,rw") stub_mounted_enabled(@provider, @mounted_output, "") filesystems = StringIO.new allow(::File).to receive(:open).with("/etc/filesystems", "a").and_yield(filesystems) @provider.run_action(:enable) expect(filesystems.string).to match(%r{^/tmp/foo:\n\tdev\t\t= /dev/sdz1\n\tvfs\t\t= jfs2\n\tmount\t\t= false\n\toptions\t\t= nodev,rw\n$}) end it "should not enable mount if it is mounted and already enabled and mount options are unchanged" do stub_mounted_enabled(@provider, @mounted_output, @enabled_output) @new_resource.options "rw" expect(@provider).not_to receive(:enable_fs) @provider.run_action(:enable) end end describe "disable_fs" do it "should disable mount if it is mounted and enabled" do stub_mounted_enabled(@provider, @mounted_output, @enabled_output) allow(::File).to receive(:open).with("/etc/filesystems", "r").and_return(<<-ETCFILESYSTEMS) /tmp/foo: dev = /dev/sdz1 vfs = jfs2 log = /dev/hd8 mount = true check = true vol = /opt free = false quota = no /tmp/abc: dev = /dev/sdz2 vfs = jfs2 mount = true options = rw ETCFILESYSTEMS filesystems = StringIO.new allow(::File).to receive(:open).with("/etc/filesystems", "w").and_yield(filesystems) @provider.run_action(:disable) expect(filesystems.string).to match(%r{^/tmp/abc:\s+dev\s+= /dev/sdz2\s+vfs\s+= jfs2\s+mount\s+= true\s+options\s+= rw\n$}) end it "should not disable mount if it is not mounted" do stub_mounted_enabled(@provider, @unmounted_output, "") expect(@provider).not_to receive(:disable_fs) @provider.run_action(:disable) end end end chef-12.14.60/spec/unit/provider/mount/mount_spec.rb000066400000000000000000000507611276456504500223070ustar00rootroot00000000000000# # Author:: Joshua Timberman () # 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" require "ostruct" describe Chef::Provider::Mount::Mount do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Mount.new("/tmp/foo") @new_resource.device "/dev/sdz1" @new_resource.device_type :device @new_resource.fstype "ext3" @new_resource.supports :remount => false @provider = Chef::Provider::Mount::Mount.new(@new_resource, @run_context) allow(::File).to receive(:exists?).with("/dev/sdz1").and_return true allow(::File).to receive(:exists?).with("/tmp/foo").and_return true allow(::File).to receive(:realpath).with("/dev/sdz1").and_return "/dev/sdz1" allow(::File).to receive(:realpath).with("/tmp/foo").and_return "/tmp/foo" end describe "when discovering the current fs state" do before do allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => "")) allow(::File).to receive(:foreach).with("/etc/fstab") end it "should create a current resource with the same mount point and device" do @provider.load_current_resource expect(@provider.current_resource.name).to eq("/tmp/foo") expect(@provider.current_resource.mount_point).to eq("/tmp/foo") expect(@provider.current_resource.device).to eq("/dev/sdz1") end it "should accecpt device_type :uuid", :not_supported_on_solaris do @status = double(:stdout => "/dev/sdz1\n", :exitstatus => 1) @new_resource.device_type :uuid @new_resource.device "d21afe51-a0fe-4dc6-9152-ac733763ae0a" @stdout_findfs = double("STDOUT", :first => "/dev/sdz1") expect(@provider).to receive(:shell_out).with("/sbin/findfs UUID=d21afe51-a0fe-4dc6-9152-ac733763ae0a").and_return(@status) @provider.load_current_resource() @provider.mountable? end describe "when dealing with network mounts" do { "nfs" => "nfsserver:/vol/path", "cifs" => "//cifsserver/share" }.each do |type, fs_spec| it "should detect network fs_spec (#{type})" do @new_resource.device fs_spec expect(@provider.network_device?).to be_truthy end it "should ignore trailing slash and set mounted to true for network mount (#{type})" do @new_resource.device fs_spec allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => "#{fs_spec}/ on /tmp/foo type #{type} (rw)\n")) @provider.load_current_resource expect(@provider.current_resource.mounted).to be_truthy end end end it "should raise an error if the mount device does not exist" do allow(::File).to receive(:exists?).with("/dev/sdz1").and_return false expect { @provider.load_current_resource(); @provider.mountable? }.to raise_error(Chef::Exceptions::Mount) end it "should not call mountable? with load_current_resource - CHEF-1565" do allow(::File).to receive(:exists?).with("/dev/sdz1").and_return false expect(@provider).to receive(:mounted?).and_return(true) expect(@provider).to receive(:enabled?).and_return(true) expect(@provider).not_to receive(:mountable?) @provider.load_current_resource end it "should raise an error if the mount device (uuid) does not exist", :not_supported_on_solaris do status = double(:stdout => "", :exitstatus => 1) @new_resource.device_type :uuid @new_resource.device "d21afe51-a0fe-4dc6-9152-ac733763ae0a" expect(@provider).to receive(:shell_out).with("/sbin/findfs UUID=d21afe51-a0fe-4dc6-9152-ac733763ae0a").and_return(status) expect(::File).to receive(:exists?).with("").and_return(false) expect { @provider.load_current_resource(); @provider.mountable? }.to raise_error(Chef::Exceptions::Mount) end it "should raise an error if the mount point does not exist" do allow(::File).to receive(:exists?).with("/tmp/foo").and_return false expect { @provider.load_current_resource(); @provider.mountable? }.to raise_error(Chef::Exceptions::Mount) end %w{tmpfs fuse cgroup}.each do |fstype| it "does not expect the device to exist for #{fstype}" do @new_resource.fstype(fstype) @new_resource.device("whatever") expect { @provider.load_current_resource(); @provider.mountable? }.not_to raise_error end end it "does not expect the device to exist if it's none" do @new_resource.device("none") expect { @provider.load_current_resource(); @provider.mountable? }.not_to raise_error end it "should set mounted true if the mount point is found in the mounts list" do allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => "/dev/sdz1 on /tmp/foo type ext3 (rw)\n")) @provider.load_current_resource() expect(@provider.current_resource.mounted).to be_truthy end it "should set mounted false if another mount point beginning with the same path is found in the mounts list" do allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => "/dev/sdz1 on /tmp/foobar type ext3 (rw)\n")) @provider.load_current_resource() expect(@provider.current_resource.mounted).to be_falsey end it "should set mounted true if the symlink target of the device is found in the mounts list" do # expand the target path to correct specs on Windows target = ::File.expand_path("/dev/mapper/target") allow(::File).to receive(:symlink?).with("#{@new_resource.device}").and_return(true) allow(::File).to receive(:readlink).with("#{@new_resource.device}").and_return(target) allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => "#{target} on /tmp/foo type ext3 (rw)\n")) @provider.load_current_resource() expect(@provider.current_resource.mounted).to be_truthy end it "should set mounted true if the symlink target of the device is relative and is found in the mounts list - CHEF-4957" do target = "xsdz1" # expand the target path to correct specs on Windows absolute_target = ::File.expand_path("/dev/xsdz1") allow(::File).to receive(:symlink?).with("#{@new_resource.device}").and_return(true) allow(::File).to receive(:readlink).with("#{@new_resource.device}").and_return(target) allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => "#{absolute_target} on /tmp/foo type ext3 (rw)\n")) @provider.load_current_resource() expect(@provider.current_resource.mounted).to be_truthy end it "should set mounted true if the mount point is found last in the mounts list" do mount = "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n" mount << "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n" allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => mount)) @provider.load_current_resource() expect(@provider.current_resource.mounted).to be_truthy end it "should set mounted false if the mount point is not last in the mounts list" do mount = "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n" mount << "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n" allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => mount)) @provider.load_current_resource() expect(@provider.current_resource.mounted).to be_falsey end it "mounted should be false if the mount point is not found in the mounts list" do allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => "/dev/sdy1 on /tmp/foo type ext3 (rw)\n")) @provider.load_current_resource() expect(@provider.current_resource.mounted).to be_falsey end it "should set enabled to true if the mount point is last in fstab" do fstab1 = "/dev/sdy1 /tmp/foo ext3 defaults 1 2\n" fstab2 = "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" allow(::File).to receive(:foreach).with("/etc/fstab").and_yield(fstab1).and_yield(fstab2) @provider.load_current_resource expect(@provider.current_resource.enabled).to be_truthy end it "should set enabled to true if the mount point is not last in fstab and mount_point is a substring of another mount" do fstab1 = "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" fstab2 = "/dev/sdy1 /tmp/foo/bar ext3 defaults 1 2\n" allow(::File).to receive(:foreach).with("/etc/fstab").and_yield(fstab1).and_yield(fstab2) @provider.load_current_resource expect(@provider.current_resource.enabled).to be_truthy end it "should set enabled to true if the symlink target is in fstab" do target = "/dev/mapper/target" allow(::File).to receive(:symlink?).with("#{@new_resource.device}").and_return(true) allow(::File).to receive(:readlink).with("#{@new_resource.device}").and_return(target) fstab = "/dev/sdz1 /tmp/foo ext3 defaults 1 2\n" allow(::File).to receive(:foreach).with("/etc/fstab").and_yield fstab @provider.load_current_resource expect(@provider.current_resource.enabled).to be_truthy end it "should set enabled to true if the symlink target is relative and is in fstab - CHEF-4957" do target = "xsdz1" allow(::File).to receive(:symlink?).with("#{@new_resource.device}").and_return(true) allow(::File).to receive(:readlink).with("#{@new_resource.device}").and_return(target) fstab = "/dev/sdz1 /tmp/foo ext3 defaults 1 2\n" allow(::File).to receive(:foreach).with("/etc/fstab").and_yield fstab @provider.load_current_resource expect(@provider.current_resource.enabled).to be_truthy end it "should set enabled to false if the mount point is not in fstab" do fstab = "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n" allow(::File).to receive(:foreach).with("/etc/fstab").and_yield fstab @provider.load_current_resource expect(@provider.current_resource.enabled).to be_falsey end it "should ignore commented lines in fstab " do fstab = "\# #{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" allow(::File).to receive(:foreach).with("/etc/fstab").and_yield fstab @provider.load_current_resource expect(@provider.current_resource.enabled).to be_falsey end it "should set enabled to false if the mount point is not last in fstab" do line_1 = "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" line_2 = "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n" allow(::File).to receive(:foreach).with("/etc/fstab").and_yield(line_1).and_yield(line_2) @provider.load_current_resource expect(@provider.current_resource.enabled).to be_falsey end it "should not mangle the mount options if the device in fstab is a symlink" do # expand the target path to correct specs on Windows target = "/dev/mapper/target" options = "rw,noexec,noauto" allow(::File).to receive(:symlink?).with(@new_resource.device).and_return(true) allow(::File).to receive(:readlink).with(@new_resource.device).and_return(target) fstab = "#{@new_resource.device} #{@new_resource.mount_point} #{@new_resource.fstype} #{options} 1 2\n" allow(::File).to receive(:foreach).with("/etc/fstab").and_yield fstab @provider.load_current_resource expect(@provider.current_resource.options).to eq(options.split(",")) end it "should not mangle the mount options if the symlink target is in fstab" do target = ::File.expand_path("/dev/mapper/target") options = "rw,noexec,noauto" allow(::File).to receive(:symlink?).with(@new_resource.device).and_return(true) allow(::File).to receive(:readlink).with(@new_resource.device).and_return(target) fstab = "#{target} #{@new_resource.mount_point} #{@new_resource.fstype} #{options} 1 2\n" allow(::File).to receive(:foreach).with("/etc/fstab").and_yield fstab @provider.load_current_resource expect(@provider.current_resource.options).to eq(options.split(",")) end end context "after the mount's state has been discovered" do before do @current_resource = Chef::Resource::Mount.new("/tmp/foo") @current_resource.device "/dev/sdz1" @current_resource.device_type :device @current_resource.fstype "ext3" @provider.current_resource = @current_resource end describe "mount_fs" do it "should mount the filesystem if it is not mounted" do expect(@provider).to receive(:shell_out!).with("mount -t ext3 -o defaults /dev/sdz1 /tmp/foo") @provider.mount_fs() end it "should mount the filesystem with options if options were passed" do options = "rw,noexec,noauto" @new_resource.options(%w{rw noexec noauto}) expect(@provider).to receive(:shell_out!).with("mount -t ext3 -o rw,noexec,noauto /dev/sdz1 /tmp/foo") @provider.mount_fs() end it "should mount the filesystem specified by uuid", :not_supported_on_solaris do status = double(:stdout => "/dev/sdz1\n", :exitstatus => 1) @new_resource.device "d21afe51-a0fe-4dc6-9152-ac733763ae0a" @new_resource.device_type :uuid allow(@provider).to receive(:shell_out).with("/sbin/findfs UUID=d21afe51-a0fe-4dc6-9152-ac733763ae0a").and_return(status) @stdout_mock = double("stdout mock") allow(@stdout_mock).to receive(:each).and_yield("#{@new_resource.device} on #{@new_resource.mount_point}") expect(@provider).to receive(:shell_out!).with("mount -t #{@new_resource.fstype} -o defaults -U #{@new_resource.device} #{@new_resource.mount_point}").and_return(@stdout_mock) @provider.mount_fs() end it "should not mount the filesystem if it is mounted" do allow(@current_resource).to receive(:mounted).and_return(true) expect(@provider).not_to receive(:shell_out!) @provider.mount_fs() end end describe "umount_fs" do it "should umount the filesystem if it is mounted" do @current_resource.mounted(true) expect(@provider).to receive(:shell_out!).with("umount /tmp/foo") @provider.umount_fs() end it "should not umount the filesystem if it is not mounted" do @current_resource.mounted(false) expect(@provider).not_to receive(:shell_out!) @provider.umount_fs() end end describe "remount_fs" do it "should use mount -o remount if remount is supported" do @new_resource.supports({ :remount => true }) @current_resource.mounted(true) expect(@provider).to receive(:shell_out!).with("mount -o remount,defaults #{@new_resource.mount_point}") @provider.remount_fs end it "should use mount -o remount with new mount options if remount is supported" do @new_resource.supports({ :remount => true }) options = "rw,noexec,noauto" @new_resource.options(%w{rw noexec noauto}) @current_resource.mounted(true) expect(@provider).to receive(:shell_out!).with("mount -o remount,rw,noexec,noauto #{@new_resource.mount_point}") @provider.remount_fs end it "should umount and mount if remount is not supported" do @new_resource.supports({ :remount => false }) @current_resource.mounted(true) expect(@provider).to receive(:umount_fs) expect(@provider).to receive(:sleep).with(1) expect(@provider).to receive(:mount_fs) @provider.remount_fs() end it "should not try to remount at all if mounted is false" do @current_resource.mounted(false) expect(@provider).not_to receive(:shell_out!) expect(@provider).not_to receive(:umount_fs) expect(@provider).not_to receive(:mount_fs) @provider.remount_fs() end end describe "when enabling the fs" do it "should enable if enabled isn't true" do @current_resource.enabled(false) @fstab = StringIO.new allow(::File).to receive(:open).with("/etc/fstab", "a").and_yield(@fstab) @provider.enable_fs expect(@fstab.string).to match(%r{^/dev/sdz1\s+/tmp/foo\s+ext3\s+defaults\s+0\s+2\s*$}) end it "should not enable if enabled is true and resources match" do @current_resource.enabled(true) @current_resource.fstype("ext3") @current_resource.options(["defaults"]) @current_resource.dump(0) @current_resource.pass(2) expect(::File).not_to receive(:open).with("/etc/fstab", "a") @provider.enable_fs end it "should enable if enabled is true and resources do not match" do @current_resource.enabled(true) @current_resource.fstype("auto") @current_resource.options(["defaults"]) @current_resource.dump(0) @current_resource.pass(2) @fstab = StringIO.new allow(::File).to receive(:readlines).and_return([]) expect(::File).to receive(:open).once.with("/etc/fstab", "w").and_yield(@fstab) expect(::File).to receive(:open).once.with("/etc/fstab", "a").and_yield(@fstab) @provider.enable_fs end end describe "when disabling the fs" do it "should disable if enabled is true" do @current_resource.enabled(true) other_mount = "/dev/sdy1 /tmp/foo ext3 defaults 1 2\n" this_mount = "/dev/sdz1 /tmp/foo ext3 defaults 1 2\n" @fstab_read = [this_mount, other_mount] allow(::File).to receive(:readlines).with("/etc/fstab").and_return(@fstab_read) @fstab_write = StringIO.new allow(::File).to receive(:open).with("/etc/fstab", "w").and_yield(@fstab_write) @provider.disable_fs expect(@fstab_write.string).to match(Regexp.escape(other_mount)) expect(@fstab_write.string).not_to match(Regexp.escape(this_mount)) end it "should disable if enabled is true and ignore commented lines" do @current_resource.enabled(true) fstab_read = [%q{/dev/sdy1 /tmp/foo ext3 defaults 1 2}, %q{/dev/sdz1 /tmp/foo ext3 defaults 1 2}, %q{#/dev/sdz1 /tmp/foo ext3 defaults 1 2}] fstab_write = StringIO.new allow(::File).to receive(:readlines).with("/etc/fstab").and_return(fstab_read) allow(::File).to receive(:open).with("/etc/fstab", "w").and_yield(fstab_write) @provider.disable_fs expect(fstab_write.string).to match(%r{^/dev/sdy1 /tmp/foo ext3 defaults 1 2$}) expect(fstab_write.string).to match(%r{^#/dev/sdz1 /tmp/foo ext3 defaults 1 2$}) expect(fstab_write.string).not_to match(%r{^/dev/sdz1 /tmp/foo ext3 defaults 1 2$}) end it "should disable only the last entry if enabled is true" do allow(@current_resource).to receive(:enabled).and_return(true) fstab_read = ["/dev/sdz1 /tmp/foo ext3 defaults 1 2\n", "/dev/sdy1 /tmp/foo ext3 defaults 1 2\n", "/dev/sdz1 /tmp/foo ext3 defaults 1 2\n", "/dev/sdz1 /tmp/foobar ext3 defaults 1 2\n"] fstab_write = StringIO.new allow(::File).to receive(:readlines).with("/etc/fstab").and_return(fstab_read) allow(::File).to receive(:open).with("/etc/fstab", "w").and_yield(fstab_write) @provider.disable_fs expect(fstab_write.string).to eq("/dev/sdz1 /tmp/foo ext3 defaults 1 2\n" + "/dev/sdy1 /tmp/foo ext3 defaults 1 2\n" + "/dev/sdz1 /tmp/foobar ext3 defaults 1 2\n") end it "should not disable if enabled is false" do allow(@current_resource).to receive(:enabled).and_return(false) allow(::File).to receive(:readlines).with("/etc/fstab").and_return([]) expect(::File).not_to receive(:open).and_yield(@fstab) @provider.disable_fs end end end end chef-12.14.60/spec/unit/provider/mount/solaris_spec.rb000066400000000000000000000727021276456504500226200ustar00rootroot00000000000000# # Author:: Lamont Granquist () # 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" require "ostruct" # Do not run these tests on windows because some path handling # code is not implemented to handle windows paths. describe Chef::Provider::Mount::Solaris, :unix_only do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:device_type) { :device } let(:fstype) { "ufs" } let(:device) { "/dev/dsk/c0t2d0s7" } let(:fsck_device) { "/dev/rdsk/c0t2d0s7" } let(:mountpoint) { "/mnt/foo" } let(:options) { nil } let(:new_resource) do new_resource = Chef::Resource::Mount.new(mountpoint) new_resource.device device new_resource.device_type device_type new_resource.fsck_device fsck_device new_resource.fstype fstype new_resource.options options new_resource.supports :remount => false new_resource end let(:provider) do Chef::Provider::Mount::Solaris.new(new_resource, run_context) end let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" #device device mount FS fsck mount mount #to mount to fsck point type pass at boot options # fd - /dev/fd fd - no - /proc - /proc proc - no - # swap /dev/dsk/c0t0d0s1 - - swap - no - # root /dev/dsk/c0t0d0s0 /dev/rdsk/c0t0d0s0 / ufs 1 no - # tmpfs swap - /tmp tmpfs - yes - # nfs cartman:/share2 - /cartman nfs - yes rw,soft # ufs /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - EOF end let(:vfstab_file) do t = Tempfile.new("rspec-vfstab") t.write(vfstab_file_contents) t.close t end let(:mount_output) do <<-EOF.gsub /^\s*/, "" /dev/dsk/c0t0d0s0 on / type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200000 on Tue Jul 31 22:34:46 2012 /dev/dsk/c0t2d0s7 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 EOF end before do stub_const("Chef::Provider::Mount::Solaris::VFSTAB", vfstab_file.path ) allow(provider).to receive(:shell_out!).with("mount -v").and_return(OpenStruct.new(:stdout => mount_output)) allow(File).to receive(:symlink?).with(device).and_return(false) allow(File).to receive(:exist?).and_call_original # Tempfile.open on ruby 1.8.7 calls File.exist? allow(File).to receive(:exist?).with(device).and_return(true) allow(File).to receive(:exist?).with(mountpoint).and_return(true) expect(File).to_not receive(:exists?) end describe "#define_resource_requirements" do before do # we're not testing the actual actions so stub them all out [:mount_fs, :umount_fs, :remount_fs, :enable_fs, :disable_fs].each { |m| allow(provider).to receive(m) } end it "run_action(:mount) should raise an error if the device does not exist" do allow(File).to receive(:exist?).with(device).and_return(false) expect { provider.run_action(:mount) }.to raise_error(Chef::Exceptions::Mount) end it "run_action(:remount) should raise an error if the device does not exist" do allow(File).to receive(:exist?).with(device).and_return(false) expect { provider.run_action(:remount) }.to raise_error(Chef::Exceptions::Mount) end it "run_action(:mount) should raise an error if the mountpoint does not exist" do allow(File).to receive(:exist?).with(mountpoint).and_return false expect { provider.run_action(:mount) }.to raise_error(Chef::Exceptions::Mount) end it "run_action(:remount) should raise an error if the mountpoint does not exist" do allow(File).to receive(:exist?).with(mountpoint).and_return false expect { provider.run_action(:remount) }.to raise_error(Chef::Exceptions::Mount) end %w{tmpfs nfs ctfs proc mntfs objfs sharefs fd smbfs vxfs}.each do |ft| context "when the device has a fstype of #{ft}" do let(:fstype) { ft } let(:fsck_device) { "-" } let(:device) { "something_that_is_not_a_file" } before do expect(File).to_not receive(:exist?).with(device) end it "run_action(:mount) should not raise an error" do expect { provider.run_action(:mount) }.to_not raise_error end it "run_action(:remount) should not raise an error" do expect { provider.run_action(:remount) }.to_not raise_error end end end end describe "#load_current_resource" do context "when loading a normal UFS filesystem with mount at boot" do before do provider.load_current_resource end it "should create a current_resource of type Chef::Resource::Mount" do expect(provider.current_resource).to be_a(Chef::Resource::Mount) end it "should set the name on the current_resource" do expect(provider.current_resource.name).to eq(mountpoint) end it "should set the mount_point on the current_resource" do expect(provider.current_resource.mount_point).to eq(mountpoint) end it "should set the device on the current_resource" do expect(provider.current_resource.device).to eq(device) end it "should set the fsck_device on the current_resource" do expect(provider.current_resource.fsck_device).to eq(fsck_device) end it "should set the device_type on the current_resource" do expect(provider.current_resource.device_type).to eq(device_type) end it "should set the mounted status on the current_resource" do expect(provider.current_resource.mounted).to be_truthy end it "should set the enabled status on the current_resource" do expect(provider.current_resource.enabled).to be_truthy end it "should set the fstype field on the current_resource" do expect(provider.current_resource.fstype).to eql("ufs") end it "should set the options field on the current_resource" do expect(provider.current_resource.options).to eql(["-"]) end it "should set the pass field on the current_resource" do expect(provider.current_resource.pass).to eql(2) end it "should not throw an exception when the device does not exist - CHEF-1565" do allow(File).to receive(:exist?).with(device).and_return(false) expect { provider.load_current_resource }.to_not raise_error end it "should not throw an exception when the mount point does not exist" do allow(File).to receive(:exist?).with(mountpoint).and_return false expect { provider.load_current_resource }.to_not raise_error end end end describe "#load_current_resource" do context "when loading a normal UFS filesystem with noauto, don't mount at boot" do let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" #device device mount FS fsck mount mount #to mount to fsck point type pass at boot options # fd - /dev/fd fd - no - /proc - /proc proc - no - # swap /dev/dsk/c0t0d0s1 - - swap - no - # root /dev/dsk/c0t0d0s0 /dev/rdsk/c0t0d0s0 / ufs 1 no - # tmpfs swap - /tmp tmpfs - yes - # nfs cartman:/share2 - /cartman nfs - yes rw,soft # ufs /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 no - EOF end before do provider.load_current_resource end it "should set the options field on the current_resource" do expect(provider.current_resource.options).to eql(["-", "noauto"]) end end context "when the device is an smbfs mount" do let(:mount_output) do <<-EOF.gsub /^\s*/, "" //solarsystem/tmp on /mnt type smbfs read/write/setuid/devices/dev=5080000 on Tue Mar 29 11:40:18 2011 EOF end let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" //WORKGROUP;username:password@host/share - /mountpoint smbfs - no fileperms=0777,dirperms=0777 EOF end let(:fsck_device) { "-" } it "should work at some point in the future" do skip "SMBFS mounts on solaris look like they will need some future code work and more investigation" end end context "when the device is an NFS mount" do let(:mount_output) do <<-EOF.gsub /^\s*/, "" cartman:/share2 on /cartman type nfs rsize=32768,wsize=32768,NFSv4,dev=4000004 on Tue Mar 29 11:40:18 2011 EOF end let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" cartman:/share2 - /cartman nfs - yes rw,soft EOF end let(:fsck_device) { "-" } let(:fstype) { "nfs" } let(:device) { "cartman:/share2" } let(:mountpoint) { "/cartman" } before do provider.load_current_resource end it "should set the name on the current_resource" do expect(provider.current_resource.name).to eq(mountpoint) end it "should set the mount_point on the current_resource" do expect(provider.current_resource.mount_point).to eq(mountpoint) end it "should set the device on the current_resource" do expect(provider.current_resource.device).to eq(device) end it "should set the device_type on the current_resource" do expect(provider.current_resource.device_type).to eq(device_type) end it "should set the mounted status on the current_resource" do expect(provider.current_resource.mounted).to be_truthy end it "should set the enabled status on the current_resource" do expect(provider.current_resource.enabled).to be_truthy end it "should set the fstype field on the current_resource" do expect(provider.current_resource.fstype).to eql("nfs") end it "should set the options field on the current_resource" do expect(provider.current_resource.options).to eql(%w{rw soft}) end it "should set the pass field on the current_resource" do # is this correct or should it be nil? # # vfstab man page says. # "A - is used to indicate no entry in a field." # 0 and - could mean different things for some file systems expect(provider.current_resource.pass).to eql(0) end end context "when the device is symlink" do let(:target) { "/dev/mapper/target" } let(:mount_output) do <<-EOF.gsub /^\s*/, "" #{target} on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 EOF end let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" #{target} /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - EOF end before do expect(File).to receive(:symlink?).with(device).at_least(:once).and_return(true) expect(File).to receive(:readlink).with(device).at_least(:once).and_return(target) provider.load_current_resource() end it "should set mounted true if the symlink target of the device is found in the mounts list" do expect(provider.current_resource.mounted).to be_truthy end it "should set enabled true if the symlink target of the device is found in the vfstab" do expect(provider.current_resource.enabled).to be_truthy end it "should have the correct mount options" do expect(provider.current_resource.options).to eql(["-"]) end end context "when the device is a relative symlink" do let(:target) { "foo" } let(:absolute_target) { File.expand_path(target, File.dirname(device)) } let(:mount_output) do <<-EOF.gsub /^\s*/, "" #{absolute_target} on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 EOF end let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" #{absolute_target} /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - EOF end before do expect(File).to receive(:symlink?).with(device).at_least(:once).and_return(true) expect(File).to receive(:readlink).with(device).at_least(:once).and_return(target) provider.load_current_resource() end it "should set mounted true if the symlink target of the device is found in the mounts list" do expect(provider.current_resource.mounted).to be_truthy end it "should set enabled true if the symlink target of the device is found in the vfstab" do expect(provider.current_resource.enabled).to be_truthy end it "should have the correct mount options" do expect(provider.current_resource.options).to eql(["-"]) end end context "when the matching mount point is last in the mounts list" do let(:mount_output) do <<-EOF.gsub /^\s*/, "" /dev/dsk/c0t0d0s0 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200000 on Tue Jul 31 22:34:46 2012 /dev/dsk/c0t2d0s7 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 EOF end it "should set mounted true" do provider.load_current_resource() expect(provider.current_resource.mounted).to be_truthy end end context "when the matching mount point is not last in the mounts list" do let(:mount_output) do <<-EOF.gsub /^\s*/, "" /dev/dsk/c0t2d0s7 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 /dev/dsk/c0t0d0s0 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200000 on Tue Jul 31 22:34:46 2012 EOF end it "should set mounted false" do provider.load_current_resource() expect(provider.current_resource.mounted).to be_falsey end end context "when the matching mount point is not in the mounts list (mountpoint wrong)" do let(:mount_output) do <<-EOF.gsub /^\s*/, "" /dev/dsk/c0t2d0s7 on /mnt/foob type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 EOF end it "should set mounted false" do provider.load_current_resource() expect(provider.current_resource.mounted).to be_falsey end end context "when the matching mount point is not in the mounts list (raw device wrong)" do let(:mount_output) do <<-EOF.gsub /^\s*/, "" /dev/dsk/c0t2d0s72 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 EOF end it "should set mounted false" do provider.load_current_resource() expect(provider.current_resource.mounted).to be_falsey end end context "when the mount point is last in fstab" do let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" /dev/dsk/c0t2d0s72 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - EOF end it "should set enabled to true" do provider.load_current_resource expect(provider.current_resource.enabled).to be_truthy end end context "when the mount point is not last in fstab and is a substring of another mount" do let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - /dev/dsk/c0t2d0s72 /dev/rdsk/c0t2d0s7 /mnt/foo/bar ufs 2 yes - EOF end it "should set enabled to true" do provider.load_current_resource expect(provider.current_resource.enabled).to be_truthy end end context "when the mount point is not last in fstab" do let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - /dev/dsk/c0t2d0s72 /dev/rdsk/c0t2d0s72 /mnt/foo ufs 2 yes - EOF end it "should set enabled to false" do provider.load_current_resource expect(provider.current_resource.enabled).to be_falsey end end context "when the mount point is not in fstab, but the mountpoint is a substring of one that is" do let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foob ufs 2 yes - EOF end it "should set enabled to false" do provider.load_current_resource expect(provider.current_resource.enabled).to be_falsey end end context "when the mount point is not in fstab, but the device is a substring of one that is" do let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" /dev/dsk/c0t2d0s72 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - EOF end it "should set enabled to false" do provider.load_current_resource expect(provider.current_resource.enabled).to be_falsey end end context "when the mountpoint line is commented out" do let(:vfstab_file_contents) do <<-EOF.gsub /^\s*/, "" #/dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - EOF end it "should set enabled to false" do provider.load_current_resource expect(provider.current_resource.enabled).to be_falsey end end end context "after the mount's state has been discovered" do describe "mount_fs" do it "should mount the filesystem" do expect(provider).to receive(:shell_out!).with("mount -F #{fstype} -o defaults #{device} #{mountpoint}") provider.mount_fs() end it "should mount the filesystem with options if options were passed" do options = "logging,noatime,largefiles,nosuid,rw,quota" new_resource.options(options.split(/,/)) expect(provider).to receive(:shell_out!).with("mount -F #{fstype} -o #{options} #{device} #{mountpoint}") provider.mount_fs() end it "should delete the 'noauto' magic option" do options = "rw,noauto" new_resource.options(%w{rw noauto}) expect(provider).to receive(:shell_out!).with("mount -F #{fstype} -o rw #{device} #{mountpoint}") provider.mount_fs() end end describe "umount_fs" do it "should umount the filesystem if it is mounted" do expect(provider).to receive(:shell_out!).with("umount #{mountpoint}") provider.umount_fs() end end describe "remount_fs without options and do not mount at boot" do it "should use mount -o remount" do new_resource.options(%w{noauto}) expect(provider).to receive(:shell_out!).with("mount -o remount #{new_resource.mount_point}") provider.remount_fs end end describe "remount_fs with options and do not mount at boot" do it "should use mount -o remount,rw" do new_resource.options(%w{rw noauto}) expect(provider).to receive(:shell_out!).with("mount -o remount,rw #{new_resource.mount_point}") provider.remount_fs end end describe "remount_fs with options and mount at boot" do it "should use mount -o remount,rw" do new_resource.options(%w{rw}) expect(provider).to receive(:shell_out!).with("mount -o remount,rw #{new_resource.mount_point}") provider.remount_fs end end describe "remount_fs without options and mount at boot" do it "should use mount -o remount" do new_resource.options([]) expect(provider).to receive(:shell_out!).with("mount -o remount #{new_resource.mount_point}") provider.remount_fs end end describe "when enabling the fs" do context "in the typical case" do let(:other_mount) { "/dev/dsk/c0t2d0s0 /dev/rdsk/c0t2d0s0 / ufs 2 yes -" } let(:this_mount) { "/dev/dsk/c0t2d0s7\t/dev/rdsk/c0t2d0s7\t/mnt/foo\tufs\t2\tyes\tdefaults\n" } let(:vfstab_file_contents) { [other_mount].join("\n") } before do allow(provider).to receive(:etc_tempfile).and_yield(Tempfile.open("vfstab")) provider.load_current_resource provider.enable_fs end it "should leave the other mountpoint alone" do expect(IO.read(vfstab_file.path)).to match(/^#{Regexp.escape(other_mount)}/) end it "should enable the mountpoint we care about" do expect(IO.read(vfstab_file.path)).to match(/^#{Regexp.escape(this_mount)}/) end end context "when the mount has options=noauto" do let(:other_mount) { "/dev/dsk/c0t2d0s0 /dev/rdsk/c0t2d0s0 / ufs 2 yes -" } let(:this_mount) { "/dev/dsk/c0t2d0s7\t/dev/rdsk/c0t2d0s7\t/mnt/foo\tufs\t2\tno\t-\n" } let(:options) { "noauto" } let(:vfstab_file_contents) { [other_mount].join("\n") } before do allow(provider).to receive(:etc_tempfile).and_yield(Tempfile.open("vfstab")) provider.load_current_resource provider.enable_fs end it "should leave the other mountpoint alone" do expect(IO.read(vfstab_file.path)).to match(/^#{Regexp.escape(other_mount)}/) end it "should enable the mountpoint we care about" do expect(IO.read(vfstab_file.path)).to match(/^#{Regexp.escape(this_mount)}/) end end context "when the new mount has options of noauto and the existing mount has mount at boot yes" do let(:existing_mount) { "/dev/dsk/c0t2d0s7\t/dev/rdsk/c0t2d0s7\t/mnt/foo\tufs\t2\tyes\t-" } let(:this_mount) { "/dev/dsk/c0t2d0s7\t/dev/rdsk/c0t2d0s7\t/mnt/foo\tufs\t2\tno\t-\n" } let(:options) { "noauto" } let(:vfstab_file_contents) { [existing_mount].join("\n") } before do allow(provider).to receive(:etc_tempfile).and_yield(Tempfile.open("vfstab")) provider.load_current_resource provider.mount_options_unchanged? provider.send(:vfstab_entry) end it "should detect a changed entry" do expect(provider.mount_options_unchanged?).to eq(false) end it "should change mount at boot to no" do expect(provider.send(:vfstab_entry)).to match(/^#{Regexp.escape(this_mount)}/) end end context "when the new mount has options of - and the existing mount has mount at boot no" do let(:existing_mount) { "/dev/dsk/c0t2d0s7\t/dev/rdsk/c0t2d0s7\t/mnt/foo\tufs\t2\tno\t-" } let(:this_mount) { "/dev/dsk/c0t2d0s7\t/dev/rdsk/c0t2d0s7\t/mnt/foo\tufs\t2\tyes\t-\n" } let(:options) { "-" } let(:vfstab_file_contents) { [existing_mount].join("\n") } before do allow(provider).to receive(:etc_tempfile).and_yield(Tempfile.open("vfstab")) provider.load_current_resource provider.mount_options_unchanged? provider.send(:vfstab_entry) end it "should detect a changed entry" do expect(provider.mount_options_unchanged?).to eq(false) end it "should change mount at boot to yes" do expect(provider.send(:vfstab_entry)).to match(/^#{Regexp.escape(this_mount)}/) end end context "when the new mount has options of noauto and the existing mount has mount at boot no" do let(:existing_mount) { "/dev/dsk/c0t2d0s7\t/dev/rdsk/c0t2d0s7\t/mnt/foo\tufs\t2\tno\t-" } let(:this_mount) { "/dev/dsk/c0t2d0s7\t/dev/rdsk/c0t2d0s7\t/mnt/foo\tufs\t2\tno\t-\n" } let(:options) { "-,noauto" } let(:vfstab_file_contents) { [existing_mount].join("\n") } before do allow(provider).to receive(:etc_tempfile).and_yield(Tempfile.open("vfstab")) provider.load_current_resource provider.mount_options_unchanged? provider.send(:vfstab_entry) end it "should detect an unchanged entry" do expect(provider.mount_options_unchanged?).to eq(true) end it "should not change mount at boot" do expect(provider.send(:vfstab_entry)).to match(/^#{Regexp.escape(this_mount)}/) end end context "when the new mount has options of - and the existing mount has mount at boot yes" do let(:existing_mount) { "/dev/dsk/c0t2d0s7\t/dev/rdsk/c0t2d0s7\t/mnt/foo\tufs\t2\tyes\t-" } let(:this_mount) { "/dev/dsk/c0t2d0s7\t/dev/rdsk/c0t2d0s7\t/mnt/foo\tufs\t2\tyes\t-\n" } let(:options) { "-" } let(:vfstab_file_contents) { [existing_mount].join("\n") } before do allow(provider).to receive(:etc_tempfile).and_yield(Tempfile.open("vfstab")) provider.load_current_resource provider.mount_options_unchanged? provider.send(:vfstab_entry) end it "should detect an unchanged entry" do expect(provider.mount_options_unchanged?).to eq(true) end it "should not change mount at boot" do expect(provider.send(:vfstab_entry)).to match(/^#{Regexp.escape(this_mount)}/) end end end describe "when disabling the fs" do context "in the typical case" do let(:other_mount) { "/dev/dsk/c0t2d0s0 /dev/rdsk/c0t2d0s0 / ufs 2 yes -" } let(:this_mount) { "/dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes -" } let(:vfstab_file_contents) { [other_mount, this_mount].join("\n") } before do allow(provider).to receive(:etc_tempfile).and_yield(Tempfile.open("vfstab")) provider.disable_fs end it "should leave the other mountpoint alone" do expect(IO.read(vfstab_file.path)).to match(/^#{Regexp.escape(other_mount)}/) end it "should disable the mountpoint we care about" do expect(IO.read(vfstab_file.path)).not_to match(/^#{Regexp.escape(this_mount)}/) end end context "when there is a commented out line" do let(:other_mount) { "/dev/dsk/c0t2d0s0 /dev/rdsk/c0t2d0s0 / ufs 2 yes -" } let(:this_mount) { "/dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes -" } let(:comment) { "#/dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes -" } let(:vfstab_file_contents) { [other_mount, this_mount, comment].join("\n") } before do allow(provider).to receive(:etc_tempfile).and_yield(Tempfile.open("vfstab")) provider.disable_fs end it "should leave the other mountpoint alone" do expect(IO.read(vfstab_file.path)).to match(/^#{Regexp.escape(other_mount)}/) end it "should disable the mountpoint we care about" do expect(IO.read(vfstab_file.path)).not_to match(/^#{Regexp.escape(this_mount)}/) end it "should keep the comment" do expect(IO.read(vfstab_file.path)).to match(/^#{Regexp.escape(comment)}/) end end context "when there is a duplicated line" do let(:other_mount) { "/dev/dsk/c0t2d0s0 /dev/rdsk/c0t2d0s0 / ufs 2 yes -" } let(:this_mount) { "/dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes -" } let(:vfstab_file_contents) { [this_mount, other_mount, this_mount].join("\n") } before do allow(provider).to receive(:etc_tempfile).and_yield(Tempfile.open("vfstab")) provider.disable_fs end it "should leave the other mountpoint alone" do expect(IO.read(vfstab_file.path)).to match(/^#{Regexp.escape(other_mount)}/) end it "should still match the duplicated mountpoint" do expect(IO.read(vfstab_file.path)).to match(/^#{Regexp.escape(this_mount)}/) end it "should have removed the last line" do expect(IO.read(vfstab_file.path)).to eql( "#{this_mount}\n#{other_mount}\n" ) end end end end end chef-12.14.60/spec/unit/provider/mount/windows_spec.rb000066400000000000000000000122241276456504500226270ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" class Chef class Util class Windows class NetUse end class Volume end end end end GUID = "\\\\?\\Volume{578e72b5-6e70-11df-b5c5-000c29d4a7d9}\\" REMOTE = "\\\\server-name\\path" describe Chef::Provider::Mount::Windows do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Mount.new("X:") @new_resource.device GUID @current_resource = Chef::Resource::Mount.new("X:") allow(Chef::Resource::Mount).to receive(:new).and_return(@current_resource) @net_use = double("Chef::Util::Windows::NetUse") allow(Chef::Util::Windows::NetUse).to receive(:new).and_return(@net_use) @vol = double("Chef::Util::Windows::Volume") allow(Chef::Util::Windows::Volume).to receive(:new).and_return(@vol) @provider = Chef::Provider::Mount::Windows.new(@new_resource, @run_context) @provider.current_resource = @current_resource end describe "when loading the current resource" do it "should set mounted true if the mount point is found" do allow(@vol).to receive(:device).and_return(@new_resource.device) expect(@current_resource).to receive(:mounted).with(true) @provider.load_current_resource end it "should set mounted false if the mount point is not found" do allow(@vol).to receive(:device).and_raise(ArgumentError) expect(@current_resource).to receive(:mounted).with(false) @provider.load_current_resource end describe "with a local device" do before do @new_resource.device GUID allow(@vol).to receive(:device).and_return(@new_resource.device) allow(@net_use).to receive(:device).and_raise(ArgumentError) end it "should determine the device is a volume GUID" do expect(@provider).to receive(:is_volume).with(@new_resource.device).and_return(true) @provider.load_current_resource end end describe "with a remote device" do before do @new_resource.device REMOTE allow(@net_use).to receive(:device).and_return(@new_resource.device) allow(@vol).to receive(:device).and_raise(ArgumentError) end it "should determine the device is remote" do expect(@provider).to receive(:is_volume).with(@new_resource.device).and_return(false) @provider.load_current_resource end end describe "when mounting a file system" do before do @new_resource.device GUID allow(@vol).to receive(:add) allow(@vol).to receive(:device).and_raise(ArgumentError) @provider.load_current_resource end it "should mount the filesystem if it is not mounted" do expect(@vol).to receive(:add).with(:remote => @new_resource.device, :username => @new_resource.username, :domainname => @new_resource.domain, :password => @new_resource.password) @provider.mount_fs end it "should not mount the filesystem if it is mounted" do expect(@vol).not_to receive(:add) allow(@current_resource).to receive(:mounted).and_return(true) @provider.mount_fs end context "mount_options_unchanged?" do it "should return true if mounted device is the same" do allow(@current_resource).to receive(:device).and_return(GUID) expect(@provider.send :mount_options_unchanged?).to be true end it "should return false if mounted device has changed" do allow(@current_resource).to receive(:device).and_return("XXXX") expect(@provider.send :mount_options_unchanged?).to be false end end end describe "when unmounting a file system" do before do @new_resource.device GUID allow(@vol).to receive(:delete) allow(@vol).to receive(:device).and_raise(ArgumentError) @provider.load_current_resource end it "should umount the filesystem if it is mounted" do allow(@current_resource).to receive(:mounted).and_return(true) expect(@vol).to receive(:delete) @provider.umount_fs end it "should not umount the filesystem if it is not mounted" do allow(@current_resource).to receive(:mounted).and_return(false) expect(@vol).not_to receive(:delete) @provider.umount_fs end end end end chef-12.14.60/spec/unit/provider/mount_spec.rb000066400000000000000000000155251276456504500211440ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Author:: Lamont Granquist () # 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::Mount do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) do new_resource = Chef::Resource::Mount.new("/tmp/foo") new_resource.device "/dev/sdz1" new_resource.name "/tmp/foo" new_resource.mount_point "/tmp/foo" new_resource.fstype "ext3" new_resource end let(:current_resource) do # this abstract superclass has no load_current_resource to call current_resource = Chef::Resource::Mount.new("/tmp/foo") current_resource.device "/dev/sdz1" current_resource.name "/tmp/foo" current_resource.mount_point "/tmp/foo" current_resource.fstype "ext3" current_resource end let(:provider) do provider = Chef::Provider::Mount.new(new_resource, run_context) provider.current_resource = current_resource provider end describe "when the target state is a mounted filesystem" do it "should mount the filesystem if it isn't mounted" do allow(current_resource).to receive(:mounted).and_return(false) expect(provider).to receive(:mount_fs).and_return(true) provider.run_action(:mount) expect(new_resource).to be_updated_by_last_action end end describe "when the target state is an unmounted filesystem" do it "should umount the filesystem if it is mounted" do allow(current_resource).to receive(:mounted).and_return(true) expect(provider).to receive(:umount_fs).and_return(true) provider.run_action(:umount) expect(new_resource).to be_updated_by_last_action end it "should not umount the filesystem if it is not mounted" do allow(current_resource).to receive(:mounted).and_return(false) expect(provider).not_to receive(:umount_fs) provider.run_action(:umount) expect(new_resource).not_to be_updated_by_last_action end end describe "when the filesystem should be remounted and the resource supports remounting" do before do new_resource.supports[:remount] = true end it "should remount the filesystem if it is mounted" do allow(current_resource).to receive(:mounted).and_return(true) expect(provider).to receive(:remount_fs).and_return(true) provider.run_action(:remount) expect(new_resource).to be_updated_by_last_action end it "should not remount the filesystem if it is not mounted" do allow(current_resource).to receive(:mounted).and_return(false) expect(provider).not_to receive(:remount_fs) provider.run_action(:remount) expect(new_resource).not_to be_updated_by_last_action end end describe "when the filesystem should be remounted and the resource does not support remounting" do before do new_resource.supports[:remount] = false allow(current_resource).to receive(:mounted).and_return(true) end it "should try a umount/remount of the filesystem" do expect(provider).to receive(:umount_fs) expect(provider).to receive(:mounted?).and_return(true, false) expect(provider).to receive(:mount_fs) provider.run_action(:remount) expect(new_resource).to be_updated_by_last_action end it "should fail when it runs out of remounts" do provider.unmount_retries = 1 expect(provider).to receive(:umount_fs) expect(provider).to receive(:mounted?).and_return(true, true) expect { provider.run_action(:remount) }.to raise_error(Chef::Exceptions::Mount) end end describe "when enabling the filesystem to be mounted" do it "should enable the mount if it isn't enable" do allow(current_resource).to receive(:enabled).and_return(false) expect(provider).not_to receive(:mount_options_unchanged?) expect(provider).to receive(:enable_fs).and_return(true) provider.run_action(:enable) expect(new_resource).to be_updated_by_last_action end it "should enable the mount if it is enabled and mount options have changed" do allow(current_resource).to receive(:enabled).and_return(true) expect(provider).to receive(:mount_options_unchanged?).and_return(false) expect(provider).to receive(:enable_fs).and_return(true) provider.run_action(:enable) expect(new_resource).to be_updated_by_last_action end it "should not enable the mount if it is enabled and mount options have not changed" do allow(current_resource).to receive(:enabled).and_return(true) expect(provider).to receive(:mount_options_unchanged?).and_return(true) expect(provider).not_to receive(:enable_fs) provider.run_action(:enable) expect(new_resource).not_to be_updated_by_last_action end end describe "when the target state is to disable the mount" do it "should disable the mount if it is enabled" do allow(current_resource).to receive(:enabled).and_return(true) expect(provider).to receive(:disable_fs).and_return(true) provider.run_action(:disable) expect(new_resource).to be_updated_by_last_action end it "should not disable the mount if it isn't enabled" do allow(current_resource).to receive(:enabled).and_return(false) expect(provider).not_to receive(:disable_fs) provider.run_action(:disable) expect(new_resource).not_to be_updated_by_last_action end end it "should delegates the mount implementation to subclasses" do expect { provider.mount_fs }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should delegates the umount implementation to subclasses" do expect { provider.umount_fs }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should delegates the remount implementation to subclasses" do expect { provider.remount_fs }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should delegates the enable implementation to subclasses" do expect { provider.enable_fs }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should delegates the disable implementation to subclasses" do expect { provider.disable_fs }.to raise_error(Chef::Exceptions::UnsupportedAction) end end chef-12.14.60/spec/unit/provider/ohai_spec.rb000066400000000000000000000055731276456504500207240ustar00rootroot00000000000000# # Author:: Michael Leinartas () # Copyright:: Copyright 2010-2016, Michael Leinartas # 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/run_context" describe Chef::Provider::Ohai do before(:each) do # Copied from client_spec @fqdn = "hostname.domainname" @hostname = "hostname" @platform = "example-platform" @platform_version = "example-platform" Chef::Config[:node_name] = @fqdn mock_ohai = { :fqdn => @fqdn, :hostname => @hostname, :platform => @platform, :platform_version => @platform_version, :data => { :origdata => "somevalue", }, :data2 => { :origdata => "somevalue", :newdata => "somevalue", }, } allow(mock_ohai).to receive(:all_plugins).and_return(true) allow(mock_ohai).to receive(:data).and_return(mock_ohai[:data], mock_ohai[:data2]) allow(Ohai::System).to receive(:new).and_return(mock_ohai) allow(Chef::Platform).to receive(:find_platform_and_version).and_return({ "platform" => @platform, "platform_version" => @platform_version }) # Fake node with a dummy save @node = Chef::Node.new @node.name(@fqdn) allow(@node).to receive(:save).and_return(@node) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Ohai.new("ohai_reload") ohai = Ohai::System.new ohai.all_plugins @node.consume_external_attrs(ohai.data, {}) @provider = Chef::Provider::Ohai.new(@new_resource, @run_context) end describe "when reloading ohai" do before do @node.automatic_attrs[:origdata] = "somevalue" end it "applies updated ohai data to the node" do expect(@node[:origdata]).to eq("somevalue") expect(@node[:newdata]).to be_nil @provider.run_action(:reload) expect(@node[:origdata]).to eq("somevalue") expect(@node[:newdata]).to eq("somevalue") end it "should reload a specific plugin and cause node to pick up new values" do @new_resource.plugin "someplugin" @provider.run_action(:reload) expect(@node[:origdata]).to eq("somevalue") expect(@node[:newdata]).to eq("somevalue") end end end chef-12.14.60/spec/unit/provider/osx_profile_spec.rb000066400000000000000000000310331276456504500223230ustar00rootroot00000000000000# # Author:: Nate Walck () # Copyright:: Copyright 2015-2016, Chef, 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::OsxProfile do let(:shell_out_success) do double("shell_out", :exitstatus => 0, :error? => false) end describe "action_create" do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) { Chef::Resource::OsxProfile.new("Profile Test", run_context) } let(:provider) { Chef::Provider::OsxProfile.new(new_resource, run_context) } let(:all_profiles) do { "_computerlevel" => [{ "ProfileDisplayName" => "Finder Settings", "ProfileIdentifier" => "com.apple.finder", "ProfileInstallDate" => "2015-11-08 23:15:21 +0000", "ProfileItems" => [{ "PayloadContent" => { "PayloadContentManagedPreferences" => { "com.apple.finder" => { "Forced" => [{ "mcx_preference_settings" => { "ShowExternalHardDrivesOnDesktop" => false } }] } } }, "PayloadDisplayName" => "Custom: (com.apple.finder)", "PayloadIdentifier" => "com.apple.finder", "PayloadType" => "com.apple.ManagedClient.preferences", "PayloadUUID" => "a017048f-684b-4e81-baa3-43afe316d739", "PayloadVersion" => 1 }], "ProfileOrganization" => "Chef", "ProfileRemovalDisallowed" => "false", "ProfileType" => "Configuration", "ProfileUUID" => "e2e09bef-e673-44a6-bcbe-ecb5f1c1b740", "ProfileVerificationState" => "unsigned", "ProfileVersion" => 1 }, { "ProfileDisplayName" => "ScreenSaver Settings", "ProfileIdentifier" => "com.testprofile.screensaver", "ProfileInstallDate" => "2015-10-05 23:15:21 +0000", "ProfileItems" => [{ "PayloadContent" => { "PayloadContentManagedPreferences" => { "com.apple.screensaver" => { "Forced" => [{ "mcx_preference_settings" => { "idleTime" => 0 } }] } } }, "PayloadDisplayName" => "Custom: (com.apple.screensaver)", "PayloadIdentifier" => "com.apple.screensaver", "PayloadType" => "com.apple.ManagedClient.preferences", "PayloadUUID" => "73fc30e0-1e57-0131-c32d-000c2944c110", "PayloadVersion" => 1 }], "ProfileOrganization" => "Chef", "ProfileRemovalDisallowed" => "false", "ProfileType" => "Configuration", "ProfileUUID" => "6e95927c-f200-54b4-85c7-52ab99b61c47", "ProfileVerificationState" => "unsigned", "ProfileVersion" => 1 }], } end # If anything is changed within this profile, be sure to update the # ProfileUUID in all_profiles to match the new config specific UUID let(:test_profile) do { "PayloadIdentifier" => "com.testprofile.screensaver", "PayloadRemovalDisallowed" => false, "PayloadScope" => "System", "PayloadType" => "Configuration", "PayloadUUID" => "1781fbec-3325-565f-9022-8aa28135c3cc", "PayloadOrganization" => "Chef", "PayloadVersion" => 1, "PayloadDisplayName" => "Screensaver Settings", "PayloadContent" => [ { "PayloadType" => "com.apple.ManagedClient.preferences", "PayloadVersion" => 1, "PayloadIdentifier" => "com.testprofile.screensaver", "PayloadUUID" => "73fc30e0-1e57-0131-c32d-000c2944c108", "PayloadEnabled" => true, "PayloadDisplayName" => "com.apple.screensaver", "PayloadContent" => { "com.apple.screensaver" => { "Forced" => [ { "mcx_preference_settings" => { "idleTime" => 0, }, }, ], }, }, }, ], } end let(:no_profiles) do {} end before(:each) do allow(provider).to receive(:cookbook_file_available?).and_return(true) allow(provider).to receive(:cache_cookbook_profile).and_return("/tmp/test.mobileconfig.remote") allow(provider).to receive(:get_new_profile_hash).and_return(test_profile) allow(provider).to receive(:get_installed_profiles).and_return(all_profiles) allow(provider).to receive(:read_plist).and_return(all_profiles) allow(::File).to receive(:unlink).and_return(true) end it "should build the get all profiles shellout command correctly" do profile_name = "com.testprofile.screensaver.mobileconfig" tempfile = "/tmp/allprofiles.plist" new_resource.profile_name profile_name allow(provider).to receive(:generate_tempfile).and_return(tempfile) allow(provider).to receive(:get_installed_profiles).and_call_original allow(provider).to receive(:read_plist).and_return(all_profiles) expect(provider).to receive(:shell_out!).with("profiles -P -o '/tmp/allprofiles.plist'") provider.load_current_resource end it "should use profile name as profile when no profile is set" do profile_name = "com.testprofile.screensaver.mobileconfig" new_resource.profile_name profile_name provider.load_current_resource expect(new_resource.profile_name).to eql(profile_name) end it "should use identifier from specified profile" do new_resource.profile test_profile provider.load_current_resource expect( provider.instance_variable_get(:@new_profile_identifier) ).to eql(test_profile["PayloadIdentifier"]) end it "should install when not installed" do new_resource.profile test_profile allow(provider).to receive(:get_installed_profiles).and_return(no_profiles) provider.load_current_resource expect { provider.run_action(:install) }.to_not raise_error end it "does not install if the profile is already installed" do new_resource.profile test_profile allow(provider).to receive(:get_installed_profiles).and_return(all_profiles) provider.load_current_resource expect(provider).to_not receive(:install_profile) expect { provider.action_install }.to_not raise_error end it "should install when installed but uuid differs" do new_resource.profile test_profile all_profiles["_computerlevel"][1]["ProfileUUID"] = "1781fbec-3325-565f-9022-9bb39245d4dd" provider.load_current_resource expect { provider.run_action(:install) }.to_not raise_error end it "should build the shellout install command correctly" do profile_path = "/tmp/test.mobileconfig" new_resource.profile test_profile # Change the profile so it triggers an install all_profiles["_computerlevel"][1]["ProfileUUID"] = "1781fbec-3325-565f-9022-9bb39245d4dd" provider.load_current_resource allow(provider).to receive(:write_profile_to_disk).and_return(profile_path) expect(provider).to receive(:shell_out).with("profiles -I -F '#{profile_path}'").and_return(shell_out_success) provider.action_install() end it "should fail if there is no identifier inside the profile" do test_profile.delete("PayloadIdentifier") new_resource.profile test_profile error_message = "The specified profile does not seem to be valid" expect { provider.run_action(:install) }.to raise_error(RuntimeError, error_message) end end describe "action_remove" do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) { Chef::Resource::OsxProfile.new("Profile Test", run_context) } let(:provider) { Chef::Provider::OsxProfile.new(new_resource, run_context) } let(:current_resource) { Chef::Resource::OsxProfile.new("Profile Test") } let(:all_profiles) do { "_computerlevel" => [{ "ProfileDisplayName" => "ScreenSaver Settings", "ProfileIdentifier" => "com.apple.screensaver", "ProfileInstallDate" => "2015-10-05 23:15:21 +0000", "ProfileItems" => [{ "PayloadContent" => { "PayloadContentManagedPreferences" => { "com.apple.screensaver" => { "Forced" => [{ "mcx_preference_settings" => { "idleTime" => 0 } }] } } }, "PayloadDisplayName" => "Custom: (com.apple.screensaver)", "PayloadIdentifier" => "com.apple.screensaver", "PayloadType" => "com.apple.ManagedClient.preferences", "PayloadUUID" => "73fc30e0-1e57-0131-c32d-000c2944c108", "PayloadVersion" => 1 }], "ProfileOrganization" => "Chef", "ProfileRemovalDisallowed" => "false", "ProfileType" => "Configuration", "ProfileUUID" => "1781fbec-3325-565f-9022-8aa28135c3cc", "ProfileVerificationState" => "unsigned", "ProfileVersion" => 1 }, { "ProfileDisplayName" => "ScreenSaver Settings", "ProfileIdentifier" => "com.testprofile.screensaver", "ProfileInstallDate" => "2015-10-05 23:15:21 +0000", "ProfileItems" => [{ "PayloadContent" => { "PayloadContentManagedPreferences" => { "com.apple.screensaver" => { "Forced" => [{ "mcx_preference_settings" => { "idleTime" => 0 } }] } } }, "PayloadDisplayName" => "Custom: (com.apple.screensaver)", "PayloadIdentifier" => "com.apple.screensaver", "PayloadType" => "com.apple.ManagedClient.preferences", "PayloadUUID" => "73fc30e0-1e57-0131-c32d-000c2944c110", "PayloadVersion" => 1 }], "ProfileOrganization" => "Chef", "ProfileRemovalDisallowed" => "false", "ProfileType" => "Configuration", "ProfileUUID" => "1781fbec-3325-565f-9022-8aa28135c3cc", "ProfileVerificationState" => "unsigned", "ProfileVersion" => 1 }], } end before(:each) do provider.current_resource = current_resource allow(provider).to receive(:get_installed_profiles).and_return(all_profiles) end it "should use resource name for identifier when not specified" do new_resource.profile_name "com.testprofile.screensaver" new_resource.action(:remove) provider.load_current_resource expect(provider.instance_variable_get(:@new_profile_identifier) ).to eql(new_resource.profile_name) end it "should use specified identifier" do new_resource.identifier "com.testprofile.screensaver" new_resource.action(:remove) provider.load_current_resource expect(provider.instance_variable_get(:@new_profile_identifier) ).to eql(new_resource.identifier) end it "should work with spaces in the identifier" do provider.action = :remove provider.define_resource_requirements expect { provider.process_resource_requirements }.not_to raise_error end it "should build the shellout remove command correctly" do new_resource.identifier "com.testprofile.screensaver" new_resource.action(:remove) provider.load_current_resource expect(provider).to receive(:shell_out).with("profiles -R -p '#{new_resource.identifier}'").and_return(shell_out_success) provider.action_remove() end end end chef-12.14.60/spec/unit/provider/package/000077500000000000000000000000001276456504500200265ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/package/aix_spec.rb000066400000000000000000000220311276456504500221440ustar00rootroot00000000000000# # Author:: Deepali Jagtap (deepali.jagtap@clogeny.com) # Author:: Prabhu Das (prabhu.das@clogeny.com) # Copyright:: Copyright 2013-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::Aix do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("samba.base") @new_resource.source("/tmp/samba.base") @provider = Chef::Provider::Package::Aix.new(@new_resource, @run_context) allow(::File).to receive(:exists?).and_return(true) end describe "assessing the current package status" do before do @bffinfo = "/usr/lib/objrepos:samba.base:3.3.12.0::COMMITTED:I:Samba for AIX: /etc/objrepos:samba.base:3.3.12.0::COMMITTED:I:Samba for AIX:" @empty_status = double("Status", :stdout => "", :exitstatus => 0) end it "should create a current resource with the name of new_resource" do status = double("Status", :stdout => @bffinfo, :exitstatus => 0) expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status) @provider.load_current_resource expect(@provider.current_resource.name).to eq("samba.base") end it "should set the current resource bff package name to the new resource bff package name" do status = double("Status", :stdout => @bffinfo, :exitstatus => 0) expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status) @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("samba.base") end it "should raise an exception if a source is supplied but not found" do allow(@provider).to receive(:shell_out).and_return(@empty_status) allow(::File).to receive(:exists?).and_return(false) @provider.load_current_resource @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Package) end it "should get the source package version from lslpp if provided" do status = double("Status", :stdout => @bffinfo, :exitstatus => 0) expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status) @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("samba.base") expect(@new_resource.version).to eq("3.3.12.0") end it "should warn if the package is not a fileset" do info = "samba.base:samba.base.samples:3.3.12.0::COMMITTED:I:Samba for AIX: /etc/objrepos:samba.base:3.3.12.0::COMMITTED:I:Samba for AIX:" status = double("Status", :stdout => info, :exitstatus => 0) expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status) expect(Chef::Log).to receive(:warn).once.with(%r{bff package by product name}) @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("samba.base") expect(@new_resource.version).to eq("3.3.12.0") end it "should return the current version installed if found by lslpp" do status = double("Status", :stdout => @bffinfo, :exitstatus => 0) @stdout = StringIO.new(@bffinfo) @stdin, @stderr = StringIO.new, StringIO.new expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(status) @provider.load_current_resource expect(@provider.current_resource.version).to eq("3.3.12.0") end it "should raise an exception if the source is not set but we are installing" do status = double("Status", :stdout => "", :exitstatus => 1, :format_for_exception => "") @new_resource = Chef::Resource::Package.new("samba.base") @provider = Chef::Provider::Package::Aix.new(@new_resource, @run_context) allow(@provider).to receive(:shell_out).and_return(status) expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end it "should raise an exception if installp/lslpp fails to run" do status = double(:stdout => "", :exitstatus => -1, :format_for_exception => "") allow(@provider).to receive(:shell_out).and_return(status) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package) end it "should return a current resource with a nil version if the package is not found" do status = double("Status", :stdout => @bffinfo, :exitstatus => 0) expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status) @provider.load_current_resource expect(@provider.current_resource.version).to be_nil end it "should raise an exception if the source doesn't provide the requested package" do wrongbffinfo = "/usr/lib/objrepos:openssl.base:0.9.8.2400::COMMITTED:I:Open Secure Socket Layer: /etc/objrepos:openssl.base:0.9.8.2400::COMMITTED:I:Open Secure Socket Layer:" status = double("Status", :stdout => wrongbffinfo, :exitstatus => 0) expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package) end end describe "candidate_version" do it "should return the candidate_version variable if already setup" do @provider.candidate_version = "3.3.12.0" expect(@provider).not_to receive(:shell_out ) @provider.candidate_version end it "should lookup the candidate_version if the variable is not already set" do status = double(:stdout => "", :exitstatus => 0) expect(@provider).to receive(:shell_out).and_return(status) @provider.candidate_version end it "should throw and exception if the exitstatus is not 0" do @status = double(:stdout => "", :exitstatus => 1, :format_for_exception => "") allow(@provider).to receive(:shell_out).and_return(@status) expect { @provider.candidate_version }.to raise_error(Chef::Exceptions::Package) end end describe "install and upgrade" do it "should run installp -aYF -d with the package source to install" do expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base samba.base", timeout: 900) @provider.install_package("samba.base", "3.3.12.0") end it "should run when the package is a path to install" do @new_resource = Chef::Resource::Package.new("/tmp/samba.base") @provider = Chef::Provider::Package::Aix.new(@new_resource, @run_context) expect(@new_resource.source).to eq("/tmp/samba.base") expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base /tmp/samba.base", timeout: 900) @provider.install_package("/tmp/samba.base", "3.3.12.0") end it "should run installp with -eLogfile option." do allow(@new_resource).to receive(:options).and_return("-e/tmp/installp.log") expect(@provider).to receive(:shell_out!).with("installp -aYF -e/tmp/installp.log -d /tmp/samba.base samba.base", timeout: 900) @provider.install_package("samba.base", "3.3.12.0") end end describe "remove" do it "should run installp -u samba.base to remove the package" do expect(@provider).to receive(:shell_out!).with("installp -u samba.base", timeout: 900) @provider.remove_package("samba.base", "3.3.12.0") end it "should run installp -u -e/tmp/installp.log with options -e/tmp/installp.log" do allow(@new_resource).to receive(:options).and_return("-e/tmp/installp.log") expect(@provider).to receive(:shell_out!).with("installp -u -e/tmp/installp.log samba.base", timeout: 900) @provider.remove_package("samba.base", "3.3.12.0") end end end chef-12.14.60/spec/unit/provider/package/apt_spec.rb000066400000000000000000000437301276456504500221600ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "ostruct" describe Chef::Provider::Package::Apt do # XXX: sorry this is ugly and was done quickly to get 12.0.2 out, this file needs a rewrite to use # let blocks and shared examples [ Chef::Resource::Package, Chef::Resource::AptPackage ].each do |resource_klass| describe "when the new_resource is a #{resource_klass}" do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = resource_klass.new("irssi", @run_context) @status = double("Status", :exitstatus => 0) @provider = Chef::Provider::Package::Apt.new(@new_resource, @run_context) @stdin = StringIO.new @stdout = <<-PKG_STATUS irssi: Installed: (none) Candidate: 0.8.14-1ubuntu4 Version table: 0.8.14-1ubuntu4 0 500 http://us.archive.ubuntu.com/ubuntu/ lucid/main Packages PKG_STATUS @stderr = "" @shell_out = OpenStruct.new(:stdout => @stdout, :stdin => @stdin, :stderr => @stderr, :status => @status, :exitstatus => 0) @timeout = 900 end describe "when loading current resource" do it "should create a current resource with the name of the new_resource" do expect(@provider).to receive(:shell_out!).with( "apt-cache policy #{@new_resource.package_name}", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ).and_return(@shell_out) @provider.load_current_resource current_resource = @provider.current_resource expect(current_resource).to be_a(Chef::Resource::Package) expect(current_resource.name).to eq("irssi") expect(current_resource.package_name).to eq("irssi") expect(current_resource.version).to eql([nil]) end it "should set the installed version if package has one" do @stdout.replace(<<-INSTALLED) sudo: Installed: 1.7.2p1-1ubuntu5.3 Candidate: 1.7.2p1-1ubuntu5.3 Version table: *** 1.7.2p1-1ubuntu5.3 0 500 http://us.archive.ubuntu.com/ubuntu/ lucid-updates/main Packages 500 http://security.ubuntu.com/ubuntu/ lucid-security/main Packages 100 /var/lib/dpkg/status 1.7.2p1-1ubuntu5 0 500 http://us.archive.ubuntu.com/ubuntu/ lucid/main Packages INSTALLED expect(@provider).to receive(:shell_out!).and_return(@shell_out) @provider.load_current_resource expect(@provider.current_resource.version).to eq(["1.7.2p1-1ubuntu5.3"]) expect(@provider.candidate_version).to eql(["1.7.2p1-1ubuntu5.3"]) end # it is the superclasses responsibility to throw most exceptions it "if the package does not exist in the cache sets installed + candidate version to nil" do @new_resource.package_name("conic-smarms") policy_out = <<-POLICY_STDOUT N: Unable to locate package conic-smarms POLICY_STDOUT policy = double(:stdout => policy_out, :exitstatus => 0) expect(@provider).to receive(:shell_out!).with( "apt-cache policy conic-smarms", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ).and_return(policy) showpkg_out = <<-SHOWPKG_STDOUT N: Unable to locate package conic-smarms SHOWPKG_STDOUT showpkg = double(:stdout => showpkg_out, :exitstatus => 0) expect(@provider).to receive(:shell_out!).with( "apt-cache showpkg conic-smarms", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ).and_return(showpkg) @provider.load_current_resource end # libmysqlclient-dev is a real package in newer versions of debian + ubuntu # list of virtual packages: http://www.debian.org/doc/packaging-manuals/virtual-package-names-list.txt it "should not install the virtual package there is a single provider package and it is installed" do @new_resource.package_name("libmysqlclient15-dev") virtual_package_out = <<-VPKG_STDOUT libmysqlclient15-dev: Installed: (none) Candidate: (none) Version table: VPKG_STDOUT virtual_package = double(:stdout => virtual_package_out, :exitstatus => 0) expect(@provider).to receive(:shell_out!).with( "apt-cache policy libmysqlclient15-dev", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ).and_return(virtual_package) showpkg_out = <<-SHOWPKG_STDOUT Package: libmysqlclient15-dev Versions: Reverse Depends: libmysqlclient-dev,libmysqlclient15-dev libmysqlclient-dev,libmysqlclient15-dev libmysqlclient-dev,libmysqlclient15-dev libmysqlclient-dev,libmysqlclient15-dev libmysqlclient-dev,libmysqlclient15-dev libmysqlclient-dev,libmysqlclient15-dev Dependencies: Provides: Reverse Provides: libmysqlclient-dev 5.1.41-3ubuntu12.7 libmysqlclient-dev 5.1.41-3ubuntu12.10 libmysqlclient-dev 5.1.41-3ubuntu12 SHOWPKG_STDOUT showpkg = double(:stdout => showpkg_out, :exitstatus => 0) expect(@provider).to receive(:shell_out!).with( "apt-cache showpkg libmysqlclient15-dev", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ).and_return(showpkg) real_package_out = <<-RPKG_STDOUT libmysqlclient-dev: Installed: 5.1.41-3ubuntu12.10 Candidate: 5.1.41-3ubuntu12.10 Version table: *** 5.1.41-3ubuntu12.10 0 500 http://us.archive.ubuntu.com/ubuntu/ lucid-updates/main Packages 100 /var/lib/dpkg/status 5.1.41-3ubuntu12.7 0 500 http://security.ubuntu.com/ubuntu/ lucid-security/main Packages 5.1.41-3ubuntu12 0 500 http://us.archive.ubuntu.com/ubuntu/ lucid/main Packages RPKG_STDOUT real_package = double(:stdout => real_package_out, :exitstatus => 0) expect(@provider).to receive(:shell_out!).with( "apt-cache policy libmysqlclient-dev", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ).and_return(real_package) @provider.load_current_resource end it "should raise an exception if you specify a virtual package with multiple provider packages" do @new_resource.package_name("mp3-decoder") virtual_package_out = <<-VPKG_STDOUT mp3-decoder: Installed: (none) Candidate: (none) Version table: VPKG_STDOUT virtual_package = double(:stdout => virtual_package_out, :exitstatus => 0) expect(@provider).to receive(:shell_out!).with( "apt-cache policy mp3-decoder", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ).and_return(virtual_package) showpkg_out = <<-SHOWPKG_STDOUT Package: mp3-decoder Versions: Reverse Depends: nautilus,mp3-decoder vux,mp3-decoder plait,mp3-decoder ecasound,mp3-decoder nautilus,mp3-decoder Dependencies: Provides: Reverse Provides: vlc-nox 1.0.6-1ubuntu1.8 vlc 1.0.6-1ubuntu1.8 vlc-nox 1.0.6-1ubuntu1 vlc 1.0.6-1ubuntu1 opencubicplayer 1:0.1.17-2 mpg321 0.2.10.6 mpg123 1.12.1-0ubuntu1 SHOWPKG_STDOUT showpkg = double(:stdout => showpkg_out, :exitstatus => 0) expect(@provider).to receive(:shell_out!).with( "apt-cache showpkg mp3-decoder", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ).and_return(showpkg) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package) end it "should run apt-cache policy with the default_release option, if there is one on the resource" do @new_resource = Chef::Resource::AptPackage.new("irssi", @run_context) @provider = Chef::Provider::Package::Apt.new(@new_resource, @run_context) allow(@new_resource).to receive(:default_release).and_return("lenny-backports") allow(@new_resource).to receive(:provider).and_return(nil) expect(@provider).to receive(:shell_out!).with( "apt-cache -o APT::Default-Release=lenny-backports policy irssi", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ).and_return(@shell_out) @provider.load_current_resource end it "raises an exception if a source is specified (CHEF-5113)" do @new_resource.source "pluto" expect(@provider).to receive(:shell_out!).with( "apt-cache policy #{@new_resource.package_name}", :env => { "DEBIAN_FRONTEND" => "noninteractive" } , :timeout => @timeout ).and_return(@shell_out) expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end end context "after loading the current resource" do before do @current_resource = resource_klass.new("irssi", @run_context) @provider.current_resource = @current_resource allow(@provider).to receive(:package_data).and_return({ "irssi" => { virtual: false, candidate_version: "0.8.12-7", installed_version: nil, }, "libmysqlclient15-dev" => { virtual: true, candidate_version: nil, installed_version: nil, }, }) end describe "install_package" do it "should run apt-get install with the package name and version" do expect(@provider).to receive(:shell_out!). with( "apt-get -q -y install irssi=0.8.12-7", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @provider.install_package(["irssi"], ["0.8.12-7"]) end it "should run apt-get install with the package name and version and options if specified" do expect(@provider).to receive(:shell_out!).with( "apt-get -q -y --force-yes install irssi=0.8.12-7", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @new_resource.options("--force-yes") @provider.install_package(["irssi"], ["0.8.12-7"]) end it "should run apt-get install with the package name and version and default_release if there is one and provider is explicitly defined" do @new_resource = nil @new_resource = Chef::Resource::AptPackage.new("irssi", @run_context) @new_resource.default_release("lenny-backports") @new_resource.provider = nil @provider.new_resource = @new_resource expect(@provider).to receive(:shell_out!).with( "apt-get -q -y -o APT::Default-Release=lenny-backports install irssi=0.8.12-7", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @provider.install_package(["irssi"], ["0.8.12-7"]) end end describe resource_klass, "upgrade_package" do it "should run install_package with the name and version" do expect(@provider).to receive(:install_package).with(["irssi"], ["0.8.12-7"]) @provider.upgrade_package(["irssi"], ["0.8.12-7"]) end end describe resource_klass, "remove_package" do it "should run apt-get remove with the package name" do expect(@provider).to receive(:shell_out!).with( "apt-get -q -y remove irssi", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @provider.remove_package(["irssi"], ["0.8.12-7"]) end it "should run apt-get remove with the package name and options if specified" do expect(@provider).to receive(:shell_out!).with( "apt-get -q -y --force-yes remove irssi", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @new_resource.options("--force-yes") @provider.remove_package(["irssi"], ["0.8.12-7"]) end end describe "when purging a package" do it "should run apt-get purge with the package name" do expect(@provider).to receive(:shell_out!).with( "apt-get -q -y purge irssi", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @provider.purge_package(["irssi"], ["0.8.12-7"]) end it "should run apt-get purge with the package name and options if specified" do expect(@provider).to receive(:shell_out!).with( "apt-get -q -y --force-yes purge irssi", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @new_resource.options("--force-yes") @provider.purge_package(["irssi"], ["0.8.12-7"]) end end describe "when preseeding a package" do before(:each) do allow(@provider).to receive(:get_preseed_file).and_return("/tmp/irssi-0.8.12-7.seed") end it "should get the full path to the preseed response file" do file = "/tmp/irssi-0.8.12-7.seed" expect(@provider).to receive(:shell_out!).with( "debconf-set-selections /tmp/irssi-0.8.12-7.seed", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @provider.preseed_package(file) end it "should run debconf-set-selections on the preseed file if it has changed" do expect(@provider).to receive(:shell_out!).with( "debconf-set-selections /tmp/irssi-0.8.12-7.seed", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) file = @provider.get_preseed_file("irssi", "0.8.12-7") @provider.preseed_package(file) end it "should not run debconf-set-selections if the preseed file has not changed" do allow(@provider).to receive(:check_all_packages_state) @current_resource.version "0.8.11" @new_resource.response_file "/tmp/file" allow(@provider).to receive(:get_preseed_file).and_return(false) expect(@provider).not_to receive(:shell_out!) @provider.run_action(:reconfig) end end describe "when reconfiguring a package" do it "should run dpkg-reconfigure package" do expect(@provider).to receive(:shell_out!).with( "dpkg-reconfigure irssi", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @provider.reconfig_package("irssi", "0.8.12-7") end end describe "when installing a virtual package" do it "should install the package without specifying a version" do @provider.package_data["libmysqlclient15-dev"][:virtual] = true expect(@provider).to receive(:shell_out!).with( "apt-get -q -y install libmysqlclient15-dev", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @provider.install_package(["libmysqlclient15-dev"], ["not_a_real_version"]) end end describe "when removing a virtual package" do it "should remove the resolved name instead of the virtual package name" do expect(@provider).to receive(:resolve_virtual_package_name).with("libmysqlclient15-dev").and_return("libmysqlclient-dev") expect(@provider).to receive(:shell_out!).with( "apt-get -q -y remove libmysqlclient-dev", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @provider.remove_package(["libmysqlclient15-dev"], ["not_a_real_version"]) end end describe "when purging a virtual package" do it "should purge the resolved name instead of the virtual package name" do expect(@provider).to receive(:resolve_virtual_package_name).with("libmysqlclient15-dev").and_return("libmysqlclient-dev") expect(@provider).to receive(:shell_out!).with( "apt-get -q -y purge libmysqlclient-dev", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @provider.purge_package(["libmysqlclient15-dev"], ["not_a_real_version"]) end end describe "when installing multiple packages" do it "can install a virtual package followed by a non-virtual package" do # https://github.com/chef/chef/issues/2914 expect(@provider).to receive(:shell_out!).with( "apt-get -q -y install libmysqlclient15-dev irssi=0.8.12-7", :env => { "DEBIAN_FRONTEND" => "noninteractive" }, :timeout => @timeout ) @provider.install_package(["libmysqlclient15-dev", "irssi"], ["not_a_real_version", "0.8.12-7"]) end end end end end end chef-12.14.60/spec/unit/provider/package/chocolatey_spec.rb000066400000000000000000000472021276456504500235240ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Chocolatey do let(:timeout) { 900 } let(:new_resource) { Chef::Resource::ChocolateyPackage.new("git") } let(:provider) do node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) Chef::Provider::Package::Chocolatey.new(new_resource, run_context) end let(:choco_install_path) { "C:\\ProgramData\\chocolatey" } let(:choco_exe) { "#{choco_install_path}\\bin\\choco.exe" } # installed packages (ConEmu is upgradable) let(:local_list_stdout) do <<-EOF Chocolatey v0.9.9.11 chocolatey|0.9.9.11 ConEmu|15.10.25.0 EOF end before do allow(provider).to receive(:choco_install_path).and_return(choco_install_path) allow(provider).to receive(:choco_exe).and_return(choco_exe) local_list_obj = double(:stdout => local_list_stdout) allow(provider).to receive(:shell_out!).with("#{choco_exe} list -l -r", { :timeout => timeout }).and_return(local_list_obj) end def allow_remote_list(package_names, args = nil) remote_list_stdout = <<-EOF Chocolatey v0.9.9.11 chocolatey|0.9.9.11 ConEmu|15.10.25.1 Git|2.6.1 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 -r #{package_names.join ' '}#{args}", { timeout: timeout }).and_return(remote_list_obj) end describe "#initialize" do it "should return the correct class" do expect(provider).to be_kind_of(Chef::Provider::Package::Chocolatey) end it "should support arrays" do expect(provider.use_multipackage_api?).to be true end end describe "#candidate_version" do it "should set the candidate_version to the latest version when not pinning" do allow_remote_list(["git"]) expect(provider.candidate_version).to eql(["2.6.2"]) end it "should set the candidate_version to pinned version if available" do allow_remote_list(["git"]) new_resource.version("2.6.1") expect(provider.candidate_version).to eql(["2.6.1"]) end it "should set the candidate_version to nil if there is no candidate" do allow_remote_list(["vim"]) new_resource.package_name("vim") expect(provider.candidate_version).to eql([nil]) end it "should set the candidate_version correctly when there are two packages to install" do allow_remote_list(%w{ConEmu chocolatey}) new_resource.package_name(%w{ConEmu chocolatey}) expect(provider.candidate_version).to eql(["15.10.25.1", "0.9.9.11"]) end it "should set the candidate_version correctly when only the first is installable" do allow_remote_list(%w{ConEmu vim}) new_resource.package_name(%w{ConEmu vim}) expect(provider.candidate_version).to eql(["15.10.25.1", nil]) end it "should set the candidate_version correctly when only the last is installable" do allow_remote_list(%w{vim chocolatey}) new_resource.package_name(%w{vim chocolatey}) expect(provider.candidate_version).to eql([nil, "0.9.9.11"]) end it "should set the candidate_version correctly when neither are is installable" do allow_remote_list(%w{vim ruby}) new_resource.package_name(%w{vim ruby}) expect(provider.candidate_version).to eql([nil, nil]) end end describe "#load_current_resource" do it "should return a current_resource" do expect(provider.load_current_resource).to be_kind_of(Chef::Resource::ChocolateyPackage) end it "should set the current_resource#package_name" do provider.load_current_resource expect(provider.current_resource.package_name).to eql(["git"]) end it "should load and downcase names in the installed_packages hash" do provider.load_current_resource expect(provider.send(:installed_packages)).to eql( { "chocolatey" => "0.9.9.11", "conemu" => "15.10.25.0" } ) end it "should load and downcase names in the available_packages hash" do allow_remote_list(["git"]) provider.load_current_resource expect(provider.send(:available_packages)).to eql( { "chocolatey" => "0.9.9.11", "conemu" => "15.10.25.1", "git" => "2.6.2", "munin-node" => "1.6.1.20130823" } ) end it "should set the current_resource.version to nil when the package is not installed" do provider.load_current_resource expect(provider.current_resource.version).to eql([nil]) end it "should set the current_resource.version to the installed version when the package is installed" do new_resource.package_name("ConEmu") provider.load_current_resource expect(provider.current_resource.version).to eql(["15.10.25.0"]) end it "should set the current_resource.version when there are two packages that are installed" do new_resource.package_name(%w{ConEmu chocolatey}) provider.load_current_resource expect(provider.current_resource.version).to eql(["15.10.25.0", "0.9.9.11"]) end it "should set the current_resource.version correctly when only the first is installed" do new_resource.package_name(%w{ConEmu git}) provider.load_current_resource expect(provider.current_resource.version).to eql(["15.10.25.0", nil]) end it "should set the current_resource.version correctly when only the last is installed" do new_resource.package_name(%w{git chocolatey}) provider.load_current_resource expect(provider.current_resource.version).to eql([nil, "0.9.9.11"]) end it "should set the current_resource.version correctly when none are installed" do new_resource.package_name(%w{git vim}) provider.load_current_resource expect(provider.current_resource.version).to eql([nil, nil]) end end describe "#action_install" do it "should install a single package" do allow_remote_list(["git"]) provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y git", { :timeout => timeout }).and_return(double) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end context "when changing the timeout to 3600" do let(:timeout) { 3600 } it "sets the timeout on shell_out commands" do allow_remote_list(["git"]) new_resource.timeout(timeout) provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y git", { :timeout => timeout }).and_return(double) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end it "should not install packages that are up-to-date" do allow_remote_list(["chocolatey"]) new_resource.package_name("chocolatey") provider.load_current_resource expect(provider).not_to receive(:install_package) provider.run_action(:install) expect(new_resource).not_to be_updated_by_last_action end it "should not upgrade packages" do allow_remote_list(["ConEmu"]) new_resource.package_name("ConEmu") provider.load_current_resource expect(provider).not_to receive(:install_package) provider.run_action(:install) expect(new_resource).not_to be_updated_by_last_action end it "should upgrade packages when given a version pin" do allow_remote_list(["ConEmu"]) new_resource.package_name("ConEmu") new_resource.version("15.10.25.1") provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -version 15.10.25.1 conemu", { :timeout => timeout }).and_return(double) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end it "should handle complicated cases when the name/version array is pruned" do # chocolatey will be pruned by the superclass out of the args to install_package and we # implicitly test that we correctly pick up new_resource.version[1] instead of # new_version.resource[0] allow_remote_list(%w{chocolatey ConEmu}) new_resource.package_name(%w{chocolatey ConEmu}) new_resource.version([nil, "15.10.25.1"]) provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -version 15.10.25.1 conemu", { :timeout => timeout }).and_return(double) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end it "should be case-insensitive" do allow_remote_list(["conemu"]) new_resource.package_name("conemu") new_resource.version("15.10.25.1") provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -version 15.10.25.1 conemu", { :timeout => timeout }).and_return(double) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end it "should split up commands when given two packages, one with a version pin" do allow_remote_list(%w{ConEmu git}) new_resource.package_name(%w{ConEmu git}) new_resource.version(["15.10.25.1", nil]) provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -version 15.10.25.1 conemu", { :timeout => timeout }).and_return(double) expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y git", { :timeout => timeout }).and_return(double) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end it "should do multipackage installs when given two packages without constraints" do allow_remote_list(["git", "munin-node"]) new_resource.package_name(["git", "munin-node"]) provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y git munin-node", { :timeout => timeout }).and_return(double) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end context "when passing a source argument" do it "should pass options into the install command" do allow_remote_list(["git"], " -source localpackages") new_resource.source("localpackages") provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -source localpackages git", { :timeout => timeout }).and_return(double) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end it "should pass options into the install command" do allow_remote_list(["git"]) new_resource.options("-force") provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} install -y -force git", { :timeout => timeout }).and_return(double) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end it "installing a package that does not exist throws an error" do allow_remote_list(["package-does-not-exist"]) new_resource.package_name("package-does-not-exist") 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"]) provider.load_current_resource expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end context "alternate source" do it "installing a package that does not exist throws an error" do allow_remote_list(["package-does-not-exist"], " -source alternate_source") new_resource.package_name("package-does-not-exist") new_resource.source("alternate_source") provider.load_current_resource expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end end end describe "#action_upgrade" do it "should install a package that is not installed" do allow_remote_list(["git"]) provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} upgrade -y git", { :timeout => timeout }).and_return(double) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action end it "should upgrade a package that is installed but upgradable" do allow_remote_list(["ConEmu"]) new_resource.package_name("ConEmu") provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} upgrade -y conemu", { :timeout => timeout }).and_return(double) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action end it "should be case insensitive" do allow_remote_list(["conemu"]) new_resource.package_name("conemu") provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} upgrade -y conemu", { :timeout => timeout }).and_return(double) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action end it "should not install a package that is up-to-date" do allow_remote_list(["chocolatey"]) new_resource.package_name("chocolatey") provider.load_current_resource expect(provider).not_to receive(:shell_out!).with("#{choco_exe} upgrade -y chocolatey", { :timeout => timeout }) provider.run_action(:upgrade) expect(new_resource).not_to be_updated_by_last_action end it "version pins work as well" do allow_remote_list(["git"]) new_resource.version("2.6.2") provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} upgrade -y -version 2.6.2 git", { :timeout => timeout }) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action end it "upgrading multiple packages uses a single command" do allow_remote_list(%w{conemu git}) new_resource.package_name(%w{conemu git}) expect(provider).to receive(:shell_out!).with("#{choco_exe} upgrade -y conemu git", { :timeout => timeout }).and_return(double) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action end it "upgrading a package that does not exist throws an error" do allow_remote_list(["package-does-not-exist"]) new_resource.package_name("package-does-not-exist") provider.load_current_resource expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package) end it "upgrading 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"]) provider.load_current_resource expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package) end context "alternate source" do it "installing a package that does not exist throws an error" do allow_remote_list(["package-does-not-exist"], " -source alternate_source") new_resource.package_name("package-does-not-exist") new_resource.source("alternate_source") provider.load_current_resource expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package) end end end describe "#action_remove" do it "does nothing when the package is already removed" do allow_remote_list(["git"]) provider.load_current_resource expect(provider).not_to receive(:remove_package) provider.run_action(:remove) expect(new_resource).not_to be_updated_by_last_action end it "does nothing when all the packages are already removed" do allow_remote_list(["git", "package-does-not-exist"]) new_resource.package_name(["git", "package-does-not-exist"]) provider.load_current_resource expect(provider).not_to receive(:remove_package) provider.run_action(:remove) expect(new_resource).not_to be_updated_by_last_action end it "removes a package" do allow_remote_list(["ConEmu"]) new_resource.package_name("ConEmu") provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} uninstall -y ConEmu", { :timeout => timeout }).and_return(double) provider.run_action(:remove) expect(new_resource).to be_updated_by_last_action end it "is case-insensitive" do allow_remote_list(["conemu"]) new_resource.package_name("conemu") provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} uninstall -y conemu", { :timeout => timeout }).and_return(double) provider.run_action(:remove) expect(new_resource).to be_updated_by_last_action end it "removes a single package when its the only one installed" do pending "this is a bug in the superclass" allow_remote_list(%w{git conemu}) new_resource.package_name(%w{git conemu}) provider.load_current_resource expect(provider).to receive(:shell_out!).with("#{choco_exe} uninstall -y conemu", { :timeout => timeout }).and_return(double) provider.run_action(:remove) expect(new_resource).to be_updated_by_last_action end end describe "#action_uninstall" do it "should call :remove with a deprecation warning" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect(Chef::Log).to receive(:deprecation).with(/please use :remove/) allow_remote_list(["ConEmu"]) new_resource.package_name("ConEmu") provider.load_current_resource expect(provider).to receive(:remove_package) provider.run_action(:uninstall) expect(new_resource).to be_updated_by_last_action end end end describe "behavior when Chocolatey is not installed" do let(:new_resource) { Chef::Resource::ChocolateyPackage.new("git") } let(:provider) do node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) Chef::Provider::Package::Chocolatey.new(new_resource, run_context) end before do # the shellout sometimes returns "", but test nil to be safe. allow(provider).to receive(:choco_install_path).and_return(nil) provider.instance_variable_set("@choco_install_path", nil) # we don't care what this returns, but we have to let it be called. allow(provider).to receive(:shell_out!).and_return(double(:stdout => "")) end let(:error_regex) do /Could not locate.*install.*cookbook.*PowerShell.*GetEnvironmentVariable/m end context "#choco_exe" do it "triggers a MissingLibrary exception when Chocolatey is not installed" do expect { provider.send(:choco_exe) }.to raise_error(Chef::Exceptions::MissingLibrary, error_regex) end end context "#load_current_resource" do it "triggers a MissingLibrary exception when Chocolatey is not installed" do expect { provider.load_current_resource }.to raise_error(Chef::Exceptions::MissingLibrary, error_regex) end end end chef-12.14.60/spec/unit/provider/package/dpkg_spec.rb000066400000000000000000000265141276456504500223220ustar00rootroot00000000000000# Author:: Bryan McLellan (btm@loftninjas.org) # Copyright:: Copyright 2009-2016, Bryan McLellan # 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::Dpkg do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:package) { "wget" } let(:source) { "/tmp/wget_1.11.4-1ubuntu1_amd64.deb" } let(:new_resource) do new_resource = Chef::Resource::DpkgPackage.new(package) new_resource.source source new_resource end let(:provider) { Chef::Provider::Package::Dpkg.new(new_resource, run_context) } let(:dpkg_deb_version) { "1.11.4" } let(:dpkg_deb_status) { status = double(:stdout => "#{package}\t#{dpkg_deb_version}", :exitstatus => 0) } let(:dpkg_s_version) { "1.11.4-1ubuntu1" } let(:dpkg_s_status) do stdout = <<-DPKG_S Package: #{package} Status: install ok installed Priority: important Section: web Installed-Size: 1944 Maintainer: Ubuntu Core developers Architecture: amd64 Version: #{dpkg_s_version} Config-Version: #{dpkg_s_version} Depends: libc6 (>= 2.8~20080505), libssl0.9.8 (>= 0.9.8f-5) Conflicts: wget-ssl DPKG_S status = double(:stdout => stdout, :exitstatus => 1) end before(:each) do allow(provider).to receive(:shell_out!).with("dpkg-deb -W #{source}", timeout: 900).and_return(dpkg_deb_status) allow(provider).to receive(:shell_out!).with("dpkg -s #{package}", timeout: 900, returns: [0, 1]).and_return(double(stdout: "", exitstatus: 1)) allow(::File).to receive(:exist?).with(source).and_return(true) end describe "#define_resource_requirements" do it "should raise an exception if a source is supplied but not found when :install" do allow(::File).to receive(:exist?).with(source).and_return(false) expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end it "should raise an exception if a source is supplied but not found when :upgrade" do allow(::File).to receive(:exist?).with(source).and_return(false) expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package) end # FIXME? we're saying we ignore source, but should supplying source on :remove or :purge be an actual error? it "should not raise an exception if a source is supplied but not found when :remove" do allow(::File).to receive(:exist?).with(source).and_return(false) expect(provider).to receive(:action_remove) expect { provider.run_action(:remove) }.not_to raise_error end it "should not raise an exception if a source is supplied but not found when :purge" do allow(::File).to receive(:exist?).with(source).and_return(false) expect(provider).to receive(:action_purge) expect { provider.run_action(:purge) }.not_to raise_error end context "when source is nil" do let(:source) { nil } it "should raise an exception if a source is nil when :install" do expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end it "should raise an exception if a source is nil when :upgrade" do expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package) end it "should not raise an exception if a source is nil when :remove" do expect(provider).to receive(:action_remove) expect { provider.run_action(:remove) }.not_to raise_error end it "should not raise an exception if a source is nil when :purge" do expect(provider).to receive(:action_purge) expect { provider.run_action(:purge) }.not_to raise_error end end end describe "when loading the current resource state" do it "should create a current resource with the name of the new_resource" do provider.load_current_resource expect(provider.current_resource.package_name).to eq(["wget"]) end describe "gets the source package version from dpkg-deb" do def check_version(version) status = double(:stdout => "wget\t#{version}", :exitstatus => 0) expect(provider).to receive(:shell_out!).with("dpkg-deb -W #{source}", timeout: 900).and_return(status) provider.load_current_resource expect(provider.current_resource.package_name).to eq(["wget"]) expect(provider.candidate_version).to eq([version]) end it "if short version provided" do check_version("1.11.4") end it "if extended version provided" do check_version("1.11.4-1ubuntu1") end it "if distro-specific version provided" do check_version("1.11.4-1ubuntu1~lucid") end it "returns the version if an epoch is used" do check_version("1:1.8.3-2") end end describe "when the package name has `-', `+' or `.' characters" do let(:package) { "f.o.o-pkg++2" } it "gets the source package name from dpkg-deb correctly" do provider.load_current_resource expect(provider.current_resource.package_name).to eq(["f.o.o-pkg++2"]) end end describe "when the package version has `~', `-', `+' or `.' characters" do let(:package) { "b.a.r-pkg++1" } let(:dpkg_deb_version) { "1.2.3+3141592-1ubuntu1~lucid" } let(:dpkg_s_version) { "1.2.3+3141592-1ubuntu1~lucid" } it "gets the source package version from dpkg-deb correctly when the package version has `~', `-', `+' or `.' characters" do provider.load_current_resource expect(provider.candidate_version).to eq(["1.2.3+3141592-1ubuntu1~lucid"]) end end describe "when the source is not set" do let(:source) { nil } it "should raise an exception if the source is not set but we are installing" do expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end end it "should return the current version installed if found by dpkg" do allow(provider).to receive(:shell_out!).with("dpkg -s #{package}", timeout: 900, returns: [0, 1]).and_return(dpkg_s_status) provider.load_current_resource expect(provider.current_resource.version).to eq(["1.11.4-1ubuntu1"]) end it "on new debian/ubuntu we get an exit(1) and no stdout from dpkg -s for uninstalled" do dpkg_s_status = double( exitstatus: 1, stdout: "", stderr: <<-EOF dpkg-query: package '#{package}' is not installed and no information is available Use dpkg --info (= dpkg-deb --info) to examine archive files, and dpkg --contents (= dpkg-deb --contents) to list their contents. EOF ) expect(provider).to receive(:shell_out!).with("dpkg -s #{package}", returns: [0, 1], timeout: 900).and_return(dpkg_s_status) provider.load_current_resource expect(provider.current_resource.version).to eq([nil]) end it "on old debian/ubuntu we get an exit(0) and we get info on stdout from dpkg -s for uninstalled" do dpkg_s_status = double( exitstatus: 0, stderr: "", stdout: <<-EOF Package: #{package} Status: unknown ok not-installed Priority: extra Section: ruby EOF ) expect(provider).to receive(:shell_out!).with("dpkg -s #{package}", returns: [0, 1], timeout: 900).and_return(dpkg_s_status) provider.load_current_resource expect(provider.current_resource.version).to eq([nil]) end it "and we should raise if we get any other exit codes from dpkg -s" do dpkg_s_status = double( exitstatus: 3, stderr: "i am very, very angry with you. i'm very, very cross. go to your room.", stdout: "" ) expect(provider).to receive(:shell_out!).with("dpkg -s #{package}", returns: [0, 1], timeout: 900).and_raise(Mixlib::ShellOut::ShellCommandFailed) expect { provider.load_current_resource }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end it "should raise an exception if dpkg-deb -W fails to run" do status = double(:stdout => "", :exitstatus => -1) expect(provider).to receive(:shell_out_with_timeout!).with("dpkg-deb -W /tmp/wget_1.11.4-1ubuntu1_amd64.deb").and_raise(Mixlib::ShellOut::ShellCommandFailed) expect { provider.load_current_resource }.to raise_error(Mixlib::ShellOut::ShellCommandFailed) end end describe Chef::Provider::Package::Dpkg, "install and upgrade" do it "should run dpkg -i with the package source" do expect(provider).to receive(:run_noninteractive).with( "dpkg -i", nil, "/tmp/wget_1.11.4-1ubuntu1_amd64.deb" ) provider.load_current_resource provider.run_action(:install) end it "should run dpkg -i if the package is a path and the source is nil" do new_resource.name "/tmp/wget_1.11.4-1ubuntu1_amd64.deb" expect(provider).to receive(:run_noninteractive).with( "dpkg -i", nil, "/tmp/wget_1.11.4-1ubuntu1_amd64.deb" ) provider.run_action(:install) end it "should run dpkg -i if the package is a path and the source is nil for an upgrade" do new_resource.name "/tmp/wget_1.11.4-1ubuntu1_amd64.deb" expect(provider).to receive(:run_noninteractive).with( "dpkg -i", nil, "/tmp/wget_1.11.4-1ubuntu1_amd64.deb" ) provider.run_action(:upgrade) end it "should run dpkg -i with the package source and options if specified" do new_resource.options "--force-yes" expect(provider).to receive(:run_noninteractive).with( "dpkg -i", "--force-yes", "/tmp/wget_1.11.4-1ubuntu1_amd64.deb" ) provider.run_action(:install) end it "should upgrade by running install_package" do expect(provider).to receive(:install_package).with(["wget"], ["1.11.4-1ubuntu1"]) provider.upgrade_package(["wget"], ["1.11.4-1ubuntu1"]) end end describe Chef::Provider::Package::Dpkg, "remove and purge" do it "should run dpkg -r to remove the package" do expect(provider).to receive(:run_noninteractive).with( "dpkg -r", nil, "wget" ) provider.remove_package(["wget"], ["1.11.4-1ubuntu1"]) end it "should run dpkg -r to remove the package with options if specified" do expect(provider).to receive(:run_noninteractive).with( "dpkg -r", "--force-yes", "wget" ) allow(new_resource).to receive(:options).and_return("--force-yes") provider.remove_package(["wget"], ["1.11.4-1ubuntu1"]) end it "should run dpkg -P to purge the package" do expect(provider).to receive(:run_noninteractive).with( "dpkg -P", nil, "wget" ) provider.purge_package(["wget"], ["1.11.4-1ubuntu1"]) end it "should run dpkg -P to purge the package with options if specified" do expect(provider).to receive(:run_noninteractive).with( "dpkg -P", "--force-yes", "wget" ) allow(new_resource).to receive(:options).and_return("--force-yes") provider.purge_package(["wget"], ["1.11.4-1ubuntu1"]) end end end chef-12.14.60/spec/unit/provider/package/easy_install_spec.rb000066400000000000000000000110231276456504500240510ustar00rootroot00000000000000# # Author:: Joe Williams () # Copyright:: Copyright 2009-2016, Joe Williams # 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::EasyInstall do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::EasyInstallPackage.new("boto") @new_resource.version("1.8d") @provider = Chef::Provider::Package::EasyInstall.new(@new_resource, @run_context) @stdin = StringIO.new @stdout = StringIO.new @status = double("Status", :exitstatus => 0) @stderr = StringIO.new @pid = 2342 allow(@provider).to receive(:popen4).and_return(@status) end describe "easy_install_binary_path" do it "should return a Chef::Provider::EasyInstall object" do provider = Chef::Provider::Package::EasyInstall.new(@node, @new_resource) expect(provider).to be_a_kind_of(Chef::Provider::Package::EasyInstall) end it "should set the current resources package name to the new resources package name" do allow($stdout).to receive(:write) @provider.load_current_resource expect(@provider.current_resource.package_name).to eq(@new_resource.package_name) end it "should return a relative path to easy_install if no easy_install_binary is given" do expect(@provider.easy_install_binary_path).to eql("easy_install") end it "should return a specific path to easy_install if a easy_install_binary is given" do expect(@new_resource).to receive(:easy_install_binary).and_return("/opt/local/bin/custom/easy_install") expect(@provider.easy_install_binary_path).to eql("/opt/local/bin/custom/easy_install") end end describe "actions_on_package" do it "should run easy_install with the package name and version" do expect(Chef).to receive(:log_deprecation).with(/easy_install package provider is deprecated/) expect(@provider).to receive(:run_command).with({ :command => "easy_install \"boto==1.8d\"", }) @provider.install_package("boto", "1.8d") end it "should run easy_install with the package name and version and specified options" do expect(Chef).to receive(:log_deprecation).with(/easy_install package provider is deprecated/) expect(@provider).to receive(:run_command).with({ :command => "easy_install --always-unzip \"boto==1.8d\"", }) allow(@new_resource).to receive(:options).and_return("--always-unzip") @provider.install_package("boto", "1.8d") end it "should run easy_install with the package name and version" do expect(Chef).to receive(:log_deprecation).with(/easy_install package provider is deprecated/) expect(@provider).to receive(:run_command).with({ :command => "easy_install \"boto==1.8d\"", }) @provider.upgrade_package("boto", "1.8d") end it "should run easy_install -m with the package name and version" do expect(Chef).to receive(:log_deprecation).with(/easy_install package provider is deprecated/) expect(@provider).to receive(:run_command).with({ :command => "easy_install -m boto", }) @provider.remove_package("boto", "1.8d") end it "should run easy_install -m with the package name and version and specified options" do expect(Chef).to receive(:log_deprecation).with(/easy_install package provider is deprecated/) expect(@provider).to receive(:run_command).with({ :command => "easy_install -x -m boto", }) allow(@new_resource).to receive(:options).and_return("-x") @provider.remove_package("boto", "1.8d") end it "should run easy_install -m with the package name and version" do expect(Chef).to receive(:log_deprecation).with(/easy_install package provider is deprecated/) expect(@provider).to receive(:run_command).with({ :command => "easy_install -m boto", }) @provider.purge_package("boto", "1.8d") end end end chef-12.14.60/spec/unit/provider/package/freebsd/000077500000000000000000000000001276456504500214405ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/package/freebsd/pkg_spec.rb000066400000000000000000000333261276456504500235670ustar00rootroot00000000000000# # Authors:: Bryan McLellan (btm@loftninjas.org) # Matthew Landauer (matthew@openaustralia.org) # Copyright:: Copyright 2009-2016, Bryan McLellan, Matthew Landauer # 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 "ostruct" describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("zsh") @current_resource = Chef::Resource::Package.new("zsh") @provider = Chef::Provider::Package::Freebsd::Pkg.new(@new_resource, @run_context) @provider.current_resource = @current_resource allow(::File).to receive(:exist?).with("/usr/ports/Makefile").and_return(false) end describe "when determining the current package state" do before do allow(@provider).to receive(:ports_candidate_version).and_return("4.3.6") end it "should create a current resource with the name of the new_resource" do current_resource = Chef::Provider::Package::Freebsd::Pkg.new(@new_resource, @run_context).current_resource expect(current_resource.name).to eq("zsh") end it "should return a version if the package is installed" do expect(@provider).to receive(:current_installed_version).and_return("4.3.6_7") @provider.load_current_resource expect(@current_resource.version).to eq("4.3.6_7") end it "should return nil if the package is not installed" do expect(@provider).to receive(:current_installed_version).and_return(nil) @provider.load_current_resource expect(@current_resource.version).to be_nil end it "should return a candidate version if it exists" do expect(@provider).to receive(:current_installed_version).and_return(nil) @provider.load_current_resource expect(@provider.candidate_version).to eql("4.3.6") end end describe "when querying for package state and attributes" do before do #@new_resource = Chef::Resource::Package.new("zsh") #@provider = Chef::Provider::Package::Freebsd::Pkg.new(@node, @new_resource) #@status = double("Status", :exitstatus => 0) #@stdin = double("STDIN", :null_object => true) #@stdout = double("STDOUT", :null_object => true) #@stderr = double("STDERR", :null_object => true) #@pid = double("PID", :null_object => true) end it "should return the version number when it is installed" do pkg_info = OpenStruct.new(:stdout => "zsh-4.3.6_7") expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0, 1], timeout: 900).and_return(pkg_info) #@provider.should_receive(:popen4).with('pkg_info -E "zsh*"').and_yield(@pid, @stdin, ["zsh-4.3.6_7"], @stderr).and_return(@status) allow(@provider).to receive(:package_name).and_return("zsh") expect(@provider.current_installed_version).to eq("4.3.6_7") end it "does not set the current version number when the package is not installed" do pkg_info = OpenStruct.new(:stdout => "") expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0, 1], timeout: 900).and_return(pkg_info) allow(@provider).to receive(:package_name).and_return("zsh") expect(@provider.current_installed_version).to be_nil end it "should return the port path for a valid port name" do whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh") expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis) #@provider.should_receive(:popen4).with("whereis -s zsh").and_yield(@pid, @stdin, ["zsh: /usr/ports/shells/zsh"], @stderr).and_return(@status) allow(@provider).to receive(:port_name).and_return("zsh") expect(@provider.port_path).to eq("/usr/ports/shells/zsh") end # Not happy with the form of these tests as they are far too closely tied to the implementation and so very fragile. it "should return the ports candidate version when given a valid port path" do allow(@provider).to receive(:port_path).and_return("/usr/ports/shells/zsh") make_v = OpenStruct.new(:stdout => "4.3.6\n", :exitstatus => 0) expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", { cwd: "/usr/ports/shells/zsh", returns: [0, 1], env: nil, timeout: 900 }).and_return(make_v) expect(@provider.ports_candidate_version).to eq("4.3.6") end it "should figure out the package name when we have ports" do allow(::File).to receive(:exist?).with("/usr/ports/Makefile").and_return(true) allow(@provider).to receive(:port_path).and_return("/usr/ports/shells/zsh") make_v = OpenStruct.new(:stdout => "zsh-4.3.6_7\n", :exitstatus => 0) expect(@provider).to receive(:shell_out!).with("make -V PKGNAME", { cwd: "/usr/ports/shells/zsh", env: nil, returns: [0, 1], timeout: 900 }).and_return(make_v) #@provider.should_receive(:ports_makefile_variable_value).with("PKGNAME").and_return("zsh-4.3.6_7") expect(@provider.package_name).to eq("zsh") end end describe Chef::Provider::Package::Freebsd::Pkg, "install_package" do before(:each) do @cmd_result = OpenStruct.new(:status => true) @provider.current_resource = @current_resource allow(@provider).to receive(:package_name).and_return("zsh") allow(@provider).to receive(:latest_link_name).and_return("zsh") allow(@provider).to receive(:port_path).and_return("/usr/ports/shells/zsh") end it "should run pkg_add -r with the package name" do expect(@provider).to receive(:shell_out!).with("pkg_add -r zsh", env: nil, timeout: 900).and_return(@cmd_result) @provider.install_package("zsh", "4.3.6_7") end end describe Chef::Provider::Package::Freebsd::Pkg, "port path" do before do #@node = Chef::Node.new @new_resource = Chef::Resource::Package.new("zsh") @new_resource.cookbook_name = "adventureclub" @provider = Chef::Provider::Package::Freebsd::Pkg.new(@new_resource, @run_context) end it "should figure out the port path from the package_name using whereis" do whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh") expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis) expect(@provider.port_path).to eq("/usr/ports/shells/zsh") end it "should use the package_name as the port path when it starts with /" do new_resource = Chef::Resource::Package.new("/usr/ports/www/wordpress") provider = Chef::Provider::Package::Freebsd::Pkg.new(new_resource, @run_context) expect(provider).not_to receive(:popen4) expect(provider.port_path).to eq("/usr/ports/www/wordpress") end it "should use the package_name as a relative path from /usr/ports when it contains / but doesn't start with it" do # @new_resource = double( "Chef::Resource::Package", # :package_name => "www/wordpress", # :cookbook_name => "xenoparadox") new_resource = Chef::Resource::Package.new("www/wordpress") provider = Chef::Provider::Package::Freebsd::Pkg.new(new_resource, @run_context) expect(provider).not_to receive(:popen4) expect(provider.port_path).to eq("/usr/ports/www/wordpress") end end describe Chef::Provider::Package::Freebsd::Pkg, "ruby-iconv (package with a dash in the name)" do before(:each) do @new_resource = Chef::Resource::Package.new("ruby-iconv") @current_resource = Chef::Resource::Package.new("ruby-iconv") @provider = Chef::Provider::Package::Freebsd::Pkg.new(@new_resource, @run_context) @provider.current_resource = @current_resource allow(@provider).to receive(:port_path).and_return("/usr/ports/converters/ruby-iconv") allow(@provider).to receive(:package_name).and_return("ruby18-iconv") allow(@provider).to receive(:latest_link_name).and_return("ruby18-iconv") @install_result = OpenStruct.new(:status => true) end it "should run pkg_add -r with the package name" do expect(@provider).to receive(:shell_out!).with("pkg_add -r ruby18-iconv", env: nil, timeout: 900).and_return(@install_result) @provider.install_package("ruby-iconv", "1.0") end end describe Chef::Provider::Package::Freebsd::Pkg, "remove_package" do before(:each) do @pkg_delete = OpenStruct.new(:status => true) @new_resource.version "4.3.6_7" @current_resource.version "4.3.6_7" @provider.current_resource = @current_resource allow(@provider).to receive(:package_name).and_return("zsh") end it "should run pkg_delete with the package name and version" do expect(@provider).to receive(:shell_out!).with("pkg_delete zsh-4.3.6_7", env: nil, timeout: 900).and_return(@pkg_delete) @provider.remove_package("zsh", "4.3.6_7") end end # CHEF-4371 # There are some port names that contain special characters such as +'s. This breaks the regular expression used to determine what # version of a package is currently installed and to get the port_path. # Example package name: bonnie++ describe Chef::Provider::Package::Freebsd::Pkg, "bonnie++ (package with a plus in the name :: CHEF-4371)" do before(:each) do @new_resource = Chef::Resource::Package.new("bonnie++") @current_resource = Chef::Resource::Package.new("bonnie++") @provider = Chef::Provider::Package::Freebsd::Pkg.new(@new_resource, @run_context) @provider.current_resource = @current_resource end it "should return the port path for a valid port name" do whereis = OpenStruct.new(:stdout => "bonnie++: /usr/ports/benchmarks/bonnie++") expect(@provider).to receive(:shell_out!).with("whereis -s bonnie++", env: nil, timeout: 900).and_return(whereis) allow(@provider).to receive(:port_name).and_return("bonnie++") expect(@provider.port_path).to eq("/usr/ports/benchmarks/bonnie++") end it "should return the version number when it is installed" do pkg_info = OpenStruct.new(:stdout => "bonnie++-1.96") expect(@provider).to receive(:shell_out!).with('pkg_info -E "bonnie++*"', env: nil, returns: [0, 1], timeout: 900).and_return(pkg_info) allow(@provider).to receive(:package_name).and_return("bonnie++") expect(@provider.current_installed_version).to eq("1.96") end end # A couple of examples to show up the difficulty of determining the command to install the binary package given the port: # PORT DIRECTORY INSTALLED PACKAGE NAME COMMAND TO INSTALL PACKAGE # /usr/ports/lang/perl5.8 perl-5.8.8_1 pkg_add -r perl # /usr/ports/databases/mysql50-server mysql-server-5.0.45_1 pkg_add -r mysql50-server # # So, in one case it appears the command to install the package can be derived from the name of the port directory and in the # other case it appears the command can be derived from the package name. Very confusing! # Well, luckily, after much poking around, I discovered that the two can be disambiguated through the use of the LATEST_LINK # variable which is set by the ports Makefile # # PORT DIRECTORY LATEST_LINK INSTALLED PACKAGE NAME COMMAND TO INSTALL PACKAGE # /usr/ports/lang/perl5.8 perl perl-5.8.8_1 pkg_add -r perl # /usr/ports/databases/mysql50-server mysql50-server mysql-server-5.0.45_1 pkg_add -r mysql50-server # # The variable LATEST_LINK is named that way because the directory that "pkg_add -r" downloads from is called "Latest" and # contains the "latest" versions of package as symbolic links to the files in the "All" directory. describe Chef::Provider::Package::Freebsd::Pkg, "install_package latest link fixes" do it "should install the perl binary package with the correct name" do @new_resource = Chef::Resource::Package.new("perl5.8") @current_resource = Chef::Resource::Package.new("perl5.8") @provider = Chef::Provider::Package::Freebsd::Pkg.new(@new_resource, @run_context) @provider.current_resource = @current_resource allow(@provider).to receive(:package_name).and_return("perl") allow(@provider).to receive(:latest_link_name).and_return("perl") cmd = OpenStruct.new(:status => true) expect(@provider).to receive(:shell_out!).with("pkg_add -r perl", env: nil, timeout: 900).and_return(cmd) @provider.install_package("perl5.8", "5.8.8_1") end it "should install the mysql50-server binary package with the correct name" do @new_resource = Chef::Resource::Package.new("mysql50-server") @current_resource = Chef::Resource::Package.new("mysql50-server") @provider = Chef::Provider::Package::Freebsd::Pkg.new(@new_resource, @run_context) @provider.current_resource = @current_resource allow(@provider).to receive(:package_name).and_return("mysql-server") allow(@provider).to receive(:latest_link_name).and_return("mysql50-server") cmd = OpenStruct.new(:status => true) expect(@provider).to receive(:shell_out!).with("pkg_add -r mysql50-server", env: nil, timeout: 900).and_return(cmd) @provider.install_package("mysql50-server", "5.0.45_1") end end end chef-12.14.60/spec/unit/provider/package/freebsd/pkgng_spec.rb000066400000000000000000000134311276456504500241070ustar00rootroot00000000000000# # Authors:: Richard Manyanza (liseki@nyikacraftsmen.com) # Copyright:: Copyright 2014-2016, Richard Manyanza # 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 "ostruct" describe Chef::Provider::Package::Freebsd::Port do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("zsh") @provider = Chef::Provider::Package::Freebsd::Pkgng.new(@new_resource, @run_context) end describe "initialization" do it "should create a current resource with the name of the new resource" do expect(@provider.current_resource.is_a?(Chef::Resource::Package)).to be_truthy expect(@provider.current_resource.name).to eq("zsh") end end describe "loading current resource" do before(:each) do allow(@provider).to receive(:current_installed_version) allow(@provider).to receive(:candidate_version) end it "should set the package name" do @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("zsh") end it "should set the current version" do expect(@provider).to receive(:current_installed_version).and_return("5.0.2") @provider.load_current_resource expect(@provider.current_resource.version).to eq("5.0.2") end it "should set the candidate version" do expect(@provider).to receive(:candidate_version).and_return("5.0.5") @provider.load_current_resource expect(@provider.instance_variable_get(:"@candidate_version")).to eq("5.0.5") end end describe "determining current installed version" do before(:each) do allow(@provider).to receive(:supports_pkgng?) @pkg_info = OpenStruct.new(:stdout => "zsh-3.1.7\nVersion : 3.1.7\n") end it "should query pkg database" do expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0, 70], timeout: 900).and_return(@pkg_info) expect(@provider.current_installed_version).to eq("3.1.7") end end describe "determining candidate version" do it "should query repository" do pkg_query = OpenStruct.new(:stdout => "5.0.5\n", :exitstatus => 0) expect(@provider).to receive(:shell_out!).with("pkg rquery '%v' zsh", env: nil, timeout: 900).and_return(pkg_query) expect(@provider.candidate_version).to eq("5.0.5") end it "should query specified repository when given option" do @provider.new_resource.options("-r LocalMirror") # This requires LocalMirror repo configuration. pkg_query = OpenStruct.new(:stdout => "5.0.3\n", :exitstatus => 0) expect(@provider).to receive(:shell_out!).with("pkg rquery -r LocalMirror '%v' zsh", env: nil, timeout: 900).and_return(pkg_query) expect(@provider.candidate_version).to eq("5.0.3") end it "should return candidate version from file when given a file" do @provider.new_resource.source("/nas/pkg/repo/zsh-5.0.1.txz") expect(@provider.candidate_version).to eq("5.0.1") end end describe "installing a binary package" do before(:each) do @install_result = OpenStruct.new(:status => true) end it "should handle package source from file" do @provider.new_resource.source("/nas/pkg/repo/zsh-5.0.1.txz") expect(@provider).to receive(:shell_out!). with("pkg add /nas/pkg/repo/zsh-5.0.1.txz", env: { "LC_ALL" => nil }, timeout: 900). and_return(@install_result) @provider.install_package("zsh", "5.0.1") end it "should handle package source over ftp or http" do @provider.new_resource.source("http://repo.example.com/zsh-5.0.1.txz") expect(@provider).to receive(:shell_out!). with("pkg add http://repo.example.com/zsh-5.0.1.txz", env: { "LC_ALL" => nil }, timeout: 900). and_return(@install_result) @provider.install_package("zsh", "5.0.1") end it "should handle a package name" do expect(@provider).to receive(:shell_out!). with("pkg install -y zsh", env: { "LC_ALL" => nil }, timeout: 900).and_return(@install_result) @provider.install_package("zsh", "5.0.1") end it "should handle a package name with a specified repo" do @provider.new_resource.options("-r LocalMirror") # This requires LocalMirror repo configuration. expect(@provider).to receive(:shell_out!). with("pkg install -y -r LocalMirror zsh", env: { "LC_ALL" => nil }, timeout: 900).and_return(@install_result) @provider.install_package("zsh", "5.0.1") end end describe "removing a binary package" do before(:each) do @install_result = OpenStruct.new(:status => true) end it "should call pkg delete" do expect(@provider).to receive(:shell_out!). with("pkg delete -y zsh-5.0.1", env: nil, timeout: 900).and_return(@install_result) @provider.remove_package("zsh", "5.0.1") end it "should not include repo option in pkg delete" do @provider.new_resource.options("-r LocalMirror") # This requires LocalMirror repo configuration. expect(@provider).to receive(:shell_out!). with("pkg delete -y zsh-5.0.1", env: nil, timeout: 900).and_return(@install_result) @provider.remove_package("zsh", "5.0.1") end end end chef-12.14.60/spec/unit/provider/package/freebsd/port_spec.rb000066400000000000000000000154121276456504500237660ustar00rootroot00000000000000# # Authors:: Richard Manyanza (liseki@nyikacraftsmen.com) # Copyright:: Copyright 2014-2016, Richard Manyanza # 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 "ostruct" describe Chef::Provider::Package::Freebsd::Port do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::FreebsdPackage.new("zsh", @run_context) @provider = Chef::Provider::Package::Freebsd::Port.new(@new_resource, @run_context) end describe "initialization" do it "should create a current resource with the name of the new resource" do expect(@provider.current_resource.is_a?(Chef::Resource::Package)).to be_truthy expect(@provider.current_resource.name).to eq("zsh") end end describe "loading current resource" do before(:each) do allow(@provider).to receive(:current_installed_version) allow(@provider).to receive(:candidate_version) end it "should set the package name" do @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("zsh") end it "should set the current version" do expect(@provider).to receive(:current_installed_version).and_return("5.0.2") @provider.load_current_resource expect(@provider.current_resource.version).to eq("5.0.2") end it "should set the candidate version" do expect(@provider).to receive(:candidate_version).and_return("5.0.5") @provider.load_current_resource expect(@provider.instance_variable_get(:"@candidate_version")).to eq("5.0.5") end end describe "determining current installed version" do before(:each) do @pkg_info = OpenStruct.new(:stdout => "zsh-3.1.7\n") end it "should check 'pkg_info' if system uses pkg_* tools" do allow(@new_resource).to receive(:supports_pkgng?) expect(@new_resource).to receive(:supports_pkgng?).and_return(false) expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0, 1], timeout: 900).and_return(@pkg_info) expect(@provider.current_installed_version).to eq("3.1.7") end it "should check 'pkg info' if make supports WITH_PKGNG if freebsd version is < 1000017" do pkg_enabled = OpenStruct.new(:stdout => "yes\n") [1000016, 1000000, 901503, 902506, 802511].each do |freebsd_version| @node.automatic_attrs[:os_version] = freebsd_version expect(@new_resource).to receive(:shell_out!).with("make -V WITH_PKGNG", env: nil).and_return(pkg_enabled) expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0, 70], timeout: 900).and_return(@pkg_info) expect(@provider.current_installed_version).to eq("3.1.7") end end it "should check 'pkg info' if the freebsd version is greater than or equal to 1000017" do freebsd_version = 1000017 @node.automatic_attrs[:os_version] = freebsd_version expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0, 70], timeout: 900).and_return(@pkg_info) expect(@provider.current_installed_version).to eq("3.1.7") end end describe "determining candidate version" do before(:each) do @port_version = OpenStruct.new(:stdout => "5.0.5\n", :exitstatus => 0) end it "should return candidate version if port exists" do allow(::File).to receive(:exist?).with("/usr/ports/Makefile").and_return(true) allow(@provider).to receive(:port_dir).and_return("/usr/ports/shells/zsh") expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", cwd: "/usr/ports/shells/zsh", env: nil, returns: [0, 1], timeout: 900). and_return(@port_version) expect(@provider.candidate_version).to eq("5.0.5") end it "should raise exception if ports tree not found" do allow(::File).to receive(:exist?).with("/usr/ports/Makefile").and_return(false) expect { @provider.candidate_version }.to raise_error(Chef::Exceptions::Package, "Ports collection could not be found") end end describe "determining port directory" do it "should return name if package name is absolute path" do allow(@provider.new_resource).to receive(:package_name).and_return("/var/ports/shells/zsh") expect(@provider.port_dir).to eq("/var/ports/shells/zsh") end it "should return full ports path given package name and category" do allow(@provider.new_resource).to receive(:package_name).and_return("shells/zsh") expect(@provider.port_dir).to eq("/usr/ports/shells/zsh") end it "should query system for path given just a name" do whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh\n") expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis) expect(@provider.port_dir).to eq("/usr/ports/shells/zsh") end it "should raise exception if not found" do whereis = OpenStruct.new(:stdout => "zsh:\n") expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis) expect { @provider.port_dir }.to raise_error(Chef::Exceptions::Package, "Could not find port with the name zsh") end end describe "building a binary package" do before(:each) do @install_result = OpenStruct.new(:status => true) end it "should run make install in port directory" do allow(@provider).to receive(:port_dir).and_return("/usr/ports/shells/zsh") expect(@provider).to receive(:shell_out!). with("make -DBATCH install clean", :timeout => 1800, :cwd => "/usr/ports/shells/zsh", :env => nil). and_return(@install_result) @provider.install_package("zsh", "5.0.5") end end describe "removing a binary package" do before(:each) do @install_result = OpenStruct.new(:status => true) end it "should run make deinstall in port directory" do allow(@provider).to receive(:port_dir).and_return("/usr/ports/shells/zsh") expect(@provider).to receive(:shell_out!). with("make deinstall", :timeout => 300, :cwd => "/usr/ports/shells/zsh", :env => nil). and_return(@install_result) @provider.remove_package("zsh", "5.0.5") end end end chef-12.14.60/spec/unit/provider/package/homebrew_spec.rb000066400000000000000000000250021276456504500231740ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Copyright 2014-2016, Chef Software, Inc. # # 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::Homebrew do let(:node) { Chef::Node.new } let(:events) { double("Chef::Events").as_null_object } let(:run_context) { double("Chef::RunContext", node: node, events: events) } let(:new_resource) { Chef::Resource::HomebrewPackage.new("emacs") } let(:current_resource) { Chef::Resource::HomebrewPackage.new("emacs") } let(:provider) do Chef::Provider::Package::Homebrew.new(new_resource, run_context) end let(:homebrew_uid) { 1001 } let(:uninstalled_brew_info) do { "name" => "emacs", "homepage" => "http://www.gnu.org/software/emacs", "versions" => { "stable" => "24.3", "bottle" => false, "devel" => nil, "head" => nil, }, "revision" => 0, "installed" => [], "linked_keg" => nil, "keg_only" => nil, "dependencies" => [], "conflicts_with" => [], "caveats" => nil, "options" => [], } end let(:installed_brew_info) do { "name" => "emacs", "homepage" => "http://www.gnu.org/software/emacs/", "versions" => { "stable" => "24.3", "bottle" => false, "devel" => nil, "head" => "HEAD", }, "revision" => 0, "installed" => [{ "version" => "24.3" }], "linked_keg" => "24.3", "keg_only" => nil, "dependencies" => [], "conflicts_with" => [], "caveats" => "", "options" => [], } end let(:keg_only_brew_info) do { "name" => "emacs-kegger", "homepage" => "http://www.gnu.org/software/emacs/", "versions" => { "stable" => "24.3-keggy", "bottle" => false, "devel" => nil, "head" => "HEAD", }, "revision" => 0, "installed" => [{ "version" => "24.3-keggy" }], "linked_keg" => nil, "keg_only" => true, "dependencies" => [], "conflicts_with" => [], "caveats" => "", "options" => [], } end let(:keg_only_uninstalled_brew_info) do { "name" => "emacs-kegger", "homepage" => "http://www.gnu.org/software/emacs/", "versions" => { "stable" => "24.3-keggy", "bottle" => false, "devel" => nil, "head" => "HEAD", }, "revision" => 0, "installed" => [], "linked_keg" => nil, "keg_only" => true, "dependencies" => [], "conflicts_with" => [], "caveats" => "", "options" => [], } end before(:each) do end describe "load_current_resource" do before(:each) do allow(provider).to receive(:current_installed_version).and_return(nil) allow(provider).to receive(:candidate_version).and_return("24.3") end it "creates a current resource with the name of the new resource" do provider.load_current_resource expect(provider.current_resource).to be_a(Chef::Resource::Package) expect(provider.current_resource.name).to eql("emacs") end it "creates a current resource with the version if the package is installed" do expect(provider).to receive(:current_installed_version).and_return("24.3") provider.load_current_resource expect(provider.current_resource.version).to eql("24.3") end it "creates a current resource with a nil version if the package is not installed" do provider.load_current_resource expect(provider.current_resource.version).to be_nil end it "sets a candidate version if one exists" do provider.load_current_resource expect(provider.candidate_version).to eql("24.3") end end describe "current_installed_version" do it "returns the latest version from brew info if the package is keg only" do allow(provider).to receive(:brew_info).and_return(keg_only_brew_info) expect(provider.current_installed_version).to eql("24.3-keggy") end it "returns the linked keg version if the package is not keg only" do allow(provider).to receive(:brew_info).and_return(installed_brew_info) expect(provider.current_installed_version).to eql("24.3") end it "returns nil if the package is not installed" do allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info) expect(provider.current_installed_version).to be_nil end it "returns nil if the package is keg only and not installed" do allow(provider).to receive(:brew_info).and_return(keg_only_uninstalled_brew_info) expect(provider.current_installed_version).to be_nil end end describe "brew" do before do expect(provider).to receive(:find_homebrew_uid).and_return(homebrew_uid) expect(Etc).to receive(:getpwuid).with(homebrew_uid).and_return(OpenStruct.new(:name => "name", :dir => "/")) end it "passes a single to the brew command and return stdout" do allow(provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => "zombo")) expect(provider.brew).to eql("zombo") end it "takes multiple arguments as an array" do allow(provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => "homestarrunner")) expect(provider.brew("info", "opts", "bananas")).to eql("homestarrunner") end context "when new_resource is Package" do let(:new_resource) { Chef::Resource::Package.new("emacs") } it "does not try to read homebrew_user from Package, which does not have it" do allow(provider).to receive(:shell_out!).and_return(OpenStruct.new(:stdout => "zombo")) expect(provider.brew).to eql("zombo") end end end context "when testing actions" do before(:each) do provider.current_resource = current_resource end describe "install_package" do before(:each) do allow(provider).to receive(:candidate_version).and_return("24.3") end it "installs the named package with brew install" do allow(provider.new_resource).to receive(:version).and_return("24.3") allow(provider.current_resource).to receive(:version).and_return(nil) allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info) expect(provider).to receive(:get_response_from_command).with("brew install emacs") provider.install_package("emacs", "24.3") end it "does not do anything if the package is installed" do allow(provider.current_resource).to receive(:version).and_return("24.3") allow(provider).to receive(:brew_info).and_return(installed_brew_info) expect(provider).not_to receive(:get_response_from_command) provider.install_package("emacs", "24.3") end it "uses options to the brew command if specified" do allow(provider.new_resource).to receive(:options).and_return("--cocoa") allow(provider.current_resource).to receive(:version).and_return("24.3") allow(provider).to receive(:get_response_from_command).with("brew install --cocoa emacs") provider.install_package("emacs", "24.3") end end describe "upgrade_package" do it "uses brew upgrade to upgrade the package if it is installed" do allow(provider.current_resource).to receive(:version).and_return("24") allow(provider).to receive(:brew_info).and_return(installed_brew_info) expect(provider).to receive(:get_response_from_command).with("brew upgrade emacs") provider.upgrade_package("emacs", "24.3") end it "does not do anything if the package version is already installed" do allow(provider.current_resource).to receive(:version).and_return("24.3") allow(provider).to receive(:brew_info).and_return(installed_brew_info) expect(provider).not_to receive(:get_response_from_command) provider.install_package("emacs", "24.3") end it "uses brew install to install the package if it is not installed" do allow(provider.current_resource).to receive(:version).and_return(nil) allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info) expect(provider).to receive(:get_response_from_command).with("brew install emacs") provider.upgrade_package("emacs", "24.3") end it "uses options to the brew command if specified" do allow(provider.current_resource).to receive(:version).and_return("24") allow(provider).to receive(:brew_info).and_return(installed_brew_info) allow(provider.new_resource).to receive(:options).and_return("--cocoa") expect(provider).to receive(:get_response_from_command).with("brew upgrade --cocoa emacs") provider.upgrade_package("emacs", "24.3") end end describe "remove_package" do it "uninstalls the package with brew uninstall" do allow(provider.current_resource).to receive(:version).and_return("24.3") allow(provider).to receive(:brew_info).and_return(installed_brew_info) expect(provider).to receive(:get_response_from_command).with("brew uninstall emacs") provider.remove_package("emacs", "24.3") end it "does not do anything if the package is not installed" do allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info) expect(provider).not_to receive(:get_response_from_command) provider.remove_package("emacs", "24.3") end end describe "purge_package" do it "uninstalls the package with brew uninstall --force" do allow(provider.current_resource).to receive(:version).and_return("24.3") allow(provider).to receive(:brew_info).and_return(installed_brew_info) expect(provider).to receive(:get_response_from_command).with("brew uninstall --force emacs") provider.purge_package("emacs", "24.3") end it "does not do anything if the package is not installed" do allow(provider).to receive(:brew_info).and_return(uninstalled_brew_info) expect(provider).not_to receive(:get_response_from_command) provider.purge_package("emacs", "24.3") end end end end chef-12.14.60/spec/unit/provider/package/ips_spec.rb000066400000000000000000000235021276456504500221620ustar00rootroot00000000000000# # Author:: Bryan McLellan # 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 "ostruct" # based on the apt specs describe Chef::Provider::Package::Ips do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("crypto/gnupg", @run_context) @current_resource = Chef::Resource::Package.new("crypto/gnupg", @run_context) allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource) @provider = Chef::Provider::Package::Ips.new(@new_resource, @run_context) end def local_output stdin = StringIO.new stdout = "" stderr = <<-PKG_STATUS pkg: info: no packages matching the following patterns you specified are installed on the system. Try specifying -r to query remotely: crypto/gnupg PKG_STATUS return OpenStruct.new(:stdout => stdout, :stdin => stdin, :stderr => stderr, :status => @status, :exitstatus => 1) end def remote_output stdout = <<-PKG_STATUS Name: security/sudo Summary: sudo - authority delegation tool State: Not Installed Publisher: omnios Version: 1.8.4.1 (1.8.4p1) Build Release: 5.11 Branch: 0.151002 Packaging Date: April 1, 2012 05:55:52 PM Size: 2.57 MB FMRI: pkg://omnios/security/sudo@1.8.4.1,5.11-0.151002:20120401T175552Z PKG_STATUS stdin = StringIO.new stderr = "" return OpenStruct.new(:stdout => stdout, :stdin => stdin, :stderr => stderr, :status => @status, :exitstatus => 0) end context "when loading current resource" do it "should create a current resource with the name of the new_resource" do expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should set the current resources package name to the new resources package name" do expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) @provider.load_current_resource expect(@current_resource.package_name).to eq(@new_resource.package_name) end it "should run pkg info with the package name" do expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) @provider.load_current_resource end it "should set the installed version to nil on the current resource if package state is not installed" do expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) @provider.load_current_resource expect(@current_resource.version).to be_nil end it "should set the installed version if package has one" do local = local_output local.stdout = <<-INSTALLED Name: crypto/gnupg Summary: GNU Privacy Guard Description: A complete and free implementation of the OpenPGP Standard as defined by RFC4880. Category: Applications/System Utilities State: Installed Publisher: solaris Version: 2.0.17 Build Release: 5.11 Branch: 0.175.0.0.0.2.537 Packaging Date: October 19, 2011 09:14:50 AM Size: 8.07 MB FMRI: pkg://solaris/crypto/gnupg@2.0.17,5.11-0.175.0.0.0.2.537:20111019T091450Z INSTALLED expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local) expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) @provider.load_current_resource expect(@current_resource.version).to eq("2.0.17") end it "should return the current resource" do expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output) expect(@provider.load_current_resource).to eql(@current_resource) end end context "when installing a package" do it "should run pkg install with the package name and version" do expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17", timeout: 900) @provider.install_package("crypto/gnupg", "2.0.17") end it "should run pkg install with the package name and version and options if specified" do expect(@provider).to receive(:shell_out).with("pkg --no-refresh install -q crypto/gnupg@2.0.17", timeout: 900) allow(@new_resource).to receive(:options).and_return("--no-refresh") @provider.install_package("crypto/gnupg", "2.0.17") end it "should not include the human-readable version in the candidate_version" do remote = remote_output remote.stdout = <<-PKG_STATUS Name: security/sudo Summary: sudo - authority delegation tool State: Not Installed Publisher: omnios Version: 1.8.4.1 (1.8.4p1) Build Release: 5.11 Branch: 0.151002 Packaging Date: April 1, 2012 05:55:52 PM Size: 2.57 MB FMRI: pkg://omnios/security/sudo@1.8.4.1,5.11-0.151002:20120401T175552Z PKG_STATUS expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output) expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote) @provider.load_current_resource expect(@current_resource.version).to be_nil expect(@provider.candidate_version).to eql("1.8.4.1") end it "should not upgrade the package if it is already installed" do local = local_output local.stdout = <<-INSTALLED Name: crypto/gnupg Summary: GNU Privacy Guard Description: A complete and free implementation of the OpenPGP Standard as defined by RFC4880. Category: Applications/System Utilities State: Installed Publisher: solaris Version: 2.0.17 Build Release: 5.11 Branch: 0.175.0.0.0.2.537 Packaging Date: October 19, 2011 09:14:50 AM Size: 8.07 MB FMRI: pkg://solaris/crypto/gnupg@2.0.17,5.11-0.175.0.0.0.2.537:20111019T091450Z INSTALLED remote = remote_output remote.stdout = <<-REMOTE Name: crypto/gnupg Summary: GNU Privacy Guard Description: A complete and free implementation of the OpenPGP Standard as defined by RFC4880. Category: Applications/System Utilities State: Not Installed Publisher: solaris Version: 2.0.18 Build Release: 5.11 Branch: 0.175.0.0.0.2.537 Packaging Date: October 19, 2011 09:14:50 AM Size: 8.07 MB FMRI: pkg://solaris/crypto/gnupg@2.0.18,5.11-0.175.0.0.0.2.537:20111019T091450Z REMOTE expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local) expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote) expect(@provider).to receive(:install_package).exactly(0).times @provider.run_action(:install) end context "when accept_license is true" do before do allow(@new_resource).to receive(:accept_license).and_return(true) end it "should run pkg install with the --accept flag" do expect(@provider).to receive(:shell_out).with("pkg install -q --accept crypto/gnupg@2.0.17", timeout: 900) @provider.install_package("crypto/gnupg", "2.0.17") end end end context "when upgrading a package" do it "should run pkg install with the package name and version" do expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17", timeout: 900) @provider.upgrade_package("crypto/gnupg", "2.0.17") end end context "when uninstalling a package" do it "should run pkg uninstall with the package name and version" do expect(@provider).to receive(:shell_out!).with("pkg uninstall -q crypto/gnupg@2.0.17", timeout: 900) @provider.remove_package("crypto/gnupg", "2.0.17") end it "should run pkg uninstall with the package name and version and options if specified" do expect(@provider).to receive(:shell_out!).with("pkg --no-refresh uninstall -q crypto/gnupg@2.0.17", timeout: 900) allow(@new_resource).to receive(:options).and_return("--no-refresh") @provider.remove_package("crypto/gnupg", "2.0.17") end end end chef-12.14.60/spec/unit/provider/package/macports_spec.rb000066400000000000000000000202361276456504500232200ustar00rootroot00000000000000# # Author:: David Balatero () # Copyright:: Copyright 2009-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::Macports do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("zsh") @current_resource = Chef::Resource::Package.new("zsh") @provider = Chef::Provider::Package::Macports.new(@new_resource, @run_context) allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource) # @status = double(:stdout => "", :exitstatus => 0) # @stdin = StringIO.new # @stdout = StringIO.new # @stderr = StringIO.new # @pid = 2342 end describe "load_current_resource" do it "should create a current resource with the name of the new_resource" do expect(@provider).to receive(:current_installed_version).and_return(nil) expect(@provider).to receive(:macports_candidate_version).and_return("4.2.7") @provider.load_current_resource expect(@provider.current_resource.name).to eq("zsh") end it "should create a current resource with the version if the package is installed" do expect(@provider).to receive(:macports_candidate_version).and_return("4.2.7") expect(@provider).to receive(:current_installed_version).and_return("4.2.7") @provider.load_current_resource expect(@provider.candidate_version).to eq("4.2.7") end it "should create a current resource with a nil version if the package is not installed" do expect(@provider).to receive(:current_installed_version).and_return(nil) expect(@provider).to receive(:macports_candidate_version).and_return("4.2.7") @provider.load_current_resource expect(@provider.current_resource.version).to be_nil end it "should set a candidate version if one exists" do expect(@provider).to receive(:current_installed_version).and_return(nil) expect(@provider).to receive(:macports_candidate_version).and_return("4.2.7") @provider.load_current_resource expect(@provider.candidate_version).to eq("4.2.7") end end describe "current_installed_version" do it "should return the current version if the package is installed" do stdout = < stdout, :exitstatus => 0) expect(@provider).to receive(:shell_out).and_return(status) expect(@provider.current_installed_version).to eq("0.9.8k_0") end it "should return nil if a package is not currently installed" do status = double(:stdout => " \n", :exitstatus => 0) expect(@provider).to receive(:shell_out).and_return(status) expect(@provider.current_installed_version).to be_nil end end describe "macports_candidate_version" do it "should return the latest available version of a given package" do status = double(:stdout => "version: 4.2.7\n", :exitstatus => 0) expect(@provider).to receive(:shell_out).and_return(status) expect(@provider.macports_candidate_version).to eq("4.2.7") end it "should return nil if there is no version for a given package" do status = double(:stdout => "Error: port fadsfadsfads not found\n", :exitstatus => 0) expect(@provider).to receive(:shell_out).and_return(status) expect(@provider.macports_candidate_version).to be_nil end end describe "install_package" do it "should run the port install command with the correct version" do expect(@current_resource).to receive(:version).and_return("4.1.6") @provider.current_resource = @current_resource expect(@provider).to receive(:shell_out!).with("port install zsh @4.2.7", timeout: 900) @provider.install_package("zsh", "4.2.7") end it "should not do anything if a package already exists with the same version" do expect(@current_resource).to receive(:version).and_return("4.2.7") @provider.current_resource = @current_resource expect(@provider).not_to receive(:shell_out!) @provider.install_package("zsh", "4.2.7") end it "should add options to the port command when specified" do expect(@current_resource).to receive(:version).and_return("4.1.6") @provider.current_resource = @current_resource allow(@new_resource).to receive(:options).and_return("-f") expect(@provider).to receive(:shell_out!).with("port -f install zsh @4.2.7", timeout: 900) @provider.install_package("zsh", "4.2.7") end end describe "purge_package" do it "should run the port uninstall command with the correct version" do expect(@provider).to receive(:shell_out!).with("port uninstall zsh @4.2.7", timeout: 900) @provider.purge_package("zsh", "4.2.7") end it "should purge the currently active version if no explicit version is passed in" do expect(@provider).to receive(:shell_out!).with("port uninstall zsh", timeout: 900) @provider.purge_package("zsh", nil) end it "should add options to the port command when specified" do allow(@new_resource).to receive(:options).and_return("-f") expect(@provider).to receive(:shell_out!).with("port -f uninstall zsh @4.2.7", timeout: 900) @provider.purge_package("zsh", "4.2.7") end end describe "remove_package" do it "should run the port deactivate command with the correct version" do expect(@provider).to receive(:shell_out!).with("port deactivate zsh @4.2.7", timeout: 900) @provider.remove_package("zsh", "4.2.7") end it "should remove the currently active version if no explicit version is passed in" do expect(@provider).to receive(:shell_out!).with("port deactivate zsh", timeout: 900) @provider.remove_package("zsh", nil) end it "should add options to the port command when specified" do allow(@new_resource).to receive(:options).and_return("-f") expect(@provider).to receive(:shell_out!).with("port -f deactivate zsh @4.2.7", timeout: 900) @provider.remove_package("zsh", "4.2.7") end end describe "upgrade_package" do it "should run the port upgrade command with the correct version" do expect(@current_resource).to receive(:version).at_least(:once).and_return("4.1.6") @provider.current_resource = @current_resource expect(@provider).to receive(:shell_out!).with("port upgrade zsh @4.2.7", timeout: 900) @provider.upgrade_package("zsh", "4.2.7") end it "should not run the port upgrade command if the version is already installed" do expect(@current_resource).to receive(:version).at_least(:once).and_return("4.2.7") @provider.current_resource = @current_resource expect(@provider).not_to receive(:shell_out!) @provider.upgrade_package("zsh", "4.2.7") end it "should call install_package if the package isn't currently installed" do expect(@current_resource).to receive(:version).at_least(:once).and_return(nil) @provider.current_resource = @current_resource expect(@provider).to receive(:install_package).and_return(true) @provider.upgrade_package("zsh", "4.2.7") end it "should add options to the port command when specified" do allow(@new_resource).to receive(:options).and_return("-f") expect(@current_resource).to receive(:version).at_least(:once).and_return("4.1.6") @provider.current_resource = @current_resource expect(@provider).to receive(:shell_out!).with("port -f upgrade zsh @4.2.7", timeout: 900) @provider.upgrade_package("zsh", "4.2.7") end end end chef-12.14.60/spec/unit/provider/package/openbsd_spec.rb000066400000000000000000000122031276456504500230150ustar00rootroot00000000000000# # Author:: Scott Bonds (scott@ggr.com) # Copyright:: Copyright 2014-2016, Scott Bonds # 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 "ostruct" describe Chef::Provider::Package::Openbsd do let(:node) do node = Chef::Node.new node.default["kernel"] = { "name" => "OpenBSD", "release" => "5.5", "machine" => "amd64" } node end let (:provider) do events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) Chef::Provider::Package::Openbsd.new(new_resource, run_context) end let(:new_resource) { Chef::Resource::Package.new(name) } before(:each) do ENV["PKG_PATH"] = nil end describe "install a package" do let(:name) { "ihavetoes" } let(:version) { "0.0" } context "when not already installed" do before do allow(provider).to receive(:shell_out!).with("pkg_info -e \"#{name}->0\"", anything()).and_return(instance_double("shellout", :stdout => "")) end context "when there is a single candidate" do context "when source is not provided" do it "should run the installation command" do expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return( instance_double("shellout", :stdout => "#{name}-#{version}\n")) expect(provider).to receive(:shell_out!).with( "pkg_add -r #{name}-#{version}", { :env => { "PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/" }, timeout: 900 } ) { OpenStruct.new :status => true } provider.run_action(:install) end end end context "when there are multiple candidates" do let(:flavor_a) { "flavora" } let(:flavor_b) { "flavorb" } context "if no version is specified" do it "should raise an exception" do expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return( instance_double("shellout", :stdout => "#{name}-#{version}-#{flavor_a}\n#{name}-#{version}-#{flavor_b}\n")) expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /multiple matching candidates/) end end context "if a flavor is specified" do let(:flavor) { "flavora" } let(:package_name) { "ihavetoes" } let(:name) { "#{package_name}--#{flavor}" } context "if no version is specified" do it "should run the installation command" do expect(provider).to receive(:shell_out!).with("pkg_info -e \"#{package_name}->0\"", anything()).and_return(instance_double("shellout", :stdout => "")) expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return( instance_double("shellout", :stdout => "#{name}-#{version}-#{flavor}\n")) expect(provider).to receive(:shell_out!).with( "pkg_add -r #{name}-#{version}-#{flavor}", { env: { "PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/" }, timeout: 900 } ) { OpenStruct.new :status => true } provider.run_action(:install) end end end context "if a version is specified" do it "should use the flavor from the version" do expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}-#{version}-#{flavor_b}\"", anything()).and_return( instance_double("shellout", :stdout => "#{name}-#{version}-#{flavor_a}\n")) new_resource.version("#{version}-#{flavor_b}") expect(provider).to receive(:shell_out!).with( "pkg_add -r #{name}-#{version}-#{flavor_b}", { env: { "PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/" }, timeout: 900 } ) { OpenStruct.new :status => true } provider.run_action(:install) end end end end end describe "delete a package" do before do @name = "ihavetoes" @new_resource = Chef::Resource::Package.new(@name) @current_resource = Chef::Resource::Package.new(@name) @provider = Chef::Provider::Package::Openbsd.new(@new_resource, @run_context) @provider.current_resource = @current_resource end it "should run the command to delete the installed package" do expect(@provider).to receive(:shell_out!).with( "pkg_delete #{@name}", env: nil, timeout: 900 ) { OpenStruct.new :status => true } @provider.remove_package(@name, nil) end end end chef-12.14.60/spec/unit/provider/package/pacman_spec.rb000066400000000000000000000160741276456504500226340ustar00rootroot00000000000000# # Author:: Jan Zimmek () # Copyright:: Copyright 2010-2016, Jan Zimmek # 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::Pacman do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("nano") @current_resource = Chef::Resource::Package.new("nano") @status = double(:stdout => "", :exitstatus => 0) @provider = Chef::Provider::Package::Pacman.new(@new_resource, @run_context) allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource) allow(@provider).to receive(:shell_out).and_return(@status) @stdin = StringIO.new @stdout = StringIO.new(<<-ERR) error: package "nano" not found ERR @stderr = StringIO.new @pid = 2342 end describe "when determining the current package state" do it "should create a current resource with the name of the new_resource" do expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should set the current resources package name to the new resources package name" do expect(@current_resource).to receive(:package_name).with(@new_resource.package_name) @provider.load_current_resource end it "should run pacman query with the package name" do expect(@provider).to receive(:shell_out).with("pacman -Qi #{@new_resource.package_name}", { timeout: 900 }).and_return(@status) @provider.load_current_resource end it "should read stdout on pacman" do allow(@provider).to receive(:shell_out).and_return(@status) @provider.load_current_resource end it "should set the installed version to nil on the current resource if pacman installed version not exists" do allow(@provider).to receive(:shell_out).and_return(@status) @provider.load_current_resource end it "should set the installed version if pacman has one" do stdout = <<-PACMAN Name : nano Version : 2.2.2-1 URL : http://www.nano-editor.org Licenses : GPL Groups : base Provides : None Depends On : glibc ncurses Optional Deps : None Required By : None Conflicts With : None Replaces : None Installed Size : 1496.00 K Packager : Andreas Radke Architecture : i686 Build Date : Mon 18 Jan 2010 06:16:16 PM CET Install Date : Mon 01 Feb 2010 10:06:30 PM CET Install Reason : Explicitly installed Install Script : Yes Description : Pico editor clone with enhancements PACMAN status = double(:stdout => stdout, :exitstatus => 0) allow(@provider).to receive(:shell_out).and_return(status) @provider.load_current_resource expect(@current_resource.version).to eq("2.2.2-1") end it "should set the candidate version if pacman has one" do status = double(:stdout => "core nano 2.2.3-1", :exitstatus => 0) allow(@provider).to receive(:shell_out).and_return(status) @provider.load_current_resource expect(@provider.candidate_version).to eql("2.2.3-1") end it "should use pacman.conf to determine valid repo names for package versions" do @pacman_conf = <<-PACMAN_CONF [options] HoldPkg = pacman glibc Architecture = auto [customrepo] Server = https://my.custom.repo [core] Include = /etc/pacman.d/mirrorlist [extra] Include = /etc/pacman.d/mirrorlist [community] Include = /etc/pacman.d/mirrorlist PACMAN_CONF status = double(:stdout => "customrepo nano 1.2.3-4", :exitstatus => 0) allow(::File).to receive(:exists?).with("/etc/pacman.conf").and_return(true) allow(::File).to receive(:read).with("/etc/pacman.conf").and_return(@pacman_conf) allow(@provider).to receive(:shell_out).and_return(status) @provider.load_current_resource expect(@provider.candidate_version).to eql("1.2.3-4") end it "should raise an exception if pacman fails" do expect(@status).to receive(:exitstatus).and_return(2) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package) end it "should not raise an exception if pacman succeeds" do expect(@status).to receive(:exitstatus).and_return(0) expect { @provider.load_current_resource }.not_to raise_error end it "should raise an exception if pacman does not return a candidate version" do allow(@provider).to receive(:shell_out).and_return(@status) expect { @provider.candidate_version }.to raise_error(Chef::Exceptions::Package) end it "should return the current resouce" do expect(@provider.load_current_resource).to eql(@current_resource) end end describe Chef::Provider::Package::Pacman, "install_package" do it "should run pacman install with the package name and version" do expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar nano", { timeout: 900 }) @provider.install_package("nano", "1.0") end it "should run pacman install with the package name and version and options if specified" do expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar --debug nano", { timeout: 900 }) allow(@new_resource).to receive(:options).and_return("--debug") @provider.install_package("nano", "1.0") end end describe Chef::Provider::Package::Pacman, "upgrade_package" do it "should run install_package with the name and version" do expect(@provider).to receive(:install_package).with("nano", "1.0") @provider.upgrade_package("nano", "1.0") end end describe Chef::Provider::Package::Pacman, "remove_package" do it "should run pacman remove with the package name" do expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar nano", { timeout: 900 }) @provider.remove_package("nano", "1.0") end it "should run pacman remove with the package name and options if specified" do expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar --debug nano", { timeout: 900 }) allow(@new_resource).to receive(:options).and_return("--debug") @provider.remove_package("nano", "1.0") end end describe Chef::Provider::Package::Pacman, "purge_package" do it "should run remove_package with the name and version" do expect(@provider).to receive(:remove_package).with("nano", "1.0") @provider.purge_package("nano", "1.0") end end end chef-12.14.60/spec/unit/provider/package/paludis_spec.rb000066400000000000000000000124751276456504500230370ustar00rootroot00000000000000# # Author:: Vasiliy Tolstov # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "ostruct" # based on the ips specs describe Chef::Provider::Package::Paludis do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("net/ntp") @current_resource = Chef::Resource::Package.new("net/ntp") allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource) @provider = Chef::Provider::Package::Paludis.new(@new_resource, @run_context) @stdin = StringIO.new @stderr = StringIO.new @stdout = <<-PKG_STATUS group/ntp 0 accounts group/ntp 0 installed-accounts net/ntp 4.2.6_p5-r2 arbor user/ntp 0 accounts user/ntp 0 installed-accounts net/ntp 4.2.6_p5-r1 installed PKG_STATUS @pid = 12345 @shell_out = OpenStruct.new(:stdout => @stdout, :stdin => @stdin, :stderr => @stderr, :status => @status, :exitstatus => 0) end context "when loading current resource" do it "should create a current resource with the name of the new_resource" do expect(@provider).to receive(:shell_out!).and_return(@shell_out) expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should set the current resources package name to the new resources package name" do expect(@provider).to receive(:shell_out!).and_return(@shell_out) expect(@current_resource).to receive(:package_name).with(@new_resource.package_name) @provider.load_current_resource end it "should run pkg info with the package name" do expect(@provider).to receive(:shell_out!).with("cave -L warning print-ids -M none -m \"#{@new_resource.package_name}\" -f \"%c/%p %v %r\n\"").and_return(@shell_out) @provider.load_current_resource end it "should return new version if package is installed" do @stdout.replace(<<-INSTALLED) group/ntp 0 accounts group/ntp 0 installed-accounts net/ntp 4.2.6_p5-r2 arbor user/ntp 0 accounts user/ntp 0 installed-accounts net/ntp 4.2.6_p5-r1 installed INSTALLED expect(@provider).to receive(:shell_out!).and_return(@shell_out) @provider.load_current_resource expect(@current_resource.version).to eq("4.2.6_p5-r1") expect(@provider.candidate_version).to eql("4.2.6_p5-r2") end it "should return the current resource" do expect(@provider).to receive(:shell_out!).and_return(@shell_out) expect(@provider.load_current_resource).to eql(@current_resource) end end context "when installing a package" do it "should run pkg install with the package name and version" do expect(@provider).to receive(:shell_out!).with("cave -L warning resolve -x \"=net/ntp-4.2.6_p5-r2\"", { :timeout => @new_resource.timeout }) @provider.install_package("net/ntp", "4.2.6_p5-r2") end it "should run pkg install with the package name and version and options if specified" do expect(@provider).to receive(:shell_out!).with("cave -L warning resolve -x --preserve-world \"=net/ntp-4.2.6_p5-r2\"", { :timeout => @new_resource.timeout }) allow(@new_resource).to receive(:options).and_return("--preserve-world") @provider.install_package("net/ntp", "4.2.6_p5-r2") end it "should not contain invalid characters for the version string" do @stdout.replace(<<-PKG_STATUS) sys-process/lsof 4.87 arbor sys-process/lsof 4.87 x86_64 PKG_STATUS expect(@provider).to receive(:shell_out!).with("cave -L warning resolve -x \"=sys-process/lsof-4.87\"", { :timeout => @new_resource.timeout }) @provider.install_package("sys-process/lsof", "4.87") end it "should not include the human-readable version in the candidate_version" do @stdout.replace(<<-PKG_STATUS) sys-process/lsof 4.87 arbor sys-process/lsof 4.87 x86_64 PKG_STATUS expect(@provider).to receive(:shell_out!).and_return(@shell_out) @provider.load_current_resource expect(@current_resource.version).to be_nil expect(@provider.candidate_version).to eql("4.87") end end context "when upgrading a package" do it "should run pkg install with the package name and version" do expect(@provider).to receive(:shell_out!).with("cave -L warning resolve -x \"=net/ntp-4.2.6_p5-r2\"", { :timeout => @new_resource.timeout }) @provider.upgrade_package("net/ntp", "4.2.6_p5-r2") end end context "when uninstalling a package" do it "should run pkg uninstall with the package name and version" do expect(@provider).to receive(:shell_out!).with("cave -L warning uninstall -x \"=net/ntp-4.2.6_p5-r2\"") @provider.remove_package("net/ntp", "4.2.6_p5-r2") end end end chef-12.14.60/spec/unit/provider/package/portage_spec.rb000066400000000000000000000342621276456504500230350ustar00rootroot00000000000000# # Author:: Caleb Tennis () # 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::Portage, "load_current_resource" do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("dev-util/git") @new_resource_without_category = Chef::Resource::Package.new("git") @current_resource = Chef::Resource::Package.new("dev-util/git") @provider = Chef::Provider::Package::Portage.new(@new_resource, @run_context) allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource) end describe "when determining the current state of the package" do it "should create a current resource with the name of new_resource" do allow(::Dir).to receive(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0"]) expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should set the current resource package name to the new resource package name" do allow(::Dir).to receive(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0"]) expect(@current_resource).to receive(:package_name).with(@new_resource.package_name) @provider.load_current_resource end it "should return a current resource with the correct version if the package is found" do allow(::Dir).to receive(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-foobar-0.9", "/var/db/pkg/dev-util/git-1.0.0"]) @provider.load_current_resource expect(@provider.current_resource.version).to eq("1.0.0") end it "should return a current resource with the correct version if the package is found with revision" do allow(::Dir).to receive(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0-r1"]) @provider.load_current_resource 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 expect(@provider.current_resource.version).to be_nil end it "should return a package name match from /var/db/pkg/* if a category isn't specified and a match is found" do allow(::Dir).to receive(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-foobar-0.9", "/var/db/pkg/dev-util/git-1.0.0"]) @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context) @provider.load_current_resource expect(@provider.current_resource.version).to eq("1.0.0") end it "should return a current resource with a nil version if a category isn't specified and a name match from /var/db/pkg/* is not found" do allow(::Dir).to receive(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/notgit-1.0.0"]) @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context) @provider.load_current_resource expect(@provider.current_resource.version).to be_nil end it "should throw an exception if a category isn't specified and multiple packages are found" do allow(::Dir).to receive(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0", "/var/db/pkg/funny-words/git-1.0.0"]) @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package) end it "should return a current resource with a nil version if a category is specified and multiple packages are found" do allow(::Dir).to receive(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0", "/var/db/pkg/funny-words/git-1.0.0"]) @provider = Chef::Provider::Package::Portage.new(@new_resource, @run_context) @provider.load_current_resource expect(@provider.current_resource.version).to be_nil end it "should return a current resource with a nil version if a category is not specified and multiple packages from the same category are found" do allow(::Dir).to receive(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0", "/var/db/pkg/dev-util/git-1.0.1"]) @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context) @provider.load_current_resource expect(@provider.current_resource.version).to be_nil end end describe "once the state of the package is known" do describe Chef::Provider::Package::Portage, "candidate_version" do it "should return the candidate_version variable if already set" do @provider.candidate_version = "1.0.0" expect(@provider).not_to receive(:shell_out) @provider.candidate_version end it "should throw an exception if the exitstatus is not 0" do status = double(:stdout => "", :exitstatus => 1) allow(@provider).to receive(:shell_out).and_return(status) expect { @provider.candidate_version }.to raise_error(Chef::Exceptions::Package) end it "should find the candidate_version if a category is specifed and there are no duplicates" do output = < output, :exitstatus => 0) expect(@provider).to receive(:shell_out).and_return(status) expect(@provider.candidate_version).to eq("1.6.0.6") end it "should find the candidate_version if a category is not specifed and there are no duplicates" do output = < output, :exitstatus => 0) @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context) expect(@provider).to receive(:shell_out).and_return(status) expect(@provider.candidate_version).to eq("1.6.0.6") end it "should throw an exception if a category is not specified and there are duplicates" do output = < output, :exitstatus => 0) @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context) expect(@provider).to receive(:shell_out).and_return(status) expect { @provider.candidate_version }.to raise_error(Chef::Exceptions::Package) end it "should find the candidate_version if a category is specifed and there are category duplicates" do output = < output, :exitstatus => 0) @provider = Chef::Provider::Package::Portage.new(@new_resource, @run_context) expect(@provider).to receive(:shell_out).and_return(status) expect(@provider.candidate_version).to eq("1.6.0.6") end end describe Chef::Provider::Package::Portage, "install_package" do it "should install a normally versioned package using portage" do expect(@provider).to receive(:shell_out!).with("emerge -g --color n --nospinner --quiet =dev-util/git-1.0.0") @provider.install_package("dev-util/git", "1.0.0") end it "should install a tilde versioned package using portage" do expect(@provider).to receive(:shell_out!).with("emerge -g --color n --nospinner --quiet ~dev-util/git-1.0.0") @provider.install_package("dev-util/git", "~1.0.0") end it "should add options to the emerge command when specified" do expect(@provider).to receive(:shell_out!).with("emerge -g --color n --nospinner --quiet --oneshot =dev-util/git-1.0.0") allow(@new_resource).to receive(:options).and_return("--oneshot") @provider.install_package("dev-util/git", "1.0.0") end end describe Chef::Provider::Package::Portage, "remove_package" do it "should un-emerge the package with no version specified" do expect(@provider).to receive(:shell_out!).with("emerge --unmerge --color n --nospinner --quiet dev-util/git") @provider.remove_package("dev-util/git", nil) end it "should un-emerge the package with a version specified" do expect(@provider).to receive(:shell_out!).with("emerge --unmerge --color n --nospinner --quiet =dev-util/git-1.0.0") @provider.remove_package("dev-util/git", "1.0.0") end end end end chef-12.14.60/spec/unit/provider/package/rpm_spec.rb000066400000000000000000000362611276456504500221730ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Author:: Daniel DeLeo () # 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::Rpm do let(:provider) { Chef::Provider::Package::Rpm.new(new_resource, run_context) } let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:package_source) { "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" } let(:package_name) { "ImageMagick-c++" } let(:new_resource) do Chef::Resource::Package.new(package_name).tap do |resource| resource.source(package_source) end end # `rpm -qp [stuff] $source` let(:rpm_qp_status) { instance_double("Mixlib::ShellOut", exitstatus: rpm_qp_exitstatus, stdout: rpm_qp_stdout) } # `rpm -q [stuff] $package_name` let(:rpm_q_status) { instance_double("Mixlib::ShellOut", exitstatus: rpm_q_exitstatus, stdout: rpm_q_stdout) } before(:each) do allow(::File).to receive(:exists?).with("PLEASE STUB File.exists? EXACTLY").and_return(true) # Ensure all shell out usage is stubbed with exact arguments allow(provider).to receive(:shell_out!).with("PLEASE STUB YOUR SHELLOUT CALLS").and_return(nil) allow(provider).to receive(:shell_out).with("PLEASE STUB YOUR SHELLOUT CALLS").and_return(nil) end describe "when the package source is not valid" do context "when source is not defiend" do let(:new_resource) { Chef::Resource::Package.new("ImageMagick-c++") } it "should raise an exception when attempting any action" do expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) end end context "when the source is a file that doesn't exist" do it "should raise an exception when attempting any action" do allow(::File).to receive(:exists?).with(package_source).and_return(false) expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) end end context "when the source is an unsupported URI scheme" do let(:package_source) { "foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" } it "should raise an exception if an uri formed source is non-supported scheme" do allow(::File).to receive(:exists?).with(package_source).and_return(false) # verify let bindings are as we expect expect(new_resource.source).to eq("foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") expect(provider.load_current_resource).to be_nil expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package) end end end describe "when the package source is valid" do before do expect(provider).to receive(:shell_out!). with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{package_source}", timeout: 900). and_return(rpm_qp_status) expect(provider).to receive(:shell_out). with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{package_name}", timeout: 900). and_return(rpm_q_status) end context "when rpm fails when querying package installed state" do before do allow(::File).to receive(:exists?).with(package_source).and_return(true) end let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" } let(:rpm_q_stdout) { "" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { -1 } it "raises an exception when attempting any action" do expected_message = "Unable to determine current version due to RPM failure." expect { provider.run_action(:install) }.to raise_error do |error| expect(error).to be_a_kind_of(Chef::Exceptions::Package) expect(error.to_s).to include(expected_message) end end end context "when the package is installed" do let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" } let(:rpm_q_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { 0 } let(:action) { :install } context "when the source is a file system path" do before do allow(::File).to receive(:exists?).with(package_source).and_return(true) provider.action = action provider.load_current_resource provider.define_resource_requirements provider.process_resource_requirements end it "should get the source package version from rpm if provided" do expect(provider.current_resource.package_name).to eq("ImageMagick-c++") expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5") end it "should return the current version installed if found by rpm" do expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5") end describe "action install" do context "when at the desired version already" do it "does nothing when the correct version is installed" do expect(provider).to_not receive(:shell_out!).with("rpm -i /tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_install end end context "when a newer version is desired" do let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" } it "runs rpm -u with the package source to upgrade" do expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_install end end context "when an older version is desired" do let(:new_resource) do Chef::Resource::RpmPackage.new(package_name).tap do |r| r.source(package_source) r.allow_downgrade(true) end end let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" } it "should run rpm -u --oldpackage with the package source to downgrade" do expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_install end end end describe "action upgrade" do let(:action) { :upgrade } context "when at the desired version already" do it "does nothing when the correct version is installed" do expect(provider).to_not receive(:shell_out!).with("rpm -i /tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_upgrade end end context "when a newer version is desired" do let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" } it "runs rpm -u with the package source to upgrade" do expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_upgrade end end context "when an older version is desired" do let(:new_resource) do Chef::Resource::RpmPackage.new(package_name).tap do |r| r.source(package_source) r.allow_downgrade(true) end end let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" } it "should run rpm -u --oldpackage with the package source to downgrade" do expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.action_upgrade end end end describe "action :remove" do let(:action) { :remove } it "should remove the package" do expect(provider).to receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900) provider.action_remove end end context "when the package name contains a tilde (chef#3503)" do let(:package_name) { "supermarket" } let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" } let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" } let(:rpm_q_stdout) { "supermarket 1.10.1~alpha.0-1.el5" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { 0 } it "should correctly determine the candidate version and installed version" do expect(provider.current_resource.package_name).to eq("supermarket") expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5") end end context "when the package name contains a plus symbol (chef#3671)" do let(:package_name) { "chef-server-core" } let(:package_source) { "/tmp/chef-server-core-12.2.0+20150713220422-1.el6.x86_64.rpm" } let(:rpm_qp_stdout) { "chef-server-core 12.2.0+20150713220422-1.el6" } let(:rpm_q_stdout) { "chef-server-core 12.2.0+20150713220422-1.el6" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { 0 } it "should correctly determine the candidate version and installed version" do expect(provider.current_resource.package_name).to eq("chef-server-core") expect(provider.new_resource.version).to eq("12.2.0+20150713220422-1.el6") end end end context "when the source is given as an URI" do before(:each) do allow(::File).to receive(:exists?).with(package_source).and_return(false) provider.action = action provider.load_current_resource provider.define_resource_requirements provider.process_resource_requirements end %w{http HTTP https HTTPS ftp FTP file FILE}.each do |scheme| context "when the source URI uses protocol scheme '#{scheme}'" do let(:package_source) { "#{scheme}://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" } it "should get the source package version from rpm if provided" do expect(provider.current_resource.package_name).to eq("ImageMagick-c++") expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5") end it "should return the current version installed if found by rpm" do expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5") end end end end end context "when the package is not installed" do let(:package_name) { "openssh-askpass" } let(:package_source) { "/tmp/openssh-askpass-1.2.3-4.el6_5.x86_64.rpm" } let(:rpm_qp_stdout) { "openssh-askpass 1.2.3-4.el6_5" } let(:rpm_q_stdout) { "package openssh-askpass is not installed" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { 0 } let(:action) { :install } before do allow(File).to receive(:exists?).with(package_source).and_return(true) provider.action = action provider.load_current_resource provider.define_resource_requirements provider.process_resource_requirements end it "should not detect the package name as version when not installed" do expect(provider.current_resource.version).to be_nil end context "when the package name contains a tilde (chef#3503)" do let(:package_name) { "supermarket" } let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" } let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" } let(:rpm_q_stdout) { "package supermarket is not installed" } let(:rpm_qp_exitstatus) { 0 } let(:rpm_q_exitstatus) { 0 } it "should correctly determine the candidate version" do expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5") end end describe "managing the package" do describe "action install" do it "installs the package" do expect(provider).to receive(:shell_out!).with("rpm -i #{package_source}", timeout: 900) provider.action_install end context "when custom resource options are given" do it "installs with custom options specified in the resource" do new_resource.options("--dbpath /var/lib/rpm") expect(provider).to receive(:shell_out!).with("rpm --dbpath /var/lib/rpm -i #{package_source}", timeout: 900) provider.action_install end end end describe "action upgrade" do let(:action) { :upgrade } it "installs the package" do expect(provider).to receive(:shell_out!).with("rpm -i #{package_source}", timeout: 900) provider.action_upgrade end end describe "when removing the package" do let(:action) { :remove } it "should do nothing" do expect(provider).to_not receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900) provider.action_remove end end end end end context "when the resource name is the path to the package" do let(:new_resource) do # When we pass a source in as the name, then #initialize in the # provider will call File.exists?. Because of the ordering in our # let() bindings and such, we have to set the stub here and not in a # before block. allow(::File).to receive(:exists?).with(package_source).and_return(true) Chef::Resource::Package.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") end let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") } it "should install from a path when the package is a path and the source is nil" do expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") provider.current_resource = current_resource expect(provider).to receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5") end it "should uprgrade from a path when the package is a path and the source is nil" do expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") current_resource.version("21.4-19.el5") provider.current_resource = current_resource expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900) provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5") end end end chef-12.14.60/spec/unit/provider/package/rubygems_spec.rb000066400000000000000000001036661276456504500232360ustar00rootroot00000000000000# # Author:: David Balatero (dbalatero@gmail.com) # # Copyright:: Copyright 2009-2016, David Balatero # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # module GemspecBackcompatCreator def gemspec(name, version) if Gem::Specification.new.method(:initialize).arity == 0 Gem::Specification.new { |s| s.name = name; s.version = version } else Gem::Specification.new(name, version) end end end require "spec_helper" require "ostruct" describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do include GemspecBackcompatCreator before do @gem_env = Chef::Provider::Package::Rubygems::CurrentGemEnvironment.new end it "determines the gem paths from the in memory rubygems" do expect(@gem_env.gem_paths).to eq(Gem.path) end it "determines the installed versions of gems from Gem.source_index" do gems = [gemspec("rspec-core", Gem::Version.new("1.2.9")), gemspec("rspec-core", Gem::Version.new("1.3.0"))] if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.0") expect(Gem::Specification).to receive(:find_all_by_name).with("rspec-core", Gem::Dependency.new("rspec-core").requirement).and_return(gems) else expect(Gem.source_index).to receive(:search).with(Gem::Dependency.new("rspec-core", nil)).and_return(gems) end expect(@gem_env.installed_versions(Gem::Dependency.new("rspec-core", nil))).to eq(gems) end it "determines the installed versions of gems from the source index (part2: the unmockening)" do expected = ["rspec-core", Gem::Version.new(RSpec::Core::Version::STRING)] actual = @gem_env.installed_versions(Gem::Dependency.new("rspec-core", nil)).map { |spec| [spec.name, spec.version] } expect(actual).to include(expected) end it "yields to a block with an alternate source list set" do sources_in_block = nil normal_sources = Gem.sources begin @gem_env.with_gem_sources("http://gems.example.org") do sources_in_block = Gem.sources raise "sources should be reset even in case of an error" end rescue RuntimeError end expect(sources_in_block).to eq(%w{http://gems.example.org}) expect(Gem.sources).to eq(normal_sources) end it "it doesnt alter the gem sources if none are set" do sources_in_block = nil normal_sources = Gem.sources begin @gem_env.with_gem_sources(nil) do sources_in_block = Gem.sources raise "sources should be reset even in case of an error" end rescue RuntimeError end expect(sources_in_block).to eq(normal_sources) expect(Gem.sources).to eq(normal_sources) end context "new default rubygems behavior" do before do Chef::Config[:rubygems_cache_enabled] = false end it "finds a matching gem candidate version on rubygems 2.0.0+" do dep = Gem::Dependency.new("rspec", ">= 0") dep_installer = Gem::DependencyInstaller.new allow(@gem_env).to receive(:dependency_installer).and_return(dep_installer) expect(dep_installer).not_to receive(:find_gems_with_sources).with(dep).and_call_original expect(@gem_env.candidate_version_from_remote(dep)).to be_kind_of(Gem::Version) end it "gives the candidate version as nil if none is found" do dep = Gem::Dependency.new("lksdjflksdjflsdkfj", ">= 0") dep_installer = Gem::DependencyInstaller.new allow(@gem_env).to receive(:dependency_installer).and_return(dep_installer) expect(dep_installer).not_to receive(:find_gems_with_sources).with(dep).and_call_original expect(@gem_env.candidate_version_from_remote(dep)).to be_nil end it "finds a matching gem from a specific gemserver when explicit sources are given (to a server that doesn't respond to api requests)" do dep = Gem::Dependency.new("rspec", ">= 0") dep_installer = Gem::DependencyInstaller.new allow(@gem_env).to receive(:dependency_installer).and_return(dep_installer) expect(dep_installer).not_to receive(:find_gems_with_sources).with(dep).and_call_original expect(@gem_env.candidate_version_from_remote(dep, "http://production.cf.rubygems.org")).to be_kind_of(Gem::Version) end end context "old rubygems caching behavior" do before do Chef::Config[:rubygems_cache_enabled] = true end it "finds a matching gem candidate version on rubygems 2.0.0+" do dep = Gem::Dependency.new("rspec", ">= 0") expect(@gem_env.candidate_version_from_remote(dep)).to be_kind_of(Gem::Version) end it "gives the candidate version as nil if none is found" do dep = Gem::Dependency.new("lksdjflksdjflsdkfj", ">= 0") expect(@gem_env.candidate_version_from_remote(dep)).to be_nil end it "finds a matching gem from a specific gemserver when explicit sources are given" do dep = Gem::Dependency.new("rspec", ">= 0") expect(@gem_env.candidate_version_from_remote(dep, "http://production.cf.rubygems.org")).to be_kind_of(Gem::Version) end end it "finds a matching candidate version from a .gem file when the path to the gem is supplied" do location = CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" expect(@gem_env.candidate_version_from_file(Gem::Dependency.new("chef-integration-test", ">= 0"), location)).to eq(Gem::Version.new("0.1.0")) expect(@gem_env.candidate_version_from_file(Gem::Dependency.new("chef-integration-test", ">= 0.2.0"), location)).to be_nil end it "installs a gem with a hash of options for the dependency installer" do dep_installer = Gem::DependencyInstaller.new expect(@gem_env).to receive(:dependency_installer).with(:install_dir => "/foo/bar").and_return(dep_installer) expect(@gem_env).to receive(:with_gem_sources).with("http://gems.example.com").and_yield expect(dep_installer).to receive(:install).with(Gem::Dependency.new("rspec", ">= 0")) @gem_env.install(Gem::Dependency.new("rspec", ">= 0"), :install_dir => "/foo/bar", :sources => ["http://gems.example.com"]) end it "builds an uninstaller for a gem with options set to avoid requiring user input" do # default options for uninstaller should be: # :ignore => true, :executables => true expect(Gem::Uninstaller).to receive(:new).with("rspec", :ignore => true, :executables => true) @gem_env.uninstaller("rspec") end it "uninstalls all versions of a gem" do uninstaller = double("gem uninstaller") expect(uninstaller).to receive(:uninstall) expect(@gem_env).to receive(:uninstaller).with("rspec", :all => true).and_return(uninstaller) @gem_env.uninstall("rspec") end it "uninstalls a specific version of a gem" do uninstaller = double("gem uninstaller") expect(uninstaller).to receive(:uninstall) expect(@gem_env).to receive(:uninstaller).with("rspec", :version => "1.2.3").and_return(uninstaller) @gem_env.uninstall("rspec", "1.2.3") end end describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do include GemspecBackcompatCreator before do Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache.clear Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache.clear @gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new("/usr/weird/bin/gem") end it "determines the gem paths from shelling out to gem env" do gem_env_output = ["/path/to/gems", "/another/path/to/gems"].join(File::PATH_SEPARATOR) shell_out_result = OpenStruct.new(:stdout => gem_env_output) expect(@gem_env).to receive(:shell_out!).with("/usr/weird/bin/gem env gempath").and_return(shell_out_result) expect(@gem_env.gem_paths).to eq(["/path/to/gems", "/another/path/to/gems"]) end it "caches the gempaths by gem_binary" do gem_env_output = ["/path/to/gems", "/another/path/to/gems"].join(File::PATH_SEPARATOR) shell_out_result = OpenStruct.new(:stdout => gem_env_output) expect(@gem_env).to receive(:shell_out!).with("/usr/weird/bin/gem env gempath").and_return(shell_out_result) expected = ["/path/to/gems", "/another/path/to/gems"] expect(@gem_env.gem_paths).to eq(["/path/to/gems", "/another/path/to/gems"]) expect(Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache["/usr/weird/bin/gem"]).to eq(expected) end it "uses the cached result for gem paths when available" do expect(@gem_env).not_to receive(:shell_out!) expected = ["/path/to/gems", "/another/path/to/gems"] Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache["/usr/weird/bin/gem"] = expected expect(@gem_env.gem_paths).to eq(["/path/to/gems", "/another/path/to/gems"]) end it "builds the gems source index from the gem paths" do allow(@gem_env).to receive(:gem_paths).and_return(["/path/to/gems", "/another/path/to/gems"]) if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.0") @gem_env.gem_specification expect(Gem::Specification.dirs).to eq([ "/path/to/gems/specifications", "/another/path/to/gems/specifications" ]) else expect(Gem::SourceIndex).to receive(:from_gems_in).with("/path/to/gems/specifications", "/another/path/to/gems/specifications") @gem_env.gem_source_index end end it "determines the installed versions of gems from the source index" do gems = [gemspec("rspec", Gem::Version.new("1.2.9")), gemspec("rspec", Gem::Version.new("1.3.0"))] rspec_dep = Gem::Dependency.new("rspec", nil) if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.0") allow(@gem_env).to receive(:gem_specification).and_return(Gem::Specification) expect(@gem_env.gem_specification).to receive(:find_all_by_name).with(rspec_dep.name, rspec_dep.requirement).and_return(gems) else allow(@gem_env).to receive(:gem_source_index).and_return(Gem.source_index) expect(@gem_env.gem_source_index).to receive(:search).with(rspec_dep).and_return(gems) end expect(@gem_env.installed_versions(Gem::Dependency.new("rspec", nil))).to eq(gems) end it "determines the installed versions of gems from the source index (part2: the unmockening)" do allow($stdout).to receive(:write) path_to_gem = if windows? `where gem`.split[1] else `which gem`.strip end skip("cant find your gem executable") if path_to_gem.empty? gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new(path_to_gem) expected = ["rspec-core", Gem::Version.new(RSpec::Core::Version::STRING)] actual = gem_env.installed_versions(Gem::Dependency.new("rspec-core", nil)).map { |s| [s.name, s.version] } expect(actual).to include(expected) end it "detects when the target gem environment is the jruby platform" do gem_env_out = <<-JRUBY_GEM_ENV RubyGems Environment: - RUBYGEMS VERSION: 1.3.6 - RUBY VERSION: 1.8.7 (2010-05-12 patchlevel 249) [java] - INSTALLATION DIRECTORY: /Users/you/.rvm/gems/jruby-1.5.0 - RUBY EXECUTABLE: /Users/you/.rvm/rubies/jruby-1.5.0/bin/jruby - EXECUTABLE DIRECTORY: /Users/you/.rvm/gems/jruby-1.5.0/bin - RUBYGEMS PLATFORMS: - ruby - universal-java-1.6 - GEM PATHS: - /Users/you/.rvm/gems/jruby-1.5.0 - /Users/you/.rvm/gems/jruby-1.5.0@global - GEM CONFIGURATION: - :update_sources => true - :verbose => true - :benchmark => false - :backtrace => false - :bulk_threshold => 1000 - "install" => "--env-shebang" - "update" => "--env-shebang" - "gem" => "--no-rdoc --no-ri" - :sources => ["https://rubygems.org/", "http://gems.github.com/"] - REMOTE SOURCES: - https://rubygems.org/ - http://gems.github.com/ JRUBY_GEM_ENV expect(@gem_env).to receive(:shell_out!).with("/usr/weird/bin/gem env").and_return(double("jruby_gem_env", :stdout => gem_env_out)) expected = ["ruby", Gem::Platform.new("universal-java-1.6")] expect(@gem_env.gem_platforms).to eq(expected) # it should also cache the result expect(Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache["/usr/weird/bin/gem"]).to eq(expected) end it "uses the cached result for gem platforms if available" do expect(@gem_env).not_to receive(:shell_out!) expected = ["ruby", Gem::Platform.new("universal-java-1.6")] Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache["/usr/weird/bin/gem"] = expected expect(@gem_env.gem_platforms).to eq(expected) end it "uses the current gem platforms when the target env is not jruby" do gem_env_out = <<-RBX_GEM_ENV RubyGems Environment: - RUBYGEMS VERSION: 1.3.6 - RUBY VERSION: 1.8.7 (2010-05-14 patchlevel 174) [x86_64-apple-darwin10.3.0] - INSTALLATION DIRECTORY: /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514 - RUBYGEMS PREFIX: /Users/ddeleo/.rvm/rubies/rbx-1.0.0-20100514 - RUBY EXECUTABLE: /Users/ddeleo/.rvm/rubies/rbx-1.0.0-20100514/bin/rbx - EXECUTABLE DIRECTORY: /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514/bin - RUBYGEMS PLATFORMS: - ruby - x86_64-darwin-10 - x86_64-rubinius-1.0 - GEM PATHS: - /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514 - /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514@global - GEM CONFIGURATION: - :update_sources => true - :verbose => true - :benchmark => false - :backtrace => false - :bulk_threshold => 1000 - :sources => ["https://rubygems.org/", "http://gems.github.com/"] - "gem" => "--no-rdoc --no-ri" - REMOTE SOURCES: - https://rubygems.org/ - http://gems.github.com/ RBX_GEM_ENV expect(@gem_env).to receive(:shell_out!).with("/usr/weird/bin/gem env").and_return(double("rbx_gem_env", :stdout => gem_env_out)) expect(@gem_env.gem_platforms).to eq(Gem.platforms) expect(Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache["/usr/weird/bin/gem"]).to eq(Gem.platforms) end it "yields to a block while masquerading as a different gems platform" do original_platforms = Gem.platforms platforms_in_block = nil begin @gem_env.with_gem_platforms(["ruby", Gem::Platform.new("sparc64-java-1.7")]) do platforms_in_block = Gem.platforms raise "gem platforms should get set to the correct value even when an error occurs" end rescue RuntimeError end expect(platforms_in_block).to eq(["ruby", Gem::Platform.new("sparc64-java-1.7")]) expect(Gem.platforms).to eq(original_platforms) end end describe Chef::Provider::Package::Rubygems do let(:target_version) { nil } let(:gem_name) { "rspec-core" } let(:gem_binary) { nil } let(:bindir) { "/usr/bin/ruby" } let(:options) { nil } let(:source) { nil } let(:new_resource) do new_resource = Chef::Resource::GemPackage.new(gem_name) new_resource.version(target_version) new_resource.gem_binary(gem_binary) if gem_binary new_resource.options(options) if options new_resource.source(source) if source new_resource end let (:current_resource) { nil } let(:provider) do run_context = Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) provider = Chef::Provider::Package::Rubygems.new(new_resource, run_context) if current_resource allow(provider).to receive(:load_current_resource) provider.current_resource = current_resource end provider end let(:gem_dep) { Gem::Dependency.new(gem_name, target_version) } before(:each) do # We choose detect omnibus via RbConfig::CONFIG['bindir'] in Chef::Provider::Package::Rubygems.new allow(RbConfig::CONFIG).to receive(:[]).with("bindir").and_return(bindir) # Rubygems uses this interally allow(RbConfig::CONFIG).to receive(:[]).with("arch").and_call_original end describe "when new_resource version is nil" do let(:target_version) { nil } it "target_version_already_installed? should return false so that we can search for candidates" do provider.load_current_resource expect(provider.target_version_already_installed?(provider.current_resource.version, new_resource.version)).to be_falsey end end describe "when new_resource version is an rspec version" do let(:current_version) { RSpec::Core::Version::STRING } let(:target_version) { current_version } it "triggers a gem configuration load so a later one will not stomp its config values" do _ = provider # ugly, is there a better way? expect(Gem.instance_variable_get(:@configuration)).not_to be_nil end it "uses the CurrentGemEnvironment implementation when no gem_binary_path is provided" do expect(provider.gem_env).to be_a_kind_of(Chef::Provider::Package::Rubygems::CurrentGemEnvironment) end context "when a gem_binary_path is provided" do let(:gem_binary) { "/usr/weird/bin/gem" } it "uses the AlternateGemEnvironment implementation when a gem_binary_path is provided" do expect(provider.gem_env.gem_binary_location).to eq(gem_binary) end context "when you try to use a hash of install options" do let(:options) { { :fail => :burger } } it "smites you" do expect { provider }.to raise_error(ArgumentError) end end end context "when in omnibus opscode" do let(:bindir) { "/opt/opscode/embedded/bin" } it "recognizes opscode as omnibus" do expect(provider.is_omnibus?).to be true end end context "when in omnibus chefdk" do let(:bindir) { "/opt/chefdk/embedded/bin" } it "recognizes chefdk as omnibus" do expect(provider.is_omnibus?).to be true end end context "when in omnibus chef" do let(:bindir) { "/opt/chef/embedded/bin" } it "recognizes chef as omnibus" do expect(provider.is_omnibus?).to be true end it "searches for a gem binary when running on Omnibus on Unix" do platform_mock :unix do allow(ENV).to receive(:[]).with("PATH").and_return("/usr/bin:/usr/sbin:/opt/chef/embedded/bin") allow(File).to receive(:exists?).with("/usr/bin/gem").and_return(false) allow(File).to receive(:exists?).with("/usr/sbin/gem").and_return(true) allow(File).to receive(:exists?).with("/opt/chef/embedded/bin/gem").and_return(true) # should not get here expect(provider.gem_env.gem_binary_location).to eq("/usr/sbin/gem") end end context "when on Windows" do let(:bindir) { "d:/opscode/chef/embedded/bin" } it "searches for a gem binary when running on Omnibus on Windows" do platform_mock :windows do allow(ENV).to receive(:[]).with("PATH").and_return('C:\windows\system32;C:\windows;C:\Ruby186\bin;d:\opscode\chef\embedded\bin') allow(File).to receive(:exists?).with('C:\\windows\\system32\\gem').and_return(false) allow(File).to receive(:exists?).with('C:\\windows\\gem').and_return(false) allow(File).to receive(:exists?).with('C:\\Ruby186\\bin\\gem').and_return(true) allow(File).to receive(:exists?).with('d:\\opscode\\chef\\bin\\gem').and_return(false) # should not get here allow(File).to receive(:exists?).with('d:\\opscode\\chef\\embedded\\bin\\gem').and_return(false) # should not get here expect(provider.gem_env.gem_binary_location).to eq('C:\Ruby186\bin\gem') end end end end it "converts the new resource into a gem dependency" do expect(provider.gem_dependency).to eq(gem_dep) end context "when the new resource is not the current version" do let(:target_version) { "~> 9000.0.2" } it "converts the new resource into a gem dependency" do expect(provider.gem_dependency).to eq(gem_dep) end end describe "when determining the currently installed version" do before do provider.load_current_resource end it "sets the current version to the version specified by the new resource if that version is installed" do expect(provider.current_resource.version).to eq(current_version) end context "if the requested version is not installed" do let(:target_version) { "9000.0.2" } it "sets the current version to the highest installed version if the requested version is not installed" do expect(provider.current_resource.version).to eq(current_version) end end context "if the package is not currently installed" do let(:gem_name) { "no-such-gem-should-exist-with-this-name" } it "leaves the current version at nil" do expect(provider.current_resource.version).to be_nil end end end describe "when determining the candidate version to install" do before do provider.load_current_resource end context "when the current version is the target version" do it "does not query for available versions" do # NOTE: odd use case -- we've equality pinned a version, but are calling :upgrade expect(provider.gem_env).not_to receive(:candidate_version_from_remote) expect(provider.gem_env).not_to receive(:install) provider.run_action(:upgrade) expect(new_resource).not_to be_updated_by_last_action end end context "when the current version satisfies the target version requirement" do let(:target_version) { ">= 0" } it "does not query for available versions on install" do expect(provider.gem_env).not_to receive(:candidate_version_from_remote) expect(provider.gem_env).not_to receive(:install) provider.run_action(:install) expect(new_resource).not_to be_updated_by_last_action end it "queries for available versions on upgrade" do expect(provider.gem_env).to receive(:candidate_version_from_remote). and_return(Gem::Version.new("9000.0.2")) expect(provider.gem_env).to receive(:install) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action end end context "when the requested source is a remote server" do let(:source) { "http://mygems.example.com" } it "determines the candidate version by querying the remote gem servers" do expect(provider.gem_env).to receive(:candidate_version_from_remote). with(gem_dep, source). and_return(Gem::Version.new(target_version)) expect(provider.candidate_version).to eq(target_version) end end context "when the requested source is a file" do let (:gem_name) { "chef-integration-test" } let (:source) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } let (:target_version) { ">= 0" } it "parses the gem's specification" do expect(provider.candidate_version).to eq("0.1.0") end end end describe "when installing a gem" do let(:target_version) { "9000.0.2" } let(:current_version) { nil } let(:candidate_version) { "9000.0.2" } let(:current_resource) do current_resource = Chef::Resource::GemPackage.new(gem_name) current_resource.version(current_version) current_resource end before do version = Gem::Version.new(candidate_version) args = [gem_dep] args << source if source allow(provider.gem_env).to receive(:candidate_version_from_remote). with(*args). and_return(version) end describe "in the current gem environment" do it "installs the gem via the gems api when no explicit options are used" do expect(provider.gem_env).to receive(:install).with(gem_dep, :sources => nil) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end context "when a remote source is provided" do let(:source) { "http://gems.example.org" } it "installs the gem via the gems api" do expect(provider.gem_env).to receive(:install).with(gem_dep, :sources => [source]) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end context "when source is a path" do let(:source) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } it "installs the gem from file via the gems api" do expect(provider.gem_env).to receive(:install).with(source) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end context "when the gem name is a file path and source is nil" do let(:gem_name) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } it "installs the gem from file via the gems api" do expect(new_resource.source).to eq(gem_name) expect(provider.gem_env).to receive(:install).with(gem_name) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end # this catches 'gem_package "foo"' when "./foo" is a file in the cwd, and instead of installing './foo' it fetches the remote gem it "installs the gem via the gems api, when the package has no file separator characters in it, but a matching file exists in cwd" do allow(::File).to receive(:exists?).and_return(true) new_resource.package_name("rspec-core") expect(provider.gem_env).to receive(:install).with(gem_dep, :sources => nil) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end context "when options are provided as a String" do let(:options) { "-i /alt/install/location" } it "installs the gem by shelling out when options are provided as a String" do expected = "gem install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" #{options}" expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end context "when another source and binary are provided" do let(:source) { "http://mirror.ops.rhcloud.com/mirror/ruby" } let(:gem_binary) { "/foo/bar" } it "installs the gem with rubygems.org as an added source" do expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --source=#{source} --source=https://rubygems.org" expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end context "when we have cleared sources and an explict source is specified" do let(:gem_binary) { "/foo/bar" } let(:source) { "http://mirror.ops.rhcloud.com/mirror/ruby" } it "installs the gem" do new_resource.clear_sources(true) expected = "#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\" --clear-sources --source=#{source}" expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end context "when no version is given" do let(:target_version) { nil } let(:options) { "-i /alt/install/location" } it "installs the gem by shelling out when options are provided but no version is given" do expected = "gem install rspec-core -q --no-rdoc --no-ri -v \"#{candidate_version}\" #{options}" expect(provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end context "when options are given as a Hash" do let(:options) { { :install_dir => "/alt/install/location" } } it "installs the gem via the gems api when options are given as a Hash" do expect(provider.gem_env).to receive(:install).with(gem_dep, { :sources => nil }.merge(options)) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end describe "at a specific version" do let(:target_version) { "9000.0.2" } it "installs the gem via the gems api" do expect(provider.gem_env).to receive(:install).with(gem_dep, :sources => nil) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end describe "at version specified with comparison operator" do context "if current version satisifies requested version" do let(:target_version) { ">=2.3.0" } let(:current_version) { "2.3.3" } it "skips the install" do expect(provider.gem_env).not_to receive(:install) provider.run_action(:install) end it "performs the upgrade" do expect(provider.gem_env).to receive(:install) provider.run_action(:upgrade) end end context "if the fuzzy operator is used" do let(:target_version) { "~>2.3.0" } let(:current_version) { "2.3.3" } it "it matches an existing gem" do expect(provider.gem_env).not_to receive(:install) provider.run_action(:install) end it "it upgrades an existing gem" do expect(provider.gem_env).to receive(:install) provider.run_action(:upgrade) end end end end describe "in an alternate gem environment" do let(:gem_binary) { "/usr/weird/bin/gem" } it "installs the gem by shelling out to gem install" do expect(provider).to receive(:shell_out!).with("#{gem_binary} install rspec-core -q --no-rdoc --no-ri -v \"#{target_version}\"", env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end context "when source is a path" do let(:source) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } let(:target_version) { ">= 0" } let(:domain) { " --local" } it "installs the gem by shelling out to gem install" do expect(provider).to receive(:shell_out!).with("#{gem_binary} install #{source} -q --no-rdoc --no-ri -v \"#{target_version}\"#{domain}", env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end context "when the package is a path and source is nil" do let(:gem_name) { CHEF_SPEC_DATA + "/gems/chef-integration-test-0.1.0.gem" } let(:target_version) { ">= 0" } let(:domain) { " --local" } it "installs the gem from file by shelling out to gem install when the package is a path and the source is nil" do expect(new_resource.source).to eq(gem_name) expect(provider).to receive(:shell_out!).with("#{gem_binary} install #{gem_name} -q --no-rdoc --no-ri -v \"#{target_version}\"#{domain}", env: nil, timeout: 900) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end end end end describe "when uninstalling a gem" do let(:gem_name) { "rspec" } let(:current_version) { "1.2.3" } let(:target_version) { nil } let(:current_resource) do current_resource = Chef::Resource::GemPackage.new(gem_name) current_resource.version(current_version) current_resource end describe "in the current gem environment" do it "uninstalls via the api when no explicit options are used" do # pre-reqs for action_remove to actually remove the package: expect(provider.new_resource.version).to be_nil expect(provider.current_resource.version).not_to be_nil # the behavior we're testing: expect(provider.gem_env).to receive(:uninstall).with("rspec", nil) provider.action_remove end context "when options are given as a Hash" do let(:options) { { :install_dir => "/alt/install/location" } } it "uninstalls via the api" do # pre-reqs for action_remove to actually remove the package: expect(provider.new_resource.version).to be_nil expect(provider.current_resource.version).not_to be_nil # the behavior we're testing: expect(provider.gem_env).to receive(:uninstall).with("rspec", nil, options) provider.action_remove end end context "when options are given as a String" do let(:options) { "-i /alt/install/location" } it "uninstalls via the gem command" do expect(provider).to receive(:shell_out!).with("gem uninstall rspec -q -x -I -a #{options}", env: nil, timeout: 900) provider.action_remove end end context "when a version is provided" do let(:target_version) { "1.2.3" } it "uninstalls a specific version of a gem" do expect(provider.gem_env).to receive(:uninstall).with("rspec", "1.2.3") provider.action_remove end end end describe "in an alternate gem environment" do let(:gem_binary) { "/usr/weird/bin/gem" } it "uninstalls via the gem command" do expect(provider).to receive(:shell_out!).with("#{gem_binary} uninstall rspec -q -x -I -a", env: nil, timeout: 900) provider.action_remove end end end end end chef-12.14.60/spec/unit/provider/package/smartos_spec.rb000066400000000000000000000113271276456504500230610ustar00rootroot00000000000000# # Author:: Trevor O (trevoro@joyent.com) # Author:: Yukihiko Sawanobori (sawanoboriyu@higanworks.com) # Copyright:: Copyright 2012-2016, Opscode # 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper")) require "ostruct" describe Chef::Provider::Package::SmartOS, "load_current_resource" do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("varnish") @current_resource = Chef::Resource::Package.new("varnish") @status = double("Status", :exitstatus => 0) @provider = Chef::Provider::Package::SmartOS.new(@new_resource, @run_context) allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource) @stdin = StringIO.new @stdout = "varnish-2.1.5nb2\n" @stderr = StringIO.new @pid = 10 @shell_out = OpenStruct.new(:stdout => @stdout, :stdin => @stdin, :stderr => @stderr, :status => @status, :exitstatus => 0) end describe "when loading current resource" do it "should create a current resource with the name of the new_resource" do expect(@provider).to receive(:shell_out!).and_return(@shell_out) expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should set the current resource package name" do expect(@provider).to receive(:shell_out!).and_return(@shell_out) expect(@current_resource).to receive(:package_name).with(@new_resource.package_name) @provider.load_current_resource end it "should set the installed version if it is installed" do expect(@provider).to receive(:shell_out!).and_return(@shell_out) @provider.load_current_resource expect(@current_resource.version).to eq("2.1.5nb2") end it "should set the installed version to nil if it's not installed" do out = OpenStruct.new(:stdout => nil) expect(@provider).to receive(:shell_out!).and_return(out) @provider.load_current_resource expect(@current_resource.version).to eq(nil) end end describe "candidate_version" do it "should return the candidate_version variable if already setup" do @provider.candidate_version = "2.1.1" expect(@provider).not_to receive(:shell_out!) @provider.candidate_version end it "should lookup the candidate_version if the variable is not already set (pkgin separated by spaces)" do search = double() expect(search).to receive(:each_line). and_yield("something-varnish-1.1.1 something varnish like\n"). and_yield("varnish-2.3.4 actual varnish\n") @shell_out = double("shell_out!", :stdout => search) expect(@provider).to receive(:shell_out!).with("/opt/local/bin/pkgin", "se", "varnish", :env => nil, :returns => [0, 1], :timeout => 900).and_return(@shell_out) expect(@provider.candidate_version).to eq("2.3.4") end it "should lookup the candidate_version if the variable is not already set (pkgin separated by semicolons)" do search = double() expect(search).to receive(:each_line). and_yield("something-varnish-1.1.1;;something varnish like\n"). and_yield("varnish-2.3.4;;actual varnish\n") @shell_out = double("shell_out!", :stdout => search) expect(@provider).to receive(:shell_out!).with("/opt/local/bin/pkgin", "se", "varnish", :env => nil, :returns => [0, 1], :timeout => 900).and_return(@shell_out) expect(@provider.candidate_version).to eq("2.3.4") end end describe "when manipulating a resource" do it "run pkgin and install the package" do out = OpenStruct.new(:stdout => nil) expect(@provider).to receive(:shell_out!).with("/opt/local/sbin/pkg_info", "-E", "varnish*", { :env => nil, :returns => [0, 1], :timeout => 900 }).and_return(@shell_out) expect(@provider).to receive(:shell_out!).with("/opt/local/bin/pkgin", "-y", "install", "varnish-2.1.5nb2", { :env => nil, :timeout => 900 }).and_return(out) @provider.load_current_resource @provider.install_package("varnish", "2.1.5nb2") end end end chef-12.14.60/spec/unit/provider/package/solaris_spec.rb000066400000000000000000000164021276456504500230440ustar00rootroot00000000000000# # Author:: Toomas Pelberg () # Copyright:: Copyright 2010-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::Solaris do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new("SUNWbash") @new_resource.source("/tmp/bash.pkg") @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context) allow(::File).to receive(:exists?).and_return(true) end describe "assessing the current package status" do before do @pkginfo = <<-PKGINFO PKGINST: SUNWbash NAME: GNU Bourne-Again shell (bash) CATEGORY: system ARCH: sparc VERSION: 11.10.0,REV=2005.01.08.05.16 BASEDIR: / VENDOR: Sun Microsystems, Inc. DESC: GNU Bourne-Again shell (bash) version 3.0 PSTAMP: sfw10-patch20070430084444 INSTDATE: Nov 04 2009 01:02 HOTLINE: Please contact your local service provider PKGINFO @status = double("Status", :stdout => "", :exitstatus => 0) end it "should create a current resource with the name of new_resource" do allow(@provider).to receive(:shell_out).and_return(@status) @provider.load_current_resource expect(@provider.current_resource.name).to eq("SUNWbash") end it "should set the current reource package name to the new resource package name" do allow(@provider).to receive(:shell_out).and_return(@status) @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("SUNWbash") end it "should raise an exception if a source is supplied but not found" do allow(@provider).to receive(:shell_out).and_return(@status) allow(::File).to receive(:exists?).and_return(false) @provider.load_current_resource @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Package) end it "should get the source package version from pkginfo if provided" do status = double(:stdout => @pkginfo, :exitstatus => 0) expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(status) expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(@status) @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("SUNWbash") expect(@new_resource.version).to eq("11.10.0,REV=2005.01.08.05.16") end it "should return the current version installed if found by pkginfo" do status = double(:stdout => @pkginfo, :exitstatus => 0) expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(@status) expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(status) @provider.load_current_resource expect(@provider.current_resource.version).to eq("11.10.0,REV=2005.01.08.05.16") end it "should raise an exception if the source is not set but we are installing" do @new_resource = Chef::Resource::Package.new("SUNWbash") @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context) allow(@provider).to receive(:shell_out).and_return(@status) expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end it "should raise an exception if pkginfo fails to run" do status = double(:stdout => "", :exitstatus => -1) allow(@provider).to receive(:shell_out).and_return(status) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package) end it "should return a current resource with a nil version if the package is not found" do expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(@status) expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(@status) @provider.load_current_resource expect(@provider.current_resource.version).to be_nil end end describe "candidate_version" do it "should return the candidate_version variable if already setup" do @provider.candidate_version = "11.10.0,REV=2005.01.08.05.16" expect(@provider).not_to receive(:shell_out) @provider.candidate_version end it "should lookup the candidate_version if the variable is not already set" do status = double(:stdout => "", :exitstatus => 0) allow(@provider).to receive(:shell_out).and_return(status) expect(@provider).to receive(:shell_out) @provider.candidate_version end it "should throw and exception if the exitstatus is not 0" do status = double(:stdout => "", :exitstatus => 1) allow(@provider).to receive(:shell_out).and_return(status) expect { @provider.candidate_version }.to raise_error(Chef::Exceptions::Package) end end describe "install and upgrade" do it "should run pkgadd -n -d with the package source to install" do expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all", { timeout: 900 }) @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end it "should run pkgadd -n -d when the package is a path to install" do @new_resource = Chef::Resource::Package.new("/tmp/bash.pkg") @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context) expect(@new_resource.source).to eq("/tmp/bash.pkg") expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all", { timeout: 900 }) @provider.install_package("/tmp/bash.pkg", "11.10.0,REV=2005.01.08.05.16") end it "should run pkgadd -n -a /tmp/myadmin -d with the package options -a /tmp/myadmin" do allow(@new_resource).to receive(:options).and_return("-a /tmp/myadmin") expect(@provider).to receive(:shell_out!).with("pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all", { timeout: 900 }) @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end end describe "remove" do it "should run pkgrm -n to remove the package" do expect(@provider).to receive(:shell_out!).with("pkgrm -n SUNWbash", { timeout: 900 }) @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end it "should run pkgrm -n -a /tmp/myadmin with options -a /tmp/myadmin" do allow(@new_resource).to receive(:options).and_return("-a /tmp/myadmin") expect(@provider).to receive(:shell_out!).with("pkgrm -n -a /tmp/myadmin SUNWbash", { timeout: 900 }) @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16") end end end chef-12.14.60/spec/unit/provider/package/windows/000077500000000000000000000000001276456504500215205ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/package/windows/exe_spec.rb000066400000000000000000000157711276456504500236530ustar00rootroot00000000000000# # Author:: Matt Wrock # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/provider/package/windows/exe" unless Chef::Platform.windows? class Chef module ReservedNames::Win32 class File def version_info nil end end end end end describe Chef::Provider::Package::Windows::Exe do let(:package_name) { "calculator" } let(:resource_source) { "calculator.exe" } let(:new_resource) do new_resource = Chef::Resource::WindowsPackage.new(package_name) new_resource.source(resource_source) new_resource end let(:uninstall_hash) do [{ "DisplayVersion" => "outdated", "UninstallString" => File.join("uninst_dir", "uninst_file"), }] end let(:uninstall_entry) do entries = [] uninstall_hash.each do |entry| entries.push(Chef::Provider::Package::Windows::RegistryUninstallEntry.new("hive", "key", entry)) end entries end let(:provider) { Chef::Provider::Package::Windows::Exe.new(new_resource, :nsis, uninstall_entry) } before(:each) do allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(true) end it "responds to shell_out!" do expect(provider).to respond_to(:shell_out!) end describe "expand_options" do it "returns an empty string if passed no options" do expect(provider.expand_options(nil)).to eql "" end it "returns a string with a leading space if passed options" do expect(provider.expand_options("--train nope --town no_way")).to eql(" --train nope --town no_way") end end describe "installed_version" do it "returns the installed version" do expect(provider.installed_version).to eql(["outdated"]) end context "no versions installed" do let(:uninstall_hash) { [] } it "returns the installed version" do expect(provider.installed_version).to eql(nil) end end end describe "package_version" do before { new_resource.version(nil) } context "source file does not exist" do before do allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(false) end it "returns nil" do expect(provider.package_version).to eql(nil) end end it "returns the version attribute if given" do new_resource.version("v55555") expect(provider.package_version).to eql("v55555") end it "returns nil if no version given" do expect(provider.package_version).to eql(nil) end end describe "remove_package" do before do allow(::File).to receive(:exist?).and_return(false) end context "no version given and one package installed with unquoted uninstall string" do it "removes installed package and quotes uninstall string" do allow(::File).to receive(:exist?).with("uninst_dir/uninst_file").and_return(true) expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"uninst_dir\/uninst_file\" \/S \/NCRC & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash)) provider.remove_package end end context "When timeout value is passed" do it "removes installed package and quotes uninstall string" do new_resource.timeout = 300 allow(::File).to receive(:exist?).with("uninst_dir/uninst_file").and_return(true) expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"uninst_dir\/uninst_file\" \/S \/NCRC & exit %%%%ERRORLEVEL%%%%/, :timeout => 300, :returns => [0]) provider.remove_package end end context "several packages installed with quoted uninstall strings" do let(:uninstall_hash) do [ { "DisplayVersion" => "v1", "UninstallString" => "\"#{File.join("uninst_dir1", "uninst_file1")}\"", }, { "DisplayVersion" => "v2", "UninstallString" => "\"#{File.join("uninst_dir2", "uninst_file2")}\"", }, ] end context "version given and installed" do it "removes given version" do new_resource.version("v2") expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"uninst_dir2\/uninst_file2\" \/S \/NCRC & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash)) provider.remove_package end end context "no version given" do it "removes both versions" do expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"uninst_dir1\/uninst_file1\" \/S \/NCRC & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash)) expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"uninst_dir2\/uninst_file2\" \/S \/NCRC & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash)) provider.remove_package end end end end context "installs nsis installer" do let(:provider) { Chef::Provider::Package::Windows::Exe.new(new_resource, :nsis, uninstall_entry) } it "calls installer with the correct flags" do expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"#{Regexp.quote(new_resource.source)}\" \/S \/NCRC & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash)) provider.install_package end end context "installs installshield installer" do let(:provider) { Chef::Provider::Package::Windows::Exe.new(new_resource, :installshield, uninstall_entry) } it "calls installer with the correct flags" do expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"#{Regexp.quote(new_resource.source)}\" \/s \/sms & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash)) provider.install_package end end context "installs inno installer" do let(:provider) { Chef::Provider::Package::Windows::Exe.new(new_resource, :inno, uninstall_entry) } it "calls installer with the correct flags" do expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"#{Regexp.quote(new_resource.source)}\" \/VERYSILENT \/SUPPRESSMSGBOXES \/NORESTART & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash)) provider.install_package end end context "installs wise installer" do let(:provider) { Chef::Provider::Package::Windows::Exe.new(new_resource, :wise, uninstall_entry) } it "calls installer with the correct flags" do expect(provider).to receive(:shell_out!).with(/start \"\" \/wait \"#{Regexp.quote(new_resource.source)}\" \/s & exit %%%%ERRORLEVEL%%%%/, kind_of(Hash)) provider.install_package end end end chef-12.14.60/spec/unit/provider/package/windows/msi_spec.rb000066400000000000000000000130261276456504500236510ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/provider/package/windows/msi" describe Chef::Provider::Package::Windows::MSI do let(:node) { double("Chef::Node") } let(:events) { double("Chef::Events").as_null_object } # mock all the methods let(:run_context) { double("Chef::RunContext", :node => node, :events => events) } let(:package_name) { "calculator" } let(:resource_source) { "calculator.msi" } let(:resource_version) { nil } let(:new_resource) do new_resource = Chef::Resource::WindowsPackage.new(package_name) new_resource.source(resource_source) new_resource.version(resource_version) new_resource end let(:uninstall_hash) do [{ "DisplayVersion" => "outdated", "UninstallString" => "MsiExec.exe /X{guid}", }] end let(:uninstall_entry) do entries = [] uninstall_hash.each do |entry| entries.push(Chef::Provider::Package::Windows::RegistryUninstallEntry.new("hive", "key", entry)) end entries end let(:provider) { Chef::Provider::Package::Windows::MSI.new(new_resource, uninstall_entry) } before do allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(true) end it "responds to shell_out!" do expect(provider).to respond_to(:shell_out!) end describe "expand_options" do it "returns an empty string if passed no options" do expect(provider.expand_options(nil)).to eql "" end it "returns a string with a leading space if passed options" do expect(provider.expand_options("--train nope --town no_way")).to eql(" --train nope --town no_way") end end describe "installed_version" do it "returns the installed version" do allow(provider).to receive(:get_product_property).and_return("{23170F69-40C1-2702-0920-000001000000}") allow(provider).to receive(:get_installed_version).with("{23170F69-40C1-2702-0920-000001000000}").and_return("3.14159.1337.42") expect(provider.installed_version).to eql("3.14159.1337.42") end it "returns the installed version in the registry when install file not present" do allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(false) expect(provider.installed_version).to eql(["outdated"]) end end describe "package_version" do it "returns the version of a package" do allow(provider).to receive(:get_product_property).with(/calculator.msi$/, "ProductVersion").and_return(42) expect(provider.package_version).to eql(42) end context "version is explicitly provided" do let(:resource_version) { "given_version" } it "returns the given version" do expect(provider.package_version).to eql("given_version") end end context "no source or version is given" do before do allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(false) end it "returns nil" do expect(provider.package_version).to eql(nil) end end end describe "install_package" do it "calls msiexec /qn /i" do expect(provider).to receive(:shell_out!).with(/msiexec \/qn \/i \"#{Regexp.quote(new_resource.source)}\"/, kind_of(Hash)) provider.install_package end end describe "remove_package" do it "calls msiexec /qn /x" do expect(provider).to receive(:shell_out!).with(/msiexec \/qn \/x \"#{Regexp.quote(new_resource.source)}\"/, kind_of(Hash)) provider.remove_package end context "no source is provided" do before do allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(false) end it "removes installed package" do expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid} \/Q/, kind_of(Hash)) provider.remove_package end context "there are multiple installs" do let(:uninstall_hash) do [ { "DisplayVersion" => "outdated", "UninstallString" => "MsiExec.exe /X{guid}", }, { "DisplayVersion" => "really_outdated", "UninstallString" => "MsiExec.exe /X{guid2}", }, ] end it "removes both installed package" do expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid} \/Q/, kind_of(Hash)) expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid2} \/Q/, kind_of(Hash)) provider.remove_package end end context "custom options includes /Q" do before { new_resource.options("/Q") } it "does not duplicate quiet switch" do expect(provider).to receive(:shell_out!).with(/MsiExec.exe \/X{guid} \/Q/, kind_of(Hash)) provider.remove_package end end end end end chef-12.14.60/spec/unit/provider/package/windows_spec.rb000066400000000000000000000327131276456504500230650ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/provider/package/windows/exe" require "chef/provider/package/windows/msi" describe Chef::Provider::Package::Windows, :windows_only do before(:each) do allow(Chef::Util::PathHelper).to receive(:windows?).and_return(true) allow(Chef::FileCache).to receive(:create_cache_path).with("package/").and_return(cache_path) end let(:node) { double("Chef::Node") } let(:events) { double("Chef::Events").as_null_object } # mock all the methods let(:run_context) { double("Chef::RunContext", :node => node, :events => events) } let(:resource_source) { "calculator.msi" } let(:resource_name) { "calculator" } let(:installer_type) { nil } let(:new_resource) do new_resource = Chef::Resource::WindowsPackage.new(resource_name) new_resource.source(resource_source) if resource_source new_resource.installer_type(installer_type) if installer_type new_resource end let(:provider) { Chef::Provider::Package::Windows.new(new_resource, run_context) } let(:cache_path) { 'c:\\cache\\' } before(:each) do allow(::File).to receive(:exist?).with(provider.new_resource.source).and_return(true) end describe "load_current_resource" do shared_examples "a local file" do before(:each) do allow(Chef::Util::PathHelper).to receive(:validate_path) allow(provider).to receive(:package_provider).and_return(double("package_provider", :installed_version => "1.0", :package_version => "2.0")) end it "creates a current resource with the name of the new resource" do provider.load_current_resource expect(provider.current_resource).to be_a(Chef::Resource::WindowsPackage) expect(provider.current_resource.name).to eql(resource_name) end it "sets the current version if the package is installed" do provider.load_current_resource expect(provider.current_resource.version).to eql("1.0") end it "sets the version to be installed" do provider.load_current_resource expect(provider.new_resource.version).to eql("2.0") end end context "when the source is a uri" do let(:resource_source) { "https://foo.bar/calculator.msi" } context "when the source has not been downloaded" do before(:each) do allow(provider).to receive(:downloadable_file_missing?).and_return(true) end it "sets the current version to unknown" do provider.load_current_resource expect(provider.current_resource.version).to eql("unknown") end end context "when the source has been downloaded" do before(:each) do allow(provider).to receive(:downloadable_file_missing?).and_return(false) end it_behaves_like "a local file" end end context "when source is a local file" do it_behaves_like "a local file" end end describe "package_provider" do shared_examples "a local file" do it "checks that the source path is valid" do expect(Chef::Util::PathHelper).to receive(:validate_path).and_call_original provider.package_provider end it "sets the package provider to MSI if the the installer type is :msi" do allow(provider).to receive(:installer_type).and_return(:msi) expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::MSI) end it "sets the package provider to Exe if the the installer type is :inno" do allow(provider).to receive(:installer_type).and_return(:inno) expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::Exe) end it "sets the package provider to Exe if the the installer type is :nsis" do allow(provider).to receive(:installer_type).and_return(:nsis) expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::Exe) end it "sets the package provider to Exe if the the installer type is :wise" do allow(provider).to receive(:installer_type).and_return(:wise) expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::Exe) end it "sets the package provider to Exe if the the installer type is :installshield" do allow(provider).to receive(:installer_type).and_return(:installshield) expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::Exe) end it "defaults to exe if the installer_type is unknown" do allow(provider).to receive(:installer_type).and_return(nil) expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::Exe) end end context "when the source is a uri" do let(:resource_source) { "https://foo.bar/calculator.msi" } context "when the source has not been downloaded" do before(:each) do allow(provider).to receive(:should_download?).and_return(true) end it "should create a package provider with source pointing at the local file" do expect(Chef::Provider::Package::Windows::MSI).to receive(:new) do |r| expect(r.source).to eq("#{cache_path}#{::File.basename(resource_source)}") end provider.package_provider end it_behaves_like "a local file" end context "when the source has been downloaded" do before(:each) do allow(provider).to receive(:should_download?).and_return(false) end it_behaves_like "a local file" end end context "when source is a local file" do it_behaves_like "a local file" end end describe "installer_type" do let(:resource_source) { "microsoft_installer.exe" } context "there is no source" do let(:uninstall_hash) do [{ "DisplayVersion" => "outdated", "UninstallString" => "blah blah", }] end let(:uninstall_key) { "blah" } let(:uninstall_entry) do entries = [] uninstall_hash.each do |entry| entries.push(Chef::Provider::Package::Windows::RegistryUninstallEntry.new("hive", uninstall_key, entry)) end entries end before do allow(Chef::Provider::Package::Windows::RegistryUninstallEntry).to receive(:find_entries).and_return(uninstall_entry) allow(::File).to receive(:exist?).with(Chef::Util::PathHelper.canonical_path(resource_source, false)).and_return(false) end context "uninstall string contains MsiExec.exe" do let(:uninstall_hash) do [{ "DisplayVersion" => "outdated", "UninstallString" => "MsiExec.exe /X{guid}", }] end it "sets installer_type to MSI" do expect(provider.installer_type).to eql(:msi) end end context "uninstall string ends with uninst.exe" do let(:uninstall_hash) do [{ "DisplayVersion" => "outdated", "UninstallString" => %q{"c:/hfhfheru/uninst.exe"}, }] end it "sets installer_type to NSIS" do expect(provider.installer_type).to eql(:nsis) end end context "uninstall key ends in _is1" do let(:uninstall_key) { "blah_is1" } it "sets installer_type to inno" do expect(provider.installer_type).to eql(:inno) end end context "eninstall entries is empty" do before { allow(Chef::Provider::Package::Windows::RegistryUninstallEntry).to receive(:find_entries).and_return([]) } it "returns nil" do expect(provider.installer_type).to eql(nil) end end end it "returns @installer_type if it is set" do provider.new_resource.installer_type(:downeaster) expect(provider.installer_type).to eql(:downeaster) end it "sets installer_type to inno if the source contains inno" do allow(::Kernel).to receive(:open).and_yield(StringIO.new("blah blah inno blah")) expect(provider.installer_type).to eql(:inno) end it "sets installer_type to wise if the source contains wise" do allow(::Kernel).to receive(:open).and_yield(StringIO.new("blah blah wise blah")) expect(provider.installer_type).to eql(:wise) end it "sets installer_type to nsis if the source contains nsis" do allow(::Kernel).to receive(:open).and_yield(StringIO.new("blah blah nullsoft blah")) expect(provider.installer_type).to eql(:nsis) end context "source ends in .msi" do let(:resource_source) { "microsoft_installer.msi" } it "sets installer_type to msi" do expect(provider.installer_type).to eql(:msi) end end context "the source is setup.exe" do let(:resource_source) { "setup.exe" } it "sets installer_type to installshield" do allow(::Kernel).to receive(:open).and_yield(StringIO.new("")) expect(provider.installer_type).to eql(:installshield) end end context "cannot determine the installer type" do let(:resource_source) { "tomfoolery.now" } it "raises an error" do allow(::Kernel).to receive(:open).and_yield(StringIO.new("")) provider.new_resource.installer_type(nil) expect { provider.installer_type }.to raise_error(Chef::Exceptions::CannotDetermineWindowsInstallerType) end end end describe "action_install" do let(:resource_name) { "blah" } let(:resource_source) { "blah.exe" } let(:installer_type) { :inno } before do allow_any_instance_of(Chef::Provider::Package::Windows::Exe).to receive(:package_version).and_return(new_resource.version) end context "no source given" do let(:resource_source) { nil } it "raises a NoWindowsPackageSource error" do expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::NoWindowsPackageSource) end context "msi installer_type" do let(:installer_type) { :msi } it "does not raise a NoWindowsPackageSource error" do expect(provider).to receive(:install_package) provider.run_action(:install) end end end context "http source given and no type given explicitly" do let(:installer_type) { nil } let(:resource_source) { "https://foo.bar/calculator.exe" } it "downloads the http resource" do expect(provider).to receive(:download_source_file) provider.run_action(:install) end end context "no version given, discovered or installed" do it "installs latest" do expect(provider).to receive(:install_package).with("blah", "latest") provider.run_action(:install) end end context "no version given or discovered but package is installed" do before { allow(provider).to receive(:current_version_array).and_return(["5.5.5"]) } it "does not install" do expect(provider).not_to receive(:install_package) provider.run_action(:install) end end context "a version is given and none is installed" do before { new_resource.version("5.5.5") } it "installs given version" do expect(provider).to receive(:install_package).with("blah", "5.5.5") provider.run_action(:install) end end context "a version is given and several are installed" do context "given version matches an installed version" do before do new_resource.version("5.5.5") allow(provider).to receive(:current_version_array).and_return([ ["5.5.5", "4.3.0", "1.1.1"] ]) end it "does not install" do expect(provider).not_to receive(:install_package) provider.run_action(:install) end end context "given version does not match an installed version" do before do new_resource.version("5.5.5") allow(provider).to receive(:current_version_array).and_return([ ["5.5.0", "4.3.0", "1.1.1"] ]) end it "installs given version" do expect(provider).to receive(:install_package).with("blah", "5.5.5") provider.run_action(:install) end end end context "a version is given and one is installed" do context "given version matches installed version" do before do new_resource.version("5.5.5") allow(provider).to receive(:current_version_array).and_return(["5.5.5"]) end it "does not install" do expect(provider).not_to receive(:install_package) provider.run_action(:install) end end context "given version does not match installed version" do before do new_resource.version("5.5.5") allow(provider).to receive(:current_version_array).and_return(["5.5.0"]) end it "installs given version" do expect(provider).to receive(:install_package).with("blah", "5.5.5") provider.run_action(:install) end end end end end chef-12.14.60/spec/unit/provider/package/yum/000077500000000000000000000000001276456504500206405ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/package/yum/yum_cache_spec.rb000066400000000000000000000016041276456504500241350ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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 chef-12.14.60/spec/unit/provider/package/yum_spec.rb000066400000000000000000002711761276456504500222150ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "securerandom" describe Chef::Provider::Package::Yum do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::YumPackage.new("cups") @status = double("Status", :exitstatus => 0) @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.2.4-11.18.el5", :candidate_version => "1.2.4-11.18.el5_2.3", :package_available? => true, :version_available? => true, :allow_multi_install => [ "kernel" ], :package_repository => "base", :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @pid = double("PID") end describe "when loading the current system state" do it "should create a current resource with the name of the new_resource" do @provider.load_current_resource expect(@provider.current_resource.name).to eq("cups") end it "should set the current resources package name to the new resources package name" do @provider.load_current_resource expect(@provider.current_resource.package_name).to eq("cups") end it "should set the installed version to nil on the current resource if no installed package" do allow(@yum_cache).to receive(:installed_version).and_return(nil) @provider.load_current_resource expect(@provider.current_resource.version).to be_nil end it "should set the installed version if yum has one" do @provider.load_current_resource expect(@provider.current_resource.version).to eq("1.2.4-11.18.el5") end it "should set the candidate version if yum info has one" do @provider.load_current_resource expect(@provider.candidate_version).to eql("1.2.4-11.18.el5_2.3") end it "should return the current resouce" do expect(@provider.load_current_resource).to eql(@provider.current_resource) end describe "when source is provided" do it "should set the candidate version" do @new_resource = Chef::Resource::YumPackage.new("testing.source") @new_resource.source "chef-server-core-12.0.5-1.rpm" @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) allow(File).to receive(:exists?).with(@new_resource.source).and_return(true) allow(@yum_cache).to receive(:installed_version).and_return(nil) shellout_double = double(:stdout => "chef-server-core 12.0.5-1") allow(@provider).to receive(:shell_out!).and_return(shellout_double) @provider.load_current_resource expect(@provider.candidate_version).to eql("12.0.5-1") end end describe "yum_binary accessor" do it "when yum-deprecated exists" do expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(true) expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) expect(@provider.yum_binary).to eql("yum-deprecated") end it "when yum-deprecated does not exist" do expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(false) expect(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) expect(@provider.yum_binary).to eql("yum") end it "when the yum_binary is set on the resource" do @new_resource.yum_binary "/usr/bin/yum-something" expect(File).not_to receive(:exist?) expect(@yum_cache).to receive(:yum_binary=).with("/usr/bin/yum-something") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) expect(@provider.yum_binary).to eql("/usr/bin/yum-something") end it "when the new_resource is a vanilla package class and yum-deprecated exists" do @new_resource = Chef::Resource::Package.new("cups") expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(true) expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) expect(@provider.yum_binary).to eql("yum-deprecated") end it "when the new_resource is a vanilla package class and yum-deprecated does not exist" do @new_resource = Chef::Resource::Package.new("cups") expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(false) expect(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) expect(@provider.yum_binary).to eql("yum") end end describe "when arch in package_name" do it "should set the arch if no existing package_name is found and new_package_name+new_arch is available" do @new_resource = Chef::Resource::YumPackage.new("testing.noarch") @yum_cache = double( "Chef::Provider::Yum::YumCache" ) allow(@yum_cache).to receive(:installed_version) do |package_name, arch| # nothing installed for package_name/new_package_name nil end allow(@yum_cache).to receive(:candidate_version) do |package_name, arch| if package_name == "testing.noarch" || package_name == "testing.more.noarch" nil # candidate for new_package_name elsif package_name == "testing" || package_name == "testing.more" "1.1" end end allow(@yum_cache).to receive(:package_available?).and_return(true) allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@provider.new_resource.package_name).to eq("testing") expect(@provider.new_resource.arch).to eq("noarch") expect(@provider.arch).to eq("noarch") @new_resource = Chef::Resource::YumPackage.new("testing.more.noarch") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@provider.new_resource.package_name).to eq("testing.more") expect(@provider.new_resource.arch).to eq("noarch") expect(@provider.arch).to eq("noarch") end describe "when version constraint in package_name" do it "should set package_version if no existing package_name is found and new_package_name is available" do @new_resource = Chef::Resource::Package.new("cups = 1.2.4-11.18.el5_2.3") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) allow(@yum_cache).to receive(:package_available?) { |pkg| pkg == "cups" ? true : false } allow(@yum_cache).to receive(:packages_from_require) do |pkg| [Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.3", "noarch", [], false, true, "base"), Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.2", "noarch", [], false, true, "base")] end expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{checking yum info}) expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{installed version}) expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{matched 2 packages,}) @provider.load_current_resource expect(@provider.new_resource.package_name).to eq("cups") expect(@provider.new_resource.version).to eq("1.2.4-11.18.el5_2.3") expect(@provider.send(:new_version_array)).to eq(["1.2.4-11.18.el5_2.3"]) expect(@provider.send(:package_name_array)).to eq(["cups"]) end end it "should not set the arch when an existing package_name is found" do @new_resource = Chef::Resource::YumPackage.new("testing.beta3") @yum_cache = double( "Chef::Provider::Yum::YumCache" ) allow(@yum_cache).to receive(:installed_version) do |package_name, arch| # installed for package_name if package_name == "testing.beta3" || package_name == "testing.beta3.more" "1.1" elsif package_name == "testing" || package_name == "testing.beta3" nil end end allow(@yum_cache).to receive(:candidate_version) do |package_name, arch| # no candidate for package_name/new_package_name nil end allow(@yum_cache).to receive(:package_available?).and_return(true) allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) # annoying side effect of the fun stub'ing above @provider.load_current_resource expect(@provider.new_resource.package_name).to eq("testing.beta3") expect(@provider.new_resource.arch).to eq(nil) expect(@provider.arch).to eq(nil) @new_resource = Chef::Resource::YumPackage.new("testing.beta3.more") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@provider.new_resource.package_name).to eq("testing.beta3.more") expect(@provider.new_resource.arch).to eq(nil) expect(@provider.arch).to eq(nil) end it "should not set the arch when no existing package_name or new_package_name+new_arch is found" do @new_resource = Chef::Resource::YumPackage.new("testing.beta3") @yum_cache = double( "Chef::Provider::Yum::YumCache" ) allow(@yum_cache).to receive(:installed_version) do |package_name, arch| # nothing installed for package_name/new_package_name nil end allow(@yum_cache).to receive(:candidate_version) do |package_name, arch| # no candidate for package_name/new_package_name nil end allow(@yum_cache).to receive(:package_available?).and_return(true) allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@provider.new_resource.package_name).to eq("testing.beta3") expect(@provider.new_resource.arch).to eq(nil) expect(@provider.arch).to eq(nil) @new_resource = Chef::Resource::YumPackage.new("testing.beta3.more") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@provider.new_resource.package_name).to eq("testing.beta3.more") expect(@provider.new_resource.arch).to eq(nil) expect(@provider.arch).to eq(nil) end it "should ensure it doesn't clobber an existing arch if passed" do @new_resource = Chef::Resource::YumPackage.new("testing.i386") @new_resource.arch("x86_64") @yum_cache = double( "Chef::Provider::Yum::YumCache" ) allow(@yum_cache).to receive(:installed_version) do |package_name, arch| # nothing installed for package_name/new_package_name nil end allow(@yum_cache).to receive(:candidate_version) do |package_name, arch| if package_name == "testing.noarch" nil # candidate for new_package_name elsif package_name == "testing" "1.1" end end.and_return("something") allow(@yum_cache).to receive(:package_available?).and_return(true) allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@provider.new_resource.package_name).to eq("testing.i386") expect(@provider.new_resource.arch).to eq("x86_64") end end it "should flush the cache if :before is true" do allow(@new_resource).to receive(:flush_cache).and_return({ :after => false, :before => true }) expect(@yum_cache).to receive(:reload).once @provider.load_current_resource end it "should flush the cache if :before is false" do allow(@new_resource).to receive(:flush_cache).and_return({ :after => false, :before => false }) expect(@yum_cache).not_to receive(:reload) @provider.load_current_resource end it "should detect --enablerepo or --disablerepo when passed among options, collect them preserving order and notify the yum cache" do allow(@new_resource).to receive(:options).and_return("--stuff --enablerepo=foo --otherthings --disablerepo=a,b,c --enablerepo=bar") expect(@yum_cache).to receive(:enable_extra_repo_control).with("--enablerepo=foo --disablerepo=a,b,c --enablerepo=bar") @provider.load_current_resource end it "should let the yum cache know extra repos are disabled if --enablerepo or --disablerepo aren't among options" do allow(@new_resource).to receive(:options).and_return("--stuff --otherthings") expect(@yum_cache).to receive(:disable_extra_repo_control) @provider.load_current_resource end it "should let the yum cache know extra repos are disabled if options aren't set" do allow(@new_resource).to receive(:options).and_return(nil) expect(@yum_cache).to receive(:disable_extra_repo_control) @provider.load_current_resource end context "when the package name isn't found" do let(:yum_cache) do double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.0.1.el5", :candidate_version => "2.0.1.el5", :package_available? => false, :version_available? => true, :disable_extra_repo_control => true ) end before do allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(yum_cache) allow(yum_cache).to receive(:yum_binary=).with("yum") @pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "2.0.1.el5", "x86_64", []) expect(yum_cache).to receive(:packages_from_require).and_return([@pkg]) end it "should search provides then set package_name to match" do @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.package_name).to eq("test-package") expect(@new_resource.version).to eq(nil) end it "should search provides then set version to match if a requirement was passed in the package name" do @new_resource = Chef::Resource::YumPackage.new("test-package = 2.0.1.el5") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.package_name).to eq("test-package") expect(@new_resource.version).to eq("2.0.1.el5") end it "should search provides then set version to match if a requirement was passed in the version" do @new_resource = Chef::Resource::YumPackage.new("test-package") @new_resource.version("= 2.0.1.el5") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.package_name).to eq("test-package") expect(@new_resource.version).to eq("2.0.1.el5") end it "should search provides and not set the version to match if a specific version was requested" do @new_resource = Chef::Resource::YumPackage.new("test-package") @new_resource.version("3.0.1.el5") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.package_name).to eq("test-package") expect(@new_resource.version).to eq("3.0.1.el5") end it "should search provides then set versions to match if requirements were passed in the package name as an array" do @new_resource = Chef::Resource::YumPackage.new(["test-package = 2.0.1.el5"]) @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.package_name).to eq(["test-package"]) expect(@new_resource.version).to eq(["2.0.1.el5"]) end it "should search provides and not set the versions to match if specific versions were requested in an array" do @new_resource = Chef::Resource::YumPackage.new(["test-package"]) @new_resource.version(["3.0.1.el5"]) @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.package_name).to eq(["test-package"]) expect(@new_resource.version).to eq(["3.0.1.el5"]) end end it "should not return an error if no version number is specified in the resource" do @new_resource = Chef::Resource::YumPackage.new("test-package") @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.0.1.el5", :candidate_version => "2.0.1.el5", :package_available? => false, :version_available? => true, :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "2.0.1.el5", "x86_64", []) expect(@yum_cache).to receive(:packages_from_require).and_return([pkg]) @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.package_name).to eq("test-package") expect(@new_resource.version).to eq(nil) end it "should give precedence to the version attribute when both a requirement in the resource name and a version attribute are specified" do @new_resource = Chef::Resource::YumPackage.new("test-package") @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.2.4-11.18.el5", :candidate_version => "1.2.4-11.18.el5", :package_available? => false, :version_available? => true, :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "2.0.1.el5", "x86_64", []) expect(@yum_cache).to receive(:packages_from_require).and_return([pkg]) @new_resource = Chef::Resource::YumPackage.new("test-package = 2.0.1.el5") @new_resource.version("3.0.1.el5") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.package_name).to eq("test-package") expect(@new_resource.version).to eq("3.0.1.el5") end it "should correctly detect the installed states of an array of package names and version numbers" do @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.0.1.el5", :candidate_version => "2.0.1.el5", :package_available? => false, :version_available? => true, :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") expect(@yum_cache).to receive(:packages_from_require).exactly(4).times.and_return([]) expect(@yum_cache).to receive(:reload_provides).twice @new_resource = Chef::Resource::YumPackage.new(["test-package", "test-package2"]) @new_resource.version(["2.0.1.el5", "3.0.1.el5"]) @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.package_name).to eq(["test-package", "test-package2"]) expect(@new_resource.version).to eq(["2.0.1.el5", "3.0.1.el5"]) end it "should search provides if no package is available - if no match in installed provides then load the complete set" do @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.2.4-11.18.el5", :candidate_version => "1.2.4-11.18.el5", :package_available? => false, :version_available? => true, :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") expect(@yum_cache).to receive(:packages_from_require).twice.and_return([]) expect(@yum_cache).to receive(:reload_provides) @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.version).to eq(nil) end it "should search provides if no package is available and not load the complete set if action is :remove or :purge" do @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.2.4-11.18.el5", :candidate_version => "1.2.4-11.18.el5", :package_available? => false, :version_available? => true, :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) expect(@yum_cache).to receive(:packages_from_require).once.and_return([]) expect(@yum_cache).not_to receive(:reload_provides) @new_resource.action(:remove) @provider.load_current_resource expect(@yum_cache).to receive(:packages_from_require).once.and_return([]) expect(@yum_cache).not_to receive(:reload_provides) @new_resource.action(:purge) @provider.load_current_resource end it "should search provides if no package is available - if no match in provides leave the name intact" do @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_provides => true, :reload_installed => true, :reset => true, :installed_version => "1.2.4-11.18.el5", :candidate_version => "1.2.4-11.18.el5", :package_available? => false, :version_available? => true, :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") expect(@yum_cache).to receive(:packages_from_require).twice.and_return([]) @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@new_resource.package_name).to eq("cups") end end describe "when installing a package" do it "should run yum install with the package name and version" do @provider.load_current_resource allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y install cups-1.2.4-11.19.el5" ) @provider.install_package("cups", "1.2.4-11.19.el5") end it "should run yum localinstall if given a path to an rpm" do allow(@new_resource).to receive(:source).and_return("/tmp/emacs-21.4-20.el5.i386.rpm") expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm" ) @provider.install_package("emacs", "21.4-20.el5") end it "should run yum localinstall if given a path to an rpm as the package" do @new_resource = Chef::Resource::Package.new("/tmp/emacs-21.4-20.el5.i386.rpm") allow(::File).to receive(:exists?).and_return(true) @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) expect(@new_resource.source).to eq("/tmp/emacs-21.4-20.el5.i386.rpm") expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm" ) @provider.install_package("/tmp/emacs-21.4-20.el5.i386.rpm", "21.4-20.el5") end it "should run yum install with the package name, version and arch" do @provider.load_current_resource allow(@new_resource).to receive(:arch).and_return("i386") allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y install cups-1.2.4-11.19.el5.i386" ) @provider.install_package("cups", "1.2.4-11.19.el5") end it "installs the package with the options given in the resource" do @provider.load_current_resource allow(@provider).to receive(:candidate_version).and_return("11") allow(@new_resource).to receive(:options).and_return("--disablerepo epmd") allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y --disablerepo epmd install cups-11" ) @provider.install_package(@new_resource.name, @provider.candidate_version) end it "should raise an exception if the package is not available" do @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_from_cache => true, :reset => true, :installed_version => "1.2.4-11.18.el5", :candidate_version => "1.2.4-11.18.el5_2.3", :package_available? => true, :version_available? => nil, :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) expect { @provider.install_package("lolcats", "0.99") }.to raise_error(Chef::Exceptions::Package, %r{Version .* not found}) end it "should raise an exception if candidate version is older than the installed version and allow_downgrade is false" do allow(@new_resource).to receive(:allow_downgrade).and_return(false) @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.2.4-11.18.el5", :candidate_version => "1.2.4-11.15.el5", :package_available? => true, :version_available? => true, :allow_multi_install => [ "kernel" ], :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect { @provider.install_package("cups", "1.2.4-11.15.el5") }.to raise_error(Chef::Exceptions::Package, %r{is newer than candidate package}) end it "should not raise an exception if candidate version is older than the installed version and the package is list in yum's installonlypkg option" do @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.2.4-11.18.el5", :candidate_version => "1.2.4-11.15.el5", :package_available? => true, :version_available? => true, :allow_multi_install => [ "cups" ], :package_repository => "base", :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y install cups-1.2.4-11.15.el5" ) @provider.install_package("cups", "1.2.4-11.15.el5") end it "should run yum downgrade if candidate version is older than the installed version and allow_downgrade is true" do allow(@new_resource).to receive(:allow_downgrade).and_return(true) @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.2.4-11.18.el5", :candidate_version => "1.2.4-11.15.el5", :package_available? => true, :version_available? => true, :allow_multi_install => [], :package_repository => "base", :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y downgrade cups-1.2.4-11.15.el5" ) @provider.install_package("cups", "1.2.4-11.15.el5") end it "should run yum install then flush the cache if :after is true" do allow(@new_resource).to receive(:flush_cache).and_return({ :after => true, :before => false }) @provider.load_current_resource allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y install cups-1.2.4-11.15.el5" ) expect(@yum_cache).to receive(:reload).once @provider.install_package("cups", "1.2.4-11.15.el5") end it "should run yum install then not flush the cache if :after is false" do allow(@new_resource).to receive(:flush_cache).and_return({ :after => false, :before => false }) @provider.load_current_resource allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y install cups-1.2.4-11.15.el5" ) expect(@yum_cache).not_to receive(:reload) @provider.install_package("cups", "1.2.4-11.15.el5") end end describe "when upgrading a package" do it "should run yum install if the package is installed and a version is given" do @provider.load_current_resource allow(@provider).to receive(:candidate_version).and_return("11") allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y install cups-11" ) @provider.upgrade_package(@new_resource.name, @provider.candidate_version) end it "should run yum install if the package is not installed" do @provider.load_current_resource @current_resource = Chef::Resource::Package.new("cups") allow(@provider).to receive(:candidate_version).and_return("11") allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y install cups-11" ) @provider.upgrade_package(@new_resource.name, @provider.candidate_version) end it "should raise an exception if candidate version is older than the installed version" do @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "1.2.4-11.18.el5", :candidate_version => "1.2.4-11.15.el5", :package_available? => true, :version_available? => true, :allow_multi_install => [ "kernel" ], :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @provider.load_current_resource expect { @provider.upgrade_package("cups", "1.2.4-11.15.el5") }.to raise_error(Chef::Exceptions::Package, %r{is newer than candidate package}) end # Test our little workaround, some crossover into Chef::Provider::Package territory it "should call action_upgrade in the parent if the current resource version is nil" do allow(@yum_cache).to receive(:installed_version).and_return(nil) @current_resource = Chef::Resource::Package.new("cups") allow(@provider).to receive(:candidate_version).and_return("11") expect(@provider).to receive(:upgrade_package).with( "cups", "11" ) @provider.run_action(:upgrade) end it "should call action_upgrade in the parent if the candidate version is nil" do @provider.load_current_resource @current_resource = Chef::Resource::Package.new("cups") allow(@provider).to receive(:candidate_version).and_return(nil) expect(@provider).not_to receive(:upgrade_package) @provider.run_action(:upgrade) end it "should call action_upgrade in the parent if the candidate is newer" do @provider.load_current_resource @current_resource = Chef::Resource::Package.new("cups") allow(@provider).to receive(:candidate_version).and_return("11") expect(@provider).to receive(:upgrade_package).with( "cups", "11" ) @provider.run_action(:upgrade) end it "should not call action_upgrade in the parent if the candidate is older" do allow(@yum_cache).to receive(:installed_version).and_return("12") @provider.load_current_resource @current_resource = Chef::Resource::Package.new("cups") allow(@provider).to receive(:candidate_version).and_return("11") expect(@provider).not_to receive(:upgrade_package) @provider.run_action(:upgrade) end end describe "when removing a package" do it "should run yum remove with the package name" do expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y remove emacs-1.0" ) @provider.remove_package("emacs", "1.0") end it "should run yum remove with the package name and arch" do allow(@new_resource).to receive(:arch).and_return("x86_64") expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y remove emacs-1.0.x86_64" ) @provider.remove_package("emacs", "1.0") end end describe "when purging a package" do it "should run yum remove with the package name" do expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y remove emacs-1.0" ) @provider.purge_package("emacs", "1.0") end end describe "when running yum" do it "should run yum once if it exits with a return code of 0" do @status = double("Status", :exitstatus => 0, :stdout => "", :stderr => "") allow(@provider).to receive(:shell_out).and_return(@status) expect(@provider).to receive(:shell_out).once.with( "yum -d0 -e0 -y install emacs-1.0", { :timeout => Chef::Config[:yum_timeout] } ) @provider.yum_command("-d0 -e0 -y install emacs-1.0") end it "should run yum once if it exits with a return code > 0 and no scriptlet failures" do @status = double("Status", :exitstatus => 2, :stdout => "failure failure", :stderr => "problem problem") allow(@provider).to receive(:shell_out).and_return(@status) expect(@provider).to receive(:shell_out).once.with( "yum -d0 -e0 -y install emacs-1.0", { :timeout => Chef::Config[:yum_timeout] } ) expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec) end it "should run yum once if it exits with a return code of 1 and %pre scriptlet failures" do @status = double("Status", :exitstatus => 1, :stdout => "error: %pre(demo-1-1.el5.centos.x86_64) scriptlet failed, exit status 2", :stderr => "") allow(@provider).to receive(:shell_out).and_return(@status) expect(@provider).to receive(:shell_out).once.with( "yum -d0 -e0 -y install emacs-1.0", { :timeout => Chef::Config[:yum_timeout] } ) # will still raise an exception, can't stub out the subsequent call expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec) end it "should run yum twice if it exits with a return code of 1 and %post scriptlet failures" do @status = double("Status", :exitstatus => 1, :stdout => "error: %post(demo-1-1.el5.centos.x86_64) scriptlet failed, exit status 2", :stderr => "") allow(@provider).to receive(:shell_out).and_return(@status) expect(@provider).to receive(:shell_out).twice.with( "yum -d0 -e0 -y install emacs-1.0", { :timeout => Chef::Config[:yum_timeout] } ) # will still raise an exception, can't stub out the subsequent call expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec) end it "should pass the yum_binary to the command if its specified" do @new_resource.yum_binary "yum-deprecated" expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @status = double("Status", :exitstatus => 0, :stdout => "", :stderr => "") allow(@provider).to receive(:shell_out).and_return(@status) expect(@provider).to receive(:shell_out).once.with( "yum-deprecated -d0 -e0 -y install emacs-1.0", { :timeout => Chef::Config[:yum_timeout] } ) @provider.yum_command("-d0 -e0 -y install emacs-1.0") end end end describe Chef::Provider::Package::Yum::RPMUtils do describe "version_parse" do before do @rpmutils = Chef::Provider::Package::Yum::RPMUtils end it "parses known good epoch strings" do [ [ "0:3.3", [ 0, "3.3", nil ] ], [ "9:1.7.3", [ 9, "1.7.3", nil ] ], [ "15:20020927", [ 15, "20020927", nil ] ], ].each do |x, y| expect(@rpmutils.version_parse(x)).to eq(y) end end it "parses strange epoch strings" do [ [ ":3.3", [ 0, "3.3", nil ] ], [ "-1:1.7.3", [ nil, nil, "1:1.7.3" ] ], [ "-:20020927", [ nil, nil, ":20020927" ] ], ].each do |x, y| expect(@rpmutils.version_parse(x)).to eq(y) end end it "parses known good version strings" do [ [ "3.3", [ nil, "3.3", nil ] ], [ "1.7.3", [ nil, "1.7.3", nil ] ], [ "20020927", [ nil, "20020927", nil ] ], ].each do |x, y| expect(@rpmutils.version_parse(x)).to eq(y) end end it "parses strange version strings" do [ [ "3..3", [ nil, "3..3", nil ] ], [ "0001.7.3", [ nil, "0001.7.3", nil ] ], [ "20020927,3", [ nil, "20020927,3", nil ] ], ].each do |x, y| expect(@rpmutils.version_parse(x)).to eq(y) end end it "parses known good version release strings" do [ [ "3.3-0.pre3.1.60.el5_5.1", [ nil, "3.3", "0.pre3.1.60.el5_5.1" ] ], [ "1.7.3-1jpp.2.el5", [ nil, "1.7.3", "1jpp.2.el5" ] ], [ "20020927-46.el5", [ nil, "20020927", "46.el5" ] ], ].each do |x, y| expect(@rpmutils.version_parse(x)).to eq(y) end end it "parses strange version release strings" do [ [ "3.3-", [ nil, "3.3", nil ] ], [ "-1jpp.2.el5", [ nil, nil, "1jpp.2.el5" ] ], [ "-0020020927-46.el5", [ nil, "-0020020927", "46.el5" ] ], ].each do |x, y| expect(@rpmutils.version_parse(x)).to eq(y) end end end describe "rpmvercmp" do before do @rpmutils = Chef::Provider::Package::Yum::RPMUtils end it "should validate version compare logic for standard examples" do [ # numeric [ "0.0.2", "0.0.1", 1 ], [ "0.2.0", "0.1.0", 1 ], [ "2.0.0", "1.0.0", 1 ], [ "0.0.1", "0.0.1", 0 ], [ "0.0.1", "0.0.2", -1 ], [ "0.1.0", "0.2.0", -1 ], [ "1.0.0", "2.0.0", -1 ], # alpha [ "bb", "aa", 1 ], [ "ab", "aa", 1 ], [ "aa", "aa", 0 ], [ "aa", "bb", -1 ], [ "aa", "ab", -1 ], [ "BB", "AA", 1 ], [ "AA", "AA", 0 ], [ "AA", "BB", -1 ], [ "aa", "AA", 1 ], [ "AA", "aa", -1 ], # alphanumeric [ "0.0.1b", "0.0.1a", 1 ], [ "0.1b.0", "0.1a.0", 1 ], [ "1b.0.0", "1a.0.0", 1 ], [ "0.0.1a", "0.0.1a", 0 ], [ "0.0.1a", "0.0.1b", -1 ], [ "0.1a.0", "0.1b.0", -1 ], [ "1a.0.0", "1b.0.0", -1 ], # alphanumeric against alphanumeric [ "0.0.1", "0.0.a", 1 ], [ "0.1.0", "0.a.0", 1 ], [ "1.0.0", "a.0.0", 1 ], [ "0.0.a", "0.0.a", 0 ], [ "0.0.a", "0.0.1", -1 ], [ "0.a.0", "0.1.0", -1 ], [ "a.0.0", "1.0.0", -1 ], # alphanumeric against numeric [ "0.0.2", "0.0.1a", 1 ], [ "0.0.2a", "0.0.1", 1 ], [ "0.0.1", "0.0.2a", -1 ], [ "0.0.1a", "0.0.2", -1 ], # length [ "0.0.1aa", "0.0.1a", 1 ], [ "0.0.1aa", "0.0.1aa", 0 ], [ "0.0.1a", "0.0.1aa", -1 ], ].each do |x, y, result| expect(@rpmutils.rpmvercmp(x, y)).to eq(result) end end it "should validate version compare logic for strange examples" do [ [ "2,0,0", "1.0.0", 1 ], [ "0.0.1", "0,0.1", 0 ], [ "1.0.0", "2,0,0", -1 ], [ "002.0.0", "001.0.0", 1 ], [ "001..0.1", "001..0.0", 1 ], [ "-001..1", "-001..0", 1 ], [ "1.0.1", nil, 1 ], [ nil, nil, 0 ], [ nil, "1.0.1", -1 ], [ "1.0.1", "", 1 ], [ "", "", 0 ], [ "", "1.0.1", -1 ], ].each do |x, y, result| expect(@rpmutils.rpmvercmp(x, y)).to eq(result) end end it "tests isalnum good input" do %w{a z A Z 0 9}.each do |t| expect(@rpmutils.isalnum(t)).to eq(true) end end it "tests isalnum bad input" do [ "-", ".", "!", "^", ":", "_" ].each do |t| expect(@rpmutils.isalnum(t)).to eq(false) end end it "tests isalpha good input" do %w{a z A Z}.each do |t| expect(@rpmutils.isalpha(t)).to eq(true) end end it "tests isalpha bad input" do [ "0", "9", "-", ".", "!", "^", ":", "_" ].each do |t| expect(@rpmutils.isalpha(t)).to eq(false) end end it "tests isdigit good input" do %w{0 9}.each do |t| expect(@rpmutils.isdigit(t)).to eq(true) end end it "tests isdigit bad input" do [ "A", "z", "-", ".", "!", "^", ":", "_" ].each do |t| expect(@rpmutils.isdigit(t)).to eq(false) end end end end describe Chef::Provider::Package::Yum::RPMVersion do describe "new - with parsing" do before do @rpmv = Chef::Provider::Package::Yum::RPMVersion.new("1:1.6.5-9.36.el5") end it "should expose evr (name-version-release) available" do expect(@rpmv.e).to eq(1) expect(@rpmv.v).to eq("1.6.5") expect(@rpmv.r).to eq("9.36.el5") expect(@rpmv.evr).to eq("1:1.6.5-9.36.el5") end it "should output a version-release string" do expect(@rpmv.to_s).to eq("1.6.5-9.36.el5") end end describe "new - no parsing" do before do @rpmv = Chef::Provider::Package::Yum::RPMVersion.new("1", "1.6.5", "9.36.el5") end it "should expose evr (name-version-release) available" do expect(@rpmv.e).to eq(1) expect(@rpmv.v).to eq("1.6.5") expect(@rpmv.r).to eq("9.36.el5") expect(@rpmv.evr).to eq("1:1.6.5-9.36.el5") end it "should output a version-release string" do expect(@rpmv.to_s).to eq("1.6.5-9.36.el5") end end it "should raise an error unless passed 1 or 3 args" do expect do Chef::Provider::Package::Yum::RPMVersion.new() end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMVersion.new("1:1.6.5-9.36.el5") end.not_to raise_error expect do Chef::Provider::Package::Yum::RPMVersion.new("1:1.6.5-9.36.el5", "extra") end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMVersion.new("1", "1.6.5", "9.36.el5") end.not_to raise_error expect do Chef::Provider::Package::Yum::RPMVersion.new("1", "1.6.5", "9.36.el5", "extra") end.to raise_error(ArgumentError) end # thanks version_class_spec.rb! describe "compare" do it "should sort based on complete epoch-version-release data" do [ # smaller, larger [ "0:1.6.5-9.36.el5", "1:1.6.5-9.36.el5" ], [ "0:2.3-15.el5", "0:3.3-15.el5" ], [ "0:alpha9.8-27.2", "0:beta9.8-27.2" ], [ "0:0.09-14jpp.3", "0:0.09-15jpp.3" ], [ "0:0.9.0-0.6.20110211.el5", "0:0.9.0-0.6.20120211.el5" ], [ "0:1.9.1-4.el5", "0:1.9.1-5.el5" ], [ "0:1.4.10-7.20090624svn.el5", "0:1.4.10-7.20090625svn.el5" ], [ "0:2.3.4-2.el5", "0:2.3.4-2.el6" ], ].each do |smaller, larger| sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller) lg = Chef::Provider::Package::Yum::RPMVersion.new(larger) expect(sm).to be < lg expect(lg).to be > sm expect(sm).not_to eq(lg) end end it "should sort based on partial epoch-version-release data" do [ # smaller, larger [ ":1.6.5-9.36.el5", "1:1.6.5-9.36.el5" ], [ "2.3-15.el5", "3.3-15.el5" ], [ "alpha9.8", "beta9.8" ], %w{14jpp 15jpp}, [ "0.9.0-0.6", "0.9.0-0.7" ], [ "0:1.9", "3:1.9" ], [ "2.3-2.el5", "2.3-2.el6" ], ].each do |smaller, larger| sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller) lg = Chef::Provider::Package::Yum::RPMVersion.new(larger) expect(sm).to be < lg expect(lg).to be > sm expect(sm).not_to eq(lg) end end it "should verify equality of complete epoch-version-release data" do [ [ "0:1.6.5-9.36.el5", "0:1.6.5-9.36.el5" ], [ "0:2.3-15.el5", "0:2.3-15.el5" ], [ "0:alpha9.8-27.2", "0:alpha9.8-27.2" ], ].each do |smaller, larger| sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller) lg = Chef::Provider::Package::Yum::RPMVersion.new(larger) expect(sm).to eq(lg) end end it "should verify equality of partial epoch-version-release data" do [ [ ":1.6.5-9.36.el5", "0:1.6.5-9.36.el5" ], [ "2.3-15.el5", "2.3-15.el5" ], [ "alpha9.8-3", "alpha9.8-3" ], ].each do |smaller, larger| sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller) lg = Chef::Provider::Package::Yum::RPMVersion.new(larger) expect(sm).to eq(lg) end end end describe "partial compare" do it "should compare based on partial epoch-version-release data" do [ # smaller, larger [ "0:1.1.1-1", "1:" ], [ "0:1.1.1-1", "0:1.1.2" ], [ "0:1.1.1-1", "0:1.1.2-1" ], [ "0:", "1:1.1.1-1" ], [ "0:1.1.1", "0:1.1.2-1" ], [ "0:1.1.1-1", "0:1.1.2-1" ], ].each do |smaller, larger| sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller) lg = Chef::Provider::Package::Yum::RPMVersion.new(larger) expect(sm.partial_compare(lg)).to eq(-1) expect(lg.partial_compare(sm)).to eq(1) expect(sm.partial_compare(lg)).not_to eq(0) end end it "should verify equality based on partial epoch-version-release data" do [ [ "0:", "0:1.1.1-1" ], [ "0:1.1.1", "0:1.1.1-1" ], [ "0:1.1.1-1", "0:1.1.1-1" ], ].each do |smaller, larger| sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller) lg = Chef::Provider::Package::Yum::RPMVersion.new(larger) expect(sm.partial_compare(lg)).to eq(0) end end end end describe Chef::Provider::Package::Yum::RPMPackage do describe "new - with parsing" do before do @rpm = Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5", "x86_64", []) end it "should expose nevra (name-epoch-version-release-arch) available" do expect(@rpm.name).to eq("testing") expect(@rpm.version.e).to eq(1) expect(@rpm.version.v).to eq("1.6.5") expect(@rpm.version.r).to eq("9.36.el5") expect(@rpm.arch).to eq("x86_64") expect(@rpm.nevra).to eq("testing-1:1.6.5-9.36.el5.x86_64") expect(@rpm.to_s).to eq(@rpm.nevra) end it "should always have at least one provide, itself" do expect(@rpm.provides.size).to eq(1) expect(@rpm.provides[0].name).to eql("testing") expect(@rpm.provides[0].version.evr).to eql("1:1.6.5-9.36.el5") expect(@rpm.provides[0].flag).to eql(:==) end end describe "new - no parsing" do before do @rpm = Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64", []) end it "should expose nevra (name-epoch-version-release-arch) available" do expect(@rpm.name).to eq("testing") expect(@rpm.version.e).to eq(1) expect(@rpm.version.v).to eq("1.6.5") expect(@rpm.version.r).to eq("9.36.el5") expect(@rpm.arch).to eq("x86_64") expect(@rpm.nevra).to eq("testing-1:1.6.5-9.36.el5.x86_64") expect(@rpm.to_s).to eq(@rpm.nevra) end it "should always have at least one provide, itself" do expect(@rpm.provides.size).to eq(1) expect(@rpm.provides[0].name).to eql("testing") expect(@rpm.provides[0].version.evr).to eql("1:1.6.5-9.36.el5") expect(@rpm.provides[0].flag).to eql(:==) end end it "should raise an error unless passed 4 or 6 args" do expect do Chef::Provider::Package::Yum::RPMPackage.new() end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMPackage.new("testing") end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5") end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5", "x86_64") end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5", "x86_64", []) end.not_to raise_error expect do Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64") end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64", []) end.not_to raise_error expect do Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64", [], "extra") end.to raise_error(ArgumentError) end describe "<=>" do it "should sort alphabetically based on package name" do [ [ "a-test", "b-test" ], [ "B-test", "a-test" ], [ "A-test", "B-test" ], [ "Aa-test", "aA-test" ], %w{1test 2test}, ].each do |smaller, larger| sm = Chef::Provider::Package::Yum::RPMPackage.new(smaller, "0:0.0.1-1", "x86_64", []) lg = Chef::Provider::Package::Yum::RPMPackage.new(larger, "0:0.0.1-1", "x86_64", []) expect(sm).to be < lg expect(lg).to be > sm expect(sm).not_to eq(lg) end end it "should sort alphabetically based on package arch" do [ %w{i386 x86_64}, %w{i386 noarch}, %w{noarch x86_64}, ].each do |smaller, larger| sm = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "0:0.0.1-1", smaller, []) lg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "0:0.0.1-1", larger, []) expect(sm).to be < lg expect(lg).to be > sm expect(sm).not_to eq(lg) end end end end describe Chef::Provider::Package::Yum::RPMDbPackage do before(:each) do # name, version, arch, installed, available, repoid @rpm_x = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "noarch", [], false, true, "base") @rpm_y = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "noarch", [], true, true, "extras") @rpm_z = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "noarch", [], true, false, "other") end describe "initialize" do it "should return a Chef::Provider::Package::Yum::RPMDbPackage object" do expect(@rpm_x).to be_kind_of(Chef::Provider::Package::Yum::RPMDbPackage) end end describe "available" do it "should return true" do expect(@rpm_x.available).to eq(true) expect(@rpm_y.available).to eq(true) expect(@rpm_z.available).to eq(false) end end describe "installed" do it "should return true" do expect(@rpm_x.installed).to eq(false) expect(@rpm_y.installed).to eq(true) expect(@rpm_z.installed).to eq(true) end end describe "repoid" do it "should return the source repository repoid" do expect(@rpm_x.repoid).to eq("base") expect(@rpm_y.repoid).to eq("extras") expect(@rpm_z.repoid).to eq("other") end end end describe Chef::Provider::Package::Yum::RPMDependency do describe "new - with parsing" do before do @rpmdep = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==) end it "should expose name, version, flag available" do expect(@rpmdep.name).to eq("testing") expect(@rpmdep.version.e).to eq(1) expect(@rpmdep.version.v).to eq("1.6.5") expect(@rpmdep.version.r).to eq("9.36.el5") expect(@rpmdep.flag).to eq(:==) end end describe "new - no parsing" do before do @rpmdep = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1", "1.6.5", "9.36.el5", :==) end it "should expose name, version, flag available" do expect(@rpmdep.name).to eq("testing") expect(@rpmdep.version.e).to eq(1) expect(@rpmdep.version.v).to eq("1.6.5") expect(@rpmdep.version.r).to eq("9.36.el5") expect(@rpmdep.flag).to eq(:==) end end it "should raise an error unless passed 3 or 5 args" do expect do Chef::Provider::Package::Yum::RPMDependency.new() end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMDependency.new("testing") end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5") end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==) end.not_to raise_error expect do Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==, "extra") end.to raise_error(ArgumentError) expect do Chef::Provider::Package::Yum::RPMDependency.new("testing", "1", "1.6.5", "9.36.el5", :==) end.not_to raise_error expect do Chef::Provider::Package::Yum::RPMDependency.new("testing", "1", "1.6.5", "9.36.el5", :==, "extra") end.to raise_error(ArgumentError) end describe "parse" do it "should parse a name, flag, version string into a valid RPMDependency object" do @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing >= 1:1.6.5-9.36.el5") expect(@rpmdep.name).to eq("testing") expect(@rpmdep.version.e).to eq(1) expect(@rpmdep.version.v).to eq("1.6.5") expect(@rpmdep.version.r).to eq("9.36.el5") expect(@rpmdep.flag).to eq(:>=) end it "should parse a name into a valid RPMDependency object" do @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing") expect(@rpmdep.name).to eq("testing") expect(@rpmdep.version.e).to eq(nil) expect(@rpmdep.version.v).to eq(nil) expect(@rpmdep.version.r).to eq(nil) expect(@rpmdep.flag).to eq(:==) end it "should parse an invalid string into the name of a RPMDependency object" do @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing blah >") expect(@rpmdep.name).to eq("testing blah >") expect(@rpmdep.version.e).to eq(nil) expect(@rpmdep.version.v).to eq(nil) expect(@rpmdep.version.r).to eq(nil) expect(@rpmdep.flag).to eq(:==) end it "should parse various valid flags" do [ [ ">", :> ], [ ">=", :>= ], [ "=", :== ], [ "==", :== ], [ "<=", :<= ], [ "<", :< ], ].each do |before, after| @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing #{before} 1:1.1-1") expect(@rpmdep.flag).to eq(after) end end it "should parse various invalid flags and treat them as names" do [ [ "<>", :== ], [ "!=", :== ], [ ">>", :== ], [ "<<", :== ], [ "!", :== ], [ "~", :== ], ].each do |before, after| @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing #{before} 1:1.1-1") expect(@rpmdep.name).to eq("testing #{before} 1:1.1-1") expect(@rpmdep.flag).to eq(after) end end end describe "satisfy?" do it "should raise an error unless a RPMDependency is passed" do @rpmprovide = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==) @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :>=) expect do @rpmprovide.satisfy?("hi") end.to raise_error(ArgumentError) expect do @rpmprovide.satisfy?(@rpmrequire) end.not_to raise_error end it "should validate dependency satisfaction logic for standard examples" do [ # names [ "test", "test", true ], [ "test", "foo", false ], # full: epoch:version-relese [ "testing = 1:1.1-1", "testing > 1:1.1-0", true ], [ "testing = 1:1.1-1", "testing >= 1:1.1-0", true ], [ "testing = 1:1.1-1", "testing >= 1:1.1-1", true ], [ "testing = 1:1.1-1", "testing = 1:1.1-1", true ], [ "testing = 1:1.1-1", "testing == 1:1.1-1", true ], [ "testing = 1:1.1-1", "testing <= 1:1.1-1", true ], [ "testing = 1:1.1-1", "testing <= 1:1.1-0", false ], [ "testing = 1:1.1-1", "testing < 1:1.1-0", false ], # partial: epoch:version [ "testing = 1:1.1", "testing > 1:1.0", true ], [ "testing = 1:1.1", "testing >= 1:1.0", true ], [ "testing = 1:1.1", "testing >= 1:1.1", true ], [ "testing = 1:1.1", "testing = 1:1.1", true ], [ "testing = 1:1.1", "testing == 1:1.1", true ], [ "testing = 1:1.1", "testing <= 1:1.1", true ], [ "testing = 1:1.1", "testing <= 1:1.0", false ], [ "testing = 1:1.1", "testing < 1:1.0", false ], # partial: epoch [ "testing = 1:", "testing > 0:", true ], [ "testing = 1:", "testing >= 0:", true ], [ "testing = 1:", "testing >= 1:", true ], [ "testing = 1:", "testing = 1:", true ], [ "testing = 1:", "testing == 1:", true ], [ "testing = 1:", "testing <= 1:", true ], [ "testing = 1:", "testing <= 0:", false ], [ "testing = 1:", "testing < 0:", false ], # mix and match! [ "testing = 1:1.1-1", "testing == 1:1.1", true ], [ "testing = 1:1.1-1", "testing == 1:", true ], ].each do |prov, req, result| @rpmprovide = Chef::Provider::Package::Yum::RPMDependency.parse(prov) @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.parse(req) expect(@rpmprovide.satisfy?(@rpmrequire)).to eq(result) expect(@rpmrequire.satisfy?(@rpmprovide)).to eq(result) end end end end # thanks resource_collection_spec.rb! describe Chef::Provider::Package::Yum::RPMDb do before(:each) do @rpmdb = Chef::Provider::Package::Yum::RPMDb.new # name, version, arch, installed, available deps_v = [ Chef::Provider::Package::Yum::RPMDependency.parse("libz.so.1()(64bit)"), Chef::Provider::Package::Yum::RPMDependency.parse("test-package-a = 0:1.6.5-9.36.el5"), ] deps_z = [ Chef::Provider::Package::Yum::RPMDependency.parse("libz.so.1()(64bit)"), Chef::Provider::Package::Yum::RPMDependency.parse("config(test) = 0:1.6.5-9.36.el5"), Chef::Provider::Package::Yum::RPMDependency.parse("test-package-c = 0:1.6.5-9.36.el5"), ] @rpm_v = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-a", "0:1.6.5-9.36.el5", "i386", deps_v, true, false, "base") @rpm_w = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "i386", [], true, true, "extras") @rpm_x = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "x86_64", [], false, true, "extras") @rpm_y = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "1:1.6.5-9.36.el5", "x86_64", [], true, true, "extras") @rpm_z = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-c", "0:1.6.5-9.36.el5", "noarch", deps_z, true, true, "base") @rpm_z_mirror = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-c", "0:1.6.5-9.36.el5", "noarch", deps_z, true, true, "base") end describe "initialize" do it "should return a Chef::Provider::Package::Yum::RPMDb object" do expect(@rpmdb).to be_kind_of(Chef::Provider::Package::Yum::RPMDb) end end describe "push" do it "should accept an RPMDbPackage object through pushing" do expect { @rpmdb.push(@rpm_w) }.not_to raise_error end it "should accept multiple RPMDbPackage object through pushing" do expect { @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z) }.not_to raise_error end it "should only accept an RPMDbPackage object" do expect { @rpmdb.push("string") }.to raise_error(ArgumentError) end it "should add the package to the package db" do @rpmdb.push(@rpm_w) expect(@rpmdb["test-package-b"]).not_to eq(nil) end it "should add conditionally add the package to the available list" do expect(@rpmdb.available_size).to eq(0) @rpmdb.push(@rpm_v, @rpm_w) expect(@rpmdb.available_size).to eq(1) end it "should add conditionally add the package to the installed list" do expect(@rpmdb.installed_size).to eq(0) @rpmdb.push(@rpm_w, @rpm_x) expect(@rpmdb.installed_size).to eq(1) end it "should have a total of 2 packages in the RPMDb" do expect(@rpmdb.size).to eq(0) @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z) expect(@rpmdb.size).to eq(2) end it "should keep the Array unique when a duplicate is pushed" do @rpmdb.push(@rpm_z, @rpm_z_mirror) expect(@rpmdb["test-package-c"].size).to eq(1) end it "should register the package provides in the provides index" do @rpmdb.push(@rpm_v, @rpm_w, @rpm_z) expect(@rpmdb.lookup_provides("test-package-a")[0]).to eq(@rpm_v) expect(@rpmdb.lookup_provides("config(test)")[0]).to eq(@rpm_z) expect(@rpmdb.lookup_provides("libz.so.1()(64bit)")[0]).to eq(@rpm_v) expect(@rpmdb.lookup_provides("libz.so.1()(64bit)")[1]).to eq(@rpm_z) end end describe "<<" do it "should accept an RPMPackage object through the << operator" do expect { @rpmdb << @rpm_w }.not_to raise_error end end describe "lookup" do it "should return an Array of RPMPackage objects by index" do @rpmdb << @rpm_w expect(@rpmdb.lookup("test-package-b")).to be_kind_of(Array) end end describe "[]" do it "should return an Array of RPMPackage objects though the [index] operator" do @rpmdb << @rpm_w expect(@rpmdb["test-package-b"]).to be_kind_of(Array) end it "should return an Array of 3 RPMPackage objects" do @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z) expect(@rpmdb["test-package-b"].size).to eq(3) end it "should return an Array of RPMPackage objects sorted from newest to oldest" do @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z) expect(@rpmdb["test-package-b"][0]).to eq(@rpm_y) expect(@rpmdb["test-package-b"][1]).to eq(@rpm_x) expect(@rpmdb["test-package-b"][2]).to eq(@rpm_w) end end describe "lookup_provides" do it "should return an Array of RPMPackage objects by index" do @rpmdb << @rpm_z x = @rpmdb.lookup_provides("config(test)") expect(x).to be_kind_of(Array) expect(x[0]).to eq(@rpm_z) end end describe "clear" do it "should clear the RPMDb" do expect(@rpmdb).to receive(:clear_available).once expect(@rpmdb).to receive(:clear_installed).once @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z) expect(@rpmdb.size).not_to eq(0) expect(@rpmdb.lookup_provides("config(test)")).to be_kind_of(Array) @rpmdb.clear expect(@rpmdb.lookup_provides("config(test)")).to eq(nil) expect(@rpmdb.size).to eq(0) end end describe "clear_available" do it "should clear the available list" do @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z) expect(@rpmdb.available_size).not_to eq(0) @rpmdb.clear_available expect(@rpmdb.available_size).to eq(0) end end describe "available?" do it "should return true if a package is available" do expect(@rpmdb.available?(@rpm_w)).to eq(false) @rpmdb.push(@rpm_v, @rpm_w) expect(@rpmdb.available?(@rpm_v)).to eq(false) expect(@rpmdb.available?(@rpm_w)).to eq(true) end end describe "clear_installed" do it "should clear the installed list" do @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z) expect(@rpmdb.installed_size).not_to eq(0) @rpmdb.clear_installed expect(@rpmdb.installed_size).to eq(0) end end describe "installed?" do it "should return true if a package is installed" do expect(@rpmdb.installed?(@rpm_w)).to eq(false) @rpmdb.push(@rpm_w, @rpm_x) expect(@rpmdb.installed?(@rpm_w)).to eq(true) expect(@rpmdb.installed?(@rpm_x)).to eq(false) end end describe "whatprovides" do it "should raise an error unless a RPMDependency is passed" do @rpmprovide = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==) @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :>=) expect do @rpmdb.whatprovides("hi") end.to raise_error(ArgumentError) expect do @rpmdb.whatprovides(@rpmrequire) end.not_to raise_error end it "should return an Array of packages statisfying a RPMDependency" do @rpmdb.push(@rpm_v, @rpm_w, @rpm_z) @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.parse("test-package-a >= 1.6.5") x = @rpmdb.whatprovides(@rpmrequire) expect(x).to be_kind_of(Array) expect(x[0]).to eq(@rpm_v) @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.parse("libz.so.1()(64bit)") x = @rpmdb.whatprovides(@rpmrequire) expect(x).to be_kind_of(Array) expect(x[0]).to eq(@rpm_v) expect(x[1]).to eq(@rpm_z) end end end describe Chef::Provider::Package::Yum::YumCache do # allow for the reset of a Singleton # thanks to Ian White (http://blog.ardes.com/2006/12/11/testing-singletons-with-ruby) class << Chef::Provider::Package::Yum::YumCache def reset_instance Singleton.send :__init__, self self end end let(:yum_exe) do StringIO.new("#!/usr/bin/python\n\naldsjfa\ldsajflkdsjf\lajsdfj") end let(:bin_exe) do StringIO.new(SecureRandom.random_bytes) end before(:each) do @stdin = double("STDIN", :nil_object => true) @stdout = double("STDOUT", :nil_object => true) @stdout_good = < 0, :stdin => @stdin, :stdout => @stdout_good, :stderr => @stderr) # new singleton each time Chef::Provider::Package::Yum::YumCache.reset_instance @yc = Chef::Provider::Package::Yum::YumCache.instance # load valid data @yc.yum_binary = "yum" allow(@yc).to receive(:shell_out!).and_return(@status) allow_any_instance_of(described_class).to receive(:which).with("yum").and_return("/usr/bin/yum") allow(::File).to receive(:open).with("/usr/bin/yum", "r") do |&block| res = block.call(yum_exe) # a bit of a hack. rewind this since it seem that no matter what # I do, we get the same StringIO objects on multiple calls to # ::File.open yum_exe.rewind; res end end describe "initialize" do it "should return a Chef::Provider::Package::Yum::YumCache object" do expect(@yc).to be_kind_of(Chef::Provider::Package::Yum::YumCache) end it "should register reload for start of Chef::Client runs" do Chef::Provider::Package::Yum::YumCache.reset_instance expect(Chef::Client).to receive(:when_run_starts) do |&b| expect(b).not_to be_nil end @yc = Chef::Provider::Package::Yum::YumCache.instance end end describe "python_bin" do it "should return the default python if an error occurs" do allow(::File).to receive(:open).with("/usr/bin/yum", "r").and_raise(StandardError) expect(@yc.python_bin).to eq("/usr/bin/python") end it "should return the default python if the yum-executable doesn't start with #!" do allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(bin_exe); bin_exe.rewind; r } expect(@yc.python_bin).to eq("/usr/bin/python") end it "should return /usr/bin/python if the interpreter is /bin/bash" do other = StringIO.new("#!/bin/bash\n# The yum executable redirecting to dnf from dnf-yum compatible package.") allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(other); other.rewind; r } expect(@yc.python_bin).to eq("/usr/bin/python") end it "should return the interpreter for yum" do other = StringIO.new("#!/usr/bin/super_python\n\nlasjdfdsaljf\nlasdjfs") allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(other); other.rewind; r } expect(@yc.python_bin).to eq("/usr/bin/super_python") end end describe "refresh" do it "should implicitly call yum-dump.py only once by default after being instantiated" do expect(@yc).to receive(:shell_out!).once @yc.installed_version("zlib") @yc.reset @yc.installed_version("zlib") end it "should run yum-dump.py using the system python when next_refresh is for :all" do @yc.reload expect(@yc).to receive(:shell_out!).with(%r{^/usr/bin/python .*/yum-dump.py --options --installed-provides --yum-lock-timeout 30$}, :timeout => Chef::Config[:yum_timeout]) @yc.refresh end it "should run yum-dump.py with the installed flag when next_refresh is for :installed" do @yc.reload_installed expect(@yc).to receive(:shell_out!).with(%r{^/usr/bin/python .*/yum-dump.py --installed --yum-lock-timeout 30$}, :timeout => Chef::Config[:yum_timeout]) @yc.refresh end it "should run yum-dump.py with the all-provides flag when next_refresh is for :provides" do @yc.reload_provides expect(@yc).to receive(:shell_out!).with(%r{^/usr/bin/python .*/yum-dump.py --options --all-provides --yum-lock-timeout 30$}, :timeout => Chef::Config[:yum_timeout]) @yc.refresh end it "should pass extra_repo_control args to yum-dump.py" do @yc.enable_extra_repo_control("--enablerepo=foo --disablerepo=bar") expect(@yc).to receive(:shell_out!).with(%r{^/usr/bin/python .*/yum-dump.py --options --installed-provides --enablerepo=foo --disablerepo=bar --yum-lock-timeout 30$}, :timeout => Chef::Config[:yum_timeout]) @yc.refresh end it "should pass extra_repo_control args and configured yum lock timeout to yum-dump.py" do Chef::Config[:yum_lock_timeout] = 999 @yc.enable_extra_repo_control("--enablerepo=foo --disablerepo=bar") expect(@yc).to receive(:shell_out!).with(%r{^/usr/bin/python .*/yum-dump.py --options --installed-provides --enablerepo=foo --disablerepo=bar --yum-lock-timeout 999$}, :timeout => Chef::Config[:yum_timeout]) @yc.refresh end it "should warn about invalid data with too many separators" do @status = double("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_bad_separators, :stderr => @stderr) allow(@yc).to receive(:shell_out!).and_return(@status) expect(Chef::Log).to receive(:warn).exactly(3).times.with(%r{Problem parsing}) @yc.refresh end it "should warn about invalid data with an incorrect type" do @status = double("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_bad_type, :stderr => @stderr) allow(@yc).to receive(:shell_out!).and_return(@status) expect(Chef::Log).to receive(:warn).exactly(2).times.with(%r{Problem parsing}) @yc.refresh end it "should warn about no output from yum-dump.py" do @status = double("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_no_output, :stderr => @stderr) allow(@yc).to receive(:shell_out!).and_return(@status) expect(Chef::Log).to receive(:warn).exactly(1).times.with(%r{no output from yum-dump.py}) @yc.refresh end it "should raise exception yum-dump.py exits with a non zero status" do @status = double("Status", :exitstatus => 1, :stdin => @stdin, :stdout => @stdout_no_output, :stderr => @stderr) allow(@yc).to receive(:shell_out!).and_return(@status) expect { @yc.refresh }.to raise_error(Chef::Exceptions::Package, %r{CentOS-Base.repo, line: 12}) end it "should parse type 'i' into an installed state for a package" do expect(@yc.available_version("erlang-mochiweb")).to eq(nil) expect(@yc.installed_version("erlang-mochiweb")).not_to eq(nil) end it "should parse type 'a' into an available state for a package" do expect(@yc.available_version("znc")).not_to eq(nil) expect(@yc.installed_version("znc")).to eq(nil) end it "should parse type 'r' into an installed and available states for a package" do expect(@yc.available_version("zip")).not_to eq(nil) expect(@yc.installed_version("zip")).not_to eq(nil) end it "should parse installonlypkgs from yum-dump.py options output" do expect(@yc.allow_multi_install).to eq(%w{kernel kernel-bigmem kernel-enterprise}) end end describe "installed_version" do it "should take one or two arguments" do expect { @yc.installed_version("zip") }.not_to raise_error expect { @yc.installed_version("zip", "i386") }.not_to raise_error expect { @yc.installed_version("zip", "i386", "extra") }.to raise_error(ArgumentError) end it "should return version-release for matching package regardless of arch" do expect(@yc.installed_version("zip", "x86_64")).to eq("2.31-2.el5") expect(@yc.installed_version("zip", nil)).to eq("2.31-2.el5") end it "should return version-release for matching package and arch" do expect(@yc.installed_version("zip", "x86_64")).to eq("2.31-2.el5") expect(@yc.installed_version("zisofs-tools", "i386")).to eq(nil) end it "should return nil for an unmatched package" do expect(@yc.installed_version(nil, nil)).to eq(nil) expect(@yc.installed_version("test1", nil)).to eq(nil) expect(@yc.installed_version("test2", "x86_64")).to eq(nil) end end describe "available_version" do it "should take one or two arguments" do expect { @yc.available_version("zisofs-tools") }.not_to raise_error expect { @yc.available_version("zisofs-tools", "i386") }.not_to raise_error expect { @yc.available_version("zisofs-tools", "i386", "extra") }.to raise_error(ArgumentError) end it "should return version-release for matching package regardless of arch" do expect(@yc.available_version("zip", "x86_64")).to eq("2.31-2.el5") expect(@yc.available_version("zip", nil)).to eq("2.31-2.el5") end it "should return version-release for matching package and arch" do expect(@yc.available_version("zip", "x86_64")).to eq("2.31-2.el5") expect(@yc.available_version("zisofs-tools", "i386")).to eq(nil) end it "should return nil for an unmatched package" do expect(@yc.available_version(nil, nil)).to eq(nil) expect(@yc.available_version("test1", nil)).to eq(nil) expect(@yc.available_version("test2", "x86_64")).to eq(nil) end end describe "version_available?" do it "should take two or three arguments" do expect { @yc.version_available?("zisofs-tools") }.to raise_error(ArgumentError) expect { @yc.version_available?("zisofs-tools", "1.0.6-3.2.2") }.not_to raise_error expect { @yc.version_available?("zisofs-tools", "1.0.6-3.2.2", "x86_64") }.not_to raise_error end it "should return true if our package-version-arch is available" do expect(@yc.version_available?("zisofs-tools", "1.0.6-3.2.2", "x86_64")).to eq(true) end it "should return true if our package-version, no arch, is available" do expect(@yc.version_available?("zisofs-tools", "1.0.6-3.2.2", nil)).to eq(true) expect(@yc.version_available?("zisofs-tools", "1.0.6-3.2.2")).to eq(true) end it "should return false if our package-version-arch isn't available" do expect(@yc.version_available?("zisofs-tools", "1.0.6-3.2.2", "pretend")).to eq(false) expect(@yc.version_available?("zisofs-tools", "pretend", "x86_64")).to eq(false) expect(@yc.version_available?("pretend", "1.0.6-3.2.2", "x86_64")).to eq(false) end it "should return false if our package-version, no arch, isn't available" do expect(@yc.version_available?("zisofs-tools", "pretend", nil)).to eq(false) expect(@yc.version_available?("zisofs-tools", "pretend")).to eq(false) expect(@yc.version_available?("pretend", "1.0.6-3.2.2")).to eq(false) end end describe "package_repository" do it "should take two or three arguments" do expect { @yc.package_repository("zisofs-tools") }.to raise_error(ArgumentError) expect { @yc.package_repository("zisofs-tools", "1.0.6-3.2.2") }.not_to raise_error expect { @yc.package_repository("zisofs-tools", "1.0.6-3.2.2", "x86_64") }.not_to raise_error end it "should return repoid for package-version-arch" do expect(@yc.package_repository("zlib-devel", "1.2.3-3", "i386")).to eq("extras") expect(@yc.package_repository("zlib-devel", "1.2.3-3", "x86_64")).to eq("base") end it "should return repoid for package-version, no arch" do expect(@yc.package_repository("zisofs-tools", "1.0.6-3.2.2", nil)).to eq("extras") expect(@yc.package_repository("zisofs-tools", "1.0.6-3.2.2")).to eq("extras") end it "should return nil when no match for package-version-arch" do expect(@yc.package_repository("zisofs-tools", "1.0.6-3.2.2", "pretend")).to eq(nil) expect(@yc.package_repository("zisofs-tools", "pretend", "x86_64")).to eq(nil) expect(@yc.package_repository("pretend", "1.0.6-3.2.2", "x86_64")).to eq(nil) end it "should return nil when no match for package-version, no arch" do expect(@yc.package_repository("zisofs-tools", "pretend", nil)).to eq(nil) expect(@yc.package_repository("zisofs-tools", "pretend")).to eq(nil) expect(@yc.package_repository("pretend", "1.0.6-3.2.2")).to eq(nil) end end describe "reset" do it "should empty the installed and available packages RPMDb" do expect(@yc.available_version("zip", "x86_64")).to eq("2.31-2.el5") expect(@yc.installed_version("zip", "x86_64")).to eq("2.31-2.el5") @yc.reset expect(@yc.available_version("zip", "x86_64")).to eq(nil) expect(@yc.installed_version("zip", "x86_64")).to eq(nil) end end describe "package_available?" do it "should return true a package name is available" do expect(@yc.package_available?("zisofs-tools")).to eq(true) expect(@yc.package_available?("moo")).to eq(false) expect(@yc.package_available?(nil)).to eq(false) end it "should return true a package name + arch is available" do expect(@yc.package_available?("zlib-devel.i386")).to eq(true) expect(@yc.package_available?("zisofs-tools.x86_64")).to eq(true) expect(@yc.package_available?("znc-test.beta1.x86_64")).to eq(true) expect(@yc.package_available?("znc-test.beta1")).to eq(true) expect(@yc.package_available?("znc-test.test.beta1")).to eq(true) expect(@yc.package_available?("moo.i386")).to eq(false) expect(@yc.package_available?("zisofs-tools.beta")).to eq(false) expect(@yc.package_available?("znc-test.test")).to eq(false) end end describe "enable_extra_repo_control" do it "should set @extra_repo_control to arg" do @yc.enable_extra_repo_control("--enablerepo=test") expect(@yc.extra_repo_control).to eq("--enablerepo=test") end it "should call reload once when set to flag cache for update" do expect(@yc).to receive(:reload).once @yc.enable_extra_repo_control("--enablerepo=test") @yc.enable_extra_repo_control("--enablerepo=test") end end describe "disable_extra_repo_control" do it "should set @extra_repo_control to nil" do @yc.enable_extra_repo_control("--enablerepo=test") @yc.disable_extra_repo_control expect(@yc.extra_repo_control).to eq(nil) end it "should call reload once when cleared to flag cache for update" do expect(@yc).to receive(:reload).once @yc.enable_extra_repo_control("--enablerepo=test") expect(@yc).to receive(:reload).once @yc.disable_extra_repo_control @yc.disable_extra_repo_control end end end describe "Chef::Provider::Package::Yum - Multi" do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Package.new(%w{cups vim}) @status = double("Status", :exitstatus => 0) @yum_cache = double( "Chef::Provider::Yum::YumCache", :reload_installed => true, :reset => true, :installed_version => "XXXX", :candidate_version => "YYYY", :package_available? => true, :version_available? => true, :allow_multi_install => [ "kernel" ], :package_repository => "base", :disable_extra_repo_control => true ) allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache) allow(@yum_cache).to receive(:yum_binary=).with("yum") allow(@yum_cache).to receive(:yum_binary=).with("yum") @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) @pid = double("PID") end describe "when loading the current system state" do it "should create a current resource with the name of the new_resource" do @provider.load_current_resource expect(@provider.current_resource.name).to eq("cups, vim") end it "should set the current resources package name to the new resources package name" do @provider.load_current_resource expect(@provider.current_resource.package_name).to eq(%w{cups vim}) end it "should set the installed version to nil on the current resource if no installed package" do allow(@yum_cache).to receive(:installed_version).and_return(nil) @provider.load_current_resource expect(@provider.current_resource.version).to eq([nil, nil]) end it "should set the installed version if yum has one" do allow(@yum_cache).to receive(:installed_version).with("cups", nil).and_return("1.2.4-11.18.el5") allow(@yum_cache).to receive(:installed_version).with("vim", nil).and_return("1.0") allow(@yum_cache).to receive(:candidate_version).with("cups", nil).and_return("1.2.4-11.18.el5_2.3") allow(@yum_cache).to receive(:candidate_version).with("vim", nil).and_return("1.5") @provider.load_current_resource expect(@provider.current_resource.version).to eq(["1.2.4-11.18.el5", "1.0"]) end it "should set the candidate version if yum info has one" do allow(@yum_cache).to receive(:installed_version).with("cups", nil).and_return("1.2.4-11.18.el5") allow(@yum_cache).to receive(:installed_version).with("vim", nil).and_return("1.0") allow(@yum_cache).to receive(:candidate_version).with("cups", nil).and_return("1.2.4-11.18.el5_2.3") allow(@yum_cache).to receive(:candidate_version).with("vim", nil).and_return("1.5") @provider.load_current_resource expect(@provider.candidate_version).to eql(["1.2.4-11.18.el5_2.3", "1.5"]) end it "should return the current resouce" do expect(@provider.load_current_resource).to eql(@provider.current_resource) end describe "when version constraint in package_name" do it "should set package_version if no existing package_name is found and new_package_name is available" do @new_resource = Chef::Resource::Package.new(["cups = 1.2.4-11.18.el5_2.3", "emacs = 24.4"]) @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) allow(@yum_cache).to receive(:package_available?) { |pkg| %w{cups emacs}.include?(pkg) ? true : false } allow(@yum_cache).to receive(:candidate_version) do |pkg| if pkg == "cups" "1.2.4-11.18.el5_2.3" elsif pkg == "emacs" "24.4" end end allow(@yum_cache).to receive(:packages_from_require) do |pkg| if pkg.name == "cups" [Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.3", "noarch", [], false, true, "base")] elsif pkg.name == "emacs" [Chef::Provider::Package::Yum::RPMDbPackage.new("emacs", "24.4", "noarch", [], false, true, "base")] end end expect(Chef::Log).to receive(:debug).exactly(2).times.with(%r{matched 1 package,}) expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{candidate version: \["1.2.4-11.18.el5_2.3", "24.4"\]}) expect(Chef::Log).to receive(:debug).at_least(2).times.with(%r{checking yum info}) @provider.load_current_resource expect(@provider.new_resource.package_name).to eq(%w{cups emacs}) expect(@provider.new_resource.version).to eq(["1.2.4-11.18.el5_2.3", "24.4"]) expect(@provider.send(:package_name_array)).to eq(%w{cups emacs}) expect(@provider.send(:new_version_array)).to eq(["1.2.4-11.18.el5_2.3", "24.4"]) end end end describe "when installing a package" do it "should run yum install with the package name and version" do @provider.load_current_resource allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) allow(@yum_cache).to receive(:installed_version).with("cups", nil).and_return("1.2.4-11.18.el5") allow(@yum_cache).to receive(:installed_version).with("vim", nil).and_return("0.9") expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y install cups-1.2.4-11.19.el5 vim-1.0" ) @provider.install_package(%w{cups vim}, ["1.2.4-11.19.el5", "1.0"]) end it "should run yum install with the package name, version and arch" do @provider.load_current_resource allow(@new_resource).to receive(:arch).and_return("i386") allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y install cups-1.2.4-11.19.el5.i386 vim-1.0.i386" ) @provider.install_package(%w{cups vim}, ["1.2.4-11.19.el5", "1.0"]) end it "installs the package with the options given in the resource" do @provider.load_current_resource allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) allow(@yum_cache).to receive(:installed_version).with("cups", nil).and_return("1.2.4-11.18.el5") allow(@yum_cache).to receive(:installed_version).with("vim", nil).and_return("0.9") expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y --disablerepo epmd install cups-1.2.4-11.19.el5 vim-1.0" ) allow(@new_resource).to receive(:options).and_return("--disablerepo epmd") @provider.install_package(%w{cups vim}, ["1.2.4-11.19.el5", "1.0"]) end it "should run yum install with the package name and version when name has arch" do @new_resource = Chef::Resource::Package.new(["cups.x86_64", "vim"]) @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context) allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1) # Inside of load_current_resource() we'll call parse_arch for cups, # and we need to craft the right response. The default mock setup above # will just return valid versions all the time which won't work for this # test. allow(@yum_cache).to receive(:installed_version).with("cups", "x86_64").and_return("XXXX") allow(@yum_cache).to receive(:candidate_version).with("cups", "x86_64").and_return("1.2.4-11.18.el5") allow(@yum_cache).to receive(:installed_version).with("cups.x86_64").and_return(nil) allow(@yum_cache).to receive(:candidate_version).with("cups.x86_64").and_return(nil) # Normal mock's for the idempotency check allow(@yum_cache).to receive(:installed_version).with("cups", nil).and_return("1.2.4-11.18.el5") allow(@yum_cache).to receive(:installed_version).with("vim", nil).and_return("0.9") @provider.load_current_resource expect(@provider).to receive(:yum_command).with( "-d0 -e0 -y install cups-1.2.4-11.19.el5.x86_64 vim-1.0" ) @provider.install_package(%w{cups vim}, ["1.2.4-11.19.el5", "1.0"]) end end end chef-12.14.60/spec/unit/provider/package/zypper_spec.rb000066400000000000000000000257731276456504500227340ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Zypper do let!(:new_resource) { Chef::Resource::ZypperPackage.new("cups") } let!(:current_resource) { Chef::Resource::ZypperPackage.new("cups") } let(:provider) do node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) Chef::Provider::Package::Zypper.new(new_resource, run_context) end let(:status) { double(:stdout => "\n", :exitstatus => 0) } before(:each) do allow(Chef::Resource::Package).to receive(:new).and_return(current_resource) allow(provider).to receive(:shell_out!).and_return(status) allow(provider).to receive(:`).and_return("2.0") end def shell_out_expectation(command, options = nil) options ||= { timeout: 900 } expect(provider).to receive(:shell_out).with(command, options) end def shell_out_expectation!(command, options = nil) options ||= { timeout: 900 } expect(provider).to receive(:shell_out!).with(command, options) end describe "when loading the current package state" do it "should create a current resource with the name of the new_resource" do expect(Chef::Resource::Package).to receive(:new).with(new_resource.name).and_return(current_resource) provider.load_current_resource end it "should set the current resources package name to the new resources package name" do expect(current_resource).to receive(:package_name).with(new_resource.package_name) provider.load_current_resource end it "should run zypper info with the package name" do shell_out_expectation!( "zypper --non-interactive info #{new_resource.package_name}" ).and_return(status) provider.load_current_resource end it "should set the installed version to nil on the current resource if zypper info installed version is (none)" do allow(provider).to receive(:shell_out).and_return(status) expect(current_resource).to receive(:version).with([nil]).and_return(true) provider.load_current_resource end it "should set the installed version if zypper info has one (zypper version < 1.13.0)" do status = double(:stdout => "Version: 1.0\nInstalled: Yes\n", :exitstatus => 0) allow(provider).to receive(:shell_out!).and_return(status) expect(current_resource).to receive(:version).with(["1.0"]).and_return(true) provider.load_current_resource end it "should set the installed version if zypper info has one (zypper version >= 1.13.0)" do status = double(:stdout => "Version : 1.0 \nInstalled : Yes \n", :exitstatus => 0) allow(provider).to receive(:shell_out!).and_return(status) expect(current_resource).to receive(:version).with(["1.0"]).and_return(true) provider.load_current_resource end it "should set the candidate version if zypper info has one (zypper version < 1.13.0)" do status = double(:stdout => "Version: 1.0\nInstalled: No\nStatus: out-of-date (version 0.9 installed)", :exitstatus => 0) allow(provider).to receive(:shell_out!).and_return(status) provider.load_current_resource expect(provider.candidate_version).to eql(["1.0"]) end it "should set the candidate version if zypper info has one (zypper version >= 1.13.0)" do status = double(:stdout => "Version : 1.0 \nInstalled : No \nStatus : out-of-date (version 0.9 installed)", :exitstatus => 0) allow(provider).to receive(:shell_out!).and_return(status) provider.load_current_resource expect(provider.candidate_version).to eql(["1.0"]) end it "should return the current resouce" do expect(provider.load_current_resource).to eql(current_resource) end end describe "install_package" do it "should run zypper install with the package name and version" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) shell_out_expectation!( "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0" ) provider.install_package(["emacs"], ["1.0"]) end it "should run zypper install without gpg checks" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install " + "--auto-agree-with-licenses emacs=1.0" ) provider.install_package(["emacs"], ["1.0"]) end it "should warn about gpg checks on zypper install" do expect(Chef::Log).to receive(:warn).with( /All packages will be installed without gpg signature checks/ ) shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install " + "--auto-agree-with-licenses emacs=1.0" ) provider.install_package(["emacs"], ["1.0"]) end end describe "upgrade_package" do it "should run zypper update with the package name and version" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) shell_out_expectation!( "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0" ) provider.upgrade_package(["emacs"], ["1.0"]) end it "should run zypper update without gpg checks" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install " + "--auto-agree-with-licenses emacs=1.0" ) provider.upgrade_package(["emacs"], ["1.0"]) end it "should warn about gpg checks on zypper upgrade" do expect(Chef::Log).to receive(:warn).with( /All packages will be installed without gpg signature checks/ ) shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install " + "--auto-agree-with-licenses emacs=1.0" ) provider.upgrade_package(["emacs"], ["1.0"]) end it "should run zypper upgrade without gpg checks" do shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install " + "--auto-agree-with-licenses emacs=1.0" ) provider.upgrade_package(["emacs"], ["1.0"]) end end describe "remove_package" do context "when package version is not explicitly specified" do it "should run zypper remove with the package name" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) shell_out_expectation!( "zypper --non-interactive remove emacs" ) provider.remove_package(["emacs"], [nil]) end end context "when package version is explicitly specified" do it "should run zypper remove with the package name" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true) shell_out_expectation!( "zypper --non-interactive remove emacs=1.0" ) provider.remove_package(["emacs"], ["1.0"]) end it "should run zypper remove without gpg checks" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) shell_out_expectation!( "zypper --non-interactive --no-gpg-checks remove emacs=1.0" ) provider.remove_package(["emacs"], ["1.0"]) end it "should warn about gpg checks on zypper remove" do expect(Chef::Log).to receive(:warn).with( /All packages will be installed without gpg signature checks/ ) shell_out_expectation!( "zypper --non-interactive --no-gpg-checks remove emacs=1.0" ) provider.remove_package(["emacs"], ["1.0"]) end end end describe "purge_package" do it "should run remove with the name and version and --clean-deps" do shell_out_expectation!( "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0" ) provider.purge_package(["emacs"], ["1.0"]) end it "should run zypper purge without gpg checks" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) shell_out_expectation!( "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0" ) provider.purge_package(["emacs"], ["1.0"]) end it "should warn about gpg checks on zypper purge" do expect(Chef::Log).to receive(:warn).with( /All packages will be installed without gpg signature checks/ ) shell_out_expectation!( "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0" ) provider.purge_package(["emacs"], ["1.0"]) end end describe "on an older zypper" do before(:each) do allow(provider).to receive(:`).and_return("0.11.6") end describe "install_package" do it "should run zypper install with the package name and version" do shell_out_expectation!( "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs" ) provider.install_package(["emacs"], ["1.0"]) end end describe "upgrade_package" do it "should run zypper update with the package name and version" do shell_out_expectation!( "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs" ) provider.upgrade_package(["emacs"], ["1.0"]) end end describe "remove_package" do it "should run zypper remove with the package name" do shell_out_expectation!( "zypper --no-gpg-checks remove -y emacs" ) provider.remove_package(["emacs"], ["1.0"]) end end end describe "when installing multiple packages" do # https://github.com/chef/chef/issues/3570 it "should install an array of package names and versions" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) shell_out_expectation!( "zypper --non-interactive --no-gpg-checks install " + "--auto-agree-with-licenses emacs=1.0 vim=2.0" ) provider.install_package(%w{emacs vim}, ["1.0", "2.0"]) end it "should remove an array of package names and versions" do allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false) shell_out_expectation!( "zypper --non-interactive --no-gpg-checks remove emacs=1.0 vim=2.0" ) provider.remove_package(%w{emacs vim}, ["1.0", "2.0"]) end end end chef-12.14.60/spec/unit/provider/package_spec.rb000066400000000000000000001054571276456504500214010ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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 do let(:node) do node = Chef::Node.new node.automatic_attrs[:platform] = :just_testing node.automatic_attrs[:platform_version] = :just_testing node end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) { Chef::Resource::Package.new("emacs") } let(:current_resource) { Chef::Resource::Package.new("emacs") } let(:candidate_version) { "1.0" } let(:provider) do provider = Chef::Provider::Package.new(new_resource, run_context) provider.current_resource = current_resource provider.candidate_version = candidate_version provider end describe "when installing a package" do before(:each) do provider.current_resource = current_resource allow(provider).to receive(:install_package).and_return(true) end it "raises a Chef::Exceptions::InvalidResourceSpecification if both multipackage and source are provided" do new_resource.package_name(%w{a b}) new_resource.source("foo") expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::InvalidResourceSpecification) end it "should raise a Chef::Exceptions::Package if no version is specified, and no candidate is available" do provider.candidate_version = nil expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end it "should call preseed_package if a response_file is given" do new_resource.response_file("foo") expect(provider).to receive(:get_preseed_file).with( new_resource.name, provider.candidate_version ).and_return("/var/cache/preseed-test") expect(provider).to receive(:preseed_package).with( "/var/cache/preseed-test" ).and_return(true) provider.run_action(:install) end it "should not call preseed_package if a response_file is not given" do expect(provider).not_to receive(:preseed_package) provider.run_action(:install) end it "should install the package at the candidate_version if it is not already installed" do expect(provider).to receive(:install_package).with( new_resource.name, provider.candidate_version ).and_return(true) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end it "should install the package at the version specified if it is not already installed" do new_resource.version("1.0") expect(provider).to receive(:install_package).with( new_resource.name, new_resource.version ).and_return(true) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end it "should install the package at the version specified if a different version is installed" do new_resource.version("1.0") allow(current_resource).to receive(:version).and_return("0.99") expect(provider).to receive(:install_package).with( new_resource.name, new_resource.version ).and_return(true) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action end it "should not install the package if it is already installed and no version is specified" do current_resource.version("1.0") expect(provider).not_to receive(:install_package) provider.run_action(:install) expect(new_resource).not_to be_updated_by_last_action end it "should not install the package if it is already installed at the version specified" do current_resource.version("1.0") new_resource.version("1.0") expect(provider).not_to receive(:install_package) provider.run_action(:install) expect(new_resource).not_to be_updated_by_last_action end it "should call the candidate_version accessor only once if the package is already installed and no version is specified" do current_resource.version("1.0") allow(provider).to receive(:candidate_version).and_return("1.0") provider.run_action(:install) end it "should call the candidate_version accessor only once if the package is already installed at the version specified" do current_resource.version("1.0") new_resource.version("1.0") provider.run_action(:install) end it "should set the resource to updated if it installs the package" do provider.run_action(:install) expect(new_resource).to be_updated end end describe "when upgrading the package" do before(:each) do allow(provider).to receive(:upgrade_package).and_return(true) end it "should upgrade the package if the current version is not the candidate version" do expect(provider).to receive(:upgrade_package).with( new_resource.name, provider.candidate_version ).and_return(true) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action end it "should set the resource to updated if it installs the package" do provider.run_action(:upgrade) expect(new_resource).to be_updated end it "should not install the package if the current version is the candidate version" do current_resource.version "1.0" expect(provider).not_to receive(:upgrade_package) provider.run_action(:upgrade) expect(new_resource).not_to be_updated_by_last_action end it "should print the word 'uninstalled' if there was no original version" do allow(current_resource).to receive(:version).and_return(nil) expect(Chef::Log).to receive(:info).with("package[emacs] upgraded emacs to 1.0") provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action end it "should raise a Chef::Exceptions::Package if current version and candidate are nil" do allow(current_resource).to receive(:version).and_return(nil) provider.candidate_version = nil expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package) end it "should not install the package if candidate version is nil" do current_resource.version "1.0" provider.candidate_version = nil expect(provider).not_to receive(:upgrade_package) provider.run_action(:upgrade) expect(new_resource).not_to be_updated_by_last_action end end describe "When removing the package" do before(:each) do allow(provider).to receive(:remove_package).and_return(true) current_resource.version "1.4.2" end it "should remove the package if it is installed" do expect(provider).to be_removing_package expect(provider).to receive(:remove_package).with("emacs", nil) provider.run_action(:remove) expect(new_resource).to be_updated expect(new_resource).to be_updated_by_last_action end it "should remove the package at a specific version if it is installed at that version" do new_resource.version "1.4.2" expect(provider).to be_removing_package expect(provider).to receive(:remove_package).with("emacs", "1.4.2") provider.run_action(:remove) expect(new_resource).to be_updated_by_last_action end it "should not remove the package at a specific version if it is not installed at that version" do new_resource.version "1.0" expect(provider).not_to be_removing_package expect(provider).not_to receive(:remove_package) provider.run_action(:remove) expect(new_resource).not_to be_updated_by_last_action end it "should not remove the package if it is not installed" do expect(provider).not_to receive(:remove_package) allow(current_resource).to receive(:version).and_return(nil) provider.run_action(:remove) expect(new_resource).not_to be_updated_by_last_action end it "should set the resource to updated if it removes the package" do provider.run_action(:remove) expect(new_resource).to be_updated end end describe "When purging the package" do before(:each) do allow(provider).to receive(:purge_package).and_return(true) current_resource.version "1.4.2" end it "should purge the package if it is installed" do expect(provider).to be_removing_package expect(provider).to receive(:purge_package).with("emacs", nil) provider.run_action(:purge) expect(new_resource).to be_updated expect(new_resource).to be_updated_by_last_action end it "should purge the package at a specific version if it is installed at that version" do new_resource.version "1.4.2" expect(provider).to be_removing_package expect(provider).to receive(:purge_package).with("emacs", "1.4.2") provider.run_action(:purge) expect(new_resource).to be_updated_by_last_action end it "should not purge the package at a specific version if it is not installed at that version" do new_resource.version "1.0" expect(provider).not_to be_removing_package expect(provider).not_to receive(:purge_package) provider.run_action(:purge) expect(new_resource).not_to be_updated_by_last_action end it "should not purge the package if it is not installed" do current_resource.instance_variable_set(:@version, nil) expect(provider).not_to be_removing_package expect(provider).not_to receive(:purge_package) provider.run_action(:purge) expect(new_resource).not_to be_updated_by_last_action end it "should set the resource to updated if it purges the package" do provider.run_action(:purge) expect(new_resource).to be_updated end end describe "when reconfiguring the package" do before(:each) do allow(provider).to receive(:reconfig_package).and_return(true) end it "should info log, reconfigure the package and update the resource" do allow(current_resource).to receive(:version).and_return("1.0") allow(new_resource).to receive(:response_file).and_return(true) expect(provider).to receive(:get_preseed_file).and_return("/var/cache/preseed-test") allow(provider).to receive(:preseed_package).and_return(true) allow(provider).to receive(:reconfig_package).and_return(true) expect(Chef::Log).to receive(:info).with("package[emacs] reconfigured") expect(provider).to receive(:reconfig_package) provider.run_action(:reconfig) expect(new_resource).to be_updated expect(new_resource).to be_updated_by_last_action end it "should debug log and not reconfigure the package if the package is not installed" do allow(current_resource).to receive(:version).and_return(nil) expect(Chef::Log).to receive(:debug).with("package[emacs] is NOT installed - nothing to do") expect(provider).not_to receive(:reconfig_package) provider.run_action(:reconfig) expect(new_resource).not_to be_updated_by_last_action end it "should debug log and not reconfigure the package if no response_file is given" do allow(current_resource).to receive(:version).and_return("1.0") allow(new_resource).to receive(:response_file).and_return(nil) expect(Chef::Log).to receive(:debug).with("package[emacs] no response_file provided - nothing to do") expect(provider).not_to receive(:reconfig_package) provider.run_action(:reconfig) expect(new_resource).not_to be_updated_by_last_action end it "should debug log and not reconfigure the package if the response_file has not changed" do allow(current_resource).to receive(:version).and_return("1.0") allow(new_resource).to receive(:response_file).and_return(true) expect(provider).to receive(:get_preseed_file).and_return(false) allow(provider).to receive(:preseed_package).and_return(false) expect(Chef::Log).to receive(:debug).with("package[emacs] preseeding has not changed - nothing to do") expect(provider).not_to receive(:reconfig_package) provider.run_action(:reconfig) expect(new_resource).not_to be_updated_by_last_action end end describe "when running commands to be implemented by subclasses" do it "should raises UnsupportedAction for install" do expect { provider.install_package("emacs", "1.4.2") }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should raises UnsupportedAction for upgrade" do expect { provider.upgrade_package("emacs", "1.4.2") }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should raises UnsupportedAction for remove" do expect { provider.remove_package("emacs", "1.4.2") }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should raises UnsupportedAction for purge" do expect { provider.purge_package("emacs", "1.4.2") }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should raise UnsupportedAction for preseed_package" do preseed_file = "/tmp/sun-jdk-package-preseed-file.seed" expect { provider.preseed_package(preseed_file) }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should raise UnsupportedAction for reconfig" do expect { provider.reconfig_package("emacs", "1.4.2") }.to raise_error(Chef::Exceptions::UnsupportedAction) end end describe "when given a response file" do let(:cookbook_repo) { File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) } let(:cookbook_loader) do Chef::Cookbook::FileVendor.fetch_from_disk(cookbook_repo) Chef::CookbookLoader.new(cookbook_repo) end let(:cookbook_collection) do cookbook_loader.load_cookbooks Chef::CookbookCollection.new(cookbook_loader) end let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) } let(:new_resource) do new_resource = Chef::Resource::Package.new("emacs") new_resource.response_file("java.response") new_resource.cookbook_name = "java" new_resource end describe "creating the cookbook file resource to fetch the response file" do before do expect(Chef::FileCache).to receive(:create_cache_path).with("preseed/java").and_return("/tmp/preseed/java") end it "sets the preseed resource's runcontext to its own run context" do allow(Chef::FileCache).to receive(:create_cache_path).and_return("/tmp/preseed/java") expect(provider.preseed_resource("java", "6").run_context).not_to be_nil expect(provider.preseed_resource("java", "6").run_context).to equal(provider.run_context) end it "should set the cookbook name of the remote file to the new resources cookbook name" do expect(provider.preseed_resource("java", "6").cookbook_name).to eq("java") end it "should set remote files source to the new resources response file" do expect(provider.preseed_resource("java", "6").source).to eq("java.response") end it "should never back up the cached response file" do expect(provider.preseed_resource("java", "6").backup).to be_falsey end it "sets the install path of the resource to $file_cache/$cookbook/$pkg_name-$pkg_version.seed" do expect(provider.preseed_resource("java", "6").path).to eq("/tmp/preseed/java/java-6.seed") end end describe "when installing the preseed file to the cache location" do let(:response_file_destination) { Dir.tmpdir + "/preseed--java--java-6.seed" } let(:response_file_resource) do response_file_resource = Chef::Resource::CookbookFile.new(response_file_destination, run_context) response_file_resource.cookbook_name = "java" response_file_resource.backup(false) response_file_resource.source("java.response") response_file_resource end before do expect(provider).to receive(:preseed_resource).with("java", "6").and_return(response_file_resource) end after do FileUtils.rm(response_file_destination) if ::File.exist?(response_file_destination) end it "creates the preseed file in the cache" do expect(response_file_resource).to receive(:run_action).with(:create) provider.get_preseed_file("java", "6") end it "returns the path to the response file if the response file was updated" do expect(provider.get_preseed_file("java", "6")).to eq(response_file_destination) end it "should return false if the response file has not been updated" do response_file_resource.updated_by_last_action(false) expect(response_file_resource).not_to be_updated_by_last_action # don't let the response_file_resource set updated to true expect(response_file_resource).to receive(:run_action).with(:create) expect(provider.get_preseed_file("java", "6")).to be(false) end end end end describe "Subclass with use_multipackage_api" do class MyPackageResource < Chef::Resource::Package end class MyPackageProvider < Chef::Provider::Package use_multipackage_api end let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) { MyPackageResource.new("installs the packages") } let(:current_resource) { MyPackageResource.new("installs the packages") } let(:provider) do provider = MyPackageProvider.new(new_resource, run_context) provider.current_resource = current_resource provider end it "has use_multipackage_api? methods on the class and instance" do expect(MyPackageProvider.use_multipackage_api?).to be true expect(provider.use_multipackage_api?).to be true end context "#a_to_s utility for subclasses" do it "converts varargs of strings to a single string" do expect(provider.send(:a_to_s, "a", nil, "b", "", "c", " ", "d e", "f-g")).to eq("a b c d e f-g") end it "converts an array of strings to a single string" do expect(provider.send(:a_to_s, ["a", nil, "b", "", "c", " ", "d e", "f-g"])).to eq("a b c d e f-g") end it "converts a mishmash of array args to a single string" do expect(provider.send(:a_to_s, "a", [ nil, "b", "", [ "c" ] ], " ", [ "d e", "f-g" ])).to eq("a b c d e f-g") end end it "when user passes string to package_name, passes arrays to install_package" do new_resource.package_name "vim" new_resource.version nil provider.candidate_version = [ "1.0" ] expect(provider).to receive(:install_package).with( [ "vim" ], [ "1.0" ] ).and_return(true) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action expect(new_resource.version).to eql(nil) end it "when user pases string to package_name and version, passes arrays to install_package" do new_resource.package_name "vim" new_resource.version "1.0" provider.candidate_version = [ "1.0" ] expect(provider).to receive(:install_package).with( [ "vim" ], [ "1.0" ] ).and_return(true) provider.run_action(:install) expect(new_resource).to be_updated_by_last_action expect(new_resource.version).to eql("1.0") end it "when user passes string to package_name, passes arrays to upgrade_package" do new_resource.package_name "vim" new_resource.version nil provider.candidate_version = [ "1.0" ] expect(provider).to receive(:upgrade_package).with( [ "vim" ], [ "1.0" ] ).and_return(true) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action expect(new_resource.version).to eql(nil) end it "when user pases string to package_name and version, passes arrays to upgrade_package" do new_resource.package_name "vim" new_resource.version "1.0" provider.candidate_version = [ "1.0" ] expect(provider).to receive(:upgrade_package).with( [ "vim" ], [ "1.0" ] ).and_return(true) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action expect(new_resource.version).to eql("1.0") end it "when user passes string to package_name, passes arrays to remove_package" do new_resource.package_name "vim" current_resource.package_name "vim" current_resource.version [ "1.0" ] provider.candidate_version = [ "1.0" ] expect(provider).to receive(:remove_package).with( [ "vim" ], [ nil ] ).and_return(true) provider.run_action(:remove) expect(new_resource).to be_updated_by_last_action expect(new_resource.version).to eql(nil) end it "when user passes string to package_name, passes arrays to purge_package" do new_resource.package_name "vim" current_resource.package_name "vim" current_resource.version [ "1.0" ] provider.candidate_version = [ "1.0" ] expect(provider).to receive(:purge_package).with( [ "vim" ], [ nil ] ).and_return(true) provider.run_action(:purge) expect(new_resource).to be_updated_by_last_action expect(new_resource.version).to eql(nil) end it "when user passes string to package_name, passes arrays to reconfig_package" do new_resource.package_name "vim" current_resource.package_name "vim" current_resource.version [ "1.0" ] allow(new_resource).to receive(:response_file).and_return(true) expect(provider).to receive(:get_preseed_file).and_return("/var/cache/preseed-test") allow(provider).to receive(:preseed_package).and_return(true) allow(provider).to receive(:reconfig_package).and_return(true) expect(provider).to receive(:reconfig_package).with( [ "vim" ], [ "1.0" ] ).and_return(true) provider.run_action(:reconfig) expect(new_resource).to be_updated_by_last_action end end describe "Chef::Provider::Package - Multi" do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) { Chef::Resource::Package.new(%w{emacs vi}) } let(:current_resource) { Chef::Resource::Package.new(%w{emacs vi}) } let(:candidate_version) { [ "1.0", "6.2" ] } class MyPackageProvider < Chef::Provider::Package use_multipackage_api end let(:provider) do provider = MyPackageProvider.new(new_resource, run_context) provider.current_resource = current_resource provider.candidate_version = candidate_version provider end describe "when installing multiple packages" do before(:each) do provider.current_resource = current_resource allow(provider).to receive(:install_package).and_return(true) end it "installs the candidate versions when none are installed" do expect(provider).to receive(:install_package).with( %w{emacs vi}, ["1.0", "6.2"] ).and_return(true) provider.run_action(:install) expect(new_resource).to be_updated end it "installs the candidate versions when some are installed" do expect(provider).to receive(:install_package).with( [ "vi" ], [ "6.2" ] ).and_return(true) current_resource.version(["1.0", nil]) provider.run_action(:install) expect(new_resource).to be_updated end it "installs the specified version when some are out of date" do current_resource.version(["1.0", "6.2"]) new_resource.version(["1.0", "6.1"]) provider.run_action(:install) expect(new_resource).to be_updated end it "does not install any version if all are installed at the right version" do current_resource.version(["1.0", "6.2"]) new_resource.version(["1.0", "6.2"]) provider.run_action(:install) expect(new_resource).not_to be_updated_by_last_action end it "does not install any version if all are installed, and no version was specified" do current_resource.version(["1.0", "6.2"]) provider.run_action(:install) expect(new_resource).not_to be_updated_by_last_action end it "raises an exception if both are not installed and no caondidates are available" do current_resource.version([nil, nil]) provider.candidate_version = [nil, nil] expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end it "raises an exception if one is not installed and no candidates are available" do current_resource.version(["1.0", nil]) provider.candidate_version = ["1.0", nil] expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end it "does not raise an exception if the packages are installed or have a candidate" do current_resource.version(["1.0", nil]) provider.candidate_version = [nil, "6.2"] expect { provider.run_action(:install) }.not_to raise_error end it "raises an exception if an explicit version is asked for, an old version is installed, but no candidate" do new_resource.version ["1.0", "6.2"] current_resource.version(["1.0", "6.1"]) provider.candidate_version = ["1.0", nil] expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end it "does not raise an exception if an explicit version is asked for, and is installed, but no candidate" do new_resource.version ["1.0", "6.2"] current_resource.version(["1.0", "6.2"]) provider.candidate_version = ["1.0", nil] expect { provider.run_action(:install) }.not_to raise_error end it "raise an exception if an explicit version is asked for, and is not installed, and no candidate" do new_resource.version ["1.0", "6.2"] current_resource.version(["1.0", nil]) provider.candidate_version = ["1.0", nil] expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package) end it "does not raise an exception if an explicit version is asked for, and is not installed, and there is a candidate" do new_resource.version ["1.0", "6.2"] current_resource.version(["1.0", nil]) provider.candidate_version = ["1.0", "6.2"] expect { provider.run_action(:install) }.not_to raise_error end end describe "when upgrading multiple packages" do before(:each) do provider.current_resource = current_resource allow(provider).to receive(:upgrade_package).and_return(true) end it "should upgrade the package if the current versions are not the candidate version" do current_resource.version ["0.9", "6.1"] expect(provider).to receive(:upgrade_package).with( new_resource.package_name, provider.candidate_version ).and_return(true) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action end it "should upgrade the package if some of current versions are not the candidate versions" do current_resource.version ["1.0", "6.1"] expect(provider).to receive(:upgrade_package).with( ["vi"], ["6.2"] ).and_return(true) provider.run_action(:upgrade) expect(new_resource).to be_updated_by_last_action end it "should not install the package if the current versions are the candidate version" do current_resource.version ["1.0", "6.2"] expect(provider).not_to receive(:upgrade_package) provider.run_action(:upgrade) expect(new_resource).not_to be_updated_by_last_action end it "should raise an exception if both are not installed and no caondidates are available" do current_resource.version([nil, nil]) provider.candidate_version = [nil, nil] expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package) end it "should raise an exception if one is not installed and no candidates are available" do current_resource.version(["1.0", nil]) provider.candidate_version = ["1.0", nil] expect { provider.run_action(:upgrade) }.to raise_error(Chef::Exceptions::Package) end it "should not raise an exception if the packages are installed or have a candidate" do current_resource.version(["1.0", nil]) provider.candidate_version = [nil, "6.2"] expect { provider.run_action(:upgrade) }.not_to raise_error end it "should not raise an exception if the packages are installed or have a candidate" do current_resource.version(["1.0", nil]) provider.candidate_version = [nil, "6.2"] expect { provider.run_action(:upgrade) }.not_to raise_error end end describe "When removing multiple packages " do before(:each) do allow(provider).to receive(:remove_package).and_return(true) current_resource.version ["1.0", "6.2"] end it "should remove the packages if all are installed" do expect(provider).to be_removing_package expect(provider).to receive(:remove_package).with(%w{emacs vi}, [nil]) provider.run_action(:remove) expect(new_resource).to be_updated expect(new_resource).to be_updated_by_last_action end it "should remove the packages if some are installed" do current_resource.version ["1.0", nil] expect(provider).to be_removing_package expect(provider).to receive(:remove_package).with(%w{emacs vi}, [nil]) provider.run_action(:remove) expect(new_resource).to be_updated expect(new_resource).to be_updated_by_last_action end it "should remove the packages at a specific version if they are installed at that version" do new_resource.version ["1.0", "6.2"] expect(provider).to be_removing_package expect(provider).to receive(:remove_package).with(%w{emacs vi}, ["1.0", "6.2"]) provider.run_action(:remove) expect(new_resource).to be_updated_by_last_action end it "should remove the packages at a specific version any are is installed at that version" do new_resource.version ["0.5", "6.2"] expect(provider).to be_removing_package expect(provider).to receive(:remove_package).with(%w{emacs vi}, ["0.5", "6.2"]) provider.run_action(:remove) expect(new_resource).to be_updated_by_last_action end it "should not remove the packages at a specific version if they are not installed at that version" do new_resource.version ["0.5", "6.0"] expect(provider).not_to be_removing_package expect(provider).not_to receive(:remove_package) provider.run_action(:remove) expect(new_resource).not_to be_updated_by_last_action end it "should not remove the packages if they are not installed" do expect(provider).not_to receive(:remove_package) allow(current_resource).to receive(:version).and_return(nil) provider.run_action(:remove) expect(new_resource).not_to be_updated_by_last_action end end describe "When purging multiple packages " do before(:each) do allow(provider).to receive(:purge_package).and_return(true) current_resource.version ["1.0", "6.2"] end it "should purge the packages if all are installed" do expect(provider).to be_removing_package expect(provider).to receive(:purge_package).with(%w{emacs vi}, [nil]) provider.run_action(:purge) expect(new_resource).to be_updated expect(new_resource).to be_updated_by_last_action end it "should purge the packages if some are installed" do current_resource.version ["1.0", nil] expect(provider).to be_removing_package expect(provider).to receive(:purge_package).with(%w{emacs vi}, [nil]) provider.run_action(:purge) expect(new_resource).to be_updated expect(new_resource).to be_updated_by_last_action end it "should purge the packages at a specific version if they are installed at that version" do new_resource.version ["1.0", "6.2"] expect(provider).to be_removing_package expect(provider).to receive(:purge_package).with(%w{emacs vi}, ["1.0", "6.2"]) provider.run_action(:purge) expect(new_resource).to be_updated_by_last_action end it "should purge the packages at a specific version any are is installed at that version" do new_resource.version ["0.5", "6.2"] expect(provider).to be_removing_package expect(provider).to receive(:purge_package).with(%w{emacs vi}, ["0.5", "6.2"]) provider.run_action(:purge) expect(new_resource).to be_updated_by_last_action end it "should not purge the packages at a specific version if they are not installed at that version" do new_resource.version ["0.5", "6.0"] expect(provider).not_to be_removing_package expect(provider).not_to receive(:purge_package) provider.run_action(:purge) expect(new_resource).not_to be_updated_by_last_action end it "should not purge the packages if they are not installed" do expect(provider).not_to receive(:purge_package) allow(current_resource).to receive(:version).and_return(nil) provider.run_action(:purge) expect(new_resource).not_to be_updated_by_last_action end end describe "shell_out helpers" do [ :shell_out_with_timeout, :shell_out_with_timeout! ].each do |method| stubbed_method = method == :shell_out_with_timeout! ? :shell_out! : :shell_out [ %w{command arg1 arg2}, "command arg1 arg2" ].each do |command| it "#{method} defaults to 900 seconds" do expect(provider).to receive(stubbed_method).with(*command, timeout: 900) provider.send(method, *command) end it "#{method} overrides the default timeout with its options" do expect(provider).to receive(stubbed_method).with(*command, timeout: 1) provider.send(method, *command, timeout: 1) end it "#{method} overrides both timeouts with the new_resource.timeout" do new_resource.timeout(99) expect(provider).to receive(stubbed_method).with(*command, timeout: 99) provider.send(method, *command, timeout: 1) end it "#{method} defaults to 900 seconds and preserves options" do expect(provider).to receive(stubbed_method).with(*command, env: nil, timeout: 900) provider.send(method, *command, env: nil) end it "#{method} overrides the default timeout with its options and preserves options" do expect(provider).to receive(stubbed_method).with(*command, timeout: 1, env: nil) provider.send(method, *command, timeout: 1, env: nil) end it "#{method} overrides both timeouts with the new_resource.timeout and preseves options" do new_resource.timeout(99) expect(provider).to receive(stubbed_method).with(*command, timeout: 99, env: nil) provider.send(method, *command, timeout: 1, env: nil) end end end end end chef-12.14.60/spec/unit/provider/powershell_script_spec.rb000066400000000000000000000100221276456504500235350ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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::PowershellScript, "action_run" do let(:powershell_version) { nil } let(:node) do node = Chef::Node.new node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s if ! powershell_version.nil? node.default[:languages] = { :powershell => { :version => powershell_version } } end node end let(:provider) do empty_events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, empty_events) new_resource = Chef::Resource::PowershellScript.new("run some powershell code", run_context) Chef::Provider::PowershellScript.new(new_resource, run_context) end context "when setting interpreter flags" do context "on nano" do before(:each) do allow(Chef::Platform).to receive(:windows_nano_server?).and_return(true) allow(provider).to receive(:is_forced_32bit).and_return(false) os_info_double = double("os_info") allow(provider.run_context.node["kernel"]).to receive(:[]).with("os_info").and_return(os_info_double) allow(os_info_double).to receive(:[]).with("system_directory").and_return("C:\\Windows\\system32") end it "sets the -Command flag as the last flag" do flags = provider.command.split(" ").keep_if { |flag| flag =~ /^-/ } expect(flags.pop).to eq("-Command") end end context "not on nano" do before(:each) do allow(Chef::Platform).to receive(:windows_nano_server?).and_return(false) allow(provider).to receive(:is_forced_32bit).and_return(false) os_info_double = double("os_info") allow(provider.run_context.node["kernel"]).to receive(:[]).with("os_info").and_return(os_info_double) allow(os_info_double).to receive(:[]).with("system_directory").and_return("C:\\Windows\\system32") end it "sets the -File flag as the last flag" do flags = provider.command.split(" ").keep_if { |flag| flag =~ /^-/ } expect(flags.pop).to eq("-File") end let(:execution_policy_flag) do execution_policy_index = 0 provider_flags = provider.flags.split(" ") execution_policy_specified = false provider_flags.find do |value| execution_policy_index += 1 execution_policy_specified = value.casecmp("-ExecutionPolicy".downcase).zero? end execution_policy = execution_policy_specified ? provider_flags[execution_policy_index] : nil end context "when running with an unspecified PowerShell version" do let(:powershell_version) { nil } it "sets the -ExecutionPolicy flag to 'Unrestricted' by default" do expect(execution_policy_flag.downcase).to eq("unrestricted".downcase) end end { "2.0" => "Unrestricted", "2.5" => "Unrestricted", "3.0" => "Bypass", "3.6" => "Bypass", "4.0" => "Bypass", "5.0" => "Bypass" }.each do |version_policy| let(:powershell_version) { version_policy[0].to_f } context "when running PowerShell version #{version_policy[0]}" do let(:powershell_version) { version_policy[0].to_f } it "sets the -ExecutionPolicy flag to '#{version_policy[1]}'" do expect(execution_policy_flag.downcase).to eq(version_policy[1].downcase) end end end end end end chef-12.14.60/spec/unit/provider/registry_key_spec.rb000066400000000000000000000311671276456504500225220ustar00rootroot00000000000000# # Author:: Lamont Granquist (lamont@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" shared_examples_for "a registry key" do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::RegistryKey.new("windows is fun", @run_context) @new_resource.key keyname @new_resource.values( testval1 ) @new_resource.recursive false @provider = Chef::Provider::RegistryKey.new(@new_resource, @run_context) allow(@provider).to receive(:running_on_windows!).and_return(true) @double_registry = double(Chef::Win32::Registry) allow(@provider).to receive(:registry).and_return(@double_registry) end describe "when first created" do end describe "executing load_current_resource" do describe "when the key exists" do before(:each) do expect(@double_registry).to receive(:key_exists?).with(keyname).and_return(true) expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval2 ) @provider.load_current_resource end it "should set the key of the current resource to the key of the new resource" do expect(@provider.current_resource.key).to eq(@new_resource.key) end it "should set the architecture of the current resource to the architecture of the new resource" do expect(@provider.current_resource.architecture).to eq(@new_resource.architecture) end it "should set the recursive flag of the current resource to the recursive flag of the new resource" do expect(@provider.current_resource.recursive).to eq(@new_resource.recursive) end it "should set the unscrubbed values of the current resource to the values it got from the registry" do expect(@provider.current_resource.unscrubbed_values).to eq([ testval2 ]) end end describe "when the key does not exist" do before(:each) do expect(@double_registry).to receive(:key_exists?).with(keyname).and_return(false) @provider.load_current_resource end it "should set the values in the current resource to empty array" do expect(@provider.current_resource.values).to eq([]) end end end describe "action_create" do context "when a case insensitive match for the key exists" do before(:each) do expect(@double_registry).to receive(:key_exists?).twice.with(keyname.downcase).and_return(true) end it "should do nothing if the if a case insensitive key and the value both exist" do @provider.new_resource.key(keyname.downcase) expect(@double_registry).to receive(:get_values).with(keyname.downcase).and_return( testval1 ) expect(@double_registry).not_to receive(:set_value) @provider.load_current_resource @provider.action_create end end context "when the key exists" do before(:each) do expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(true) end it "should do nothing if the key and the value both exist" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1 ) expect(@double_registry).not_to receive(:set_value) @provider.load_current_resource @provider.action_create end it "should create the value if the key exists but the value does not" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval2 ) expect(@double_registry).to receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create end it "should set the value if the key exists but the data does not match" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1_wrong_data ) expect(@double_registry).to receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create end it "should set the value if the key exists but the type does not match" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1_wrong_type ) expect(@double_registry).to receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create end end context "when the key exists and the values in the new resource are empty" do it "when a value is in the key, it should do nothing" do @provider.new_resource.values([]) expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(true) expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1 ) expect(@double_registry).not_to receive(:create_key) expect(@double_registry).not_to receive(:set_value) @provider.load_current_resource @provider.action_create end it "when no value is in the key, it should do nothing" do @provider.new_resource.values([]) expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(true) expect(@double_registry).to receive(:get_values).with(keyname).and_return( nil ) expect(@double_registry).not_to receive(:create_key) expect(@double_registry).not_to receive(:set_value) @provider.load_current_resource @provider.action_create end end context "when the key does not exist" do before(:each) do expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(false) end it "should create the key and the value" do expect(@double_registry).to receive(:create_key).with(keyname, false) expect(@double_registry).to receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create end end context "when the key does not exist and the values in the new resource are empty" do it "should create the key" do @new_resource.values([]) expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(false) expect(@double_registry).to receive(:create_key).with(keyname, false) expect(@double_registry).not_to receive(:set_value) @provider.load_current_resource @provider.action_create end end end describe "action_create_if_missing" do context "when the key exists" do before(:each) do expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(true) end it "should do nothing if the key and the value both exist" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1 ) expect(@double_registry).not_to receive(:set_value) @provider.load_current_resource @provider.action_create_if_missing end it "should create the value if the key exists but the value does not" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval2 ) expect(@double_registry).to receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create_if_missing end it "should not set the value if the key exists but the data does not match" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1_wrong_data ) expect(@double_registry).not_to receive(:set_value) @provider.load_current_resource @provider.action_create_if_missing end it "should not set the value if the key exists but the type does not match" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1_wrong_type ) expect(@double_registry).not_to receive(:set_value) @provider.load_current_resource @provider.action_create_if_missing end end context "when the key does not exist" do before(:each) do expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(false) end it "should create the key and the value" do expect(@double_registry).to receive(:create_key).with(keyname, false) expect(@double_registry).to receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create_if_missing end end end describe "action_delete" do context "when the key exists" do before(:each) do expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(true) end it "deletes the value when the value exists" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1 ) expect(@double_registry).to receive(:delete_value).with(keyname, testval1) @provider.load_current_resource @provider.action_delete end it "deletes the value when the value exists, but the type is wrong" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1_wrong_type ) expect(@double_registry).to receive(:delete_value).with(keyname, testval1) @provider.load_current_resource @provider.action_delete end it "deletes the value when the value exists, but the data is wrong" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1_wrong_data ) expect(@double_registry).to receive(:delete_value).with(keyname, testval1) @provider.load_current_resource @provider.action_delete end it "does not delete the value when the value does not exist" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval2 ) expect(@double_registry).not_to receive(:delete_value) @provider.load_current_resource @provider.action_delete end end context "when the key does not exist" do before(:each) do expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(false) end it "does nothing" do expect(@double_registry).not_to receive(:delete_value) @provider.load_current_resource @provider.action_delete end end end describe "action_delete_key" do context "when the key exists" do before(:each) do expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(true) end it "deletes the key" do expect(@double_registry).to receive(:get_values).with(keyname).and_return( testval1 ) expect(@double_registry).to receive(:delete_key).with(keyname, false) @provider.load_current_resource @provider.action_delete_key end end context "when the key does not exist" do before(:each) do expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(false) end it "does nothing" do expect(@double_registry).not_to receive(:delete_key) @provider.load_current_resource @provider.action_delete_key end end end end describe Chef::Provider::RegistryKey do context "when the key data is safe" do let(:keyname) { 'HKLM\Software\Opscode\Testing\Safe' } let(:testval1) { { :name => "one", :type => :string, :data => "1" } } let(:testval1_wrong_type) { { :name => "one", :type => :multi_string, :data => "1" } } let(:testval1_wrong_data) { { :name => "one", :type => :string, :data => "2" } } let(:testval2) { { :name => "two", :type => :string, :data => "2" } } it_should_behave_like "a registry key" end context "when the key data is unsafe" do let(:keyname) { 'HKLM\Software\Opscode\Testing\Unsafe' } let(:testval1) { { :name => "one", :type => :binary, :data => 255.chr * 1 } } let(:testval1_wrong_type) { { :name => "one", :type => :string, :data => 255.chr * 1 } } let(:testval1_wrong_data) { { :name => "one", :type => :binary, :data => 254.chr * 1 } } let(:testval2) { { :name => "two", :type => :binary, :data => 0.chr * 1 } } it_should_behave_like "a registry key" end end chef-12.14.60/spec/unit/provider/remote_directory_spec.rb000066400000000000000000000241341276456504500233550ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-2016, Daniel DeLeo # 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 "digest/md5" require "tmpdir" require "chef/mixin/file_class" class Chef::CFCCheck include Chef::Mixin::FileClass end describe Chef::Provider::RemoteDirectory do before do allow_any_instance_of(Chef::FileAccessControl).to receive(:set_all) @resource = Chef::Resource::RemoteDirectory.new(File.join(Dir.tmpdir, "tafty")) # in CHEF_SPEC_DATA/cookbooks/openldap/files/default/remotedir @resource.source "remotedir" @resource.cookbook("openldap") @cookbook_repo = ::File.expand_path(::File.join(CHEF_SPEC_DATA, "cookbooks")) Chef::Cookbook::FileVendor.fetch_from_disk(@cookbook_repo) @node = Chef::Node.new cl = Chef::CookbookLoader.new(@cookbook_repo) cl.load_cookbooks @cookbook_collection = Chef::CookbookCollection.new(cl) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) @provider = Chef::Provider::RemoteDirectory.new(@resource, @run_context) @provider.current_resource = @resource.clone end describe "when the contents of the directory changed on the first run and not on the second run" do before do @resource_second_run = @resource.clone @provider_second_run = Chef::Provider::RemoteDirectory.new(@resource_second_run, @run_context) @provider.run_action(:create) @provider_second_run.run_action(:create) end it "identifies that the state has changed the after first run" do @provider_second_run.new_resource.updated_by_last_action? == true end it "identifies that the state has not changed after the second run" do @provider_second_run.new_resource.updated_by_last_action? == false end end describe "when access control is configured on the resource" do before do @resource.mode "0750" @resource.group "wheel" @resource.owner "root" @resource.files_mode "0640" @resource.files_group "staff" @resource.files_owner "toor" @resource.files_backup 23 @resource.source "remotedir_root" end it "configures access control on intermediate directorys" do directory_resource = @provider.send(:directory_resource, File.join(Dir.tmpdir, "intermediate_dir")) expect(directory_resource.path).to eq(File.join(Dir.tmpdir, "intermediate_dir")) expect(directory_resource.mode).to eq("0750") expect(directory_resource.group).to eq("wheel") expect(directory_resource.owner).to eq("root") expect(directory_resource.recursive).to be_truthy end it "configures access control on files in the directory" do @resource.cookbook "berlin_style_tasty_cupcakes" cookbook_file = @provider.send(:cookbook_file_resource, "/target/destination/path.txt", "relative/source/path.txt") expect(cookbook_file.cookbook_name).to eq("berlin_style_tasty_cupcakes") expect(cookbook_file.source).to eq("remotedir_root/relative/source/path.txt") expect(cookbook_file.mode).to eq("0640") expect(cookbook_file.group).to eq("staff") expect(cookbook_file.owner).to eq("toor") expect(cookbook_file.backup).to eq(23) end it "respects sensitive flag" do @resource.cookbook "gondola_rides" @resource.sensitive true cookbook_file = @provider.send(:cookbook_file_resource, "/target/destination/path.txt", "relative/source/path.txt") expect(cookbook_file.sensitive).to eq(true) @resource.sensitive false cookbook_file = @provider.send(:cookbook_file_resource, "/target/destination/path.txt", "relative/source/path.txt") expect(cookbook_file.sensitive).to eq(false) end end describe "when creating the remote directory" do before do @node.automatic_attrs[:platform] = :just_testing @node.automatic_attrs[:platform_version] = :just_testing @destination_dir = make_canonical_temp_directory << "/remote_directory_test" @resource.path(@destination_dir) end after { FileUtils.rm_rf(@destination_dir) } # CHEF-3552 it "creates the toplevel directory without error " do @resource.recursive(false) @provider.run_action(:create) expect(::File.exist?(@destination_dir)).to be_truthy end it "transfers the directory with all contents" do @provider.run_action(:create) expect(::File.exist?(@destination_dir + "/remote_dir_file1.txt")).to be_truthy expect(::File.exist?(@destination_dir + "/remote_dir_file2.txt")).to be_truthy expect(::File.exist?(@destination_dir + "/remotesubdir/remote_subdir_file1.txt")).to be_truthy expect(::File.exist?(@destination_dir + "/remotesubdir/remote_subdir_file2.txt")).to be_truthy expect(::File.exist?(@destination_dir + "/remotesubdir/.a_dotfile")).to be_truthy expect(::File.exist?(@destination_dir + "/.a_dotdir/.a_dotfile_in_a_dotdir")).to be_truthy end describe "only if it is missing" do it "should not overwrite existing files" do @resource.overwrite(true) @provider.run_action(:create) File.open(@destination_dir + "/remote_dir_file1.txt", "a") { |f| f.puts "blah blah blah" } File.open(@destination_dir + "/remotesubdir/remote_subdir_file1.txt", "a") { |f| f.puts "blah blah blah" } file1md5 = Digest::MD5.hexdigest(File.read(@destination_dir + "/remote_dir_file1.txt")) subdirfile1md5 = Digest::MD5.hexdigest(File.read(@destination_dir + "/remotesubdir/remote_subdir_file1.txt")) @provider.run_action(:create_if_missing) expect(file1md5.eql?(Digest::MD5.hexdigest(File.read(@destination_dir + "/remote_dir_file1.txt")))).to be_truthy expect(subdirfile1md5.eql?(Digest::MD5.hexdigest(File.read(@destination_dir + "/remotesubdir/remote_subdir_file1.txt")))).to be_truthy end end describe "with purging enabled" do before { @resource.purge(true) } it "removes existing files if purge is true" do @provider.run_action(:create) FileUtils.touch(@destination_dir + "/marked_for_death.txt") FileUtils.touch(@destination_dir + "/remotesubdir/marked_for_death_again.txt") @provider.run_action(:create) expect(::File.exist?(@destination_dir + "/remote_dir_file1.txt")).to be_truthy expect(::File.exist?(@destination_dir + "/remote_dir_file2.txt")).to be_truthy expect(::File.exist?(@destination_dir + "/remotesubdir/remote_subdir_file1.txt")).to be_truthy expect(::File.exist?(@destination_dir + "/remotesubdir/remote_subdir_file2.txt")).to be_truthy expect(::File.exist?(@destination_dir + "/marked_for_death.txt")).to be_falsey expect(::File.exist?(@destination_dir + "/remotesubdir/marked_for_death_again.txt")).to be_falsey end it "removes files in subdirectories before files above" do @provider.run_action(:create) FileUtils.mkdir_p(@destination_dir + "/a/multiply/nested/directory/") FileUtils.touch(@destination_dir + "/a/foo.txt") FileUtils.touch(@destination_dir + "/a/multiply/bar.txt") FileUtils.touch(@destination_dir + "/a/multiply/nested/baz.txt") FileUtils.touch(@destination_dir + "/a/multiply/nested/directory/qux.txt") @provider.run_action(:create) expect(::File.exist?(@destination_dir + "/a/foo.txt")).to be_falsey expect(::File.exist?(@destination_dir + "/a/multiply/bar.txt")).to be_falsey expect(::File.exist?(@destination_dir + "/a/multiply/nested/baz.txt")).to be_falsey expect(::File.exist?(@destination_dir + "/a/multiply/nested/directory/qux.txt")).to be_falsey end it "removes directory symlinks properly", :not_supported_on_win2k3 do symlinked_dir_path = @destination_dir + "/symlinked_dir" @provider.action = :create @provider.run_action @fclass = Chef::CFCCheck.new Dir.mktmpdir do |tmp_dir| begin @fclass.file_class.symlink(tmp_dir.dup, symlinked_dir_path) expect(::File.exist?(symlinked_dir_path)).to be_truthy @provider.run_action expect(::File.exist?(symlinked_dir_path)).to be_falsey expect(::File.exist?(tmp_dir)).to be_truthy rescue Chef::Exceptions::Win32APIError skip "This must be run as an Administrator to create symlinks" end end end end describe "with overwrite disabled" do before { @resource.purge(false) } before { @resource.overwrite(false) } it "leaves modifications alone" do @provider.run_action(:create) ::File.open(@destination_dir + "/remote_dir_file1.txt", "a") { |f| f.puts "blah blah blah" } ::File.open(@destination_dir + "/remotesubdir/remote_subdir_file1.txt", "a") { |f| f.puts "blah blah blah" } file1md5 = Digest::MD5.hexdigest(::File.read(@destination_dir + "/remote_dir_file1.txt")) subdirfile1md5 = Digest::MD5.hexdigest(::File.read(@destination_dir + "/remotesubdir/remote_subdir_file1.txt")) @provider.run_action(:create) expect(file1md5.eql?(Digest::MD5.hexdigest(::File.read(@destination_dir + "/remote_dir_file1.txt")))).to be_truthy expect(subdirfile1md5.eql?(Digest::MD5.hexdigest(::File.read(@destination_dir + "/remotesubdir/remote_subdir_file1.txt")))).to be_truthy end end end end chef-12.14.60/spec/unit/provider/remote_file/000077500000000000000000000000001276456504500207255ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/remote_file/cache_control_data_spec.rb000066400000000000000000000226011276456504500260610ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2013-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 "uri" CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH = 64 CACHE_FILE_CHECKSUM_HEX_LENGTH = 32 CACHE_FILE_JSON_FILE_EXTENSION_LENGTH = 5 CACHE_FILE_PATH_LIMIT = CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH + 1 + CACHE_FILE_CHECKSUM_HEX_LENGTH + CACHE_FILE_JSON_FILE_EXTENSION_LENGTH # {friendly}-{md5hex}.json == 102 describe Chef::Provider::RemoteFile::CacheControlData do let(:uri) { URI.parse("http://www.google.com/robots.txt") } subject(:cache_control_data) do Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, current_file_checksum) end let(:cache_path) { "remote_file/http___www_google_com_robots_txt-6dc1b24315d0cff764d30344199c6f7b.json" } let(:old_cache_path) { "remote_file/http___www_google_com_robots_txt-9839677abeeadf0691026e0cabca2339.json" } # the checksum of the file we have on disk already let(:current_file_checksum) { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } context "when loading data for an unknown URI" do before do expect(Chef::FileCache).to receive(:has_key?).with(cache_path).and_return(false) expect(Chef::FileCache).to receive(:has_key?).with(old_cache_path).and_return(false) end context "and there is no current copy of the file" do let(:current_file_checksum) { nil } it "returns empty cache control data" do expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to be_nil end end it "returns empty cache control data" do expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to be_nil end context "and the URI contains a password" do let(:uri) { URI.parse("http://bob:password@example.org/") } let(:cache_path) { "remote_file/http___bob_XXXX_example_org_-44be109aa176a165ef599c12d97af792.json" } let(:old_cache_path) { "remote_file/http___bob_XXXX_example_org_-f121caacb74c05a35bcefdf578ed5fc9.json" } it "loads the cache data from a path based on a sanitized URI" do Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, current_file_checksum) end end end describe "when loading data for a known URI" do # the checksum of the file last we fetched it. let(:last_fetched_checksum) { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } let(:etag) { "\"a-strong-identifier\"" } let(:mtime) { "Tue, 21 May 2013 19:19:23 GMT" } let(:cache_json_data) do cache = {} cache["etag"] = etag cache["mtime"] = mtime cache["checksum"] = last_fetched_checksum Chef::JSONCompat.to_json(cache) end context "when the cache control data uses sha256 for its name" do before do expect(Chef::FileCache).to receive(:has_key?).with(cache_path).and_return(true) expect(Chef::FileCache).to receive(:load).with(cache_path).and_return(cache_json_data) end context "and there is no on-disk copy of the file" do let(:current_file_checksum) { nil } it "returns empty cache control data" do expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to be_nil end end context "and the cached checksum does not match the on-disk copy" do let(:current_file_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" } it "returns empty cache control data" do expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to be_nil end end context "and the cached checksum matches the on-disk copy" do context "when the filename uses sha256" do before do expect(Chef::FileCache).not_to receive(:has_key?).with(old_cache_path) end it "populates the cache control data" do expect(cache_control_data.etag).to eq(etag) expect(cache_control_data.mtime).to eq(mtime) end end end context "and the cached checksum data is corrupted" do let(:cache_json_data) { '{"foo",,"bar" []}' } it "returns empty cache control data" do expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to be_nil end context "and it still is valid JSON" do let(:cache_json_data) { "" } it "returns empty cache control data" do expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to be_nil end end end end context "when the filename uses md5" do before do expect(Chef::FileCache).to receive(:has_key?).with(cache_path).and_return(false) expect(Chef::FileCache).to receive(:has_key?).with(old_cache_path).and_return(true) expect(Chef::FileCache).to receive(:load).with(old_cache_path).and_return(cache_json_data) end it "populates the cache control data and creates the cache control data file with the correct path" do expect(Chef::FileCache).to receive(:store).with(cache_path, cache_json_data) expect(Chef::FileCache).to receive(:delete).with(old_cache_path) expect(cache_control_data.etag).to eq(etag) expect(cache_control_data.mtime).to eq(mtime) end end end describe "when saving to disk" do let(:etag) { "\"a-strong-identifier\"" } let(:mtime) { "Tue, 21 May 2013 19:19:23 GMT" } let(:fetched_file_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" } let(:expected_serialization_data) do data = {} data["etag"] = etag data["mtime"] = mtime data["checksum"] = fetched_file_checksum data end before do cache_control_data.etag = etag cache_control_data.mtime = mtime cache_control_data.checksum = fetched_file_checksum end it "serializes its attributes to JSON" do # we have to test this separately because ruby 1.8 hash order is unstable # so we can't count on the order of the keys in the json format. json_data = cache_control_data.json_data expect(Chef::JSONCompat.from_json(json_data)).to eq(expected_serialization_data) end it "writes data to the cache" do json_data = cache_control_data.json_data expect(Chef::FileCache).to receive(:store).with(cache_path, json_data) cache_control_data.save end context "and the URI contains a password" do let(:uri) { URI.parse("http://bob:password@example.org/") } let(:cache_path) { "remote_file/http___bob_XXXX_example_org_-44be109aa176a165ef599c12d97af792.json" } let(:old_cache_path) { "remote_file/http___bob_XXXX_example_org_-f121caacb74c05a35bcefdf578ed5fc9.json" } it "writes the data to the cache with a sanitized path name" do json_data = cache_control_data.json_data expect(Chef::FileCache).to receive(:store).with(cache_path, json_data) cache_control_data.save end end # Cover the very long remote file path case -- see CHEF-4422 where # local cache file names generated from the long uri exceeded # local file system path limits resulting in exceptions from # file system API's on both Windows and Unix systems. context "and the URI results in a file cache path that exceeds #{CACHE_FILE_PATH_LIMIT} characters in length" do let(:long_remote_path) { "http://www.bing.com/" + ("0" * (CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH * 2 )) } let(:uri) { URI.parse(long_remote_path) } let(:truncated_remote_uri) { URI.parse(long_remote_path[0...CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH]) } let(:truncated_file_cache_path) do cache_control_data_truncated = Chef::Provider::RemoteFile::CacheControlData.load_and_validate(truncated_remote_uri, current_file_checksum) cache_control_data_truncated.send("sanitized_cache_file_basename")[0...CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH] end it "truncates the file cache path to 102 characters" do normalized_cache_path = cache_control_data.send("sanitized_cache_file_basename") expect(Chef::FileCache).to receive(:store).with("remote_file/" + normalized_cache_path, cache_control_data.json_data) cache_control_data.save expect(normalized_cache_path.length).to eq(CACHE_FILE_PATH_LIMIT) end it "uses a file cache path that starts with the first #{CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH} characters of the URI" do normalized_cache_path = cache_control_data.send("sanitized_cache_file_basename") expect(truncated_file_cache_path.length).to eq(CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH) expect(normalized_cache_path.start_with?(truncated_file_cache_path)).to eq(true) end end end end chef-12.14.60/spec/unit/provider/remote_file/content_spec.rb000066400000000000000000000223561276456504500237460ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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::RemoteFile::Content do # # mock setup # let(:current_resource) do Chef::Resource::RemoteFile.new("remote-file-content-spec (current resource)") end let(:source) { [ "http://opscode.com/seattle.txt" ] } let(:new_resource) do r = Chef::Resource::RemoteFile.new("remote-file-content-spec (current resource)") r.source(source) r end let(:run_context) { double("Chef::RunContext") } # # subject # let(:content) do Chef::Provider::RemoteFile::Content.new(new_resource, current_resource, run_context) end describe "when the checksum of the current_resource matches the checksum set on the resource" do before do allow(new_resource).to receive(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") allow(current_resource).to receive(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") end it "should return nil for the tempfile" do expect(content.tempfile).to be_nil end it "should not call any fetcher" do expect(Chef::Provider::RemoteFile::Fetcher).not_to receive(:for_resource) end end describe "when the checksum of the current_resource is a partial match for the checksum set on the resource" do before do allow(new_resource).to receive(:checksum).and_return("0fd012fd") allow(current_resource).to receive(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") end it "should return nil for the tempfile" do expect(content.tempfile).to be_nil end it "should not call any fetcher" do expect(Chef::Provider::RemoteFile::Fetcher).not_to receive(:for_resource) end end shared_examples_for "the resource needs fetching" do before do # FIXME: test one or the other nil, test both not nil and not equal, abuse the regexp a little @uri = double("URI") expect(URI).to receive(:parse).with(new_resource.source[0]).and_return(@uri) end describe "when the fetcher returns nil for the tempfile" do before do http_fetcher = double("Chef::Provider::RemoteFile::HTTP", :fetch => nil) expect(Chef::Provider::RemoteFile::Fetcher).to receive(:for_resource).with(@uri, new_resource, current_resource).and_return(http_fetcher) end it "should return nil for the tempfile" do expect(content.tempfile).to be_nil end end describe "when the fetcher returns a valid tempfile" do let(:mtime) { Time.now } let(:tempfile) { double("Tempfile") } let(:http_fetcher) { double("Chef::Provider::RemoteFile::HTTP", :fetch => tempfile) } before do expect(Chef::Provider::RemoteFile::Fetcher).to receive(:for_resource).with(@uri, new_resource, current_resource).and_return(http_fetcher) end it "should return the tempfile object to the caller" do expect(content.tempfile).to eq(tempfile) end end end describe "when the checksum are both nil" do before do expect(new_resource.checksum).to be_nil expect(current_resource.checksum).to be_nil end it_behaves_like "the resource needs fetching" end describe "when the current_resource checksum is nil" do before do allow(new_resource).to receive(:checksum).and_return("fd012fd") allow(current_resource).to receive(:checksum).and_return(nil) end it_behaves_like "the resource needs fetching" end describe "when the new_resource checksum is nil" do before do allow(new_resource).to receive(:checksum).and_return(nil) allow(current_resource).to receive(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") end it_behaves_like "the resource needs fetching" end describe "when the checksums are a partial match, but not to the leading portion" do before do allow(new_resource).to receive(:checksum).and_return("fd012fd") allow(current_resource).to receive(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") end it_behaves_like "the resource needs fetching" end describe "when the fetcher throws an exception" do before do allow(new_resource).to receive(:checksum).and_return(nil) allow(current_resource).to receive(:checksum).and_return(nil) @uri = double("URI") expect(URI).to receive(:parse).with(new_resource.source[0]).and_return(@uri) http_fetcher = double("Chef::Provider::RemoteFile::HTTP") expect(http_fetcher).to receive(:fetch).and_raise(Errno::ECONNREFUSED) expect(Chef::Provider::RemoteFile::Fetcher).to receive(:for_resource).with(@uri, new_resource, current_resource).and_return(http_fetcher) end it "should propagate the error back to the caller" do expect { content.tempfile }.to raise_error(Errno::ECONNREFUSED) end end describe "when there is an array of sources and the first fails" do # https://github.com/opscode/chef/pull/1358#issuecomment-40853299 def create_exception(exception_class) if [ Net::HTTPServerException, Net::HTTPFatalError ].include? exception_class exception_class.new("message", { "something" => 1 }) else exception_class.new end end let(:source) { [ "http://opscode.com/seattle.txt", "http://opscode.com/nyc.txt" ] } ### Test each exception we care about and make sure they all behave properly [ SocketError, Errno::ECONNREFUSED, Errno::ENOENT, Errno::EACCES, Timeout::Error, Net::HTTPServerException, Net::HTTPFatalError, Net::FTPError, ].each do |exception| describe "with an exception of #{exception}" do before do allow(new_resource).to receive(:checksum).and_return(nil) allow(current_resource).to receive(:checksum).and_return(nil) @uri0 = double("URI0") @uri1 = double("URI1") expect(URI).to receive(:parse).with(new_resource.source[0]).and_return(@uri0) expect(URI).to receive(:parse).with(new_resource.source[1]).and_return(@uri1) @http_fetcher_throws_exception = double("Chef::Provider::RemoteFile::HTTP") expect(@http_fetcher_throws_exception).to receive(:fetch).at_least(:once).and_raise(create_exception(exception)) expect(Chef::Provider::RemoteFile::Fetcher).to receive(:for_resource).with(@uri0, new_resource, current_resource).and_return(@http_fetcher_throws_exception) end describe "the second url should succeed" do before do @tempfile = double("Tempfile") mtime = Time.now http_fetcher_works = double("Chef::Provider::RemoteFile::HTTP", :fetch => @tempfile) expect(Chef::Provider::RemoteFile::Fetcher).to receive(:for_resource).with(@uri1, new_resource, current_resource).and_return(http_fetcher_works) end it "should return a valid tempfile" do expect(content.tempfile).to eq(@tempfile) end it "should not mutate the new_resource" do content.tempfile expect(new_resource.source.length).to eq(2) end end describe "when both urls fail" do before do expect(Chef::Provider::RemoteFile::Fetcher).to receive(:for_resource).with(@uri1, new_resource, current_resource).and_return(@http_fetcher_throws_exception) end it "should propagate the error back to the caller" do expect { content.tempfile }.to raise_error(exception) end end end end end describe "when there is an array of sources and the first succeeds" do let(:source) { [ "http://opscode.com/seattle.txt", "http://opscode.com/nyc.txt" ] } before do allow(new_resource).to receive(:checksum).and_return(nil) allow(current_resource).to receive(:checksum).and_return(nil) @uri0 = double("URI0") expect(URI).to receive(:parse).with(new_resource.source[0]).and_return(@uri0) expect(URI).not_to receive(:parse).with(new_resource.source[1]) @tempfile = double("Tempfile") mtime = Time.now http_fetcher_works = double("Chef::Provider::RemoteFile::HTTP", :fetch => @tempfile) expect(Chef::Provider::RemoteFile::Fetcher).to receive(:for_resource).with(@uri0, new_resource, current_resource).and_return(http_fetcher_works) end it "should return a valid tempfile" do expect(content.tempfile).to eq(@tempfile) end it "should not mutate the new_resource" do content.tempfile expect(new_resource.source.length).to eq(2) end end end chef-12.14.60/spec/unit/provider/remote_file/fetcher_spec.rb000066400000000000000000000064471276456504500237170ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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::RemoteFile::Fetcher do let(:current_resource) { double("current resource") } let(:new_resource) { double("new resource") } let(:fetcher_instance) { double("fetcher") } describe "when passed a network share" do before do expect(Chef::Provider::RemoteFile::NetworkFile).to receive(:new).and_return(fetcher_instance) end context "when host is a name" do let(:source) { "\\\\foohost\\fooshare\\Foo.tar.gz" } it "returns a network file fetcher" do expect(described_class.for_resource(source, new_resource, current_resource)).to eq(fetcher_instance) end end context "when host is an ip" do let(:source) { "\\\\127.0.0.1\\fooshare\\Foo.tar.gz" } it "returns a network file fetcher" do expect(described_class.for_resource(source, new_resource, current_resource)).to eq(fetcher_instance) end end end describe "when passed an http url" do let(:uri) { double("uri", :scheme => "http" ) } before do expect(Chef::Provider::RemoteFile::HTTP).to receive(:new).and_return(fetcher_instance) end it "returns an http fetcher" do expect(described_class.for_resource(uri, new_resource, current_resource)).to eq(fetcher_instance) end end describe "when passed an https url" do let(:uri) { double("uri", :scheme => "https" ) } before do expect(Chef::Provider::RemoteFile::HTTP).to receive(:new).and_return(fetcher_instance) end it "returns an http fetcher" do expect(described_class.for_resource(uri, new_resource, current_resource)).to eq(fetcher_instance) end end describe "when passed an ftp url" do let(:uri) { double("uri", :scheme => "ftp" ) } before do expect(Chef::Provider::RemoteFile::FTP).to receive(:new).and_return(fetcher_instance) end it "returns an ftp fetcher" do expect(described_class.for_resource(uri, new_resource, current_resource)).to eq(fetcher_instance) end end describe "when passed a file url" do let(:uri) { double("uri", :scheme => "file" ) } before do expect(Chef::Provider::RemoteFile::LocalFile).to receive(:new).and_return(fetcher_instance) end it "returns a localfile fetcher" do expect(described_class.for_resource(uri, new_resource, current_resource)).to eq(fetcher_instance) end end describe "when passed a url we do not recognize" do let(:uri) { double("uri", :scheme => "xyzzy" ) } it "throws an ArgumentError exception" do expect { described_class.for_resource(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end end end chef-12.14.60/spec/unit/provider/remote_file/ftp_spec.rb000066400000000000000000000163761276456504500230720ustar00rootroot00000000000000# # Author:: Jesse Campbell () # Copyright:: Copyright 2013-2016, Jesse Campbell # 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::RemoteFile::FTP do let(:enclosing_directory) do canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates"))) end let(:resource_path) do canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt"))) end let(:new_resource) do r = Chef::Resource::RemoteFile.new("remote file ftp backend test (new resource)") r.ftp_active_mode(false) r.path(resource_path) r end let(:current_resource) do Chef::Resource::RemoteFile.new("remote file ftp backend test (current resource)'") end let(:ftp) do ftp = double(Net::FTP, {}) allow(ftp).to receive(:connect) allow(ftp).to receive(:login) allow(ftp).to receive(:voidcmd) allow(ftp).to receive(:mtime).and_return(Time.now) allow(ftp).to receive(:getbinaryfile) allow(ftp).to receive(:close) allow(ftp).to receive(:passive=) ftp end let(:tempfile_path) { "/tmp/somedir/remote-file-ftp-backend-spec-test" } let(:tempfile) do t = StringIO.new allow(t).to receive(:path).and_return(tempfile_path) t end let(:uri) { URI.parse("ftp://opscode.com/seattle.txt") } before(:each) do allow(Net::FTP).to receive(:new).with(no_args).and_return(ftp) allow(Tempfile).to receive(:new).and_return(tempfile) end describe "when first created" do it "throws an argument exception when no path is given" do uri.path = "" expect { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end it "throws an argument exception when only a / is given" do uri.path = "/" expect { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end it "throws an argument exception when no filename is given" do uri.path = "/the/whole/path/" expect { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end it "throws an argument exception when the typecode is invalid" do uri.typecode = "d" expect { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end it "does not use passive mode when new_resource sets ftp_active_mode to true" do new_resource.ftp_active_mode(true) fetcher = Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) expect(fetcher.use_passive_mode?).to be_falsey end it "uses passive mode when new_resource sets ftp_active_mode to false" do new_resource.ftp_active_mode(false) fetcher = Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) expect(fetcher.use_passive_mode?).to be_truthy end end describe "when fetching the object" do let(:cache_control_data) { Chef::Provider::RemoteFile::CacheControlData.new(uri) } let(:current_resource_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" } subject(:fetcher) { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) } before do current_resource.checksum(current_resource_checksum) #Chef::Provider::RemoteFile::CacheControlData.should_receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data) end it "should connect to the host from the uri on the default port 21" do expect(ftp).to receive(:connect).with("opscode.com", 21) fetcher.fetch end it "should set passive true when ftp_active_mode is false" do new_resource.ftp_active_mode(false) expect(ftp).to receive(:passive=).with(true) fetcher.fetch end it "should set passive false when ftp_active_mode is false" do new_resource.ftp_active_mode(true) expect(ftp).to receive(:passive=).with(false) fetcher.fetch end it "should use anonymous ftp when no userinfo is provided" do expect(ftp).to receive(:login).with("anonymous", nil) fetcher.fetch end context "and the URI specifies an alternate port" do let(:uri) { URI.parse("ftp://opscode.com:8021/seattle.txt") } it "should connect on an alternate port when one is provided" do uri = URI.parse("ftp://opscode.com:8021/seattle.txt") expect(ftp).to receive(:connect).with("opscode.com", 8021) fetcher.fetch end end context "and the URI contains a username and password" do let(:uri) { URI.parse("ftp://the_user:the_password@opscode.com/seattle.txt") } it "should use authenticated ftp when userinfo is provided" do expect(ftp).to receive(:login).with("the_user", "the_password") fetcher.fetch end end context "and the uri sets the typecode to ascii" do let(:uri) { URI.parse("ftp://the_user:the_password@opscode.com/seattle.txt;type=a") } it "fetches the file with ascii typecode set" do expect(ftp).to receive(:voidcmd).with("TYPE A").once fetcher.fetch end end context "and the uri sets the typecode to image" do let(:uri) { URI.parse("ftp://the_user:the_password@opscode.com/seattle.txt;type=i") } it "should accept image for the typecode" do expect(ftp).to receive(:voidcmd).with("TYPE I").once fetcher.fetch end end context "and the uri specifies a nested path" do let(:uri) { URI.parse("ftp://opscode.com/the/whole/path/seattle.txt") } it "should fetch the file from the correct path" do expect(ftp).to receive(:voidcmd).with("CWD the").once expect(ftp).to receive(:voidcmd).with("CWD whole").once expect(ftp).to receive(:voidcmd).with("CWD path").once expect(ftp).to receive(:getbinaryfile).with("seattle.txt", tempfile.path) fetcher.fetch end end context "when not using last modified based conditional fetching" do before do new_resource.use_last_modified(false) end it "should return a tempfile in the result" do result = fetcher.fetch expect(result).to equal(tempfile) end end context "and proxying is enabled" do before do stub_const("ENV", "ftp_proxy" => "socks5://bill:ted@socks.example.com:5000") end it "fetches the file via the proxy" do current_socks_server = ENV["SOCKS_SERVER"] expect(ENV).to receive(:[]=).with("SOCKS_SERVER", "socks5://bill:ted@socks.example.com:5000").ordered expect(ENV).to receive(:[]=).with("SOCKS_SERVER", current_socks_server).ordered result = fetcher.fetch expect(result).to equal(tempfile) end end end end chef-12.14.60/spec/unit/provider/remote_file/http_spec.rb000066400000000000000000000271631276456504500232540ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-2016, Lamont Granquist # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Provider::RemoteFile::HTTP do let(:uri) { URI.parse("http://opscode.com/seattle.txt") } let(:existing_file_source) { nil } let(:current_resource_checksum) { "41e78735319af11327e9d2ca8535ea1c191e5ac1f76bb08d88fe6c3f93a8c8e5" } let(:current_resource) do current_resource = Chef::Resource::RemoteFile.new("/tmp/foo.txt") current_resource.source(existing_file_source) if existing_file_source current_resource.checksum(current_resource_checksum) current_resource end let(:new_resource) do Chef::Resource::RemoteFile.new("/tmp/foo.txt") end subject(:fetcher) do Chef::Provider::RemoteFile::HTTP.new(uri, new_resource, current_resource) end let(:cache_control_data) { Chef::Provider::RemoteFile::CacheControlData.new(uri) } describe "generating cache control headers" do context "and there is no valid cache control data for this URI on disk" do before do expect(Chef::Provider::RemoteFile::CacheControlData).to receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data) end it "does not add conditional GET headers" do expect(fetcher.conditional_get_headers).to eq({}) end context "and the resource specifies custom headers" do before do new_resource.headers("x-myapp-header" => "custom-header-value") end it "has the user-specified custom headers" do expect(fetcher.headers).to eq({ "x-myapp-header" => "custom-header-value" }) end end end context "and the cache control data matches the existing file" do # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 let(:etag) { "\"a-strong-unique-identifier\"" } # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 let(:mtime) { "Tue, 21 May 2013 19:19:23 GMT" } before do cache_control_data.etag = etag cache_control_data.mtime = mtime expect(Chef::Provider::RemoteFile::CacheControlData).to receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data) end context "and no conditional get features are enabled" do before do new_resource.use_conditional_get(false) end it "does not add headers to the request" do expect(fetcher.headers).to eq({}) end end context "and conditional get is enabled" do before do new_resource.use_conditional_get(true) end it "adds If-None-Match and If-Modified-Since headers to the request" do headers = fetcher.headers expect(headers["if-none-match"]).to eq(etag) expect(headers["if-modified-since"]).to eq(mtime) end context "and custom headers are provided" do before do new_resource.headers("x-myapp-header" => "app-specific-header", "if-none-match" => "custom-etag", "if-modified-since" => "custom-last-modified") end it "preserves non-conflicting headers" do expect(fetcher.headers["x-myapp-header"]).to eq("app-specific-header") end it "prefers user-supplied cache control headers" do headers = fetcher.headers expect(headers["if-none-match"]).to eq("custom-etag") expect(headers["if-modified-since"]).to eq("custom-last-modified") end end end context "and etag support is enabled" do before do new_resource.use_conditional_get(false) new_resource.use_etags(true) end it "only adds If-None-Match headers to the request" do headers = fetcher.headers expect(headers["if-none-match"]).to eq(etag) expect(headers).not_to have_key("if-modified-since") end end context "and mtime support is enabled" do before do new_resource.use_conditional_get(false) new_resource.use_last_modified(true) end it "only adds If-Modified-Since headers to the request" do headers = fetcher.headers expect(headers["if-modified-since"]).to eq(mtime) expect(headers).not_to have_key("if-none-match") end end end end describe "when fetching the uri" do let(:expected_http_opts) { {} } let(:expected_http_args) { [uri, expected_http_opts] } let(:tempfile_path) { "/tmp/chef-mock-tempfile-abc123" } let(:tempfile) { double(Tempfile, :path => tempfile_path, :close => nil) } let(:last_response) { {} } let(:event_dispatcher) do event_dispatcher = double(Chef::EventDispatch::Dispatcher) allow(event_dispatcher).to receive(:formatter?).and_return(false) event_dispatcher end let(:rest) do rest = double(Chef::HTTP::Simple) allow(rest).to receive(:streaming_request).and_return(tempfile) allow(rest).to receive(:last_response).and_return(last_response) rest end before do new_resource.headers({}) new_resource.use_last_modified(false) allow(new_resource).to receive(:events).and_return(event_dispatcher) expect(Chef::Provider::RemoteFile::CacheControlData).to receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data) expect(Chef::HTTP::Simple).to receive(:new).with(*expected_http_args).and_return(rest) end describe "and the request does not return new content" do it "should return a nil tempfile for a 304 HTTPNotModifed" do # Streaming request returns nil for 304 errors allow(rest).to receive(:streaming_request).and_return(nil) expect(fetcher.fetch).to be_nil end end describe "and the request returns new content" do let(:fetched_content_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" } before do expect(cache_control_data).to receive(:save) expect(Chef::Digester).to receive(:checksum_for_file).with(tempfile_path).and_return(fetched_content_checksum) end it "should return a tempfile" do result = fetcher.fetch expect(result).to eq(tempfile) expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to be_nil expect(cache_control_data.checksum).to eq(fetched_content_checksum) end context "with progress reports" do before do Chef::Config[:show_download_progress] = true end it "should yield its progress" do allow(rest).to receive(:streaming_request_with_progress).and_yield(50, 100).and_yield(70, 100).and_return(tempfile) expect(event_dispatcher).to receive(:formatter?).and_return(true) expect(event_dispatcher).to receive(:resource_update_progress).with(new_resource, 50, 100, 10).ordered expect(event_dispatcher).to receive(:resource_update_progress).with(new_resource, 70, 100, 10).ordered fetcher.fetch end end context "and the response does not contain an etag" do let(:last_response) { { "etag" => nil } } it "does not include an etag in the result" do fetcher.fetch expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to be_nil expect(cache_control_data.checksum).to eq(fetched_content_checksum) end end context "and the response has an etag header" do let(:last_response) { { "etag" => "abc123" } } it "includes the etag value in the response" do fetcher.fetch expect(cache_control_data.etag).to eq("abc123") expect(cache_control_data.mtime).to be_nil expect(cache_control_data.checksum).to eq(fetched_content_checksum) end end context "and the response has no Date or Last-Modified header" do let(:last_response) { { "date" => nil, "last_modified" => nil } } it "does not set an mtime in the result" do # RFC 2616 suggests that servers that do not set a Date header do not # have a reliable clock, so no use in making them deal with dates. fetcher.fetch expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to be_nil expect(cache_control_data.checksum).to eq(fetched_content_checksum) end end context "and the response has a Last-Modified header" do let(:last_response) do # Last-Modified should be preferred to Date if both are set { "date" => "Fri, 17 May 2013 23:23:23 GMT", "last_modified" => "Fri, 17 May 2013 11:11:11 GMT" } end it "sets the mtime to the Last-Modified time in the response" do fetcher.fetch expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to eq(last_response["last_modified"]) end end context "and the response has a Date header but no Last-Modified header" do let(:last_response) do { "date" => "Fri, 17 May 2013 23:23:23 GMT", "last_modified" => nil } end it "sets the mtime to the Date in the response" do fetcher.fetch expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to eq(last_response["date"]) expect(cache_control_data.checksum).to eq(fetched_content_checksum) end end context "and the target file is a tarball [CHEF-3140]" do let(:uri) { URI.parse("http://opscode.com/tarball.tgz") } let(:expected_http_opts) { { :disable_gzip => true } } # CHEF-3140 # Some servers return tarballs as content type tar and encoding gzip, which # is totally wrong. When this happens and gzip isn't disabled, Chef::HTTP::Simple # will decompress the file for you, which is not at all what you expected # to happen (you end up with an uncomressed tar archive instead of the # gzipped tar archive you expected). To work around this behavior, we # detect when users are fetching gzipped files and turn off gzip in # Chef::HTTP::Simple. it "should disable gzip compression in the client" do # Before block in the parent context has set an expectation on # Chef::HTTP::Simple.new() being called with expected arguments. Here we fufil # that expectation, so that we can explicitly set it for this test. # This is intended to provide insurance that refactoring of the parent # context does not negate the value of this particular example. Chef::HTTP::Simple.new(*expected_http_args) expect(Chef::HTTP::Simple).to receive(:new).once.with(*expected_http_args).and_return(rest) fetcher.fetch expect(cache_control_data.etag).to be_nil expect(cache_control_data.mtime).to be_nil expect(cache_control_data.checksum).to eq(fetched_content_checksum) end end end end end chef-12.14.60/spec/unit/provider/remote_file/local_file_spec.rb000066400000000000000000000067671276456504500243750ustar00rootroot00000000000000# # Author:: Jesse Campbell () # Copyright:: Copyright 2013-2016, Jesse Campbell # 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 "uri" require "addressable/uri" describe Chef::Provider::RemoteFile::LocalFile do let(:uri) { URI.parse("file:///nyan_cat.png") } let(:new_resource) { Chef::Resource::RemoteFile.new("local file backend test (new_resource)") } let(:current_resource) { Chef::Resource::RemoteFile.new("local file backend test (current_resource)") } subject(:fetcher) { Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource) } context "when parsing source path on windows" do before do allow(Chef::Platform).to receive(:windows?).and_return(true) end describe "when given local unix path" do let(:uri) { URI.parse("file:///nyan_cat.png") } it "returns a correct unix path" do expect(fetcher.source_path).to eq("/nyan_cat.png") end end describe "when given local windows path" do let(:uri) { URI.parse("file:///z:/windows/path/file.txt") } it "returns a valid windows local path" do expect(fetcher.source_path).to eq("z:/windows/path/file.txt") end end describe "when given local windows path with spaces" do let(:uri) { URI.parse(Addressable::URI.encode("file:///z:/windows/path/foo & bar.txt")) } it "returns a valid windows local path" do expect(fetcher.source_path).to eq("z:/windows/path/foo & bar.txt") end end describe "when given unc windows path" do let(:uri) { URI.parse("file:////server/share/windows/path/file.txt") } it "returns a valid windows unc path" do expect(fetcher.source_path).to eq("//server/share/windows/path/file.txt") end end describe "when given unc windows path with spaces" do let(:uri) { URI.parse(Addressable::URI.encode("file:////server/share/windows/path/foo & bar.txt")) } it "returns a valid windows unc path" do expect(fetcher.source_path).to eq("//server/share/windows/path/foo & bar.txt") end end end context "when first created" do it "stores the uri it is passed" do expect(fetcher.uri).to eq(uri) end it "stores the new_resource" do expect(fetcher.new_resource).to eq(new_resource) end end describe "when fetching the object" do let(:tempfile) { double("Tempfile", :path => "/tmp/foo/bar/nyan.png", :close => nil) } let(:chef_tempfile) { double("Chef::FileContentManagement::Tempfile", :tempfile => tempfile) } before do current_resource.source("file:///nyan_cat.png") end it "stages the local file to a temporary file" do expect(Chef::FileContentManagement::Tempfile).to receive(:new).with(new_resource).and_return(chef_tempfile) expect(::FileUtils).to receive(:cp).with(uri.path, tempfile.path) expect(tempfile).to receive(:close) result = fetcher.fetch expect(result).to eq(tempfile) end end end chef-12.14.60/spec/unit/provider/remote_file/network_file_spec.rb000066400000000000000000000032531276456504500247570ustar00rootroot00000000000000# # Author:: Jay Mundrawala () # Copyright:: Copyright 2015-2016, Chef Software # 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::RemoteFile::NetworkFile do let(:source) { "\\\\foohost\\fooshare\\Foo.tar.gz" } let(:new_resource) { Chef::Resource::RemoteFile.new("network file (new_resource)") } let(:current_resource) { Chef::Resource::RemoteFile.new("network file (current_resource)") } subject(:fetcher) { Chef::Provider::RemoteFile::NetworkFile.new(source, new_resource, current_resource) } describe "when fetching the object" do let(:tempfile) { double("Tempfile", :path => "/tmp/foo/bar/Foo.tar.gz", :close => nil) } let(:chef_tempfile) { double("Chef::FileContentManagement::Tempfile", :tempfile => tempfile) } it "stages the local file to a temporary file" do expect(Chef::FileContentManagement::Tempfile).to receive(:new).with(new_resource).and_return(chef_tempfile) expect(::FileUtils).to receive(:cp).with(source, tempfile.path) expect(tempfile).to receive(:close) result = fetcher.fetch expect(result).to eq(tempfile) end end end chef-12.14.60/spec/unit/provider/remote_file/sftp_spec.rb000066400000000000000000000120241276456504500232370ustar00rootroot00000000000000# # Author:: John Kerry () # Copyright:: Copyright 2013-2016, John Kerry # 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::RemoteFile::SFTP do #built out dependencies let(:enclosing_directory) do canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates"))) end let(:resource_path) do canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt"))) end let(:new_resource) do r = Chef::Resource::RemoteFile.new("remote file sftp backend test (new resource)") r.path(resource_path) r end let(:current_resource) do Chef::Resource::RemoteFile.new("remote file sftp backend test (current resource)'") end let(:uri) { URI.parse("sftp://conan:cthu1hu@opscode.com/seattle.txt") } let(:sftp) do sftp = double(Net::SFTP, {}) allow(sftp).to receive(:download!) sftp end let(:tempfile_path) { "/tmp/somedir/remote-file-sftp-backend-spec-test" } let(:tempfile) do t = StringIO.new allow(t).to receive(:path).and_return(tempfile_path) t end before(:each) do allow(Net::SFTP).to receive(:start).with(any_args).and_return(sftp) allow(Tempfile).to receive(:new).and_return(tempfile) end describe "on initialization without user and password provided in the URI" do it "throws an argument exception with no userinfo is given" do uri.userinfo = nil uri.password = nil uri.user = nil expect { Chef::Provider::RemoteFile::SFTP.new(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end it "throws an argument exception with no user name is given" do uri.userinfo = ":cthu1hu" uri.password = "cthu1hu" uri.user = nil expect { Chef::Provider::RemoteFile::SFTP.new(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end it "throws an argument exception with no password is given" do uri.userinfo = "conan:" uri.password = nil uri.user = "conan" expect { Chef::Provider::RemoteFile::SFTP.new(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end end describe "on initialization with user and password provided in the URI" do it "throws an argument exception when no path is given" do uri.path = "" expect { Chef::Provider::RemoteFile::SFTP.new(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end it "throws an argument exception when only a / is given" do uri.path = "/" expect { Chef::Provider::RemoteFile::SFTP.new(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end it "throws an argument exception when no filename is given" do uri.path = "/the/whole/path/" expect { Chef::Provider::RemoteFile::SFTP.new(uri, new_resource, current_resource) }.to raise_error(ArgumentError) end end describe "when fetching the object" do let(:cache_control_data) { Chef::Provider::RemoteFile::CacheControlData.new(uri) } let(:current_resource_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" } subject(:fetcher) { Chef::Provider::RemoteFile::SFTP.new(uri, new_resource, current_resource) } before do current_resource.checksum(current_resource_checksum) end it "should attempt to download a file from the provided url and path" do expect(sftp).to receive(:download!).with("/seattle.txt", "/tmp/somedir/remote-file-sftp-backend-spec-test") fetcher.fetch end context "and the URI specifies an alternate port" do let(:uri) { URI.parse("ftp://conan:cthu1hu@opscode.com:8021/seattle.txt") } it "should connect on an alternate port when one is provided" do expect(Net::SFTP).to receive(:start).with("opscode.com:8021", "conan", :password => "cthu1hu") fetcher.fetch end end context "and the uri specifies a nested path" do let(:uri) { URI.parse("ftp://conan:cthu1hu@opscode.com/the/whole/path/seattle.txt") } it "should fetch the file from the correct path" do expect(sftp).to receive(:download!).with("the/whole/path/seattle.txt", "/tmp/somedir/remote-file-sftp-backend-spec-test") fetcher.fetch end end context "when not using last modified based conditional fetching" do before do new_resource.use_last_modified(false) end it "should return a tempfile in the result" do result = fetcher.fetch expect(result).to equal(tempfile) end end end end chef-12.14.60/spec/unit/provider/remote_file_spec.rb000066400000000000000000000041061276456504500222650ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Lamont Granquist () # 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" require "support/shared/unit/provider/file" describe Chef::Provider::RemoteFile do let(:resource) do resource = Chef::Resource::RemoteFile.new("seattle", @run_context) resource.path(resource_path) resource.source("http://foo") resource.cookbook_name = "monkey" resource end let(:content) do content = double("Chef::Provider::File::Content::RemoteFile") end let(:node) { double("Chef::Node") } let(:events) { double("Chef::Events").as_null_object } # mock all the methods let(:run_context) { double("Chef::RunContext", :node => node, :events => events) } let(:enclosing_directory) do canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates"))) end let(:resource_path) do canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt"))) end subject(:provider) do provider = described_class.new(resource, run_context) allow(provider).to receive(:content).and_return(content) allow(provider).to receive(:update_new_resource_checksum).and_return(nil) # Otherwise it doesn't behave like a File provider provider end before do allow(Chef::FileCache).to receive(:load).with("remote_file/#{resource.name}").and_raise(Chef::Exceptions::FileNotFound) end it_behaves_like Chef::Provider::File it_behaves_like "a file provider with source field" end chef-12.14.60/spec/unit/provider/route_spec.rb000066400000000000000000000245141276456504500211360ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Copyright:: Copyright 2009-2016, Bryan McLellan # 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::Route do before do @node = Chef::Node.new @cookbook_collection = Chef::CookbookCollection.new([]) @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) @new_resource = Chef::Resource::Route.new("10.0.0.10") @new_resource.gateway "10.0.0.9" @current_resource = Chef::Resource::Route.new("10.0.0.10") @current_resource.gateway "10.0.0.9" @provider = Chef::Provider::Route.new(@new_resource, @run_context) @provider.current_resource = @current_resource end describe Chef::Provider::Route, "hex2ip" do it "should return nil if ip address is invalid" do expect(@provider.hex2ip("foo")).to be_nil # does not even look like an ip expect(@provider.hex2ip("ABCDEFGH")).to be_nil # 8 chars, but invalid end it "should return quad-dotted notation for a valid IP" do expect(@provider.hex2ip("01234567")).to eq("103.69.35.1") expect(@provider.hex2ip("0064a8c0")).to eq("192.168.100.0") expect(@provider.hex2ip("00FFFFFF")).to eq("255.255.255.0") end end describe Chef::Provider::Route, "load_current_resource" do context "on linux" do before do @node.automatic_attrs[:os] = "linux" routing_table = "Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT\n" + "eth0 0064A8C0 0984A8C0 0003 0 0 0 00FFFFFF 0 0 0\n" route_file = StringIO.new(routing_table) allow(File).to receive(:open).with("/proc/net/route", "r").and_return(route_file) end it "should set is_running to false when a route is not detected" do resource = Chef::Resource::Route.new("10.10.10.0/24") allow(resource).to receive(:gateway).and_return("10.0.0.1") allow(resource).to receive(:device).and_return("eth0") provider = Chef::Provider::Route.new(resource, @run_context) provider.load_current_resource expect(provider.is_running).to be_falsey end it "should detect existing routes and set is_running attribute correctly" do resource = Chef::Resource::Route.new("192.168.100.0/24") allow(resource).to receive(:gateway).and_return("192.168.132.9") allow(resource).to receive(:device).and_return("eth0") provider = Chef::Provider::Route.new(resource, @run_context) provider.load_current_resource expect(provider.is_running).to be_truthy end it "should use gateway value when matching routes" do resource = Chef::Resource::Route.new("192.168.100.0/24") allow(resource).to receive(:gateway).and_return("10.10.10.10") allow(resource).to receive(:device).and_return("eth0") provider = Chef::Provider::Route.new(resource, @run_context) provider.load_current_resource expect(provider.is_running).to be_falsey end end end describe Chef::Provider::Route, "action_add" do it "should add the route if it does not exist" do allow(@provider).to receive(:run_command).and_return(true) allow(@current_resource).to receive(:gateway).and_return(nil) expect(@provider).to receive(:generate_command).once.with(:add) expect(@provider).to receive(:generate_config) @provider.run_action(:add) expect(@new_resource).to be_updated end it "should not add the route if it exists" do allow(@provider).to receive(:run_command).and_return(true) allow(@provider).to receive(:is_running).and_return(true) expect(@provider).not_to receive(:generate_command).with(:add) expect(@provider).to receive(:generate_config) @provider.run_action(:add) expect(@new_resource).not_to be_updated end it "should not delete config file for :add action (CHEF-3332)" do @node.automatic_attrs[:platform] = "centos" route_file = StringIO.new expect(File).to receive(:new).and_return(route_file) @resource_add = Chef::Resource::Route.new("192.168.1.0/24 via 192.168.0.1") @run_context.resource_collection << @resource_add allow(@provider).to receive(:run_command).and_return(true) @resource_add.action(:add) @provider.run_action(:add) expect(route_file.string.split("\n").size).to eq(1) expect(route_file.string).to match(/^192\.168\.1\.0\/24 via 192\.168\.0\.1$/) end end describe Chef::Provider::Route, "action_delete" do it "should delete the route if it exists" do allow(@provider).to receive(:run_command).and_return(true) expect(@provider).to receive(:generate_command).once.with(:delete) allow(@provider).to receive(:is_running).and_return(true) @provider.run_action(:delete) expect(@new_resource).to be_updated end it "should not delete the route if it does not exist" do allow(@current_resource).to receive(:gateway).and_return(nil) allow(@provider).to receive(:run_command).and_return(true) expect(@provider).not_to receive(:generate_command).with(:add) @provider.run_action(:delete) expect(@new_resource).not_to be_updated end end describe Chef::Provider::Route, "generate_command for action_add" do it "should include a netmask when a one is specified" do allow(@new_resource).to receive(:netmask).and_return("255.255.0.0") expect(@provider.generate_command(:add)).to match(/\/\d{1,2}\s/) end it "should not include a netmask when a one is specified" do allow(@new_resource).to receive(:netmask).and_return(nil) expect(@provider.generate_command(:add)).not_to match(/\/\d{1,2}\s/) end it "should include ' via $gateway ' when a gateway is specified" do expect(@provider.generate_command(:add)).to match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\s/) end it "should not include ' via $gateway ' when a gateway is not specified" do allow(@new_resource).to receive(:gateway).and_return(nil) expect(@provider.generate_command(:add)).not_to match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\s/) end end describe Chef::Provider::Route, "generate_command for action_delete" do it "should include a netmask when a one is specified" do allow(@new_resource).to receive(:netmask).and_return("255.255.0.0") expect(@provider.generate_command(:delete)).to match(/\/\d{1,2}\s/) end it "should not include a netmask when a one is specified" do allow(@new_resource).to receive(:netmask).and_return(nil) expect(@provider.generate_command(:delete)).not_to match(/\/\d{1,2}\s/) end it "should include ' via $gateway ' when a gateway is specified" do expect(@provider.generate_command(:delete)).to match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\s/) end it "should not include ' via $gateway ' when a gateway is not specified" do allow(@new_resource).to receive(:gateway).and_return(nil) expect(@provider.generate_command(:delete)).not_to match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\s/) end end describe Chef::Provider::Route, "config_file_contents for action_add" do it "should include a netmask when a one is specified" do allow(@new_resource).to receive(:netmask).and_return("255.255.0.0") expect(@provider.config_file_contents(:add, { :target => @new_resource.target, :netmask => @new_resource.netmask })).to match(/\/\d{1,2}.*\n$/) end it "should not include a netmask when a one is specified" do expect(@provider.config_file_contents(:add, { :target => @new_resource.target })).not_to match(/\/\d{1,2}.*\n$/) end it "should include ' via $gateway ' when a gateway is specified" do expect(@provider.config_file_contents(:add, { :target => @new_resource.target, :gateway => @new_resource.gateway })).to match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\n/) end it "should not include ' via $gateway ' when a gateway is not specified" do expect(@provider.generate_command(:add)).not_to match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\n/) end end describe Chef::Provider::Route, "config_file_contents for action_delete" do it "should return an empty string" do expect(@provider.config_file_contents(:delete)).to match(/^$/) end end describe Chef::Provider::Route, "generate_config method" do %w{ centos redhat fedora }.each do |platform| it "should write a route file on #{platform} platform" do @node.automatic_attrs[:platform] = platform route_file = StringIO.new expect(File).to receive(:new).with("/etc/sysconfig/network-scripts/route-eth0", "w").and_return(route_file) #Chef::Log.should_receive(:debug).with("route[10.0.0.10] writing route.eth0\n10.0.0.10 via 10.0.0.9\n") @run_context.resource_collection << @new_resource @provider.generate_config end end it "should put all routes for a device in a route config file" do @node.automatic_attrs[:platform] = "centos" route_file = StringIO.new expect(File).to receive(:new).and_return(route_file) @run_context.resource_collection << Chef::Resource::Route.new("192.168.1.0/24 via 192.168.0.1") @run_context.resource_collection << Chef::Resource::Route.new("192.168.2.0/24 via 192.168.0.1") @run_context.resource_collection << Chef::Resource::Route.new("192.168.3.0/24 via 192.168.0.1") @provider.action = :add @provider.generate_config expect(route_file.string.split("\n").size).to eq(3) expect(route_file.string).to match(/^192\.168\.1\.0\/24 via 192\.168\.0\.1$/) expect(route_file.string).to match(/^192\.168\.2\.0\/24 via 192\.168\.0\.1$/) expect(route_file.string).to match(/^192\.168\.3\.0\/24 via 192\.168\.0\.1$/) end end end chef-12.14.60/spec/unit/provider/ruby_block_spec.rb000066400000000000000000000031331276456504500221250ustar00rootroot00000000000000# # Author:: AJ Christensen () # Copyright:: Copyright 2009-2016, Opscode # 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::RubyBlock, "initialize" do before(:each) do $evil_global_evil_laugh = :wahwah @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::RubyBlock.new("bloc party") @new_resource.block { $evil_global_evil_laugh = :mwahahaha } @provider = Chef::Provider::RubyBlock.new(@new_resource, @run_context) end it "should call the block and flag the resource as updated" do @provider.run_action(:run) expect($evil_global_evil_laugh).to eq(:mwahahaha) expect(@new_resource).to be_updated end it "accepts `create' as an alias for `run'" do # SEE ALSO: CHEF-3500 # "create" used to be the default action, it was renamed. @provider.run_action(:create) expect($evil_global_evil_laugh).to eq(:mwahahaha) expect(@new_resource).to be_updated end end chef-12.14.60/spec/unit/provider/script_spec.rb000066400000000000000000000071451276456504500213050ustar00rootroot00000000000000# # Author:: Adam Jacob (adam@chef.io) # Copyright:: Copyright 2009-2016, Opscode # 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::Script, "action_run" do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:new_resource) do new_resource = Chef::Resource::Script.new("run some perl code") new_resource.code "$| = 1; print 'i like beans'" new_resource.interpreter "perl" new_resource end let(:provider) { Chef::Provider::Script.new(new_resource, run_context) } let(:tempfile) { Tempfile.open("rspec-provider-script") } before(:each) do allow(provider).to receive(:shell_out!).and_return(true) allow(provider).to receive(:script_file).and_return(tempfile) end context "#script_file" do it "creates a temporary file to store the script" do allow(provider).to receive(:script_file).and_call_original expect(provider.script_file).to be_an_instance_of(Tempfile) end end context "#unlink_script_file" do it "unlinks the tempfile" do tempfile_path = tempfile.path provider.unlink_script_file expect(File.exist?(tempfile_path)).to be false end end context "#set_owner_and_group" do it "sets the owner and group for the script file" do new_resource.user "toor" new_resource.group "wheel" expect(FileUtils).to receive(:chown).with("toor", "wheel", tempfile.path) provider.set_owner_and_group end end context "with the script file set to the correct owner and group" do before do allow(provider).to receive(:set_owner_and_group) end describe "when writing the script to the file" do it "should put the contents of the script in the temp file" do allow(provider).to receive(:unlink_script_file) # stub to avoid remove provider.action_run expect(IO.read(tempfile.path)).to eq("$| = 1; print 'i like beans'\n") provider.unlink_script_file end it "closes before executing the script and unlinks it when finished" do tempfile_path = tempfile.path provider.action_run expect(tempfile).to be_closed expect(File.exist?(tempfile_path)).to be false end end describe "when running the script" do let (:default_opts) do { timeout: 3600, returns: 0, log_level: :info, log_tag: "script[run some perl code]" } end before do allow(STDOUT).to receive(:tty?).and_return(false) end it 'should set the command to "interpreter" "tempfile"' do expect(provider.command).to eq(%Q{"perl" "#{tempfile.path}"}) end it "should call shell_out! with the command" do expect(provider).to receive(:shell_out!).with(provider.command, default_opts).and_return(true) provider.action_run end it "should set the command to 'interpreter flags tempfile'" do new_resource.flags "-f" expect(provider.command).to eq(%Q{"perl" -f "#{tempfile.path}"}) end end end end chef-12.14.60/spec/unit/provider/service/000077500000000000000000000000001276456504500200735ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/service/aix_service_spec.rb000066400000000000000000000153031276456504500237350ustar00rootroot00000000000000# # Author:: Kaustubh # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Provider::Service::Aix do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @current_resource = Chef::Resource::Service.new("chef") @provider = Chef::Provider::Service::Aix.new(@new_resource, @run_context) allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) end describe "load current resource" do it "should create a current resource with the name of the new resource and determine the status" do @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out!).and_return(@status) expect(Chef::Resource::Service).to receive(:new).and_return(@current_resource) expect(@current_resource).to receive(:service_name).with("chef") expect(@provider).to receive(:determine_current_status!) @provider.load_current_resource end end describe "determine current status" do context "when the service is active" do before do @status = double("Status", :exitstatus => 0, :stdout => "chef chef 12345 active\n") end it "current resource is running" do expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status) expect(@provider).to receive(:is_resource_group?).and_return false @provider.load_current_resource expect(@current_resource.running).to be_truthy end end context "when the service is inoperative" do before do @status = double("Status", :exitstatus => 0, :stdout => "chef chef inoperative\n") end it "current resource is not running" do expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status) expect(@provider).to receive(:is_resource_group?).and_return false @provider.load_current_resource expect(@current_resource.running).to be_falsey end end context "when there is no such service" do before do @status = double("Status", :exitstatus => 1, :stdout => "0513-085 The chef Subsystem is not on file.\n") end it "current resource is not running" do expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status) expect(@provider).to receive(:is_resource_group?).and_return false @provider.load_current_resource expect(@current_resource.running).to be_falsey end end end describe "is resource group" do context "when there are multiple subsystems associated with group" do before do @status = double("Status", :exitstatus => 0, :stdout => "chef1 chef 12345 active\nchef2 chef 12334 active\nchef3 chef inoperative") end it "service is a group" do expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@status) @provider.load_current_resource expect(@provider.instance_eval("@is_resource_group")).to be_truthy end end context "when there is a single subsystem in the group" do before do @status = double("Status", :exitstatus => 0, :stdout => "chef1 chef inoperative\n") end it "service is a group" do expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@status) @provider.load_current_resource expect(@provider.instance_eval("@is_resource_group")).to be_truthy end end context "when the service is a subsystem" do before do @group_status = double("Status", :exitstatus => 1, :stdout => "0513-086 The chef Group is not on file.\n") @service_status = double("Status", :exitstatus => 0, :stdout => "chef chef inoperative\n") end it "service is a subsystem" do expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@group_status) expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@service_status) @provider.load_current_resource expect(@provider.instance_eval("@is_resource_group")).to be_falsey end end end describe "when starting the service" do before do @new_resource.service_name "apache" end it "should call the start command for groups" do @provider.instance_eval("@is_resource_group = true") expect(@provider).to receive(:shell_out!).with("startsrc -g #{@new_resource.service_name}") @provider.start_service end it "should call the start command for subsystem" do expect(@provider).to receive(:shell_out!).with("startsrc -s #{@new_resource.service_name}") @provider.start_service end end describe "when stopping a service" do before do @new_resource.service_name "apache" end it "should call the stop command for groups" do @provider.instance_eval("@is_resource_group = true") expect(@provider).to receive(:shell_out!).with("stopsrc -g #{@new_resource.service_name}") @provider.stop_service end it "should call the stop command for subsystem" do expect(@provider).to receive(:shell_out!).with("stopsrc -s #{@new_resource.service_name}") @provider.stop_service end end describe "when reloading a service" do before do @new_resource.service_name "apache" end it "should call the reload command for groups" do @provider.instance_eval("@is_resource_group = true") expect(@provider).to receive(:shell_out!).with("refresh -g #{@new_resource.service_name}") @provider.reload_service end it "should call the reload command for subsystem" do expect(@provider).to receive(:shell_out!).with("refresh -s #{@new_resource.service_name}") @provider.reload_service end end describe "when restarting the service" do it "should call stop service followed by start service" do expect(@provider).to receive(:stop_service) expect(@provider).to receive(:start_service) @provider.restart_service end end end chef-12.14.60/spec/unit/provider/service/aixinit_service_spec.rb000066400000000000000000000221641276456504500246240ustar00rootroot00000000000000# # Author:: kaustubh () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Provider::Service::AixInit do before(:each) do @node = Chef::Node.new @node.automatic_attrs[:command] = { :ps => "fuuuu" } @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @provider = Chef::Provider::Service::AixInit.new(@new_resource, @run_context) @current_resource = Chef::Resource::Service.new("chef") @provider.current_resource = @current_resource @pid, @stdin, @stdout, @stderr = nil, nil, nil, nil end describe "load_current_resource" do it "sets current resource attributes" do expect(@provider).to receive(:set_current_resource_attributes) @provider.load_current_resource end end describe "action_enable" do shared_examples_for "the service is up to date" do it "does not enable the service" do expect(@provider).not_to receive(:enable_service) @provider.action_enable @provider.set_updated_status expect(@provider.new_resource).not_to be_updated end end shared_examples_for "the service is not up to date" do it "enables the service and sets the resource as updated" do expect(@provider).to receive(:enable_service).and_return(true) @provider.action_enable @provider.set_updated_status expect(@provider.new_resource).to be_updated end end context "when the service is disabled" do before do @current_resource.enabled(false) end it_behaves_like "the service is not up to date" end context "when the service is enabled" do before do @current_resource.enabled(true) @current_resource.priority(80) end context "and the service sets no priority" do it_behaves_like "the service is up to date" end context "and the service requests the same priority as is set" do before do @new_resource.priority(80) end it_behaves_like "the service is up to date" end context "and the service requests a different priority than is set" do before do @new_resource.priority(20) end it_behaves_like "the service is not up to date" end end end describe "enable_service" do before do allow(Dir).to receive(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).and_return([]) end context "when the service doesn't set a priority" do it "creates symlink with status S" do expect(@provider).to receive(:create_symlink).with(2, "S", "") @provider.enable_service end end context "when the service sets a simple priority (integer)" do before do @new_resource.priority(75) end it "creates a symlink with status S and a priority" do expect(@provider).to receive(:create_symlink).with(2, "S", 75) @provider.enable_service end end context "when the service sets complex priorities (hash)" do before do priority = { 2 => [:start, 20], 3 => [:stop, 10] } @new_resource.priority(priority) end it "create symlink with status start (S) or stop (K) and a priority " do expect(@provider).to receive(:create_symlink).with(2, "S", 20) expect(@provider).to receive(:create_symlink).with(3, "K", 10) @provider.enable_service end end end describe "disable_service" do before do allow(Dir).to receive(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).and_return([]) end context "when the service doesn't set a priority" do it "creates symlinks with status stop (K)" do expect(@provider).to receive(:create_symlink).with(2, "K", "") @provider.disable_service end end context "when the service sets a simple priority (integer)" do before do @new_resource.priority(75) end it "create symlink with status stop (k) and a priority " do expect(@provider).to receive(:create_symlink).with(2, "K", 25) @provider.disable_service end end context "when the service sets complex priorities (hash)" do before do @priority = { 2 => [:start, 20], 3 => [:stop, 10] } @new_resource.priority(@priority) end it "create symlink with status stop (k) and a priority " do expect(@provider).to receive(:create_symlink).with(3, "K", 90) @provider.disable_service end end end describe "set_current_resource_attributes" do context "when rc2.d contains only start script" do before do files = ["/etc/rc.d/rc2.d/S20apache"] allow(Dir).to receive(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]chef"]).and_return(files) end it "the service is enabled" do expect(@provider.current_resource).to receive(:enabled).with(true) expect(@provider.current_resource).to receive(:priority).with(20) @provider.set_current_resource_attributes end end context "when rc2.d contains only stop script" do before do files = ["/etc/rc.d/rc2.d/K20apache"] @priority = { 2 => [:stop, 20] } allow(Dir).to receive(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]chef"]).and_return(files) end it "the service is not enabled" do expect(@provider.current_resource).to receive(:enabled).with(false) expect(@provider.current_resource).to receive(:priority).with(@priority) @provider.set_current_resource_attributes end end context "when rc2.d contains both start and stop scripts" do before do @files = ["/etc/rc.d/rc2.d/S20apache", "/etc/rc.d/rc2.d/K80apache"] # FIXME: this is clearly buggy the duplicated keys do not work #@priority = {2 => [:start, 20], 2 => [:stop, 80]} @priority = { 2 => [:stop, 80] } allow(Dir).to receive(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]chef"]).and_return(@files) end it "the service is enabled" do expect(@current_resource).to receive(:enabled).with(true) expect(@current_resource).to receive(:priority).with(@priority) @provider.set_current_resource_attributes end end context "when rc2.d contains only start script (without priority)" do before do files = ["/etc/rc.d/rc2.d/Sapache"] allow(Dir).to receive(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).and_return(files) end it "the service is enabled" do expect(@provider.current_resource).to receive(:enabled).with(true) expect(@provider.current_resource).to receive(:priority).with("") @provider.set_current_resource_attributes end end context "when rc2.d contains only stop script (without priority)" do before do files = ["/etc/rc.d/rc2.d/Kapache"] @priority = { 2 => [:stop, ""] } allow(Dir).to receive(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).and_return(files) end it "the service is not enabled" do expect(@provider.current_resource).to receive(:enabled).with(false) expect(@provider.current_resource).to receive(:priority).with(@priority) @provider.set_current_resource_attributes end end context "when rc2.d contains both start and stop scripts" do before do files = ["/etc/rc.d/rc2.d/Sapache", "/etc/rc.d/rc2.d/Kapache"] # FIXME: this is clearly buggy the duplicated keys do not work #@priority = {2 => [:start, ''], 2 => [:stop, '']} @priority = { 2 => [:stop, ""] } allow(Dir).to receive(:glob).with(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).and_return(files) end it "the service is enabled" do expect(@current_resource).to receive(:enabled).with(true) expect(@current_resource).to receive(:priority).with(@priority) @provider.set_current_resource_attributes end end end end chef-12.14.60/spec/unit/provider/service/arch_service_spec.rb000066400000000000000000000323611276456504500240740ustar00rootroot00000000000000# # Author:: Jan Zimmek () # Author:: AJ Christensen () # 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" require "ostruct" # most of this code has been ripped from init_service_spec.rb # and is only slightly modified to match "arch" needs. describe Chef::Provider::Service::Arch, "load_current_resource" do before(:each) do @node = Chef::Node.new @node.automatic_attrs[:command] = { :ps => "ps -ef" } @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @new_resource.pattern("chef") @new_resource.supports({ :status => false }) @provider = Chef::Provider::Service::Arch.new(@new_resource, @run_context) allow(::File).to receive(:exists?).with("/etc/rc.conf").and_return(true) allow(::File).to receive(:read).with("/etc/rc.conf").and_return("DAEMONS=(network apache sshd)") end describe "when first created" do it "should set the current resources service name to the new resources service name" do allow(@provider).to receive(:shell_out).and_return(OpenStruct.new(:exitstatus => 0, :stdout => "")) @provider.load_current_resource expect(@provider.current_resource.service_name).to eq("chef") end end describe "when the service supports status" do before do @new_resource.supports({ :status => true }) end it "should run '/etc/rc.d/service_name status'" do expect(@provider).to receive(:shell_out).with("/etc/rc.d/chef status").and_return(OpenStruct.new(:exitstatus => 0)) @provider.load_current_resource end it "should set running to true if the status command returns 0" do allow(@provider).to receive(:shell_out).with("/etc/rc.d/chef status").and_return(OpenStruct.new(:exitstatus => 0)) @provider.load_current_resource expect(@provider.current_resource.running).to be_truthy end it "should set running to false if the status command returns anything except 0" do allow(@provider).to receive(:shell_out).with("/etc/rc.d/chef status").and_return(OpenStruct.new(:exitstatus => 1)) @provider.load_current_resource expect(@provider.current_resource.running).to be_falsey end it "should set running to false if the status command raises" do allow(@provider).to receive(:shell_out).with("/etc/rc.d/chef status").and_raise(Mixlib::ShellOut::ShellCommandFailed) @provider.load_current_resource expect(@provider.current_resource.running).to be_falsey end end describe "when a status command has been specified" do before do @new_resource.status_command("/etc/rc.d/chefhasmonkeypants status") end it "should run the services status command if one has been specified" do expect(@provider).to receive(:shell_out).with("/etc/rc.d/chefhasmonkeypants status").and_return(OpenStruct.new(:exitstatus => 0)) @provider.load_current_resource end end it "should raise error if the node has a nil ps attribute and no other means to get status" do @node.automatic_attrs[:command] = { :ps => nil } @provider.define_resource_requirements @provider.action = :start expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end it "should raise error if the node has an empty ps attribute and no other means to get status" do @node.automatic_attrs[:command] = { :ps => "" } @provider.define_resource_requirements @provider.action = :start expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end it "should fail if file /etc/rc.conf does not exist" do allow(::File).to receive(:exists?).with("/etc/rc.conf").and_return(false) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service) end it "should fail if file /etc/rc.conf does not contain DAEMONS array" do allow(::File).to receive(:read).with("/etc/rc.conf").and_return("") expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service) end describe "when discovering service status with ps" do before do @stdout = StringIO.new(<<-DEFAULT_PS) aj 7842 5057 0 21:26 pts/2 00:00:06 vi init.rb aj 7903 5016 0 21:26 pts/5 00:00:00 /bin/bash aj 8119 6041 0 21:34 pts/3 00:00:03 vi init_service_spec.rb DEFAULT_PS @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out!).and_return(@status) @node.automatic_attrs[:command] = { :ps => "ps -ef" } end it "determines the service is running when it appears in ps" do @stdout = StringIO.new(<<-RUNNING_PS) aj 7842 5057 0 21:26 pts/2 00:00:06 chef aj 7842 5057 0 21:26 pts/2 00:00:06 poos RUNNING_PS allow(@status).to receive(:stdout).and_return(@stdout) @provider.load_current_resource expect(@provider.current_resource.running).to be_truthy end it "determines the service is not running when it does not appear in ps" do allow(@provider).to receive(:shell_out!).and_return(@status) @provider.load_current_resource expect(@provider.current_resource.running).to be_falsey end it "should raise an exception if ps fails" do allow(@provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) @provider.load_current_resource @provider.action = :start @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end it "should return existing entries in DAEMONS array" do allow(::File).to receive(:read).with("/etc/rc.conf").and_return("DAEMONS=(network !apache ssh)") expect(@provider.daemons).to eq(["network", "!apache", "ssh"]) end context "when the current service status is known" do before do @current_resource = Chef::Resource::Service.new("chef") @provider.current_resource = @current_resource end describe Chef::Provider::Service::Arch, "enable_service" do # before(:each) do # @new_resource = double("Chef::Resource::Service", # :null_object => true, # :name => "chef", # :service_name => "chef", # :running => false # ) # @new_resource.stub(:start_command).and_return(false) # # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource) # Chef::Resource::Service.stub(:new).and_return(@current_resource) # end it "should add chef to DAEMONS array" do allow(::File).to receive(:read).with("/etc/rc.conf").and_return("DAEMONS=(network)") expect(@provider).to receive(:update_daemons).with(%w{network chef}) @provider.enable_service() end end describe Chef::Provider::Service::Arch, "disable_service" do # before(:each) do # @new_resource = double("Chef::Resource::Service", # :null_object => true, # :name => "chef", # :service_name => "chef", # :running => false # ) # @new_resource.stub(:start_command).and_return(false) # # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource) # Chef::Resource::Service.stub(:new).and_return(@current_resource) # end it "should remove chef from DAEMONS array" do allow(::File).to receive(:read).with("/etc/rc.conf").and_return("DAEMONS=(network chef)") expect(@provider).to receive(:update_daemons).with(["network", "!chef"]) @provider.disable_service() end end describe Chef::Provider::Service::Arch, "start_service" do # before(:each) do # @new_resource = double("Chef::Resource::Service", # :null_object => true, # :name => "chef", # :service_name => "chef", # :running => false # ) # @new_resource.stub(:start_command).and_return(false) # # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource) # Chef::Resource::Service.stub(:new).and_return(@current_resource) # end it "should call the start command if one is specified" do allow(@new_resource).to receive(:start_command).and_return("/etc/rc.d/chef startyousillysally") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/chef startyousillysally") @provider.start_service() end it "should call '/etc/rc.d/service_name start' if no start command is specified" do expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} start") @provider.start_service() end end describe Chef::Provider::Service::Arch, "stop_service" do # before(:each) do # @new_resource = double("Chef::Resource::Service", # :null_object => true, # :name => "chef", # :service_name => "chef", # :running => false # ) # @new_resource.stub(:stop_command).and_return(false) # # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource) # Chef::Resource::Service.stub(:new).and_return(@current_resource) # end it "should call the stop command if one is specified" do allow(@new_resource).to receive(:stop_command).and_return("/etc/rc.d/chef itoldyoutostop") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/chef itoldyoutostop") @provider.stop_service() end it "should call '/etc/rc.d/service_name stop' if no stop command is specified" do expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} stop") @provider.stop_service() end end describe Chef::Provider::Service::Arch, "restart_service" do # before(:each) do # @new_resource = double("Chef::Resource::Service", # :null_object => true, # :name => "chef", # :service_name => "chef", # :running => false # ) # @new_resource.stub(:restart_command).and_return(false) # @new_resource.stub(:supports).and_return({:restart => false}) # # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource) # Chef::Resource::Service.stub(:new).and_return(@current_resource) # end it "should call 'restart' on the service_name if the resource supports it" do allow(@new_resource).to receive(:supports).and_return({ :restart => true }) expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} restart") @provider.restart_service() end it "should call the restart_command if one has been specified" do allow(@new_resource).to receive(:restart_command).and_return("/etc/rc.d/chef restartinafire") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} restartinafire") @provider.restart_service() end it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do expect(@provider).to receive(:stop_service) expect(@provider).to receive(:sleep).with(1) expect(@provider).to receive(:start_service) @provider.restart_service() end end describe Chef::Provider::Service::Arch, "reload_service" do # before(:each) do # @new_resource = double("Chef::Resource::Service", # :null_object => true, # :name => "chef", # :service_name => "chef", # :running => false # ) # @new_resource.stub(:reload_command).and_return(false) # @new_resource.stub(:supports).and_return({:reload => false}) # # @provider = Chef::Provider::Service::Arch.new(@node, @new_resource) # Chef::Resource::Service.stub(:new).and_return(@current_resource) # end it "should call 'reload' on the service if it supports it" do allow(@new_resource).to receive(:supports).and_return({ :reload => true }) expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} reload") @provider.reload_service() end it "should should run the user specified reload command if one is specified and the service doesn't support reload" do allow(@new_resource).to receive(:reload_command).and_return("/etc/rc.d/chef lollerpants") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{@new_resource.service_name} lollerpants") @provider.reload_service() end end end end chef-12.14.60/spec/unit/provider/service/debian_service_spec.rb000066400000000000000000000321011276456504500243710ustar00rootroot00000000000000# # Author:: AJ Christensen () # Copyright:: Copyright 2008-2016, HJK Solutions, LLC # 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::Service::Debian do before(:each) do @node = Chef::Node.new @node.automatic_attrs[:command] = { :ps => "fuuuu" } @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @provider = Chef::Provider::Service::Debian.new(@new_resource, @run_context) @current_resource = Chef::Resource::Service.new("chef") @provider.current_resource = @current_resource @pid, @stdin, @stdout, @stderr = nil, nil, nil, nil end describe "load_current_resource" do it "ensures /usr/sbin/update-rc.d is available" do expect(File).to receive(:exists?).with("/usr/sbin/update-rc.d") .and_return(false) @provider.define_resource_requirements expect do @provider.process_resource_requirements end.to raise_error(Chef::Exceptions::Service) end context "when update-rc.d shows init linked to rc*.d/" do before do allow(@provider).to receive(:assert_update_rcd_available) result = <<-UPDATE_RC_D_SUCCESS Removing any system startup links for /etc/init.d/chef ... /etc/rc0.d/K20chef /etc/rc1.d/K20chef /etc/rc2.d/S20chef /etc/rc3.d/S20chef /etc/rc4.d/S20chef /etc/rc5.d/S20chef /etc/rc6.d/K20chef UPDATE_RC_D_SUCCESS @stdout = StringIO.new(result) @stderr = StringIO.new @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out!).and_return(@status) allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) end it "says the service is enabled" do expect(@provider.service_currently_enabled?(@provider.get_priority)).to be_truthy end it "stores the 'enabled' state" do allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) expect(@provider.load_current_resource).to equal(@current_resource) expect(@current_resource.enabled).to be_truthy end end context "when update-rc.d shows init isn't linked to rc*.d/" do before do allow(@provider).to receive(:assert_update_rcd_available) @status = double("Status", :exitstatus => 0) @stdout = StringIO.new( " Removing any system startup links for /etc/init.d/chef ...") @stderr = StringIO.new @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out!).and_return(@status) allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) end it "says the service is disabled" do expect(@provider.service_currently_enabled?(@provider.get_priority)).to be_falsey end it "stores the 'disabled' state" do allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) expect(@provider.load_current_resource).to equal(@current_resource) expect(@current_resource.enabled).to be_falsey end end context "when update-rc.d fails" do before do @status = double("Status", :exitstatus => -1) allow(@provider).to receive(:popen4).and_return(@status) end it "raises an error" do @provider.define_resource_requirements expect do @provider.process_resource_requirements end.to raise_error(Chef::Exceptions::Service) end end { "Debian/Lenny and older" => { "linked" => { "stdout" => <<-STDOUT, Removing any system startup links for /etc/init.d/chef ... /etc/rc0.d/K20chef /etc/rc1.d/K20chef /etc/rc2.d/S20chef /etc/rc3.d/S20chef /etc/rc4.d/S20chef /etc/rc5.d/S20chef /etc/rc6.d/K20chef STDOUT "stderr" => "", "priorities" => { "0" => [:stop, "20"], "1" => [:stop, "20"], "2" => [:start, "20"], "3" => [:start, "20"], "4" => [:start, "20"], "5" => [:start, "20"], "6" => [:stop, "20"], }, }, "not linked" => { "stdout" => " Removing any system startup links for /etc/init.d/chef ...", "stderr" => "", }, }, "Debian/Squeeze and earlier" => { "linked" => { "stdout" => "update-rc.d: using dependency based boot sequencing", "stderr" => <<-STDERR, insserv: remove service /etc/init.d/../rc0.d/K20chef-client insserv: remove service /etc/init.d/../rc1.d/K20chef-client insserv: remove service /etc/init.d/../rc2.d/S20chef-client insserv: remove service /etc/init.d/../rc3.d/S20chef-client insserv: remove service /etc/init.d/../rc4.d/S20chef-client insserv: remove service /etc/init.d/../rc5.d/S20chef-client insserv: remove service /etc/init.d/../rc6.d/K20chef-client insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop STDERR "priorities" => { "0" => [:stop, "20"], "1" => [:stop, "20"], "2" => [:start, "20"], "3" => [:start, "20"], "4" => [:start, "20"], "5" => [:start, "20"], "6" => [:stop, "20"], }, }, "not linked" => { "stdout" => "update-rc.d: using dependency based boot sequencing", "stderr" => "", }, }, "Debian/Wheezy and earlier, a service only starting at run level S" => { "linked" => { "stdout" => "", "stderr" => <<-STDERR, insserv: remove service /etc/init.d/../rc0.d/K06rpcbind insserv: remove service /etc/init.d/../rc1.d/K06rpcbind insserv: remove service /etc/init.d/../rc6.d/K06rpcbind insserv: remove service /etc/init.d/../rcS.d/S13rpcbind insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop STDERR "priorities" => { "0" => [:stop, "06"], "1" => [:stop, "06"], "6" => [:stop, "06"], "S" => [:start, "13"], }, }, "not linked" => { "stdout" => "", "stderr" => "insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop", }, }, }.each do |model, expected_results| context "on #{model}" do context "when update-rc.d shows init linked to rc*.d/" do before do allow(@provider).to receive(:assert_update_rcd_available) @stdout = StringIO.new(expected_results["linked"]["stdout"]) @stderr = StringIO.new(expected_results["linked"]["stderr"]) @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out!).and_return(@status) allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) end it "says the service is enabled" do expect(@provider.service_currently_enabled?(@provider.get_priority)).to be_truthy end it "stores the 'enabled' state" do allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) expect(@provider.load_current_resource).to equal(@current_resource) expect(@current_resource.enabled).to be_truthy end it "stores the start/stop priorities of the service" do @provider.load_current_resource expect(@provider.current_resource.priority).to eq(expected_results["linked"]["priorities"]) end end context "when update-rc.d shows init isn't linked to rc*.d/" do before do allow(@provider).to receive(:assert_update_rcd_available) @stdout = StringIO.new(expected_results["not linked"]["stdout"]) @stderr = StringIO.new(expected_results["not linked"]["stderr"]) @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out!).and_return(@status) allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) end it "says the service is disabled" do expect(@provider.service_currently_enabled?(@provider.get_priority)).to be_falsey end it "stores the 'disabled' state" do allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) expect(@provider.load_current_resource).to equal(@current_resource) expect(@current_resource.enabled).to be_falsey end end end end end describe "action_enable" do shared_examples_for "the service is up to date" do it "does not enable the service" do expect(@provider).not_to receive(:enable_service) @provider.action_enable @provider.set_updated_status expect(@provider.new_resource).not_to be_updated end end shared_examples_for "the service is not up to date" do it "enables the service and sets the resource as updated" do expect(@provider).to receive(:enable_service).and_return(true) @provider.action_enable @provider.set_updated_status expect(@provider.new_resource).to be_updated end end context "when the service is disabled" do before do @current_resource.enabled(false) end it_behaves_like "the service is not up to date" end context "when the service is enabled" do before do @current_resource.enabled(true) @current_resource.priority(80) end context "and the service sets no priority" do it_behaves_like "the service is up to date" end context "and the service requests the same priority as is set" do before do @new_resource.priority(80) end it_behaves_like "the service is up to date" end context "and the service requests a different priority than is set" do before do @new_resource.priority(20) end it_behaves_like "the service is not up to date" end end end def expect_commands(provider, commands) commands.each do |command| expect(provider).to receive(:shell_out!).with(command) end end describe "enable_service" do let(:service_name) { @new_resource.service_name } context "when the service doesn't set a priority" do it "calls update-rc.d 'service_name' defaults" do expect_commands(@provider, [ "/usr/sbin/update-rc.d -f #{service_name} remove", "/usr/sbin/update-rc.d #{service_name} defaults", ]) @provider.enable_service end end context "when the service sets a simple priority" do before do @new_resource.priority(75) end it "calls update-rc.d 'service_name' defaults" do expect_commands(@provider, [ "/usr/sbin/update-rc.d -f #{service_name} remove", "/usr/sbin/update-rc.d #{service_name} defaults 75 25", ]) @provider.enable_service end end context "when the service sets complex priorities" do before do @new_resource.priority(2 => [:start, 20], 3 => [:stop, 55]) end it "calls update-rc.d 'service_name' with those priorities" do expect_commands(@provider, [ "/usr/sbin/update-rc.d -f #{service_name} remove", "/usr/sbin/update-rc.d #{service_name} start 20 2 . stop 55 3 . ", ]) @provider.enable_service end end end describe "disable_service" do let(:service_name) { @new_resource.service_name } context "when the service doesn't set a priority" do it "calls update-rc.d -f 'service_name' remove + stop with default priority" do expect_commands(@provider, [ "/usr/sbin/update-rc.d -f #{service_name} remove", "/usr/sbin/update-rc.d -f #{service_name} stop 80 2 3 4 5 .", ]) @provider.disable_service end end context "when the service sets a simple priority" do before do @new_resource.priority(75) end it "calls update-rc.d -f 'service_name' remove + stop with the specified priority" do expect_commands(@provider, [ "/usr/sbin/update-rc.d -f #{service_name} remove", "/usr/sbin/update-rc.d -f #{service_name} stop #{100 - @new_resource.priority} 2 3 4 5 .", ]) @provider.disable_service end end end end chef-12.14.60/spec/unit/provider/service/freebsd_service_spec.rb000066400000000000000000000556521276456504500246010ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Copyright:: Copyright 2009-2016, Bryan McLellan # 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" class Chef::Provider::Service::Freebsd public :service_enable_variable_name public :determine_enabled_status! public :determine_current_status! end describe Chef::Provider::Service::Freebsd do let(:node) do node = Chef::Node.new node.automatic_attrs[:command] = { :ps => "ps -ax" } node end let(:new_resource) do new_resource = Chef::Resource::Service.new("apache22") new_resource.pattern("httpd") new_resource.supports({ :status => false }) new_resource end let(:current_resource) do current_resource = Chef::Resource::Service.new("apache22") current_resource end let(:provider) do events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) provider = Chef::Provider::Service::Freebsd.new(new_resource, run_context) provider.action = :start provider end before do allow(Chef::Resource::Service).to receive(:new).and_return(current_resource) end def stub_etc_rcd_script allow(::File).to receive(:exist?).and_return(false) expect(::File).to receive(:exist?).with("/etc/rc.d/#{new_resource.service_name}").and_return(true) end def stub_usr_local_rcd_script allow(::File).to receive(:exist?).and_return(false) expect(::File).to receive(:exist?).with("/usr/local/etc/rc.d/#{new_resource.service_name}").and_return(true) end def run_load_current_resource stub_usr_local_rcd_script provider.load_current_resource end describe Chef::Provider::Service::Freebsd, "initialize" do it "should default enabled_state_found to false" do expect(provider.enabled_state_found).to be false end it "should find /usr/local/etc/rc.d init scripts" do stub_usr_local_rcd_script expect(provider.init_command).to eql "/usr/local/etc/rc.d/apache22" end it "should find /etc/rc.d init scripts" do stub_etc_rcd_script expect(provider.init_command).to eql "/etc/rc.d/apache22" end it "should set init_command to nil if it can't find anything" do allow(::File).to receive(:exist?).and_return(false) expect(provider.init_command).to be nil end end describe Chef::Provider::Service::Freebsd, "determine_current_status!" do before do stub_usr_local_rcd_script provider.current_resource = current_resource current_resource.service_name(new_resource.service_name) end context "when a status command has been specified" do let(:status) { double(:stdout => "", :exitstatus => 0) } before do new_resource.status_command("/bin/chefhasmonkeypants status") end it "should run the services status command if one has been specified" do expect(provider).to receive(:shell_out).with("/bin/chefhasmonkeypants status").and_return(status) provider.determine_current_status! end end context "when the service supports status" do let(:status) { double(:stdout => "", :exitstatus => 0) } before do new_resource.supports({ :status => true }) end it "should run '/etc/init.d/service_name status'" do expect(provider).to receive(:shell_out).with("/usr/local/etc/rc.d/#{new_resource.service_name} status").and_return(status) provider.determine_current_status! end it "should set running to true if the status command returns 0" do expect(provider).to receive(:shell_out).with("/usr/local/etc/rc.d/#{new_resource.service_name} status").and_return(status) provider.determine_current_status! expect(current_resource.running).to be true end it "should set running to false if the status command returns anything except 0" do expect(provider).to receive(:shell_out).with("/usr/local/etc/rc.d/#{new_resource.service_name} status").and_raise(Mixlib::ShellOut::ShellCommandFailed) provider.determine_current_status! expect(current_resource.running).to be false end end context "when we have a 'ps' attribute" do let(:stdout) do StringIO.new(<<-PS_SAMPLE) 413 ?? Ss 0:02.51 /usr/sbin/syslogd -s 539 ?? Is 0:00.14 /usr/sbin/sshd 545 ?? Ss 0:17.53 sendmail: accepting connections (sendmail) PS_SAMPLE end let(:status) { double(:stdout => stdout, :exitstatus => 0) } before do node.automatic_attrs[:command] = { :ps => "ps -ax" } end it "should shell_out! the node's ps command" do expect(provider).to receive(:shell_out!).with(node[:command][:ps]).and_return(status) provider.determine_current_status! end it "should read stdout of the ps command" do allow(provider).to receive(:shell_out!).and_return(status) expect(stdout).to receive(:each_line).and_return(true) provider.determine_current_status! end context "when the regex matches the output" do let(:stdout) do StringIO.new(<<-PS_SAMPLE) 555 ?? Ss 0:05.16 /usr/sbin/cron -s 9881 ?? Ss 0:06.67 /usr/local/sbin/httpd -DNOHTTPACCEPT PS_SAMPLE end it "should set running to true" do allow(provider).to receive(:shell_out!).and_return(status) provider.determine_current_status! expect(current_resource.running).to be_truthy end end it "should set running to false if the regex doesn't match" do allow(provider).to receive(:shell_out!).and_return(status) provider.determine_current_status! expect(current_resource.running).to be_falsey end it "should set running to nil if ps fails" do allow(provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) provider.determine_current_status! expect(current_resource.running).to be_nil expect(provider.status_load_success).to be_nil end context "when ps is empty string" do before do node.automatic_attrs[:command] = { :ps => "" } end it "should set running to nil" do provider.determine_current_status! expect(current_resource.running).to be_nil end end end end describe Chef::Provider::Service::Freebsd, "determine_enabled_status!" do before do stub_usr_local_rcd_script provider.current_resource = current_resource current_resource.service_name(new_resource.service_name) allow(provider).to receive(:service_enable_variable_name).and_return("#{new_resource.service_name}_enable") end context "when /etc/rc.conf does not exist" do before do expect(::File).to receive(:exist?).with("/etc/rc.conf").and_return(false) end it "sets enabled to false" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when /etc/rc.conf does exist" do before do expect(::File).to receive(:exist?).with("/etc/rc.conf").and_return(true) expect(provider).to receive(:read_rc_conf).and_return(lines) end %w{YES Yes yes yEs YeS}.each do |setting| context "when the enable variable is set to #{setting}" do let(:lines) { [ %Q{#{new_resource.service_name}_enable="#{setting}"} ] } it "sets enabled to true" do provider.determine_enabled_status! expect(current_resource.enabled).to be true end end end %w{No NO no nO None NONE none nOnE}.each do |setting| context "when the enable variable is set to #{setting}" do let(:lines) { [ %Q{#{new_resource.service_name}_enable="#{setting}"} ] } it "sets enabled to false" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end end context "when the enable variable is garbage" do let(:lines) { [ %Q{#{new_resource.service_name}_enable="alskdjflasdkjflakdfj"} ] } it "sets enabled to false" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when the enable variable partial matches (left) some other service and we are disabled" do let(:lines) do [ %Q{thing_#{new_resource.service_name}_enable="YES"}, %Q{#{new_resource.service_name}_enable="NO"}, ] end it "sets enabled based on the exact match (false)" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when the enable variable partial matches (right) some other service and we are disabled" do let(:lines) do [ %Q{#{new_resource.service_name}_thing_enable="YES"}, %Q{#{new_resource.service_name}_enable="NO"}, ] end it "sets enabled based on the exact match (false)" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when the enable variable partial matches (left) some other disabled service and we are enabled" do let(:lines) do [ %Q{thing_#{new_resource.service_name}_enable="NO"}, %Q{#{new_resource.service_name}_enable="YES"}, ] end it "sets enabled based on the exact match (true)" do provider.determine_enabled_status! expect(current_resource.enabled).to be true end end context "when the enable variable partial matches (right) some other disabled service and we are enabled" do let(:lines) do [ %Q{#{new_resource.service_name}_thing_enable="NO"}, %Q{#{new_resource.service_name}_enable="YES"}, ] end it "sets enabled based on the exact match (true)" do provider.determine_enabled_status! expect(current_resource.enabled).to be true end end context "when the enable variable only partial matches (left) some other enabled service" do let(:lines) { [ %Q{thing_#{new_resource.service_name}_enable="YES"} ] } it "sets enabled to false" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when the enable variable only partial matches (right) some other enabled service" do let(:lines) { [ %Q{#{new_resource.service_name}_thing_enable="YES"} ] } it "sets enabled to false" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when nothing matches" do let(:lines) { [] } it "sets enabled to true" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end end end describe Chef::Provider::Service::Freebsd, "service_enable_variable_name" do before do stub_usr_local_rcd_script provider.current_resource = current_resource current_resource.service_name(new_resource.service_name) expect(::File).to receive(:open).with("/usr/local/etc/rc.d/#{new_resource.service_name}").and_yield(rcscript) end context "when the rc script has a 'name' variable" do let(:rcscript) do StringIO.new(<<-EOF) name="#{new_resource.service_name}" rcvar=`set_rcvar` EOF end it "should not raise an exception if the rcscript have a name variable" do expect { provider.service_enable_variable_name }.not_to raise_error end it "should not run rcvar" do expect(provider).not_to receive(:shell_out!) provider.service_enable_variable_name end it "should return the enable variable determined from the rcscript name" do expect(provider.service_enable_variable_name).to eql "#{new_resource.service_name}_enable" end end describe "when the rcscript does not have a name variable" do let(:rcscript) do StringIO.new <<-EOF rcvar=`set_rcvar` EOF end before do status = double(:stdout => rcvar_stdout, :exitstatus => 0) allow(provider).to receive(:shell_out!).with("/usr/local/etc/rc.d/#{new_resource.service_name} rcvar").and_return(status) end describe "when rcvar returns foobar_enable" do let(:rcvar_stdout) do rcvar_stdout = <<-EOF # apache22 # # #{new_resource.service_name}_enable="YES" # (default: "") EOF end it "should get the service name from rcvar if the rcscript does not have a name variable" do expect(provider.service_enable_variable_name).to eq("#{new_resource.service_name}_enable") end it "should not raise an exception if the rcscript does not have a name variable" do expect { provider.service_enable_variable_name }.not_to raise_error end end describe "when rcvar does not return foobar_enable" do let(:rcvar_stdout) do rcvar_stdout = <<-EOF # service_with_noname # EOF end it "should return nil" do expect(provider.service_enable_variable_name).to be nil end end end end describe Chef::Provider::Service::Freebsd, "load_current_resource" do before(:each) do stub_usr_local_rcd_script expect(provider).to receive(:determine_current_status!) current_resource.running(false) allow(provider).to receive(:service_enable_variable_name).and_return "#{new_resource.service_name}_enable" end it "should create a current resource with the name of the new resource" do expect(Chef::Resource::Service).to receive(:new).and_return(current_resource) provider.load_current_resource end it "should set the current resources service name to the new resources service name" do provider.load_current_resource expect(current_resource.service_name).to eq(new_resource.service_name) end it "should return the current resource" do expect(provider.load_current_resource).to eql(current_resource) end end context "when testing actions" do before(:each) do stub_usr_local_rcd_script expect(provider).to receive(:determine_current_status!) current_resource.running(false) expect(provider).to receive(:determine_enabled_status!) current_resource.enabled(false) provider.load_current_resource end describe Chef::Provider::Service::Freebsd, "start_service" do it "should call the start command if one is specified" do new_resource.start_command("/etc/rc.d/chef startyousillysally") expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/chef startyousillysally") provider.start_service() end it "should call '/usr/local/etc/rc.d/service_name faststart' if no start command is specified" do expect(provider).to receive(:shell_out_with_systems_locale!).with("/usr/local/etc/rc.d/#{new_resource.service_name} faststart") provider.start_service() end end describe Chef::Provider::Service::Freebsd, "stop_service" do it "should call the stop command if one is specified" do new_resource.stop_command("/etc/init.d/chef itoldyoutostop") expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef itoldyoutostop") provider.stop_service() end it "should call '/usr/local/etc/rc.d/service_name faststop' if no stop command is specified" do expect(provider).to receive(:shell_out_with_systems_locale!).with("/usr/local/etc/rc.d/#{new_resource.service_name} faststop") provider.stop_service() end end describe Chef::Provider::Service::Freebsd, "restart_service" do it "should call 'restart' on the service_name if the resource supports it" do new_resource.supports({ :restart => true }) expect(provider).to receive(:shell_out_with_systems_locale!).with("/usr/local/etc/rc.d/#{new_resource.service_name} fastrestart") provider.restart_service() end it "should call the restart_command if one has been specified" do new_resource.restart_command("/etc/init.d/chef restartinafire") expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef restartinafire") provider.restart_service() end it "otherwise it should call stop and start" do expect(provider).to receive(:stop_service) expect(provider).to receive(:start_service) provider.restart_service() end end end describe Chef::Provider::Service::Freebsd, "define_resource_requirements" do before do provider.current_resource = current_resource end context "when the init script is not found" do before do provider.init_command = nil allow(provider).to receive(:service_enable_variable_name).and_return("#{new_resource.service_name}_enable") end %w{start reload restart enable}.each do |action| it "should raise an exception when the action is #{action}" do provider.define_resource_requirements provider.action = action expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end %w{stop disable}.each do |action| it "should not raise an error when the action is #{action}" do provider.define_resource_requirements provider.action = action expect { provider.process_resource_requirements }.not_to raise_error end end end context "when the init script is found, but the service_enable_variable_name is nil" do before do provider.init_command = nil allow(provider).to receive(:service_enable_variable_name).and_return(nil) end %w{start reload restart enable}.each do |action| it "should raise an exception when the action is #{action}" do provider.action = action provider.define_resource_requirements expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end %w{stop disable}.each do |action| it "should not raise an error when the action is #{action}" do provider.action = action provider.define_resource_requirements expect { provider.process_resource_requirements }.not_to raise_error end end end end describe Chef::Provider::Service::Freebsd, "enable_service" do before do provider.current_resource = current_resource allow(provider).to receive(:service_enable_variable_name).and_return("#{new_resource.service_name}_enable") end it "should enable the service if it is not enabled" do allow(current_resource).to receive(:enabled).and_return(false) expect(provider).to receive(:read_rc_conf).and_return([ "foo", "#{new_resource.service_name}_enable=\"NO\"", "bar" ]) expect(provider).to receive(:write_rc_conf).with(["foo", "bar", "#{new_resource.service_name}_enable=\"YES\""]) provider.enable_service() end it "should not partial match an already enabled service" do allow(current_resource).to receive(:enabled).and_return(false) expect(provider).to receive(:read_rc_conf).and_return([ "foo", "thing_#{new_resource.service_name}_enable=\"NO\"", "bar" ]) expect(provider).to receive(:write_rc_conf).with(["foo", "thing_#{new_resource.service_name}_enable=\"NO\"", "bar", "#{new_resource.service_name}_enable=\"YES\""]) provider.enable_service() end it "should enable the service if it is not enabled and not already specified in the rc.conf file" do allow(current_resource).to receive(:enabled).and_return(false) expect(provider).to receive(:read_rc_conf).and_return(%w{foo bar}) expect(provider).to receive(:write_rc_conf).with(["foo", "bar", "#{new_resource.service_name}_enable=\"YES\""]) provider.enable_service() end it "should not enable the service if it is already enabled" do allow(current_resource).to receive(:enabled).and_return(true) expect(provider).not_to receive(:write_rc_conf) provider.enable_service end it "should remove commented out versions of it being enabled" do allow(current_resource).to receive(:enabled).and_return(false) expect(provider).to receive(:read_rc_conf).and_return([ "foo", "bar", "\# #{new_resource.service_name}_enable=\"YES\"", "\# #{new_resource.service_name}_enable=\"NO\""]) expect(provider).to receive(:write_rc_conf).with(["foo", "bar", "#{new_resource.service_name}_enable=\"YES\""]) provider.enable_service() end end describe Chef::Provider::Service::Freebsd, "disable_service" do before do provider.current_resource = current_resource allow(provider).to receive(:service_enable_variable_name).and_return("#{new_resource.service_name}_enable") end it "should disable the service if it is not disabled" do allow(current_resource).to receive(:enabled).and_return(true) expect(provider).to receive(:read_rc_conf).and_return([ "foo", "#{new_resource.service_name}_enable=\"YES\"", "bar" ]) expect(provider).to receive(:write_rc_conf).with(["foo", "bar", "#{new_resource.service_name}_enable=\"NO\""]) provider.disable_service() end it "should not disable an enabled service that partially matches" do allow(current_resource).to receive(:enabled).and_return(true) expect(provider).to receive(:read_rc_conf).and_return([ "foo", "thing_#{new_resource.service_name}_enable=\"YES\"", "bar" ]) expect(provider).to receive(:write_rc_conf).with(["foo", "thing_#{new_resource.service_name}_enable=\"YES\"", "bar", "#{new_resource.service_name}_enable=\"NO\""]) provider.disable_service() end it "should not disable the service if it is already disabled" do allow(current_resource).to receive(:enabled).and_return(false) expect(provider).not_to receive(:write_rc_conf) provider.disable_service() end it "should remove commented out versions of it being disabled or enabled" do allow(current_resource).to receive(:enabled).and_return(true) expect(provider).to receive(:read_rc_conf).and_return([ "foo", "bar", "\# #{new_resource.service_name}_enable=\"YES\"", "\# #{new_resource.service_name}_enable=\"NO\""]) expect(provider).to receive(:write_rc_conf).with(["foo", "bar", "#{new_resource.service_name}_enable=\"NO\""]) provider.disable_service() end end end chef-12.14.60/spec/unit/provider/service/gentoo_service_spec.rb000066400000000000000000000127751276456504500244610ustar00rootroot00000000000000# # Author:: Lee Jensen () # Author:: AJ Christensen () # 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::Service::Gentoo do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @current_resource = Chef::Resource::Service.new("chef") @provider = Chef::Provider::Service::Gentoo.new(@new_resource, @run_context) allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out).and_return(@status) allow(File).to receive(:exists?).with("/etc/init.d/chef").and_return(true) allow(File).to receive(:exists?).with("/sbin/rc-update").and_return(true) allow(File).to receive(:exists?).with("/etc/runlevels/default/chef").and_return(false) allow(File).to receive(:readable?).with("/etc/runlevels/default/chef").and_return(false) end # new test: found_enabled state # describe "load_current_resource" do it "should raise Chef::Exceptions::Service if /sbin/rc-update does not exist" do expect(File).to receive(:exists?).with("/sbin/rc-update").and_return(false) @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end it "should track when service file is not found in /etc/runlevels" do @provider.load_current_resource expect(@provider.instance_variable_get("@found_script")).to be_falsey end it "should track when service file is found in /etc/runlevels/**/" do allow(Dir).to receive(:glob).with("/etc/runlevels/**/chef").and_return(["/etc/runlevels/default/chef"]) @provider.load_current_resource expect(@provider.instance_variable_get("@found_script")).to be_truthy end describe "when detecting the service enable state" do describe "and the glob returns a default service script file" do before do allow(Dir).to receive(:glob).with("/etc/runlevels/**/chef").and_return(["/etc/runlevels/default/chef"]) end describe "and the file exists and is readable" do before do allow(File).to receive(:exists?).with("/etc/runlevels/default/chef").and_return(true) allow(File).to receive(:readable?).with("/etc/runlevels/default/chef").and_return(true) end it "should set enabled to true" do @provider.load_current_resource expect(@current_resource.enabled).to be_truthy end end describe "and the file exists but is not readable" do before do allow(File).to receive(:exists?).with("/etc/runlevels/default/chef").and_return(true) allow(File).to receive(:readable?).with("/etc/runlevels/default/chef").and_return(false) end it "should set enabled to false" do @provider.load_current_resource expect(@current_resource.enabled).to be_falsey end end describe "and the file does not exist" do before do allow(File).to receive(:exists?).with("/etc/runlevels/default/chef").and_return(false) allow(File).to receive(:readable?).with("/etc/runlevels/default/chef").and_return("foobarbaz") end it "should set enabled to false" do @provider.load_current_resource expect(@current_resource.enabled).to be_falsey end end end end it "should return the current_resource" do expect(@provider.load_current_resource).to eq(@current_resource) end it "should support the status command automatically" do @provider.load_current_resource expect(@provider.supports[:status]).to be true end it "should support the restart command automatically" do @provider.load_current_resource expect(@provider.supports[:restart]).to be true end it "should not support the reload command automatically" do @provider.load_current_resource expect(@provider.supports[:reload]).to be_falsey end end describe "action_methods" do before(:each) { allow(@provider).to receive(:load_current_resource).and_return(@current_resource) } describe Chef::Provider::Service::Gentoo, "enable_service" do it "should call rc-update add *service* default" do expect(@provider).to receive(:shell_out!).with("/sbin/rc-update add chef default") @provider.enable_service() end end describe Chef::Provider::Service::Gentoo, "disable_service" do it "should call rc-update del *service* default" do expect(@provider).to receive(:shell_out!).with("/sbin/rc-update del chef default") @provider.disable_service() end end end end chef-12.14.60/spec/unit/provider/service/init_service_spec.rb000066400000000000000000000225161276456504500241230ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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::Service::Init, "load_current_resource" do before(:each) do @node = Chef::Node.new @node.automatic_attrs[:command] = { :ps => "ps -ef" } @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @current_resource = Chef::Resource::Service.new("chef") @provider = Chef::Provider::Service::Init.new(@new_resource, @run_context) allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @stdout = StringIO.new(<<-PS) aj 7842 5057 0 21:26 pts/2 00:00:06 vi init.rb aj 7903 5016 0 21:26 pts/5 00:00:00 /bin/bash aj 8119 6041 0 21:34 pts/3 00:00:03 vi init_service_spec.rb PS @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out!).and_return(@status) end it "should create a current resource with the name of the new resource" do @provider.load_current_resource expect(@provider.current_resource).to equal(@current_resource) end it "should set the current resources service name to the new resources service name" do @provider.load_current_resource expect(@current_resource.service_name).to eq("chef") end describe "when the service supports status" do before do @new_resource.supports({ :status => true }) end it "should run '/etc/init.d/service_name status'" do expect(@provider).to receive(:shell_out).with("/etc/init.d/#{@current_resource.service_name} status").and_return(@status) @provider.load_current_resource end it "should set running to true if the status command returns 0" do allow(@provider).to receive(:shell_out).with("/etc/init.d/#{@current_resource.service_name} status").and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the status command returns anything except 0" do allow(@status).to receive(:exitstatus).and_return(1) allow(@provider).to receive(:shell_out).with("/etc/init.d/#{@current_resource.service_name} status").and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_falsey end it "should set running to false if the status command raises" do allow(@provider).to receive(:shell_out).and_raise(Mixlib::ShellOut::ShellCommandFailed) @provider.load_current_resource expect(@current_resource.running).to be_falsey end end describe "when a status command has been specified" do before do allow(@new_resource).to receive(:status_command).and_return("/etc/init.d/chefhasmonkeypants status") end it "should run the services status command if one has been specified" do expect(@provider).to receive(:shell_out).with("/etc/init.d/chefhasmonkeypants status").and_return(@status) @provider.load_current_resource end end describe "when an init command has been specified" do before do allow(@new_resource).to receive(:init_command).and_return("/opt/chef-server/service/erchef") @provider = Chef::Provider::Service::Init.new(@new_resource, @run_context) end it "should use the init_command if one has been specified" do expect(@provider).to receive(:shell_out_with_systems_locale!).with("/opt/chef-server/service/erchef start") @provider.start_service end end describe "when the node has not specified a ps command" do it "should raise an error if the node has a nil ps attribute" do @node.automatic_attrs[:command] = { :ps => nil } @provider.load_current_resource @provider.action = :start @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end it "should raise an error if the node has an empty ps attribute" do @node.automatic_attrs[:command] = { :ps => "" } @provider.load_current_resource @provider.action = :start @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end describe "when we have a 'ps' attribute" do it "should shell_out! the node's ps command" do expect(@provider).to receive(:shell_out!).and_return(@status) @provider.load_current_resource end it "should set running to true if the regex matches the output" do @stdout = StringIO.new(<<-RUNNING_PS) aj 7842 5057 0 21:26 pts/2 00:00:06 chef aj 7842 5057 0 21:26 pts/2 00:00:06 poos RUNNING_PS allow(@status).to receive(:stdout).and_return(@stdout) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the regex doesn't match" do allow(@provider).to receive(:shell_out!).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_falsey end it "should raise an exception if ps fails" do allow(@provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) @provider.load_current_resource @provider.action = :start @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end it "should return the current resource" do expect(@provider.load_current_resource).to eql(@current_resource) end describe "when starting the service" do it "should call the start command if one is specified" do @new_resource.start_command("/etc/init.d/chef startyousillysally") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef startyousillysally") @provider.start_service() end it "should call '/etc/init.d/service_name start' if no start command is specified" do expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/#{@new_resource.service_name} start") @provider.start_service() end end describe Chef::Provider::Service::Init, "stop_service" do it "should call the stop command if one is specified" do @new_resource.stop_command("/etc/init.d/chef itoldyoutostop") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef itoldyoutostop") @provider.stop_service() end it "should call '/etc/init.d/service_name stop' if no stop command is specified" do expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/#{@new_resource.service_name} stop") @provider.stop_service() end end describe "when restarting a service" do it "should call 'restart' on the service_name if the resource supports it" do @new_resource.supports({ :restart => true }) expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/#{@new_resource.service_name} restart") @provider.restart_service() end it "should call the restart_command if one has been specified" do @new_resource.restart_command("/etc/init.d/chef restartinafire") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/#{@new_resource.service_name} restartinafire") @provider.restart_service() end it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do expect(@provider).to receive(:stop_service) expect(@provider).to receive(:sleep).with(1) expect(@provider).to receive(:start_service) @provider.restart_service() end end describe "when reloading a service" do it "should call 'reload' on the service if it supports it" do @new_resource.supports({ :reload => true }) expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef reload") @provider.reload_service() end it "should should run the user specified reload command if one is specified and the service doesn't support reload" do @new_resource.reload_command("/etc/init.d/chef lollerpants") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef lollerpants") @provider.reload_service() end end describe "when a custom command has been specified" do before do @new_resource.start_command("/etc/init.d/chef startyousillysally") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef startyousillysally") end it "should still pass all why run assertions" do expect { @provider.run_action(:start) }.not_to raise_error end end end chef-12.14.60/spec/unit/provider/service/insserv_service_spec.rb000066400000000000000000000053161276456504500246500ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Provider::Service::Insserv do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @node.automatic_attrs[:command] = { :ps => "ps -ax" } @new_resource = Chef::Resource::Service.new("initgrediant") @current_resource = Chef::Resource::Service.new("initgrediant") @provider = Chef::Provider::Service::Insserv.new(@new_resource, @run_context) @status = double("Process::Status mock", :exitstatus => 0, :stdout => "") allow(@provider).to receive(:shell_out!).and_return(@status) end describe "load_current_resource" do describe "when startup links exist" do before do allow(Dir).to receive(:glob).with("/etc/rc**/S*initgrediant").and_return(["/etc/rc5.d/S18initgrediant", "/etc/rc2.d/S18initgrediant", "/etc/rc4.d/S18initgrediant", "/etc/rc3.d/S18initgrediant"]) end it "sets the current enabled status to true" do @provider.load_current_resource expect(@provider.current_resource.enabled).to be_truthy end end describe "when startup links do not exist" do before do allow(Dir).to receive(:glob).with("/etc/rc**/S*initgrediant").and_return([]) end it "sets the current enabled status to false" do @provider.load_current_resource expect(@provider.current_resource.enabled).to be_falsey end end end describe "enable_service" do it "should call insserv and create the default links" do expect(@provider).to receive(:shell_out!).with("/sbin/insserv -r -f #{@new_resource.service_name}") expect(@provider).to receive(:shell_out!).with("/sbin/insserv -d -f #{@new_resource.service_name}") @provider.enable_service end end describe "disable_service" do it "should call insserv and remove the links" do expect(@provider).to receive(:shell_out!).with("/sbin/insserv -r -f #{@new_resource.service_name}") @provider.disable_service end end end chef-12.14.60/spec/unit/provider/service/invokercd_service_spec.rb000066400000000000000000000217401276456504500251420ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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::Service::Invokercd, "load_current_resource" do before(:each) do @node = Chef::Node.new @node.automatic_attrs[:command] = { :ps => "ps -ef" } @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @current_resource = Chef::Resource::Service.new("chef") @provider = Chef::Provider::Service::Invokercd.new(@new_resource, @run_context) allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @stdout = StringIO.new(<<-PS) aj 7842 5057 0 21:26 pts/2 00:00:06 vi init.rb aj 7903 5016 0 21:26 pts/5 00:00:00 /bin/bash aj 8119 6041 0 21:34 pts/3 00:00:03 vi init_service_spec.rb PS @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out!).and_return(@status) end it "should create a current resource with the name of the new resource" do @provider.load_current_resource expect(@provider.current_resource).to equal(@current_resource) end it "should set the current resources service name to the new resources service name" do @provider.load_current_resource expect(@current_resource.service_name).to eq("chef") end describe "when the service supports status" do before do @new_resource.supports({ :status => true }) end it "should run '/usr/sbin/invoke-rc.d service_name status'" do expect(@provider).to receive(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_return(@status) @provider.load_current_resource end it "should set running to true if the status command returns 0" do allow(@provider).to receive(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the status command returns anything except 0" do allow(@status).to receive(:exitstatus).and_return(1) allow(@provider).to receive(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_falsey end it "should set running to false if the status command raises" do allow(@provider).to receive(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_raise(Mixlib::ShellOut::ShellCommandFailed) @provider.load_current_resource expect(@current_resource.running).to be_falsey end end describe "when a status command has been specified" do before do allow(@new_resource).to receive(:status_command).and_return("/usr/sbin/invoke-rc.d chefhasmonkeypants status") end it "should run the services status command if one has been specified" do expect(@provider).to receive(:shell_out).with("/usr/sbin/invoke-rc.d chefhasmonkeypants status").and_return(@status) @provider.load_current_resource end end describe "when the node has not specified a ps command" do it "should raise error if the node has a nil ps attribute and no other means to get status" do @node.automatic_attrs[:command] = { :ps => nil } @provider.action = :start @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end it "should raise error if the node has an empty ps attribute and no other means to get status" do @node.automatic_attrs[:command] = { :ps => "" } @provider.action = :start @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end describe "when we have a 'ps' attribute" do it "should shell_out! the node's ps command" do @status = double("Status", :exitstatus => 0, :stdout => @stdout) expect(@provider).to receive(:shell_out!).with(@node[:command][:ps]).and_return(@status) @provider.load_current_resource end it "should set running to true if the regex matches the output" do @stdout = StringIO.new(<<-RUNNING_PS) aj 7842 5057 0 21:26 pts/2 00:00:06 chef aj 7842 5057 0 21:26 pts/2 00:00:06 poos RUNNING_PS @status = double("Status", :exitstatus => 0, :stdout => @stdout) expect(@provider).to receive(:shell_out!).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the regex doesn't match" do @status = double("Status", :exitstatus => 0, :stdout => @stdout) expect(@provider).to receive(:shell_out!).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_falsey end it "should raise an exception if ps fails" do allow(@provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) @provider.action = :start @provider.load_current_resource @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end it "should return the current resource" do expect(@provider.load_current_resource).to eql(@current_resource) end describe "when starting the service" do it "should call the start command if one is specified" do @new_resource.start_command("/usr/sbin/invoke-rc.d chef startyousillysally") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d chef startyousillysally") @provider.start_service() end it "should call '/usr/sbin/invoke-rc.d service_name start' if no start command is specified" do expect(@provider).to receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} start") @provider.start_service() end end describe Chef::Provider::Service::Invokercd, "stop_service" do it "should call the stop command if one is specified" do @new_resource.stop_command("/usr/sbin/invoke-rc.d chef itoldyoutostop") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d chef itoldyoutostop") @provider.stop_service() end it "should call '/usr/sbin/invoke-rc.d service_name stop' if no stop command is specified" do expect(@provider).to receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} stop") @provider.stop_service() end end describe "when restarting a service" do it "should call 'restart' on the service_name if the resource supports it" do @new_resource.supports({ :restart => true }) expect(@provider).to receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} restart") @provider.restart_service() end it "should call the restart_command if one has been specified" do @new_resource.restart_command("/usr/sbin/invoke-rc.d chef restartinafire") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} restartinafire") @provider.restart_service() end it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do expect(@provider).to receive(:stop_service) expect(@provider).to receive(:sleep).with(1) expect(@provider).to receive(:start_service) @provider.restart_service() end end describe "when reloading a service" do it "should call 'reload' on the service if it supports it" do @new_resource.supports({ :reload => true }) expect(@provider).to receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d chef reload") @provider.reload_service() end it "should should run the user specified reload command if one is specified and the service doesn't support reload" do @new_resource.reload_command("/usr/sbin/invoke-rc.d chef lollerpants") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/usr/sbin/invoke-rc.d chef lollerpants") @provider.reload_service() end end end chef-12.14.60/spec/unit/provider/service/macosx_spec.rb000066400000000000000000000314511276456504500227300ustar00rootroot00000000000000# # Author:: Igor Afonov # Copyright:: Copyright 2011-2016, Igor Afonov # 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::Service::Macosx do describe ".gather_plist_dirs" do context "when HOME directory is set" do before do allow(Chef::Util::PathHelper).to receive(:home).with("Library", "LaunchAgents").and_yield("/Users/someuser/Library/LaunchAgents") end it "includes users's LaunchAgents folder" do expect(described_class.gather_plist_dirs).to include("/Users/someuser/Library/LaunchAgents") end end context "when HOME directory is not set" do before do allow(Chef::Util::PathHelper).to receive(:home).with("Library", "LaunchAgents").and_return(nil) end it "doesn't include user's LaunchAgents folder" do expect(described_class.gather_plist_dirs).not_to include("~/Library/LaunchAgents") end end end context "when service name is given as" do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:provider) { described_class.new(new_resource, run_context) } let(:launchctl_stdout) { StringIO.new } let(:plutil_stdout) { String.new <<-XML } Label io.redis.redis-server XML %w{Daemon Agent}.each do |service_type| ["redis-server", "io.redis.redis-server"].each do |service_name| ["10.9", "10.10", "10.11"].each do |platform_version| let(:plist) { "/Library/LaunchDaemons/io.redis.redis-server.plist" } let(:session) { StringIO.new } if service_type == "Agent" let(:plist) { "/Library/LaunchAgents/io.redis.redis-server.plist" } let(:session) { "-S Aqua " } let(:su_cmd) { "su -l igor -c" } if platform_version == "10.9" let(:su_cmd) { "su igor -c" } end end let(:service_label) { "io.redis.redis-server" } before do allow(Dir).to receive(:glob).and_return([plist], []) allow(Etc).to receive(:getlogin).and_return("igor") allow(node).to receive(:[]).with("platform_version").and_return(platform_version) cmd = "launchctl list #{service_label}" allow(provider).to receive(:shell_out_with_systems_locale). with(/(#{su_cmd} '#{cmd}'|#{cmd})/). and_return(double("Status", :stdout => launchctl_stdout, :exitstatus => 0)) allow(File).to receive(:exists?).and_return([true], []) allow(provider).to receive(:shell_out_with_systems_locale!). with(/plutil -convert xml1 -o/). and_return(double("Status", :stdout => plutil_stdout)) end context "#{service_name} that is a #{service_type} running Osx #{platform_version}" do let(:new_resource) { Chef::Resource::MacosxService.new(service_name) } let!(:current_resource) { Chef::Resource::MacosxService.new(service_name) } describe "#load_current_resource" do # CHEF-5223 "you can't glob for a file that hasn't been converged # onto the node yet." context "when the plist doesn't exist" do def run_resource_setup_for_action(action) new_resource.action(action) provider.action = action provider.load_current_resource provider.define_resource_requirements provider.process_resource_requirements end before do allow(Dir).to receive(:glob).and_return([]) allow(File).to receive(:exists?).and_return([true], []) allow(provider).to receive(:shell_out!). with(/plutil -convert xml1 -o/). and_raise(Mixlib::ShellOut::ShellCommandFailed) end it "works for action :nothing" do expect { run_resource_setup_for_action(:nothing) }.not_to raise_error end it "works for action :start" do expect { run_resource_setup_for_action(:start) }.not_to raise_error end it "errors if action is :enable" do expect { run_resource_setup_for_action(:enable) }.to raise_error(Chef::Exceptions::Service) end it "errors if action is :disable" do expect { run_resource_setup_for_action(:disable) }.to raise_error(Chef::Exceptions::Service) end end context "when launchctl returns pid in service list" do let(:launchctl_stdout) { StringIO.new <<-SVC_LIST } { "LimitLoadToSessionType" = "System"; "Label" = "io.redis.redis-server"; "TimeOut" = 30; "OnDemand" = false; "LastExitStatus" = 0; "PID" = 62803; "Program" = "do_some.sh"; "ProgramArguments" = ( "path/to/do_something.sh"; "-f"; ); }; SVC_LIST before do provider.load_current_resource end it "sets resource running state to true" do expect(provider.current_resource.running).to be_truthy end it "sets resouce enabled state to true" do expect(provider.current_resource.enabled).to be_truthy end end describe "running unsupported actions" do before do allow(Dir).to receive(:glob).and_return(["#{plist}"], []) allow(File).to receive(:exists?).and_return([true], []) end it "should throw an exception when reload action is attempted" do expect { provider.run_action(:reload) }.to raise_error(Chef::Exceptions::UnsupportedAction) end end context "when launchctl returns empty service pid" do let(:launchctl_stdout) { StringIO.new <<-SVC_LIST } { "LimitLoadToSessionType" = "System"; "Label" = "io.redis.redis-server"; "TimeOut" = 30; "OnDemand" = false; "LastExitStatus" = 0; "Program" = "do_some.sh"; "ProgramArguments" = ( "path/to/do_something.sh"; "-f"; ); }; SVC_LIST before do provider.load_current_resource end it "sets resource running state to false" do expect(provider.current_resource.running).to be_falsey end it "sets resouce enabled state to true" do expect(provider.current_resource.enabled).to be_truthy end end context "when launchctl doesn't return service entry at all" do let(:launchctl_stdout) { StringIO.new <<-SVC_LIST } Could not find service "io.redis.redis-server" in domain for system SVC_LIST it "sets service running state to false" do provider.load_current_resource expect(provider.current_resource.running).to be_falsey end context "and plist for service is not available" do before do allow(Dir).to receive(:glob).and_return([]) provider.load_current_resource end it "sets resouce enabled state to false" do expect(provider.current_resource.enabled).to be_falsey end end context "and plist for service is available" do before do allow(Dir).to receive(:glob).and_return(["#{plist}"], []) provider.load_current_resource end it "sets resouce enabled state to true" do expect(provider.current_resource.enabled).to be_truthy end end describe "and several plists match service name" do it "throws exception" do allow(Dir).to receive(:glob).and_return(["#{plist}", "/Users/wtf/something.plist"]) provider.load_current_resource provider.define_resource_requirements expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end end end describe "#start_service" do before do allow(Chef::Resource::MacosxService).to receive(:new).and_return(current_resource) provider.load_current_resource allow(current_resource).to receive(:running).and_return(false) end it "calls the start command if one is specified and service is not running" do allow(new_resource).to receive(:start_command).and_return("cowsay dirty") expect(provider).to receive(:shell_out_with_systems_locale!).with("cowsay dirty") provider.start_service end it "shows warning message if service is already running" do allow(current_resource).to receive(:running).and_return(true) expect(Chef::Log).to receive(:debug).with("macosx_service[#{service_name}] already running, not starting") provider.start_service end it "starts service via launchctl if service found" do cmd = "launchctl load -w " + session + plist expect(provider).to receive(:shell_out_with_systems_locale). with(/(#{su_cmd} .#{cmd}.|#{cmd})/). and_return(0) provider.start_service end end describe "#stop_service" do before do allow(Chef::Resource::MacosxService).to receive(:new).and_return(current_resource) provider.load_current_resource allow(current_resource).to receive(:running).and_return(true) end it "calls the stop command if one is specified and service is running" do allow(new_resource).to receive(:stop_command).and_return("kill -9 123") expect(provider).to receive(:shell_out_with_systems_locale!).with("kill -9 123") provider.stop_service end it "shows warning message if service is not running" do allow(current_resource).to receive(:running).and_return(false) expect(Chef::Log).to receive(:debug).with("macosx_service[#{service_name}] not running, not stopping") provider.stop_service end it "stops the service via launchctl if service found" do cmd = "launchctl unload -w " + plist expect(provider).to receive(:shell_out_with_systems_locale). with(/(#{su_cmd} .#{cmd}.|#{cmd})/). and_return(0) provider.stop_service end end describe "#restart_service" do before do allow(Chef::Resource::Service).to receive(:new).and_return(current_resource) provider.load_current_resource allow(current_resource).to receive(:running).and_return(true) allow(provider).to receive(:sleep) end it "issues a command if given" do allow(new_resource).to receive(:restart_command).and_return("reload that thing") expect(provider).to receive(:shell_out_with_systems_locale!).with("reload that thing") provider.restart_service end it "stops and then starts service" do expect(provider).to receive(:unload_service) expect(provider).to receive(:load_service); provider.restart_service end end end end end end end end chef-12.14.60/spec/unit/provider/service/openbsd_service_spec.rb000066400000000000000000000502151276456504500246070ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Author:: Scott Bonds (scott@ggr.com) # Copyright:: Copyright 2009-2016, Bryan McLellan # Copyright:: Copyright 2014-2016, Scott Bonds # 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" class Chef::Provider::Service::Openbsd public :builtin_service_enable_variable_name public :determine_enabled_status! public :determine_current_status! public :is_enabled? attr_accessor :rc_conf, :rc_conf_local end describe Chef::Provider::Service::Openbsd do let(:node) do node = Chef::Node.new node.automatic_attrs[:command] = { :ps => "ps -ax" } node end let(:supports) { { :status => false } } let(:new_resource) do new_resource = Chef::Resource::Service.new("sndiod") new_resource.pattern("sndiod") new_resource.supports(supports) new_resource end let(:current_resource) do current_resource = Chef::Resource::Service.new("sndiod") current_resource end let(:provider) do events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) allow(::File).to receive(:read).with("/etc/rc.conf").and_return("") allow(::File).to receive(:read).with("/etc/rc.conf.local").and_return("") provider = Chef::Provider::Service::Openbsd.new(new_resource, run_context) provider.action = :start provider end before do allow(Chef::Resource::Service).to receive(:new).and_return(current_resource) end def stub_etc_rcd_script allow(::File).to receive(:exist?).and_return(false) expect(::File).to receive(:exist?).with("/etc/rc.d/#{new_resource.service_name}").and_return(true) end def run_load_current_resource stub_etc_rcd_script provider.load_current_resource end describe Chef::Provider::Service::Openbsd, "initialize" do it "should find /etc/rc.d init scripts" do stub_etc_rcd_script expect(provider.init_command).to eql "/etc/rc.d/sndiod" end it "should set init_command to nil if it can't find anything" do expect(::File).to receive(:exist?).with("/etc/rc.d/sndiod").and_return(false) expect(provider.init_command).to be nil end end describe Chef::Provider::Service::Openbsd, "determine_current_status!" do before do stub_etc_rcd_script provider.current_resource = current_resource current_resource.service_name(new_resource.service_name) end context "when a status command has been specified" do let(:status) { double(:stdout => "", :exitstatus => 0) } before do new_resource.status_command("/bin/chefhasmonkeypants status") end it "should run the services status command if one has been specified" do expect(provider).to receive(:shell_out).with("/bin/chefhasmonkeypants status").and_return(status) provider.determine_current_status! end end context "when the service supports status" do let(:status) { double(:stdout => "", :exitstatus => 0) } let(:supports) { { :status => true } } it "should run '/etc/rc.d/service_name status'" do expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_return(status) provider.determine_current_status! end it "should set running to true if the status command returns 0" do expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_return(status) provider.determine_current_status! expect(current_resource.running).to be true end it "should set running to false if the status command returns anything except 0" do expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_raise(Mixlib::ShellOut::ShellCommandFailed) provider.determine_current_status! expect(current_resource.running).to be false end end end describe Chef::Provider::Service::Openbsd, "determine_enabled_status!" do before do stub_etc_rcd_script provider.current_resource = current_resource current_resource.service_name(new_resource.service_name) allow(provider).to receive(:service_enable_variable_name).and_return("#{new_resource.service_name}_enable") end context "when the service is builtin" do before do expect(::File).to receive(:open).with("/etc/rc.d/#{new_resource.service_name}") provider.rc_conf = "#{provider.builtin_service_enable_variable_name}=NO" provider.rc_conf_local = lines.join("\n") end %w{YES Yes yes yEs YeS}.each do |setting| context "when the enable variable is set to #{setting}" do let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}="#{setting}"} ] } it "sets enabled to true" do provider.determine_enabled_status! expect(current_resource.enabled).to be true end end end %w{No NO no nO None NONE none nOnE}.each do |setting| context "when the enable variable is set to #{setting}" do let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}="#{setting}"} ] } it "sets enabled to false" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end end context "when the enable variable is garbage" do let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}_enable="alskdjflasdkjflakdfj"} ] } it "sets enabled to false" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when the enable variable partial matches (left) some other service and we are disabled" do let(:lines) do [ %Q{thing_#{provider.builtin_service_enable_variable_name}="YES"}, %Q{#{provider.builtin_service_enable_variable_name}="NO"}, ] end it "sets enabled based on the exact match (false)" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when the enable variable partial matches (right) some other service and we are disabled" do let(:lines) do [ %Q{#{provider.builtin_service_enable_variable_name}_thing="YES"}, %Q{#{provider.builtin_service_enable_variable_name}}, ] end it "sets enabled based on the exact match (false)" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when the enable variable partial matches (left) some other disabled service and we are enabled" do let(:lines) do [ %Q{thing_#{provider.builtin_service_enable_variable_name}="NO"}, %Q{#{provider.builtin_service_enable_variable_name}="YES"}, ] end it "sets enabled based on the exact match (true)" do provider.determine_enabled_status! expect(current_resource.enabled).to be true end end context "when the enable variable partial matches (right) some other disabled service and we are enabled" do let(:lines) do [ %Q{#{provider.builtin_service_enable_variable_name}_thing="NO"}, %Q{#{provider.builtin_service_enable_variable_name}="YES"}, ] end it "sets enabled based on the exact match (true)" do provider.determine_enabled_status! expect(current_resource.enabled).to be true end end context "when the enable variable only partial matches (left) some other enabled service" do let(:lines) { [ %Q{thing_#{provider.builtin_service_enable_variable_name}_enable="YES"} ] } it "sets enabled to false" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when the enable variable only partial matches (right) some other enabled service" do let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}_thing_enable="YES"} ] } it "sets enabled to false" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end context "when nothing matches" do let(:lines) { [] } it "sets enabled to true" do provider.determine_enabled_status! expect(current_resource.enabled).to be false end end end end describe Chef::Provider::Service::Openbsd, "load_current_resource" do before(:each) do stub_etc_rcd_script expect(provider).to receive(:determine_current_status!) current_resource.running(false) allow(provider).to receive(:service_enable_variable_name).and_return "#{new_resource.service_name}_enable" expect(::File).to receive(:open).with("/etc/rc.d/#{new_resource.service_name}") end it "should create a current resource with the name of the new resource" do expect(Chef::Resource::Service).to receive(:new).and_return(current_resource) provider.load_current_resource end it "should set the current resources service name to the new resources service name" do provider.load_current_resource expect(current_resource.service_name).to eq(new_resource.service_name) end it "should return the current resource" do expect(provider.load_current_resource).to eql(current_resource) end end context "when testing actions" do before(:each) do stub_etc_rcd_script expect(provider).to receive(:determine_current_status!) current_resource.running(false) expect(provider).to receive(:determine_enabled_status!) current_resource.enabled(false) provider.load_current_resource end describe Chef::Provider::Service::Openbsd, "start_service" do it "should call the start command if one is specified" do new_resource.start_command("/etc/rc.d/chef startyousillysally") expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/chef startyousillysally") provider.start_service() end it "should call '/usr/local/etc/rc.d/service_name start' if no start command is specified" do expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} start") provider.start_service() end end describe Chef::Provider::Service::Openbsd, "stop_service" do it "should call the stop command if one is specified" do new_resource.stop_command("/etc/init.d/chef itoldyoutostop") expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef itoldyoutostop") provider.stop_service() end it "should call '/usr/local/etc/rc.d/service_name stop' if no stop command is specified" do expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} stop") provider.stop_service() end end describe Chef::Provider::Service::Openbsd, "restart_service" do context "when the new_resource supports restart" do let(:supports) { { restart: true } } it "should call 'restart' on the service_name if the resource supports it" do expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} restart") provider.restart_service() end end it "should call the restart_command if one has been specified" do new_resource.restart_command("/etc/init.d/chef restartinafire") expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef restartinafire") provider.restart_service() end it "otherwise it should call stop and start" do expect(provider).to receive(:stop_service) expect(provider).to receive(:start_service) provider.restart_service() end end end describe Chef::Provider::Service::Openbsd, "define_resource_requirements" do before do provider.current_resource = current_resource end context "when the init script is not found" do before do provider.init_command = nil allow(provider).to receive(:builtin_service_enable_variable_name).and_return("#{new_resource.service_name}_enable") end %w{start reload restart enable}.each do |action| it "should raise an exception when the action is #{action}" do provider.define_resource_requirements provider.action = action expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end %w{stop disable}.each do |action| it "should not raise an error when the action is #{action}" do provider.define_resource_requirements provider.action = action expect { provider.process_resource_requirements }.not_to raise_error end end end context "when the init script is found, but the service_enable_variable_name is nil" do before do allow(provider).to receive(:builtin_service_enable_variable_name).and_return(nil) end %w{start reload restart enable}.each do |action| it "should raise an exception when the action is #{action}" do provider.action = action provider.define_resource_requirements expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end %w{stop disable}.each do |action| it "should not raise an error when the action is #{action}" do provider.action = action provider.define_resource_requirements expect { provider.process_resource_requirements }.not_to raise_error end end end end describe Chef::Provider::Service::Openbsd, "enable_service" do before do provider.current_resource = current_resource allow(FileUtils).to receive(:touch).with("/etc/rc.conf.local") end context "is builtin and disabled by default" do before do provider.rc_conf = "#{provider.builtin_service_enable_variable_name}=NO" end context "is enabled" do before do provider.rc_conf_local = "#{provider.builtin_service_enable_variable_name}=\"\"" end it "should not change rc.conf.local since it is already enabled" do expect(::File).not_to receive(:write) provider.enable_service end end context "is disabled" do before do provider.rc_conf_local = "" end it "should enable the service by adding a line to rc.conf.local" do expect(::File).to receive(:write).with("/etc/rc.conf.local", include("#{provider.builtin_service_enable_variable_name}=\"\"")) expect(provider.is_enabled?).to be false provider.enable_service expect(provider.is_enabled?).to be true end end end context "is builtin and enabled by default" do before do provider.rc_conf = "#{provider.builtin_service_enable_variable_name}=\"\"" end context "is enabled" do before do provider.rc_conf_local = "" end it "should not change rc.conf.local since it is already enabled" do expect(::File).not_to receive(:write) provider.enable_service end end context "is disabled" do before do provider.rc_conf_local = "#{provider.builtin_service_enable_variable_name}=NO" end it "should enable the service by removing a line from rc.conf.local" do expect(::File).to receive(:write).with("/etc/rc.conf.local", /^(?!#{provider.builtin_service_enable_variable_name})$/) expect(provider.is_enabled?).to be false provider.enable_service expect(provider.is_enabled?).to be true end end end context "is not builtin" do before do provider.rc_conf = "" end context "is enabled" do before do provider.rc_conf_local = "pkg_scripts=\"#{new_resource.service_name}\"\n" end it "should not change rc.conf.local since it is already enabled" do expect(::File).not_to receive(:write) provider.enable_service end end context "is disabled" do before do provider.rc_conf_local = "" end it "should enable the service by adding it to the pkg_scripts list" do expect(::File).to receive(:write).with("/etc/rc.conf.local", "\npkg_scripts=\"#{new_resource.service_name}\"\n") expect(provider.is_enabled?).to be false provider.enable_service expect(provider.is_enabled?).to be true end end end end describe Chef::Provider::Service::Openbsd, "disable_service" do before do provider.current_resource = current_resource allow(FileUtils).to receive(:touch).with("/etc/rc.conf.local") end context "is builtin and disabled by default" do before do provider.rc_conf = "#{provider.builtin_service_enable_variable_name}=NO" end context "is enabled" do before do provider.rc_conf_local = "#{provider.builtin_service_enable_variable_name}=\"\"" end it "should disable the service by removing its line from rc.conf.local" do expect(::File).to receive(:write).with("/etc/rc.conf.local", /^(?!#{provider.builtin_service_enable_variable_name})$/) expect(provider.is_enabled?).to be true provider.disable_service expect(provider.is_enabled?).to be false end end context "is disabled" do before do provider.rc_conf_local = "" end it "should not change rc.conf.local since it is already disabled" do expect(::File).not_to receive(:write) provider.disable_service end end end context "is builtin and enabled by default" do before do provider.rc_conf = "#{provider.builtin_service_enable_variable_name}=\"\"" end context "is enabled" do before do provider.rc_conf_local = "" end it "should disable the service by adding a line to rc.conf.local" do expect(::File).to receive(:write).with("/etc/rc.conf.local", include("#{provider.builtin_service_enable_variable_name}=\"NO\"")) expect(provider.is_enabled?).to be true provider.disable_service expect(provider.is_enabled?).to be false end end context "is disabled" do before do provider.rc_conf_local = "#{provider.builtin_service_enable_variable_name}=NO" end it "should not change rc.conf.local since it is already disabled" do expect(::File).not_to receive(:write) provider.disable_service end end end context "is not builtin" do before do provider.rc_conf = "" end context "is enabled" do before do provider.rc_conf_local = "pkg_scripts=\"#{new_resource.service_name}\"\n" end it "should disable the service by removing it from the pkg_scripts list" do expect(::File).to receive(:write).with("/etc/rc.conf.local", /^(?!#{new_resource.service_name})$/) expect(provider.is_enabled?).to be true provider.disable_service expect(provider.is_enabled?).to be false end end context "is disabled" do before do provider.rc_conf_local = "" end it "should not change rc.conf.local since it is already disabled" do expect(::File).not_to receive(:write) provider.disable_service end end end end end chef-12.14.60/spec/unit/provider/service/redhat_spec.rb000066400000000000000000000307631276456504500227120ustar00rootroot00000000000000# # Author:: AJ Christensen () # Copyright:: Copyright 2008-2016, HJK Solutions, LLC # 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper")) require "ostruct" shared_examples_for "define_resource_requirements_common" do it "should raise an error if /sbin/chkconfig does not exist" do allow(File).to receive(:exists?).with("/sbin/chkconfig").and_return(false) allow(@provider).to receive(:shell_out).with("/sbin/service chef status").and_raise(Errno::ENOENT) allow(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_raise(Errno::ENOENT) @provider.load_current_resource @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end it "should not raise an error if the service exists but is not added to any runlevels" do status = double("Status", :exitstatus => 0, :stdout => "" , :stderr => "") expect(@provider).to receive(:shell_out).with("/sbin/service chef status").and_return(status) chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "", :stderr => "service chef supports chkconfig, but is not referenced in any runlevel (run 'chkconfig --add chef')") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_return(chkconfig) @provider.load_current_resource @provider.define_resource_requirements expect { @provider.process_resource_requirements }.not_to raise_error end end describe "Chef::Provider::Service::Redhat" do before(:each) do @node = Chef::Node.new @node.automatic_attrs[:command] = { :ps => "foo" } @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @current_resource = Chef::Resource::Service.new("chef") @provider = Chef::Provider::Service::Redhat.new(@new_resource, @run_context) @provider.action = :start allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) allow(File).to receive(:exists?).with("/sbin/chkconfig").and_return(true) end describe "while not in why run mode" do before(:each) do Chef::Config[:why_run] = false end describe "load current resource" do before do status = double("Status", :exitstatus => 0, :stdout => "" , :stderr => "") allow(@provider).to receive(:shell_out).with("/sbin/service chef status").and_return(status) end it "sets supports[:status] to true by default" do chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:off 2:off 3:off 4:off 5:on 6:off", :stderr => "") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_return(chkconfig) expect(@provider.service_missing).to be false @provider.load_current_resource expect(@provider.supports[:status]).to be true end it "lets the user override supports[:status] in the new_resource" do @new_resource.supports( { status: false } ) @new_resource.pattern "myservice" chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:off 2:off 3:off 4:off 5:on 6:off", :stderr => "") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_return(chkconfig) foo_out = double("ps_command", :exitstatus => 0, :stdout => "a line that matches myservice", :stderr => "") expect(@provider).to receive(:shell_out!).with("foo").and_return(foo_out) expect(@provider.service_missing).to be false expect(@provider).not_to receive(:shell_out).with("/sbin/service chef status") @provider.load_current_resource expect(@provider.supports[:status]).to be false end it "sets the current enabled status to true if the service is enabled for any run level" do chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:off 2:off 3:off 4:off 5:on 6:off", :stderr => "") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_return(chkconfig) expect(@provider.service_missing).to be false @provider.load_current_resource expect(@current_resource.enabled).to be true end it "sets the current enabled status to false if the regex does not match" do chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:off 2:off 3:off 4:off 5:off 6:off", :stderr => "") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_return(chkconfig) expect(@provider.service_missing).to be false expect(@provider.load_current_resource).to eql(@current_resource) expect(@current_resource.enabled).to be false end it "sets the current enabled status to true if the service is enabled at specified run levels" do @new_resource.run_levels([1, 2]) chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:on 2:on 3:off 4:off 5:off 6:off", :stderr => "") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_return(chkconfig) expect(@provider.service_missing).to be false @provider.load_current_resource expect(@current_resource.enabled).to be true expect(@provider.current_run_levels).to eql([1, 2]) end it "sets the current enabled status to false if the service is enabled at a run level it should not" do @new_resource.run_levels([1, 2]) chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:on 2:on 3:on 4:off 5:off 6:off", :stderr => "") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_return(chkconfig) expect(@provider.service_missing).to be false @provider.load_current_resource expect(@current_resource.enabled).to be false expect(@provider.current_run_levels).to eql([1, 2, 3]) end it "sets the current enabled status to false if the service is not enabled at specified run levels" do @new_resource.run_levels([ 2 ]) chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:on 2:off 3:off 4:off 5:off 6:off", :stderr => "") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_return(chkconfig) expect(@provider.service_missing).to be false @provider.load_current_resource expect(@current_resource.enabled).to be false expect(@provider.current_run_levels).to eql([1]) end end describe "define resource requirements" do it_should_behave_like "define_resource_requirements_common" context "when the service does not exist" do before do status = double("Status", :exitstatus => 1, :stdout => "", :stderr => "chef: unrecognized service") expect(@provider).to receive(:shell_out).with("/sbin/service chef status").and_return(status) chkconfig = double("Chkconfig", :existatus => 1, :stdout => "", :stderr => "error reading information on service chef: No such file or directory") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_return(chkconfig) @provider.load_current_resource @provider.define_resource_requirements end %w{start reload restart enable}.each do |action| it "should raise an error when the action is #{action}" do @provider.action = action expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end %w{start reload restart}.each do |action| it "should not raise an error when the action is #{action} and init_command is set" do @new_resource.init_command("/etc/init.d/chef") @provider.action = action expect { @provider.process_resource_requirements }.not_to raise_error end it "should not raise an error when the action is #{action} and #{action}_command is set" do @new_resource.send("#{action}_command", "/etc/init.d/chef #{action}") @provider.action = action expect { @provider.process_resource_requirements }.not_to raise_error end end %w{stop disable}.each do |action| it "should not raise an error when the action is #{action}" do @provider.action = action expect { @provider.process_resource_requirements }.not_to raise_error end end end end end describe "while in why run mode" do before(:each) do Chef::Config[:why_run] = true end after do Chef::Config[:why_run] = false end describe "define resource requirements" do it_should_behave_like "define_resource_requirements_common" it "should not raise an error if the service does not exist" do status = double("Status", :exitstatus => 1, :stdout => "", :stderr => "chef: unrecognized service") expect(@provider).to receive(:shell_out).with("/sbin/service chef status").and_return(status) chkconfig = double("Chkconfig", :existatus => 1, :stdout => "", :stderr => "error reading information on service chef: No such file or directory") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0, 1]).and_return(chkconfig) @provider.load_current_resource @provider.define_resource_requirements expect { @provider.process_resource_requirements }.not_to raise_error end end end describe "enable_service" do it "should call chkconfig to add 'service_name'" do expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig #{@new_resource.service_name} on") @provider.enable_service end it "should call chkconfig to add 'service_name' at specified run_levels" do allow(@provider).to receive(:run_levels).and_return([1, 2]) expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} on") @provider.enable_service end it "should call chkconfig to add 'service_name' at specified run_levels when run_levels do not match" do allow(@provider).to receive(:run_levels).and_return([1, 2]) allow(@provider).to receive(:current_run_levels).and_return([1, 3]) expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} on") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 3 #{@new_resource.service_name} off") @provider.enable_service end it "should call chkconfig to add 'service_name' at specified run_levels if there is an extra run_level" do allow(@provider).to receive(:run_levels).and_return([1, 2]) allow(@provider).to receive(:current_run_levels).and_return([1, 2, 3]) expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} on") expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 3 #{@new_resource.service_name} off") @provider.enable_service end end describe "disable_service" do it "should call chkconfig to del 'service_name'" do expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig #{@new_resource.service_name} off") @provider.disable_service end it "should call chkconfig to del 'service_name' at specified run_levels" do allow(@provider).to receive(:run_levels).and_return([1, 2]) expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} off") @provider.disable_service end end end chef-12.14.60/spec/unit/provider/service/simple_service_spec.rb000066400000000000000000000160251276456504500244470ustar00rootroot00000000000000# # Author:: Mathieu Sauve-Frankel # Copyright:: Copyright 2009-2016, Mathieu Sauve Frankel # 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::Service::Simple, "load_current_resource" do before(:each) do @node = Chef::Node.new @node.automatic_attrs[:command] = { :ps => "ps -ef" } @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @current_resource = Chef::Resource::Service.new("chef") @provider = Chef::Provider::Service::Simple.new(@new_resource, @run_context) allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @stdout = StringIO.new(<<-NOMOCKINGSTRINGSPLZ) aj 7842 5057 0 21:26 pts/2 00:00:06 vi init.rb aj 7903 5016 0 21:26 pts/5 00:00:00 /bin/bash aj 8119 6041 0 21:34 pts/3 00:00:03 vi simple_service_spec.rb NOMOCKINGSTRINGSPLZ @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out!).and_return(@status) end it "should create a current resource with the name of the new resource" do expect(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should set the current resources service name to the new resources service name" do expect(@current_resource).to receive(:service_name).with(@new_resource.service_name) @provider.load_current_resource end it "should raise error if the node has a nil ps attribute and no other means to get status" do @node.automatic_attrs[:command] = { :ps => nil } @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end it "should raise error if the node has an empty ps attribute and no other means to get status" do @node.automatic_attrs[:command] = { :ps => "" } @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end describe "when we have a 'ps' attribute" do it "should shell_out! the node's ps command" do expect(@provider).to receive(:shell_out!).with(@node[:command][:ps]).and_return(@status) @provider.load_current_resource end it "should read stdout of the ps command" do allow(@provider).to receive(:shell_out!).and_return(@status) expect(@stdout).to receive(:each_line).and_return(true) @provider.load_current_resource end it "should set running to true if the regex matches the output" do @stdout = StringIO.new(<<-NOMOCKINGSTRINGSPLZ) aj 7842 5057 0 21:26 pts/2 00:00:06 chef aj 7842 5057 0 21:26 pts/2 00:00:06 poos NOMOCKINGSTRINGSPLZ @status = double("Status", :exitstatus => 0, :stdout => @stdout) allow(@provider).to receive(:shell_out!).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the regex doesn't match" do allow(@provider).to receive(:shell_out!).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_falsey end it "should raise an exception if ps fails" do allow(@provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) @provider.action = :start @provider.load_current_resource @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end it "should return the current resource" do expect(@provider.load_current_resource).to eql(@current_resource) end describe "when starting the service" do it "should call the start command if one is specified" do allow(@new_resource).to receive(:start_command).and_return("#{@new_resource.start_command}") expect(@provider).to receive(:shell_out_with_systems_locale!).with("#{@new_resource.start_command}") @provider.start_service() end it "should raise an exception if no start command is specified" do @provider.define_resource_requirements @provider.action = :start expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end describe "when stopping a service" do it "should call the stop command if one is specified" do @new_resource.stop_command("/etc/init.d/themadness stop") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/themadness stop") @provider.stop_service() end it "should raise an exception if no stop command is specified" do @provider.define_resource_requirements @provider.action = :stop expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end describe Chef::Provider::Service::Simple, "restart_service" do it "should call the restart command if one has been specified" do @new_resource.restart_command("/etc/init.d/foo restart") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/foo restart") @provider.restart_service() end it "should raise an exception if the resource doesn't support restart, no restart command is provided, and no stop command is provided" do @provider.define_resource_requirements @provider.action = :restart expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do expect(@provider).to receive(:stop_service) expect(@provider).to receive(:sleep).with(1) expect(@provider).to receive(:start_service) @provider.restart_service() end end describe Chef::Provider::Service::Simple, "reload_service" do it "should raise an exception if reload is requested but no command is specified" do @provider.define_resource_requirements @provider.action = :reload expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should should run the user specified reload command if one is specified" do @new_resource.reload_command("kill -9 1") expect(@provider).to receive(:shell_out_with_systems_locale!).with("kill -9 1") @provider.reload_service() end end end chef-12.14.60/spec/unit/provider/service/solaris_smf_service_spec.rb000066400000000000000000000262221276456504500254770ustar00rootroot00000000000000# # Author:: Toomas Pelberg () # Copyright:: Copyright 2010-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::Service::Solaris do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @current_resource = Chef::Resource::Service.new("chef") @provider = Chef::Provider::Service::Solaris.new(@new_resource, @run_context) allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) # enabled / started service (svcs -l chef) enabled_svc_stdout = [ "fmri svc:/application/chef:default", "name chef service", "enabled true", "state online", "next_state none", "state_time April 2, 2015 04:25:19 PM EDT", "logfile /var/svc/log/application-chef:default.log", "restarter svc:/system/svc/restarter:default", "contract_id 1115271", "dependency require_all/error svc:/milestone/multi-user:default (online)", ].join("\n") # disabled / stopped service (svcs -l chef) disabled_svc_stdout = [ "fmri svc:/application/chef:default", "name chef service", "enabled false", "state disabled", "next_state none", "state_time April 2, 2015 04:25:19 PM EDT", "logfile /var/svc/log/application-chef:default.log", "restarter svc:/system/svc/restarter:default", "contract_id 1115271", "dependency require_all/error svc:/milestone/multi-user:default (online)", ].join("\n") # disabled / stopped service (svcs -l chef) maintenance_svc_stdout = [ "fmri svc:/application/chef:default", "name chef service", "enabled true", "state maintenance", "next_state none", "state_time April 2, 2015 04:25:19 PM EDT", "logfile /var/svc/log/application-chef:default.log", "restarter svc:/system/svc/restarter:default", "contract_id 1115271", "dependency require_all/error svc:/milestone/multi-user:default (online)", ].join("\n") # shell_out! return value for a service that is running @enabled_svc_status = double("Status", :exitstatus => 0, :stdout => enabled_svc_stdout, :stdin => "", :stderr => "") # shell_out! return value for a service that is disabled @disabled_svc_status = double("Status", :exitstatus => 0, :stdout => disabled_svc_stdout, :stdin => "", :stderr => "") # shell_out! return value for a service that is in maintenance mode @maintenance_svc_status = double("Status", :exitstatus => 0, :stdout => maintenance_svc_stdout, :stdin => "", :stderr => "") # shell_out! return value for a service that does not exist @no_svc_status = double("Status", :exitstatus => 1, :stdout => "", :stdin => "", :stderr => "svcs: Pattern 'chef' doesn't match any instances\n") # shell_out! return value for a successful execution @success = double("clear", :exitstatus => 0, :stdout => "", :stdin => "", :stderr => "") end it "should raise an error if /bin/svcs and /usr/sbin/svcadm are not executable" do allow(File).to receive(:executable?).with("/bin/svcs").and_return(false) allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(false) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service) end it "should raise an error if /bin/svcs is not executable" do allow(File).to receive(:executable?).with("/bin/svcs").and_return(false) allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(true) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service) end it "should raise an error if /usr/sbin/svcadm is not executable" do allow(File).to receive(:executable?).with("/bin/svcs").and_return(true) allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(false) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Service) end describe "on a host with /bin/svcs and /usr/sbin/svcadm" do before do allow(File).to receive(:executable?).with("/bin/svcs").and_return(true) allow(File).to receive(:executable?).with("/usr/sbin/svcadm").and_return(true) end describe "when discovering the current service state" do it "should create a current resource with the name of the new resource" do expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@enabled_svc_status) expect(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should return the current resource" do expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@enabled_svc_status) expect(@provider.load_current_resource).to eql(@current_resource) end it "should call '/bin/svcs -l service_name'" do expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@enabled_svc_status) @provider.load_current_resource end it "should mark service as not running" do expect(@provider).to receive(:shell_out!).and_return(@disabled_svc_status) expect(@current_resource).to receive(:running).with(false) @provider.load_current_resource end it "should mark service as running" do expect(@provider).to receive(:shell_out!).and_return(@enabled_svc_status) expect(@current_resource).to receive(:running).with(true) @provider.load_current_resource end it "should not mark service as maintenance" do expect(@provider).to receive(:shell_out!).and_return(@enabled_svc_status) @provider.load_current_resource expect(@provider.maintenance).to be_falsey end it "should mark service as maintenance" do expect(@provider).to receive(:shell_out!).and_return(@maintenance_svc_status) @provider.load_current_resource expect(@provider.maintenance).to be_truthy end end describe "when enabling the service" do before(:each) do @provider.current_resource = @current_resource end it "should call svcadm enable -s chef" do expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@enabled_svc_status) expect(@provider).not_to receive(:shell_out!).with("/usr/sbin/svcadm", "clear", @current_resource.service_name) expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "enable", "-s", @current_resource.service_name).and_return(@success) @provider.load_current_resource expect(@provider.enable_service).to be_truthy expect(@current_resource.enabled).to be_truthy end it "should call svcadm enable -s chef for start_service" do expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@enabled_svc_status) expect(@provider).not_to receive(:shell_out!).with("/usr/sbin/svcadm", "clear", @current_resource.service_name) expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "enable", "-s", @current_resource.service_name).and_return(@success) @provider.load_current_resource expect(@provider.start_service).to be_truthy expect(@current_resource.enabled).to be_truthy end it "should call svcadm clear chef for start_service when state maintenance" do # we are in maint mode expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@maintenance_svc_status) expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "clear", @current_resource.service_name).and_return(@success) expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "enable", "-s", @current_resource.service_name).and_return(@success) # load the resource, then enable it @provider.load_current_resource expect(@provider.enable_service).to be_truthy # now we are enabled expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@enabled_svc_status) @provider.load_current_resource expect(@current_resource.enabled).to be_truthy end end describe "when disabling the service" do before(:each) do @provider.current_resource = @current_resource end it "should call svcadm disable -s chef" do expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@disabled_svc_status) expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "disable", "-s", "chef").and_return(@success) @provider.load_current_resource expect(@provider.disable_service).to be_truthy expect(@current_resource.enabled).to be_falsey end it "should call svcadm disable -s chef for stop_service" do expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@disabled_svc_status) expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "disable", "-s", "chef").and_return(@success) @provider.load_current_resource expect(@provider.stop_service).to be_truthy expect(@current_resource.enabled).to be_falsey end end describe "when reloading the service" do before(:each) do @provider.current_resource = @current_resource allow(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@enabled_svc_status) end it "should call svcadm refresh chef" do expect(@provider).to receive(:shell_out!).with("/usr/sbin/svcadm", "refresh", "chef") @provider.reload_service end end describe "when the service doesn't exist" do before(:each) do @provider.current_resource = @current_resource expect(@provider).to receive(:shell_out!).with("/bin/svcs", "-l", "chef", { :returns => [0, 1] }).and_return(@no_svc_status) end it "should be marked not running" do @provider.service_status expect(@current_resource.running).to be_falsey end it "should be marked not enabled" do @provider.service_status expect(@current_resource.enabled).to be_falsey end end end end chef-12.14.60/spec/unit/provider/service/systemd_service_spec.rb000066400000000000000000000405061276456504500246470ustar00rootroot00000000000000# # Author:: Stephen Haynes () # Author:: Davide Cavalca () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Provider::Service::Systemd do let(:node) do node = Chef::Node.new node.default["etc"] = Hash.new node.default["etc"]["passwd"] = { "joe" => { "uid" => 10000, }, } node end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:service_name) { "rsyslog.service" } let(:new_resource) { Chef::Resource::Service.new(service_name) } let(:provider) { Chef::Provider::Service::Systemd.new(new_resource, run_context) } let(:shell_out_success) do double("shell_out_with_systems_locale", :exitstatus => 0, :error? => false) end let(:shell_out_failure) do double("shell_out_with_systems_locale", :exitstatus => 1, :error? => true) end let(:current_resource) { Chef::Resource::Service.new(service_name) } before(:each) do allow(Chef::Resource::Service).to receive(:new).with(service_name).and_return(current_resource) end describe "load_current_resource" do before(:each) do allow(provider).to receive(:is_active?).and_return(false) allow(provider).to receive(:is_enabled?).and_return(false) allow(provider).to receive(:is_masked?).and_return(false) end it "should create a current resource with the name of the new resource" do expect(Chef::Resource::Service).to receive(:new).with(new_resource.name).and_return(current_resource) provider.load_current_resource end it "should set the current resources service name to the new resources service name" do provider.load_current_resource expect(current_resource.service_name).to eql(service_name) end it "should check if the service is running" do expect(provider).to receive(:is_active?) provider.load_current_resource end it "should set running to true if the service is running" do allow(provider).to receive(:is_active?).and_return(true) provider.load_current_resource expect(current_resource.running).to be true end it "should set running to false if the service is not running" do allow(provider).to receive(:is_active?).and_return(false) provider.load_current_resource expect(current_resource.running).to be false end describe "when a status command has been specified" do before do allow(new_resource).to receive(:status_command).and_return("/bin/chefhasmonkeypants status") end it "should run the services status command if one has been specified" do allow(provider).to receive(:shell_out).and_return(shell_out_success) provider.load_current_resource expect(current_resource.running).to be true end it "should run the services status command if one has been specified and properly set status check state" do allow(provider).to receive(:shell_out).with("/bin/chefhasmonkeypants status").and_return(shell_out_success) provider.load_current_resource expect(provider.status_check_success).to be true end it "should set running to false if a status command fails" do allow(provider).to receive(:shell_out).and_return(shell_out_failure) provider.load_current_resource expect(current_resource.running).to be false end it "should update state to indicate status check failed when a status command fails" do allow(provider).to receive(:shell_out).and_return(shell_out_failure) provider.load_current_resource expect(provider.status_check_success).to be false end end it "should check if the service is enabled" do expect(provider).to receive(:is_enabled?) provider.load_current_resource end it "should set enabled to true if the service is enabled" do allow(provider).to receive(:is_enabled?).and_return(true) provider.load_current_resource expect(current_resource.enabled).to be true end it "should set enabled to false if the service is not enabled" do allow(provider).to receive(:is_enabled?).and_return(false) provider.load_current_resource expect(current_resource.enabled).to be false end it "should check if the service is masked" do expect(provider).to receive(:is_masked?) provider.load_current_resource end it "should set masked to true if the service is masked" do allow(provider).to receive(:is_masked?).and_return(true) provider.load_current_resource expect(current_resource.masked).to be true end it "should set masked to false if the service is not masked" do allow(provider).to receive(:is_masked?).and_return(false) provider.load_current_resource expect(current_resource.masked).to be false end it "should return the current resource" do expect(provider.load_current_resource).to eql(current_resource) end end def setup_current_resource provider.current_resource = current_resource current_resource.service_name(service_name) end %w{/usr/bin/systemctl /bin/systemctl}.each do |systemctl_path| describe "when systemctl path is #{systemctl_path}" do before(:each) do setup_current_resource allow(provider).to receive(:which).with("systemctl").and_return(systemctl_path) end describe "start and stop service" do it "should call the start command if one is specified" do allow(new_resource).to receive(:start_command).and_return("/sbin/rsyslog startyousillysally") expect(provider).to receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog startyousillysally") provider.start_service end context "when a user is not specified" do it "should call '#{systemctl_path} --system start service_name' if no start command is specified" do expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system start #{service_name}", {}).and_return(shell_out_success) provider.start_service end it "should not call '#{systemctl_path} --system start service_name' if it is already running" do current_resource.running(true) expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system start #{service_name}", {}) provider.start_service end end context "when a user is specified" do it "should call '#{systemctl_path} --user start service_name' if no start command is specified" do current_resource.user("joe") expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --user start #{service_name}", { :environment => { "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/10000/bus" }, :user => "joe" }).and_return(shell_out_success) provider.start_service end it "should not call '#{systemctl_path} --user start service_name' if it is already running" do current_resource.running(true) current_resource.user("joe") expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --user start #{service_name}", { :environment => { "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/10000/bus" }, :user => "joe" }) provider.start_service end end it "should call the restart command if one is specified" do current_resource.running(true) allow(new_resource).to receive(:restart_command).and_return("/sbin/rsyslog restartyousillysally") expect(provider).to receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog restartyousillysally") provider.restart_service end it "should call '#{systemctl_path} --system restart service_name' if no restart command is specified" do current_resource.running(true) expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system restart #{service_name}", {}).and_return(shell_out_success) provider.restart_service end describe "reload service" do context "when a reload command is specified" do it "should call the reload command" do current_resource.running(true) allow(new_resource).to receive(:reload_command).and_return("/sbin/rsyslog reloadyousillysally") expect(provider).to receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog reloadyousillysally") provider.reload_service end end context "when a reload command is not specified" do it "should call '#{systemctl_path} --system reload service_name' if the service is running" do current_resource.running(true) expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system reload #{service_name}", {}).and_return(shell_out_success) provider.reload_service end it "should start the service if the service is not running" do current_resource.running(false) expect(provider).to receive(:start_service).and_return(true) provider.reload_service end end end it "should call the stop command if one is specified" do current_resource.running(true) allow(new_resource).to receive(:stop_command).and_return("/sbin/rsyslog stopyousillysally") expect(provider).to receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog stopyousillysally") provider.stop_service end it "should call '#{systemctl_path} --system stop service_name' if no stop command is specified" do current_resource.running(true) expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system stop #{service_name}", {}).and_return(shell_out_success) provider.stop_service end it "should not call '#{systemctl_path} --system stop service_name' if it is already stopped" do current_resource.running(false) expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system stop #{service_name}", {}) provider.stop_service end end describe "enable and disable service" do before(:each) do provider.current_resource = current_resource current_resource.service_name(service_name) allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end it "should call '#{systemctl_path} --system enable service_name' to enable the service" do expect(provider).to receive(:shell_out!).with("#{systemctl_path} --system enable #{service_name}", {}).and_return(shell_out_success) provider.enable_service end it "should call '#{systemctl_path} --system disable service_name' to disable the service" do expect(provider).to receive(:shell_out!).with("#{systemctl_path} --system disable #{service_name}", {}).and_return(shell_out_success) provider.disable_service end end describe "mask and unmask service" do before(:each) do provider.current_resource = current_resource current_resource.service_name(service_name) allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end it "should call '#{systemctl_path} --system mask service_name' to mask the service" do expect(provider).to receive(:shell_out!).with("#{systemctl_path} --system mask #{service_name}", {}).and_return(shell_out_success) provider.mask_service end it "should call '#{systemctl_path} --system unmask service_name' to unmask the service" do expect(provider).to receive(:shell_out!).with("#{systemctl_path} --system unmask #{service_name}", {}).and_return(shell_out_success) provider.unmask_service end end describe "is_active?" do before(:each) do provider.current_resource = current_resource current_resource.service_name(service_name) allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end it "should return true if '#{systemctl_path} --system is-active service_name' returns 0" do expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-active #{service_name} --quiet", {}).and_return(shell_out_success) expect(provider.is_active?).to be true end it "should return false if '#{systemctl_path} --system is-active service_name' returns anything except 0" do expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-active #{service_name} --quiet", {}).and_return(shell_out_failure) expect(provider.is_active?).to be false end end describe "is_enabled?" do before(:each) do provider.current_resource = current_resource current_resource.service_name(service_name) allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end it "should return true if '#{systemctl_path} --system is-enabled service_name' returns 0" do expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name} --quiet", {}).and_return(shell_out_success) expect(provider.is_enabled?).to be true end it "should return false if '#{systemctl_path} --system is-enabled service_name' returns anything except 0" do expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name} --quiet", {}).and_return(shell_out_failure) expect(provider.is_enabled?).to be false end end describe "is_masked?" do before(:each) do provider.current_resource = current_resource current_resource.service_name(service_name) allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end it "should return true if '#{systemctl_path} --system is-enabled service_name' returns 'masked' and returns anything except 0" do expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name}", {}).and_return(double(:stdout => "masked", :exitstatus => shell_out_failure)) expect(provider.is_masked?).to be true end it "should return true if '#{systemctl_path} --system is-enabled service_name' outputs 'masked-runtime' and returns anything except 0" do expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name}", {}).and_return(double(:stdout => "masked-runtime", :exitstatus => shell_out_failure)) expect(provider.is_masked?).to be true end it "should return false if '#{systemctl_path} --system is-enabled service_name' returns 0" do expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name}", {}).and_return(double(:stdout => "enabled", :exitstatus => shell_out_success)) expect(provider.is_masked?).to be false end it "should return false if '#{systemctl_path} --system is-enabled service_name' returns anything except 0 and outputs an error'" do expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name}", {}).and_return(double(:stdout => "Failed to get unit file state for #{service_name}: No such file or directory", :exitstatus => shell_out_failure)) expect(provider.is_masked?).to be false end end end end end chef-12.14.60/spec/unit/provider/service/upstart_service_spec.rb000066400000000000000000000376611276456504500246710ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Copyright:: Copyright 2010-2016, Bryan McLellan # 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::Service::Upstart do let(:shell_out_success) do double("shell_out_with_systems_locale", :exitstatus => 0, :error? => false) end before(:each) do @node = Chef::Node.new @node.name("upstarter") @node.automatic_attrs[:platform] = "ubuntu" @node.automatic_attrs[:platform_version] = "9.10" @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("rsyslog") @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context) end describe "when first created" do before do @platform = nil end it "should return /etc/event.d as the upstart job directory when running on Ubuntu 9.04" do @node.automatic_attrs[:platform_version] = "9.04" #Chef::Platform.stub(:find_platform_and_version).and_return([ "ubuntu", "9.04" ]) @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context) expect(@provider.instance_variable_get(:@upstart_job_dir)).to eq("/etc/event.d") expect(@provider.instance_variable_get(:@upstart_conf_suffix)).to eq("") end it "should return /etc/init as the upstart job directory when running on Ubuntu 9.10" do @node.automatic_attrs[:platform_version] = "9.10" @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context) expect(@provider.instance_variable_get(:@upstart_job_dir)).to eq("/etc/init") expect(@provider.instance_variable_get(:@upstart_conf_suffix)).to eq(".conf") end it "should return /etc/init as the upstart job directory by default" do @node.automatic_attrs[:platform_version] = "9000" @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context) expect(@provider.instance_variable_get(:@upstart_job_dir)).to eq("/etc/init") expect(@provider.instance_variable_get(:@upstart_conf_suffix)).to eq(".conf") end end describe "load_current_resource" do before(:each) do @node.automatic_attrs[:command] = { :ps => "ps -ax" } @current_resource = Chef::Resource::Service.new("rsyslog") allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @status = double("Status", :exitstatus => 0) allow(@provider).to receive(:popen4).and_return(@status) @stdin = StringIO.new @stdout = StringIO.new @stderr = StringIO.new @pid = double("PID") allow(::File).to receive(:exists?).and_return(true) allow(::File).to receive(:open).and_return(true) end it "should create a current resource with the name of the new resource" do expect(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should set the current resources service name to the new resources service name" do expect(@current_resource).to receive(:service_name).with(@new_resource.service_name) @provider.load_current_resource end it "should not change the service name when parameters are specified" do @new_resource.parameters({ "OSD_ID" => "2" }) @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context) @provider.current_resource = @current_resource expect(@new_resource.service_name).to eq(@current_resource.service_name) end it "should run '/sbin/status rsyslog'" do expect(@provider).to receive(:popen4).with("/sbin/status rsyslog").and_return(@status) @provider.load_current_resource end describe "when the status command uses the new format" do it "should set running to true if the goal state is 'start'" do @stdout = StringIO.new("rsyslog start/running") allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to true if the goal state is 'start' but current state is not 'running'" do @stdout = StringIO.new("rsyslog start/starting") allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the goal state is 'stop'" do @stdout = StringIO.new("rsyslog stop/waiting") allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_falsey end end describe "when the status command uses the new format with an instance" do it "should set running to true if the goal state is 'start'" do @stdout = StringIO.new("rsyslog (test) start/running, process 100") allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to true if the goal state is 'start' but current state is not 'running'" do @stdout = StringIO.new("rsyslog (test) start/starting, process 100") allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the goal state is 'stop'" do @stdout = StringIO.new("rsyslog (test) stop/waiting, process 100") allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_falsey end end describe "when the status command uses the old format" do it "should set running to true if the goal state is 'start'" do @stdout = StringIO.new("rsyslog (start) running, process 32225") allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to true if the goal state is 'start' but current state is not 'running'" do @stdout = StringIO.new("rsyslog (start) starting, process 32225") allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_truthy end it "should set running to false if the goal state is 'stop'" do @stdout = StringIO.new("rsyslog (stop) waiting") allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.load_current_resource expect(@current_resource.running).to be_falsey end end it "should set running to false if it catches a Chef::Exceptions::Exec" do allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_raise(Chef::Exceptions::Exec) expect(@current_resource).to receive(:running).with(false) @provider.load_current_resource end it "should set enabled to true when it finds 'starts on'" do @lines = double("start on filesystem", :gets => "start on filesystem") allow(::File).to receive(:open).and_yield(@lines) expect(@current_resource).to receive(:running).with(false) @provider.load_current_resource end it "should set enabled to false when it finds '#starts on'" do @lines = double("start on filesystem", :gets => "#start on filesystem") allow(::File).to receive(:open).and_yield(@lines) expect(@current_resource).to receive(:running).with(false) @provider.load_current_resource end it "should assume disable when no job configuration file is found" do allow(::File).to receive(:exists?).and_return(false) expect(@current_resource).to receive(:running).with(false) @provider.load_current_resource end it "should track state when the upstart configuration file fails to load" do expect(File).to receive(:exists?).and_return false @provider.load_current_resource expect(@provider.instance_variable_get("@config_file_found")).to eq(false) end describe "when a status command has been specified" do before do allow(@new_resource).to receive(:status_command).and_return("/bin/chefhasmonkeypants status") end it "should run the services status command if one has been specified" do allow(@provider).to receive(:shell_out!).with("/bin/chefhasmonkeypants status").and_return(shell_out_success) expect(@current_resource).to receive(:running).with(true) @provider.load_current_resource end it "should track state when the user-provided status command fails" do allow(@provider).to receive(:shell_out!).and_raise(Errno::ENOENT) @provider.load_current_resource expect(@provider.instance_variable_get("@command_success")).to eq(false) end it "should set running to false if it catches a Chef::Exceptions::Exec when using a status command" do allow(@provider).to receive(:shell_out!).and_raise(Errno::ENOENT) expect(@current_resource).to receive(:running).with(false) @provider.load_current_resource end end it "should track state when we fail to obtain service status via upstart_goal_state" do expect(@provider).to receive(:upstart_goal_state).and_raise Chef::Exceptions::Exec @provider.load_current_resource expect(@provider.instance_variable_get("@command_success")).to eq(false) end it "should return the current resource" do expect(@provider.load_current_resource).to eql(@current_resource) end end describe "enable and disable service" do before(:each) do @current_resource = Chef::Resource::Service.new("rsyslog") allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @provider.current_resource = @current_resource allow(Chef::Util::FileEdit).to receive(:new) end it "should enable the service if it is not enabled" do @file = Object.new allow(Chef::Util::FileEdit).to receive(:new).and_return(@file) allow(@current_resource).to receive(:enabled).and_return(false) expect(@file).to receive(:search_file_replace) expect(@file).to receive(:write_file) @provider.enable_service() end it "should disable the service if it is enabled" do @file = Object.new allow(Chef::Util::FileEdit).to receive(:new).and_return(@file) allow(@current_resource).to receive(:enabled).and_return(true) expect(@file).to receive(:search_file_replace) expect(@file).to receive(:write_file) @provider.disable_service() end end describe "start and stop service" do before(:each) do @current_resource = Chef::Resource::Service.new("rsyslog") allow(Chef::Resource::Service).to receive(:new).and_return(@current_resource) @provider.current_resource = @current_resource end it "should call the start command if one is specified" do allow(@new_resource).to receive(:start_command).and_return("/sbin/rsyslog startyousillysally") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog startyousillysally") @provider.start_service() end it "should call '/sbin/start service_name' if no start command is specified" do expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(shell_out_success) @provider.start_service() end it "should not call '/sbin/start service_name' if it is already running" do allow(@current_resource).to receive(:running).and_return(true) expect(@provider).not_to receive(:shell_out_with_systems_locale!) @provider.start_service() end it "should pass parameters to the start command if they are provided" do @new_resource = Chef::Resource::Service.new("rsyslog") @new_resource.parameters({ "OSD_ID" => "2" }) @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context) @provider.current_resource = @current_resource expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start rsyslog OSD_ID=2").and_return(shell_out_success) @provider.start_service() end it "should call the restart command if one is specified" do allow(@current_resource).to receive(:running).and_return(true) allow(@new_resource).to receive(:restart_command).and_return("/sbin/rsyslog restartyousillysally") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog restartyousillysally") @provider.restart_service() end it "should call '/sbin/restart service_name' if no restart command is specified" do allow(@current_resource).to receive(:running).and_return(true) expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/restart #{@new_resource.service_name}").and_return(shell_out_success) @provider.restart_service() end it "should call '/sbin/start service_name' if restart_service is called for a stopped service" do allow(@current_resource).to receive(:running).and_return(false) expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(shell_out_success) @provider.restart_service() end it "should call the reload command if one is specified" do allow(@current_resource).to receive(:running).and_return(true) allow(@new_resource).to receive(:reload_command).and_return("/sbin/rsyslog reloadyousillysally") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog reloadyousillysally") @provider.reload_service() end it "should call '/sbin/reload service_name' if no reload command is specified" do allow(@current_resource).to receive(:running).and_return(true) expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/reload #{@new_resource.service_name}").and_return(shell_out_success) @provider.reload_service() end it "should call the stop command if one is specified" do allow(@current_resource).to receive(:running).and_return(true) allow(@new_resource).to receive(:stop_command).and_return("/sbin/rsyslog stopyousillysally") expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/rsyslog stopyousillysally") @provider.stop_service() end it "should call '/sbin/stop service_name' if no stop command is specified" do allow(@current_resource).to receive(:running).and_return(true) expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/stop #{@new_resource.service_name}").and_return(shell_out_success) @provider.stop_service() end it "should not call '/sbin/stop service_name' if it is already stopped" do allow(@current_resource).to receive(:running).and_return(false) expect(@provider).not_to receive(:shell_out_with_systems_locale!).with("/sbin/stop #{@new_resource.service_name}") @provider.stop_service() end end end chef-12.14.60/spec/unit/provider/service/windows_spec.rb000066400000000000000000000476061276456504500231410ustar00rootroot00000000000000# # Author:: Nuo Yan # Author:: Seth Chisamore # Copyright:: Copyright 2010-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 "mixlib/shellout" describe Chef::Provider::Service::Windows, "load_current_resource" do include_context "Win32" let(:new_resource) { Chef::Resource::WindowsService.new("chef") } let(:provider) do prvdr = Chef::Provider::Service::Windows.new(new_resource, Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new)) prvdr.current_resource = Chef::Resource::WindowsService.new("current-chef") prvdr end let(:service_right) { Chef::Provider::Service::Windows::SERVICE_RIGHT } before(:all) do Win32::Service = Class.new end before(:each) do Win32::Service::AUTO_START = 0x00000002 Win32::Service::DEMAND_START = 0x00000003 Win32::Service::DISABLED = 0x00000004 allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running")) allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "auto start")) allow(Win32::Service).to receive(:exists?).and_return(true) allow(Win32::Service).to receive(:configure).and_return(Win32::Service) allow(Chef::ReservedNames::Win32::Security).to receive(:get_account_right).and_return([]) end after(:each) do Win32::Service.send(:remove_const, "AUTO_START") if defined?(Win32::Service::AUTO_START) Win32::Service.send(:remove_const, "DEMAND_START") if defined?(Win32::Service::DEMAND_START) Win32::Service.send(:remove_const, "DISABLED") if defined?(Win32::Service::DISABLED) end it "sets the current resources service name to the new resources service name" do provider.load_current_resource expect(provider.current_resource.service_name).to eq("chef") end it "returns the current resource" do expect(provider.load_current_resource).to equal(provider.current_resource) end it "sets the current resources status" do provider.load_current_resource expect(provider.current_resource.running).to be_truthy end it "sets the current resources start type" do provider.load_current_resource expect(provider.current_resource.enabled).to be_truthy end it "does not set the current resources start type if it is neither AUTO START or DISABLED" do allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "manual")) provider.load_current_resource expect(provider.current_resource.enabled).to be_nil end describe Chef::Provider::Service::Windows, "start_service" do before(:each) do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "running")) end it "calls the start command if one is specified" do new_resource.start_command "sc start chef" expect(provider).to receive(:shell_out!).with("#{new_resource.start_command}").and_return("Starting custom service") provider.start_service expect(new_resource.updated_by_last_action?).to be_truthy end it "uses the built-in command if no start command is specified" do expect(Win32::Service).to receive(:start).with(new_resource.service_name) provider.start_service expect(new_resource.updated_by_last_action?).to be_truthy end it "does nothing if the service does not exist" do allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) provider.start_service expect(new_resource.updated_by_last_action?).to be_falsey end it "does nothing if the service is running" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running")) provider.load_current_resource expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) provider.start_service expect(new_resource.updated_by_last_action?).to be_falsey end it "raises an error if the service is paused" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "paused")) provider.load_current_resource expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) expect { provider.start_service }.to raise_error( Chef::Exceptions::Service ) expect(new_resource.updated_by_last_action?).to be_falsey end it "waits and continues if the service is in start_pending" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "start pending"), double("StatusStruct", :current_state => "start pending"), double("StatusStruct", :current_state => "running")) provider.load_current_resource expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) provider.start_service expect(new_resource.updated_by_last_action?).to be_falsey end it "fails if the service is in stop_pending" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stop pending")) provider.load_current_resource expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) expect { provider.start_service }.to raise_error( Chef::Exceptions::Service ) expect(new_resource.updated_by_last_action?).to be_falsey end describe "running as a different account" do let(:old_run_as_user) { new_resource.run_as_user } let(:old_run_as_password) { new_resource.run_as_password } before do new_resource.run_as_user(".\\wallace") new_resource.run_as_password("Wensleydale") end after do new_resource.run_as_user(old_run_as_user) new_resource.run_as_password(old_run_as_password) end it "calls #grant_service_logon if the :run_as_user and :run_as_password attributes are present" do expect(Win32::Service).to receive(:start) expect(provider).to receive(:grant_service_logon).and_return(true) provider.start_service end it "does not grant user SeServiceLogonRight if it already has it" do expect(Win32::Service).to receive(:start) expect(Chef::ReservedNames::Win32::Security).to receive(:get_account_right).with("wallace").and_return([service_right]) expect(Chef::ReservedNames::Win32::Security).not_to receive(:add_account_right).with("wallace", service_right) provider.start_service end end end describe Chef::Provider::Service::Windows, "stop_service" do before(:each) do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running"), double("StatusStruct", :current_state => "stopped")) end it "calls the stop command if one is specified" do new_resource.stop_command "sc stop chef" expect(provider).to receive(:shell_out!).with("#{new_resource.stop_command}").and_return("Stopping custom service") provider.stop_service expect(new_resource.updated_by_last_action?).to be_truthy end it "uses the built-in command if no stop command is specified" do expect(Win32::Service).to receive(:stop).with(new_resource.service_name) provider.stop_service expect(new_resource.updated_by_last_action?).to be_truthy end it "does nothing if the service does not exist" do allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) provider.stop_service expect(new_resource.updated_by_last_action?).to be_falsey end it "does nothing if the service is stopped" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stopped")) provider.load_current_resource expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) provider.stop_service expect(new_resource.updated_by_last_action?).to be_falsey end it "raises an error if the service is paused" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "paused")) provider.load_current_resource expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) expect { provider.stop_service }.to raise_error( Chef::Exceptions::Service ) expect(new_resource.updated_by_last_action?).to be_falsey end it "waits and continue if the service is in stop_pending" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stop pending"), double("StatusStruct", :current_state => "stop pending"), double("StatusStruct", :current_state => "stopped")) provider.load_current_resource expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) provider.stop_service expect(new_resource.updated_by_last_action?).to be_falsey end it "fails if the service is in start_pending" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "start pending")) provider.load_current_resource expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) expect { provider.stop_service }.to raise_error( Chef::Exceptions::Service ) expect(new_resource.updated_by_last_action?).to be_falsey end it "passes custom timeout to the stop command if provided" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running")) new_resource.timeout 1 expect(Win32::Service).to receive(:stop).with(new_resource.service_name) Timeout.timeout(2) do expect { provider.stop_service }.to raise_error(Timeout::Error) end expect(new_resource.updated_by_last_action?).to be_falsey end end describe Chef::Provider::Service::Windows, "restart_service" do it "calls the restart command if one is specified" do new_resource.restart_command "sc restart" expect(provider).to receive(:shell_out!).with("#{new_resource.restart_command}") provider.restart_service expect(new_resource.updated_by_last_action?).to be_truthy end it "stops then starts the service if it is running" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "running"), double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "running")) expect(Win32::Service).to receive(:stop).with(new_resource.service_name) expect(Win32::Service).to receive(:start).with(new_resource.service_name) provider.restart_service expect(new_resource.updated_by_last_action?).to be_truthy end it "just starts the service if it is stopped" do allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return( double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "stopped"), double("StatusStruct", :current_state => "running")) expect(Win32::Service).to receive(:start).with(new_resource.service_name) provider.restart_service expect(new_resource.updated_by_last_action?).to be_truthy end it "does nothing if the service does not exist" do allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:stop).with(new_resource.service_name) expect(Win32::Service).not_to receive(:start).with(new_resource.service_name) provider.restart_service expect(new_resource.updated_by_last_action?).to be_falsey end end describe Chef::Provider::Service::Windows, "enable_service" do before(:each) do allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "disabled")) end it "enables service" do expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::AUTO_START) provider.enable_service expect(new_resource.updated_by_last_action?).to be_truthy end it "does nothing if the service does not exist" do allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:configure) provider.enable_service expect(new_resource.updated_by_last_action?).to be_falsey end end describe Chef::Provider::Service::Windows, "action_enable" do it "does nothing if the service is enabled" do allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "auto start")) expect(provider).not_to receive(:enable_service) provider.action_enable end it "enables the service if it is not set to automatic start" do allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "disabled")) expect(provider).to receive(:enable_service) provider.action_enable end end describe Chef::Provider::Service::Windows, "action_disable" do it "does nothing if the service is disabled" do allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "disabled")) expect(provider).not_to receive(:disable_service) provider.action_disable end it "disables the service if it is not set to disabled" do allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "auto start")) expect(provider).to receive(:disable_service) provider.action_disable end end describe Chef::Provider::Service::Windows, "disable_service" do before(:each) do allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return( double("ConfigStruct", :start_type => "auto start")) end it "disables service" do expect(Win32::Service).to receive(:configure) provider.disable_service expect(new_resource.updated_by_last_action?).to be_truthy end it "does nothing if the service does not exist" do allow(Win32::Service).to receive(:exists?).with(new_resource.service_name).and_return(false) expect(Win32::Service).not_to receive(:configure) provider.disable_service expect(new_resource.updated_by_last_action?).to be_falsey end end describe Chef::Provider::Service::Windows, "action_configure_startup" do { :automatic => "auto start", :manual => "demand start", :disabled => "disabled" }.each do |type, win32| it "sets the startup type to #{type} if it is something else" do new_resource.startup_type(type) allow(provider).to receive(:current_start_type).and_return("fire") expect(provider).to receive(:set_startup_type).with(type) provider.action_configure_startup end it "leaves the startup type as #{type} if it is already set" do new_resource.startup_type(type) allow(provider).to receive(:current_start_type).and_return(win32) expect(provider).not_to receive(:set_startup_type).with(type) provider.action_configure_startup end end end describe Chef::Provider::Service::Windows, "set_start_type" do it "when called with :automatic it calls Win32::Service#configure with Win32::Service::AUTO_START" do expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::AUTO_START) provider.send(:set_startup_type, :automatic) end it "when called with :manual it calls Win32::Service#configure with Win32::Service::DEMAND_START" do expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::DEMAND_START) provider.send(:set_startup_type, :manual) end it "when called with :disabled it calls Win32::Service#configure with Win32::Service::DISABLED" do expect(Win32::Service).to receive(:configure).with(:service_name => new_resource.service_name, :start_type => Win32::Service::DISABLED) provider.send(:set_startup_type, :disabled) end it "raises an exception when given an unknown start type" do expect { provider.send(:set_startup_type, :fire_truck) }.to raise_error(Chef::Exceptions::ConfigurationError) end end shared_context "testing private methods" do let(:private_methods) do described_class.private_instance_methods end before do described_class.send(:public, *private_methods) end after do described_class.send(:private, *private_methods) end end describe "grant_service_logon" do include_context "testing private methods" let(:username) { "unit_test_user" } it "calls win32 api to grant user SeServiceLogonRight" do expect(Chef::ReservedNames::Win32::Security).to receive(:add_account_right).with(username, service_right) expect(provider.grant_service_logon(username)).to equal true end it "strips '.\' from user name when sending to win32 api" do expect(Chef::ReservedNames::Win32::Security).to receive(:add_account_right).with(username, service_right) expect(provider.grant_service_logon(".\\#{username}")).to equal true end it "raises an exception when the grant fails" do expect(Chef::ReservedNames::Win32::Security).to receive(:add_account_right).and_raise(Chef::Exceptions::Win32APIError, "barf") expect { provider.grant_service_logon(username) }.to raise_error(Chef::Exceptions::Service) end end describe "cleaning usernames" do include_context "testing private methods" it "correctly reformats usernames to create valid filenames" do expect(provider.clean_username_for_path("\\\\problem username/oink.txt")).to eq("_problem_username_oink_txt") expect(provider.clean_username_for_path("boring_username")).to eq("boring_username") end it "correctly reformats usernames for the policy file" do expect(provider.canonicalize_username(".\\maryann")).to eq("maryann") expect(provider.canonicalize_username("maryann")).to eq("maryann") expect(provider.canonicalize_username("\\\\maryann")).to eq("maryann") expect(provider.canonicalize_username("mydomain\\\\maryann")).to eq("mydomain\\\\maryann") expect(provider.canonicalize_username("\\\\mydomain\\\\maryann")).to eq("mydomain\\\\maryann") end end end chef-12.14.60/spec/unit/provider/service_spec.rb000066400000000000000000000142701276456504500214360ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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::Service do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::Service.new("chef") @current_resource = Chef::Resource::Service.new("chef") @provider = Chef::Provider::Service.new(@new_resource, @run_context) @provider.current_resource = @current_resource allow(@provider).to receive(:load_current_resource) end describe "when enabling the service" do it "should enable the service if disabled and set the resource as updated" do @current_resource.enabled(false) expect(@provider).to receive(:enable_service).and_return(true) @provider.action_enable @provider.set_updated_status expect(@provider.new_resource).to be_updated end it "should not enable the service if already enabled" do @current_resource.enabled(true) expect(@provider).not_to receive(:enable_service) @provider.action_enable @provider.set_updated_status expect(@provider.new_resource).not_to be_updated end end describe "when disabling the service" do it "should disable the service if enabled and set the resource as updated" do allow(@current_resource).to receive(:enabled).and_return(true) expect(@provider).to receive(:disable_service).and_return(true) @provider.run_action(:disable) expect(@provider.new_resource).to be_updated end it "should not disable the service if already disabled" do allow(@current_resource).to receive(:enabled).and_return(false) expect(@provider).not_to receive(:disable_service) @provider.run_action(:disable) expect(@provider.new_resource).not_to be_updated end end describe "action_start" do it "should start the service if it isn't running and set the resource as updated" do @current_resource.running(false) expect(@provider).to receive(:start_service).with(no_args).and_return(true) @provider.run_action(:start) expect(@provider.new_resource).to be_updated end it "should not start the service if already running" do @current_resource.running(true) expect(@provider).not_to receive(:start_service) @provider.run_action(:start) expect(@provider.new_resource).not_to be_updated end end describe "action_stop" do it "should stop the service if it is running and set the resource as updated" do allow(@current_resource).to receive(:running).and_return(true) expect(@provider).to receive(:stop_service).and_return(true) @provider.run_action(:stop) expect(@provider.new_resource).to be_updated end it "should not stop the service if it's already stopped" do allow(@current_resource).to receive(:running).and_return(false) expect(@provider).not_to receive(:stop_service) @provider.run_action(:stop) expect(@provider.new_resource).not_to be_updated end end describe "action_restart" do before do @current_resource.supports(:restart => true) end it "should restart the service if it's supported and set the resource as updated" do expect(@provider).to receive(:restart_service).and_return(true) @provider.run_action(:restart) expect(@provider.new_resource).to be_updated end it "should restart the service even if it isn't running and set the resource as updated" do allow(@current_resource).to receive(:running).and_return(false) expect(@provider).to receive(:restart_service).and_return(true) @provider.run_action(:restart) expect(@provider.new_resource).to be_updated end end describe "action_reload" do before do @new_resource.supports(:reload => true) end it "should raise an exception if reload isn't supported" do @new_resource.supports(:reload => false) allow(@new_resource).to receive(:reload_command).and_return(false) expect { @provider.run_action(:reload) }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "should reload the service if it is running and set the resource as updated" do allow(@current_resource).to receive(:running).and_return(true) expect(@provider).to receive(:reload_service).and_return(true) @provider.run_action(:reload) expect(@provider.new_resource).to be_updated end it "should not reload the service if it's stopped" do allow(@current_resource).to receive(:running).and_return(false) expect(@provider).not_to receive(:reload_service) @provider.run_action(:stop) expect(@provider.new_resource).not_to be_updated end end it "delegates enable_service to subclasses" do expect { @provider.enable_service }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "delegates disable_service to subclasses" do expect { @provider.disable_service }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "delegates start_service to subclasses" do expect { @provider.start_service }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "delegates stop_service to subclasses" do expect { @provider.stop_service }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "delegates restart_service to subclasses" do expect { @provider.restart_service }.to raise_error(Chef::Exceptions::UnsupportedAction) end it "delegates reload_service to subclasses" do expect { @provider.reload_service }.to raise_error(Chef::Exceptions::UnsupportedAction) end end chef-12.14.60/spec/unit/provider/subversion_spec.rb000066400000000000000000000361721276456504500222020ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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::Subversion do before do @resource = Chef::Resource::Subversion.new("my app") @resource.repository "http://svn.example.org/trunk/" @resource.destination "/my/deploy/dir" @resource.revision "12345" @resource.svn_arguments(false) @resource.svn_info_args(false) @resource.svn_binary "svn" @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @provider = Chef::Provider::Subversion.new(@resource, @run_context) end it "converts resource attributes to options for run_command and popen4" do expect(@provider.run_options).to eq({}) @resource.user "deployninja" expect(@provider.run_options).to eq({ :user => "deployninja" }) end context "determining the revision of the currently deployed code" do before do @stdout = double("stdout") @stderr = double("stderr") @exitstatus = double("exitstatus") end it "sets the revision to nil if there isn't any deployed code yet" do expect(::File).to receive(:exist?).with("/my/deploy/dir/.svn").and_return(false) expect(@provider.find_current_revision).to be_nil end it "determines the current revision if there's a checkout with svn data available" do example_svn_info = "Path: .\n" + "URL: http://svn.example.org/trunk/myapp\n" + "Repository Root: http://svn.example.org\n" + "Repository UUID: d62ff500-7bbc-012c-85f1-0026b0e37c24\n" + "Revision: 11739\nNode Kind: directory\n" + "Schedule: normal\n" + "Last Changed Author: codeninja\n" + "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n" expect(::File).to receive(:exist?).at_least(1).times.with("/my/deploy/dir/.svn").and_return(true) expected_command = ["svn info", { :cwd => "/my/deploy/dir", :returns => [0, 1] }] expect(@provider).to receive(:shell_out!).with(*expected_command). and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => "")) expect(@provider.find_current_revision).to eql("11410") end it "gives nil as the current revision if the deploy dir isn't a SVN working copy" do example_svn_info = "svn: '/tmp/deploydir' is not a working copy\n" expect(::File).to receive(:exist?).with("/my/deploy/dir/.svn").and_return(true) expected_command = ["svn info", { :cwd => "/my/deploy/dir", :returns => [0, 1] }] expect(@provider).to receive(:shell_out!).with(*expected_command). and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => "")) expect(@provider.find_current_revision).to be_nil end it "finds the current revision when loading the current resource state" do # note: the test is kinda janky, but it provides regression coverage for CHEF-2092 @resource.instance_variable_set(:@action, :sync) expect(@provider).to receive(:find_current_revision).and_return("12345") @provider.load_current_resource expect(@provider.current_resource.revision).to eq("12345") end end it "creates the current_resource object and sets its revision to the current deployment's revision as long as we're not exporting" do allow(@provider).to receive(:find_current_revision).and_return("11410") @provider.new_resource.instance_variable_set :@action, [:checkout] @provider.load_current_resource expect(@provider.current_resource.name).to eql(@resource.name) expect(@provider.current_resource.revision).to eql("11410") end context "resolving revisions to an integer" do before do @stdout = double("stdout") @stderr = double("stderr") @resource.svn_info_args "--no-auth-cache" end it "returns the revision number as is if it's already an integer" do expect(@provider.revision_int).to eql("12345") end it "queries the server and resolves the revision if it's not an integer (i.e. 'HEAD')" do example_svn_info = "Path: .\n" + "URL: http://svn.example.org/trunk/myapp\n" + "Repository Root: http://svn.example.org\n" + "Repository UUID: d62ff500-7bbc-012c-85f1-0026b0e37c24\n" + "Revision: 11739\nNode Kind: directory\n" + "Schedule: normal\n" + "Last Changed Author: codeninja\n" + "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n" @resource.revision "HEAD" expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache -rHEAD", { :cwd => "/my/deploy/dir", :returns => [0, 1] }] expect(@provider).to receive(:shell_out!).with(*expected_command). and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => "")) expect(@provider.revision_int).to eql("11410") end it "returns a helpful message if data from `svn info` can't be parsed" do example_svn_info = "some random text from an error message\n" @resource.revision "HEAD" expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache -rHEAD", { :cwd => "/my/deploy/dir", :returns => [0, 1] }] expect(@provider).to receive(:shell_out!).with(*expected_command). and_return(double("ShellOut result", :stdout => example_svn_info, :stderr => "")) expect { @provider.revision_int }.to raise_error(RuntimeError, "Could not parse `svn info` data: some random text from an error message\n") end it "responds to :revision_slug as an alias for revision_sha" do expect(@provider).to respond_to(:revision_slug) end end it "generates a checkout command with default options" do expect(@provider.checkout_command).to eql("svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir") end it "generates a checkout command with authentication" do @resource.svn_username "deployNinja" @resource.svn_password "vanish!" expect(@provider.checkout_command).to eql("svn checkout -q --username deployNinja --password vanish! " + "-r12345 http://svn.example.org/trunk/ /my/deploy/dir") end it "generates a checkout command with arbitrary options" do @resource.svn_arguments "--no-auth-cache" expect(@provider.checkout_command).to eql("svn checkout --no-auth-cache -q -r12345 " + "http://svn.example.org/trunk/ /my/deploy/dir") end it "generates a sync command with default options" do expect(@provider.sync_command).to eql("svn update -q -r12345 /my/deploy/dir") end it "generates an export command with default options" do expect(@provider.export_command).to eql("svn export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir") end it "doesn't try to find the current revision when loading the resource if running an export" do @provider.new_resource.instance_variable_set :@action, [:export] expect(@provider).not_to receive(:find_current_revision) @provider.load_current_resource end it "doesn't try to find the current revision when loading the resource if running a force export" do @provider.new_resource.instance_variable_set :@action, [:force_export] expect(@provider).not_to receive(:find_current_revision) @provider.load_current_resource end it "runs an export with the --force option" do allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) expected_cmd = "svn export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir" expect(@provider).to receive(:shell_out!).with(expected_cmd, {}) @provider.run_action(:force_export) expect(@resource).to be_updated end it "runs the checkout command for action_checkout" do allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) expected_cmd = "svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir" expect(@provider).to receive(:shell_out!).with(expected_cmd, {}) @provider.run_action(:checkout) expect(@resource).to be_updated end it "raises an error if the svn checkout command would fail because the enclosing directory doesn't exist" do expect { @provider.run_action(:sync) }.to raise_error(Chef::Exceptions::MissingParentDirectory) end it "should not checkout if the destination exists or is a non empty directory" do allow(::File).to receive(:exist?).with("/my/deploy/dir/.svn").and_return(false) allow(::File).to receive(:exist?).with("/my/deploy/dir").and_return(true) allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) allow(::Dir).to receive(:entries).with("/my/deploy/dir").and_return([".", "..", "foo", "bar"]) expect(@provider).not_to receive(:checkout_command) @provider.run_action(:checkout) expect(@resource).not_to be_updated end it "runs commands with the user and group specified in the resource" do allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) @resource.user "whois" @resource.group "thisis" expected_cmd = "svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir" expect(@provider).to receive(:shell_out!).with(expected_cmd, { user: "whois", group: "thisis" }) @provider.run_action(:checkout) expect(@resource).to be_updated end it "does a checkout for action_sync if there's no deploy dir" do allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) expect(::File).to receive(:exist?).with("/my/deploy/dir/.svn").twice.and_return(false) expect(@provider).to receive(:action_checkout) @provider.run_action(:sync) end it "does a checkout for action_sync if the deploy dir exists but is empty" do allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) expect(::File).to receive(:exist?).with("/my/deploy/dir/.svn").twice.and_return(false) expect(@provider).to receive(:action_checkout) @provider.run_action(:sync) end it "runs the sync_command on action_sync if the deploy dir exists and isn't empty" do allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) expect(::File).to receive(:exist?).with("/my/deploy/dir/.svn").and_return(true) allow(@provider).to receive(:find_current_revision).and_return("11410") allow(@provider).to receive(:current_revision_matches_target_revision?).and_return(false) expected_cmd = "svn update -q -r12345 /my/deploy/dir" expect(@provider).to receive(:shell_out!).with(expected_cmd, {}) @provider.run_action(:sync) expect(@resource).to be_updated end it "does not fetch any updates if the remote revision matches the current revision" do allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) expect(::File).to receive(:exist?).with("/my/deploy/dir/.svn").and_return(true) allow(@provider).to receive(:find_current_revision).and_return("12345") allow(@provider).to receive(:current_revision_matches_target_revision?).and_return(true) @provider.run_action(:sync) expect(@resource).not_to be_updated end it "runs the export_command on action_export" do allow(::File).to receive(:directory?).with("/my/deploy").and_return(true) expected_cmd = "svn export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir" expect(@provider).to receive(:shell_out!).with(expected_cmd, {}) @provider.run_action(:export) expect(@resource).to be_updated end context "selects the correct svn binary" do before do end it "selects 'svn' as the binary by default" do @resource.svn_binary nil allow(ChefConfig).to receive(:windows?) { false } expect(@provider).to receive(:svn_binary).and_return("svn") expect(@provider.export_command).to eql( "svn export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir") end it "selects an svn binary with an exe extension on windows" do @resource.svn_binary nil allow(ChefConfig).to receive(:windows?) { true } expect(@provider).to receive(:svn_binary).and_return("svn.exe") expect(@provider.export_command).to eql( "svn.exe export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir") end it "uses a custom svn binary as part of the svn command" do @resource.svn_binary "teapot" expect(@provider).to receive(:svn_binary).and_return("teapot") expect(@provider.export_command).to eql( "teapot export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir") end it "wraps custom svn binary with quotes if it contains whitespace" do @resource.svn_binary "c:/program files (x86)/subversion/svn.exe" expect(@provider).to receive(:svn_binary).and_return("c:/program files (x86)/subversion/svn.exe") expect(@provider.export_command).to eql( '"c:/program files (x86)/subversion/svn.exe" export --force -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir') end end shared_examples_for "proxied configuration" do it "generates a checkout command with a http proxy" do expect(@provider.checkout_command).to eql("svn checkout -q" + " --config-option servers:global:http-proxy-host=somehost --config-option servers:global:http-proxy-port=1" + " -r12345 #{repository_url} /my/deploy/dir" ) end end describe "when proxy environment variables exist" do let(:http_proxy_uri) { "http://somehost:1" } let(:http_no_proxy) { "svn.example.org" } before (:all) do @original_env = ENV.to_hash end after (:all) do ENV.clear ENV.update(@original_env) end context "http_proxy is specified" do let(:repository_url) { "http://svn.example.org/trunk/" } before do ENV["http_proxy"] = http_proxy_uri end it_should_behave_like "proxied configuration" end context "https_proxy is specified" do let(:repository_url) { "https://svn.example.org/trunk/" } before do ENV["http_proxy"] = nil ENV["https_proxy"] = http_proxy_uri @resource.repository "https://svn.example.org/trunk/" end it_should_behave_like "proxied configuration" end context "when no_proxy is specified" do before do ENV["http_proxy"] = http_proxy_uri ENV["no_proxy"] = http_no_proxy end it "generates a checkout command with default options" do expect(@provider.checkout_command).to eql("svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir") end end end end chef-12.14.60/spec/unit/provider/systemd_unit_spec.rb000066400000000000000000001070341276456504500225260ustar00rootroot00000000000000# # Author:: Nathan Williams () # Copyright:: Copyright (c), Nathan Williams # 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::SystemdUnit do let(:node) do Chef::Node.new.tap do |n| n.default["etc"] = {} n.default["etc"]["passwd"] = { "joe" => { "uid" => 1_000, }, } end end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:unit_name) { "sysstat-collect.timer" } let(:user_name) { "joe" } let(:current_resource) { Chef::Resource::SystemdUnit.new(unit_name) } let(:new_resource) { Chef::Resource::SystemdUnit.new(unit_name) } let(:provider) { Chef::Provider::SystemdUnit.new(new_resource, run_context) } let(:unit_path_system) { "/etc/systemd/system/sysstat-collect.timer" } let(:unit_path_user) { "/etc/systemd/user/sysstat-collect.timer" } let(:unit_content_string) { "[Unit]\nDescription = Run system activity accounting tool every 10 minutes\n\n[Timer]\nOnCalendar = *:00/10\n\n[Install]\nWantedBy = sysstat.service\n" } let(:malformed_content_string) { "derp" } let(:unit_content_hash) do { "Unit" => { "Description" => "Run system activity accounting tool every 10 minutes", }, "Timer" => { "OnCalendar" => "*:00/10", }, "Install" => { "WantedBy" => "sysstat.service", }, } end let(:user_cmd_opts) do { :user => "joe", :environment => { "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/1000/bus", }, } end let(:shell_out_success) do double("shell_out_with_systems_locale", :exitstatus => 0, :error? => false) end let(:shell_out_failure) do double("shell_out_with_systems_locale", :exitstatus => 1, :error? => true) end let(:shell_out_masked) do double("shell_out_with_systems_locale", :exit_status => 0, :error? => false, :stdout => "masked") end let(:shell_out_static) do double("shell_out_with_systems_locale", :exit_status => 0, :error? => false, :stdout => "static") end before(:each) do allow(Chef::Resource::SystemdUnit).to receive(:new) .with(unit_name) .and_return(current_resource) end describe "define_resource_requirements" do before(:each) do provider.action = :create allow(provider).to receive(:active?).and_return(false) allow(provider).to receive(:enabled?).and_return(false) allow(provider).to receive(:masked?).and_return(false) allow(provider).to receive(:static?).and_return(false) end it "accepts valid resource requirements" do new_resource.content(unit_content_string) provider.load_current_resource provider.define_resource_requirements expect { provider.process_resource_requirements }.to_not raise_error end it "rejects failed resource requirements" do new_resource.content(malformed_content_string) provider.load_current_resource provider.define_resource_requirements expect { provider.process_resource_requirements }.to raise_error(IniParse::ParseError) end end describe "load_current_resource" do before(:each) do allow(provider).to receive(:active?).and_return(false) allow(provider).to receive(:enabled?).and_return(false) allow(provider).to receive(:masked?).and_return(false) allow(provider).to receive(:static?).and_return(false) end it "should create a current resource with the name of the new resource" do expect(Chef::Resource::SystemdUnit).to receive(:new) .with(unit_name) .and_return(current_resource) provider.load_current_resource end it "should check if the unit is active" do expect(provider).to receive(:active?) provider.load_current_resource end it "sets the active property to true if the unit is active" do allow(provider).to receive(:active?).and_return(true) provider.load_current_resource expect(current_resource.active).to be true end it "sets the active property to false if the unit is not active" do allow(provider).to receive(:active?).and_return(false) provider.load_current_resource expect(current_resource.active).to be false end it "should check if the unit is enabled" do expect(provider).to receive(:enabled?) provider.load_current_resource end it "sets the enabled property to true if the unit is enabled" do allow(provider).to receive(:enabled?).and_return(true) provider.load_current_resource expect(current_resource.enabled).to be true end it "sets the enabled property to false if the unit is not enabled" do allow(provider).to receive(:enabled?).and_return(false) provider.load_current_resource expect(current_resource.enabled).to be false end it "should check if the unit is masked" do expect(provider).to receive(:masked?) provider.load_current_resource end it "sets the masked property to true if the unit is masked" do allow(provider).to receive(:masked?).and_return(true) provider.load_current_resource expect(current_resource.masked).to be true end it "sets the masked property to false if the unit is masked" do allow(provider).to receive(:masked?).and_return(false) provider.load_current_resource expect(current_resource.masked).to be false end it "should check if the unit is static" do expect(provider).to receive(:static?) provider.load_current_resource end it "sets the static property to true if the unit is static" do allow(provider).to receive(:static?).and_return(true) provider.load_current_resource expect(current_resource.static).to be true end it "sets the static property to false if the unit is not static" do allow(provider).to receive(:static?).and_return(false) provider.load_current_resource expect(current_resource.static).to be false end it "loads the system unit content if the file exists and user is not set" do allow(File).to receive(:exist?) .with(unit_path_system) .and_return(true) allow(File).to receive(:read) .with(unit_path_system) .and_return(unit_content_string) expect(File).to receive(:exist?) .with(unit_path_system) expect(File).to receive(:read) .with(unit_path_system) provider.load_current_resource expect(current_resource.content).to eq(unit_content_string) end it "does not load the system unit content if the unit file is not present and the user is not set" do allow(File).to receive(:exist?) .with(unit_path_system) .and_return(false) expect(File).to_not receive(:read) .with(unit_path_system) provider.load_current_resource expect(current_resource.content).to eq(nil) end it "loads the user unit content if the file exists and user is set" do new_resource.user("joe") allow(File).to receive(:exist?) .with(unit_path_user) .and_return(true) allow(File).to receive(:read) .with(unit_path_user) .and_return(unit_content_string) expect(File).to receive(:exist?) .with(unit_path_user) expect(File).to receive(:read) .with(unit_path_user) provider.load_current_resource expect(current_resource.content).to eq(unit_content_string) end it "does not load the user unit if the file does not exist and user is set" do new_resource.user("joe") allow(File).to receive(:exist?) .with(unit_path_user) .and_return(false) expect(File).to_not receive(:read) .with(unit_path_user) provider.load_current_resource expect(current_resource.content).to eq(nil) end end %w{/bin/systemctl /usr/bin/systemctl}.each do |systemctl_path| describe "when systemctl path is #{systemctl_path}" do before(:each) do provider.current_resource = current_resource allow(provider).to receive(:which) .with("systemctl") .and_return(systemctl_path) end describe "creates/deletes the unit" do it "creates the unit file when it does not exist" do allow(provider).to receive(:manage_unit_file) .with(:create) .and_return(true) allow(provider).to receive(:daemon_reload) .and_return(true) expect(provider).to receive(:manage_unit_file).with(:create) provider.action_create end it "creates the file when the unit content is different" do allow(provider).to receive(:manage_unit_file) .with(:create) .and_return(true) allow(provider).to receive(:daemon_reload) .and_return(true) expect(provider).to receive(:manage_unit_file).with(:create) provider.action_create end it "does not create the unit file when the content is the same" do current_resource.content(unit_content_string) allow(provider).to receive(:manage_unit_file).with(:create) allow(provider).to receive(:daemon_reload) .and_return(true) expect(provider).to_not receive(:manage_unit_file) provider.action_create end it "triggers a daemon-reload when creating a unit with triggers_reload" do allow(provider).to receive(:manage_unit_file).with(:create) expect(new_resource.triggers_reload).to eq true allow(provider).to receive(:shell_out_with_systems_locale!) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} daemon-reload") provider.action_create end it "triggers a daemon-reload when deleting a unit with triggers_reload" do allow(File).to receive(:exist?) .with(unit_path_system) .and_return(true) allow(provider).to receive(:manage_unit_file).with(:delete) expect(new_resource.triggers_reload).to eq true allow(provider).to receive(:shell_out_with_systems_locale!) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} daemon-reload") provider.action_delete end it "does not trigger a daemon-reload when creating a unit without triggers_reload" do new_resource.triggers_reload(false) allow(provider).to receive(:manage_unit_file).with(:create) allow(provider).to receive(:shell_out_with_systems_locale!) expect(provider).to_not receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} daemon-reload") provider.action_create end it "does not trigger a daemon-reload when deleting a unit without triggers_reload" do new_resource.triggers_reload(false) allow(File).to receive(:exist?) .with(unit_path_system) .and_return(true) allow(provider).to receive(:manage_unit_file).with(:delete) allow(provider).to receive(:shell_out_with_systems_locale!) expect(provider).to_not receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} daemon-reload") provider.action_delete end context "when a user is specified" do it "deletes the file when it exists" do new_resource.user("joe") allow(File).to receive(:exist?) .with(unit_path_user) .and_return(true) allow(provider).to receive(:manage_unit_file) .with(:delete) .and_return(true) allow(provider).to receive(:daemon_reload) expect(provider).to receive(:manage_unit_file).with(:delete) provider.action_delete end it "does not delete the file when it is absent" do new_resource.user("joe") allow(File).to receive(:exist?) .with(unit_path_user) .and_return(false) allow(provider).to receive(:manage_unit_file).with(:delete) expect(provider).to_not receive(:manage_unit_file) provider.action_delete end end context "when no user is specified" do it "deletes the file when it exists" do allow(File).to receive(:exist?) .with(unit_path_system) .and_return(true) allow(provider).to receive(:manage_unit_file) .with(:delete) allow(provider).to receive(:daemon_reload) expect(provider).to receive(:manage_unit_file).with(:delete) provider.action_delete end it "does not delete the file when it is absent" do allow(File).to receive(:exist?) .with(unit_path_system) .and_return(false) allow(provider).to receive(:manage_unit_file).with(:delete) allow(provider).to receive(:daemon_reload) expect(provider).to_not receive(:manage_unit_file) provider.action_delete end end end describe "enables/disables the unit" do context "when a user is specified" do it "enables the unit when it is disabled" do current_resource.user(user_name) current_resource.enabled(false) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --user enable #{unit_name}", user_cmd_opts) .and_return(shell_out_success) provider.action_enable end it "does not enable the unit when it is enabled" do current_resource.user(user_name) current_resource.enabled(true) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_enable end it "does not enable the unit when it is static" do current_resource.user(user_name) current_resource.static(true) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_enable end it "disables the unit when it is enabled" do current_resource.user(user_name) current_resource.enabled(true) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --user disable #{unit_name}", user_cmd_opts) .and_return(shell_out_success) provider.action_disable end it "does not disable the unit when it is disabled" do current_resource.user(user_name) current_resource.enabled(false) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_disable end it "does not disable the unit when it is static" do current_resource.user(user_name) current_resource.static(true) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_disable end end context "when no user is specified" do it "enables the unit when it is disabled" do current_resource.enabled(false) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --system enable #{unit_name}", {}) .and_return(shell_out_success) provider.action_enable end it "does not enable the unit when it is enabled" do current_resource.enabled(true) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_enable end it "does not enable the unit when it is static" do current_resource.static(true) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_enable end it "disables the unit when it is enabled" do current_resource.enabled(true) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --system disable #{unit_name}", {}) .and_return(shell_out_success) provider.action_disable end it "does not disable the unit when it is disabled" do current_resource.enabled(false) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_disable end it "does not disable the unit when it is static" do current_resource.user(user_name) current_resource.static(true) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_disable end end end describe "masks/unmasks the unit" do context "when a user is specified" do it "masks the unit when it is unmasked" do current_resource.user(user_name) current_resource.masked(false) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --user mask #{unit_name}", user_cmd_opts) .and_return(shell_out_success) provider.action_mask end it "does not mask the unit when it is masked" do current_resource.user(user_name) current_resource.masked(true) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_mask end it "unmasks the unit when it is masked" do current_resource.user(user_name) current_resource.masked(true) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --user unmask #{unit_name}", user_cmd_opts) .and_return(shell_out_success) provider.action_unmask end it "does not unmask the unit when it is unmasked" do current_resource.user(user_name) current_resource.masked(false) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_unmask end end context "when no user is specified" do it "masks the unit when it is unmasked" do current_resource.masked(false) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --system mask #{unit_name}", {}) .and_return(shell_out_success) provider.action_mask end it "does not mask the unit when it is masked" do current_resource.masked(true) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_mask end it "unmasks the unit when it is masked" do current_resource.masked(true) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --system unmask #{unit_name}", {}) .and_return(shell_out_success) provider.action_unmask end it "does not unmask the unit when it is unmasked" do current_resource.masked(false) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_unmask end end end describe "starts/stops the unit" do context "when a user is specified" do it "starts the unit when it is inactive" do current_resource.user(user_name) current_resource.active(false) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --user start #{unit_name}", user_cmd_opts) .and_return(shell_out_success) provider.action_start end it "does not start the unit when it is active" do current_resource.user(user_name) current_resource.active(true) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_start end it "stops the unit when it is active" do current_resource.user(user_name) current_resource.active(true) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --user stop #{unit_name}", user_cmd_opts) .and_return(shell_out_success) provider.action_stop end it "does not stop the unit when it is inactive" do current_resource.user(user_name) current_resource.active(false) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_stop end end context "when no user is specified" do it "starts the unit when it is inactive" do current_resource.active(false) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --system start #{unit_name}", {}) .and_return(shell_out_success) provider.action_start end it "does not start the unit when it is active" do current_resource.active(true) expect(provider).to_not receive(:shell_out_with_systems_locale!) provider.action_start end it "stops the unit when it is active" do current_resource.active(true) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --system stop #{unit_name}", {}) .and_return(shell_out_success) provider.action_stop end it "does not stop the unit when it is inactive" do current_resource.active(false) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_stop end end end describe "restarts/reloads the unit" do context "when a user is specified" do it "restarts the unit" do current_resource.user(user_name) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --user restart #{unit_name}", user_cmd_opts) .and_return(shell_out_success) provider.action_restart end it "reloads the unit if active" do current_resource.user(user_name) current_resource.active(true) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --user reload #{unit_name}", user_cmd_opts) .and_return(shell_out_success) provider.action_reload end it "does not reload if the unit is inactive" do current_resource.user(user_name) current_resource.active(false) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_reload end end context "when no user is specified" do it "restarts the unit" do expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --system restart #{unit_name}", {}) .and_return(shell_out_success) provider.action_restart end it "reloads the unit if active" do current_resource.active(true) expect(provider).to receive(:shell_out_with_systems_locale!) .with("#{systemctl_path} --system reload #{unit_name}", {}) .and_return(shell_out_success) provider.action_reload end it "does not reload the unit if inactive" do current_resource.active(false) expect(provider).not_to receive(:shell_out_with_systems_locale!) provider.action_reload end 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 allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end context "when a user is specified" do it "returns true when unit is active" do current_resource.user(user_name) expect(provider).to receive(:shell_out) .with("#{systemctl_path} --user is-active #{unit_name}", user_cmd_opts) .and_return(shell_out_success) expect(provider.active?).to be true end it "returns false when unit is inactive" do current_resource.user(user_name) expect(provider).to receive(:shell_out) .with("#{systemctl_path} --user is-active #{unit_name}", user_cmd_opts) .and_return(shell_out_failure) expect(provider.active?).to be false end end context "when no user is specified" do it "returns true when unit is active" do expect(provider).to receive(:shell_out) .with("#{systemctl_path} --system is-active #{unit_name}", {}) .and_return(shell_out_success) expect(provider.active?).to be true end it "returns false when unit is not active" do expect(provider).to receive(:shell_out) .with("#{systemctl_path} --system is-active #{unit_name}", {}) .and_return(shell_out_failure) expect(provider.active?).to be false end end end describe "#enabled?" do before(:each) do provider.current_resource = current_resource allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end context "when a user is specified" do it "returns true when unit is enabled" do current_resource.user(user_name) expect(provider).to receive(:shell_out) .with("#{systemctl_path} --user is-enabled #{unit_name}", user_cmd_opts) .and_return(shell_out_success) expect(provider.enabled?).to be true end it "returns false when unit is not enabled" do current_resource.user(user_name) expect(provider).to receive(:shell_out) .with("#{systemctl_path} --user is-enabled #{unit_name}", user_cmd_opts) .and_return(shell_out_failure) expect(provider.enabled?).to be false end end context "when no user is specified" do it "returns true when unit is enabled" do expect(provider).to receive(:shell_out) .with("#{systemctl_path} --system is-enabled #{unit_name}", {}) .and_return(shell_out_success) expect(provider.enabled?).to be true end it "returns false when unit is not enabled" do expect(provider).to receive(:shell_out) .with("#{systemctl_path} --system is-enabled #{unit_name}", {}) .and_return(shell_out_failure) expect(provider.enabled?).to be false end end end describe "#masked?" do before(:each) do provider.current_resource = current_resource allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end context "when a user is specified" do it "returns true when the unit is masked" do current_resource.user(user_name) expect(provider).to receive(:shell_out) .with("#{systemctl_path} --user status #{unit_name}", user_cmd_opts) .and_return(shell_out_masked) expect(provider.masked?).to be true end it "returns false when the unit is not masked" do current_resource.user(user_name) expect(provider).to receive(:shell_out) .with("#{systemctl_path} --user status #{unit_name}", user_cmd_opts) .and_return(shell_out_static) expect(provider.masked?).to be false end end context "when no user is specified" do it "returns true when the unit is masked" do expect(provider).to receive(:shell_out) .with("#{systemctl_path} --system status #{unit_name}", {}) .and_return(shell_out_masked) expect(provider.masked?).to be true end it "returns false when the unit is not masked" do expect(provider).to receive(:shell_out) .with("#{systemctl_path} --system status #{unit_name}", {}) .and_return(shell_out_static) expect(provider.masked?).to be false end end end describe "#static?" do before(:each) do provider.current_resource = current_resource allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end context "when a user is specified" do it "returns true when the unit is static" do current_resource.user(user_name) expect(provider).to receive(:shell_out) .with("#{systemctl_path} --user is-enabled #{unit_name}", user_cmd_opts) .and_return(shell_out_static) expect(provider.static?).to be true end it "returns false when the unit is not static" do current_resource.user(user_name) expect(provider).to receive(:shell_out) .with("#{systemctl_path} --user is-enabled #{unit_name}", user_cmd_opts) .and_return(shell_out_masked) expect(provider.static?).to be false end end context "when no user is specified" do it "returns true when the unit is static" do expect(provider).to receive(:shell_out) .with("#{systemctl_path} --system is-enabled #{unit_name}", {}) .and_return(shell_out_static) expect(provider.static?).to be true end it "returns false when the unit is not static" do expect(provider).to receive(:shell_out) .with("#{systemctl_path} --system is-enabled #{unit_name}", {}) .and_return(shell_out_masked) expect(provider.static?).to be false end end end end end end chef-12.14.60/spec/unit/provider/template/000077500000000000000000000000001276456504500202465ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/template/content_spec.rb000066400000000000000000000146101276456504500232610ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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::Template::Content do let(:enclosing_directory) do canonicalize_path(Dir.mktmpdir) end let(:resource_path) do canonicalize_path(File.expand_path(File.join(enclosing_directory, "openldap_stuff.conf"))) end let(:new_resource) do double("Chef::Resource::Template (new)", :cookbook_name => "openldap", :recipe_name => "default", :source_line => "/Users/lamont/solo/cookbooks/openldap/recipes/default.rb:2:in `from_file'", :source_line_file => "/Users/lamont/solo/cookbooks/openldap/recipes/default.rb", :source_line_number => "2", :source => "openldap_stuff.conf.erb", :name => "openldap_stuff.conf", :path => resource_path, :local => false, :cookbook => nil, :variables => {}, :inline_helper_blocks => {}, :inline_helper_modules => [], :helper_modules => []) end let(:rendered_file_locations) do [Dir.tmpdir + "/openldap_stuff.conf", enclosing_directory + "/openldap_stuff.conf"] end let(:run_context) do cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) Chef::Cookbook::FileVendor.fetch_from_disk(cookbook_repo) cl = Chef::CookbookLoader.new(cookbook_repo) cl.load_cookbooks cookbook_collection = Chef::CookbookCollection.new(cl) node = Chef::Node.new double("Chef::Resource::RunContext", :node => node, :cookbook_collection => cookbook_collection) end let(:content) do current_resource = double("Chef::Resource::Template (current)") Chef::Provider::Template::Content.new(new_resource, current_resource, run_context) end after do rendered_file_locations.each do |file| FileUtils.rm(file) if ::File.exist?(file) end end it "finds the template file in the cookbook cache if it isn't local" do expect(content.template_location).to eq(CHEF_SPEC_DATA + "/cookbooks/openldap/templates/default/openldap_stuff.conf.erb") end it "finds the template file locally if it is local" do allow(new_resource).to receive(:local).and_return(true) allow(new_resource).to receive(:source).and_return("/tmp/its_on_disk.erb") expect(content.template_location).to eq("/tmp/its_on_disk.erb") end it "should use the cookbook name if defined in the template resource" do allow(new_resource).to receive(:cookbook_name).and_return("apache2") allow(new_resource).to receive(:cookbook).and_return("openldap") allow(new_resource).to receive(:source).and_return("test.erb") expect(content.template_location).to eq(CHEF_SPEC_DATA + "/cookbooks/openldap/templates/default/test.erb") end it "returns a tempfile in the tempdir when :file_staging_uses_destdir is not set" do Chef::Config[:file_staging_uses_destdir] = false expect(content.tempfile.path.start_with?(Dir.tmpdir)).to be true expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be false end it "returns a tempfile in the destdir when :file_staging_uses_destdir is set" do Chef::Config[:file_staging_uses_destdir] = true expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be true end context "when creating a tempfile in destdir fails" do let(:enclosing_directory) do canonicalize_path("/nonexisting/path") end it "returns a tempfile in the tempdir when :file_deployment_uses_destdir is set to :auto" do Chef::Config[:file_staging_uses_destdir] = :auto expect(content.tempfile.path.start_with?(Dir.tmpdir)).to be true expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be false end it "fails when :file_desployment_uses_destdir is set" do Chef::Config[:file_staging_uses_destdir] = true expect { content.tempfile }.to raise_error(Chef::Exceptions::FileContentStagingError) end it "returns a tempfile in the tempdir when :file_desployment_uses_destdir is not set" do expect(content.tempfile.path.start_with?(Dir.tmpdir)).to be true expect(canonicalize_path(content.tempfile.path).start_with?(enclosing_directory)).to be false end end it "creates the template with the rendered content" do run_context.node.normal[:slappiness] = "a warm gun" expect(IO.read(content.tempfile.path)).to eq("slappiness is a warm gun") end describe "when using location helpers" do let(:new_resource) do double("Chef::Resource::Template (new)", :cookbook_name => "openldap", :recipe_name => "default", :source_line => CHEF_SPEC_DATA + "/cookbooks/openldap/recipes/default.rb:2:in `from_file'", :source_line_file => CHEF_SPEC_DATA + "/cookbooks/openldap/recipes/default.rb", :source_line_number => "2", :source => "helpers.erb", :name => "helpers.erb", :path => CHEF_SPEC_DATA + "/cookbooks/openldap/templates/default/helpers.erb", :local => false, :cookbook => nil, :variables => {}, :inline_helper_blocks => {}, :inline_helper_modules => [], :helper_modules => []) end it "creates the template with the rendered content" do expect(IO.read(content.tempfile.path)).to eql <) # Author:: Lamont Granquist () # 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 "stringio" require "spec_helper" require "etc" require "ostruct" require "support/shared/unit/provider/file" describe Chef::Provider::Template do let(:node) { double("Chef::Node") } let(:events) { double("Chef::Events").as_null_object } # mock all the methods let(:run_context) { double("Chef::RunContext", :node => node, :events => events) } let(:enclosing_directory) do canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates"))) end let(:resource_path) do canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt"))) end # Subject let(:provider) do provider = described_class.new(resource, run_context) allow(provider).to receive(:content).and_return(content) provider end let(:resource) do resource = Chef::Resource::Template.new("seattle", @run_context) resource.path(resource_path) resource end let(:content) do content = double("Chef::Provider::File::Content::Template", :template_location => "/foo/bar/baz") allow(File).to receive(:exists?).with("/foo/bar/baz").and_return(true) content end it_behaves_like Chef::Provider::File context "when creating the template" do let(:node) { double("Chef::Node") } let(:events) { double("Chef::Events").as_null_object } # mock all the methods let(:run_context) { double("Chef::RunContext", :node => node, :events => events) } let(:enclosing_directory) do canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates"))) end let(:resource_path) do canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt"))) end # Subject let(:provider) do provider = described_class.new(resource, run_context) allow(provider).to receive(:content).and_return(content) provider end it "stops executing when the local template source can't be found" do setup_normal_file allow(content).to receive(:template_location).and_return("/baz/bar/foo") allow(File).to receive(:exists?).with("/baz/bar/foo").and_return(false) expect { provider.run_action(:create) }.to raise_error Chef::Mixin::WhyRun::ResourceRequirements::Assertion::AssertionFailure end end it_behaves_like "a file provider with source field" end chef-12.14.60/spec/unit/provider/user/000077500000000000000000000000001276456504500174115ustar00rootroot00000000000000chef-12.14.60/spec/unit/provider/user/dscl_spec.rb000066400000000000000000001006631276456504500217030ustar00rootroot00000000000000# # Author:: Dreamcat4 () # Copyright:: Copyright 2009-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 "ostruct" require "mixlib/shellout" describe Chef::Provider::User::Dscl do before do allow(ChefConfig).to receive(:windows?) { false } end let(:shellcmdresult) do Struct.new(:stdout, :stderr, :exitstatus) end let(:node) do node = Chef::Node.new allow(node).to receive(:[]).with(:platform_version).and_return(mac_version) allow(node).to receive(:[]).with(:platform).and_return("mac_os_x") node end let(:events) do Chef::EventDispatch::Dispatcher.new end let(:run_context) do Chef::RunContext.new(node, {}, events) end let(:new_resource) do r = Chef::Resource::User::DsclUser.new("toor") r.password(password) r.salt(salt) r.iterations(iterations) r end let(:provider) do Chef::Provider::User::Dscl.new(new_resource, run_context) end let(:mac_version) do "10.9.1" end let(:password) { nil } let(:salt) { nil } let(:iterations) { nil } let(:salted_sha512_password) do "0f543f021c63255e64e121a3585601b8ecfedf6d2\ 705ddac69e682a33db5dbcdb9b56a2520bc8fff63a\ 2ba6b7984c0737ff0b7949455071581f7affcd536d\ 402b6cdb097" end let(:salted_sha512_pbkdf2_password) do "c734b6e4787c3727bb35e29fdd92b97c\ 1de12df509577a045728255ec7c6c5f5\ c18efa05ed02b682ffa7ebc05119900e\ b1d4880833aa7a190afc13e2bf0936b8\ 20123e8c98f0f9bcac2a629d9163caac\ 9464a8c234f3919082400b4f939bb77b\ c5adbbac718b7eb99463a7b679571e0f\ 1c9fef2ef08d0b9e9c2bcf644eed2ffc" end let(:salted_sha512_pbkdf2_salt) do "2d942d8364a9ccf2b8e5cb7ed1ff58f78\ e29dbfee7f9db58859144d061fd0058" end let(:salted_sha512_pbkdf2_iterations) do 25000 end let(:vagrant_sha_512) do "6f75d7190441facc34291ebbea1fc756b242d4f\ e9bcff141bccb84f1979e27e539539aa31f9f7dcc92c0cea959\ ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30" end let(:vagrant_sha_512_pbkdf2) do "12601a90db17cbf\ 8ba4808e6382fb0d3b9d8a6c1a190477bf680ab21afb\ 6065467136e55cc208a6f74156e3daf20fb13369ef4b\ 7bafa047d80359fb46a48a4adccd548ebb33851b093\ 47cca84341a7f93a27147343f89fb843fb46c0017d2\ 64afa4976baacf941b915bd1ec1ca24c30b3e759e02\ 403e02f59fe7ff5938a7636c" end let(:vagrant_sha_512_pbkdf2_salt) do "ee954be472fdc60ddf89484781433993625f006af6ec810c08f49a7e413946a1" end let(:vagrant_sha_512_pbkdf2_iterations) do 34482 end describe "when shelling out to dscl" do it "should run dscl with the supplied cmd /Path args" do shell_return = shellcmdresult.new("stdout", "err", 0) expect(provider).to receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return) expect(provider.run_dscl("cmd /Path args")).to eq("stdout") end it "returns an empty string from delete commands" do shell_return = shellcmdresult.new("out", "err", 23) expect(provider).to receive(:shell_out).with("dscl . -delete /Path args").and_return(shell_return) expect(provider.run_dscl("delete /Path args")).to eq("") end it "should raise an exception for any other command" do shell_return = shellcmdresult.new("out", "err", 23) expect(provider).to receive(:shell_out).with("dscl . -cmd /Path arguments").and_return(shell_return) expect { provider.run_dscl("cmd /Path arguments") }.to raise_error(Chef::Exceptions::DsclCommandFailed) end it "raises an exception when dscl reports 'no such key'" do shell_return = shellcmdresult.new("No such key: ", "err", 23) expect(provider).to receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return) expect { provider.run_dscl("cmd /Path args") }.to raise_error(Chef::Exceptions::DsclCommandFailed) end it "raises an exception when dscl reports 'eDSRecordNotFound'" do shell_return = shellcmdresult.new(" DS Error: -14136 (eDSRecordNotFound)", "err", -14136) expect(provider).to receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return) expect { provider.run_dscl("cmd /Path args") }.to raise_error(Chef::Exceptions::DsclCommandFailed) end end describe "get_free_uid" do before do expect(provider).to receive(:run_dscl).with("list /Users uid").and_return("\nwheel 200\nstaff 201\nbrahms 500\nchopin 501\n") end describe "when resource is configured as system" do before do new_resource.system(true) end it "should return the first unused uid number on or above 500" do expect(provider.get_free_uid).to eq(202) end end it "should return the first unused uid number on or above 200" do expect(provider.get_free_uid).to eq(502) end it "should raise an exception when the search limit is exhausted" do search_limit = 1 expect { provider.get_free_uid(search_limit) }.to raise_error(RuntimeError) end end describe "uid_used?" do it "should return false if not given any valid uid number" do expect(provider.uid_used?(nil)).to be_falsey end describe "when called with a user id" do before do expect(provider).to receive(:run_dscl).with("list /Users uid").and_return("\naj 500\n") end it "should return true for a used uid number" do expect(provider.uid_used?(500)).to be_truthy end it "should return false for an unused uid number" do expect(provider.uid_used?(501)).to be_falsey end end end describe "when determining the uid to set" do it "raises RequestedUIDUnavailable if the requested uid is already in use" do allow(provider).to receive(:uid_used?).and_return(true) expect(provider).to receive(:get_free_uid).and_return(501) expect { provider.dscl_set_uid }.to raise_error(Chef::Exceptions::RequestedUIDUnavailable) end it "finds a valid, unused uid when none is specified" do expect(provider).to receive(:run_dscl).with("list /Users uid").and_return("") expect(provider).to receive(:run_dscl).with("create /Users/toor UniqueID 501") expect(provider).to receive(:get_free_uid).and_return(501) provider.dscl_set_uid expect(new_resource.uid).to eq(501) end it "sets the uid specified in the resource" do new_resource.uid(1000) expect(provider).to receive(:run_dscl).with("create /Users/toor UniqueID 1000").and_return(true) expect(provider).to receive(:run_dscl).with("list /Users uid").and_return("") provider.dscl_set_uid end end describe "when modifying the home directory" do let(:current_resource) do new_resource.dup end before do new_resource.supports({ :manage_home => true }) new_resource.home("/Users/toor") provider.current_resource = current_resource end it "deletes the home directory when resource#home is nil" do new_resource.instance_variable_set(:@home, nil) expect(provider).to receive(:run_dscl).with("delete /Users/toor NFSHomeDirectory").and_return(true) provider.dscl_set_home end it "raises InvalidHomeDirectory when the resource's home directory doesn't look right" do new_resource.home("epic-fail") expect { provider.dscl_set_home }.to raise_error(Chef::Exceptions::InvalidHomeDirectory) end it "moves the users home to the new location if it exists and the target location is different" do new_resource.supports(:manage_home => true) current_home = CHEF_SPEC_DATA + "/old_home_dir" current_home_files = [current_home + "/my-dot-emacs", current_home + "/my-dot-vim"] current_resource.home(current_home) new_resource.gid(23) allow(::File).to receive(:exists?).with("/old/home/toor").and_return(true) allow(::File).to receive(:exists?).with("/Users/toor").and_return(true) expect(FileUtils).to receive(:mkdir_p).with("/Users/toor").and_return(true) expect(FileUtils).to receive(:rmdir).with(current_home) expect(::Dir).to receive(:glob).with("#{CHEF_SPEC_DATA}/old_home_dir/*", ::File::FNM_DOTMATCH).and_return(current_home_files) expect(FileUtils).to receive(:mv).with(current_home_files, "/Users/toor", :force => true) expect(FileUtils).to receive(:chown_R).with("toor", "23", "/Users/toor") expect(provider).to receive(:run_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'") provider.dscl_set_home end it "should raise an exception when the systems user template dir (skel) cannot be found" do allow(::File).to receive(:exists?).and_return(false, false, false) expect { provider.dscl_set_home }.to raise_error(Chef::Exceptions::User) end it "should run ditto to copy any missing files from skel to the new home dir" do expect(::File).to receive(:exists?).with("/System/Library/User\ Template/English.lproj").and_return(true) expect(FileUtils).to receive(:chown_R).with("toor", "", "/Users/toor") expect(provider).to receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'") provider.ditto_home end it "creates the user's NFSHomeDirectory and home directory" do expect(provider).to receive(:run_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'").and_return(true) expect(provider).to receive(:ditto_home) provider.dscl_set_home end end describe "resource_requirements" do let(:dscl_exists) { true } let(:plutil_exists) { true } before do allow(::File).to receive(:exists?).with("/usr/bin/dscl").and_return(dscl_exists) allow(::File).to receive(:exists?).with("/usr/bin/plutil").and_return(plutil_exists) end def run_requirements provider.define_resource_requirements provider.action = :create provider.process_resource_requirements end describe "when dscl doesn't exist" do let(:dscl_exists) { false } it "should raise an error" do expect { run_requirements }.to raise_error(Chef::Exceptions::User) end end describe "when plutil doesn't exist" do let(:plutil_exists) { false } it "should raise an error" do expect { run_requirements }.to raise_error(Chef::Exceptions::User) end end describe "when on Mac 10.6" do let(:mac_version) do "10.6.5" end it "should raise an error" do expect { run_requirements }.to raise_error(Chef::Exceptions::User) end end describe "when on Mac 10.7" do let(:mac_version) do "10.7.5" end describe "when password is SALTED-SHA512" do let(:password) { salted_sha512_password } it "should not raise an error" do expect { run_requirements }.not_to raise_error end end describe "when password is SALTED-SHA512-PBKDF2" do let(:password) { salted_sha512_pbkdf2_password } it "should raise an error" do expect { run_requirements }.to raise_error(Chef::Exceptions::User) end end end [ "10.9", "10.10"].each do |version| describe "when on Mac #{version}" do let(:mac_version) do "#{version}.2" end describe "when password is SALTED-SHA512" do let(:password) { salted_sha512_password } it "should raise an error" do expect { run_requirements }.to raise_error(Chef::Exceptions::User) end end describe "when password is SALTED-SHA512-PBKDF2" do let(:password) { salted_sha512_pbkdf2_password } describe "when salt and iteration is not set" do it "should raise an error" do expect { run_requirements }.to raise_error(Chef::Exceptions::User) end end describe "when salt and iteration is set" do let(:salt) { salted_sha512_pbkdf2_salt } let(:iterations) { salted_sha512_pbkdf2_iterations } it "should not raise an error" do expect { run_requirements }.not_to raise_error end end end end end end describe "load_current_resource" do # set this to any of the user plist files under spec/data let(:user_plist_file) { nil } before do expect(provider).to receive(:shell_out).with("dscacheutil '-flushcache'") expect(provider).to receive(:shell_out).with("plutil -convert xml1 -o - /var/db/dslocal/nodes/Default/users/toor.plist") do if user_plist_file.nil? shellcmdresult.new("Can not find the file", "Sorry!!", 1) else shellcmdresult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.plist.xml")), "", 0) end end if !user_plist_file.nil? expect(provider).to receive(:convert_binary_plist_to_xml).and_return(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.shadow.xml"))) end end describe "when user is not there" do it "shouldn't raise an error" do expect { provider.load_current_resource }.not_to raise_error end it "should set @user_exists" do provider.load_current_resource expect(provider.instance_variable_get(:@user_exists)).to be_falsey end it "should set username" do provider.load_current_resource expect(provider.current_resource.username).to eq("toor") end end describe "when user is there" do let(:password) { "something" } # Load password during load_current_resource describe "on 10.7" do let(:mac_version) do "10.7.5" end let(:user_plist_file) { "10.7" } it "collects the user data correctly" do provider.load_current_resource expect(provider.current_resource.comment).to eq("vagrant") expect(provider.current_resource.uid).to eq("501") expect(provider.current_resource.gid).to eq("80") expect(provider.current_resource.home).to eq("/Users/vagrant") expect(provider.current_resource.shell).to eq("/bin/bash") expect(provider.current_resource.password).to eq(vagrant_sha_512) end describe "when a plain password is set that is same" do let(:password) { "vagrant" } it "diverged_password? should report false" do provider.load_current_resource expect(provider.diverged_password?).to be_falsey end end describe "when a plain password is set that is different" do let(:password) { "not_vagrant" } it "diverged_password? should report true" do provider.load_current_resource expect(provider.diverged_password?).to be_truthy end end describe "when iterations change" do let(:password) { vagrant_sha_512 } let(:iterations) { 12345 } it "diverged_password? should report false" do provider.load_current_resource expect(provider.diverged_password?).to be_falsey end end describe "when shadow hash changes" do let(:password) { salted_sha512_password } it "diverged_password? should report true" do provider.load_current_resource expect(provider.diverged_password?).to be_truthy end end describe "when salt change" do let(:password) { vagrant_sha_512 } let(:salt) { "SOMETHINGRANDOM" } it "diverged_password? should report false" do provider.load_current_resource expect(provider.diverged_password?).to be_falsey end end end describe "on 10.8" do let(:mac_version) do "10.8.3" end let(:user_plist_file) { "10.8" } it "collects the user data correctly" do provider.load_current_resource expect(provider.current_resource.comment).to eq("vagrant") expect(provider.current_resource.uid).to eq("501") expect(provider.current_resource.gid).to eq("80") expect(provider.current_resource.home).to eq("/Users/vagrant") expect(provider.current_resource.shell).to eq("/bin/bash") expect(provider.current_resource.password).to eq("ea4c2d265d801ba0ec0dfccd\ 253dfc1de91cbe0806b4acc1ed7fe22aebcf6beb5344d0f442e590\ ffa04d679075da3afb119e41b72b5eaf08ee4aa54693722646d5\ 19ee04843deb8a3e977428d33f625e83887913e5c13b70035961\ 5e00ad7bc3e7a0c98afc3e19d1360272454f8d33a9214d2fbe8b\ e68d1f9821b26689312366") expect(provider.current_resource.salt).to eq("f994ef2f73b7c5594ebd1553300976b20733ce0e24d659783d87f3d81cbbb6a9") expect(provider.current_resource.iterations).to eq(39840) end end describe "on 10.7 upgraded to 10.8" do # In this scenario user password is still in 10.7 format let(:mac_version) do "10.8.3" end let(:user_plist_file) { "10.7-8" } it "collects the user data correctly" do provider.load_current_resource expect(provider.current_resource.comment).to eq("vagrant") expect(provider.current_resource.uid).to eq("501") expect(provider.current_resource.gid).to eq("80") expect(provider.current_resource.home).to eq("/Users/vagrant") expect(provider.current_resource.shell).to eq("/bin/bash") expect(provider.current_resource.password).to eq("6f75d7190441facc34291ebbea1fc756b242d4f\ e9bcff141bccb84f1979e27e539539aa31f9f7dcc92c0cea959\ ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30") end describe "when a plain text password is set" do it "reports password needs to be updated" do provider.load_current_resource expect(provider.diverged_password?).to be_truthy end end describe "when a salted-sha512-pbkdf2 shadow is set" do let(:password) { salted_sha512_pbkdf2_password } let(:salt) { salted_sha512_pbkdf2_salt } let(:iterations) { salted_sha512_pbkdf2_iterations } it "reports password needs to be updated" do provider.load_current_resource expect(provider.diverged_password?).to be_truthy end end end describe "on 10.9" do let(:mac_version) do "10.9.1" end let(:user_plist_file) { "10.9" } it "collects the user data correctly" do provider.load_current_resource expect(provider.current_resource.comment).to eq("vagrant") expect(provider.current_resource.uid).to eq("501") expect(provider.current_resource.gid).to eq("80") expect(provider.current_resource.home).to eq("/Users/vagrant") expect(provider.current_resource.shell).to eq("/bin/bash") expect(provider.current_resource.password).to eq(vagrant_sha_512_pbkdf2) expect(provider.current_resource.salt).to eq(vagrant_sha_512_pbkdf2_salt) expect(provider.current_resource.iterations).to eq(vagrant_sha_512_pbkdf2_iterations) end describe "when a plain password is set that is same" do let(:password) { "vagrant" } it "diverged_password? should report false" do provider.load_current_resource expect(provider.diverged_password?).to be_falsey end end describe "when a plain password is set that is different" do let(:password) { "not_vagrant" } it "diverged_password? should report true" do provider.load_current_resource expect(provider.diverged_password?).to be_truthy end end describe "when iterations change" do let(:password) { vagrant_sha_512_pbkdf2 } let(:salt) { vagrant_sha_512_pbkdf2_salt } let(:iterations) { 12345 } it "diverged_password? should report true" do provider.load_current_resource expect(provider.diverged_password?).to be_truthy end end describe "when shadow hash changes" do let(:password) { salted_sha512_pbkdf2_password } let(:salt) { vagrant_sha_512_pbkdf2_salt } let(:iterations) { vagrant_sha_512_pbkdf2_iterations } it "diverged_password? should report true" do provider.load_current_resource expect(provider.diverged_password?).to be_truthy end end describe "when salt change" do let(:password) { vagrant_sha_512_pbkdf2 } let(:salt) { salted_sha512_pbkdf2_salt } let(:iterations) { vagrant_sha_512_pbkdf2_iterations } it "diverged_password? should report true" do provider.load_current_resource expect(provider.diverged_password?).to be_truthy end end describe "when salt isn't found" do it "diverged_password? should report true" do provider.load_current_resource provider.current_resource.salt(nil) expect(provider.diverged_password?).to be_truthy end end end end end describe "salted_sha512_pbkdf2?" do it "should return true when the string is a salted_sha512_pbkdf2 hash" do expect(provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password)).to be_truthy end it "should return false otherwise" do expect(provider.salted_sha512_pbkdf2?(salted_sha512_password)).to be_falsey expect(provider.salted_sha512_pbkdf2?("any other string")).to be_falsey end end describe "salted_sha512?" do it "should return true when the string is a salted_sha512_pbkdf2 hash" do expect(provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password)).to be_truthy end it "should return false otherwise" do expect(provider.salted_sha512?(salted_sha512_pbkdf2_password)).to be_falsey expect(provider.salted_sha512?("any other string")).to be_falsey end end describe "prepare_password_shadow_info" do describe "when on Mac 10.7" do let(:mac_version) do "10.7.1" end describe "when the password is plain text" do let(:password) { "vagrant" } it "password_shadow_info should have salted-sha-512 format" do shadow_info = provider.prepare_password_shadow_info expect(shadow_info).to have_key("SALTED-SHA512") info = shadow_info["SALTED-SHA512"].string.unpack("H*").first expect(provider.salted_sha512?(info)).to be_truthy end end describe "when the password is salted-sha-512" do let(:password) { vagrant_sha_512 } it "password_shadow_info should have salted-sha-512 format" do shadow_info = provider.prepare_password_shadow_info expect(shadow_info).to have_key("SALTED-SHA512") info = shadow_info["SALTED-SHA512"].string.unpack("H*").first expect(provider.salted_sha512?(info)).to be_truthy expect(info).to eq(vagrant_sha_512) end end end ["10.8", "10.9", "10.10"].each do |version| describe "when on Mac #{version}" do let(:mac_version) do "#{version}.1" end describe "when the password is plain text" do let(:password) { "vagrant" } it "password_shadow_info should have salted-sha-512 format" do shadow_info = provider.prepare_password_shadow_info expect(shadow_info).to have_key("SALTED-SHA512-PBKDF2") expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("entropy") expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("salt") expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("iterations") info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack("H*").first expect(provider.salted_sha512_pbkdf2?(info)).to be_truthy end end describe "when the password is salted-sha-512" do let(:password) { vagrant_sha_512_pbkdf2 } let(:iterations) { vagrant_sha_512_pbkdf2_iterations } let(:salt) { vagrant_sha_512_pbkdf2_salt } it "password_shadow_info should have salted-sha-512 format" do shadow_info = provider.prepare_password_shadow_info expect(shadow_info).to have_key("SALTED-SHA512-PBKDF2") expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("entropy") expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("salt") expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("iterations") info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack("H*").first expect(provider.salted_sha512_pbkdf2?(info)).to be_truthy expect(info).to eq(vagrant_sha_512_pbkdf2) end end end end end describe "set_password" do before do new_resource.password("something") end it "should sleep and flush the dscl cache before saving the password" do expect(provider).to receive(:prepare_password_shadow_info).and_return({}) mock_shellout = double("Mock::Shellout") allow(mock_shellout).to receive(:run_command) expect(Mixlib::ShellOut).to receive(:new).and_return(mock_shellout) expect(provider).to receive(:read_user_info) expect(provider).to receive(:dscl_set) expect(provider).to receive(:sleep).with(3) expect(provider).to receive(:save_user_info) provider.set_password end end describe "when the user does not yet exist and chef is creating it" do context "with a numeric gid" do before do new_resource.comment "#mockssuck" new_resource.gid 1001 end it "creates the user, comment field, sets uid, gid, configures the home directory, sets the shell, and sets the password" do expect(provider).to receive :dscl_create_user expect(provider).to receive :dscl_create_comment expect(provider).to receive :dscl_set_uid expect(provider).to receive :dscl_set_gid expect(provider).to receive :dscl_set_home expect(provider).to receive :dscl_set_shell expect(provider).to receive :set_password provider.create_user end it "creates the user and sets the comment field" do expect(provider).to receive(:run_dscl).with("create /Users/toor").and_return(true) provider.dscl_create_user end it "sets the comment field" do expect(provider).to receive(:run_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true) provider.dscl_create_comment end it "sets the comment field to username" do new_resource.comment nil expect(provider).to receive(:run_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true) provider.dscl_create_comment expect(new_resource.comment).to eq("#mockssuck") end it "should run run_dscl with create /Users/user PrimaryGroupID to set the users primary group" do expect(provider).to receive(:run_dscl).with("create /Users/toor PrimaryGroupID '1001'").and_return(true) provider.dscl_set_gid end it "should run run_dscl with create /Users/user UserShell to set the users login shell" do expect(provider).to receive(:run_dscl).with("create /Users/toor UserShell '/usr/bin/false'").and_return(true) provider.dscl_set_shell end end context "with a non-numeric gid" do before do new_resource.comment "#mockssuck" new_resource.gid "newgroup" end it "should map the group name to a numeric ID when the group exists" do expect(provider).to receive(:run_dscl).with("read /Groups/newgroup PrimaryGroupID").ordered.and_return("PrimaryGroupID: 1001\n") expect(provider).to receive(:run_dscl).with("create /Users/toor PrimaryGroupID '1001'").ordered.and_return(true) provider.dscl_set_gid end it "should raise an exception when the group does not exist" do shell_return = shellcmdresult.new(" DS Error: -14136 (eDSRecordNotFound)", "err", -14136) expect(provider).to receive(:shell_out).with("dscl . -read /Groups/newgroup PrimaryGroupID").and_return(shell_return) expect { provider.dscl_set_gid }.to raise_error(Chef::Exceptions::GroupIDNotFound) end end it "should set group ID to 20 if it's not specified" do new_resource.gid nil expect(provider).to receive(:run_dscl).with("create /Users/toor PrimaryGroupID '20'").ordered.and_return(true) provider.dscl_set_gid expect(new_resource.gid).to eq(20) end end describe "when the user exists and chef is managing it" do before do current_resource = new_resource.dup provider.current_resource = current_resource # These are all different from current_resource new_resource.username "mud" new_resource.uid 2342 new_resource.gid 2342 new_resource.home "/Users/death" new_resource.password "goaway" end it "sets the user, comment field, uid, gid, moves the home directory, sets the shell, and sets the password" do expect(provider).to receive :dscl_create_user expect(provider).to receive :dscl_create_comment expect(provider).to receive :dscl_set_uid expect(provider).to receive :dscl_set_gid expect(provider).to receive :dscl_set_home expect(provider).to receive :dscl_set_shell expect(provider).to receive :set_password provider.create_user end end describe "when changing the gid" do before do current_resource = new_resource.dup provider.current_resource = current_resource # This is different from current_resource new_resource.gid 2342 end it "sets the gid" do expect(provider).to receive :dscl_set_gid provider.manage_user end end describe "when the user exists" do before do expect(provider).to receive(:shell_out).with("dscacheutil '-flushcache'") expect(provider).to receive(:shell_out).with("plutil -convert xml1 -o - /var/db/dslocal/nodes/Default/users/toor.plist") do shellcmdresult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/10.9.plist.xml")), "", 0) end provider.load_current_resource end describe "when Chef is removing the user" do it "removes the user from the groups and deletes home directory when the resource is configured to manage home" do new_resource.supports({ :manage_home => true }) expect(provider).to receive(:run_dscl).with("list /Groups").and_return("my_group\nyour_group\nreal_group\n") expect(provider).to receive(:run_dscl).with("read /Groups/my_group").and_raise(Chef::Exceptions::DsclCommandFailed) # Empty group expect(provider).to receive(:run_dscl).with("read /Groups/your_group").and_return("GroupMembership: not_you") expect(provider).to receive(:run_dscl).with("read /Groups/real_group").and_return("GroupMembership: toor") expect(provider).to receive(:run_dscl).with("delete /Groups/real_group GroupMembership 'toor'") expect(provider).to receive(:run_dscl).with("delete /Users/toor") expect(FileUtils).to receive(:rm_rf).with("/Users/vagrant") provider.remove_user end end describe "when user is not locked" do it "determines the user as not locked" do expect(provider).not_to be_locked end end describe "when user is locked" do before do auth_authority = provider.instance_variable_get(:@authentication_authority) provider.instance_variable_set(:@authentication_authority, auth_authority + ";DisabledUser;") end it "determines the user as not locked" do expect(provider).to be_locked end it "can unlock the user" do expect(provider).to receive(:run_dscl).with("create /Users/toor AuthenticationAuthority ';ShadowHash;HASHLIST:'") provider.unlock_user end end end describe "when locking the user" do it "should run run_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do expect(provider).to receive(:run_dscl).with("append /Users/toor AuthenticationAuthority ';DisabledUser;'") provider.lock_user end end end chef-12.14.60/spec/unit/provider/user/linux_spec.rb000066400000000000000000000044331276456504500221130ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # 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" require "chef/provider/user/useradd" describe Chef::Provider::User::Linux do subject(:provider) do p = described_class.new(@new_resource, @run_context) p.current_resource = @current_resource p end supported_useradd_options = { "comment" => "-c", "gid" => "-g", "uid" => "-u", "shell" => "-s", "password" => "-p", } include_examples "a useradd-based user provider", supported_useradd_options describe "manage_home behavior" do before(:each) do @new_resource = Chef::Resource::User::LinuxUser.new("adam", @run_context) @current_resource = Chef::Resource::User::LinuxUser.new("adam", @run_context) end it "sets supports manage_home to true" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect( @new_resource.supports[:manage_home] ).to be true end it "sets supports non-unique to true" do Chef::Config[:treat_deprecation_warnings_as_errors] = false expect( @new_resource.supports[:non_unique] ).to be true end it "defaults manage_home to true" do expect( @new_resource.manage_home ).to be false end it "by default manage_home is false and we use -M" do expect( provider.useradd_options ).to eql(["-M"]) end it "setting manage_home to false includes -M" do @new_resource.manage_home false expect( provider.useradd_options ).to eql(["-M"]) end it "setting manage_home to true includes -m" do @new_resource.manage_home true expect( provider.useradd_options ).to eql(["-m"]) end end end chef-12.14.60/spec/unit/provider/user/pw_spec.rb000066400000000000000000000216031276456504500214000ustar00rootroot00000000000000# # Author:: Stephen Haynes () # 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::User::Pw do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::User::PwUser.new("adam") @new_resource.comment "Adam Jacob" @new_resource.uid 1000 @new_resource.gid 1000 @new_resource.home "/home/adam" @new_resource.shell "/usr/bin/zsh" @new_resource.password "abracadabra" @new_resource.supports :manage_home => true @current_resource = Chef::Resource::User::PwUser.new("adam") @current_resource.comment "Adam Jacob" @current_resource.uid 1000 @current_resource.gid 1000 @current_resource.home "/home/adam" @current_resource.shell "/usr/bin/zsh" @current_resource.password "abracadabra" @provider = Chef::Provider::User::Pw.new(@new_resource, @run_context) @provider.current_resource = @current_resource end describe "setting options to the pw command" do field_list = { "comment" => "-c", "home" => "-d", "gid" => "-g", "uid" => "-u", "shell" => "-s", } field_list.each do |attribute, option| it "should check for differences in #{attribute} between the new and current resources" do expect(@current_resource).to receive(attribute) expect(@new_resource).to receive(attribute) @provider.set_options end it "should set the option for #{attribute} if the new resources #{attribute} is not null" do allow(@new_resource).to receive(attribute).and_return("hola") expect(@provider.set_options).to eql(" #{@new_resource.username} #{option} '#{@new_resource.send(attribute)}' -m") end it "should set the option for #{attribute} if the new resources #{attribute} is not null, without homedir management" do allow(@new_resource).to receive(:supports).and_return({ :manage_home => false }) allow(@new_resource).to receive(attribute).and_return("hola") expect(@provider.set_options).to eql(" #{@new_resource.username} #{option} '#{@new_resource.send(attribute)}'") end end it "should combine all the possible options" do match_string = " adam" field_list.sort { |a, b| a[0] <=> b[0] }.each do |attribute, option| allow(@new_resource).to receive(attribute).and_return("hola") match_string << " #{option} 'hola'" end match_string << " -m" expect(@provider.set_options).to eql(match_string) end end describe "create_user" do before(:each) do allow(@provider).to receive(:run_command).and_return(true) allow(@provider).to receive(:modify_password).and_return(true) end it "should run pw useradd with the return of set_options" do expect(@provider).to receive(:run_command).with({ :command => "pw useradd adam -m" }).and_return(true) @provider.create_user end it "should modify the password" do expect(@provider).to receive(:modify_password).and_return(true) @provider.create_user end end describe "manage_user" do before(:each) do allow(@provider).to receive(:run_command).and_return(true) allow(@provider).to receive(:modify_password).and_return(true) end it "should run pw usermod with the return of set_options" do expect(@provider).to receive(:run_command).with({ :command => "pw usermod adam -m" }).and_return(true) @provider.manage_user end it "should modify the password" do expect(@provider).to receive(:modify_password).and_return(true) @provider.create_user end end describe "remove_user" do it "should run pw userdel with the new resources user name" do @new_resource.supports :manage_home => false expect(@provider).to receive(:run_command).with({ :command => "pw userdel #{@new_resource.username}" }).and_return(true) @provider.remove_user end it "should run pw userdel with the new resources user name and -r if manage_home is true" do expect(@provider).to receive(:run_command).with({ :command => "pw userdel #{@new_resource.username} -r" }).and_return(true) @provider.remove_user end end describe "determining if the user is locked" do it "should return true if user is locked" do allow(@current_resource).to receive(:password).and_return("*LOCKED*abracadabra") expect(@provider.check_lock).to eql(true) end it "should return false if user is not locked" do allow(@current_resource).to receive(:password).and_return("abracadabra") expect(@provider.check_lock).to eql(false) end end describe "when locking the user" do it "should run pw lock with the new resources username" do expect(@provider).to receive(:run_command).with({ :command => "pw lock #{@new_resource.username}" }) @provider.lock_user end end describe "when unlocking the user" do it "should run pw unlock with the new resources username" do expect(@provider).to receive(:run_command).with({ :command => "pw unlock #{@new_resource.username}" }) @provider.unlock_user end end describe "when modifying the password" do before(:each) do @status = double("Status", :exitstatus => 0) allow(@provider).to receive(:popen4).and_return(@status) @pid, @stdin, @stdout, @stderr = nil, nil, nil, nil end describe "and the new password has not been specified" do before(:each) do allow(@new_resource).to receive(:password).and_return(nil) end it "logs an appropriate message" do @provider.modify_password end end describe "and the new password has been specified" do before(:each) do allow(@new_resource).to receive(:password).and_return("abracadabra") end it "should check for differences in password between the new and current resources" do expect(@current_resource).to receive(:password) expect(@new_resource).to receive(:password) @provider.modify_password end end describe "and the passwords are identical" do before(:each) do allow(@new_resource).to receive(:password).and_return("abracadabra") allow(@current_resource).to receive(:password).and_return("abracadabra") end it "logs an appropriate message" do @provider.modify_password end end describe "and the passwords are different" do before(:each) do allow(@new_resource).to receive(:password).and_return("abracadabra") allow(@current_resource).to receive(:password).and_return("sesame") end it "should log an appropriate message" do @provider.modify_password end it "should run pw usermod with the username and the option -H 0" do expect(@provider).to receive(:popen4).with("pw usermod adam -H 0", :waitlast => true).and_return(@status) @provider.modify_password end it "should send the new password to the stdin of pw usermod" do @stdin = StringIO.new allow(@provider).to receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @provider.modify_password expect(@stdin.string).to eq("abracadabra\n") end it "should raise an exception if pw usermod fails" do expect(@status).to receive(:exitstatus).and_return(1) expect { @provider.modify_password }.to raise_error(Chef::Exceptions::User) end it "should not raise an exception if pw usermod succeeds" do expect(@status).to receive(:exitstatus).and_return(0) expect { @provider.modify_password }.not_to raise_error end end end describe "when loading the current state" do before do @provider.new_resource = Chef::Resource::User::PwUser.new("adam") end it "should raise an error if the required binary /usr/sbin/pw doesn't exist" do expect(File).to receive(:exists?).with("/usr/sbin/pw").and_return(false) expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::User) end it "shouldn't raise an error if /usr/sbin/pw exists" do allow(File).to receive(:exists?).and_return(true) expect { @provider.load_current_resource }.not_to raise_error end end end chef-12.14.60/spec/unit/provider/user/solaris_spec.rb000066400000000000000000000116341276456504500224310ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # Author:: Dave Eddy () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Copyright:: Copyright 2015-2016, Dave Eddy # # 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 "mixlib/shellout" require "spec_helper" describe Chef::Provider::User::Solaris do let(:shellcmdresult) do Struct.new(:stdout, :stderr, :exitstatus) end subject(:provider) do p = described_class.new(@new_resource, @run_context) p.current_resource = @current_resource # Prevent the useradd-based provider tests from trying to write /etc/shadow allow(p).to receive(:write_shadow_file) p end describe "when we want to set a password" do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::User::SolarisUser.new("adam", @run_context) @current_resource = Chef::Resource::User::SolarisUser.new("adam", @run_context) @new_resource.password "hocus-pocus" end it "should use its own shadow file writer to set the password" do expect(provider).to receive(:write_shadow_file) allow(provider).to receive(:shell_out!).and_return(true) provider.manage_user end it "should write out a modified version of the password file" do # Let this test run #write_shadow_file allow(provider).to receive(:write_shadow_file).and_call_original password_file = Tempfile.new("shadow") password_file.puts "adam:existingpassword:15441::::::" password_file.close provider.password_file = password_file.path allow(provider).to receive(:shell_out!).and_return(true) # may not be able to write to /etc for tests... temp_file = Tempfile.new("shadow") allow(Tempfile).to receive(:new).with("shadow", "/etc").and_return(temp_file) @new_resource.password "verysecurepassword" provider.manage_user expect(::File.open(password_file.path, "r").read).to match(/adam:verysecurepassword:/) password_file.unlink end end describe "when managing user locked status" do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::User::SolarisUser.new("dave") @current_resource = @new_resource.dup @provider = Chef::Provider::User::Solaris.new(@new_resource, @run_context) @provider.current_resource = @current_resource end describe "when determining if the user is locked" do # locked shadow lines [ "dave:LK:::::::", "dave:*LK*:::::::", "dave:*LK*foobar:::::::", "dave:*LK*bahamas10:::::::", "dave:*LK*L....:::::::", ].each do |shadow| it "should return true if user is locked with #{shadow}" do shell_return = shellcmdresult.new(shadow + "\n", "", 0) expect(provider).to receive(:shell_out!).with("getent", "shadow", @new_resource.username).and_return(shell_return) expect(provider.check_lock).to eql(true) end end # unlocked shadow lines [ "dave:NP:::::::", "dave:*NP*:::::::", "dave:foobar:::::::", "dave:bahamas10:::::::", "dave:L...:::::::", ].each do |shadow| it "should return false if user is unlocked with #{shadow}" do shell_return = shellcmdresult.new(shadow + "\n", "", 0) expect(provider).to receive(:shell_out!).with("getent", "shadow", @new_resource.username).and_return(shell_return) expect(provider.check_lock).to eql(false) end end end describe "when locking the user" do it "should run passwd -l with the new resources username" do shell_return = shellcmdresult.new("", "", 0) expect(provider).to receive(:shell_out!).with("passwd", "-l", @new_resource.username).and_return(shell_return) provider.lock_user end end describe "when unlocking the user" do it "should run passwd -u with the new resources username" do shell_return = shellcmdresult.new("", "", 0) expect(provider).to receive(:shell_out!).with("passwd", "-u", @new_resource.username).and_return(shell_return) provider.unlock_user end end end end chef-12.14.60/spec/unit/provider/user/windows_spec.rb000066400000000000000000000136011276456504500224430ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" class Chef class Util class Windows class NetUser end end end end describe Chef::Provider::User::Windows do before(:each) do @node = Chef::Node.new @new_resource = Chef::Resource::User::WindowsUser.new("monkey") @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @current_resource = Chef::Resource::User::WindowsUser.new("monkey") @net_user = double("Chef::Util::Windows::NetUser") allow(Chef::Util::Windows::NetUser).to receive(:new).and_return(@net_user) @provider = Chef::Provider::User::Windows.new(@new_resource, @run_context) @provider.current_resource = @current_resource end it "creates a net_user object with the provided username" do @new_resource.username "not-monkey" expect(Chef::Util::Windows::NetUser).to receive(:new).with("not-monkey") @provider = Chef::Provider::User::Windows.new(@new_resource, @run_context) end describe "when comparing the user's current attributes to the desired attributes" do before do @new_resource.comment "Adam Jacob" @new_resource.uid 1000 @new_resource.gid 1000 @new_resource.home "/home/adam" @new_resource.shell "/usr/bin/zsh" @new_resource.password "abracadabra" @provider.current_resource = @new_resource.clone end describe "and the attributes match" do it "doesn't set the comment field to be updated" do expect(@provider.set_options).not_to have_key(:full_name) end it "doesn't set the home directory to be updated" do expect(@provider.set_options).not_to have_key(:home_dir) end it "doesn't set the group id to be updated" do expect(@provider.set_options).not_to have_key(:primary_group_id) end it "doesn't set the user id to be updated" do expect(@provider.set_options).not_to have_key(:user_id) end it "doesn't set the shell to be updated" do expect(@provider.set_options).not_to have_key(:script_path) end it "doesn't set the password to be updated" do expect(@provider.set_options).not_to have_key(:password) end end describe "and the attributes do not match" do before do @current_resource = Chef::Resource::User::WindowsUser.new("adam") @current_resource.comment "Adam Jacob-foo" @current_resource.uid 1111 @current_resource.gid 1111 @current_resource.home "/home/adam-foo" @current_resource.shell "/usr/bin/tcsh" @current_resource.password "foobarbaz" @provider.current_resource = @current_resource end it "marks the full_name field to be updated" do expect(@provider.set_options[:full_name]).to eq("Adam Jacob") end it "marks the home_dir attribute to be updated" do expect(@provider.set_options[:home_dir]).to eq("/home/adam") end it "ignores the primary_group_id attribute" do expect(@provider.set_options[:primary_group_id]).to eq(nil) end it "marks the user_id attribute to be updated" do expect(@provider.set_options[:user_id]).to eq(1000) end it "marks the script_path attribute to be updated" do expect(@provider.set_options[:script_path]).to eq("/usr/bin/zsh") end it "marks the password attribute to be updated" do expect(@provider.set_options[:password]).to eq("abracadabra") end end end describe "when creating the user" do it "should call @net_user.add with the return of set_options" do allow(@provider).to receive(:set_options).and_return(:name => "monkey") expect(@net_user).to receive(:add).with(:name => "monkey") @provider.create_user end end describe "manage_user" do before(:each) do allow(@provider).to receive(:set_options).and_return(:name => "monkey") end it "should call @net_user.update with the return of set_options" do expect(@net_user).to receive(:update).with(:name => "monkey") @provider.manage_user end end describe "when removing the user" do it "should call @net_user.delete" do expect(@net_user).to receive(:delete) @provider.remove_user end end describe "when checking if the user is locked" do before(:each) do @current_resource.password "abracadabra" end it "should return true if user is locked" do allow(@net_user).to receive(:check_enabled).and_return(true) expect(@provider.check_lock).to eql(true) end it "should return false if user is not locked" do allow(@net_user).to receive(:check_enabled).and_return(false) expect(@provider.check_lock).to eql(false) end end describe "locking the user" do it "should call @net_user.disable_account" do allow(@net_user).to receive(:check_enabled).and_return(true) expect(@net_user).to receive(:disable_account) @provider.lock_user end end describe "unlocking the user" do it "should call @net_user.enable_account" do allow(@net_user).to receive(:check_enabled).and_return(false) expect(@net_user).to receive(:enable_account) @provider.unlock_user end end end chef-12.14.60/spec/unit/provider/user_spec.rb000066400000000000000000000414041276456504500207530ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" EtcPwnamIsh = Struct.new(:name, :passwd, :uid, :gid, :gecos, :dir, :shell, :change, :uclass, :expire) EtcGrnamIsh = Struct.new(:name, :passwd, :gid, :mem) describe Chef::Provider::User do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::User.new("adam") @new_resource.comment "Adam Jacob" @new_resource.uid 1000 @new_resource.gid 1000 @new_resource.home "/home/adam" @new_resource.shell "/usr/bin/zsh" @current_resource = Chef::Resource::User.new("adam") @current_resource.comment "Adam Jacob" @current_resource.uid 1000 @current_resource.gid 1000 @current_resource.home "/home/adam" @current_resource.shell "/usr/bin/zsh" @provider = Chef::Provider::User.new(@new_resource, @run_context) @provider.current_resource = @current_resource end describe "when first created" do it "assume the user exists by default" do expect(@provider.user_exists).to eql(true) end it "does not know the locked state" do expect(@provider.locked).to eql(nil) end end describe "executing load_current_resource" do before(:each) do @node = Chef::Node.new #@new_resource = double("Chef::Resource::User", # :null_object => true, # :username => "adam", # :comment => "Adam Jacob", # :uid => 1000, # :gid => 1000, # :home => "/home/adam", # :shell => "/usr/bin/zsh", # :password => nil, # :updated => nil #) allow(Chef::Resource::User).to receive(:new).and_return(@current_resource) @pw_user = EtcPwnamIsh.new @pw_user.name = "adam" @pw_user.gid = 1000 @pw_user.uid = 1000 @pw_user.gecos = "Adam Jacob" @pw_user.dir = "/home/adam" @pw_user.shell = "/usr/bin/zsh" @pw_user.passwd = "*" allow(Etc).to receive(:getpwnam).and_return(@pw_user) end it "should create a current resource with the same name as the new resource" do @provider.load_current_resource expect(@provider.current_resource.name).to eq("adam") end it "should set the username of the current resource to the username of the new resource" do @provider.load_current_resource expect(@current_resource.username).to eq(@new_resource.username) end it "should change the encoding of gecos to the encoding of the new resource" do @pw_user.gecos.force_encoding("ASCII-8BIT") @provider.load_current_resource expect(@provider.current_resource.comment.encoding).to eq(@new_resource.comment.encoding) end it "should look up the user in /etc/passwd with getpwnam" do expect(Etc).to receive(:getpwnam).with(@new_resource.username).and_return(@pw_user) @provider.load_current_resource end it "should set user_exists to false if the user is not found with getpwnam" do expect(Etc).to receive(:getpwnam).and_raise(ArgumentError) @provider.load_current_resource expect(@provider.user_exists).to eql(false) end # The mapping between the Chef::Resource::User and Getpwnam struct user_attrib_map = { :uid => :uid, :gid => :gid, :comment => :gecos, :home => :dir, :shell => :shell, } user_attrib_map.each do |user_attrib, getpwnam_attrib| it "should set the current resources #{user_attrib} based on getpwnam #{getpwnam_attrib}" do expect(@current_resource).to receive(user_attrib).with(@pw_user.send(getpwnam_attrib)) @provider.load_current_resource end end it "should attempt to convert the group gid if one has been supplied" do expect(@provider).to receive(:convert_group_name) @provider.load_current_resource end it "shouldn't try and convert the group gid if none has been supplied" do allow(@new_resource).to receive(:gid).and_return(nil) expect(@provider).not_to receive(:convert_group_name) @provider.load_current_resource end it "should return the current resource" do expect(@provider.load_current_resource).to eql(@current_resource) end describe "and running assertions" do def self.shadow_lib_unavail? begin require "rubygems" require "shadow" rescue LoadError skip "ruby-shadow gem not installed for dynamic load test" true else false end end before (:each) do user = @pw_user.dup user.name = "root" user.passwd = "x" @new_resource.password "some new password" allow(Etc).to receive(:getpwnam).and_return(user) end unless shadow_lib_unavail? context "and we have the ruby-shadow gem" do skip "and we are not root (rerun this again as root)", :requires_unprivileged_user => true context "and we are root", :requires_root => true do it "should pass assertions when ruby-shadow can be loaded" do @provider.action = "create" original_method = @provider.method(:require) expect(@provider).to receive(:require) { |*args| original_method.call(*args) } passwd_info = Struct::PasswdEntry.new(:sp_namp => "adm ", :sp_pwdp => "$1$T0N0Q.lc$nyG6pFI3Dpqa5cxUz/57j0", :sp_lstchg => 14861, :sp_min => 0, :sp_max => 99999, :sp_warn => 7, :sp_inact => -1, :sp_expire => -1, :sp_flag => -1) expect(Shadow::Passwd).to receive(:getspnam).with("adam").and_return(passwd_info) @provider.load_current_resource @provider.define_resource_requirements @provider.process_resource_requirements end end end end it "should fail assertions when ruby-shadow cannot be loaded" do expect(@provider).to receive(:require).with("shadow") { raise LoadError } @provider.load_current_resource @provider.define_resource_requirements expect { @provider.process_resource_requirements }.to raise_error Chef::Exceptions::MissingLibrary end end end describe "compare_user" do let(:mapping) do { "username" => %w{adam Adam}, "comment" => ["Adam Jacob", "adam jacob"], "uid" => [1000, 1001], "gid" => [1000, 1001], "home" => ["/home/adam", "/Users/adam"], "shell" => ["/usr/bin/zsh", "/bin/bash"], "password" => %w{abcd 12345}, } end %w{uid gid comment home shell password}.each do |attribute| it "should return true if #{attribute} doesn't match" do @new_resource.send(attribute, mapping[attribute][0]) @current_resource.send(attribute, mapping[attribute][1]) expect(@provider.compare_user).to eql(true) end end %w{uid gid}.each do |attribute| it "should return false if string #{attribute} matches fixnum" do @new_resource.send(attribute, "100") @current_resource.send(attribute, 100) expect(@provider.compare_user).to eql(false) end end it "should return false if the objects are identical" do expect(@provider.compare_user).to eql(false) end end describe "action_create" do before(:each) do allow(@provider).to receive(:load_current_resource) # @current_resource = double("Chef::Resource::User", # :null_object => true, # :username => "adam", # :comment => "Adam Jacob", # :uid => 1000, # :gid => 1000, # :home => "/home/adam", # :shell => "/usr/bin/zsh", # :password => nil, # :updated => nil # ) # @provider = Chef::Provider::User.new(@node, @new_resource) # @provider.current_resource = @current_resource # @provider.user_exists = false # @provider.stub(:create_user).and_return(true) # @provider.stub(:manage_user).and_return(true) end it "should call create_user if the user does not exist" do @provider.user_exists = false expect(@provider).to receive(:create_user).and_return(true) @provider.action_create @provider.set_updated_status expect(@new_resource).to be_updated end it "should call manage_user if the user exists and has mismatched attributes" do @provider.user_exists = true allow(@provider).to receive(:compare_user).and_return(true) expect(@provider).to receive(:manage_user).and_return(true) @provider.action_create end it "should set the new_resources updated flag when it creates the user if we call manage_user" do @provider.user_exists = true allow(@provider).to receive(:compare_user).and_return(true) allow(@provider).to receive(:manage_user).and_return(true) @provider.action_create @provider.set_updated_status expect(@new_resource).to be_updated end end describe "action_remove" do before(:each) do allow(@provider).to receive(:load_current_resource) end it "should not call remove_user if the user does not exist" do @provider.user_exists = false expect(@provider).not_to receive(:remove_user) @provider.action_remove end it "should call remove_user if the user exists" do @provider.user_exists = true expect(@provider).to receive(:remove_user) @provider.action_remove end it "should set the new_resources updated flag to true if the user is removed" do @provider.user_exists = true expect(@provider).to receive(:remove_user) @provider.action_remove @provider.set_updated_status expect(@new_resource).to be_updated end end describe "action_manage" do before(:each) do allow(@provider).to receive(:load_current_resource) # @node = Chef::Node.new # @new_resource = double("Chef::Resource::User", # :null_object => true # ) # @current_resource = double("Chef::Resource::User", # :null_object => true # ) # @provider = Chef::Provider::User.new(@node, @new_resource) # @provider.current_resource = @current_resource # @provider.user_exists = true # @provider.stub(:manage_user).and_return(true) end it "should run manage_user if the user exists and has mismatched attributes" do expect(@provider).to receive(:compare_user).and_return(true) expect(@provider).to receive(:manage_user).and_return(true) @provider.action_manage end it "should set the new resources updated flag to true if manage_user is called" do allow(@provider).to receive(:compare_user).and_return(true) allow(@provider).to receive(:manage_user).and_return(true) @provider.action_manage @provider.set_updated_status expect(@new_resource).to be_updated end it "should not run manage_user if the user does not exist" do @provider.user_exists = false expect(@provider).not_to receive(:manage_user) @provider.action_manage end it "should not run manage_user if the user exists but has no differing attributes" do expect(@provider).to receive(:compare_user).and_return(false) expect(@provider).not_to receive(:manage_user) @provider.action_manage end end describe "action_modify" do before(:each) do allow(@provider).to receive(:load_current_resource) # @node = Chef::Node.new # @new_resource = double("Chef::Resource::User", # :null_object => true # ) # @current_resource = double("Chef::Resource::User", # :null_object => true # ) # @provider = Chef::Provider::User.new(@node, @new_resource) # @provider.current_resource = @current_resource # @provider.user_exists = true # @provider.stub(:manage_user).and_return(true) end it "should run manage_user if the user exists and has mismatched attributes" do expect(@provider).to receive(:compare_user).and_return(true) expect(@provider).to receive(:manage_user).and_return(true) @provider.action_modify end it "should set the new resources updated flag to true if manage_user is called" do allow(@provider).to receive(:compare_user).and_return(true) allow(@provider).to receive(:manage_user).and_return(true) @provider.action_modify @provider.set_updated_status expect(@new_resource).to be_updated end it "should not run manage_user if the user exists but has no differing attributes" do expect(@provider).to receive(:compare_user).and_return(false) expect(@provider).not_to receive(:manage_user) @provider.action_modify end it "should raise a Chef::Exceptions::User if the user doesn't exist" do @provider.user_exists = false expect { @provider.action = :modify; @provider.run_action }.to raise_error(Chef::Exceptions::User) end end describe "action_lock" do before(:each) do allow(@provider).to receive(:load_current_resource) end it "should lock the user if it exists and is unlocked" do allow(@provider).to receive(:check_lock).and_return(false) expect(@provider).to receive(:lock_user).and_return(true) @provider.action_lock end it "should set the new resources updated flag to true if lock_user is called" do allow(@provider).to receive(:check_lock).and_return(false) expect(@provider).to receive(:lock_user) @provider.action_lock @provider.set_updated_status expect(@new_resource).to be_updated end it "should raise a Chef::Exceptions::User if we try and lock a user that does not exist" do @provider.user_exists = false @provider.action = :lock expect { @provider.run_action }.to raise_error(Chef::Exceptions::User) end end describe "action_unlock" do before(:each) do allow(@provider).to receive(:load_current_resource) # @node = Chef::Node.new # @new_resource = double("Chef::Resource::User", # :null_object => true # ) # @current_resource = double("Chef::Resource::User", # :null_object => true # ) # @provider = Chef::Provider::User.new(@node, @new_resource) # @provider.current_resource = @current_resource # @provider.user_exists = true # @provider.stub(:check_lock).and_return(true) # @provider.stub(:unlock_user).and_return(true) end it "should unlock the user if it exists and is locked" do allow(@provider).to receive(:check_lock).and_return(true) expect(@provider).to receive(:unlock_user).and_return(true) @provider.action_unlock @provider.set_updated_status expect(@new_resource).to be_updated end it "should raise a Chef::Exceptions::User if we try and unlock a user that does not exist" do @provider.user_exists = false @provider.action = :unlock expect { @provider.run_action }.to raise_error(Chef::Exceptions::User) end end describe "convert_group_name" do before do @new_resource.gid("999") @group = EtcGrnamIsh.new("wheel", "*", 999, []) end it "should lookup the group name locally" do expect(Etc).to receive(:getgrnam).with("999").and_return(@group) expect(@provider.convert_group_name).to eq(999) end it "should raise an error if we can't translate the group name during resource assertions" do expect(Etc).to receive(:getgrnam).and_raise(ArgumentError) @provider.action = :create @provider.define_resource_requirements @provider.convert_group_name expect { @provider.process_resource_requirements }.to raise_error(Chef::Exceptions::User) end it "does not raise an error if we can't translate the group name during resource assertions if we are removing the user" do expect(Etc).to receive(:getgrnam).and_raise(ArgumentError) @provider.action = :remove @provider.define_resource_requirements @provider.convert_group_name expect { @provider.process_resource_requirements }.not_to raise_error end it "should set the new resources gid to the integerized version if available" do expect(Etc).to receive(:getgrnam).with("999").and_return(@group) @provider.convert_group_name expect(@new_resource.gid).to eq(999) end end end chef-12.14.60/spec/unit/provider/whyrun_safe_ruby_block_spec.rb000066400000000000000000000032071276456504500245410ustar00rootroot00000000000000# # Author:: Phil Dibowitz () # Copyright:: Copyright 2013-2016, Facebook # 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::WhyrunSafeRubyBlock, "initialize" do before(:each) do $evil_global_evil_laugh = :wahwah @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::WhyrunSafeRubyBlock.new("bloc party") @new_resource.block { $evil_global_evil_laugh = :mwahahaha } @provider = Chef::Provider::WhyrunSafeRubyBlock.new(@new_resource, @run_context) end it "should call the block and flag the resource as updated" do @provider.run_action(:run) expect($evil_global_evil_laugh).to eq(:mwahahaha) expect(@new_resource).to be_updated end it "should call the block and flat the resource as updated - even in whyrun" do Chef::Config[:why_run] = true @provider.run_action(:run) expect($evil_global_evil_laugh).to eq(:mwahahaha) expect(@new_resource).to be_updated Chef::Config[:why_run] = false end end chef-12.14.60/spec/unit/provider/yum_repository_spec.rb000066400000000000000000000022061276456504500231030ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 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::YumRepository do let(:new_resource) { Chef::Resource::YumRepository.new("multiverse") } let(:provider) do node = Chef::Node.new events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(node, {}, events) Chef::Provider::YumRepository.new(new_resource, run_context) end it "responds to load_current_resource" do expect(provider).to respond_to(:load_current_resource) end end chef-12.14.60/spec/unit/provider_resolver_spec.rb000066400000000000000000001123511276456504500217160ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/mixin/convert_to_class_name" require "chef/provider_resolver" require "chef/platform/service_helpers" require "support/shared/integration/integration_helper" require "tmpdir" require "fileutils" include Chef::Mixin::ConvertToClassName # Open up Provider so we can write things down easier in here #module Chef::Provider describe Chef::ProviderResolver do include IntegrationSupport # Root the filesystem under a temp directory so Chef.path_to will point at it when_the_repository "is empty" do before do allow(Chef).to receive(:path_to) { |path| File.join(path_to(""), path) } end let(:resource_name) { :service } let(:provider) { nil } let(:action) { :start } let(:node) do node = Chef::Node.new node.automatic[:os] = os node.automatic[:platform_family] = platform_family node.automatic[:platform] = platform node.automatic[:platform_version] = platform_version node.automatic[:kernel] = { machine: "i386" } node end let(:run_context) { Chef::RunContext.new(node, nil, nil) } let(:provider_resolver) { Chef::ProviderResolver.new(node, resource, action) } let(:resolved_provider) do begin resource ? resource.provider_for_action(action).class : nil rescue Chef::Exceptions::ProviderNotFound nil end end let(:service_name) { "test" } let(:resource) do resource_class = Chef::ResourceResolver.resolve(resource_name, node: node) if resource_class resource = resource_class.new(service_name, run_context) resource.provider = provider if provider end resource end def self.on_platform(platform, *tags, platform_version: "11.0.1", platform_family: nil, os: nil, &block) Array(platform).each do |platform| Array(platform_version).each do |platform_version| on_one_platform(platform, platform_version, platform_family || platform, os || platform_family || platform, *tags, &block) end end end def self.on_one_platform(platform, platform_version, platform_family, os, *tags, &block) describe "on #{platform} #{platform_version}, platform_family: #{platform_family}, os: #{os}", *tags do let(:os) { os } let(:platform) { platform } let(:platform_family) { platform_family } let(:platform_version) { platform_version } define_singleton_method(:os) { os } define_singleton_method(:platform) { platform } define_singleton_method(:platform_family) { platform_family } define_singleton_method(:platform_version) { platform_version } instance_eval(&block) end end def self.expect_providers(**providers) providers.each do |name, expected| describe name.to_s do let(:resource_name) { name } tags = [] expected_provider = nil expected_resource = nil Array(expected).each do |p| if p.is_a?(Class) && p <= Chef::Provider expected_provider = p elsif p.is_a?(Class) && p <= Chef::Resource expected_resource = p else tags << p end end if expected_resource && expected_provider it "'#{name}' resolves to resource #{expected_resource} and provider #{expected_provider}", *tags do expect(resource.class).to eql(expected_resource) provider = double(expected_provider, class: expected_provider) expect(provider).to receive(:action=).with(action) expect(expected_provider).to receive(:new).with(resource, run_context).and_return(provider) expect(resolved_provider).to eql(expected_provider) end elsif expected_provider it "'#{name}' resolves to provider #{expected_provider}", *tags do provider = double(expected_provider) expect(provider).to receive(:action=).with(action) expect(expected_provider).to receive(:new).with(resource, run_context).and_return(provider) expect(resolved_provider).to eql(expected_provider) end else it "'#{name}' fails to resolve (since #{name.inspect} is unsupported on #{platform} #{platform_version})", *tags do expect(resolved_provider).to be_nil end end end end end describe "resolving service resource" do def stub_service_providers(*services) services.each do |service| case service when :debian file "usr/sbin/update-rc.d", "" when :invokercd file "usr/sbin/invoke-rc.d", "" when :insserv file "sbin/insserv", "" when :upstart file "sbin/initctl", "" when :redhat file "sbin/chkconfig", "" when :systemd file "proc/1/comm", "systemd\n" else raise ArgumentError, service end end end def stub_service_configs(*configs) configs.each do |config| case config when :initd file "etc/init.d/#{service_name}", "" when :upstart file "etc/init/#{service_name}.conf", "" when :xinetd file "etc/xinetd.d/#{service_name}", "" when :etc_rcd file "etc/rc.d/#{service_name}", "" when :usr_local_etc_rcd file "usr/local/etc/rc.d/#{service_name}", "" when :systemd file "proc/1/comm", "systemd\n" file "etc/systemd/system/#{service_name}.service", "" else raise ArgumentError, config end end end shared_examples_for "an ubuntu platform with upstart, update-rc.d and systemd" do before do stub_service_providers(:debian, :invokercd, :upstart, :systemd) end it "when only the SysV init script exists, it returns a Service::Debian provider" do stub_service_configs(:initd, :systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Systemd) end it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do stub_service_configs(:initd, :upstart, :systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Systemd) end it "when only the Upstart script exists, it returns a Service::Upstart provider" do stub_service_configs(:upstart, :systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Systemd) end it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do stub_service_configs(:systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Systemd) end it "when only the SysV init script exists, it returns a Service::Debian provider" do stub_service_configs(:initd) expect(resolved_provider).to eql(Chef::Provider::Service::Debian) end it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do stub_service_configs(:initd, :upstart) expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) end it "when only the Upstart script exists, it returns a Service::Upstart provider" do stub_service_configs(:upstart) expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) end it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do stub_service_configs expect(resolved_provider).to eql(Chef::Provider::Service::Systemd) end end shared_examples_for "an ubuntu platform with upstart and update-rc.d" do before do stub_service_providers(:debian, :invokercd, :upstart) end # needs to be handled by the highest priority init.d handler context "when only the SysV init script exists" do before do stub_service_configs(:initd) end it "enables init, invokercd, debian and upstart providers" do expect(provider_resolver.enabled_handlers).to include( Chef::Provider::Service::Debian, Chef::Provider::Service::Init, Chef::Provider::Service::Invokercd, Chef::Provider::Service::Upstart ) end it "supports all the enabled handlers except for upstart" do expect(provider_resolver.supported_handlers).to include( Chef::Provider::Service::Debian, Chef::Provider::Service::Init, Chef::Provider::Service::Invokercd ) expect(provider_resolver.supported_handlers).to_not include( Chef::Provider::Service::Upstart ) end it "returns a Service::Debian provider" do expect(resolved_provider).to eql(Chef::Provider::Service::Debian) end end # on ubuntu this must be handled by upstart, the init script will exit 1 and fail context "when both SysV and Upstart scripts exist" do before do stub_service_configs(:initd, :upstart) end it "enables init, invokercd, debian and upstart providers" do expect(provider_resolver.enabled_handlers).to include( Chef::Provider::Service::Debian, Chef::Provider::Service::Init, Chef::Provider::Service::Invokercd, Chef::Provider::Service::Upstart ) end it "supports all the enabled handlers" do expect(provider_resolver.supported_handlers).to include( Chef::Provider::Service::Debian, Chef::Provider::Service::Init, Chef::Provider::Service::Invokercd, Chef::Provider::Service::Upstart ) end it "returns a Service::Upstart provider" do expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) end end # this case is a pure-upstart script which is easy context "when only the Upstart script exists" do before do stub_service_configs(:upstart) end it "enables init, invokercd, debian and upstart providers" do expect(provider_resolver.enabled_handlers).to include( Chef::Provider::Service::Debian, Chef::Provider::Service::Init, Chef::Provider::Service::Invokercd, Chef::Provider::Service::Upstart ) end it "supports only the upstart handler" do expect(provider_resolver.supported_handlers).to include( Chef::Provider::Service::Upstart ) expect(provider_resolver.supported_handlers).to_not include( Chef::Provider::Service::Debian, Chef::Provider::Service::Init, Chef::Provider::Service::Invokercd ) end it "returns a Service::Upstart provider" do expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) end end # this case is important to get correct for why-run when no config is setup context "when both do not exist" do before do stub_service_configs end it "enables init, invokercd, debian and upstart providers" do expect(provider_resolver.enabled_handlers).to include( Chef::Provider::Service::Debian, Chef::Provider::Service::Init, Chef::Provider::Service::Invokercd, Chef::Provider::Service::Upstart ) end it "no providers claim to support the resource" do expect(provider_resolver.supported_handlers).to_not include( Chef::Provider::Service::Upstart, Chef::Provider::Service::Debian, Chef::Provider::Service::Init, Chef::Provider::Service::Invokercd ) end it "returns a Debian Provider" do expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) end end end shared_examples_for "a debian platform using the insserv provider" do context "with a default install" do before do stub_service_providers(:debian, :invokercd, :insserv) end it "uses the Service::Insserv Provider to manage sysv init scripts" do stub_service_configs(:initd) expect(resolved_provider).to eql(Chef::Provider::Service::Insserv) end it "uses the Service::Insserv Provider when there is no config" do stub_service_configs expect(resolved_provider).to eql(Chef::Provider::Service::Insserv) end end context "when the user has installed upstart" do before do stub_service_providers(:debian, :invokercd, :insserv, :upstart) end it "when only the SysV init script exists, it returns an Insserv provider" do stub_service_configs(:initd) expect(resolved_provider).to eql(Chef::Provider::Service::Insserv) end it "when both SysV and Upstart scripts exist, it returns a Service::Upstart provider" do stub_service_configs(:initd, :upstart) expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) end it "when only the Upstart script exists, it returns a Service::Upstart provider" do stub_service_configs(:upstart) expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) end it "when both do not exist, it calls the old style provider resolver and returns a Debian Provider" do stub_service_configs expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) end end end on_platform "ubuntu", platform_version: "15.10", platform_family: "debian", os: "linux" do it_behaves_like "an ubuntu platform with upstart, update-rc.d and systemd" it "when the unit-files are missing and system-ctl list-unit-files returns an error" do stub_service_providers(:debian, :invokercd, :upstart, :systemd) stub_service_configs(:initd, :upstart) mock_shellout_command("/bin/systemctl list-unit-files", exitstatus: 1) expect(resolved_provider).to eql(Chef::Provider::Service::Upstart) end end on_platform "ubuntu", platform_version: "14.10", platform_family: "debian", os: "linux" do it_behaves_like "an ubuntu platform with upstart, update-rc.d and systemd" end on_platform "ubuntu", platform_version: "14.04", platform_family: "debian", os: "linux" do it_behaves_like "an ubuntu platform with upstart and update-rc.d" end on_platform "ubuntu", platform_version: "10.04", platform_family: "debian", os: "linux" do it_behaves_like "an ubuntu platform with upstart and update-rc.d" end # old debian uses the Debian provider (does not have insserv or upstart, or update-rc.d???) on_platform "debian", platform_version: "4.0", os: "linux" do #it_behaves_like "a debian platform using the debian provider" end # Debian replaced the debian provider with insserv in the FIXME:VERSION distro on_platform "debian", platform_version: "7.0", os: "linux" do it_behaves_like "a debian platform using the insserv provider" end on_platform %w{solaris2 openindiana opensolaris nexentacore omnios smartos}, os: "solaris2", platform_version: "5.11" do it "returns a Solaris provider" do stub_service_providers stub_service_configs expect(resolved_provider).to eql(Chef::Provider::Service::Solaris) end it "always returns a Solaris provider" do # no matter what we stub on the next two lines we should get a Solaris provider stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) stub_service_configs(:initd, :upstart, :xinetd, :usr_local_etc_rcd, :systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Solaris) end end on_platform %w{mswin mingw32 windows}, platform_family: "windows", platform_version: "5.11" do it "returns a Windows provider" do stub_service_providers stub_service_configs expect(resolved_provider).to eql(Chef::Provider::Service::Windows) end it "always returns a Windows provider" do # no matter what we stub on the next two lines we should get a Windows provider stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) stub_service_configs(:initd, :upstart, :xinetd, :usr_local_etc_rcd, :systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Windows) end end on_platform %w{mac_os_x mac_os_x_server}, os: "darwin", platform_family: "mac_os_x", platform_version: "10.9.2" do it "returns a Macosx provider" do stub_service_providers stub_service_configs expect(resolved_provider).to eql(Chef::Provider::Service::Macosx) end it "always returns a Macosx provider" do # no matter what we stub on the next two lines we should get a Macosx provider stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) stub_service_configs(:initd, :upstart, :xinetd, :usr_local_etc_rcd, :systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Macosx) end end on_platform "freebsd", os: "freebsd", platform_version: "10.3" do it "returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do stub_service_providers stub_service_configs(:usr_local_etc_rcd) expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end it "returns a Freebsd provider if it finds the /etc/rc.d initscript" do stub_service_providers stub_service_configs(:etc_rcd) expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do # should only care about :usr_local_etc_rcd stub in the service configs stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) stub_service_configs(:usr_local_etc_rcd, :initd, :upstart, :xinetd, :systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do # should only care about :etc_rcd stub in the service configs stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) stub_service_configs(:etc_rcd, :initd, :upstart, :xinetd, :systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end it "always returns a freebsd provider by default?" do stub_service_providers stub_service_configs expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end end on_platform "netbsd", os: "netbsd", platform_version: "7.0.1" do it "returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do stub_service_providers stub_service_configs(:usr_local_etc_rcd) expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end it "returns a Freebsd provider if it finds the /etc/rc.d initscript" do stub_service_providers stub_service_configs(:etc_rcd) expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do # should only care about :usr_local_etc_rcd stub in the service configs stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) stub_service_configs(:usr_local_etc_rcd, :initd, :upstart, :xinetd, :systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do # should only care about :etc_rcd stub in the service configs stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd) stub_service_configs(:etc_rcd, :initd, :upstart, :xinetd, :systemd) expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end it "always returns a freebsd provider by default?" do stub_service_providers stub_service_configs expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd) end end end PROVIDERS = { bash: [ Chef::Resource::Bash, Chef::Provider::Script ], breakpoint: [ Chef::Resource::Breakpoint, Chef::Provider::Breakpoint ], chef_gem: [ Chef::Resource::ChefGem, Chef::Provider::Package::Rubygems ], cookbook_file: [ Chef::Resource::CookbookFile, Chef::Provider::CookbookFile ], csh: [ Chef::Resource::Csh, Chef::Provider::Script ], deploy: [ Chef::Resource::Deploy, Chef::Provider::Deploy::Timestamped ], deploy_revision: [ Chef::Resource::DeployRevision, Chef::Provider::Deploy::Revision ], directory: [ Chef::Resource::Directory, Chef::Provider::Directory ], easy_install_package: [ Chef::Resource::EasyInstallPackage, Chef::Provider::Package::EasyInstall ], erl_call: [ Chef::Resource::ErlCall, Chef::Provider::ErlCall ], execute: [ Chef::Resource::Execute, Chef::Provider::Execute ], file: [ Chef::Resource::File, Chef::Provider::File ], gem_package: [ Chef::Resource::GemPackage, Chef::Provider::Package::Rubygems ], git: [ Chef::Resource::Git, Chef::Provider::Git ], group: [ Chef::Resource::Group, Chef::Provider::Group::Gpasswd ], homebrew_package: [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ], http_request: [ Chef::Resource::HttpRequest, Chef::Provider::HttpRequest ], ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], link: [ Chef::Resource::Link, Chef::Provider::Link ], log: [ Chef::Resource::Log, Chef::Provider::Log::ChefLog ], macports_package: [ Chef::Resource::MacportsPackage, Chef::Provider::Package::Macports ], mdadm: [ Chef::Resource::Mdadm, Chef::Provider::Mdadm ], mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Mount ], perl: [ Chef::Resource::Perl, Chef::Provider::Script ], portage_package: [ Chef::Resource::PortagePackage, Chef::Provider::Package::Portage ], python: [ Chef::Resource::Python, Chef::Provider::Script ], remote_directory: [ Chef::Resource::RemoteDirectory, Chef::Provider::RemoteDirectory ], route: [ Chef::Resource::Route, Chef::Provider::Route ], ruby: [ Chef::Resource::Ruby, Chef::Provider::Script ], ruby_block: [ Chef::Resource::RubyBlock, Chef::Provider::RubyBlock ], script: [ Chef::Resource::Script, Chef::Provider::Script ], subversion: [ Chef::Resource::Subversion, Chef::Provider::Subversion ], template: [ Chef::Resource::Template, Chef::Provider::Template ], timestamped_deploy: [ Chef::Resource::TimestampedDeploy, Chef::Provider::Deploy::Timestamped ], aix_user: [ Chef::Resource::User::AixUser, Chef::Provider::User::Aix ], dscl_user: [ Chef::Resource::User::DsclUser, Chef::Provider::User::Dscl ], linux_user: [ Chef::Resource::User::LinuxUser, Chef::Provider::User::Linux ], pw_user: [ Chef::Resource::User::PwUser, Chef::Provider::User::Pw ], solaris_user: [ Chef::Resource::User::SolarisUser, Chef::Provider::User::Solaris ], windows_user: [ Chef::Resource::User::WindowsUser, Chef::Provider::User::Windows ], whyrun_safe_ruby_block: [ Chef::Resource::WhyrunSafeRubyBlock, Chef::Provider::WhyrunSafeRubyBlock ], # We want to check that these are unsupported: apt_package: nil, bff_package: nil, dpkg_package: nil, dsc_script: nil, ips_package: nil, pacman_package: nil, paludis_package: nil, rpm_package: nil, smartos_package: nil, solaris_package: nil, yum_package: nil, windows_package: nil, windows_service: nil, "linux" => { apt_package: [ Chef::Resource::AptPackage, Chef::Provider::Package::Apt ], dpkg_package: [ Chef::Resource::DpkgPackage, Chef::Provider::Package::Dpkg ], pacman_package: [ Chef::Resource::PacmanPackage, Chef::Provider::Package::Pacman ], paludis_package: [ Chef::Resource::PaludisPackage, Chef::Provider::Package::Paludis ], rpm_package: [ Chef::Resource::RpmPackage, Chef::Provider::Package::Rpm ], yum_package: [ Chef::Resource::YumPackage, Chef::Provider::Package::Yum ], "debian" => { ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Debian ], package: [ Chef::Resource::AptPackage, Chef::Provider::Package::Apt ], # service: [ Chef::Resource::DebianService, Chef::Provider::Service::Debian ], "debian" => { "7.0" => { }, "6.0" => { ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], # service: [ Chef::Resource::InsservService, Chef::Provider::Service::Insserv ], }, "5.0" => { ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], }, }, "gcel" => { "3.1.4" => { ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], }, }, "linaro" => { "3.1.4" => { ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], }, }, "linuxmint" => { "3.1.4" => { ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], # service: [ Chef::Resource::UpstartService, Chef::Provider::Service::Upstart ], }, }, "raspbian" => { "3.1.4" => { ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], }, }, "ubuntu" => { "11.10" => { }, "10.04" => { ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ], }, }, }, "arch" => { # TODO should be Chef::Resource::PacmanPackage package: [ Chef::Resource::Package, Chef::Provider::Package::Pacman ], "arch" => { "3.1.4" => { }, }, }, "suse" => { group: [ Chef::Resource::Group, Chef::Provider::Group::Gpasswd ], "suse" => { "12.0" => { }, %w{11.1 11.2 11.3} => { group: [ Chef::Resource::Group, Chef::Provider::Group::Suse ], }, }, "opensuse" => { # service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ], package: [ Chef::Resource::ZypperPackage, Chef::Provider::Package::Zypper ], group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ], "12.3" => { }, "12.2" => { group: [ Chef::Resource::Group, Chef::Provider::Group::Suse ], }, }, }, "gentoo" => { # TODO should be Chef::Resource::PortagePackage package: [ Chef::Resource::Package, Chef::Provider::Package::Portage ], portage_package: [ Chef::Resource::PortagePackage, Chef::Provider::Package::Portage ], # service: [ Chef::Resource::GentooService, Chef::Provider::Service::Gentoo ], "gentoo" => { "3.1.4" => { }, }, }, "rhel" => { # service: [ Chef::Resource::SystemdService, Chef::Provider::Service::Systemd ], package: [ Chef::Resource::YumPackage, Chef::Provider::Package::Yum ], ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Redhat ], %w{amazon xcp xenserver ibm_powerkvm cloudlinux parallels} => { "3.1.4" => { # service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ], }, }, %w{redhat centos scientific oracle} => { "7.0" => { }, "6.0" => { # service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ], }, }, "fedora" => { "15.0" => { }, "14.0" => { # service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ], }, }, }, }, "freebsd" => { "freebsd" => { group: [ Chef::Resource::Group, Chef::Provider::Group::Pw ], user: [ Chef::Resource::User::PwUser, Chef::Provider::User::Pw ], "freebsd" => { "10.3" => { }, }, }, }, "darwin" => { %w{mac_os_x mac_os_x_server} => { group: [ Chef::Resource::Group, Chef::Provider::Group::Dscl ], package: [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ], osx_profile: [ Chef::Resource::OsxProfile, Chef::Provider::OsxProfile], user: [ Chef::Resource::User::DsclUser, Chef::Provider::User::Dscl ], "mac_os_x" => { "10.9.2" => { }, }, }, }, "windows" => { batch: [ Chef::Resource::Batch, Chef::Provider::Batch ], dsc_script: [ Chef::Resource::DscScript, Chef::Provider::DscScript ], env: [ Chef::Resource::Env, Chef::Provider::Env::Windows ], group: [ Chef::Resource::Group, Chef::Provider::Group::Windows ], mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Windows ], package: [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ], powershell_script: [ Chef::Resource::PowershellScript, Chef::Provider::PowershellScript ], service: [ Chef::Resource::WindowsService, Chef::Provider::Service::Windows ], user: [ Chef::Resource::User::WindowsUser, Chef::Provider::User::Windows ], windows_package: [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ], windows_service: [ Chef::Resource::WindowsService, Chef::Provider::Service::Windows ], "windows" => { %w{mswin mingw32 windows} => { "10.9.2" => { }, }, }, }, "aix" => { bff_package: [ Chef::Resource::BffPackage, Chef::Provider::Package::Aix ], cron: [ Chef::Resource::Cron, Chef::Provider::Cron::Aix ], group: [ Chef::Resource::Group, Chef::Provider::Group::Aix ], ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Aix ], mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Aix ], # TODO should be Chef::Resource::BffPackage package: [ Chef::Resource::Package, Chef::Provider::Package::Aix ], rpm_package: [ Chef::Resource::RpmPackage, Chef::Provider::Package::Rpm ], user: [ Chef::Resource::User::AixUser, Chef::Provider::User::Aix ], # service: [ Chef::Resource::AixService, Chef::Provider::Service::Aix ], "aix" => { "aix" => { "5.6" => { }, }, }, }, "hpux" => { "hpux" => { "hpux" => { "3.1.4" => { group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ], }, }, }, }, "netbsd" => { "netbsd" => { "netbsd" => { "3.1.4" => { group: [ Chef::Resource::Group, Chef::Provider::Group::Groupmod ], }, }, }, }, "openbsd" => { group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ], package: [ Chef::Resource::OpenbsdPackage, Chef::Provider::Package::Openbsd ], "openbsd" => { "openbsd" => { "3.1.4" => { }, }, }, }, "solaris2" => { group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ], ips_package: [ Chef::Resource::IpsPackage, Chef::Provider::Package::Ips ], package: [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ], mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Solaris ], solaris_package: [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ], "smartos" => { smartos_package: [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ], package: [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ], "smartos" => { "3.1.4" => { }, }, }, "solaris2" => { "nexentacore" => { "3.1.4" => { }, }, "omnios" => { "3.1.4" => { user: [ Chef::Resource::User::SolarisUser, Chef::Provider::User::Solaris ], }, }, "openindiana" => { "3.1.4" => { }, }, "opensolaris" => { "3.1.4" => { }, }, "solaris2" => { user: [ Chef::Resource::User::SolarisUser, Chef::Provider::User::Solaris ], "5.11" => { package: [ Chef::Resource::IpsPackage, Chef::Provider::Package::Ips ], }, "5.9" => { }, }, }, }, "solaris" => { "solaris" => { "solaris" => { "3.1.4" => { }, }, }, }, "exherbo" => { "exherbo" => { "exherbo" => { "3.1.4" => { # TODO should be Chef::Resource::PaludisPackage package: [ Chef::Resource::Package, Chef::Provider::Package::Paludis ], }, }, }, }, } def self.create_provider_tests(providers, test, expected, filter) expected = expected.merge(providers.select { |key, value| key.is_a?(Symbol) }) providers.each do |key, value| if !key.is_a?(Symbol) next_test = test.merge({ filter => key }) next_filter = case filter when :os :platform_family when :platform_family :platform when :platform :platform_version when :platform_version nil else raise "Hash too deep; only os, platform_family, platform and platform_version supported" end create_provider_tests(value, next_test, expected, next_filter) end end # If there is no filter, we're as deep as we need to go if !filter on_platform test.delete(:platform), test do expect_providers(expected) end end end create_provider_tests(PROVIDERS, {}, {}, :os) end end chef-12.14.60/spec/unit/provider_spec.rb000066400000000000000000000141541276456504500177770ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" class NoWhyrunDemonstrator < Chef::Provider attr_reader :system_state_altered def whyrun_supported? false end def load_current_resource end def action_foo @system_state_altered = true end end class ConvergeActionDemonstrator < Chef::Provider attr_reader :system_state_altered def whyrun_supported? true end def load_current_resource end def action_foo converge_by("running a state changing action") do @system_state_altered = true end end end class CheckResourceSemanticsDemonstrator < ConvergeActionDemonstrator def check_resource_semantics! raise Chef::Exceptions::InvalidResourceSpecification.new("check_resource_semantics!") end end describe Chef::Provider do before(:each) do @cookbook_collection = Chef::CookbookCollection.new([]) @node = Chef::Node.new @node.name "latte" @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events) @resource = Chef::Resource.new("funk", @run_context) @resource.cookbook_name = "a_delicious_pie" @provider = Chef::Provider.new(@resource, @run_context) end it "should mixin shell_out" do expect(@provider.respond_to?(:shell_out)).to be true end it "should mixin shell_out!" do expect(@provider.respond_to?(:shell_out!)).to be true end it "should mixin shell_out_with_systems_locale" do expect(@provider.respond_to?(:shell_out_with_systems_locale)).to be true end it "should store the resource passed to new as new_resource" do expect(@provider.new_resource).to eql(@resource) end it "should store the node passed to new as node" do expect(@provider.node).to eql(@node) end it "should have nil for current_resource by default" do expect(@provider.current_resource).to eql(nil) end it "should not support whyrun by default" do expect(@provider.send(:whyrun_supported?)).to eql(false) end it "should do nothing for check_resource_semantics! by default" do expect { @provider.check_resource_semantics! }.not_to raise_error end it "should return true for action_nothing" do expect(@provider.action_nothing).to eql(true) end it "evals embedded recipes with a pristine resource collection" do @provider.run_context.instance_variable_set(:@resource_collection, "doesn't matter what this is") temporary_collection = nil snitch = Proc.new { temporary_collection = @run_context.resource_collection } @provider.send(:recipe_eval, &snitch) expect(temporary_collection).to be_an_instance_of(Chef::ResourceCollection) expect(@provider.run_context.instance_variable_get(:@resource_collection)).to eq("doesn't matter what this is") end it "does not re-load recipes when creating the temporary run context" do expect_any_instance_of(Chef::RunContext).not_to receive(:load) snitch = Proc.new { temporary_collection = @run_context.resource_collection } @provider.send(:recipe_eval, &snitch) end context "when no converge actions are queued" do before do allow(@provider).to receive(:whyrun_supported?).and_return(true) allow(@provider).to receive(:load_current_resource) end it "does not mark the new resource as updated" do expect(@resource).not_to be_updated expect(@resource).not_to be_updated_by_last_action end end context "when converge actions have been added to the queue" do describe "and provider supports whyrun mode" do before do @provider = ConvergeActionDemonstrator.new(@resource, @run_context) end it "should tell us that it does support whyrun" do expect(@provider).to be_whyrun_supported end it "queues up converge actions" do @provider.action_foo expect(@provider.send(:converge_actions).actions.size).to eq(1) end it "executes pending converge actions to converge the system" do @provider.run_action(:foo) expect(@provider.instance_variable_get(:@system_state_altered)).to be_truthy end it "marks the resource as updated" do @provider.run_action(:foo) expect(@resource).to be_updated expect(@resource).to be_updated_by_last_action end end describe "and provider does not support whyrun mode" do before do Chef::Config[:why_run] = true @provider = NoWhyrunDemonstrator.new(@resource, @run_context) end after do Chef::Config[:why_run] = false end it "should tell us that it doesn't support whyrun" do expect(@provider).not_to be_whyrun_supported end it "should automatically generate a converge_by block on the provider's behalf" do @provider.run_action(:foo) expect(@provider.send(:converge_actions).actions.size).to eq(0) expect(@provider.system_state_altered).to be_falsey end it "should automatically execute the generated converge_by block" do @provider.run_action(:foo) expect(@provider.system_state_altered).to be_falsey expect(@resource).not_to be_updated expect(@resource).not_to be_updated_by_last_action end end describe "and the resource is invalid" do let(:provider) { CheckResourceSemanticsDemonstrator.new(@resource, @run_context) } it "fails with InvalidResourceSpecification when run" do expect { provider.run_action(:foo) }.to raise_error(Chef::Exceptions::InvalidResourceSpecification) end end end end chef-12.14.60/spec/unit/pure_application_spec.rb000066400000000000000000000020771276456504500215040ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2014-2016, Chef 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. # This spec file intentionally doesn't include spec_helper.rb to # be able to test only Chef::Application. # Regression test for CHEF-5169 require "chef/application" describe "Chef::Application" do let(:app) { Chef::Application.new } describe "load_config_file" do it "calls ConfigFetcher successfully without NameError" do expect { app.load_config_file }.not_to raise_error end end end chef-12.14.60/spec/unit/recipe_spec.rb000066400000000000000000000645451276456504500174250ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: Tim Hinderliter () # Author:: Seth Chisamore () # 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" require "chef/platform/resource_priority_map" describe Chef::Recipe do let(:cookbook_collection) do cookbook_repo = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks")) cookbook_loader = Chef::CookbookLoader.new(cookbook_repo) cookbook_loader.load_cookbooks Chef::CookbookCollection.new(cookbook_loader) end let(:node) do Chef::Node.new end let(:run_context) do events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, cookbook_collection, events) end let(:recipe) do Chef::Recipe.new("hjk", "test", run_context) end describe "method_missing" do describe "resources" do it "should load a two word (zen_master) resource" do expect do recipe.zen_master "monkey" do peace true end end.not_to raise_error end it "should load a one word (cat) resource" do expect do recipe.cat "loulou" do pretty_kitty true end end.not_to raise_error end it "should load a four word (one_two_three_four) resource" do expect do recipe.one_two_three_four "numbers" do i_can_count true end end.not_to raise_error end it "should throw an error if you access a resource that we can't find" do expect { recipe.not_home("not_home_resource") }.to raise_error(NameError) end it "should require a name argument" do expect do recipe.cat end.to raise_error(ArgumentError) end it "should allow regular errors (not NameErrors) to pass unchanged" do expect do recipe.cat("felix") { raise ArgumentError, "You Suck" } end.to raise_error(ArgumentError) end it "should add our zen_master to the collection" do recipe.zen_master "monkey" do peace true end expect(run_context.resource_collection.lookup("zen_master[monkey]").name).to eql("monkey") end it "should add our zen masters to the collection in the order they appear" do %w{monkey dog cat}.each do |name| recipe.zen_master name do peace true end end expect(run_context.resource_collection.map { |r| r.name }).to eql(%w{monkey dog cat}) end it "should return the new resource after creating it" do res = recipe.zen_master "makoto" do peace true end expect(res.resource_name).to eql(:zen_master) expect(res.name).to eql("makoto") end describe "should locate platform mapped resources" do it "locate resource for particular platform" do ShaunTheSheep = Class.new(Chef::Resource) ShaunTheSheep.resource_name :shaun_the_sheep ShaunTheSheep.provides :laughter, :platform => ["television"] node.automatic[:platform] = "television" node.automatic[:platform_version] = "123" res = recipe.laughter "timmy" expect(res.name).to eql("timmy") res.kind_of?(ShaunTheSheep) end it "locate a resource for all platforms" do YourMom = Class.new(Chef::Resource) YourMom.resource_name :your_mom YourMom.provides :love_and_caring res = recipe.love_and_caring "mommy" expect(res.name).to eql("mommy") res.kind_of?(YourMom) end describe "when there is more than one resource that resolves on a node" do before do node.automatic[:platform] = "nbc_sports" Sounders = Class.new(Chef::Resource) Sounders.resource_name :sounders TottenhamHotspur = Class.new(Chef::Resource) TottenhamHotspur.resource_name :tottenham_hotspur end after do Object.send(:remove_const, :Sounders) Object.send(:remove_const, :TottenhamHotspur) end it "selects the first one alphabetically" do Sounders.provides :football, platform: "nbc_sports" TottenhamHotspur.provides :football, platform: "nbc_sports" res1 = recipe.football "club world cup" expect(res1.name).to eql("club world cup") expect(res1).to be_a_kind_of(Sounders) end it "selects the first one alphabetically even if the declaration order is reversed" do TottenhamHotspur.provides :football2, platform: "nbc_sports" Sounders.provides :football2, platform: "nbc_sports" res1 = recipe.football2 "club world cup" expect(res1.name).to eql("club world cup") expect(res1).to be_a_kind_of(Sounders) end end end end describe "creating resources via build_resource" do let(:zm_resource) do recipe.build_resource(:zen_master, "klopp") do something "bvb" end end it "applies attributes from the block to the resource" do expect(zm_resource.something).to eq("bvb") end it "sets contextual attributes on the resource" do expect(zm_resource.recipe_name).to eq("test") expect(zm_resource.cookbook_name).to eq("hjk") expect(zm_resource.source_line).to include(__FILE__) expect(zm_resource.declared_type).to eq(:zen_master) end it "does not add the resource to the resource collection" do zm_resource # force let binding evaluation expect { run_context.resource_collection.resources(:zen_master => "klopp") }.to raise_error(Chef::Exceptions::ResourceNotFound) end end describe "when cloning resources" do def expect_warning expect(Chef).to receive(:log_deprecation).with(/^Cloning resource attributes for zen_master\[klopp\]/) end it "should emit a 3694 warning when attributes change" do recipe.zen_master "klopp" do something "bvb" end expect_warning recipe.zen_master "klopp" do something "vbv" end end it "should emit a 3694 warning when attributes change" do recipe.zen_master "klopp" do something "bvb" end expect_warning recipe.zen_master "klopp" do something "bvb" peace true end end it "should emit a 3694 warning when attributes change" do recipe.zen_master "klopp" do something "bvb" peace true end expect_warning recipe.zen_master "klopp" do something "bvb" end end it "should emit a 3694 warning for non-trivial attributes (unfortunately)" do recipe.zen_master "klopp" do something "bvb" end expect_warning recipe.zen_master "klopp" do something "bvb" end end it "should not emit a 3694 warning for completely trivial resource cloning" do recipe.zen_master "klopp" expect(Chef).to_not receive(:log_deprecation) recipe.zen_master "klopp" end it "should not emit a 3694 warning when attributes do not change and the first action is :nothing" do recipe.zen_master "klopp" do action :nothing end expect(Chef).to_not receive(:log_deprecation) recipe.zen_master "klopp" do action :score end end it "should not emit a 3694 warning when attributes do not change and the second action is :nothing" do recipe.zen_master "klopp" do action :score end expect(Chef).to_not receive(:log_deprecation) recipe.zen_master "klopp" do action :nothing end end class Coerced < Chef::Resource resource_name :coerced provides :coerced default_action :whatever property :package_name, [String, Array], coerce: proc { |x| [x].flatten }, name_property: true def after_created Array(action).each do |action| run_action(action) end end action :whatever do package_name # unlazy the package_name end end it "does not emit 3694 when the name_property is unlazied by running it at compile_time" do recipe.coerced "string" expect(Chef).to_not receive(:log_deprecation) recipe.coerced "string" end it "validating resources via build_resource" do expect do recipe.build_resource(:remote_file, "klopp") do source Chef::DelayedEvaluator.new { "http://chef.io" } end end.to_not raise_error end end describe "creating resources via declare_resource" do let(:zm_resource) do recipe.declare_resource(:zen_master, "klopp") do something "bvb" end end it "applies attributes from the block to the resource" do expect(zm_resource.something).to eq("bvb") end it "sets contextual attributes on the resource" do expect(zm_resource.recipe_name).to eq("test") expect(zm_resource.cookbook_name).to eq("hjk") expect(zm_resource.source_line).to include(__FILE__) end it "adds the resource to the resource collection" do zm_resource # force let binding evaluation expect(run_context.resource_collection.resources(:zen_master => "klopp")).to eq(zm_resource) end it "will insert another resource if create_if_missing is not set (cloned resource as of Chef-12)" do expect(Chef).to receive(:log_deprecation).with(/^Cloning resource attributes for zen_master\[klopp\]/) zm_resource recipe.declare_resource(:zen_master, "klopp") expect(run_context.resource_collection.count).to eql(2) end it "does not insert two resources if create_if_missing is used" do zm_resource Chef::Config[:treat_deprecation_warnings_as_errors] = false recipe.declare_resource(:zen_master, "klopp", create_if_missing: true) expect(run_context.resource_collection.count).to eql(1) end context "injecting a different run_context" do let(:run_context2) do events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, cookbook_collection, events) end it "should insert resources into the correct run_context" do zm_resource recipe.declare_resource(:zen_master, "klopp2", run_context: run_context2) run_context2.resource_collection.lookup("zen_master[klopp2]") expect { run_context2.resource_collection.lookup("zen_master[klopp]") }.to raise_error(Chef::Exceptions::ResourceNotFound) expect { run_context.resource_collection.lookup("zen_master[klopp2]") }.to raise_error(Chef::Exceptions::ResourceNotFound) run_context.resource_collection.lookup("zen_master[klopp]") end end end describe "creating a resource with short name" do # zen_follower resource has this: # provides :follower, :on_platforms => ["zen"] before do node.automatic_attrs[:platform] = "zen" end let(:resource_follower) do recipe.declare_resource(:follower, "srst") do master "none" end end it "defines the resource using the declaration name with short name" do resource_follower expect(run_context.resource_collection.lookup("follower[srst]")).not_to be_nil end end describe "creating a resource with a long name" do let(:resource_zn_follower) do recipe.declare_resource(:zen_follower, "srst") do master "none" end end it "defines the resource using the declaration name with long name" do resource_zn_follower expect(run_context.resource_collection.lookup("zen_follower[srst]")).not_to be_nil end end describe "when attempting to create a resource of an invalid type" do it "gives a sane error message when using method_missing" do expect do recipe.no_such_resource("foo") end.to raise_error(NoMethodError, %q{No resource or method named `no_such_resource' for `Chef::Recipe "test"'}) end it "gives a sane error message when using method_missing 'bare'" do expect do recipe.instance_eval do # Giving an argument will change this from NameError to NoMethodError no_such_resource end end.to raise_error(NameError, %q{No resource, method, or local variable named `no_such_resource' for `Chef::Recipe "test"'}) end it "gives a sane error message when using build_resource" do expect { recipe.build_resource(:no_such_resource, "foo") }.to raise_error(Chef::Exceptions::NoSuchResourceType) end it "gives a sane error message when using declare_resource" do expect { recipe.declare_resource(:no_such_resource, "bar") }.to raise_error(Chef::Exceptions::NoSuchResourceType) end end describe "when creating a resource that contains an error in the attributes block" do it "does not obfuscate the error source" do expect do recipe.zen_master("klopp") do this_method_doesnt_exist end end.to raise_error(NoMethodError, "undefined method `this_method_doesnt_exist' for Chef::Resource::ZenMaster") end end describe "resource cloning" do let(:second_recipe) do Chef::Recipe.new("second_cb", "second_recipe", run_context) end let(:original_resource) do recipe.zen_master("klopp") do something "bvb09" action :score end end let(:duplicated_resource) do original_resource second_recipe.zen_master("klopp") do # attrs should be cloned end end it "copies attributes from the first resource" do expect(Chef).to receive(:log_deprecation).with(/^Cloning resource attributes for zen_master\[klopp\]/) expect(duplicated_resource.something).to eq("bvb09") end it "does not copy the action from the first resource" do expect(Chef).to receive(:log_deprecation).with(/^Cloning resource attributes for zen_master\[klopp\]/) expect(original_resource.action).to eq([:score]) expect(duplicated_resource.action).to eq([:nothing]) end it "does not copy the source location of the first resource" do expect(Chef).to receive(:log_deprecation).with(/^Cloning resource attributes for zen_master\[klopp\]/) # sanity check source location: expect(original_resource.source_line).to include(__FILE__) expect(duplicated_resource.source_line).to include(__FILE__) # actual test: expect(original_resource.source_line).not_to eq(duplicated_resource.source_line) end it "sets the cookbook name on the cloned resource to that resource's cookbook" do expect(Chef).to receive(:log_deprecation).with(/^Cloning resource attributes for zen_master\[klopp\]/) expect(duplicated_resource.cookbook_name).to eq("second_cb") end it "sets the recipe name on the cloned resource to that resoure's recipe" do expect(Chef).to receive(:log_deprecation).with(/^Cloning resource attributes for zen_master\[klopp\]/) expect(duplicated_resource.recipe_name).to eq("second_recipe") end end describe "resource definitions" do it "should execute defined resources" do crow_define = Chef::ResourceDefinition.new crow_define.define :crow, :peace => false, :something => true do zen_master "lao tzu" do peace params[:peace] something params[:something] end end run_context.definitions[:crow] = crow_define recipe.crow "mine" do peace true end expect(run_context.resource_collection.resources(:zen_master => "lao tzu").name).to eql("lao tzu") expect(run_context.resource_collection.resources(:zen_master => "lao tzu").something).to eql(true) end it "should set the node on defined resources" do crow_define = Chef::ResourceDefinition.new crow_define.define :crow, :peace => false, :something => true do zen_master "lao tzu" do peace params[:peace] something params[:something] end end run_context.definitions[:crow] = crow_define node.normal[:foo] = false recipe.crow "mine" do something node[:foo] end expect(recipe.resources(:zen_master => "lao tzu").something).to eql(false) end it "should return the last statement in the definition as the retval" do crow_define = Chef::ResourceDefinition.new crow_define.define :crow, :peace => false, :something => true do "the return val" end run_context.definitions[:crow] = crow_define crow_block = recipe.crow "mine" do peace true end expect(crow_block).to eql("the return val") end end end describe "instance_eval" do it "should handle an instance_eval properly" do code = <<-CODE zen_master "gnome" do peace = true end CODE expect { recipe.instance_eval(code) }.not_to raise_error expect(recipe.resources(:zen_master => "gnome").name).to eql("gnome") end end describe "handle exec calls" do it "should raise ResourceNotFound error if exec is used" do code = <<-CODE exec 'do_not_try_to_exec' CODE expect { recipe.instance_eval(code) }.to raise_error(Chef::Exceptions::ResourceNotFound) end end describe "from_file" do it "should load a resource from a ruby file" do recipe.from_file(File.join(CHEF_SPEC_DATA, "recipes", "test.rb")) res = recipe.resources(:file => "/etc/nsswitch.conf") expect(res.name).to eql("/etc/nsswitch.conf") expect(res.action).to eql([:create]) expect(res.owner).to eql("root") expect(res.group).to eql("root") expect(res.mode).to eql(0644) end it "should raise an exception if the file cannot be found or read" do expect { recipe.from_file("/tmp/monkeydiving") }.to raise_error(IOError) end end describe "include_recipe" do it "should evaluate another recipe with include_recipe" do expect(node).to receive(:loaded_recipe).with(:openldap, "gigantor") allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) run_context.include_recipe "openldap::gigantor" res = run_context.resource_collection.resources(:cat => "blanket") expect(res.name).to eql("blanket") expect(res.pretty_kitty).to eql(false) end it "should load the default recipe for a cookbook if include_recipe is called without a ::" do expect(node).to receive(:loaded_recipe).with(:openldap, "default") allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) run_context.include_recipe "openldap" res = run_context.resource_collection.resources(:cat => "blanket") expect(res.name).to eql("blanket") expect(res.pretty_kitty).to eql(true) end it "should store that it has seen a recipe in the run_context" do expect(node).to receive(:loaded_recipe).with(:openldap, "default") allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) run_context.include_recipe "openldap" expect(run_context.loaded_recipe?("openldap")).to be_truthy end it "should not include the same recipe twice" do expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once) allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context) recipe.include_recipe "openldap" expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context) recipe.include_recipe "openldap" end it "will load a recipe out of the current cookbook when include_recipe is called with a leading ::" do openldap_recipe = Chef::Recipe.new("openldap", "test", run_context) expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once) allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context) openldap_recipe.include_recipe "::default" end it "will not include the same recipe twice when using leading :: syntax" do openldap_recipe = Chef::Recipe.new("openldap", "test", run_context) expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once) allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context) openldap_recipe.include_recipe "::default" expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context) openldap_recipe.include_recipe "openldap::default" end it "will not include the same recipe twice when using leading :: syntax (reversed order)" do openldap_recipe = Chef::Recipe.new("openldap", "test", run_context) expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once) allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context) openldap_recipe.include_recipe "openldap::default" expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context) openldap_recipe.include_recipe "::default" end it "will not load a recipe twice when called first from an LWRP provider" do openldap_recipe = Chef::Recipe.new("openldap", "test", run_context) expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once) allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context) openldap_recipe.include_recipe "::default" expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context) openldap_recipe.openldap_includer("do it").run_action(:run) end it "will not load a recipe twice when called last from an LWRP provider" do openldap_recipe = Chef::Recipe.new("openldap", "test", run_context) expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once) allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context) openldap_recipe.openldap_includer("do it").run_action(:run) expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context) openldap_recipe.include_recipe "::default" end it "will not load a recipe twice when called both times from an LWRP provider" do openldap_recipe = Chef::Recipe.new("openldap", "test", run_context) expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once) allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false) expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context) openldap_recipe.openldap_includer("do it").run_action(:run) expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context) openldap_recipe.openldap_includer("do it").run_action(:run) end end describe "tags" do describe "with the default node object" do let(:node) { Chef::Node.new } it "should return false for any tags" do expect(recipe.tagged?("foo")).to be(false) end end it "should initialize tags to an empty Array" do expect(node.tags).to eql([]) end it "should set tags via tag" do recipe.tag "foo" expect(node.tags).to include("foo") end it "should set multiple tags via tag" do recipe.tag "foo", "bar" expect(node.tags).to include("foo") expect(node.tags).to include("bar") end it "should not set the same tag twice via tag" do recipe.tag "foo" recipe.tag "foo" expect(node.tags).to eql([ "foo" ]) end it "should return the current list of tags from tag with no arguments" do recipe.tag "foo" expect(recipe.tag).to eql([ "foo" ]) end it "should return true from tagged? if node is tagged" do recipe.tag "foo" expect(recipe.tagged?("foo")).to be(true) end it "should return false from tagged? if node is not tagged" do expect(recipe.tagged?("foo")).to be(false) end it "should return false from tagged? if node is not tagged" do expect(recipe.tagged?("foo")).to be(false) end it "should remove a tag from the tag list via untag" do recipe.tag "foo" recipe.untag "foo" expect(node.tags).to eql([]) end it "should remove multiple tags from the tag list via untag" do recipe.tag "foo", "bar" recipe.untag "bar", "foo" expect(node.tags).to eql([]) end end describe "included DSL" do it "should include features from Chef::DSL::Audit" do expect(recipe.singleton_class.included_modules).to include(Chef::DSL::Audit) expect(recipe.respond_to?(:control_group)).to be true end it "should respond to :ps_credential from Chef::DSL::Powershell" do expect(recipe.respond_to?(:ps_credential)).to be true end end end chef-12.14.60/spec/unit/resource/000077500000000000000000000000001276456504500164305ustar00rootroot00000000000000chef-12.14.60/spec/unit/resource/apt_package_spec.rb000066400000000000000000000023201276456504500222230ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::AptPackage, "initialize" do static_provider_resolution( resource: Chef::Resource::AptPackage, provider: Chef::Provider::Package::Apt, name: :apt_package, action: :install, os: "linux" ) let(:resource) { Chef::Resource::AptPackage.new("foo") } it "should support default_release" do resource.default_release("lenny-backports") expect(resource.default_release).to eql("lenny-backports") end end chef-12.14.60/spec/unit/resource/apt_repository_spec.rb000066400000000000000000000036511276456504500230570ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 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::Resource::AptRepository do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:resource) { Chef::Resource::AptRepository.new("multiverse", run_context) } it "should create a new Chef::Resource::AptUpdate" do expect(resource).to be_a_kind_of(Chef::Resource) expect(resource).to be_a_kind_of(Chef::Resource::AptRepository) end it "the default keyserver should be keyserver.ubuntu.com" do expect(resource.keyserver).to eql("keyserver.ubuntu.com") end it "the default distribution should be nillable" do expect(resource.distribution(nil)).to eql(nil) expect(resource.distribution).to eql(nil) end it "should resolve to a Noop class when apt-get is not found" do expect(Chef::Provider::AptRepository).to receive(:which).with("apt-get").and_return(false) expect(resource.provider_for_action(:add)).to be_a(Chef::Provider::Noop) end it "should resolve to a AptRepository class when apt-get is found" do expect(Chef::Provider::AptRepository).to receive(:which).with("apt-get").and_return(true) expect(resource.provider_for_action(:add)).to be_a(Chef::Provider::AptRepository) end end chef-12.14.60/spec/unit/resource/apt_update_spec.rb000066400000000000000000000035111276456504500221150ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 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::Resource::AptUpdate do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:resource) { Chef::Resource::AptUpdate.new("update", run_context) } it "should create a new Chef::Resource::AptUpdate" do expect(resource).to be_a_kind_of(Chef::Resource) expect(resource).to be_a_kind_of(Chef::Resource::AptUpdate) end it "the default frequency should be 1 day" do expect(resource.frequency).to eql(86_400) end it "the frequency should accept integers" do resource.frequency(400) expect(resource.frequency).to eql(400) end it "should resolve to a Noop class when apt-get is not found" do expect(Chef::Provider::AptUpdate).to receive(:which).with("apt-get").and_return(false) expect(resource.provider_for_action(:add)).to be_a(Chef::Provider::Noop) end it "should resolve to a AptUpdate class when apt-get is found" do expect(Chef::Provider::AptUpdate).to receive(:which).with("apt-get").and_return(true) expect(resource.provider_for_action(:add)).to be_a(Chef::Provider::AptUpdate) end end chef-12.14.60/spec/unit/resource/bash_spec.rb000066400000000000000000000022671276456504500207130ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Resource::Bash do before(:each) do @resource = Chef::Resource::Bash.new("fakey_fakerton") end it "should create a new Chef::Resource::Bash" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Bash) end it "should have a resource name of :bash" do expect(@resource.resource_name).to eql(:bash) end it "should have an interpreter of bash" do expect(@resource.interpreter).to eql("bash") end end chef-12.14.60/spec/unit/resource/batch_spec.rb000066400000000000000000000026541276456504500210570ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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::Resource::Batch do before(:each) do node = Chef::Node.new node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s node.automatic[:os] = "windows" run_context = Chef::RunContext.new(node, nil, nil) @resource = Chef::Resource::Batch.new("batch_unit_test", run_context) end it "should create a new Chef::Resource::Batch" do expect(@resource).to be_a_kind_of(Chef::Resource::Batch) end context "windows script" do let(:resource_instance) { @resource } let(:resource_instance_name ) { @resource.command } let(:resource_name) { :batch } let(:interpreter_file_name) { "cmd.exe" } it_should_behave_like "a Windows script resource" end end chef-12.14.60/spec/unit/resource/breakpoint_spec.rb000066400000000000000000000026051276456504500221300ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::Breakpoint do static_provider_resolution( resource: Chef::Resource::Breakpoint, provider: Chef::Provider::Breakpoint, name: :breakpoint, action: :break ) before do @breakpoint = Chef::Resource::Breakpoint.new end it "allows the action :break" do expect(@breakpoint.allowed_actions).to include(:break) end it "defaults to the break action" do expect(@breakpoint.action).to eq([:break]) end it "names itself after the line number of the file where it's created" do expect(@breakpoint.name).to match(/breakpoint_spec\.rb\:[\d]{2}\:in \`new\'$/) end end chef-12.14.60/spec/unit/resource/chef_gem_spec.rb000066400000000000000000000112121276456504500215210ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Bryan McLellan # 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" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::ChefGem, "initialize" do static_provider_resolution( resource: Chef::Resource::ChefGem, provider: Chef::Provider::Package::Rubygems, name: :chef_gem, action: :install ) end describe Chef::Resource::ChefGem, "gem_binary" do let(:resource) { Chef::Resource::ChefGem.new("foo") } it "should raise an exception when gem_binary is set" do expect { resource.gem_binary("/lol/cats/gem") }.to raise_error(ArgumentError) end it "should set the gem_binary based on computing it from RbConfig" do expect(resource.gem_binary).to eql("#{RbConfig::CONFIG['bindir']}/gem") end it "should set the gem_binary based on computing it from RbConfig" do expect(resource.compile_time).to be nil end context "when building the resource" do let(:node) do Chef::Node.new end let(:run_context) do Chef::RunContext.new(node, {}, nil) end let(:recipe) do Chef::Recipe.new("hjk", "test", run_context) end let(:chef_gem_compile_time) { nil } let(:resource) do Chef::Config[:chef_gem_compile_time] = chef_gem_compile_time Chef::Resource::ChefGem.new("foo", run_context) end before do expect(Chef::Resource::ChefGem).to receive(:new).and_return(resource) end it "runs the install at compile-time by default", chef: "< 13" do expect(resource).to receive(:run_action).with(:install) expect(Chef::Log).to receive(:deprecation).at_least(:once) recipe.chef_gem "foo" end # the default behavior will change in Chef-13 it "does not runs the install at compile-time by default", chef: ">= 13" do expect(resource).not_to receive(:run_action).with(:install) expect(Chef::Log).not_to receive(:deprecation) recipe.chef_gem "foo" end it "compile_time true installs at compile-time" do expect(resource).to receive(:run_action).with(:install) expect(Chef::Log).not_to receive(:deprecation) recipe.chef_gem "foo" do compile_time true end end it "compile_time false does not install at compile-time" do expect(resource).not_to receive(:run_action).with(:install) expect(Chef::Log).not_to receive(:deprecation) recipe.chef_gem "foo" do compile_time false end end describe "when Chef::Config[:chef_gem_compile_time] is explicitly true" do let(:chef_gem_compile_time) { true } before do expect(Chef::Log).not_to receive(:deprecation) end it "by default installs at compile-time" do expect(resource).to receive(:run_action).with(:install) recipe.chef_gem "foo" end it "compile_time true installs at compile-time" do expect(resource).to receive(:run_action).with(:install) recipe.chef_gem "foo" do compile_time true end end it "compile_time false does not install at compile-time" do expect(resource).not_to receive(:run_action).with(:install) recipe.chef_gem "foo" do compile_time false end end end describe "when Chef::Config[:chef_gem_compile_time] is explicitly false" do let(:chef_gem_compile_time) { false } before do expect(Chef::Log).not_to receive(:deprecation) end it "by default does not install at compile-time" do expect(resource).not_to receive(:run_action).with(:install) recipe.chef_gem "foo" end it "compile_time true installs at compile-time" do expect(resource).to receive(:run_action).with(:install) recipe.chef_gem "foo" do compile_time true end end it "compile_time false does not install at compile-time" do expect(resource).not_to receive(:run_action).with(:install) recipe.chef_gem "foo" do compile_time false end end end end end chef-12.14.60/spec/unit/resource/chocolatey_package_spec.rb000066400000000000000000000042331276456504500235760ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Resource::ChocolateyPackage do let(:resource) { Chef::Resource::ChocolateyPackage.new("fakey_fakerton") } it "should create a new Chef::Resource::ChocolateyPackage" do expect(resource).to be_a_kind_of(Chef::Resource) expect(resource).to be_a_kind_of(Chef::Resource::Package) expect(resource).to be_a_instance_of(Chef::Resource::ChocolateyPackage) end it "should have a resource name of :python" do expect(resource.resource_name).to eql(:chocolatey_package) end it "should coerce its name to a package_name array" do expect(resource.package_name).to eql(["fakey_fakerton"]) end it "the package_name setter should coerce to arrays" do resource.package_name("git") expect(resource.package_name).to eql(["git"]) end it "the package_name setter should accept arrays" do resource.package_name(%w{git unzip}) expect(resource.package_name).to eql(%w{git unzip}) end it "the name should accept arrays" do resource = Chef::Resource::ChocolateyPackage.new(%w{git unzip}) expect(resource.package_name).to eql(%w{git unzip}) end it "the default version should be nil" do expect(resource.version).to eql(nil) end it "the version setter should coerce to arrays" do resource.version("1.2.3") expect(resource.version).to eql(["1.2.3"]) end it "the version setter should accept arrays" do resource.version(["1.2.3", "4.5.6"]) expect(resource.version).to eql(["1.2.3", "4.5.6"]) end end chef-12.14.60/spec/unit/resource/conditional_action_not_nothing_spec.rb000066400000000000000000000025701276456504500262410ustar00rootroot00000000000000# # Author:: Xabier de Zuazo () # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::ConditionalActionNotNothing do describe "after running a :nothing action" do before do @action = :nothing @conditional = Chef::Resource::ConditionalActionNotNothing.new(@action) end it "indicates that resource convergence should not continue" do expect(@conditional.continue?).to be_falsey end end describe "after running an action different to :nothing" do before do @action = :something @conditional = Chef::Resource::ConditionalActionNotNothing.new(@action) end it "indicates that resource convergence should continue" do expect(@conditional.continue?).to be_truthy end end end chef-12.14.60/spec/unit/resource/conditional_spec.rb000066400000000000000000000221061276456504500222730ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "ostruct" describe Chef::Resource::Conditional do before do allow_any_instance_of(Mixlib::ShellOut).to receive(:run_command).and_return(nil) @status = OpenStruct.new(:success? => true) allow_any_instance_of(Mixlib::ShellOut).to receive(:status).and_return(@status) @parent_resource = Chef::Resource.new(nil, Chef::Node.new) end it "raises an exception when neither a block or command is given" do expect { Chef::Resource::Conditional.send(:new, :always, @parent_resource, nil, {}) }.to raise_error(ArgumentError, /requires either a command or a block/) end it "does not evaluate a guard interpreter on initialization of the conditional" do expect_any_instance_of(Chef::Resource::Conditional).not_to receive(:configure) expect(Chef::GuardInterpreter::DefaultGuardInterpreter).not_to receive(:new) expect(Chef::GuardInterpreter::ResourceGuardInterpreter).not_to receive(:new) Chef::Resource::Conditional.only_if(@parent_resource, "true") end describe "configure" do it "raises an exception when a guard_interpreter is specified and a block is given" do @parent_resource.guard_interpreter :canadian_mounties conditional = Chef::Resource::Conditional.send(:new, :always, @parent_resource, nil, {}) { True } expect { conditional.configure }.to raise_error(ArgumentError, /does not support blocks/) end end describe "when created as an `only_if`" do describe "after running a successful command given as a string" do before do @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "true") end it "indicates that resource convergence should continue" do expect(@conditional.continue?).to be_truthy end end describe "after running a negative/false command given as a string" do before do @status.send("success?=", false) @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "false") end it "indicates that resource convergence should not continue" do expect(@conditional.continue?).to be_falsey end end describe "after running a successful command given as an array" do before do @conditional = Chef::Resource::Conditional.only_if(@parent_resource, ["true"]) end it "indicates that resource convergence should continue" do expect(@conditional.continue?).to be true end end describe "after running a negative/false command given as an array" do before do @status.send("success?=", false) @conditional = Chef::Resource::Conditional.only_if(@parent_resource, ["false"]) end it "indicates that resource convergence should not continue" do expect(@conditional.continue?).to be false end end describe "after running a command which timed out" do before do @conditional = Chef::Resource::Conditional.only_if(@parent_resource, "false") allow_any_instance_of(Chef::GuardInterpreter::DefaultGuardInterpreter).to receive(:shell_out).and_raise(Chef::Exceptions::CommandTimeout) end it "indicates that resource convergence should not continue" do expect(@conditional.continue?).to be_falsey end it "should log a warning" do expect(Chef::Log).to receive(:warn).with("Command 'false' timed out") @conditional.continue? end end describe "after running a block that returns a truthy value" do before do @conditional = Chef::Resource::Conditional.only_if(@parent_resource) { Object.new } end it "indicates that resource convergence should continue" do expect(@conditional.continue?).to be_truthy end end describe "after running a block that returns a falsey value" do before do @conditional = Chef::Resource::Conditional.only_if(@parent_resource) { nil } end it "indicates that resource convergence should not continue" do expect(@conditional.continue?).to be_falsey end end describe "after running a block that returns a string value" do before do @conditional = Chef::Resource::Conditional.only_if(@parent_resource) { "some command" } end it "logs a warning" do expect(Chef::Log).to receive(:warn).with("only_if block for [] returned \"some command\", did you mean to run a command? If so use 'only_if \"some command\"' in your code.") @conditional.evaluate end end describe "after running a block that returns a string value on a sensitive resource" do before do @parent_resource.sensitive(true) @conditional = Chef::Resource::Conditional.only_if(@parent_resource) { "some command" } end it "logs a warning" do expect(Chef::Log).to receive(:warn).with("only_if block for [] returned a string, did you mean to run a command?") @conditional.evaluate end end end describe "when created as a `not_if`" do describe "after running a successful/true command given as a string" do before do @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "true") end it "indicates that resource convergence should not continue" do expect(@conditional.continue?).to be_falsey end end describe "after running a failed/false command given as a string" do before do @status.send("success?=", false) @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "false") end it "indicates that resource convergence should continue" do expect(@conditional.continue?).to be_truthy end end describe "after running a successful/true command given as an array" do before do @conditional = Chef::Resource::Conditional.not_if(@parent_resource, ["true"]) end it "indicates that resource convergence should not continue" do expect(@conditional.continue?).to be false end end describe "after running a failed/false command given as an array" do before do @status.send("success?=", false) @conditional = Chef::Resource::Conditional.not_if(@parent_resource, ["false"]) end it "indicates that resource convergence should continue" do expect(@conditional.continue?).to be true end end describe "after running a command which timed out" do before do @conditional = Chef::Resource::Conditional.not_if(@parent_resource, "false") allow_any_instance_of(Chef::GuardInterpreter::DefaultGuardInterpreter).to receive(:shell_out).and_raise(Chef::Exceptions::CommandTimeout) end it "indicates that resource convergence should continue" do expect(@conditional.continue?).to be_truthy end it "should log a warning" do expect(Chef::Log).to receive(:warn).with("Command 'false' timed out") @conditional.continue? end end describe "after running a block that returns a truthy value" do before do @conditional = Chef::Resource::Conditional.not_if(@parent_resource) { Object.new } end it "indicates that resource convergence should not continue" do expect(@conditional.continue?).to be_falsey end end describe "after running a block that returns a falsey value" do before do @conditional = Chef::Resource::Conditional.not_if(@parent_resource) { nil } end it "indicates that resource convergence should continue" do expect(@conditional.continue?).to be_truthy end end describe "after running a block that returns a string value" do before do @conditional = Chef::Resource::Conditional.not_if(@parent_resource) { "some command" } end it "logs a warning" do expect(Chef::Log).to receive(:warn).with("not_if block for [] returned \"some command\", did you mean to run a command? If so use 'not_if \"some command\"' in your code.") @conditional.evaluate end end describe "after running a block that returns a string value on a sensitive resource" do before do @parent_resource.sensitive(true) @conditional = Chef::Resource::Conditional.not_if(@parent_resource) { "some command" } end it "logs a warning" do expect(Chef::Log).to receive(:warn).with("not_if block for [] returned a string, did you mean to run a command?") @conditional.evaluate end end end end chef-12.14.60/spec/unit/resource/cookbook_file_spec.rb000066400000000000000000000057641276456504500226100ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Tyler Cloke () # Copyright:: Copyright 2010-2016, Chef Software, Inc. #p License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::CookbookFile do before do @cookbook_file = Chef::Resource::CookbookFile.new("sourcecode_tarball.tgz") end it "uses the name parameter for the source parameter" do expect(@cookbook_file.name).to eq("sourcecode_tarball.tgz") end it "has a source parameter" do @cookbook_file.name("config_file.conf") expect(@cookbook_file.name).to eq("config_file.conf") end it "defaults to a nil cookbook parameter (current cookbook will be used)" do expect(@cookbook_file.cookbook).to be_nil end it "has a cookbook parameter" do @cookbook_file.cookbook("munin") expect(@cookbook_file.cookbook).to eq("munin") end it "sets the provider to Chef::Provider::CookbookFile" do expect(@cookbook_file.provider).to eq(Chef::Provider::CookbookFile) end describe "when it has a backup number, group, mode, owner, source, checksum, and cookbook on nix or path, rights, deny_rights, checksum on windows" do before do if Chef::Platform.windows? @cookbook_file.path("C:/temp/origin/file.txt") @cookbook_file.rights(:read, "Everyone") @cookbook_file.deny_rights(:full_control, "Clumsy_Sam") else @cookbook_file.path("/tmp/origin/file.txt") @cookbook_file.group("wheel") @cookbook_file.mode("0664") @cookbook_file.owner("root") @cookbook_file.source("/tmp/foo.txt") @cookbook_file.cookbook("/tmp/cookbooks/cooked.rb") end @cookbook_file.checksum("1" * 64) end it "describes the state" do state = @cookbook_file.state if Chef::Platform.windows? puts state expect(state[:rights]).to eq([{ :permissions => :read, :principals => "Everyone" }]) expect(state[:deny_rights]).to eq([{ :permissions => :full_control, :principals => "Clumsy_Sam" }]) else expect(state[:group]).to eq("wheel") expect(state[:mode]).to eq("0664") expect(state[:owner]).to eq("root") end expect(state[:checksum]).to eq("1" * 64) end it "returns the path as its identity" do if Chef::Platform.windows? expect(@cookbook_file.identity).to eq("C:/temp/origin/file.txt") else expect(@cookbook_file.identity).to eq("/tmp/origin/file.txt") end end end end chef-12.14.60/spec/unit/resource/cron_spec.rb000066400000000000000000000124051276456504500207320ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Author:: Tyler Cloke () # Copyright:: Copyright 2009-2016, Bryan McLellan # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Cron do before(:each) do @resource = Chef::Resource::Cron.new("cronify") end it "should create a new Chef::Resource::Cron" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Cron) end it "should have a name" do expect(@resource.name).to eql("cronify") end it "should have a default action of 'create'" do expect(@resource.action).to eql([:create]) end it "should accept create or delete for action" do expect { @resource.action :create }.not_to raise_error expect { @resource.action :delete }.not_to raise_error expect { @resource.action :lolcat }.to raise_error(ArgumentError) end it "should allow you to set a command" do @resource.command "/bin/true" expect(@resource.command).to eql("/bin/true") end it "should allow you to set a user" do @resource.user "daemon" expect(@resource.user).to eql("daemon") end it "should allow you to specify the minute" do @resource.minute "30" expect(@resource.minute).to eql("30") end it "should allow you to specify the hour" do @resource.hour "6" expect(@resource.hour).to eql("6") end it "should allow you to specify the day" do @resource.day "10" expect(@resource.day).to eql("10") end it "should allow you to specify the month" do @resource.month "10" expect(@resource.month).to eql("10") end it "should allow you to specify the weekday" do @resource.weekday "2" expect(@resource.weekday).to eql("2") end it "should allow you to specify the mailto variable" do @resource.mailto "test@example.com" expect(@resource.mailto).to eql("test@example.com") end it "should allow you to specify the path" do @resource.path "/usr/bin:/usr/sbin" expect(@resource.path).to eql("/usr/bin:/usr/sbin") end it "should allow you to specify the home directory" do @resource.home "/root" expect(@resource.home).to eql("/root") end it "should allow you to specify the shell to run the command with" do @resource.shell "/bin/zsh" expect(@resource.shell).to eql("/bin/zsh") end it "should allow you to specify environment variables hash" do env = { "TEST" => "LOL" } @resource.environment env expect(@resource.environment).to eql(env) end it "should allow * for all time and date values" do %w{minute hour day month weekday}.each do |x| expect(@resource.send(x, "*")).to eql("*") end end it "should allow ranges for all time and date values" do %w{minute hour day month weekday}.each do |x| expect(@resource.send(x, "1-2,5")).to eql("1-2,5") end end it "should have a default value of * for all time and date values" do %w{minute hour day month weekday}.each do |x| expect(@resource.send(x)).to eql("*") end end it "should have a default value of root for the user" do expect(@resource.user).to eql("root") end it "should reject any minute over 59" do expect { @resource.minute "60" }.to raise_error(RangeError) end it "should reject any hour over 23" do expect { @resource.hour "24" }.to raise_error(RangeError) end it "should reject any day over 31" do expect { @resource.day "32" }.to raise_error(RangeError) end it "should reject any month over 12" do expect { @resource.month "13" }.to raise_error(RangeError) end describe "weekday" do it "should reject any weekday over 7" do expect { @resource.weekday "8" }.to raise_error(RangeError) end it "should reject any symbols which don't represent day of week" do expect { @resource.weekday :foo }.to raise_error(RangeError) end end it "should convert integer schedule values to a string" do %w{minute hour day month weekday}.each do |x| expect(@resource.send(x, 5)).to eql("5") end end describe "when it has a time (minute, hour, day, month, weeekend) and user" do before do @resource.command("tackle") @resource.minute("1") @resource.hour("2") @resource.day("3") @resource.month("4") @resource.weekday("5") @resource.user("root") end it "describes the state" do state = @resource.state expect(state[:minute]).to eq("1") expect(state[:hour]).to eq("2") expect(state[:day]).to eq("3") expect(state[:month]).to eq("4") expect(state[:weekday]).to eq("5") expect(state[:user]).to eq("root") end it "returns the command as its identity" do expect(@resource.identity).to eq("tackle") end end end chef-12.14.60/spec/unit/resource/csh_spec.rb000066400000000000000000000022571276456504500205520ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Resource::Csh do before(:each) do @resource = Chef::Resource::Csh.new("fakey_fakerton") end it "should create a new Chef::Resource::Csh" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Csh) end it "should have a resource name of :csh" do expect(@resource.resource_name).to eql(:csh) end it "should have an interpreter of csh" do expect(@resource.interpreter).to eql("csh") end end chef-12.14.60/spec/unit/resource/deploy_revision_spec.rb000066400000000000000000000023031276456504500231770ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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 "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::DeployRevision do static_provider_resolution( resource: Chef::Resource::DeployRevision, provider: Chef::Provider::Deploy::Revision, name: :deploy_revision, action: :deploy ) end describe Chef::Resource::DeployBranch do static_provider_resolution( resource: Chef::Resource::DeployBranch, provider: Chef::Provider::Deploy::Revision, name: :deploy_branch, action: :deploy ) end chef-12.14.60/spec/unit/resource/deploy_spec.rb000066400000000000000000000244661276456504500212770ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::Deploy do static_provider_resolution( resource: Chef::Resource::Deploy, provider: Chef::Provider::Deploy::Timestamped, name: :deploy, action: :deploy ) class << self def resource_has_a_string_attribute(attr_name) it "has a String attribute for #{attr_name}" do @resource.send(attr_name, "this is a string") expect(@resource.send(attr_name)).to eql("this is a string") expect { @resource.send(attr_name, 8675309) }.to raise_error(ArgumentError) end end def resource_has_a_boolean_attribute(attr_name, opts = { :defaults_to => false }) it "has a Boolean attribute for #{attr_name}" do expect(@resource.send(attr_name)).to eql(opts[:defaults_to]) @resource.send(attr_name, !opts[:defaults_to]) expect(@resource.send(attr_name)).to eql( !opts[:defaults_to] ) end end def resource_has_a_callback_attribute(attr_name) it "has a Callback attribute #{attr_name}" do callback_block = lambda { :noop } expect { @resource.send(attr_name, &callback_block) }.not_to raise_error expect(@resource.send(attr_name)).to eq(callback_block) callback_file = "path/to/callback.rb" expect { @resource.send(attr_name, callback_file) }.not_to raise_error expect(@resource.send(attr_name)).to eq(callback_file) expect { @resource.send(attr_name, :this_is_fail) }.to raise_error(ArgumentError) end end end before do @resource = Chef::Resource::Deploy.new("/my/deploy/dir") end resource_has_a_string_attribute(:repo) resource_has_a_string_attribute(:deploy_to) resource_has_a_string_attribute(:role) resource_has_a_string_attribute(:restart_command) resource_has_a_string_attribute(:migration_command) resource_has_a_string_attribute(:user) resource_has_a_string_attribute(:group) resource_has_a_string_attribute(:repository_cache) resource_has_a_string_attribute(:copy_exclude) resource_has_a_string_attribute(:revision) resource_has_a_string_attribute(:remote) resource_has_a_string_attribute(:git_ssh_wrapper) resource_has_a_string_attribute(:svn_username) resource_has_a_string_attribute(:svn_password) resource_has_a_string_attribute(:svn_arguments) resource_has_a_string_attribute(:svn_info_args) resource_has_a_boolean_attribute(:migrate, :defaults_to => false) resource_has_a_boolean_attribute(:enable_submodules, :defaults_to => false) resource_has_a_boolean_attribute(:shallow_clone, :defaults_to => false) it "uses the first argument as the deploy directory" do expect(@resource.deploy_to).to eql("/my/deploy/dir") end # For git, any revision, branch, tag, whatever is resolved to a SHA1 ref. # For svn, the branch is included in the repo URL. # Therefore, revision and branch ARE NOT SEPARATE THINGS it "aliases #revision as #branch" do @resource.branch "stable" expect(@resource.revision).to eql("stable") end it "takes the SCM resource to use as a constant, and defaults to git" do expect(@resource.scm_provider).to eql(Chef::Provider::Git) @resource.scm_provider Chef::Provider::Subversion expect(@resource.scm_provider).to eql(Chef::Provider::Subversion) end it "allows scm providers to be set via symbol" do expect(@resource.scm_provider).to eq(Chef::Provider::Git) @resource.scm_provider :subversion expect(@resource.scm_provider).to eq(Chef::Provider::Subversion) end it "allows scm providers to be set via string" do expect(@resource.scm_provider).to eq(Chef::Provider::Git) @resource.scm_provider "subversion" expect(@resource.scm_provider).to eq(Chef::Provider::Subversion) end it "has a boolean attribute for svn_force_export defaulting to false" do expect(@resource.svn_force_export).to be_falsey @resource.svn_force_export true expect(@resource.svn_force_export).to be_truthy expect { @resource.svn_force_export(10053) }.to raise_error(ArgumentError) end it "takes arbitrary environment variables in a hash" do @resource.environment "RAILS_ENV" => "production" expect(@resource.environment).to eq({ "RAILS_ENV" => "production" }) end it "takes string arguments to environment for backwards compat, setting RAILS_ENV, RACK_ENV, and MERB_ENV" do @resource.environment "production" expect(@resource.environment).to eq({ "RAILS_ENV" => "production", "RACK_ENV" => "production", "MERB_ENV" => "production" }) end it "sets destination to $deploy_to/shared/$repository_cache" do expect(@resource.destination).to eql("/my/deploy/dir/shared/cached-copy") end it "sets shared_path to $deploy_to/shared" do expect(@resource.shared_path).to eql("/my/deploy/dir/shared") end it "sets current_path to $deploy_to/current" do expect(@resource.current_path).to eql("/my/deploy/dir/current") end it "gets the current_path correct even if the shared_path is set (regression test)" do @resource.shared_path expect(@resource.current_path).to eql("/my/deploy/dir/current") end it "allows depth to be set via integer" do expect(@resource.depth).to be_nil @resource.depth 1 expect(@resource.depth).to eql(1) end it "gives #depth as 5 if shallow clone is true, nil otherwise" do expect(@resource.depth).to be_nil @resource.shallow_clone true expect(@resource.depth).to eql(5) end it "aliases repo as repository" do @resource.repository "git@github.com/opcode/cookbooks.git" expect(@resource.repo).to eql("git@github.com/opcode/cookbooks.git") end it "aliases git_ssh_wrapper as ssh_wrapper" do @resource.ssh_wrapper "git_my_repo.sh" expect(@resource.git_ssh_wrapper).to eql("git_my_repo.sh") end it "has an Array attribute purge_before_symlink, default: log, tmp/pids, public/system" do expect(@resource.purge_before_symlink).to eq(%w{ log tmp/pids public/system }) @resource.purge_before_symlink %w{foo bar baz} expect(@resource.purge_before_symlink).to eq(%w{foo bar baz}) end it "has an Array attribute create_dirs_before_symlink, default: tmp, public, config" do expect(@resource.create_dirs_before_symlink).to eq(%w{tmp public config}) @resource.create_dirs_before_symlink %w{foo bar baz} expect(@resource.create_dirs_before_symlink).to eq(%w{foo bar baz}) end it 'has a Hash attribute symlinks, default: {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"}' do default = { "system" => "public/system", "pids" => "tmp/pids", "log" => "log" } expect(@resource.symlinks).to eq(default) @resource.symlinks "foo" => "bar/baz" expect(@resource.symlinks).to eq({ "foo" => "bar/baz" }) end it 'has a Hash attribute symlink_before_migrate, default "config/database.yml" => "config/database.yml"' do expect(@resource.symlink_before_migrate).to eq({ "config/database.yml" => "config/database.yml" }) @resource.symlink_before_migrate "wtf?" => "wtf is going on" expect(@resource.symlink_before_migrate).to eq({ "wtf?" => "wtf is going on" }) end resource_has_a_callback_attribute :before_migrate resource_has_a_callback_attribute :before_symlink resource_has_a_callback_attribute :before_restart resource_has_a_callback_attribute :after_restart it "aliases restart_command as restart" do @resource.restart "foobaz" expect(@resource.restart_command).to eq("foobaz") end it "takes a block for the restart parameter" do restart_like_this = lambda { p :noop } @resource.restart(&restart_like_this) expect(@resource.restart).to eq(restart_like_this) end it "allows providers to be set with a full class name" do @resource.provider Chef::Provider::Deploy::Timestamped expect(@resource.provider).to eq(Chef::Provider::Deploy::Timestamped) end it "allows deploy providers to be set via symbol" do @resource.provider :revision expect(@resource.provider).to eq(Chef::Provider::Deploy::Revision) end it "allows deploy providers to be set via string" do @resource.provider "revision" expect(@resource.provider).to eq(Chef::Provider::Deploy::Revision) end it "defaults keep_releases to 5" do expect(@resource.keep_releases).to eq(5) end it "allows keep_releases to be set via integer" do @resource.keep_releases 10 expect(@resource.keep_releases).to eq(10) end it "enforces a minimum keep_releases of 1" do @resource.keep_releases 0 expect(@resource.keep_releases).to eq(1) end describe "when it has a timeout attribute" do let(:ten_seconds) { 10 } before { @resource.timeout(ten_seconds) } it "stores this timeout" do expect(@resource.timeout).to eq(ten_seconds) end end describe "when it has no timeout attribute" do it "should have no default timeout" do expect(@resource.timeout).to be_nil end end describe "when it has meta application root, revision, user, group, scm provider, repository cache, environment, simlinks and migrate" do before do @resource.repository("http://uri.org") @resource.deploy_to("/") @resource.revision("1.2.3") @resource.user("root") @resource.group("pokemon") @resource.scm_provider(Chef::Provider::Git) @resource.repository_cache("cached-copy") @resource.environment({ "SUDO" => "TRUE" }) @resource.symlinks({ "system" => "public/system" }) @resource.migrate(false) end it "describes its state" do state = @resource.state expect(state[:deploy_to]).to eq("/") expect(state[:revision]).to eq("1.2.3") end it "returns the repository URI as its identity" do expect(@resource.identity).to eq("http://uri.org") end end end chef-12.14.60/spec/unit/resource/directory_spec.rb000066400000000000000000000051371276456504500220010ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # 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::Resource::Directory do before(:each) do @resource = Chef::Resource::Directory.new("fakey_fakerton") end it "should create a new Chef::Resource::Directory" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Directory) end it "should have a name" do expect(@resource.name).to eql("fakey_fakerton") end it "should have a default action of 'create'" do expect(@resource.action).to eql([:create]) end it "should accept create or delete for action" do expect { @resource.action :create }.not_to raise_error expect { @resource.action :delete }.not_to raise_error expect { @resource.action :blues }.to raise_error(ArgumentError) end it "should use the object name as the path by default" do expect(@resource.path).to eql("fakey_fakerton") end it "should accept a string as the path" do expect { @resource.path "/tmp" }.not_to raise_error expect(@resource.path).to eql("/tmp") expect { @resource.path Hash.new }.to raise_error(ArgumentError) end it "should allow you to have specify whether the action is recursive with true/false" do expect { @resource.recursive true }.not_to raise_error expect { @resource.recursive false }.not_to raise_error expect { @resource.recursive "monkey" }.to raise_error(ArgumentError) end describe "when it has group, mode, and owner" do before do @resource.path("/tmp/foo/bar/") @resource.group("wheel") @resource.mode("0664") @resource.owner("root") end it "describes its state" do state = @resource.state expect(state[:group]).to eq("wheel") expect(state[:mode]).to eq("0664") expect(state[:owner]).to eq("root") end it "returns the directory path as its identity" do expect(@resource.identity).to eq("/tmp/foo/bar/") end end end chef-12.14.60/spec/unit/resource/dpkg_package_spec.rb000066400000000000000000000017721276456504500223760ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::DpkgPackage, "initialize" do static_provider_resolution( resource: Chef::Resource::DpkgPackage, provider: Chef::Provider::Package::Dpkg, name: :dpkg_package, action: :install, os: "linux" ) end chef-12.14.60/spec/unit/resource/dsc_resource_spec.rb000066400000000000000000000072551276456504500224600ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::DscResource do let(:dsc_test_resource_name) { "DSCTest" } let(:dsc_test_property_name) { :DSCTestProperty } let(:dsc_test_property_value) { "DSCTestValue" } let(:dsc_test_reboot_action) { :reboot_now } let(:dsc_test_timeout) { 101 } context "when Powershell supports Dsc" do let(:dsc_test_run_context) do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = "5.0.10018.0" empty_events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, empty_events) end let(:dsc_test_resource) do Chef::Resource::DscResource.new(dsc_test_resource_name, dsc_test_run_context) end it "has a default action of `:run`" do expect(dsc_test_resource.action).to eq([:run]) end it "has an ed_actions attribute with only the `:run` and `:nothing` attributes" do expect(dsc_test_resource.allowed_actions.to_set).to eq([:run, :nothing].to_set) end it "allows the resource attribute to be set" do dsc_test_resource.resource(dsc_test_resource_name) expect(dsc_test_resource.resource).to eq(dsc_test_resource_name) end it "allows the module_name attribute to be set" do dsc_test_resource.module_name(dsc_test_resource_name) expect(dsc_test_resource.module_name).to eq(dsc_test_resource_name) end it "allows the reboot_action attribute to be set" do dsc_test_resource.reboot_action(dsc_test_reboot_action) expect(dsc_test_resource.reboot_action).to eq(dsc_test_reboot_action) end it "allows the timeout attribute to be set" do dsc_test_resource.timeout(dsc_test_timeout) expect(dsc_test_resource.timeout).to eq(dsc_test_timeout) end context "when setting a dsc property" do it "allows setting a dsc property with a property name of type Symbol" do dsc_test_resource.property(dsc_test_property_name, dsc_test_property_value) expect(dsc_test_resource.property(dsc_test_property_name)).to eq(dsc_test_property_value) expect(dsc_test_resource.properties[dsc_test_property_name]).to eq(dsc_test_property_value) end it "raises a TypeError if property_name is not a symbol" do expect do dsc_test_resource.property("Foo", dsc_test_property_value) end.to raise_error(TypeError) end context "when using DelayedEvaluators" do it "allows setting a dsc property with a property name of type Symbol" do dsc_test_resource.property(dsc_test_property_name, Chef::DelayedEvaluator.new do dsc_test_property_value end) expect(dsc_test_resource.property(dsc_test_property_name)).to eq(dsc_test_property_value) expect(dsc_test_resource.properties[dsc_test_property_name]).to eq(dsc_test_property_value) end end end context "Powershell DSL methods" do it "responds to :ps_credential" do expect(dsc_test_resource.respond_to?(:ps_credential)).to be true end end end end chef-12.14.60/spec/unit/resource/dsc_script_spec.rb000066400000000000000000000134601276456504500221300ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::DscScript do let(:dsc_test_resource_name) { "DSCTest" } context "when Powershell supports Dsc" do let(:dsc_test_run_context) do node = Chef::Node.new node.automatic[:languages][:powershell][:version] = "4.0" empty_events = Chef::EventDispatch::Dispatcher.new Chef::RunContext.new(node, {}, empty_events) end let(:dsc_test_resource) do Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context) end let(:configuration_code) { 'echo "This is supposed to create a configuration document."' } let(:configuration_path) { "c:/myconfigs/formatc.ps1" } let(:configuration_name) { "formatme" } let(:configuration_data) { '@{AllNodes = @( @{ NodeName = "localhost"; PSDscAllowPlainTextPassword = $true })}' } let(:configuration_data_script) { "c:/myconfigs/data/safedata.psd1" } it "has a default action of `:run`" do expect(dsc_test_resource.action).to eq([:run]) end it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do expect(dsc_test_resource.allowed_actions.to_set).to eq([:run, :nothing].to_set) end it "allows the code attribute to be set" do dsc_test_resource.code(configuration_code) expect(dsc_test_resource.code).to eq(configuration_code) end it "allows the command attribute to be set" do dsc_test_resource.command(configuration_path) expect(dsc_test_resource.command).to eq(configuration_path) end it "allows the configuration_name attribute to be set" do dsc_test_resource.configuration_name(configuration_name) expect(dsc_test_resource.configuration_name).to eq(configuration_name) end it "allows the configuration_data attribute to be set" do dsc_test_resource.configuration_data(configuration_data) expect(dsc_test_resource.configuration_data).to eq(configuration_data) end it "allows the configuration_data_script attribute to be set" do dsc_test_resource.configuration_data_script(configuration_data_script) expect(dsc_test_resource.configuration_data_script).to eq(configuration_data_script) end it "has the ps_credential helper method" do expect(dsc_test_resource).to respond_to(:ps_credential) end context "when calling imports" do let(:module_name) { "FooModule" } let(:module_name_b) { "BarModule" } let(:dsc_resources) { %w{ResourceA ResourceB} } it "allows an arbitrary number of resources to be set for a module to be set" do dsc_test_resource.imports module_name, *dsc_resources module_imports = dsc_test_resource.imports[module_name] expect(module_imports).to eq(dsc_resources) end it "adds * to the imports when no resources are set for a moudle" do dsc_test_resource.imports module_name module_imports = dsc_test_resource.imports[module_name] expect(module_imports).to eq(["*"]) end it "allows an arbitrary number of modules" do dsc_test_resource.imports module_name dsc_test_resource.imports module_name_b expect(dsc_test_resource.imports).to have_key(module_name) expect(dsc_test_resource.imports).to have_key(module_name_b) end it "allows resources to be added for a module" do dsc_test_resource.imports module_name, dsc_resources[0] dsc_test_resource.imports module_name, dsc_resources[1] module_imports = dsc_test_resource.imports[module_name] expect(module_imports).to eq(dsc_resources) end end it "raises an ArgumentError exception if an attempt is made to set the code attribute when the command attribute is already set" do dsc_test_resource.command(configuration_path) expect { dsc_test_resource.code(configuration_code) }.to raise_error(ArgumentError) end it "raises an ArgumentError exception if an attempt is made to set the command attribute when the code attribute is already set" do dsc_test_resource.code(configuration_code) expect { dsc_test_resource.command(configuration_path) }.to raise_error(ArgumentError) end it "raises an ArgumentError exception if an attempt is made to set the configuration_name attribute when the code attribute is already set" do dsc_test_resource.code(configuration_code) expect { dsc_test_resource.configuration_name(configuration_name) }.to raise_error(ArgumentError) end it "raises an ArgumentError exception if an attempt is made to set the configuration_data attribute when the configuration_data_script attribute is already set" do dsc_test_resource.configuration_data_script(configuration_data_script) expect { dsc_test_resource.configuration_data(configuration_data) }.to raise_error(ArgumentError) end it "raises an ArgumentError exception if an attempt is made to set the configuration_data_script attribute when the configuration_data attribute is already set" do dsc_test_resource.configuration_data(configuration_data) expect { dsc_test_resource.configuration_data_script(configuration_data_script) }.to raise_error(ArgumentError) end end end chef-12.14.60/spec/unit/resource/easy_install_package_spec.rb000066400000000000000000000024671276456504500241420ustar00rootroot00000000000000# # Author:: Joe Williams () # Copyright:: Copyright 2009-2016, Joe Williams # 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 "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::EasyInstallPackage, "initialize" do static_provider_resolution( resource: Chef::Resource::EasyInstallPackage, provider: Chef::Provider::Package::EasyInstall, name: :easy_install_package, action: :install ) before(:each) do @resource = Chef::Resource::EasyInstallPackage.new("foo") end it "should allow you to set the easy_install_binary attribute" do @resource.easy_install_binary "/opt/local/bin/easy_install" expect(@resource.easy_install_binary).to eql("/opt/local/bin/easy_install") end end chef-12.14.60/spec/unit/resource/env_spec.rb000066400000000000000000000046621276456504500205670ustar00rootroot00000000000000# # Author:: Doug MacEachern () # Author:: Tyler Cloke () # Copyright:: Copyright 2010-2016, VMware, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Env do before(:each) do @resource = Chef::Resource::Env.new("FOO") end it "should create a new Chef::Resource::Env" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Env) end it "should have a name" do expect(@resource.name).to eql("FOO") end it "should have a default action of 'create'" do expect(@resource.action).to eql([:create]) end { :create => false, :delete => false, :modify => false, :flibber => true }.each do |action, bad_value| it "should #{bad_value ? 'not' : ''} accept #{action}" do if bad_value expect { @resource.action action }.to raise_error(ArgumentError) else expect { @resource.action action }.not_to raise_error end end end it "should use the object name as the key_name by default" do expect(@resource.key_name).to eql("FOO") end it "should accept a string as the env value via 'value'" do expect { @resource.value "bar" }.not_to raise_error end it "should not accept a Hash for the env value via 'to'" do expect { @resource.value Hash.new }.to raise_error(ArgumentError) end it "should allow you to set an env value via 'to'" do @resource.value "bar" expect(@resource.value).to eql("bar") end describe "when it has key name and value" do before do @resource.key_name("charmander") @resource.value("level7") @resource.delim("hi") end it "describes its state" do state = @resource.state expect(state[:value]).to eq("level7") end it "returns the key name as its identity" do expect(@resource.identity).to eq("charmander") end end end chef-12.14.60/spec/unit/resource/erl_call_spec.rb000066400000000000000000000045301276456504500215460ustar00rootroot00000000000000# # Author:: Joe Williams () # Author:: Tyler Cloke () # Copyright:: Copyright 2009-2016, Joe Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::ErlCall do before(:each) do @resource = Chef::Resource::ErlCall.new("fakey_fakerton") end it "should create a new Chef::Resource::ErlCall" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::ErlCall) end it "should have a resource name of :erl_call" do expect(@resource.resource_name).to eql(:erl_call) end it "should have a default action of run" do expect(@resource.action).to eql([:run]) end it "should accept run as an action" do expect { @resource.action :run }.not_to raise_error end it "should allow you to set the code attribute" do @resource.code "q()." expect(@resource.code).to eql("q().") end it "should allow you to set the cookie attribute" do @resource.cookie "nomnomnom" expect(@resource.cookie).to eql("nomnomnom") end it "should allow you to set the distributed attribute" do @resource.distributed true expect(@resource.distributed).to eql(true) end it "should allow you to set the name_type attribute" do @resource.name_type "sname" expect(@resource.name_type).to eql("sname") end it "should allow you to set the node_name attribute" do @resource.node_name "chef@erlang" expect(@resource.node_name).to eql("chef@erlang") end describe "when it has cookie and node_name" do before do @resource.code("erl-call:function()") @resource.cookie("cookie") @resource.node_name("raster") end it "returns the code as its identity" do expect(@resource.identity).to eq("erl-call:function()") end end end chef-12.14.60/spec/unit/resource/execute_spec.rb000066400000000000000000000023031276456504500214270ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # 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::Resource::Execute do let(:resource_instance_name) { "some command" } let(:execute_resource) { Chef::Resource::Execute.new(resource_instance_name) } it_behaves_like "an execute resource" it "default guard interpreter should be :execute interpreter" do expect(execute_resource.guard_interpreter).to be(:execute) end it "defaults to not being a guard interpreter" do expect(execute_resource.is_guard_interpreter).to eq(false) end end chef-12.14.60/spec/unit/resource/file/000077500000000000000000000000001276456504500173475ustar00rootroot00000000000000chef-12.14.60/spec/unit/resource/file/verification_spec.rb000066400000000000000000000122601276456504500233710ustar00rootroot00000000000000# # Author:: Steven Danna () # Copyright:: Copyright 2014-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::File::Verification do let(:t_block) { Proc.new { true } } let(:f_block) { Proc.new { false } } let(:path_block) { Proc.new { |path| path } } let(:temp_path) { "/tmp/foobar" } describe "verification registration" do it "registers a verification for later use" do class Chef::Resource::File::Verification::Wombat < Chef::Resource::File::Verification provides :tabmow end expect(Chef::Resource::File::Verification.lookup(:tabmow)).to eq(Chef::Resource::File::Verification::Wombat) end it "raises an error if a verification can't be found" do expect { Chef::Resource::File::Verification.lookup(:dne) }.to raise_error(Chef::Exceptions::VerificationNotFound) end end describe "#verify" do let(:parent_resource) { Chef::Resource.new("llama") } it "expects a string argument" do v = Chef::Resource::File::Verification.new(parent_resource, nil, {}) {} expect { v.verify("/foo/bar") }.to_not raise_error expect { v.verify }.to raise_error(ArgumentError) end it "accepts an options hash" do v = Chef::Resource::File::Verification.new(parent_resource, nil, {}) {} expect { v.verify("/foo/bar", { :future => true }) }.to_not raise_error end context "with a verification block" do it "passes a file path to the block" do v = Chef::Resource::File::Verification.new(parent_resource, nil, {}, &path_block) expect(v.verify(temp_path)).to eq(temp_path) end it "returns true if the block returned true" do v = Chef::Resource::File::Verification.new(parent_resource, nil, {}, &t_block) expect(v.verify(temp_path)).to eq(true) end it "returns false if the block returned false" do v = Chef::Resource::File::Verification.new(parent_resource, nil, {}, &f_block) expect(v.verify(temp_path)).to eq(false) end end context "with a verification command(String)" do before(:each) do allow(Chef::Log).to receive(:deprecation).and_return(nil) end def platform_specific_verify_command(variable_name) if windows? "if \"#{temp_path}\" == \"%{#{variable_name}}\" (exit 0) else (exit 1)" else "test #{temp_path} = %{#{variable_name}}" end end it "substitutes \%{file} with the path" do test_command = platform_specific_verify_command("file") v = Chef::Resource::File::Verification.new(parent_resource, test_command, {}) expect(v.verify(temp_path)).to eq(true) end it "warns about deprecation when \%{file} is used" do expect(Chef::Log).to receive(:deprecation).with(/%{file} is deprecated/, /verification_spec\.rb/) test_command = platform_specific_verify_command("file") Chef::Resource::File::Verification.new(parent_resource, test_command, {}) .verify(temp_path) end it "does not warn about deprecation when \%{file} is not used" do expect(Chef::Log).to_not receive(:deprecation) test_command = platform_specific_verify_command("path") Chef::Resource::File::Verification.new(parent_resource, test_command, {}) .verify(temp_path) end it "substitutes \%{path} with the path" do test_command = platform_specific_verify_command("path") v = Chef::Resource::File::Verification.new(parent_resource, test_command, {}) expect(v.verify(temp_path)).to eq(true) end it "returns false if the command fails" do v = Chef::Resource::File::Verification.new(parent_resource, "false", {}) expect(v.verify(temp_path)).to eq(false) end it "returns true if the command succeeds" do v = Chef::Resource::File::Verification.new(parent_resource, "true", {}) expect(v.verify(temp_path)).to eq(true) end end context "with a named verification(Symbol)" do before(:each) do class Chef::Resource::File::Verification::Turtle < Chef::Resource::File::Verification provides :cats def verify(path, opts) end end end it "delegates to the registered verification" do registered_verification = double() allow(Chef::Resource::File::Verification::Turtle).to receive(:new).and_return(registered_verification) v = Chef::Resource::File::Verification.new(parent_resource, :cats, {}) expect(registered_verification).to receive(:verify).with(temp_path, {}) v.verify(temp_path, {}) end end end end chef-12.14.60/spec/unit/resource/file_spec.rb000066400000000000000000000105271276456504500207130ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Resource::File do before(:each) do @resource = Chef::Resource::File.new("fakey_fakerton") end it "should have a name" do expect(@resource.name).to eql("fakey_fakerton") end it "should have a default action of 'create'" do expect(@resource.action).to eql([:create]) end it "should have a default content of nil" do expect(@resource.content).to be_nil end it "should be set to back up 5 files by default" do expect(@resource.backup).to eql(5) end it "should only accept strings for content" do expect { @resource.content 5 }.to raise_error(ArgumentError) expect { @resource.content :foo }.to raise_error(ArgumentError) expect { @resource.content "hello" => "there" }.to raise_error(ArgumentError) expect { @resource.content "hi" }.not_to raise_error end it "should only accept false or a number for backup" do expect { @resource.backup true }.to raise_error(ArgumentError) expect { @resource.backup false }.not_to raise_error expect { @resource.backup 10 }.not_to raise_error expect { @resource.backup "blues" }.to raise_error(ArgumentError) end it "should accept a sha256 for checksum" do expect { @resource.checksum "0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa" }.not_to raise_error expect { @resource.checksum "monkey!" }.to raise_error(ArgumentError) end it "should accept create, delete or touch for action" do expect { @resource.action :create }.not_to raise_error expect { @resource.action :delete }.not_to raise_error expect { @resource.action :touch }.not_to raise_error expect { @resource.action :blues }.to raise_error(ArgumentError) end it "should accept a block, symbol, or string for verify" do expect { @resource.verify {} }.not_to raise_error expect { @resource.verify "" }.not_to raise_error expect { @resource.verify :json }.not_to raise_error expect { @resource.verify true }.to raise_error(ArgumentError) expect { @resource.verify false }.to raise_error(ArgumentError) end it "should accept multiple verify statements" do @resource.verify "foo" @resource.verify "bar" @resource.verify.length == 2 end it "should use the object name as the path by default" do expect(@resource.path).to eql("fakey_fakerton") end it "should accept a string as the path" do expect { @resource.path "/tmp" }.not_to raise_error expect(@resource.path).to eql("/tmp") expect { @resource.path Hash.new }.to raise_error(ArgumentError) end describe "when it has a path, owner, group, mode, and checksum" do before do @resource.path("/tmp/foo.txt") @resource.owner("root") @resource.group("wheel") @resource.mode("0644") @resource.checksum("1" * 64) end context "on unix", :unix_only do it "describes its state" do state = @resource.state expect(state[:owner]).to eq("root") expect(state[:group]).to eq("wheel") expect(state[:mode]).to eq("0644") expect(state[:checksum]).to eq("1" * 64) end end it "returns the file path as its identity" do expect(@resource.identity).to eq("/tmp/foo.txt") end end describe "when access controls are set on windows", :windows_only => true do before do @resource.rights :read, "Everyone" @resource.rights :full_control, "DOMAIN\User" end it "describes its state including windows ACL attributes" do state = @resource.state expect(state[:rights]).to eq([ { :permissions => :read, :principals => "Everyone" }, { :permissions => :full_control, :principals => "DOMAIN\User" } ]) end end end chef-12.14.60/spec/unit/resource/freebsd_package_spec.rb000066400000000000000000000061571276456504500230650ustar00rootroot00000000000000# # Authors:: AJ Christensen () # Richard Manyanza () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Copyright:: Copyright 2014-2016, Richard Manyanza. # 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 "ostruct" describe Chef::Resource::FreebsdPackage do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @resource = Chef::Resource::FreebsdPackage.new("foo", @run_context) end describe "Initialization" do it "should return a Chef::Resource::FreebsdPackage" do expect(@resource).to be_a_kind_of(Chef::Resource::FreebsdPackage) end it "should set the resource_name to :freebsd_package" do expect(@resource.resource_name).to eql(:freebsd_package) end it "should not set the provider" do expect(@resource.provider).to be_nil end end describe "Assigning provider after creation" do describe "if ports specified as source" do it "should be Freebsd::Port" do @resource.source("ports") @resource.after_created expect(@resource.provider).to eq(Chef::Provider::Package::Freebsd::Port) end end describe "if freebsd_version is greater than or equal to 1000017" do it "should be Freebsd::Pkgng" do [1000017, 1000018, 1000500, 1001001, 1100000].each do |freebsd_version| @node.automatic_attrs[:os_version] = freebsd_version @resource.after_created expect(@resource.provider).to eq(Chef::Provider::Package::Freebsd::Pkgng) end end end describe "if pkgng enabled" do it "should be Freebsd::Pkgng" do pkg_enabled = OpenStruct.new(:stdout => "yes\n") allow(@resource).to receive(:shell_out!).with("make -V WITH_PKGNG", :env => nil).and_return(pkg_enabled) @resource.after_created expect(@resource.provider).to eq(Chef::Provider::Package::Freebsd::Pkgng) end end describe "if freebsd_version is less than 1000017 and pkgng not enabled" do it "should be Freebsd::Pkg" do pkg_enabled = OpenStruct.new(:stdout => "\n") allow(@resource).to receive(:shell_out!).with("make -V WITH_PKGNG", :env => nil).and_return(pkg_enabled) [1000016, 1000000, 901503, 902506, 802511].each do |freebsd_version| @node.automatic_attrs[:os_version] = freebsd_version @resource.after_created expect(@resource.provider).to eq(Chef::Provider::Package::Freebsd::Pkg) end end end end end chef-12.14.60/spec/unit/resource/gem_package_spec.rb000066400000000000000000000024551276456504500222200ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::GemPackage, "initialize" do static_provider_resolution( resource: Chef::Resource::GemPackage, provider: Chef::Provider::Package::Rubygems, name: :gem_package, action: :install ) end describe Chef::Resource::GemPackage, "gem_binary" do before(:each) do @resource = Chef::Resource::GemPackage.new("foo") end it "should set the gem_binary variable to whatever is passed in" do @resource.gem_binary("/opt/local/bin/gem") expect(@resource.gem_binary).to eql("/opt/local/bin/gem") end end chef-12.14.60/spec/unit/resource/git_spec.rb000066400000000000000000000026251276456504500205570ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::Git do static_provider_resolution( resource: Chef::Resource::Git, provider: Chef::Provider::Git, name: :git, action: :sync ) before(:each) do @git = Chef::Resource::Git.new("my awesome webapp") end it "is a kind of Scm Resource" do expect(@git).to be_a_kind_of(Chef::Resource::Scm) expect(@git).to be_an_instance_of(Chef::Resource::Git) end it "uses aliases revision as branch" do @git.branch "HEAD" expect(@git.revision).to eql("HEAD") end it "aliases revision as reference" do @git.reference "v1.0 tag" expect(@git.revision).to eql("v1.0 tag") end end chef-12.14.60/spec/unit/resource/group_spec.rb000066400000000000000000000112271276456504500211260ustar00rootroot00000000000000# # Author:: AJ Christensen () # Author:: Tyler Cloke (); # 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::Resource::Group, "initialize" do before(:each) do @resource = Chef::Resource::Group.new("admin") end it "should create a new Chef::Resource::Group" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Group) end it "should set the resource_name to :group" do expect(@resource.resource_name).to eql(:group) end it "should set the group_name equal to the argument to initialize" do expect(@resource.group_name).to eql("admin") end it "should default gid to nil" do expect(@resource.gid).to eql(nil) end it "should default members to an empty array" do expect(@resource.members).to eql([]) end it "should alias users to members, also an empty array" do expect(@resource.users).to eql([]) end it "should set action to :create" do expect(@resource.action).to eql([:create]) end %w{create remove modify manage}.each do |action| it "should allow action #{action}" do expect(@resource.allowed_actions.detect { |a| a == action.to_sym }).to eql(action.to_sym) end end it "should accept domain groups (@ or \ separator) on non-windows" do expect { @resource.group_name "domain\@group" }.not_to raise_error expect(@resource.group_name).to eq("domain\@group") expect { @resource.group_name "domain\\group" }.not_to raise_error expect(@resource.group_name).to eq("domain\\group") expect { @resource.group_name "domain\\group^name" }.not_to raise_error expect(@resource.group_name).to eq("domain\\group^name") end end describe Chef::Resource::Group, "group_name" do before(:each) do @resource = Chef::Resource::Group.new("admin") end it "should allow a string" do @resource.group_name "pirates" expect(@resource.group_name).to eql("pirates") end it "should not allow a hash" do expect { @resource.send(:group_name, { :aj => "is freakin awesome" }) }.to raise_error(ArgumentError) end end describe Chef::Resource::Group, "gid" do before(:each) do @resource = Chef::Resource::Group.new("admin") end it "should allow an integer" do @resource.gid 100 expect(@resource.gid).to eql(100) end it "should not allow a hash" do expect { @resource.send(:gid, { :aj => "is freakin awesome" }) }.to raise_error(ArgumentError) end end describe Chef::Resource::Group, "members" do before(:each) do @resource = Chef::Resource::Group.new("admin") end [ :users, :members].each do |method| it "(#{method}) should allow and convert a string" do @resource.send(method, "aj") expect(@resource.send(method)).to eql(["aj"]) end it "(#{method}) should split a string on commas" do @resource.send(method, "aj,adam") expect(@resource.send(method)).to eql( %w{aj adam} ) end it "(#{method}) should allow an array" do @resource.send(method, %w{aj adam}) expect(@resource.send(method)).to eql( %w{aj adam} ) end it "(#{method}) should not allow a hash" do expect { @resource.send(method, { :aj => "is freakin awesome" }) }.to raise_error(ArgumentError) end end end describe Chef::Resource::Group, "append" do before(:each) do @resource = Chef::Resource::Group.new("admin") end it "should default to false" do expect(@resource.append).to eql(false) end it "should allow a boolean" do @resource.append true expect(@resource.append).to eql(true) end it "should not allow a hash" do expect { @resource.send(:gid, { :aj => "is freakin awesome" }) }.to raise_error(ArgumentError) end describe "when it has members" do before do @resource.group_name("pokemon") @resource.members(%w{blastoise pikachu}) end it "describes its state" do state = @resource.state expect(state[:members]).to eql(%w{blastoise pikachu}) end it "returns the group name as its identity" do expect(@resource.identity).to eq("pokemon") end end end chef-12.14.60/spec/unit/resource/homebrew_package_spec.rb000066400000000000000000000030011276456504500232440ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Copyright 2014-2016, Chef Software, Inc. # # 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 "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::HomebrewPackage, "initialize" do static_provider_resolution( resource: Chef::Resource::HomebrewPackage, provider: Chef::Provider::Package::Homebrew, name: :homebrew_package, action: :install, os: "mac_os_x" ) let(:resource) { Chef::Resource::HomebrewPackage.new("emacs") } shared_examples "home_brew user set and returned" do it "returns the configured homebrew_user" do resource.homebrew_user user expect(resource.homebrew_user).to eql(user) end end context "homebrew_user is set" do let(:user) { "Captain Picard" } include_examples "home_brew user set and returned" context "as an integer" do let(:user) { 1001 } include_examples "home_brew user set and returned" end end end chef-12.14.60/spec/unit/resource/http_request_spec.rb000066400000000000000000000034101276456504500225140ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # 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::Resource::HttpRequest do before(:each) do @resource = Chef::Resource::HttpRequest.new("fakey_fakerton") end it "should create a new Chef::Resource::HttpRequest" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::HttpRequest) end it "should set url to a string" do @resource.url "http://slashdot.org" expect(@resource.url).to eql("http://slashdot.org") end it "should set the message to the name by default" do expect(@resource.message).to eql("fakey_fakerton") end it "should set message to a string" do @resource.message "monkeybars" expect(@resource.message).to eql("monkeybars") end describe "when it has a message and headers" do before do @resource.url("http://www.trololol.net") @resource.message("Get sum post brah.") @resource.headers({ "head" => "tail" }) end it "returns the url as its identity" do expect(@resource.identity).to eq("http://www.trololol.net") end end end chef-12.14.60/spec/unit/resource/ifconfig_spec.rb000066400000000000000000000073361276456504500215640ustar00rootroot00000000000000# # Author:: Tyler Cloke () # Copyright:: Copyright 2009-2016, Joe Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Ifconfig do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @resource = Chef::Resource::Ifconfig.new("fakey_fakerton", @run_context) end describe "when it has target, hardware address, inet address, and a mask" do before do @resource.device("charmander") @resource.target("team_rocket") @resource.hwaddr("11.2223.223") @resource.inet_addr("434.2343.23") @resource.mask("255.255.545") end it "describes its state" do state = @resource.state expect(state[:inet_addr]).to eq("434.2343.23") expect(state[:mask]).to eq("255.255.545") end it "returns the device as its identity" do expect(@resource.identity).to eq("charmander") end end shared_examples "being a platform based on an old Debian" do |platform, version| before do @node.automatic_attrs[:os] = "linux" @node.automatic_attrs[:platform_family] = "debian" @node.automatic_attrs[:platform] = platform @node.automatic_attrs[:platform_version] = version end it "should use an ordinary Provider::Ifconfig as a provider for #{platform} #{version}" do expect(@resource.provider_for_action(:add).class).to eq(Chef::Provider::Ifconfig) end end shared_examples "being a platform based on RedHat" do |platform, version| before do @node.automatic_attrs[:os] = "linux" @node.automatic_attrs[:platform_family] = "rhel" @node.automatic_attrs[:platform] = platform @node.automatic_attrs[:platform_version] = version end it "should use an Provider::Ifconfig::Redhat as a provider for #{platform} #{version}" do expect(@resource.provider_for_action(:add)).to be_a_kind_of(Chef::Provider::Ifconfig::Redhat) end end shared_examples "being a platform based on a recent Debian" do |platform, version| before do @node.automatic_attrs[:os] = "linux" @node.automatic_attrs[:platform_family] = "debian" @node.automatic_attrs[:platform] = platform @node.automatic_attrs[:platform_version] = version end it "should use an Ifconfig::Debian as a provider for #{platform} #{version}" do expect(@resource.provider_for_action(:add)).to be_a_kind_of(Chef::Provider::Ifconfig::Debian) end end describe "when it is a RedHat platform" do it_should_behave_like "being a platform based on RedHat", "redhat", "4.0" end describe "when it is an old Debian platform" do it_should_behave_like "being a platform based on an old Debian", "debian", "6.0" end describe "when it is a new Debian platform" do it_should_behave_like "being a platform based on a recent Debian", "debian", "7.0" end describe "when it is an old Ubuntu platform" do it_should_behave_like "being a platform based on an old Debian", "ubuntu", "11.04" end describe "when it is a new Ubuntu platform" do it_should_behave_like "being a platform based on a recent Debian", "ubuntu", "11.10" end end chef-12.14.60/spec/unit/resource/ips_package_spec.rb000066400000000000000000000023261276456504500222400ustar00rootroot00000000000000# # Author:: Bryan McLellan # 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 "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::IpsPackage, "initialize" do static_provider_resolution( resource: Chef::Resource::IpsPackage, provider: Chef::Provider::Package::Ips, name: :ips_package, action: :install, os: "solaris2" ) before(:each) do @resource = Chef::Resource::IpsPackage.new("crypto/gnupg") end it "should support accept_license" do @resource.accept_license(true) expect(@resource.accept_license).to eql(true) end end chef-12.14.60/spec/unit/resource/ksh_spec.rb000066400000000000000000000023001276456504500205470ustar00rootroot00000000000000# # Author:: Nolan Davidson () # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Ksh do before(:each) do @resource = Chef::Resource::Ksh.new("fakey_fakerton") end it "should create a new Chef::Resource::Ksh" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Ksh) end it "should have a resource name of :ksh" do expect(@resource.resource_name).to eql(:ksh) end it "should have an interpreter of ksh" do expect(@resource.interpreter).to eql("ksh") end end chef-12.14.60/spec/unit/resource/launchd_spec.rb000066400000000000000000000016431276456504500214110ustar00rootroot00000000000000# require "spec_helper" describe Chef::Resource::Launchd do @launchd = Chef::Resource::Launchd.new("io.chef.chef-client") let(:resource) do Chef::Resource::Launchd.new( "io.chef.chef-client", run_context ) end it "should create a new Chef::Resource::Launchd" do expect(resource).to be_a_kind_of(Chef::Resource) expect(resource).to be_a_kind_of(Chef::Resource::Launchd) end it "should have a resource name of Launchd" do expect(resource.resource_name).to eql(:launchd) end it "should have a default action of create" do expect(resource.action).to eql([:create]) end it "should accept enable, disable, create, and delete as actions" do expect { resource.action :enable }.not_to raise_error expect { resource.action :disable }.not_to raise_error expect { resource.action :create }.not_to raise_error expect { resource.action :delete }.not_to raise_error end end chef-12.14.60/spec/unit/resource/link_spec.rb000066400000000000000000000104241276456504500207250ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # 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::Resource::Link do before(:each) do expect_any_instance_of(Chef::Resource::Link).to receive(:verify_links_supported!).and_return(true) @resource = Chef::Resource::Link.new("fakey_fakerton") end it "should create a new Chef::Resource::Link" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Link) end it "should have a name" do expect(@resource.name).to eql("fakey_fakerton") end it "should have a default action of 'create'" do expect(@resource.action).to eql([:create]) end { :create => false, :delete => false, :blues => true }.each do |action, bad_value| it "should #{bad_value ? 'not' : ''} accept #{action}" do if bad_value expect { @resource.action action }.to raise_error(ArgumentError) else expect { @resource.action action }.not_to raise_error end end end it "should use the object name as the target_file by default" do expect(@resource.target_file).to eql("fakey_fakerton") end it "should accept a delayed evaluator as the target path" do @resource.target_file Chef::DelayedEvaluator.new { "my_lazy_name" } expect(@resource.target_file).to eql("my_lazy_name") end it "should accept a delayed evaluator when accessing via 'path'" do @resource.target_file Chef::DelayedEvaluator.new { "my_lazy_name" } expect(@resource.path).to eql("my_lazy_name") end it "should accept a delayed evaluator via 'to'" do @resource.to Chef::DelayedEvaluator.new { "my_lazy_name" } expect(@resource.to).to eql("my_lazy_name") end it "should accept a string as the link source via 'to'" do expect { @resource.to "/tmp" }.not_to raise_error end it "should not accept a Hash for the link source via 'to'" do expect { @resource.to Hash.new }.to raise_error(ArgumentError) end it "should allow you to set a link source via 'to'" do @resource.to "/tmp/foo" expect(@resource.to).to eql("/tmp/foo") end it "should allow you to specify the link type" do @resource.link_type "symbolic" expect(@resource.link_type).to eql(:symbolic) end it "should default to a symbolic link" do expect(@resource.link_type).to eql(:symbolic) end it "should accept a hard link_type" do @resource.link_type :hard expect(@resource.link_type).to eql(:hard) end it "should reject any other link_type but :hard and :symbolic" do expect { @resource.link_type "x-men" }.to raise_error(ArgumentError) end it "should accept a group name or id for group" do expect { @resource.group "root" }.not_to raise_error expect { @resource.group 123 }.not_to raise_error expect { @resource.group "root:goo" }.to raise_error(ArgumentError) end it "should accept a user name or id for owner" do expect { @resource.owner "root" }.not_to raise_error expect { @resource.owner 123 }.not_to raise_error expect { @resource.owner "root:goo" }.to raise_error(ArgumentError) end describe "when it has to, link_type, owner, and group" do before do @resource.target_file("/var/target.tar") @resource.to("/to/dir/file.tar") @resource.link_type(:symbolic) @resource.owner("root") @resource.group("0664") end it "describes its state" do state = @resource.state expect(state[:to]).to eq("/to/dir/file.tar") expect(state[:owner]).to eq("root") expect(state[:group]).to eq("0664") end it "returns the target file as its identity" do expect(@resource.identity).to eq("/var/target.tar") end end end chef-12.14.60/spec/unit/resource/log_spec.rb000066400000000000000000000041721276456504500205540ustar00rootroot00000000000000# # Author:: Cary Penniman () # Author:: Tyler Cloke () # 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::Resource::Log do before(:each) do @log_str = "this is my string to log" @resource = Chef::Resource::Log.new(@log_str) end it "should create a new Chef::Resource::Log" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Log) end it "supports the :write actions" do expect(@resource.allowed_actions).to include(:write) end it "should have a name of log" do expect(@resource.resource_name).to eq(:log) end it "should allow you to set a log string" do expect(@resource.name).to eq(@log_str) end it "should set the message to the first argument to new" do expect(@resource.message).to eq(@log_str) end it "should accept a string for the log message" do @resource.message "this is different" expect(@resource.message).to eq("this is different") end it "should accept a vaild level option" do @resource.level :debug @resource.level :info @resource.level :warn @resource.level :error @resource.level :fatal expect { @resource.level :unsupported }.to raise_error(ArgumentError) end describe "when the identity is defined" do before do @resource = Chef::Resource::Log.new("ery day I'm loggin-in") end it "returns the log string as its identity" do expect(@resource.identity).to eq("ery day I'm loggin-in") end end end chef-12.14.60/spec/unit/resource/macports_package_spec.rb000066400000000000000000000020301276456504500232650ustar00rootroot00000000000000# # Author:: David Balatero () # Copyright:: Copyright 2009-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 "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::MacportsPackage, "initialize" do static_provider_resolution( resource: Chef::Resource::MacportsPackage, provider: Chef::Provider::Package::Macports, name: :macports_package, action: :install, os: "mac_os_x" ) end chef-12.14.60/spec/unit/resource/mdadm_spec.rb000066400000000000000000000061101276456504500210470ustar00rootroot00000000000000# # Author:: Joe Williams () # Author:: Tyler Cloke () # Copyright:: Copyright 2009-2016, Joe Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Mdadm do before(:each) do @resource = Chef::Resource::Mdadm.new("fakey_fakerton") end it "should create a new Chef::Resource::Mdadm" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Mdadm) end it "should have a resource name of :mdadm" do expect(@resource.resource_name).to eql(:mdadm) end it "should have a default action of create" do expect(@resource.action).to eql([:create]) end it "should accept create, assemble, stop as actions" do expect { @resource.action :create }.not_to raise_error expect { @resource.action :assemble }.not_to raise_error expect { @resource.action :stop }.not_to raise_error end it "should allow you to set the raid_device attribute" do @resource.raid_device "/dev/md3" expect(@resource.raid_device).to eql("/dev/md3") end it "should allow you to set the chunk attribute" do @resource.chunk 256 expect(@resource.chunk).to eql(256) end it "should allow you to set the level attribute" do @resource.level 1 expect(@resource.level).to eql(1) end it "should allow you to set the metadata attribute" do @resource.metadata "1.2" expect(@resource.metadata).to eql("1.2") end it "should allow you to set the bitmap attribute" do @resource.bitmap "internal" expect(@resource.bitmap).to eql("internal") end it "should allow you to set the layout attribute" do @resource.layout "f2" expect(@resource.layout).to eql("f2") end it "should allow you to set the devices attribute" do @resource.devices ["/dev/sda", "/dev/sdb"] expect(@resource.devices).to eql(["/dev/sda", "/dev/sdb"]) end it "should allow you to set the exists attribute" do @resource.exists true expect(@resource.exists).to eql(true) end describe "when it has devices, level, and chunk" do before do @resource.raid_device("raider") @resource.devices(%w{device1 device2}) @resource.level(1) @resource.chunk(42) end it "describes its state" do state = @resource.state expect(state[:devices]).to eql(%w{device1 device2}) expect(state[:level]).to eq(1) expect(state[:chunk]).to eq(42) end it "returns the raid device as its identity" do expect(@resource.identity).to eq("raider") end end end chef-12.14.60/spec/unit/resource/mount_spec.rb000066400000000000000000000145221276456504500211350ustar00rootroot00000000000000# # Author:: Joshua Timberman () # Author:: Tyler Cloke () # Copyright:: Copyright 2009-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::Resource::Mount do before(:each) do @resource = Chef::Resource::Mount.new("filesystem") end it "should create a new Chef::Resource::Mount" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Mount) end it "should have a name" do expect(@resource.name).to eql("filesystem") end it "should set mount_point to the name" do expect(@resource.mount_point).to eql("filesystem") end it "should have a default action of mount" do expect(@resource.action).to eql([:mount]) end it "should accept mount, umount and remount as actions" do expect { @resource.action :mount }.not_to raise_error expect { @resource.action :umount }.not_to raise_error expect { @resource.action :remount }.not_to raise_error expect { @resource.action :brooklyn }.to raise_error(ArgumentError) end it "should allow you to set the device attribute" do @resource.device "/dev/sdb3" expect(@resource.device).to eql("/dev/sdb3") end it "should set fsck_device to '-' by default" do expect(@resource.fsck_device).to eql("-") end it "should allow you to set the fsck_device attribute" do @resource.fsck_device "/dev/rdsk/sdb3" expect(@resource.fsck_device).to eql("/dev/rdsk/sdb3") end it "should allow you to set the fstype attribute" do @resource.fstype "nfs" expect(@resource.fstype).to eql("nfs") end it "should allow you to set the dump attribute" do @resource.dump 1 expect(@resource.dump).to eql(1) end it "should allow you to set the pass attribute" do @resource.pass 1 expect(@resource.pass).to eql(1) end it "should set the options attribute to defaults" do expect(@resource.options).to eql(["defaults"]) end it "should allow options to be sent as a string, and convert to array" do @resource.options "rw,noexec" expect(@resource.options).to be_a_kind_of(Array) end it "should allow options attribute as an array" do @resource.options %w{ro nosuid} expect(@resource.options).to be_a_kind_of(Array) end it "should allow options to be sent as a delayed evaluator" do @resource.options Chef::DelayedEvaluator.new { %w{rw noexec} } expect(@resource.options).to eql(%w{rw noexec}) end it "should allow options to be sent as a delayed evaluator, and convert to array" do @resource.options Chef::DelayedEvaluator.new { "rw,noexec" } expect(@resource.options).to be_a_kind_of(Array) expect(@resource.options).to eql(%w{rw noexec}) end it "should accept true for mounted" do @resource.mounted(true) expect(@resource.mounted).to eql(true) end it "should accept false for mounted" do @resource.mounted(false) expect(@resource.mounted).to eql(false) end it "should set mounted to false by default" do expect(@resource.mounted).to eql(false) end it "should not accept a string for mounted" do expect { @resource.mounted("poop") }.to raise_error(ArgumentError) end it "should accept true for enabled" do @resource.enabled(true) expect(@resource.enabled).to eql(true) end it "should accept false for enabled" do @resource.enabled(false) expect(@resource.enabled).to eql(false) end it "should set enabled to false by default" do expect(@resource.enabled).to eql(false) end it "should not accept a string for enabled" do expect { @resource.enabled("poop") }.to raise_error(ArgumentError) end it "should default all feature support to false" do support_hash = { :remount => false } expect(@resource.supports).to eq(support_hash) end it "should allow you to set feature support as an array" do support_array = [ :remount ] support_hash = { :remount => true } @resource.supports(support_array) expect(@resource.supports).to eq(support_hash) end it "should allow you to set feature support as a hash" do support_hash = { :remount => true } @resource.supports(support_hash) expect(@resource.supports).to eq(support_hash) end it "should allow you to set username" do @resource.username("Administrator") expect(@resource.username).to eq("Administrator") end it "should allow you to set password" do @resource.password("Jetstream123!") expect(@resource.password).to eq("Jetstream123!") end it "should allow you to set domain" do @resource.domain("TEST_DOMAIN") expect(@resource.domain).to eq("TEST_DOMAIN") end describe "when it has mount point, device type, and fstype" do before do @resource.device("charmander") @resource.mount_point("123.456") @resource.device_type(:device) @resource.fstype("ranked") end it "describes its state" do state = @resource.state expect(state[:mount_point]).to eq("123.456") expect(state[:device_type]).to eql(:device) expect(state[:fstype]).to eq("ranked") end it "returns the device as its identity" do expect(@resource.identity).to eq("charmander") end end describe "when it has username, password and domain" do before do @resource.mount_point("T:") @resource.device("charmander") @resource.username("Administrator") @resource.password("Jetstream123!") @resource.domain("TEST_DOMAIN") end it "describes its state" do state = @resource.state expect(state[:mount_point]).to eq("T:") expect(state[:username]).to eq("Administrator") expect(state[:password]).to eq("Jetstream123!") expect(state[:domain]).to eq("TEST_DOMAIN") expect(state[:device_type]).to eql(:device) expect(state[:fstype]).to eq("auto") end end end chef-12.14.60/spec/unit/resource/ohai_spec.rb000066400000000000000000000032551276456504500207140ustar00rootroot00000000000000# # Author:: Michael Leinartas () # Copyright:: Copyright 2010-2016, Michael Leinartas # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Ohai do before(:each) do @resource = Chef::Resource::Ohai.new("ohai_reload") end it "should create a new Chef::Resource::Ohai" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Ohai) end it "should have a resource name of :ohai" do expect(@resource.resource_name).to eql(:ohai) end it "should have a default action of create" do expect(@resource.action).to eql([:reload]) end it "should allow you to set the plugin attribute" do @resource.plugin "passwd" expect(@resource.plugin).to eql("passwd") end describe "when it has a plugin value" do before do @resource.name("test") @resource.plugin("passwd") end it "describes its state" do state = @resource.state expect(state[:plugin]).to eq("passwd") end it "returns the name as its identity" do expect(@resource.identity).to eq("test") end end end chef-12.14.60/spec/unit/resource/openbsd_package_spec.rb000066400000000000000000000030531276456504500230750ustar00rootroot00000000000000# # Authors:: AJ Christensen () # Richard Manyanza () # Scott Bonds () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Copyright:: Copyright 2014-2016, Richard Manyanza, Scott Bonds # 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 "ostruct" describe Chef::Resource::OpenbsdPackage do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @resource = Chef::Resource::OpenbsdPackage.new("foo", @run_context) end describe "Initialization" do it "should return a Chef::Resource::OpenbsdPackage" do expect(@resource).to be_a_kind_of(Chef::Resource::OpenbsdPackage) end it "should set the resource_name to :openbsd_package" do expect(@resource.resource_name).to eql(:openbsd_package) end it "should not set the provider" do expect(@resource.provider).to be_nil end end end chef-12.14.60/spec/unit/resource/osx_profile_spec.rb000066400000000000000000000040011276456504500223130ustar00rootroot00000000000000# # Author:: Nate Walck () # Copyright:: Copyright 2015-2016, Facebook, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::OsxProfile do let(:resource) do Chef::Resource::OsxProfile.new( "Test Profile Resource", run_context) end it "should create a new Chef::Resource::OsxProfile" do expect(resource).to be_a_kind_of(Chef::Resource) expect(resource).to be_a_kind_of(Chef::Resource::OsxProfile) end it "should have a resource name of profile" do expect(resource.resource_name).to eql(:osx_profile) end it "should have a default action of install" do expect(resource.action).to eql([:install]) end it "should accept install and remove as actions" do expect { resource.action :install }.not_to raise_error expect { resource.action :remove }.not_to raise_error end it "should allow you to set the profile attribute" do resource.profile "com.testprofile.screensaver" expect(resource.profile).to eql("com.testprofile.screensaver") end it "should allow you to set the profile attribute to a string" do resource.profile "com.testprofile.screensaver" expect(resource.profile).to be_a(String) expect(resource.profile).to eql("com.testprofile.screensaver") end it "should allow you to set the profile attribute to a hash" do test_profile = { "profile" => false } resource.profile test_profile expect(resource.profile).to be_a(Hash) end end chef-12.14.60/spec/unit/resource/package_spec.rb000066400000000000000000000053171276456504500213700ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # 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::Resource::Package do before(:each) do @resource = Chef::Resource::Package.new("emacs") end it "should create a new Chef::Resource::Package" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Package) end it "should set the package_name to the first argument to new" do expect(@resource.package_name).to eql("emacs") end it "should accept a string for the package name" do @resource.package_name "something" expect(@resource.package_name).to eql("something") end it "should accept a string for the version" do @resource.version "something" expect(@resource.version).to eql("something") end it "should accept a string for the response file" do @resource.response_file "something" expect(@resource.response_file).to eql("something") end it "should accept a hash for response file template variables" do @resource.response_file_variables({ :variables => true }) expect(@resource.response_file_variables).to eql({ :variables => true }) end it "should accept a string for the source" do @resource.source "something" expect(@resource.source).to eql("something") end it "should accept a string for the options" do @resource.options "something" expect(@resource.options).to eql("something") end describe "when it has a package_name and version" do before do @resource.package_name("tomcat") @resource.version("10.9.8") @resource.options("-al") end it "describes its state" do state = @resource.state expect(state[:version]).to eq("10.9.8") expect(state[:options]).to eq("-al") end it "returns the file path as its identity" do expect(@resource.identity).to eq("tomcat") end end # String, Integer [ "600", 600 ].each do |val| it "supports setting a timeout as a #{val.class}" do @resource.timeout(val) expect(@resource.timeout).to eql(val) end end end chef-12.14.60/spec/unit/resource/pacman_package_spec.rb000066400000000000000000000017771276456504500227150ustar00rootroot00000000000000# # Author:: Jan Zimmek () # Copyright:: Copyright 2010-2016, Jan Zimmek # 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 "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::PacmanPackage, "initialize" do static_provider_resolution( resource: Chef::Resource::PacmanPackage, provider: Chef::Provider::Package::Pacman, name: :pacman_package, action: :install, os: "linux" ) end chef-12.14.60/spec/unit/resource/perl_spec.rb000066400000000000000000000022671276456504500207400ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Resource::Perl do before(:each) do @resource = Chef::Resource::Perl.new("fakey_fakerton") end it "should create a new Chef::Resource::Perl" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Perl) end it "should have a resource name of :perl" do expect(@resource.resource_name).to eql(:perl) end it "should have an interpreter of perl" do expect(@resource.interpreter).to eql("perl") end end chef-12.14.60/spec/unit/resource/portage_package_spec.rb000066400000000000000000000024571276456504500231130ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) describe Chef::Resource::PortagePackage, "initialize" do before(:each) do @resource = Chef::Resource::PortagePackage.new("foo") end it "should return a Chef::Resource::PortagePackage" do expect(@resource).to be_a_kind_of(Chef::Resource::PortagePackage) end it "should set the resource_name to :portage_package" do expect(@resource.resource_name).to eql(:portage_package) end it "should set the provider to Chef::Provider::Package::Portage" do expect(@resource.provider).to eql(Chef::Provider::Package::Portage) end end chef-12.14.60/spec/unit/resource/powershell_script_spec.rb000066400000000000000000000145331276456504500235450ustar00rootroot00000000000000# # Author:: Adam Edwards () # Copyright:: Copyright 2013-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::Resource::PowershellScript do before(:each) do node = Chef::Node.new node.default["kernel"] = Hash.new node.default["kernel"][:machine] = :x86_64.to_s node.automatic[:os] = "windows" run_context = Chef::RunContext.new(node, nil, nil) @resource = Chef::Resource::PowershellScript.new("powershell_unit_test", run_context) end it "creates a new Chef::Resource::PowershellScript" do expect(@resource).to be_a_kind_of(Chef::Resource::PowershellScript) end it "sets convert_boolean_return to false by default" do expect(@resource.convert_boolean_return).to eq(false) end it "returns the value for convert_boolean_return that was set" do @resource.convert_boolean_return true expect(@resource.convert_boolean_return).to eq(true) @resource.convert_boolean_return false expect(@resource.convert_boolean_return).to eq(false) end it "raises an error when architecture is i386 on Windows Nano Server" do allow(Chef::Platform).to receive(:windows_nano_server?).and_return(true) expect { @resource.architecture(:i386) }.to raise_error(Chef::Exceptions::Win32ArchitectureIncorrect, "cannot execute script with requested architecture 'i386' on Windows Nano Server") end context "when using guards" do let(:resource) { @resource } before(:each) do allow(resource).to receive(:run_action) allow(resource).to receive(:updated).and_return(true) end it "inherits exactly the :cwd, :environment, :group, :path, :user, :umask, and :architecture attributes from a parent resource class" do inherited_difference = Chef::Resource::PowershellScript.guard_inherited_attributes - [:cwd, :environment, :group, :path, :user, :umask, :architecture ] expect(inherited_difference).to eq([]) end it "allows guard interpreter to be set to Chef::Resource::Script" do resource.guard_interpreter(:script) allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) resource.only_if("echo hi") end it "allows guard interpreter to be set to Chef::Resource::Bash derived from Chef::Resource::Script" do resource.guard_interpreter(:bash) allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) resource.only_if("echo hi") end it "allows guard interpreter to be set to Chef::Resource::PowershellScript derived indirectly from Chef::Resource::Script" do resource.guard_interpreter(:powershell_script) allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(false) resource.only_if("echo hi") end it "enables convert_boolean_return by default for guards in the context of powershell_script when no guard params are specified" do allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:evaluate_action).and_return(true) allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( { :convert_boolean_return => true, :code => "$true" }).and_return(Proc.new {}) resource.only_if("$true") end it "enables convert_boolean_return by default for guards in non-Chef::Resource::Script derived resources when no guard params are specified" do node = Chef::Node.new run_context = Chef::RunContext.new(node, nil, nil) file_resource = Chef::Resource::File.new("idontexist", run_context) file_resource.guard_interpreter :powershell_script allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( { :convert_boolean_return => true, :code => "$true" }).and_return(Proc.new {}) resource.only_if("$true") end it "enables convert_boolean_return by default for guards in the context of powershell_script when guard params are specified" do guard_parameters = { :cwd => "/etc/chef", :architecture => :x86_64 } allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( { :convert_boolean_return => true, :code => "$true" }.merge(guard_parameters)).and_return(Proc.new {}) resource.only_if("$true", guard_parameters) end it "passes convert_boolean_return as true if it was specified as true in a guard parameter" do guard_parameters = { :cwd => "/etc/chef", :convert_boolean_return => true, :architecture => :x86_64 } allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( { :convert_boolean_return => true, :code => "$true" }.merge(guard_parameters)).and_return(Proc.new {}) resource.only_if("$true", guard_parameters) end it "passes convert_boolean_return as false if it was specified as true in a guard parameter" do other_guard_parameters = { :cwd => "/etc/chef", :architecture => :x86_64 } parameters_with_boolean_disabled = other_guard_parameters.merge({ :convert_boolean_return => false, :code => "$true" }) allow_any_instance_of(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:block_from_attributes).with( parameters_with_boolean_disabled).and_return(Proc.new {}) resource.only_if("$true", parameters_with_boolean_disabled) end end context "as a script running in Windows-based scripting language" do let(:resource_instance) { @resource } let(:resource_instance_name ) { @resource.command } let(:resource_name) { :powershell_script } let(:interpreter_file_name) { "powershell.exe" } it_behaves_like "a Windows script resource" end end chef-12.14.60/spec/unit/resource/python_spec.rb000066400000000000000000000023071276456504500213120ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Resource::Python do before(:each) do @resource = Chef::Resource::Python.new("fakey_fakerton") end it "should create a new Chef::Resource::Python" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Python) end it "should have a resource name of :python" do expect(@resource.resource_name).to eql(:python) end it "should have an interpreter of python" do expect(@resource.interpreter).to eql("python") end end chef-12.14.60/spec/unit/resource/registry_key_spec.rb000066400000000000000000000154101276456504500225100ustar00rootroot00000000000000# # Author:: Lamont Granquist () # 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" describe Chef::Resource::RegistryKey, "initialize" do before(:each) do @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius') end it "should create a new Chef::Resource::RegistryKey" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::RegistryKey) end it "should set the resource_name to :registry_key" do expect(@resource.resource_name).to eql(:registry_key) end it "should set the key equal to the argument to initialize" do expect(@resource.key).to eql('HKCU\Software\Raxicoricofallapatorius') end it "should default recursive to false" do expect(@resource.recursive).to eql(false) end it "should default architecture to :machine" do expect(@resource.architecture).to eql(:machine) end it "should set action to :create" do expect(@resource.action).to eql([:create]) end %w{create create_if_missing delete delete_key}.each do |action| it "should allow action #{action}" do expect(@resource.allowed_actions.detect { |a| a == action.to_sym }).to eql(action.to_sym) end end end describe Chef::Resource::RegistryKey, "key" do before(:each) do @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius') end it "should allow a string" do @resource.key 'HKCU\Software\Poosh' expect(@resource.key).to eql('HKCU\Software\Poosh') end it "should not allow an integer" do expect { @resource.send(:key, 100) }.to raise_error(ArgumentError) end it "should not allow a hash" do expect { @resource.send(:key, { :sonic => "screwdriver" }) }.to raise_error(ArgumentError) end end describe Chef::Resource::RegistryKey, "values" do before(:each) do @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius') end it "should allow a single proper hash of registry values" do @resource.values( { :name => "poosh", :type => :string, :data => "carmen" } ) expect(@resource.values).to eql([ { :name => "poosh", :type => :string, :data => "carmen" } ]) end it "should allow an array of proper hashes of registry values" do @resource.values [ { :name => "poosh", :type => :string, :data => "carmen" } ] expect(@resource.values).to eql([ { :name => "poosh", :type => :string, :data => "carmen" } ]) end it "should return checksummed data if the type is unsafe" do @resource.values( { :name => "poosh", :type => :binary, :data => 255.chr * 1 }) expect(@resource.values).to eql([ { :name => "poosh", :type => :binary, :data => "a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89" } ]) end it "should throw an exception if the name field is missing" do expect { @resource.values [ { :type => :string, :data => "carmen" } ] }.to raise_error(ArgumentError) end it "should throw an exception if the type field is missing" do expect { @resource.values [ { :name => "poosh", :data => "carmen" } ] }.to raise_error(ArgumentError) end it "should throw an exception if the data field is missing" do expect { @resource.values [ { :name => "poosh", :type => :string } ] }.to raise_error(ArgumentError) end it "should throw an exception if extra fields are present" do expect { @resource.values [ { :name => "poosh", :type => :string, :data => "carmen", :screwdriver => "sonic" } ] }.to raise_error(ArgumentError) end it "should not allow a string" do expect { @resource.send(:values, "souffle") }.to raise_error(ArgumentError) end it "should not allow an integer" do expect { @resource.send(:values, 100) }.to raise_error(ArgumentError) end end describe Chef::Resource::RegistryKey, "recursive" do before(:each) do @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius') end it "should allow a boolean" do @resource.recursive(true) expect(@resource.recursive).to eql(true) end it "should not allow a hash" do expect { @resource.recursive({ :sonic => :screwdriver }) }.to raise_error(ArgumentError) end it "should not allow an array" do expect { @resource.recursive([:nose, :chin]) }.to raise_error(ArgumentError) end it "should not allow a string" do expect { @resource.recursive("souffle") }.to raise_error(ArgumentError) end it "should not allow an integer" do expect { @resource.recursive(100) }.to raise_error(ArgumentError) end end describe Chef::Resource::RegistryKey, "architecture" do before(:each) do @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius') end [ :i386, :x86_64, :machine ].each do |arch| it "should allow #{arch} as a symbol" do @resource.architecture(arch) expect(@resource.architecture).to eql(arch) end end it "should not allow a hash" do expect { @resource.architecture({ :sonic => :screwdriver }) }.to raise_error(ArgumentError) end it "should not allow an array" do expect { @resource.architecture([:nose, :chin]) }.to raise_error(ArgumentError) end it "should not allow a string" do expect { @resource.architecture("souffle") }.to raise_error(ArgumentError) end it "should not allow an integer" do expect { @resource.architecture(100) }.to raise_error(ArgumentError) end end describe Chef::Resource::RegistryKey, ":unscrubbed_values" do before(:each) do @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius') end it "should return unsafe data as-is" do key_values = [ { :name => "poosh", :type => :binary, :data => 255.chr * 1 } ] @resource.values(key_values) expect(@resource.unscrubbed_values).to eql(key_values) end end describe Chef::Resource::RegistryKey, "state" do before(:each) do @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius') end it "should return scrubbed values" do @resource.values([ { :name => "poosh", :type => :binary, :data => 255.chr * 1 } ]) expect(@resource.state).to eql( { :values => [{ :name => "poosh", :type => :binary, :data => "a8100ae6aa1940d0b663bb31cd466142ebbdbd5187131b92d93818987832eb89" }] } ) end end chef-12.14.60/spec/unit/resource/remote_directory_spec.rb000066400000000000000000000057641276456504500233620ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Resource::RemoteDirectory do before(:each) do @resource = Chef::Resource::RemoteDirectory.new("/etc/dunk") end it "should create a new Chef::Resource::RemoteDirectory" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::RemoteDirectory) end it "should set the path to the first argument to new" do expect(@resource.path).to eql("/etc/dunk") end it "should accept a string for the remote directory source" do @resource.source "foo" expect(@resource.source).to eql("foo") end it "should have the basename of the remote directory resource as the default source" do expect(@resource.source).to eql("dunk") end it "should accept a number for the remote files backup" do @resource.files_backup 1 expect(@resource.files_backup).to eql(1) end it "should accept false for the remote files backup" do @resource.files_backup false expect(@resource.files_backup).to eql(false) end it "should accept 3 or 4 digets for the files_mode" do @resource.files_mode 100 expect(@resource.files_mode).to eql(100) @resource.files_mode 1000 expect(@resource.files_mode).to eql(1000) end it "should accept a string or number for the files group" do @resource.files_group "heart" expect(@resource.files_group).to eql("heart") @resource.files_group 1000 expect(@resource.files_group).to eql(1000) end it "should accept a string or number for the files owner" do @resource.files_owner "heart" expect(@resource.files_owner).to eql("heart") @resource.files_owner 1000 expect(@resource.files_owner).to eql(1000) end describe "when it has cookbook, files owner, files mode, and source" do before do @resource.path("/var/path/") @resource.cookbook("pokemon.rb") @resource.files_owner("root") @resource.files_group("supergroup") @resource.files_mode("0664") @resource.source("/var/source/") end it "describes its state" do state = @resource.state expect(state[:files_owner]).to eq("root") expect(state[:files_group]).to eq("supergroup") expect(state[:files_mode]).to eq("0664") end it "returns the path as its identity" do expect(@resource.identity).to eq("/var/path/") end end end chef-12.14.60/spec/unit/resource/remote_file_spec.rb000066400000000000000000000164511276456504500222700ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # 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::Resource::RemoteFile do before(:each) do @resource = Chef::Resource::RemoteFile.new("fakey_fakerton") end describe "initialize" do it "should create a new Chef::Resource::RemoteFile" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::File) expect(@resource).to be_a_kind_of(Chef::Resource::RemoteFile) end end it "says its provider is RemoteFile when the source is an absolute URI" do @resource.source("http://www.google.com/robots.txt") expect(@resource.provider).to eq(Chef::Provider::RemoteFile) expect(Chef::Platform.find_provider(:noplatform, "noversion", @resource)).to eq(Chef::Provider::RemoteFile) end it "says its provider is RemoteFile when the source is a network share" do @resource.source("\\\\fakey\\fakerton\\fake.txt") expect(@resource.provider).to eq(Chef::Provider::RemoteFile) expect(Chef::Platform.find_provider(:noplatform, "noversion", @resource)).to eq(Chef::Provider::RemoteFile) end describe "source" do it "does not have a default value for 'source'" do expect(@resource.source).to eql([]) end it "should accept a URI for the remote file source" do @resource.source "http://opscode.com/" expect(@resource.source).to eql([ "http://opscode.com/" ]) end it "should accept a windows network share source" do @resource.source "\\\\fakey\\fakerton\\fake.txt" expect(@resource.source).to eql([ "\\\\fakey\\fakerton\\fake.txt" ]) end it "should accept file URIs with spaces" do @resource.source("file:///C:/foo bar") expect(@resource.source).to eql(["file:///C:/foo bar"]) end it "should accept a delayed evalutator (string) for the remote file source" do @resource.source Chef::DelayedEvaluator.new { "http://opscode.com/" } expect(@resource.source).to eql([ "http://opscode.com/" ]) end it "should accept an array of URIs for the remote file source" do @resource.source([ "http://opscode.com/", "http://puppetlabs.com/" ]) expect(@resource.source).to eql([ "http://opscode.com/", "http://puppetlabs.com/" ]) end it "should accept a delated evaluator (array) for the remote file source" do @resource.source Chef::DelayedEvaluator.new { [ "http://opscode.com/", "http://puppetlabs.com/" ] } expect(@resource.source).to eql([ "http://opscode.com/", "http://puppetlabs.com/" ]) end it "should accept an multiple URIs as arguments for the remote file source" do @resource.source("http://opscode.com/", "http://puppetlabs.com/") expect(@resource.source).to eql([ "http://opscode.com/", "http://puppetlabs.com/" ]) end it "should only accept a single argument if a delayed evalutor is used" do expect do @resource.source("http://opscode.com/", Chef::DelayedEvaluator.new { "http://opscode.com/" }) end.to raise_error(Chef::Exceptions::InvalidRemoteFileURI) end it "should only accept a single array item if a delayed evalutor is used" do expect do @resource.source(["http://opscode.com/", Chef::DelayedEvaluator.new { "http://opscode.com/" }]) end.to raise_error(Chef::Exceptions::InvalidRemoteFileURI) end it "does not accept a non-URI as the source" do expect { @resource.source("not-a-uri") }.to raise_error(Chef::Exceptions::InvalidRemoteFileURI) end it "does not accept a non-URI as the source when read from a delayed evaluator" do expect do @resource.source(Chef::DelayedEvaluator.new { "not-a-uri" }) @resource.source end.to raise_error(Chef::Exceptions::InvalidRemoteFileURI) end it "should raise an exception when source is an empty array" do expect { @resource.source([]) }.to raise_error(ArgumentError) end end describe "checksum" do it "should accept a string for the checksum object" do @resource.checksum "asdf" expect(@resource.checksum).to eql("asdf") end it "should default to nil" do expect(@resource.checksum).to eq(nil) end end describe "ftp_active_mode" do it "should accept a boolean for the ftp_active_mode object" do @resource.ftp_active_mode true expect(@resource.ftp_active_mode).to be_truthy end it "should default to false" do expect(@resource.ftp_active_mode).to be_falsey end end describe "conditional get options" do it "defaults to using etags and last modified" do expect(@resource.use_etags).to be_truthy expect(@resource.use_last_modified).to be_truthy end it "enable or disables etag and last modified options as a group" do @resource.use_conditional_get(false) expect(@resource.use_etags).to be_falsey expect(@resource.use_last_modified).to be_falsey @resource.use_conditional_get(true) expect(@resource.use_etags).to be_truthy expect(@resource.use_last_modified).to be_truthy end it "disables etags indivdually" do @resource.use_etags(false) expect(@resource.use_etags).to be_falsey expect(@resource.use_last_modified).to be_truthy end it "disables last modified individually" do @resource.use_last_modified(false) expect(@resource.use_last_modified).to be_falsey expect(@resource.use_etags).to be_truthy end end describe "when it has group, mode, owner, source, and checksum" do before do if Chef::Platform.windows? @resource.path("C:/temp/origin/file.txt") @resource.rights(:read, "Everyone") @resource.deny_rights(:full_control, "Clumsy_Sam") else @resource.path("/this/path/") @resource.group("pokemon") @resource.mode("0664") @resource.owner("root") end @resource.source("https://www.google.com/images/srpr/logo3w.png") @resource.checksum("1" * 26) end it "describes its state" do state = @resource.state if Chef::Platform.windows? puts state expect(state[:rights]).to eq([{ :permissions => :read, :principals => "Everyone" }]) expect(state[:deny_rights]).to eq([{ :permissions => :full_control, :principals => "Clumsy_Sam" }]) else expect(state[:group]).to eq("pokemon") expect(state[:mode]).to eq("0664") expect(state[:owner]).to eq("root") expect(state[:checksum]).to eq("1" * 26) end end it "returns the path as its identity" do if Chef::Platform.windows? expect(@resource.identity).to eq("C:/temp/origin/file.txt") else expect(@resource.identity).to eq("/this/path/") end end end end chef-12.14.60/spec/unit/resource/resource_notification_spec.rb000066400000000000000000000171741276456504500243760ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef/resource/resource_notification" describe Chef::Resource::Notification do let(:notification) { Chef::Resource::Notification.new(:service_apache, :restart, :template_httpd_conf) } it "has a resource to be notified" do expect(notification.resource).to eq(:service_apache) end it "has an action to take on the service" do expect(notification.action).to eq(:restart) end it "has a notifying resource" do expect(notification.notifying_resource).to eq(:template_httpd_conf) end it "is a duplicate of another notification with the same target resource and action" do other = Chef::Resource::Notification.new(:service_apache, :restart, :sync_web_app_code) expect(notification.duplicates?(other)).to be_truthy end it "is not a duplicate of another notification if the actions differ" do other = Chef::Resource::Notification.new(:service_apache, :enable, :install_apache) expect(notification.duplicates?(other)).to be_falsey end it "is not a duplicate of another notification if the target resources differ" do other = Chef::Resource::Notification.new(:service_sshd, :restart, :template_httpd_conf) expect(notification.duplicates?(other)).to be_falsey end it "raises an ArgumentError if you try to check a non-ducktype object for duplication" do expect { notification.duplicates?(:not_a_notification) }.to raise_error(ArgumentError) end it "takes no action to resolve a resource reference that doesn't need to be resolved" do @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") notification.resource = @keyboard_cat @long_cat = Chef::Resource::Cat.new("long_cat") notification.notifying_resource = @long_cat @resource_collection = Chef::ResourceCollection.new # would raise an error since the resource is not in the collection notification.resolve_resource_reference(@resource_collection) expect(notification.resource).to eq(@keyboard_cat) end it "resolves a lazy reference to a resource" do notification.resource = { :cat => "keyboard_cat" } @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @keyboard_cat @long_cat = Chef::Resource::Cat.new("long_cat") notification.notifying_resource = @long_cat notification.resolve_resource_reference(@resource_collection) expect(notification.resource).to eq(@keyboard_cat) end it "resolves a lazy reference to its notifying resource" do @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") notification.resource = @keyboard_cat notification.notifying_resource = { :cat => "long_cat" } @long_cat = Chef::Resource::Cat.new("long_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @long_cat notification.resolve_resource_reference(@resource_collection) expect(notification.notifying_resource).to eq(@long_cat) end it "resolves lazy references to both its resource and its notifying resource" do notification.resource = { :cat => "keyboard_cat" } @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @keyboard_cat notification.notifying_resource = { :cat => "long_cat" } @long_cat = Chef::Resource::Cat.new("long_cat") @resource_collection << @long_cat notification.resolve_resource_reference(@resource_collection) expect(notification.resource).to eq(@keyboard_cat) expect(notification.notifying_resource).to eq(@long_cat) end it "raises a RuntimeError if you try to reference multiple resources" do notification.resource = { :cat => %w{keyboard_cat cheez_cat} } @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") @cheez_cat = Chef::Resource::Cat.new("cheez_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @keyboard_cat @resource_collection << @cheez_cat @long_cat = Chef::Resource::Cat.new("long_cat") notification.notifying_resource = @long_cat expect { notification.resolve_resource_reference(@resource_collection) }.to raise_error(RuntimeError) end it "raises a RuntimeError if you try to reference multiple notifying resources" do notification.notifying_resource = { :cat => %w{long_cat cheez_cat} } @long_cat = Chef::Resource::Cat.new("long_cat") @cheez_cat = Chef::Resource::Cat.new("cheez_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @long_cat @resource_collection << @cheez_cat @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") notification.resource = @keyboard_cat expect { notification.resolve_resource_reference(@resource_collection) }.to raise_error(RuntimeError) end it "raises a RuntimeError if it can't find a resource in the resource collection when resolving a lazy reference" do notification.resource = { :cat => "keyboard_cat" } @cheez_cat = Chef::Resource::Cat.new("cheez_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @cheez_cat @long_cat = Chef::Resource::Cat.new("long_cat") notification.notifying_resource = @long_cat expect { notification.resolve_resource_reference(@resource_collection) }.to raise_error(RuntimeError) end it "raises a RuntimeError if it can't find a notifying resource in the resource collection when resolving a lazy reference" do notification.notifying_resource = { :cat => "long_cat" } @cheez_cat = Chef::Resource::Cat.new("cheez_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @cheez_cat @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") notification.resource = @keyboard_cat expect { notification.resolve_resource_reference(@resource_collection) }.to raise_error(RuntimeError) end it "raises an ArgumentError if improper syntax is used in the lazy reference to its resource" do notification.resource = "cat => keyboard_cat" @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @keyboard_cat @long_cat = Chef::Resource::Cat.new("long_cat") notification.notifying_resource = @long_cat expect { notification.resolve_resource_reference(@resource_collection) }.to raise_error(ArgumentError) end it "raises an ArgumentError if improper syntax is used in the lazy reference to its notifying resource" do notification.notifying_resource = "cat => long_cat" @long_cat = Chef::Resource::Cat.new("long_cat") @resource_collection = Chef::ResourceCollection.new @resource_collection << @long_cat @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") notification.resource = @keyboard_cat expect { notification.resolve_resource_reference(@resource_collection) }.to raise_error(ArgumentError) end # Create test to resolve lazy references to both notifying resource and dest. resource # Create tests to check proper error raising end chef-12.14.60/spec/unit/resource/route_spec.rb000066400000000000000000000061061276456504500211300ustar00rootroot00000000000000# # Author:: Bryan McLellan (btm@loftninjas.org) # Author:: Tyler Cloke () # Copyright:: Copyright 2008-2016, Bryan McLellan # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::Route do before(:each) do @resource = Chef::Resource::Route.new("10.0.0.10") end it "should create a new Chef::Resource::Route" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Route) end it "should have a name" do expect(@resource.name).to eql("10.0.0.10") end it "should have a default action of 'add'" do expect(@resource.action).to eql([:add]) end it "should accept add or delete for action" do expect { @resource.action :add }.not_to raise_error expect { @resource.action :delete }.not_to raise_error expect { @resource.action :lolcat }.to raise_error(ArgumentError) end it "should use the object name as the target by default" do expect(@resource.target).to eql("10.0.0.10") end it "should allow you to specify the netmask" do @resource.netmask "255.255.255.0" expect(@resource.netmask).to eql("255.255.255.0") end it "should allow you to specify the gateway" do @resource.gateway "10.0.0.1" expect(@resource.gateway).to eql("10.0.0.1") end it "should allow you to specify the metric" do @resource.metric 10 expect(@resource.metric).to eql(10) end it "should allow you to specify the device" do @resource.device "eth0" expect(@resource.device).to eql("eth0") end it "should allow you to specify the route type" do @resource.route_type "host" expect(@resource.route_type).to eql(:host) end it "should default to a host route type" do expect(@resource.route_type).to eql(:host) end it "should accept a net route type" do @resource.route_type :net expect(@resource.route_type).to eql(:net) end it "should reject any other route_type but :host and :net" do expect { @resource.route_type "lolcat" }.to raise_error(ArgumentError) end describe "when it has netmask, gateway, and device" do before do @resource.target("charmander") @resource.netmask("lemask") @resource.gateway("111.111.111") @resource.device("forcefield") end it "describes its state" do state = @resource.state expect(state[:netmask]).to eq("lemask") expect(state[:gateway]).to eq("111.111.111") end it "returns the target as its identity" do expect(@resource.identity).to eq("charmander") end end end chef-12.14.60/spec/unit/resource/rpm_package_spec.rb000066400000000000000000000027421276456504500222450ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2010-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::RpmPackage, "initialize" do %w{linux aix}.each do |os| static_provider_resolution( resource: Chef::Resource::RpmPackage, provider: Chef::Provider::Package::Rpm, name: :rpm_package, action: :install, os: os ) end end describe Chef::Resource::RpmPackage, "allow_downgrade" do before(:each) do @resource = Chef::Resource::RpmPackage.new("foo") end it "should allow you to specify whether allow_downgrade is true or false" do expect { @resource.allow_downgrade true }.not_to raise_error expect { @resource.allow_downgrade false }.not_to raise_error expect { @resource.allow_downgrade "monkey" }.to raise_error(ArgumentError) end end chef-12.14.60/spec/unit/resource/ruby_block_spec.rb000066400000000000000000000034051276456504500221240ustar00rootroot00000000000000# # Author:: AJ Christensen () # Author:: Tyler Cloke () # 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::Resource::RubyBlock do before(:each) do @resource = Chef::Resource::RubyBlock.new("fakey_fakerton") end it "should create a new Chef::Resource::RubyBlock" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::RubyBlock) end it "should have a default action of 'run'" do expect(@resource.action).to eql([:run]) end it "should have a resource name of :ruby_block" do expect(@resource.resource_name).to eql(:ruby_block) end it "should accept a ruby block/proc/.. for the 'block' parameter" do expect(@resource.block do "foo" end.call).to eql("foo") end it "allows the action to be 'create'" do @resource.action :create expect(@resource.action).to eq([:create]) end describe "when it has been initialized with block code" do before do @resource.block_name("puts 'harrrr'") end it "returns the block as its identity" do expect(@resource.identity).to eq("puts 'harrrr'") end end end chef-12.14.60/spec/unit/resource/ruby_spec.rb000066400000000000000000000022671276456504500207570ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Resource::Ruby do before(:each) do @resource = Chef::Resource::Ruby.new("fakey_fakerton") end it "should create a new Chef::Resource::Ruby" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Ruby) end it "should have a resource name of :ruby" do expect(@resource.resource_name).to eql(:ruby) end it "should have an interpreter of ruby" do expect(@resource.interpreter).to eql("ruby") end end chef-12.14.60/spec/unit/resource/scm_spec.rb000066400000000000000000000134071276456504500205560ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Tyler Cloke () # 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::Resource::Scm do before(:each) do @resource = Chef::Resource::Scm.new("my awesome app") end it "should be a SCM resource" do expect(@resource).to be_a_kind_of(Chef::Resource::Scm) end it "supports :checkout, :export, :sync, :diff, and :log actions" do expect(@resource.allowed_actions).to include(:checkout) expect(@resource.allowed_actions).to include(:export) expect(@resource.allowed_actions).to include(:sync) expect(@resource.allowed_actions).to include(:diff) expect(@resource.allowed_actions).to include(:log) end it "takes the destination path as a string" do @resource.destination "/path/to/deploy/dir" expect(@resource.destination).to eql("/path/to/deploy/dir") end it "takes a string for the repository URL" do @resource.repository "git://github.com/opscode/chef.git" expect(@resource.repository).to eql("git://github.com/opscode/chef.git") end it "takes a string for the revision" do @resource.revision "abcdef" expect(@resource.revision).to eql("abcdef") end it "defaults to the ``HEAD'' revision" do expect(@resource.revision).to eql("HEAD") end it "takes a string for the user to run as" do @resource.user "dr_deploy" expect(@resource.user).to eql("dr_deploy") end it "also takes an integer for the user to run as" do @resource.user 0 expect(@resource.user).to eql(0) end it "takes a string for the group to run as, defaulting to nil" do expect(@resource.group).to be_nil @resource.group "opsdevs" expect(@resource.group).to eq("opsdevs") end it "also takes an integer for the group to run as" do @resource.group 23 expect(@resource.group).to eq(23) end it "has a svn_username String attribute" do @resource.svn_username "moartestsplz" expect(@resource.svn_username).to eql("moartestsplz") end it "has a svn_password String attribute" do @resource.svn_password "taftplz" expect(@resource.svn_password).to eql("taftplz") end it "has a svn_arguments String attribute" do @resource.svn_arguments "--more-taft plz" expect(@resource.svn_arguments).to eql("--more-taft plz") end it "has a svn_info_args String attribute" do expect(@resource.svn_info_args).to be_nil @resource.svn_info_args("--no-moar-plaintext-creds yep") expect(@resource.svn_info_args).to eq("--no-moar-plaintext-creds yep") end it "takes the depth as an integer for shallow clones" do @resource.depth 5 expect(@resource.depth).to eq(5) expect { @resource.depth "five" }.to raise_error(ArgumentError) end it "defaults to nil depth for a full clone" do expect(@resource.depth).to be_nil end it "takes a boolean for #enable_submodules" do @resource.enable_submodules true expect(@resource.enable_submodules).to be_truthy expect { @resource.enable_submodules "lolz" }.to raise_error(ArgumentError) end it "defaults to not enabling submodules" do expect(@resource.enable_submodules).to be_falsey end it "takes a boolean for #enable_checkout" do @resource.enable_checkout true expect(@resource.enable_checkout).to be_truthy expect { @resource.enable_checkout "lolz" }.to raise_error(ArgumentError) end it "defaults to enabling checkout" do expect(@resource.enable_checkout).to be_truthy end it "takes a string for the remote" do @resource.remote "opscode" expect(@resource.remote).to eql("opscode") expect { @resource.remote 1337 }.to raise_error(ArgumentError) end it "defaults to ``origin'' for the remote" do expect(@resource.remote).to eq("origin") end it "takes a string for the ssh wrapper" do @resource.ssh_wrapper "with_ssh_fu" expect(@resource.ssh_wrapper).to eql("with_ssh_fu") end it "defaults to nil for the ssh wrapper" do expect(@resource.ssh_wrapper).to be_nil end it "defaults to nil for the environment" do expect(@resource.environment).to be_nil end describe "when it has a timeout attribute" do let(:ten_seconds) { 10 } before { @resource.timeout(ten_seconds) } it "stores this timeout" do expect(@resource.timeout).to eq(ten_seconds) end end describe "when it has no timeout attribute" do it "should have no default timeout" do expect(@resource.timeout).to be_nil end end describe "when it has repository, revision, user, and group" do before do @resource.destination("hell") @resource.repository("apt") @resource.revision("1.2.3") @resource.user("root") @resource.group("super_adventure_club") end it "describes its state" do state = @resource.state expect(state[:revision]).to eq("1.2.3") end it "returns the destination as its identity" do expect(@resource.identity).to eq("hell") end end describe "when it has a environment attribute" do let(:test_environment) { { "CHEF_ENV" => "/tmp" } } before { @resource.environment(test_environment) } it "stores this environment" do expect(@resource.environment).to eq(test_environment) end end end chef-12.14.60/spec/unit/resource/script_spec.rb000066400000000000000000000026601276456504500212770ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tyler Cloke () # 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::Resource::Script do let(:resource_instance_name) { "fakey_fakerton" } let(:script_resource) { Chef::Resource::Script.new(resource_instance_name) } let(:resource_name) { :script } it "should accept a string for the interpreter" do script_resource.interpreter "naaaaNaNaNaaNaaNaaNaa" expect(script_resource.interpreter).to eql("naaaaNaNaNaaNaaNaaNaa") end context "when it has interpreter and flags" do before do script_resource.interpreter("gcc") script_resource.flags("-al") end it "returns the name as its identity" do expect(script_resource.identity).to eq(resource_instance_name) end end it_behaves_like "a script resource" end chef-12.14.60/spec/unit/resource/service_spec.rb000066400000000000000000000130541276456504500214320ustar00rootroot00000000000000# # Author:: AJ Christensen () # Author:: Tyler Cloke () # 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::Resource::Service do before(:each) do @resource = Chef::Resource::Service.new("chef") end it "should create a new Chef::Resource::Service" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::Service) end it "should not set a provider unless node[:init_package] is defined as systemd" do expect(@resource.provider).to eq(nil) end it "should set the service_name to the first argument to new" do expect(@resource.service_name).to eql("chef") end it "should set the pattern to be the service name by default" do expect(@resource.pattern).to eql("chef") end it "should accept a string for the service name" do @resource.service_name "something" expect(@resource.service_name).to eql("something") end it "should accept a string for the service pattern" do @resource.pattern ".*" expect(@resource.pattern).to eql(".*") end it "should not accept a regexp for the service pattern" do expect do @resource.pattern /.*/ end.to raise_error(ArgumentError) end it "should accept a string for the service start command" do @resource.start_command "/etc/init.d/chef start" expect(@resource.start_command).to eql("/etc/init.d/chef start") end it "should not accept a regexp for the service start command" do expect do @resource.start_command /.*/ end.to raise_error(ArgumentError) end it "should accept a string for the service stop command" do @resource.stop_command "/etc/init.d/chef stop" expect(@resource.stop_command).to eql("/etc/init.d/chef stop") end it "should not accept a regexp for the service stop command" do expect do @resource.stop_command /.*/ end.to raise_error(ArgumentError) end it "should accept a string for the service status command" do @resource.status_command "/etc/init.d/chef status" expect(@resource.status_command).to eql("/etc/init.d/chef status") end it "should not accept a regexp for the service status command" do expect do @resource.status_command /.*/ end.to raise_error(ArgumentError) end it "should accept a string for the service restart command" do @resource.restart_command "/etc/init.d/chef restart" expect(@resource.restart_command).to eql("/etc/init.d/chef restart") end it "should not accept a regexp for the service restart command" do expect do @resource.restart_command /.*/ end.to raise_error(ArgumentError) end it "should accept a string for the service reload command" do @resource.reload_command "/etc/init.d/chef reload" expect(@resource.reload_command).to eql("/etc/init.d/chef reload") end it "should not accept a regexp for the service reload command" do expect do @resource.reload_command /.*/ end.to raise_error(ArgumentError) end it "should accept a string for the service init command" do @resource.init_command "/etc/init.d/chef" expect(@resource.init_command).to eql("/etc/init.d/chef") end it "should not accept a regexp for the service init command" do expect do @resource.init_command /.*/ end.to raise_error(ArgumentError) end %w{enabled running}.each do |attrib| it "should accept true for #{attrib}" do @resource.send(attrib, true) expect(@resource.send(attrib)).to eql(true) end it "should accept false for #{attrib}" do @resource.send(attrib, false) expect(@resource.send(attrib)).to eql(false) end it "should not accept a string for #{attrib}" do expect { @resource.send(attrib, "poop") }.to raise_error(ArgumentError) end it "should default all the feature support to nil" do support_hash = { :status => nil, :restart => nil, :reload => nil } expect(@resource.supports).to eq(support_hash) end it "should allow you to set what features this resource supports as a array" do support_array = [ :status, :restart ] support_hash = { :status => true, :restart => true, :reload => nil } @resource.supports(support_array) expect(@resource.supports).to eq(support_hash) end it "should allow you to set what features this resource supports as a hash" do support_hash = { :status => true, :restart => true, :reload => false } @resource.supports(support_hash) expect(@resource.supports).to eq(support_hash) end end describe "when it has pattern and supports" do before do @resource.service_name("superfriend") @resource.enabled(true) @resource.running(false) end it "describes its state" do state = @resource.state expect(state[:enabled]).to eql(true) expect(state[:running]).to eql(false) end it "returns the service name as its identity" do expect(@resource.identity).to eq("superfriend") end end end chef-12.14.60/spec/unit/resource/smartos_package_spec.rb000066400000000000000000000020621276456504500231320ustar00rootroot00000000000000# # Author:: Thomas Bishop () # Copyright:: Copyright 2010-2016, Thomas Bishop # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::SmartosPackage, "initialize" do static_provider_resolution( resource: Chef::Resource::SmartosPackage, provider: Chef::Provider::Package::SmartOS, name: :smartos_package, action: :install, os: "solaris2", platform_family: "smartos" ) end chef-12.14.60/spec/unit/resource/solaris_package_spec.rb000066400000000000000000000025121276456504500231160ustar00rootroot00000000000000# # Author:: Prabhu Das () # Copyright:: Copyright 2013-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 "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::SolarisPackage, "initialize" do %w{solaris2 nexentacore}.each do |platform_family| static_provider_resolution( resource: Chef::Resource::SolarisPackage, provider: Chef::Provider::Package::Solaris, name: :solaris_package, action: :install, os: "solaris2", platform_family: platform_family ) end before(:each) do @resource = Chef::Resource::SolarisPackage.new("foo") end it "should set the package_name to the name provided" do expect(@resource.package_name).to eql("foo") end end chef-12.14.60/spec/unit/resource/subversion_spec.rb000066400000000000000000000043201276456504500221650ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::Subversion do static_provider_resolution( resource: Chef::Resource::Subversion, provider: Chef::Provider::Subversion, name: :subversion, action: :install ) before do @svn = Chef::Resource::Subversion.new("ohai, svn project!") end it "is a subclass of Resource::Scm" do expect(@svn).to be_an_instance_of(Chef::Resource::Subversion) expect(@svn).to be_a_kind_of(Chef::Resource::Scm) end it "allows the force_export action" do expect(@svn.allowed_actions).to include(:force_export) end it "sets svn info arguments to --no-auth-cache by default" do expect(@svn.svn_info_args).to eq("--no-auth-cache") end it "resets svn info arguments to nil when given false in the setter" do @svn.svn_info_args(false) expect(@svn.svn_info_args).to be_nil end it "sets svn arguments to --no-auth-cache by default" do expect(@svn.svn_arguments).to eq("--no-auth-cache") end it "sets svn binary to nil by default" do expect(@svn.svn_binary).to be_nil end it "resets svn arguments to nil when given false in the setter" do @svn.svn_arguments(false) expect(@svn.svn_arguments).to be_nil end it "hides password from custom exception message" do @svn.svn_password "l33th4x0rpa$$w0rd" e = @svn.customize_exception(Chef::Exceptions::Exec.new "Exception with password #{@svn.svn_password}") expect(e.message.include?(@svn.svn_password)).to be_falsey end end chef-12.14.60/spec/unit/resource/systemd_unit_spec.rb000066400000000000000000000113111276456504500225130ustar00rootroot00000000000000# # Author:: Nathan Williams () # Copyright:: Copyright 2016, Nathan Williams # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::SystemdUnit do before(:each) do @resource = Chef::Resource::SystemdUnit.new("sysstat-collect.timer") end let(:unit_content_string) { "[Unit]\nDescription = Run system activity accounting tool every 10 minutes\n\n[Timer]\nOnCalendar = *:00/10\n\n[Install]\nWantedBy = sysstat.service\n" } let(:unit_content_hash) do { "Unit" => { "Description" => "Run system activity accounting tool every 10 minutes", }, "Timer" => { "OnCalendar" => "*:00/10", }, "Install" => { "WantedBy" => "sysstat.service", }, } end it "creates a new Chef::Resource::SystemdUnit" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::SystemdUnit) end it "should have a name" do expect(@resource.name).to eql("sysstat-collect.timer") end it "has a default action of nothing" do expect(@resource.action).to eql([:nothing]) end it "supports appropriate unit actions" do expect { @resource.action :create }.not_to raise_error expect { @resource.action :delete }.not_to raise_error expect { @resource.action :enable }.not_to raise_error expect { @resource.action :disable }.not_to raise_error expect { @resource.action :mask }.not_to raise_error expect { @resource.action :unmask }.not_to raise_error expect { @resource.action :start }.not_to raise_error expect { @resource.action :stop }.not_to raise_error expect { @resource.action :restart }.not_to raise_error expect { @resource.action :reload }.not_to raise_error expect { @resource.action :wrong }.to raise_error(ArgumentError) end it "accepts boolean state properties" do expect { @resource.active false }.not_to raise_error expect { @resource.active true }.not_to raise_error expect { @resource.active "yes" }.to raise_error(ArgumentError) expect { @resource.enabled true }.not_to raise_error expect { @resource.enabled false }.not_to raise_error expect { @resource.enabled "no" }.to raise_error(ArgumentError) expect { @resource.masked true }.not_to raise_error expect { @resource.masked false }.not_to raise_error expect { @resource.masked :nope }.to raise_error(ArgumentError) expect { @resource.static true }.not_to raise_error expect { @resource.static false }.not_to raise_error expect { @resource.static "yep" }.to raise_error(ArgumentError) end it "accepts the content property" do expect { @resource.content nil }.not_to raise_error expect { @resource.content "test" }.not_to raise_error expect { @resource.content({}) }.not_to raise_error expect { @resource.content 5 }.to raise_error(ArgumentError) end it "accepts the user property" do expect { @resource.user nil }.not_to raise_error expect { @resource.user "deploy" }.not_to raise_error expect { @resource.user 5 }.to raise_error(ArgumentError) end it "accepts the triggers_reload property" do expect { @resource.triggers_reload true }.not_to raise_error expect { @resource.triggers_reload false }.not_to raise_error expect { @resource.triggers_reload "no" }.to raise_error(ArgumentError) end it "reports its state" do @resource.active true @resource.enabled true @resource.masked false @resource.static false @resource.content "test" state = @resource.state expect(state[:active]).to eq(true) expect(state[:enabled]).to eq(true) expect(state[:masked]).to eq(false) expect(state[:static]).to eq(false) expect(state[:content]).to eq("test") end it "returns the unit name as its identity" do expect(@resource.identity).to eq("sysstat-collect.timer") end it "serializes to ini with a string-formatted content property" do @resource.content(unit_content_string) expect(@resource.to_ini).to eq unit_content_string end it "serializes to ini with a hash-formatted content property" do @resource.content(unit_content_hash) expect(@resource.to_ini).to eq unit_content_string end end chef-12.14.60/spec/unit/resource/template_spec.rb000066400000000000000000000150621276456504500216060ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Resource::Template do before(:each) do @resource = Chef::Resource::Template.new("fakey_fakerton") end describe "initialize" do it "should create a new Chef::Resource::Template" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::File) expect(@resource).to be_a_kind_of(Chef::Resource::Template) end end describe "source" do it "should accept a string for the template source" do @resource.source "something" expect(@resource.source).to eql("something") end it "should have a default based on the param name with .erb appended" do expect(@resource.source).to eql("fakey_fakerton.erb") end it "should use only the basename of the file as the default" do r = Chef::Resource::Template.new("/tmp/obit/fakey_fakerton") expect(r.source).to eql("fakey_fakerton.erb") end end describe "variables" do it "should accept a hash for the variable list" do @resource.variables({ :reluctance => :awkward }) expect(@resource.variables).to eq({ :reluctance => :awkward }) end end describe "cookbook" do it "should accept a string for the cookbook name" do @resource.cookbook("foo") expect(@resource.cookbook).to eq("foo") end it "should default to nil" do expect(@resource.cookbook).to eq(nil) end end describe "local" do it "should accept a boolean for whether a template is local or remote" do @resource.local(true) expect(@resource.local).to eq(true) end it "should default to false" do expect(@resource.local).to eq(false) end end describe "when it has a path, owner, group, mode, and checksum" do before do @resource.path("/tmp/foo.txt") @resource.owner("root") @resource.group("wheel") @resource.mode("0644") @resource.checksum("1" * 64) end context "on unix", :unix_only do it "describes its state" do state = @resource.state expect(state[:owner]).to eq("root") expect(state[:group]).to eq("wheel") expect(state[:mode]).to eq("0644") expect(state[:checksum]).to eq("1" * 64) end end context "on windows", :windows_only do # according to Chef::Resource::File, windows state attributes are rights + deny_rights skip "it describes its state" end it "returns the file path as its identity" do expect(@resource.identity).to eq("/tmp/foo.txt") end end describe "defining helper methods" do module ExampleHelpers def static_example "static_example" end end it "collects helper method bodies as blocks" do @resource.helper(:example_1) { "example_1" } @resource.helper(:example_2) { "example_2" } expect(@resource.inline_helper_blocks[:example_1].call).to eq("example_1") expect(@resource.inline_helper_blocks[:example_2].call).to eq("example_2") end it "compiles helper methods into a module" do @resource.helper(:example_1) { "example_1" } @resource.helper(:example_2) { "example_2" } modules = @resource.helper_modules expect(modules.size).to eq(1) o = Object.new modules.each { |m| o.extend(m) } expect(o.example_1).to eq("example_1") expect(o.example_2).to eq("example_2") end it "compiles helper methods with arguments into a module" do @resource.helper(:shout) { |quiet| quiet.upcase } modules = @resource.helper_modules o = Object.new modules.each { |m| o.extend(m) } expect(o.shout("shout")).to eq("SHOUT") end it "raises an error when attempting to define a helper method without a method body" do expect { @resource.helper(:example) }.to raise_error(Chef::Exceptions::ValidationFailed) end it "raises an error when attempting to define a helper method with a non-Symbod method name" do expect { @resource.helper("example") { "fail" } }.to raise_error(Chef::Exceptions::ValidationFailed) end it "collects helper module bodies as blocks" do @resource.helpers do def example_1 "example_1" end end module_body = @resource.inline_helper_modules.first expect(module_body).to be_a(Proc) end it "compiles helper module bodies into modules" do @resource.helpers do def example_1 "example_1" end end modules = @resource.helper_modules expect(modules.size).to eq(1) o = Object.new modules.each { |m| o.extend(m) } expect(o.example_1).to eq("example_1") end it "raises an error when no block or module name is given for helpers definition" do expect { @resource.helpers() }.to raise_error(Chef::Exceptions::ValidationFailed) end it "raises an error when a non-module is given for helpers definition" do expect { @resource.helpers("NotAModule") }.to raise_error(Chef::Exceptions::ValidationFailed) end it "raises an error when a module name and block are both given for helpers definition" do expect { @resource.helpers(ExampleHelpers) { module_code } }.to raise_error(Chef::Exceptions::ValidationFailed) end it "collects helper modules" do @resource.helpers(ExampleHelpers) expect(@resource.helper_modules).to include(ExampleHelpers) end it "combines all helpers into a set of compiled modules" do @resource.helpers(ExampleHelpers) @resource.helpers do def inline_module "inline_module" end end @resource.helper(:inline_method) { "inline_method" } expect(@resource.helper_modules.size).to eq(3) o = Object.new @resource.helper_modules.each { |m| o.extend(m) } expect(o.static_example).to eq("static_example") expect(o.inline_module).to eq("inline_module") expect(o.inline_method).to eq("inline_method") end end end chef-12.14.60/spec/unit/resource/timestamped_deploy_spec.rb000066400000000000000000000017551276456504500236670ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::TimestampedDeploy, "initialize" do static_provider_resolution( resource: Chef::Resource::TimestampedDeploy, provider: Chef::Provider::Deploy::Timestamped, name: :timestamped_deploy, action: :deploy, os: "linux", platform_family: "rhel" ) end chef-12.14.60/spec/unit/resource/user_spec.rb000066400000000000000000000075251276456504500207560ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::Resource::User, "initialize" do before(:each) do @resource = Chef::Resource::User.new("adam") end it "should create a new Chef::Resource::User" do expect(@resource).to be_a_kind_of(Chef::Resource) expect(@resource).to be_a_kind_of(Chef::Resource::User) end it "should set the resource_name to :user" do expect(@resource.resource_name).to eql(:user_resource_abstract_base_class) end it "should set the username equal to the argument to initialize" do expect(@resource.username).to eql("adam") end %w{comment uid gid home shell password}.each do |attrib| it "should set #{attrib} to nil" do expect(@resource.send(attrib)).to eql(nil) end end it "should set action to :create" do expect(@resource.action).to eql([:create]) end it "should set supports[:manage_home] to false" do expect(@resource.supports[:manage_home]).to eql(false) end it "should set supports[:non_unique] to false" do expect(@resource.supports[:non_unique]).to eql(false) end it "should set force to false" do expect(@resource.force).to eql(false) end %w{create remove modify manage lock unlock}.each do |action| it "should allow action #{action}" do expect(@resource.allowed_actions.detect { |a| a == action.to_sym }).to eql(action.to_sym) end end it "should accept domain users (@ or \ separator) on non-windows" do expect { @resource.username "domain\@user" }.not_to raise_error expect(@resource.username).to eq("domain\@user") expect { @resource.username "domain\\user" }.not_to raise_error expect(@resource.username).to eq("domain\\user") end end %w{username comment home shell password}.each do |attrib| describe Chef::Resource::User, attrib do before(:each) do @resource = Chef::Resource::User.new("adam") end it "should allow a string" do @resource.send(attrib, "adam") expect(@resource.send(attrib)).to eql("adam") end it "should not allow a hash" do expect { @resource.send(attrib, { :woot => "i found it" }) }.to raise_error(ArgumentError) end end end %w{uid gid}.each do |attrib| describe Chef::Resource::User, attrib do before(:each) do @resource = Chef::Resource::User.new("adam") end it "should allow a string" do @resource.send(attrib, "100") expect(@resource.send(attrib)).to eql("100") end it "should allow an integer" do @resource.send(attrib, 100) expect(@resource.send(attrib)).to eql(100) end it "should not allow a hash" do expect { @resource.send(attrib, { :woot => "i found it" }) }.to raise_error(ArgumentError) end end describe "when it has uid, gid, and home" do before do @resource = Chef::Resource::User.new("root") @resource.uid(123) @resource.gid(456) @resource.home("/usr/local/root/") end it "describes its state" do state = @resource.state expect(state[:uid]).to eq(123) expect(state[:gid]).to eq(456) expect(state[:home]).to eq("/usr/local/root/") end it "returns the username as its identity" do expect(@resource.identity).to eq("root") end end end chef-12.14.60/spec/unit/resource/windows_package_spec.rb000066400000000000000000000056361276456504500231460ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::WindowsPackage, "initialize" do before(:each) do stub_const("File::ALT_SEPARATOR", "\\") end static_provider_resolution( resource: Chef::Resource::WindowsPackage, provider: Chef::Provider::Package::Windows, os: "windows", name: :windows_package, action: :start ) let(:resource) { Chef::Resource::WindowsPackage.new("solitaire.msi") } it "returns a Chef::Resource::WindowsPackage" do expect(resource).to be_a_kind_of(Chef::Resource::WindowsPackage) end it "sets the resource_name to :windows_package" do expect(resource.resource_name).to eql(:windows_package) end it "supports setting installer_type as a symbol" do resource.installer_type(:msi) expect(resource.installer_type).to eql(:msi) end # String, Integer [ "600", 600 ].each do |val| it "supports setting a timeout as a #{val.class}" do resource.timeout(val) expect(resource.timeout).to eql(val) end end # String, Integer, Array [ "42", 42, [47, 48, 49] ].each do |val| it "supports setting an alternate return value as a #{val.class}" do resource.returns(val) expect(resource.returns).to eql(val) end end it "coverts a source to an absolute path" do allow(::File).to receive(:absolute_path).and_return("c:\\files\\frost.msi") resource.source("frost.msi") expect(resource.source).to eql "c:\\files\\frost.msi" end it "converts slashes to backslashes in the source path" do allow(::File).to receive(:absolute_path).and_return("c:\\frost.msi") resource.source("c:/frost.msi") expect(resource.source).to eql "c:\\frost.msi" end it "defaults source to the resource name" do # it's a little late to stub out File.absolute_path expect(resource.source).to include("solitaire.msi") end it "supports the checksum attribute" do resource.checksum("somechecksum") expect(resource.checksum).to eq("somechecksum") end context "when a URL is used" do let(:resource_source) { "https://foo.bar/solitare.msi" } let(:resource) { Chef::Resource::WindowsPackage.new(resource_source) } it "should return the source unmodified" do expect(resource.source).to eq(resource_source) end end end chef-12.14.60/spec/unit/resource/windows_service_spec.rb000066400000000000000000000030431276456504500232010ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::Resource::WindowsService, "initialize" do static_provider_resolution( resource: Chef::Resource::WindowsService, provider: Chef::Provider::Service::Windows, os: "windows", name: :windows_service, action: :start ) let(:resource) { Chef::Resource::WindowsService.new("BITS") } it "returns a Chef::Resource::WindowsService" do expect(resource).to be_a_kind_of(Chef::Resource::WindowsService) end it "sets the resource_name to :windows_service" do expect(resource.resource_name).to eql(:windows_service) end it "supports setting startup_type" do resource.startup_type(:manual) expect(resource.startup_type).to eql(:manual) end it "allows the action to be 'configure_startup'" do resource.action :configure_startup expect(resource.action).to eq([:configure_startup]) end end chef-12.14.60/spec/unit/resource/yum_package_spec.rb000066400000000000000000000054011276456504500222540ustar00rootroot00000000000000# # Author:: AJ Christensen () # 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" require "support/shared/unit/resource/static_provider_resolution" describe Chef::Resource::YumPackage, "initialize" do static_provider_resolution( resource: Chef::Resource::YumPackage, provider: Chef::Provider::Package::Yum, name: :yum_package, action: :install, os: "linux", platform_family: "rhel" ) end describe Chef::Resource::YumPackage, "arch" do before(:each) do @resource = Chef::Resource::YumPackage.new("foo") end it "should set the arch variable to whatever is passed in" do @resource.arch("i386") expect(@resource.arch).to eql("i386") end end describe Chef::Resource::YumPackage, "flush_cache" do before(:each) do @resource = Chef::Resource::YumPackage.new("foo") end it "should default the flush timing to false" do flush_hash = { :before => false, :after => false } expect(@resource.flush_cache).to eq(flush_hash) end it "should allow you to set the flush timing with an array" do flush_array = [ :before, :after ] flush_hash = { :before => true, :after => true } @resource.flush_cache(flush_array) expect(@resource.flush_cache).to eq(flush_hash) end it "should allow you to set the flush timing with a hash" do flush_hash = { :before => true, :after => true } @resource.flush_cache(flush_hash) expect(@resource.flush_cache).to eq(flush_hash) end end describe Chef::Resource::YumPackage, "allow_downgrade" do before(:each) do @resource = Chef::Resource::YumPackage.new("foo") end it "should allow you to specify whether allow_downgrade is true or false" do expect { @resource.allow_downgrade true }.not_to raise_error expect { @resource.allow_downgrade false }.not_to raise_error expect { @resource.allow_downgrade "monkey" }.to raise_error(ArgumentError) end end describe Chef::Resource::YumPackage, "yum_binary" do let(:resource) { Chef::Resource::YumPackage.new("foo") } it "should allow you to specify the yum_binary" do resource.yum_binary "/usr/bin/yum-something" expect(resource.yum_binary).to eql("/usr/bin/yum-something") end end chef-12.14.60/spec/unit/resource/yum_repository_spec.rb000066400000000000000000000035631276456504500231070ustar00rootroot00000000000000# # Author:: Thom May () # Copyright:: Copyright (c) 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::Resource::YumRepository do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:resource) { Chef::Resource::YumRepository.new("multiverse", run_context) } context "on linux", :linux_only do it "should create a new Chef::Resource::YumRepository" do expect(resource).to be_a_kind_of(Chef::Resource) expect(resource).to be_a_kind_of(Chef::Resource::YumRepository) end it "should resolve to a Noop class when yum is not found" do expect(Chef::Provider::YumRepository).to receive(:which).with("yum").and_return(false) expect(resource.provider_for_action(:add)).to be_a(Chef::Provider::Noop) end it "should resolve to a YumRepository class when yum is found" do expect(Chef::Provider::YumRepository).to receive(:which).with("yum").and_return(true) expect(resource.provider_for_action(:add)).to be_a(Chef::Provider::YumRepository) end end context "on windows", :windows_only do it "should resolve to a NoOp provider" do expect(resource.provider_for_action(:add)).to be_a(Chef::Provider::Noop) end end end chef-12.14.60/spec/unit/resource_builder_spec.rb000066400000000000000000000000371276456504500214750ustar00rootroot00000000000000# see spec/unit/recipe_spec.rb chef-12.14.60/spec/unit/resource_collection/000077500000000000000000000000001276456504500206435ustar00rootroot00000000000000chef-12.14.60/spec/unit/resource_collection/resource_list_spec.rb000066400000000000000000000100251276456504500250620ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::ResourceCollection::ResourceList do let(:resource_list) { Chef::ResourceCollection::ResourceList.new() } let(:resource) { Chef::Resource::ZenMaster.new("makoto") } let(:second_resource) { Chef::Resource::ZenMaster.new("hattori") } def insert_resource(res) expect { resource_list.insert(res) }.not_to raise_error end describe "initialize" do it "should return a Chef::ResourceList" do expect(resource_list).to be_instance_of(Chef::ResourceCollection::ResourceList) end end describe "insert" do it "should be able to insert a Chef::Resource" do insert_resource(resource) expect(resource_list[0]).to be(resource) end it "should insert things in order" do insert_resource(resource) insert_resource(second_resource) expect(resource_list[0]).to be(resource) expect(resource_list[1]).to be(second_resource) end it "should raise error when trying to install something other than Chef::Resource" do expect { resource_list.insert("not a resource") }.to raise_error(ArgumentError) end end describe "accessors" do it "should be able to insert with []=" do expect { resource_list[0] = resource }.not_to raise_error expect { resource_list[1] = second_resource }.not_to raise_error expect(resource_list[0]).to be(resource) expect(resource_list[1]).to be(second_resource) end it "should be empty by default" do expect(resource_list.empty?).to be_truthy end describe "when resources are inserted" do before do insert_resource(resource) insert_resource(second_resource) end it "should get resources with all_resources method" do resources = resource_list.all_resources expect(resources[0]).to be(resource) expect(resources[1]).to be(second_resource) end it "should be able to get resources with each" do current = 0 expected_resources = [resource, second_resource] resource_list.each do |r| expect(r).to be(expected_resources[current]) current += 1 end expect(current).to eq(2) end it "should be able to get resources with each_index" do current = 0 resource_list.each_index do |i| expect(i).to eq(current) current += 1 end expect(current).to eq(2) end it "should be able to check if the list is empty" do expect(resource_list.empty?).to be_falsey end end end describe "during execute" do before(:each) do insert_resource(resource) insert_resource(second_resource) end it "should execute resources in order" do current = 0 expected_resources = [resource, second_resource] resource_list.execute_each_resource do |r| expect(r).to be(expected_resources[current]) current += 1 end expect(current).to eq(2) end it "should be able to insert resources on the fly" do resource_to_inject = Chef::Resource::ZenMaster.new("there is no spoon") expected_resources = [resource, resource_to_inject, second_resource] resource_list.execute_each_resource do |r| resource_list.insert(resource_to_inject) if r == resource end expect(resource_list.all_resources).to eq(expected_resources) end end end chef-12.14.60/spec/unit/resource_collection/resource_set_spec.rb000066400000000000000000000200441276456504500247040ustar00rootroot00000000000000# # Author:: Tyler Ball () # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" describe Chef::ResourceCollection::ResourceSet do let(:collection) { Chef::ResourceCollection::ResourceSet.new } let(:zen_master_name) { "Neo" } let(:zen_master2_name) { "Morpheus" } let(:zen_follower_name) { "Squid" } let(:zen_master) { Chef::Resource::ZenMaster.new(zen_master_name) } let(:zen_master2) { Chef::Resource::ZenMaster.new(zen_master2_name) } let(:zen_follower) { Chef::Resource::ZenFollower.new(zen_follower_name) } describe "initialize" do it "should return a Chef::ResourceSet" do expect(collection).to be_instance_of(Chef::ResourceCollection::ResourceSet) end end describe "keys" do it "should return an empty list for an empty ResourceSet" do expect(collection.keys).to eq([]) end it "should return the keys for a non-empty ResourceSet" do collection.instance_variable_get(:@resources_by_key)["key"] = nil expect(collection.keys).to eq(["key"]) end end describe "insert_as, lookup and find" do # To validate insert_as you need lookup, and vice-versa - putting all tests in 1 context to avoid duplication it "should accept only Chef::Resources" do expect { collection.insert_as(zen_master) }.to_not raise_error expect { collection.insert_as("string") }.to raise_error(ArgumentError) end it "should allow you to lookup resources by a default .to_s" do collection.insert_as(zen_master) expect(collection.lookup(zen_master.to_s)).to equal(zen_master) end it "should use a custom type and name to insert" do collection.insert_as(zen_master, "OtherResource", "other_resource") expect(collection.lookup("OtherResource[other_resource]")).to equal(zen_master) end it "should raise an exception if you send something strange to lookup" do expect { collection.lookup(:symbol) }.to raise_error(ArgumentError) end it "should raise an exception if it cannot find a resource with lookup" do expect { collection.lookup(zen_master.to_s) }.to raise_error(Chef::Exceptions::ResourceNotFound) end it "should find a resource by type symbol and name" do collection.insert_as(zen_master) expect(collection.find(:zen_master => zen_master_name)).to equal(zen_master) end it "should find a resource by type symbol and array of names" do collection.insert_as(zen_master) collection.insert_as(zen_master2) check_by_names(collection.find(:zen_master => [zen_master_name, zen_master2_name]), zen_master_name, zen_master2_name) end it "should find a resource by type symbol and array of names with custom names" do collection.insert_as(zen_master, :zzz, "name1") collection.insert_as(zen_master2, :zzz, "name2") check_by_names(collection.find( :zzz => %w{name1 name2}), zen_master_name, zen_master2_name) end it "should find resources of multiple kinds (:zen_master => a, :zen_follower => b)" do collection.insert_as(zen_master) collection.insert_as(zen_follower) check_by_names(collection.find(:zen_master => [zen_master_name], :zen_follower => [zen_follower_name]), zen_master_name, zen_follower_name) end it "should find resources of multiple kinds (:zen_master => a, :zen_follower => b with custom names)" do collection.insert_as(zen_master, :zzz, "name1") collection.insert_as(zen_master2, :zzz, "name2") collection.insert_as(zen_follower, :yyy, "name3") check_by_names(collection.find(:zzz => %w{name1 name2}, :yyy => ["name3"]), zen_master_name, zen_follower_name, zen_master2_name) end it "should find a resource by string zen_master[a]" do collection.insert_as(zen_master) expect(collection.find("zen_master[#{zen_master_name}]")).to eq(zen_master) end it "should find a resource by string zen_master[a] with custom names" do collection.insert_as(zen_master, :zzz, "name1") expect(collection.find("zzz[name1]")).to eq(zen_master) end it "should find resources by strings of zen_master[a,b]" do collection.insert_as(zen_master) collection.insert_as(zen_master2) check_by_names(collection.find("zen_master[#{zen_master_name},#{zen_master2_name}]"), zen_master_name, zen_master2_name) end it "should find resources by strings of zen_master[a,b] with custom names" do collection.insert_as(zen_master, :zzz, "name1") collection.insert_as(zen_master2, :zzz, "name2") check_by_names(collection.find("zzz[name1,name2]"), zen_master_name, zen_master2_name) end it "should find resources of multiple types by strings of zen_master[a]" do collection.insert_as(zen_master) collection.insert_as(zen_follower) check_by_names(collection.find("zen_master[#{zen_master_name}]", "zen_follower[#{zen_follower_name}]"), zen_master_name, zen_follower_name) end it "should find resources of multiple types by strings of zen_master[a] with custom names" do collection.insert_as(zen_master, :zzz, "name1") collection.insert_as(zen_master2, :zzz, "name2") collection.insert_as(zen_follower, :yyy, "name3") check_by_names(collection.find("zzz[name1,name2]", "yyy[name3]"), zen_master_name, zen_follower_name, zen_master2_name) end it "should only keep the last copy when multiple instances of a Resource are inserted" do collection.insert_as(zen_master) expect(collection.find("zen_master[#{zen_master_name}]")).to eq(zen_master) new_zm = zen_master.dup new_zm.retries = 10 expect(new_zm).to_not eq(zen_master) collection.insert_as(new_zm) expect(collection.find("zen_master[#{zen_master_name}]")).to eq(new_zm) end it "should raise an exception if you pass a bad name to resources" do expect { collection.find("michael jackson") }.to raise_error(ArgumentError) end it "should raise an exception if you pass something other than a string or hash to resource" do expect { collection.find([Array.new]) }.to raise_error(ArgumentError) end it "raises an error when attempting to find a resource that does not exist" do expect { collection.find("script[nonesuch]") }.to raise_error(Chef::Exceptions::ResourceNotFound) end end describe "validate_lookup_spec!" do it "accepts a string of the form 'resource_type[resource_name]'" do expect(collection.validate_lookup_spec!("resource_type[resource_name]")).to be_truthy end it "accepts a single-element :resource_type => 'resource_name' Hash" do expect(collection.validate_lookup_spec!(:service => "apache2")).to be_truthy end it "accepts a chef resource object" do expect(collection.validate_lookup_spec!(zen_master)).to be_truthy end it "rejects a malformed query string" do expect { collection.validate_lookup_spec!("resource_type[missing-end-bracket") }.to \ raise_error(Chef::Exceptions::InvalidResourceSpecification) end it "rejects an argument that is not a String, Hash, or Chef::Resource" do expect { collection.validate_lookup_spec!(Object.new) }.to \ raise_error(Chef::Exceptions::InvalidResourceSpecification) end end def check_by_names(results, *names) expect(results.size).to eq(names.size) names.each do |name| expect(results.detect { |r| r.name == name }).to_not eq(nil) end end end chef-12.14.60/spec/unit/resource_collection/stepable_iterator_spec.rb000066400000000000000000000101051276456504500257070ustar00rootroot00000000000000# Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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::ResourceCollection::StepableIterator do CRSI = Chef::ResourceCollection::StepableIterator it "has an empty array for its collection by default" do expect(CRSI.new.collection).to eq([]) end describe "doing basic iteration" do before do @simple_collection = [1, 2, 3, 4] @iterator = CRSI.for_collection(@simple_collection) end it "re-initializes the instance with a collection" do expect(@iterator.collection).to equal(@simple_collection) expect(@iterator.size).to eq(4) end it "iterates over the collection" do sum = 0 @iterator.each do |int| sum += int end expect(sum).to eq(10) end it "iterates over the collection with each_index" do collected_by_index = [] @iterator.each_index do |idx| collected_by_index << @simple_collection[idx] end expect(collected_by_index).to eq(@simple_collection) expect(collected_by_index).not_to equal(@simple_collection) end it "iterates over the collection with index and element" do collected = {} @iterator.each_with_index do |element, index| collected[index] = element end expect(collected).to eq({ 0 => 1, 1 => 2, 2 => 3, 3 => 4 }) end end describe "pausing and resuming iteration" do before do @collection = [] @snitch_var = nil @collection << lambda { @snitch_var = 23 } @collection << lambda { @iterator.pause } @collection << lambda { @snitch_var = 42 } @iterator = CRSI.for_collection(@collection) @iterator.each { |proc| proc.call } end it "allows the iteration to be paused" do expect(@snitch_var).to eq(23) end it "allows the iteration to be resumed" do expect(@snitch_var).to eq(23) @iterator.resume expect(@snitch_var).to eq(42) end it "allows iteration to be rewound" do @iterator.skip_back(2) @iterator.resume expect(@snitch_var).to eq(23) @iterator.resume expect(@snitch_var).to eq(42) end it "allows iteration to be fast forwarded" do @iterator.skip_forward @iterator.resume expect(@snitch_var).to eq(23) end it "allows iteration to be rewound" do @snitch_var = nil @iterator.rewind expect(@iterator.position).to eq(0) @iterator.resume expect(@snitch_var).to eq(23) end it "allows iteration to be stepped" do @snitch_var = nil @iterator.rewind @iterator.step expect(@iterator.position).to eq(1) expect(@snitch_var).to eq(23) end it "doesn't step if there are no more steps" do expect(@iterator.step).to eq(3) expect { @iterator.step }.not_to raise_error expect(@iterator.step).to be_nil end it "allows the iteration to start by being stepped" do @snitch_var = nil @iterator = CRSI.for_collection(@collection) @iterator.iterate_on(:element) { |proc| proc.call } @iterator.step expect(@iterator.position).to eq(1) expect(@snitch_var).to eq(23) end it "should work correctly when elements are added to the collection during iteration" do @collection.insert(2, lambda { @snitch_var = 815 }) @collection.insert(3, lambda { @iterator.pause }) @iterator.resume expect(@snitch_var).to eq(815) @iterator.resume expect(@snitch_var).to eq(42) end end end chef-12.14.60/spec/unit/resource_collection_spec.rb000066400000000000000000000333161276456504500222100ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # 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::ResourceCollection do let(:rc) { Chef::ResourceCollection.new() } let(:resource) { Chef::Resource::ZenMaster.new("makoto") } it "should throw an error when calling a non-delegated method" do expect { rc.not_a_method }.to raise_error(NoMethodError) end describe "initialize" do it "should return a Chef::ResourceCollection" do expect(rc).to be_kind_of(Chef::ResourceCollection) end end describe "[]" do it "should accept Chef::Resources through [index]" do expect { rc[0] = resource }.not_to raise_error expect { rc[0] = "string" }.to raise_error(ArgumentError) end it "should allow you to fetch Chef::Resources by position" do rc[0] = resource expect(rc[0]).to eql(resource) end end describe "push" do it "should accept Chef::Resources through pushing" do expect { rc.push(resource) }.not_to raise_error expect { rc.push("string") }.to raise_error(ArgumentError) end end describe "<<" do it "should accept the << operator" do expect { rc << resource }.not_to raise_error end end describe "insert" do it "should accept only Chef::Resources" do expect { rc.insert(resource) }.not_to raise_error expect { rc.insert("string") }.to raise_error(ArgumentError) end it "should accept named arguments in any order" do rc.insert(resource, :instance_name => "foo", :resource_type => "bar") expect(rc[0]).to eq(resource) end it "should append resources to the end of the collection when not executing a run" do zmr = Chef::Resource::ZenMaster.new("there is no spoon") rc.insert(resource) rc.insert(zmr) expect(rc[0]).to eql(resource) expect(rc[1]).to eql(zmr) end it "should insert resources to the middle of the collection if called while executing a run" do resource_to_inject = Chef::Resource::ZenMaster.new("there is no spoon") zmr = Chef::Resource::ZenMaster.new("morpheus") dummy = Chef::Resource::ZenMaster.new("keanu reeves") rc.insert(zmr) rc.insert(dummy) rc.execute_each_resource do |resource| rc.insert(resource_to_inject) if resource == zmr end expect(rc[0]).to eql(zmr) expect(rc[1]).to eql(resource_to_inject) expect(rc[2]).to eql(dummy) end end describe "each" do it "should allow you to iterate over every resource in the collection" do load_up_resources results = Array.new expect do rc.each do |r| results << r.name end end.not_to raise_error results.each_index do |i| case i when 0 expect(results[i]).to eql("dog") when 1 expect(results[i]).to eql("cat") when 2 expect(results[i]).to eql("monkey") end end end end describe "each_index" do it "should allow you to iterate over every resource by index" do load_up_resources results = Array.new expect do rc.each_index do |i| results << rc[i].name end end.not_to raise_error results.each_index do |i| case i when 0 expect(results[i]).to eql("dog") when 1 expect(results[i]).to eql("cat") when 2 expect(results[i]).to eql("monkey") end end end end describe "lookup" do it "should allow you to find resources by name via lookup" do zmr = Chef::Resource::ZenMaster.new("dog") rc << zmr expect(rc.lookup(zmr.to_s)).to eql(zmr) zmr = Chef::Resource::ZenMaster.new("cat") rc[0] = zmr expect(rc.lookup(zmr)).to eql(zmr) zmr = Chef::Resource::ZenMaster.new("monkey") rc.push(zmr) expect(rc.lookup(zmr)).to eql(zmr) end it "should raise an exception if you send something strange to lookup" do expect { rc.lookup(:symbol) }.to raise_error(ArgumentError) end it "should raise an exception if it cannot find a resource with lookup" do expect { rc.lookup("zen_master[dog]") }.to raise_error(Chef::Exceptions::ResourceNotFound) end end describe "delete" do it "should allow you to delete resources by name via delete" do zmr = Chef::Resource::ZenMaster.new("dog") rc << zmr expect(rc).not_to be_empty expect(rc.delete(zmr.to_s)).to eql(zmr) expect(rc).to be_empty zmr = Chef::Resource::ZenMaster.new("cat") rc[0] = zmr expect(rc).not_to be_empty expect(rc.delete(zmr)).to eql(zmr) expect(rc).to be_empty zmr = Chef::Resource::ZenMaster.new("monkey") rc.push(zmr) expect(rc).not_to be_empty expect(rc.delete(zmr)).to eql(zmr) expect(rc).to be_empty end it "should raise an exception if you send something strange to delete" do expect { rc.delete(:symbol) }.to raise_error(ArgumentError) end it "should raise an exception if it cannot find a resource with delete" do expect { rc.delete("zen_master[dog]") }.to raise_error(Chef::Exceptions::ResourceNotFound) end end describe "resources" do it "should find a resource by symbol and name (:zen_master => monkey)" do load_up_resources expect(rc.resources(:zen_master => "monkey").name).to eql("monkey") end it "should find a resource by symbol and array of names (:zen_master => [a,b])" do load_up_resources results = rc.resources(:zen_master => %w{monkey dog}) expect(results.length).to eql(2) check_by_names(results, "monkey", "dog") end it "should find resources of multiple kinds (:zen_master => a, :file => b)" do load_up_resources results = rc.resources(:zen_master => "monkey", :file => "something") expect(results.length).to eql(2) check_by_names(results, "monkey", "something") end it "should find a resource by string zen_master[a]" do load_up_resources expect(rc.resources("zen_master[monkey]").name).to eql("monkey") end it "should find resources by strings of zen_master[a,b]" do load_up_resources results = rc.resources("zen_master[monkey,dog]") expect(results.length).to eql(2) check_by_names(results, "monkey", "dog") end it "should find resources of multiple types by strings of zen_master[a]" do load_up_resources results = rc.resources("zen_master[monkey]", "file[something]") expect(results.length).to eql(2) check_by_names(results, "monkey", "something") end it "should raise an exception if you pass a bad name to resources" do expect { rc.resources("michael jackson") }.to raise_error(ArgumentError) end it "should raise an exception if you pass something other than a string or hash to resource" do expect { rc.resources([Array.new]) }.to raise_error(ArgumentError) end it "raises an error when attempting to find a resource that does not exist" do expect { rc.find("script[nonesuch]") }.to raise_error(Chef::Exceptions::ResourceNotFound) end end describe "when validating a resource query object" do it "accepts a string of the form 'resource_type[resource_name]'" do expect(rc.validate_lookup_spec!("resource_type[resource_name]")).to be_truthy end it "accepts a single-element :resource_type => 'resource_name' Hash" do expect(rc.validate_lookup_spec!(:service => "apache2")).to be_truthy end it "accepts a chef resource object" do res = Chef::Resource.new("foo", nil) expect(rc.validate_lookup_spec!(res)).to be_truthy end it "rejects a malformed query string" do expect do rc.validate_lookup_spec!("resource_type[missing-end-bracket") end.to raise_error(Chef::Exceptions::InvalidResourceSpecification) end it "rejects an argument that is not a String, Hash, or Chef::Resource" do expect do rc.validate_lookup_spec!(Object.new) end.to raise_error(Chef::Exceptions::InvalidResourceSpecification) end end describe "to_json" do it "should serialize to json" do json = rc.to_json expect(json).to match(/json_class/) expect(json).to match(/instance_vars/) end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { rc } end end describe "self.from_json" do it "should not respond to this method" do expect(rc.respond_to?(:from_json)).to eq(false) end it "should convert from json using the CHEF::JSONCompat library" do rc << resource json = Chef::JSONCompat.to_json(rc) s_rc = Chef::JSONCompat.from_json(json) expect(s_rc).to be_a_kind_of(Chef::ResourceCollection) expect(s_rc[0].name).to eql(resource.name) end end describe "provides access to the raw resources array" do it "returns the resources via the all_resources method" do expect(rc.all_resources).to equal(rc.instance_variable_get(:@resource_list).instance_variable_get(:@resources)) end end describe "provides access to stepable iterator" do it "returns the iterator object" do rc.instance_variable_get(:@resource_list).instance_variable_set(:@iterator, :fooboar) expect(rc.iterator).to eq(:fooboar) end end describe "multiple run_contexts" do let(:node) { Chef::Node.new } let(:parent_run_context) { Chef::RunContext.new(node, {}, nil) } let(:parent_resource_collection) { parent_run_context.resource_collection } let(:child_run_context) { parent_run_context.create_child } let(:child_resource_collection) { child_run_context.resource_collection } it "should find resources in the parent run_context with lookup" do zmr = Chef::Resource::ZenMaster.new("dog") parent_resource_collection << zmr expect(child_resource_collection.lookup(zmr.to_s)).to eql(zmr) end it "should not find resources in the parent run_context with lookup_local" do zmr = Chef::Resource::ZenMaster.new("dog") parent_resource_collection << zmr expect { child_resource_collection.lookup_local(zmr.to_s) }.to raise_error(Chef::Exceptions::ResourceNotFound) end it "should find resources in the child run_context with lookup_local" do zmr = Chef::Resource::ZenMaster.new("dog") child_resource_collection << zmr expect(child_resource_collection.lookup_local(zmr.to_s)).to eql(zmr) end it "should find resources in the parent run_context with find" do zmr = Chef::Resource::ZenMaster.new("dog") parent_resource_collection << zmr expect(child_resource_collection.find(zmr.to_s)).to eql(zmr) end it "should not find resources in the parent run_context with find_local" do zmr = Chef::Resource::ZenMaster.new("dog") parent_resource_collection << zmr expect { child_resource_collection.find_local(zmr.to_s) }.to raise_error(Chef::Exceptions::ResourceNotFound) end it "should find resources in the child run_context with find_local" do zmr = Chef::Resource::ZenMaster.new("dog") child_resource_collection << zmr expect(child_resource_collection.find_local(zmr.to_s)).to eql(zmr) end it "should not find resources in the child run_context in any way from the parent" do zmr = Chef::Resource::ZenMaster.new("dog") child_resource_collection << zmr expect { parent_resource_collection.find_local(zmr.to_s) }.to raise_error(Chef::Exceptions::ResourceNotFound) expect { parent_resource_collection.find(zmr.to_s) }.to raise_error(Chef::Exceptions::ResourceNotFound) expect { parent_resource_collection.lookup_local(zmr.to_s) }.to raise_error(Chef::Exceptions::ResourceNotFound) expect { parent_resource_collection.lookup(zmr.to_s) }.to raise_error(Chef::Exceptions::ResourceNotFound) end it "should behave correctly when there is an identically named resource in the child and parent" do a = Chef::Resource::File.new("something") a.content("foo") parent_resource_collection << a b = Chef::Resource::File.new("something") b.content("bar") child_resource_collection << b expect(child_resource_collection.find_local("file[something]").content).to eql("bar") expect(child_resource_collection.find("file[something]").content).to eql("bar") expect(child_resource_collection.lookup_local("file[something]").content).to eql("bar") expect(child_resource_collection.lookup("file[something]").content).to eql("bar") expect(parent_resource_collection.find_local("file[something]").content).to eql("foo") expect(parent_resource_collection.find("file[something]").content).to eql("foo") expect(parent_resource_collection.lookup_local("file[something]").content).to eql("foo") expect(parent_resource_collection.lookup("file[something]").content).to eql("foo") end end def check_by_names(results, *names) names.each do |res_name| expect(results.detect { |res| res.name == res_name }).not_to eql(nil) end end def load_up_resources %w{dog cat monkey}.each do |n| rc << Chef::Resource::ZenMaster.new(n) end rc << Chef::Resource::File.new("something") end end chef-12.14.60/spec/unit/resource_definition_spec.rb000066400000000000000000000064601276456504500222050ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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::ResourceDefinition do let(:defn) { Chef::ResourceDefinition.new() } describe "initialize" do it "should be a Chef::ResourceDefinition" do expect(defn).to be_a_kind_of(Chef::ResourceDefinition) end it "should not initialize a new node if one is not provided" do expect(defn.node).to eql(nil) end it "should accept a node as an argument" do node = Chef::Node.new node.name("bobo") defn = Chef::ResourceDefinition.new(node) expect(defn.node.name).to eq("bobo") end end describe "node" do it "should set the node with node=" do node = Chef::Node.new node.name("bobo") defn.node = node expect(defn.node.name).to eq("bobo") end it "should return the node" do defn.node = Chef::Node.new expect(defn.node).to be_a_kind_of(Chef::Node) end end it "should accept a new definition with a symbol for a name" do expect do defn.define :smoke do end end.not_to raise_error expect do defn.define "george washington" do end end.to raise_error(ArgumentError) expect(defn.name).to eql(:smoke) end it "should accept a new definition with a hash" do expect do defn.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do end end.not_to raise_error end it "should expose the prototype hash params in the params hash" do defn.define(:smoke, :cigar => "cuban", :cigarette => "marlboro") {} expect(defn.params[:cigar]).to eql("cuban") expect(defn.params[:cigarette]).to eql("marlboro") end it "should store the block passed to define as a proc under recipe" do defn.define :smoke do "I am what I am" end expect(defn.recipe).to be_a_kind_of(Proc) expect(defn.recipe.call).to eql("I am what I am") end it "should set parameters based on method_missing" do defn.mind "to fly" expect(defn.params[:mind]).to eql("to fly") end it "should raise an exception if prototype_params is not a hash" do expect do defn.define :monkey, Array.new do end end.to raise_error(ArgumentError) end it "should raise an exception if define is called without a block" do expect do defn.define :monkey end.to raise_error(ArgumentError) end it "should load a description from a file" do defn.from_file(File.join(CHEF_SPEC_DATA, "definitions", "test.rb")) expect(defn.name).to eql(:rico_suave) expect(defn.params[:rich]).to eql("smooth") end it "should turn itself into a string based on the name with to_s" do defn.name = :woot expect(defn.to_s).to eql("woot") end end chef-12.14.60/spec/unit/resource_reporter_spec.rb000066400000000000000000000725461276456504500217270ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Author:: Prajakta Purohit () # Author:: Tyler Cloke () # # 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 File.expand_path("../../spec_helper", __FILE__) require "chef/resource_reporter" require "socket" describe Chef::ResourceReporter do before(:all) do @reporting_toggle_default = Chef::Config[:enable_reporting] Chef::Config[:enable_reporting] = true end after(:all) do Chef::Config[:enable_reporting] = @reporting_toggle_default end before do @node = Chef::Node.new @node.name("spitfire") @rest_client = double("Chef::ServerAPI (mock)") allow(@rest_client).to receive(:post).and_return(true) @resource_reporter = Chef::ResourceReporter.new(@rest_client) @new_resource = Chef::Resource::File.new("/tmp/a-file.txt") @cookbook_name = "monkey" @new_resource.cookbook_name = @cookbook_name @cookbook_version = double("Cookbook::Version", :version => "1.2.3") allow(@new_resource).to receive(:cookbook_version).and_return(@cookbook_version) @current_resource = Chef::Resource::File.new("/tmp/a-file.txt") @start_time = Time.new @end_time = Time.new + 20 @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @run_status = Chef::RunStatus.new(@node, @events) @run_list = Chef::RunList.new @run_list << "recipe[lobster]" << "role[rage]" << "recipe[fist]" @expansion = Chef::RunList::RunListExpansion.new("_default", @run_list.run_list_items) @run_id = @run_status.run_id allow(Time).to receive(:now).and_return(@start_time, @end_time) end context "when first created" do it "has no updated resources" do expect(@resource_reporter.updated_resources.size).to eq(0) end it "reports a successful run" do expect(@resource_reporter.status).to eq("success") end it "assumes the resource history feature is supported" do expect(@resource_reporter.reporting_enabled?).to be_truthy end it "should have no error_descriptions" do expect(@resource_reporter.error_descriptions).to eq({}) # @resource_reporter.error_descriptions.should be_empty # @resource_reporter.should have(0).error_descriptions end end context "after the chef run completes" do before do end it "reports a successful run" do skip "refactor how node gets set." expect(@resource_reporter.status).to eq("success") end end context "when chef fails" do before do allow(@rest_client).to receive(:raw_request).and_return({ "result" => "ok" }); allow(@rest_client).to receive(:post).and_return({ "uri" => "https://example.com/reports/nodes/spitfire/runs/#{@run_id}" }); end context "before converging any resources" do before do @resource_reporter.run_started(@run_status) @exception = Exception.new @resource_reporter.run_failed(@exception) end it "sets the run status to 'failure'" do expect(@resource_reporter.status).to eq("failure") end it "keeps the exception data" do expect(@resource_reporter.exception).to eq(@exception) end end context "when a resource fails before loading current state" do before do @exception = Exception.new @exception.set_backtrace(caller) @resource_reporter.resource_action_start(@new_resource, :create) @resource_reporter.resource_failed(@new_resource, :create, @exception) @resource_reporter.resource_completed(@new_resource) end it "collects the resource as an updated resource" do expect(@resource_reporter.updated_resources.size).to eq(1) end it "collects the desired state of the resource" do update_record = @resource_reporter.updated_resources.first expect(update_record.new_resource).to eq(@new_resource) end end # TODO: make sure a resource that is skipped because of `not_if` doesn't # leave us in a bad state. context "once the a resource's current state is loaded" do before do @resource_reporter.resource_action_start(@new_resource, :create) @resource_reporter.resource_current_state_loaded(@new_resource, :create, @current_resource) end context "and the resource was not updated" do before do @resource_reporter.resource_up_to_date(@new_resource, :create) end it "has no updated resources" do expect(@resource_reporter.updated_resources.size).to eq(0) end end context "and the resource was updated" do before do @new_resource.content("this is the old content") @current_resource.content("this is the new hotness") @resource_reporter.resource_updated(@new_resource, :create) @resource_reporter.resource_completed(@new_resource) end it "collects the updated resource" do expect(@resource_reporter.updated_resources.size).to eq(1) end it "collects the old state of the resource" do update_record = @resource_reporter.updated_resources.first expect(update_record.current_resource).to eq(@current_resource) end it "collects the new state of the resource" do update_record = @resource_reporter.updated_resources.first expect(update_record.new_resource).to eq(@new_resource) end context "and a subsequent resource fails before loading current resource" do before do @next_new_resource = Chef::Resource::Service.new("apache2") @exception = Exception.new @exception.set_backtrace(caller) @resource_reporter.resource_failed(@next_new_resource, :create, @exception) @resource_reporter.resource_completed(@next_new_resource) end it "collects the desired state of the failed resource" do failed_resource_update = @resource_reporter.updated_resources.last expect(failed_resource_update.new_resource).to eq(@next_new_resource) end it "does not have the current state of the failed resource" do failed_resource_update = @resource_reporter.updated_resources.last expect(failed_resource_update.current_resource).to be_nil end end end # Some providers, such as RemoteDirectory and some LWRPs use other # resources for their implementation. These should be hidden from reporting # since we only care about the top-level resource and not the sub-resources # used for implementation. context "and a nested resource is updated" do before do @implementation_resource = Chef::Resource::CookbookFile.new("/preseed-file.txt") @resource_reporter.resource_action_start(@implementation_resource , :create) @resource_reporter.resource_current_state_loaded(@implementation_resource, :create, @implementation_resource) @resource_reporter.resource_updated(@implementation_resource, :create) @resource_reporter.resource_completed(@implementation_resource) @resource_reporter.resource_updated(@new_resource, :create) @resource_reporter.resource_completed(@new_resource) end it "does not collect data about the nested resource" do expect(@resource_reporter.updated_resources.size).to eq(1) end end context "and a nested resource runs but is not updated" do before do @implementation_resource = Chef::Resource::CookbookFile.new("/preseed-file.txt") @resource_reporter.resource_action_start(@implementation_resource , :create) @resource_reporter.resource_current_state_loaded(@implementation_resource, :create, @implementation_resource) @resource_reporter.resource_up_to_date(@implementation_resource, :create) @resource_reporter.resource_completed(@implementation_resource) @resource_reporter.resource_updated(@new_resource, :create) @resource_reporter.resource_completed(@new_resource) end it "does not collect data about the nested resource" do expect(@resource_reporter.updated_resources.size).to eq(1) end end context "and the resource failed to converge" do before do @exception = Exception.new @exception.set_backtrace(caller) @resource_reporter.resource_failed(@new_resource, :create, @exception) @resource_reporter.resource_completed(@new_resource) end it "collects the resource as an updated resource" do expect(@resource_reporter.updated_resources.size).to eq(1) end it "collects the desired state of the resource" do update_record = @resource_reporter.updated_resources.first expect(update_record.new_resource).to eq(@new_resource) end it "collects the current state of the resource" do update_record = @resource_reporter.updated_resources.first expect(update_record.current_resource).to eq(@current_resource) end end end end describe "when generating a report for the server" do before do allow(@rest_client).to receive(:raw_request).and_return({ "result" => "ok" }); allow(@rest_client).to receive(:post).and_return({ "uri" => "https://example.com/reports/nodes/spitfire/runs/#{@run_id}" }); @resource_reporter.run_started(@run_status) end context "when the new_resource does not have a string for name and identity" do context "the new_resource name and id are nil" do before do @bad_resource = Chef::Resource::File.new("/tmp/nameless_file.txt") allow(@bad_resource).to receive(:name).and_return(nil) allow(@bad_resource).to receive(:identity).and_return(nil) allow(@bad_resource).to receive(:path).and_return(nil) @resource_reporter.resource_action_start(@bad_resource, :create) @resource_reporter.resource_current_state_loaded(@bad_resource, :create, @current_resource) @resource_reporter.resource_updated(@bad_resource, :create) @resource_reporter.resource_completed(@bad_resource) @run_status.stop_clock @report = @resource_reporter.prepare_run_data @first_update_report = @report["resources"].first end it "resource_name in prepared_run_data is a string" do expect(@first_update_report["name"].class).to eq(String) end it "resource_id in prepared_run_data is a string" do expect(@first_update_report["id"].class).to eq(String) end end context "the new_resource name and id are hashes" do before do @bad_resource = Chef::Resource::File.new("/tmp/filename_as_hash.txt") allow(@bad_resource).to receive(:name).and_return({ :foo => :bar }) allow(@bad_resource).to receive(:identity).and_return({ :foo => :bar }) allow(@bad_resource).to receive(:path).and_return({ :foo => :bar }) @resource_reporter.resource_action_start(@bad_resource, :create) @resource_reporter.resource_current_state_loaded(@bad_resource, :create, @current_resource) @resource_reporter.resource_updated(@bad_resource, :create) @resource_reporter.resource_completed(@bad_resource) @run_status.stop_clock @report = @resource_reporter.prepare_run_data @first_update_report = @report["resources"].first end # Ruby 1.8.7 flattens out hash to string using join instead of inspect, resulting in # irb(main):001:0> {:foo => :bar}.to_s # => "foobar" # instead of the expected # irb(main):001:0> {:foo => :bar}.to_s # => "{:foo=>:bar}" # Hence checking for the class instead of the actual value. it "resource_name in prepared_run_data is a string" do expect(@first_update_report["name"].class).to eq(String) end it "resource_id in prepared_run_data is a string" do expect(@first_update_report["id"].class).to eq(String) end end end shared_examples_for "a successful client run" do before do # TODO: add inputs to generate expected output. # expected_data = { # "action" : "end", # "resources" : [ # { # "type" : "file", # "id" : "/etc/passwd", # "name" : "User Defined Resource Block Name", # "duration" : "1200", # "result" : "modified", # "before" : { # "state" : "exists", # "group" : "root", # "owner" : "root", # "checksum" : "xyz" # }, # "after" : { # "state" : "modified", # "group" : "root", # "owner" : "root", # "checksum" : "abc" # }, # "delta" : "" # }, # {...} # ], # "status" : "success" # "data" : "" # } @resource_reporter.resource_action_start(new_resource, :create) @resource_reporter.resource_current_state_loaded(new_resource, :create, current_resource) @resource_reporter.resource_updated(new_resource, :create) @resource_reporter.resource_completed(new_resource) @run_status.stop_clock @report = @resource_reporter.prepare_run_data @first_update_report = @report["resources"].first end it "includes the run's status" do expect(@report).to have_key("status") end it "includes a list of updated resources" do expect(@report).to have_key("resources") end it "includes an updated resource's type" do expect(@first_update_report).to have_key("type") end it "includes an updated resource's initial state" do expect(@first_update_report["before"]).to eq(current_resource.state) end it "includes an updated resource's final state" do expect(@first_update_report["after"]).to eq(new_resource.state) end it "includes the resource's name" do expect(@first_update_report["name"]).to eq(new_resource.name) end it "includes the resource's id attribute" do expect(@first_update_report["id"]).to eq(new_resource.identity) end it "includes the elapsed time for the resource to converge" do # TODO: API takes integer number of milliseconds as a string. This # should be an int. expect(@first_update_report).to have_key("duration") expect(@first_update_report["duration"].to_i).to be_within(100).of(0) end it "includes the action executed by the resource" do # TODO: rename as "action" expect(@first_update_report["result"]).to eq("create") end it "includes the cookbook name of the resource" do expect(@first_update_report).to have_key("cookbook_name") expect(@first_update_report["cookbook_name"]).to eq(@cookbook_name) end it "includes the cookbook version of the resource" do expect(@first_update_report).to have_key("cookbook_version") expect(@first_update_report["cookbook_version"]).to eq("1.2.3") end it "includes the total resource count" do expect(@report).to have_key("total_res_count") expect(@report["total_res_count"]).to eq("1") end it "includes the data hash" do expect(@report).to have_key("data") expect(@report["data"]).to eq({}) end it "includes the run_list" do expect(@report).to have_key("run_list") expect(@report["run_list"]).to eq(Chef::JSONCompat.to_json(@run_status.node.run_list)) end it "includes the expanded_run_list" do expect(@report).to have_key("expanded_run_list") end it "includes the end_time" do expect(@report).to have_key("end_time") expect(@report["end_time"]).to eq(@run_status.end_time.to_s) end end context "when the resource is a File" do let(:new_resource) { @new_resource } let(:current_resource) { @current_resource } it_should_behave_like "a successful client run" end context "when the resource is a RegistryKey with binary data" do let(:new_resource) do resource = Chef::Resource::RegistryKey.new('Wubba\Lubba\Dub\Dubs') resource.values([ { :name => "rick", :type => :binary, :data => 255.chr * 1 } ]) allow(resource).to receive(:cookbook_name).and_return(@cookbook_name) allow(resource).to receive(:cookbook_version).and_return(@cookbook_version) resource end let(:current_resource) do resource = Chef::Resource::RegistryKey.new('Wubba\Lubba\Dub\Dubs') resource.values([ { :name => "rick", :type => :binary, :data => 255.chr * 1 } ]) resource end it_should_behave_like "a successful client run" end context "for an unsuccessful run" do before do @backtrace = ["foo.rb:1 in `foo!'", "bar.rb:2 in `bar!", "'baz.rb:3 in `baz!'"] @node = Chef::Node.new @node.name("spitfire") @exception = ArgumentError.new allow(@exception).to receive(:inspect).and_return("Net::HTTPServerException") allow(@exception).to receive(:message).and_return("Object not found") allow(@exception).to receive(:backtrace).and_return(@backtrace) @resource_reporter.run_list_expand_failed(@node, @exception) @resource_reporter.run_failed(@exception) @report = @resource_reporter.prepare_run_data end it "includes the exception type in the event data" do expect(@report).to have_key("data") expect(@report["data"]["exception"]).to have_key("class") expect(@report["data"]["exception"]["class"]).to eq("Net::HTTPServerException") end it "includes the exception message in the event data" do expect(@report["data"]["exception"]).to have_key("message") expect(@report["data"]["exception"]["message"]).to eq("Object not found") end it "includes the exception trace in the event data" do expect(@report["data"]["exception"]).to have_key("backtrace") expect(@report["data"]["exception"]["backtrace"]).to eq(Chef::JSONCompat.to_json(@backtrace)) end it "includes the error inspector output in the event data" do expect(@report["data"]["exception"]).to have_key("description") expect(@report["data"]["exception"]["description"]).to include({ "title" => "Error expanding the run_list:", "sections" => [{ "Unexpected Error:" => "ArgumentError: Object not found" }] }) end end context "when new_resource does not have a cookbook_name" do before do @bad_resource = Chef::Resource::File.new("/tmp/a-file.txt") @bad_resource.cookbook_name = nil @resource_reporter.resource_action_start(@bad_resource, :create) @resource_reporter.resource_current_state_loaded(@bad_resource, :create, @current_resource) @resource_reporter.resource_updated(@bad_resource, :create) @resource_reporter.resource_completed(@bad_resource) @run_status.stop_clock @report = @resource_reporter.prepare_run_data @first_update_report = @report["resources"].first end it "includes an updated resource's initial state" do expect(@first_update_report["before"]).to eq(@current_resource.state) end it "includes an updated resource's final state" do expect(@first_update_report["after"]).to eq(@new_resource.state) end it "includes the resource's name" do expect(@first_update_report["name"]).to eq(@new_resource.name) end it "includes the resource's id attribute" do expect(@first_update_report["id"]).to eq(@new_resource.identity) end it "includes the elapsed time for the resource to converge" do # TODO: API takes integer number of milliseconds as a string. This # should be an int. expect(@first_update_report).to have_key("duration") expect(@first_update_report["duration"].to_i).to be_within(100).of(0) end it "includes the action executed by the resource" do # TODO: rename as "action" expect(@first_update_report["result"]).to eq("create") end it "does not include a cookbook name for the resource" do expect(@first_update_report).not_to have_key("cookbook_name") end it "does not include a cookbook version for the resource" do expect(@first_update_report).not_to have_key("cookbook_version") end end context "when including a resource that overrides Resource#state" do before do @current_state_resource = Chef::Resource::WithState.new("Stateful", @run_context) @current_state_resource.state = nil @new_state_resource = Chef::Resource::WithState.new("Stateful", @run_context) @new_state_resource.state = "Running" @resource_reporter.resource_action_start(@new_state_resource, :create) @resource_reporter.resource_current_state_loaded(@new_state_resource, :create, @current_state_resource) @resource_reporter.resource_updated(@new_state_resource, :create) @resource_reporter.resource_completed(@new_state_resource) @run_status.stop_clock @report = @resource_reporter.prepare_run_data @first_update_report = @report["resources"].first end it "sets before to {} instead of nil" do expect(@first_update_report).to have_key("before") expect(@first_update_report["before"]).to eq({}) end it "sets after to {} instead of 'Running'" do expect(@first_update_report).to have_key("after") expect(@first_update_report["after"]).to eq({}) end end end describe "when updating resource history on the server" do before do @resource_reporter.run_started(@run_status) @run_status.start_clock end context "when the server does not support storing resource history" do before do # 404 getting the run_id @response = Net::HTTPNotFound.new("a response body", "404", "Not Found") @error = Net::HTTPServerException.new("404 message", @response) expect(@rest_client).to receive(:post). with("reports/nodes/spitfire/runs", { :action => :start, :run_id => @run_id, :start_time => @start_time.to_s }, { "X-Ops-Reporting-Protocol-Version" => Chef::ResourceReporter::PROTOCOL_VERSION }). and_raise(@error) end it "assumes the feature is not enabled" do @resource_reporter.run_started(@run_status) expect(@resource_reporter.reporting_enabled?).to be_falsey end it "does not send a resource report to the server" do @resource_reporter.run_started(@run_status) expect(@rest_client).not_to receive(:post) @resource_reporter.run_completed(@node) end it "prints an error about the 404" do expect(Chef::Log).to receive(:debug).with(/404/) @resource_reporter.run_started(@run_status) end end context "when the server returns a 500 to the client" do before do # 500 getting the run_id @response = Net::HTTPInternalServerError.new("a response body", "500", "Internal Server Error") @error = Net::HTTPServerException.new("500 message", @response) expect(@rest_client).to receive(:post). with("reports/nodes/spitfire/runs", { :action => :start, :run_id => @run_id, :start_time => @start_time.to_s }, { "X-Ops-Reporting-Protocol-Version" => Chef::ResourceReporter::PROTOCOL_VERSION }). and_raise(@error) end it "assumes the feature is not enabled" do @resource_reporter.run_started(@run_status) expect(@resource_reporter.reporting_enabled?).to be_falsey end it "does not send a resource report to the server" do @resource_reporter.run_started(@run_status) expect(@rest_client).not_to receive(:post) @resource_reporter.run_completed(@node) end it "prints an error about the error" do expect(Chef::Log).to receive(:info).with(/500/) @resource_reporter.run_started(@run_status) end end context "when the server returns a 500 to the client and enable_reporting_url_fatals is true" do before do @enable_reporting_url_fatals = Chef::Config[:enable_reporting_url_fatals] Chef::Config[:enable_reporting_url_fatals] = true # 500 getting the run_id @response = Net::HTTPInternalServerError.new("a response body", "500", "Internal Server Error") @error = Net::HTTPServerException.new("500 message", @response) expect(@rest_client).to receive(:post). with("reports/nodes/spitfire/runs", { :action => :start, :run_id => @run_id, :start_time => @start_time.to_s }, { "X-Ops-Reporting-Protocol-Version" => Chef::ResourceReporter::PROTOCOL_VERSION }). and_raise(@error) end after do Chef::Config[:enable_reporting_url_fatals] = @enable_reporting_url_fatals end it "fails the run and prints an message about the error" do expect(Chef::Log).to receive(:error).with(/500/) expect do @resource_reporter.run_started(@run_status) end.to raise_error(Net::HTTPServerException) end end context "after creating the run history document" do before do response = { "uri" => "https://example.com/reports/nodes/spitfire/runs/@run_id" } expect(@rest_client).to receive(:post). with("reports/nodes/spitfire/runs", { :action => :start, :run_id => @run_id, :start_time => @start_time.to_s }, { "X-Ops-Reporting-Protocol-Version" => Chef::ResourceReporter::PROTOCOL_VERSION }). and_return(response) @resource_reporter.run_started(@run_status) end it "creates a run document on the server at the start of the run" do expect(@resource_reporter.run_id).to eq(@run_id) end it "updates the run document with resource updates at the end of the run" do # update some resources... @resource_reporter.resource_action_start(@new_resource, :create) @resource_reporter.resource_current_state_loaded(@new_resource, :create, @current_resource) @resource_reporter.resource_updated(@new_resource, :create) allow(@resource_reporter).to receive(:end_time).and_return(@end_time) @expected_data = @resource_reporter.prepare_run_data response = { "result" => "ok" } expect(@rest_client).to receive(:raw_request).ordered do |method, url, headers, data| expect(method).to eq(:POST) expect(headers).to eq({ "Content-Encoding" => "gzip", "X-Ops-Reporting-Protocol-Version" => Chef::ResourceReporter::PROTOCOL_VERSION, }) data_stream = Zlib::GzipReader.new(StringIO.new(data)) data = data_stream.read expect(data).to eq(Chef::JSONCompat.to_json(@expected_data)) response end @resource_reporter.run_completed(@node) end end context "when data report post is enabled and the server response fails" do before do @enable_reporting_url_fatals = Chef::Config[:enable_reporting_url_fatals] Chef::Config[:enable_reporting_url_fatals] = true end after do Chef::Config[:enable_reporting_url_fatals] = @enable_reporting_url_fatals end it "should log 4xx errors" do response = Net::HTTPClientError.new("forbidden", "403", "Forbidden") error = Net::HTTPServerException.new("403 message", response) allow(@rest_client).to receive(:raw_request).and_raise(error) expect(Chef::Log).to receive(:error).with(/403/) @resource_reporter.post_reporting_data end it "should log error 5xx errors" do response = Net::HTTPServerError.new("internal error", "500", "Internal Server Error") error = Net::HTTPFatalError.new("500 message", response) allow(@rest_client).to receive(:raw_request).and_raise(error) expect(Chef::Log).to receive(:error).with(/500/) @resource_reporter.post_reporting_data end it "should log if a socket error happens" do allow(@rest_client).to receive(:raw_request).and_raise(SocketError.new("test socket error")) expect(Chef::Log).to receive(:error).with(/test socket error/) @resource_reporter.post_reporting_data end it "should raise if an unkwown error happens" do allow(@rest_client).to receive(:raw_request).and_raise(Exception.new) expect do @resource_reporter.post_reporting_data end.to raise_error(Exception) end end end end chef-12.14.60/spec/unit/resource_resolver_spec.rb000066400000000000000000000030501276456504500217060ustar00rootroot00000000000000# # Author:: Ranjib Dey # Copyright:: Copyright 2015-2016, Ranjib Dey . # 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/resource_resolver" describe Chef::ResourceResolver do it "#resolve" do expect(described_class.resolve(:execute)).to eq(Chef::Resource::Execute) end it "#list" do expect(described_class.list(:package)).to_not be_empty end context "instance methods" do let(:resolver) do described_class.new(Chef::Node.new, "execute") end it "#resolve" do expect(resolver.resolve).to eq Chef::Resource::Execute end it "#list" do expect(resolver.list).to eq [ Chef::Resource::Execute ] end it "#provided_by? returns true when resource class is in the list" do expect(resolver.provided_by?(Chef::Resource::Execute)).to be_truthy end it "#provided_by? returns false when resource class is not in the list" do expect(resolver.provided_by?(Chef::Resource::File)).to be_falsey end end end chef-12.14.60/spec/unit/resource_spec.rb000066400000000000000000001145731276456504500200020ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Walters () # Author:: Tim Hinderliter () # Author:: Seth Chisamore () # 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::Resource do let(:cookbook_repo_path) { File.join(CHEF_SPEC_DATA, "cookbooks") } let(:cookbook_collection) { Chef::CookbookCollection.new(Chef::CookbookLoader.new(cookbook_repo_path)) } let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) } let(:resource) { resource_class.new("funk", run_context) } let(:resource_class) { Chef::Resource } it "should mixin shell_out" do expect(resource.respond_to?(:shell_out)).to be true end it "should mixin shell_out!" do expect(resource.respond_to?(:shell_out!)).to be true end it "should mixin shell_out_with_systems_locale" do expect(resource.respond_to?(:shell_out_with_systems_locale)).to be true end describe "when inherited" do it "adds an entry to a list of subclasses" do subclass = Class.new(Chef::Resource) expect(Chef::Resource.resource_classes).to include(subclass) end it "keeps track of subclasses of subclasses" do subclass = Class.new(Chef::Resource) subclass_of_subclass = Class.new(subclass) expect(Chef::Resource.resource_classes).to include(subclass_of_subclass) end end describe "when declaring the identity attribute" do it "has :name as identity attribute by default" do expect(Chef::Resource.identity_attr).to eq(:name) end it "sets an identity attribute" do resource_class = Class.new(Chef::Resource) resource_class.identity_attr(:path) expect(resource_class.identity_attr).to eq(:path) end it "inherits an identity attribute from a superclass" do resource_class = Class.new(Chef::Resource) resource_subclass = Class.new(resource_class) resource_class.identity_attr(:package_name) expect(resource_subclass.identity_attr).to eq(:package_name) end it "overrides the identity attribute from a superclass when the identity attr is set" do resource_class = Class.new(Chef::Resource) resource_subclass = Class.new(resource_class) resource_class.identity_attr(:package_name) resource_subclass.identity_attr(:something_else) expect(resource_subclass.identity_attr).to eq(:something_else) end end describe "when no identity attribute has been declared" do let(:resource_sans_id) { Chef::Resource.new("my-name") } # Would rather force identity attributes to be set for everything, # but that's not plausible for back compat reasons. it "uses the name as the identity" do expect(resource_sans_id.identity).to eq("my-name") end end describe "when an identity attribute has been declared" do let(:file_resource) do file_resource_class = Class.new(Chef::Resource) do identity_attr :path attr_accessor :path end file_resource = file_resource_class.new("identity-attr-test") file_resource.path = "/tmp/foo.txt" file_resource end it "gives the value of its identity attribute" do expect(file_resource.identity).to eq("/tmp/foo.txt") end end describe "when declaring state attributes" do it "has no state_attrs by default" do expect(Chef::Resource.state_attrs).to be_empty end it "sets a list of state attributes" do resource_class = Class.new(Chef::Resource) resource_class.state_attrs(:checksum, :owner, :group, :mode) expect(resource_class.state_attrs).to match_array([:checksum, :owner, :group, :mode]) end it "inherits state attributes from the superclass" do resource_class = Class.new(Chef::Resource) resource_subclass = Class.new(resource_class) resource_class.state_attrs(:checksum, :owner, :group, :mode) expect(resource_subclass.state_attrs).to match_array([:checksum, :owner, :group, :mode]) end it "combines inherited state attributes with non-inherited state attributes" do resource_class = Class.new(Chef::Resource) resource_subclass = Class.new(resource_class) resource_class.state_attrs(:checksum, :owner) resource_subclass.state_attrs(:group, :mode) expect(resource_subclass.state_attrs).to match_array([:checksum, :owner, :group, :mode]) end end describe "when a set of state attributes has been declared" do let(:file_resource) do file_resource_class = Class.new(Chef::Resource) do state_attrs :checksum, :owner, :group, :mode attr_accessor :checksum attr_accessor :owner attr_accessor :group attr_accessor :mode end file_resource = file_resource_class.new("describe-state-test") file_resource.checksum = "abc123" file_resource.owner = "root" file_resource.group = "wheel" file_resource.mode = "0644" file_resource end it "describes its state" do resource_state = file_resource.state expect(resource_state.keys).to match_array([:checksum, :owner, :group, :mode]) expect(resource_state[:checksum]).to eq("abc123") expect(resource_state[:owner]).to eq("root") expect(resource_state[:group]).to eq("wheel") expect(resource_state[:mode]).to eq("0644") end end describe "#state_for_resource_reporter" do context "when a property is marked as sensitive" do it "suppresses the sensitive property's value" do resource_class = Class.new(Chef::Resource) { property :foo, String, sensitive: true } resource = resource_class.new("sensitive_property_tests") resource.foo = "some value" expect(resource.state_for_resource_reporter[:foo]).to eq("*sensitive value suppressed*") end end context "when a property is not marked as sensitive" do it "does not suppress the property's value" do resource_class = Class.new(Chef::Resource) { property :foo, String } resource = resource_class.new("sensitive_property_tests") resource.foo = "some value" expect(resource.state_for_resource_reporter[:foo]).to eq("some value") end end end describe "load_from" do let(:prior_resource) do prior_resource = Chef::Resource.new("funk") prior_resource.supports(:funky => true) prior_resource.source_line prior_resource.allowed_actions << :funkytown prior_resource.action(:funkytown) prior_resource end before(:each) do resource.allowed_actions << :funkytown run_context.resource_collection << prior_resource end it "should load the attributes of a prior resource" do resource.load_from(prior_resource) expect(resource.supports).to eq({ :funky => true }) end it "should not inherit the action from the prior resource" do resource.load_from(prior_resource) expect(resource.action).not_to eq(prior_resource.action) end end describe "name" do it "should have a name" do expect(resource.name).to eql("funk") end it "should let you set a new name" do resource.name "monkey" expect(resource.name).to eql("monkey") end it "coerces arrays to names" do expect(resource.name %w{a b}).to eql("a, b") end it "should coerce objects to a string" do expect(resource.name Object.new).to be_a(String) end end describe "noop" do it "should accept true or false for noop" do expect { resource.noop true }.not_to raise_error expect { resource.noop false }.not_to raise_error expect { resource.noop "eat it" }.to raise_error(ArgumentError) end end describe "notifies" do it "should make notified resources appear in the actions hash" do run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee") resource.notifies :reload, run_context.resource_collection.find(:zen_master => "coffee") expect(resource.delayed_notifications.detect { |e| e.resource.name == "coffee" && e.action == :reload }).not_to be_nil end it "should make notified resources be capable of acting immediately" do run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee") resource.notifies :reload, run_context.resource_collection.find(:zen_master => "coffee"), :immediate expect(resource.immediate_notifications.detect { |e| e.resource.name == "coffee" && e.action == :reload }).not_to be_nil end it "should raise an exception if told to act in other than :delay or :immediate(ly)" do run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee") expect do resource.notifies :reload, run_context.resource_collection.find(:zen_master => "coffee"), :someday end.to raise_error(ArgumentError) end it "should allow multiple notified resources appear in the actions hash" do run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee") resource.notifies :reload, run_context.resource_collection.find(:zen_master => "coffee") expect(resource.delayed_notifications.detect { |e| e.resource.name == "coffee" && e.action == :reload }).not_to be_nil run_context.resource_collection << Chef::Resource::ZenMaster.new("beans") resource.notifies :reload, run_context.resource_collection.find(:zen_master => "beans") expect(resource.delayed_notifications.detect { |e| e.resource.name == "beans" && e.action == :reload }).not_to be_nil end it "creates a notification for a resource that is not yet in the resource collection" do resource.notifies(:restart, :service => "apache") expected_notification = Chef::Resource::Notification.new({ :service => "apache" }, :restart, resource) expect(resource.delayed_notifications).to include(expected_notification) end it "notifies another resource immediately" do resource.notifies_immediately(:restart, :service => "apache") expected_notification = Chef::Resource::Notification.new({ :service => "apache" }, :restart, resource) expect(resource.immediate_notifications).to include(expected_notification) end it "notifies a resource to take action at the end of the chef run" do resource.notifies_delayed(:restart, :service => "apache") expected_notification = Chef::Resource::Notification.new({ :service => "apache" }, :restart, resource) expect(resource.delayed_notifications).to include(expected_notification) end it "notifies a resource with an array for its name via its prettified string name" do run_context.resource_collection << Chef::Resource::ZenMaster.new(%w{coffee tea}) resource.notifies :reload, run_context.resource_collection.find(:zen_master => "coffee, tea") expect(resource.delayed_notifications.detect { |e| e.resource.name == "coffee, tea" && e.action == :reload }).not_to be_nil end end describe "subscribes" do it "should make resources appear in the actions hash of subscribed nodes" do run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee") zr = run_context.resource_collection.find(:zen_master => "coffee") resource.subscribes :reload, zr expect(zr.delayed_notifications.detect { |e| e.resource.name == "funk" && e.action == :reload }).not_to be_nil end it "should make resources appear in the actions hash of subscribed nodes" do run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee") zr = run_context.resource_collection.find(:zen_master => "coffee") resource.subscribes :reload, zr expect(zr.delayed_notifications.detect { |e| e.resource.name == resource.name && e.action == :reload }).not_to be_nil run_context.resource_collection << Chef::Resource::ZenMaster.new("bean") zrb = run_context.resource_collection.find(:zen_master => "bean") zrb.subscribes :reload, zr expect(zr.delayed_notifications.detect { |e| e.resource.name == resource.name && e.action == :reload }).not_to be_nil end it "should make subscribed resources be capable of acting immediately" do run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee") zr = run_context.resource_collection.find(:zen_master => "coffee") resource.subscribes :reload, zr, :immediately expect(zr.immediate_notifications.detect { |e| e.resource.name == resource.name && e.action == :reload }).not_to be_nil end end describe "defined_at" do it "should correctly parse source_line on unix-like operating systems" do resource.source_line = "/some/path/to/file.rb:80:in `wombat_tears'" expect(resource.defined_at).to eq("/some/path/to/file.rb line 80") end it "should correctly parse source_line on Windows" do resource.source_line = "C:/some/path/to/file.rb:80 in 1`wombat_tears'" expect(resource.defined_at).to eq("C:/some/path/to/file.rb line 80") end it "should include the cookbook and recipe when it knows it" do resource.source_line = "/some/path/to/file.rb:80:in `wombat_tears'" resource.recipe_name = "wombats" resource.cookbook_name = "animals" expect(resource.defined_at).to eq("animals::wombats line 80") end it "should recognize dynamically defined resources" do expect(resource.defined_at).to eq("dynamically defined") end end describe "to_s" do it "should become a string like resource_name[name]" do zm = Chef::Resource::ZenMaster.new("coffee") expect(zm.to_s).to eql("zen_master[coffee]") end end describe "self.resource_name" do context "When resource_name is not set" do it "and there are no provides lines, resource_name is nil" do c = Class.new(Chef::Resource) do end r = c.new("hi") r.declared_type = :d expect(c.resource_name).to be_nil expect(r.resource_name).to be_nil expect(r.declared_type).to eq :d end it "and there are no provides lines, resource_name is used" do c = Class.new(Chef::Resource) do def initialize(*args, &block) @resource_name = :blah super end end r = c.new("hi") r.declared_type = :d expect(c.resource_name).to be_nil expect(r.resource_name).to eq :blah expect(r.declared_type).to eq :d end it "and the resource class gets a late-bound name, resource_name is nil" do c = Class.new(Chef::Resource) do def self.name "ResourceSpecNameTest" end end r = c.new("hi") r.declared_type = :d expect(c.resource_name).to be_nil expect(r.resource_name).to be_nil expect(r.declared_type).to eq :d end end it "resource_name without provides is honored" do c = Class.new(Chef::Resource) do resource_name "blah" end r = c.new("hi") r.declared_type = :d expect(c.resource_name).to eq :blah expect(r.resource_name).to eq :blah expect(r.declared_type).to eq :d end it "setting class.resource_name with 'resource_name = blah' overrides declared_type" do c = Class.new(Chef::Resource) do provides :self_resource_name_test_2 end c.resource_name = :blah r = c.new("hi") r.declared_type = :d expect(c.resource_name).to eq :blah expect(r.resource_name).to eq :blah expect(r.declared_type).to eq :d end it "setting class.resource_name with 'resource_name blah' overrides declared_type" do c = Class.new(Chef::Resource) do resource_name :blah provides :self_resource_name_test_3 end r = c.new("hi") r.declared_type = :d expect(c.resource_name).to eq :blah expect(r.resource_name).to eq :blah expect(r.declared_type).to eq :d end end describe "is" do it "should return the arguments passed with 'is'" do zm = Chef::Resource::ZenMaster.new("coffee") expect(zm.is("one", "two", "three")).to eq(%w{one two three}) end it "should allow arguments preceded by is to methods" do resource.noop(resource.is(true)) expect(resource.noop).to eql(true) end end describe "to_json" do it "should serialize to json" do json = resource.to_json expect(json).to match(/json_class/) expect(json).to match(/instance_vars/) end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { resource } end end describe "to_hash" do context "when the resource has a property with a default" do let(:resource_class) { Class.new(Chef::Resource) { property :a, default: 1 } } it "should include the default in the hash" do expect(resource.to_hash.keys.sort).to eq([:a, :allowed_actions, :params, :provider, :updated, :updated_by_last_action, :before, :supports, :noop, :ignore_failure, :name, :source_line, :action, :retries, :retry_delay, :elapsed_time, :default_guard_interpreter, :guard_interpreter, :sensitive].sort) expect(resource.to_hash[:name]).to eq "funk" expect(resource.to_hash[:a]).to eq 1 end end it "should convert to a hash" do hash = resource.to_hash expected_keys = [ :allowed_actions, :params, :provider, :updated, :updated_by_last_action, :before, :supports, :noop, :ignore_failure, :name, :source_line, :action, :retries, :retry_delay, :elapsed_time, :default_guard_interpreter, :guard_interpreter, :sensitive ] expect(hash.keys - expected_keys).to eq([]) expect(expected_keys - hash.keys).to eq([]) expect(hash[:name]).to eql("funk") end end describe "self.json_create" do it "should deserialize itself from json" do json = Chef::JSONCompat.to_json(resource) serialized_node = Chef::JSONCompat.from_json(json) expect(serialized_node).to be_a_kind_of(Chef::Resource) expect(serialized_node.name).to eql(resource.name) end end describe "supports" do it "should allow you to set what features this resource supports" do support_hash = { :one => :two } resource.supports(support_hash) expect(resource.supports).to eql(support_hash) end it "should return the current value of supports" do expect(resource.supports).to eq({}) end end describe "ignore_failure" do it "should default to throwing an error if a provider fails for a resource" do expect(resource.ignore_failure).to eq(false) end it "should allow you to set whether a provider should throw exceptions with ignore_failure" do resource.ignore_failure(true) expect(resource.ignore_failure).to eq(true) end it "should allow you to epic_fail" do resource.epic_fail(true) expect(resource.epic_fail).to eq(true) end end describe "retries" do let(:retriable_resource) do retriable_resource = Chef::Resource::Cat.new("precious", run_context) retriable_resource.provider = Chef::Provider::SnakeOil retriable_resource.action = :purr retriable_resource end before do node.automatic_attrs[:platform] = "fubuntu" node.automatic_attrs[:platform_version] = "10.04" end it "should default to not retrying if a provider fails for a resource" do expect(retriable_resource.retries).to eq(0) end it "should allow you to set how many retries a provider should attempt after a failure" do retriable_resource.retries(2) expect(retriable_resource.retries).to eq(2) end it "should default to a retry delay of 2 seconds" do expect(retriable_resource.retry_delay).to eq(2) end it "should allow you to set the retry delay" do retriable_resource.retry_delay(10) expect(retriable_resource.retry_delay).to eq(10) end it "should keep given value of retries intact after the provider fails for a resource" do retriable_resource.retries(3) retriable_resource.retry_delay(0) # No need to wait. provider = Chef::Provider::SnakeOil.new(retriable_resource, run_context) allow(Chef::Provider::SnakeOil).to receive(:new).and_return(provider) allow(provider).to receive(:action_purr).and_raise expect(retriable_resource).to receive(:sleep).exactly(3).times expect { retriable_resource.run_action(:purr) }.to raise_error(RuntimeError) expect(retriable_resource.retries).to eq(3) end end describe "setting the base provider class for the resource" do it "defaults to Chef::Provider for the base class" do expect(Chef::Resource.provider_base).to eq(Chef::Provider) end it "allows the base provider to be overridden" do Chef::Config.treat_deprecation_warnings_as_errors(false) class OverrideProviderBaseTest < Chef::Resource provider_base Chef::Provider::Package end expect(OverrideProviderBaseTest.provider_base).to eq(Chef::Provider::Package) end it "warns when setting provider_base" do expect do class OverrideProviderBaseTest2 < Chef::Resource provider_base Chef::Provider::Package end end.to raise_error(Chef::Exceptions::DeprecatedFeatureError) end end it "runs an action by finding its provider, loading the current resource and then running the action" do skip end describe "when updated by a provider" do before do resource.updated_by_last_action(true) end it "records that it was updated" do expect(resource).to be_updated end it "records that the last action updated the resource" do expect(resource).to be_updated_by_last_action end describe "and then run again without being updated" do before do resource.updated_by_last_action(false) end it "reports that it is updated" do expect(resource).to be_updated end it "reports that it was not updated by the last action" do expect(resource).not_to be_updated_by_last_action end end end describe "when invoking its action" do let(:resource) do resource = Chef::Resource.new("provided", run_context) resource.provider = Chef::Provider::SnakeOil resource end before do node.automatic_attrs[:platform] = "fubuntu" node.automatic_attrs[:platform_version] = "10.04" end it "does not run only_if if no only_if command is given" do expect_any_instance_of(Chef::Resource::Conditional).not_to receive(:evaluate) resource.only_if.clear resource.run_action(:purr) end it "runs runs an only_if when one is given" do snitch_variable = nil resource.only_if { snitch_variable = true } expect(resource.only_if.first.positivity).to eq(:only_if) #Chef::Mixin::Command.should_receive(:only_if).with(true, {}).and_return(false) resource.run_action(:purr) expect(snitch_variable).to be_truthy end it "runs multiple only_if conditionals" do snitch_var1, snitch_var2 = nil, nil resource.only_if { snitch_var1 = 1 } resource.only_if { snitch_var2 = 2 } resource.run_action(:purr) expect(snitch_var1).to eq(1) expect(snitch_var2).to eq(2) end it "accepts command options for only_if conditionals" do expect_any_instance_of(Chef::Resource::Conditional).to receive(:evaluate_command).at_least(1).times resource.only_if("true", :cwd => "/tmp") expect(resource.only_if.first.command_opts).to eq({ :cwd => "/tmp" }) resource.run_action(:purr) end it "runs not_if as a command when it is a string" do expect_any_instance_of(Chef::Resource::Conditional).to receive(:evaluate_command).at_least(1).times resource.not_if "pwd" resource.run_action(:purr) end it "runs not_if as a block when it is a ruby block" do expect_any_instance_of(Chef::Resource::Conditional).to receive(:evaluate_block).at_least(1).times resource.not_if { puts "foo" } resource.run_action(:purr) end it "does not run not_if if no not_if command is given" do expect_any_instance_of(Chef::Resource::Conditional).not_to receive(:evaluate) resource.not_if.clear resource.run_action(:purr) end it "accepts command options for not_if conditionals" do resource.not_if("pwd" , :cwd => "/tmp") expect(resource.not_if.first.command_opts).to eq({ :cwd => "/tmp" }) end it "accepts multiple not_if conditionals" do snitch_var1, snitch_var2 = true, true resource.not_if { snitch_var1 = nil } resource.not_if { snitch_var2 = false } resource.run_action(:purr) expect(snitch_var1).to be_nil expect(snitch_var2).to be_falsey end it "reports 0 elapsed time if actual elapsed time is < 0" do expected = Time.now allow(Time).to receive(:now).and_return(expected, expected - 1) resource.run_action(:purr) expect(resource.elapsed_time).to eq(0) end describe "guard_interpreter attribute" do it "should be set to :default by default" do expect(resource.guard_interpreter).to eq(:default) end it "if set to :default should return :default when read" do resource.guard_interpreter(:default) expect(resource.guard_interpreter).to eq(:default) end it "should raise Chef::Exceptions::ValidationFailed on an attempt to set the guard_interpreter attribute to something other than a Symbol" do expect { resource.guard_interpreter("command_dot_com") }.to raise_error(Chef::Exceptions::ValidationFailed) end it "should not raise an exception when setting the guard interpreter attribute to a Symbol" do allow(Chef::GuardInterpreter::ResourceGuardInterpreter).to receive(:new).and_return(nil) expect { resource.guard_interpreter(:command_dot_com) }.not_to raise_error end end end describe "should_skip?" do before do resource = Chef::Resource::Cat.new("sugar", run_context) end it "should return false by default" do expect(resource.should_skip?(:purr)).to be_falsey end it "should return false when only_if is met" do resource.only_if { true } expect(resource.should_skip?(:purr)).to be_falsey end it "should return true when only_if is not met" do resource.only_if { false } expect(resource.should_skip?(:purr)).to be_truthy end it "should return true when not_if is met" do resource.not_if { true } expect(resource.should_skip?(:purr)).to be_truthy end it "should return false when not_if is not met" do resource.not_if { false } expect(resource.should_skip?(:purr)).to be_falsey end it "should return true when only_if is met but also not_if is met" do resource.only_if { true } resource.not_if { true } expect(resource.should_skip?(:purr)).to be_truthy end it "should return false when only_if is met and also not_if is not met" do resource.only_if { true } resource.not_if { false } expect(resource.should_skip?(:purr)).to be_falsey end it "should return true when one of multiple only_if's is not met" do resource.only_if { true } resource.only_if { false } resource.only_if { true } expect(resource.should_skip?(:purr)).to be_truthy end it "should return true when one of multiple not_if's is met" do resource.not_if { false } resource.not_if { true } resource.not_if { false } expect(resource.should_skip?(:purr)).to be_truthy end it "should return false when all of multiple only_if's are met" do resource.only_if { true } resource.only_if { true } resource.only_if { true } expect(resource.should_skip?(:purr)).to be_falsey end it "should return false when all of multiple not_if's are not met" do resource.not_if { false } resource.not_if { false } resource.not_if { false } expect(resource.should_skip?(:purr)).to be_falsey end it "should return true when action is :nothing" do expect(resource.should_skip?(:nothing)).to be_truthy end it "should return true when action is :nothing ignoring only_if/not_if conditionals" do resource.only_if { true } resource.not_if { false } expect(resource.should_skip?(:nothing)).to be_truthy end it "should print \"skipped due to action :nothing\" message for doc formatter when action is :nothing" do fdoc = Chef::Formatters.new(:doc, STDOUT, STDERR) allow(run_context).to receive(:events).and_return(fdoc) expect(fdoc).to receive(:puts).with(" (skipped due to action :nothing)", anything()) resource.should_skip?(:nothing) end end describe "when resource action is :nothing" do let(:resource1) do resource1 = Chef::Resource::Cat.new("sugar", run_context) resource1.action = :nothing resource1 end before do node.automatic_attrs[:platform] = "fubuntu" node.automatic_attrs[:platform_version] = "10.04" end it "should not run only_if/not_if conditionals (CHEF-972)" do snitch_var1 = 0 resource1.only_if { snitch_var1 = 1 } resource1.not_if { snitch_var1 = 2 } resource1.run_action(:nothing) expect(snitch_var1).to eq(0) end it "should run only_if/not_if conditionals when notified to run another action (CHEF-972)" do snitch_var1 = snitch_var2 = 0 runner = Chef::Runner.new(run_context) Chef::Platform.set( :resource => :cat, :provider => Chef::Provider::SnakeOil ) resource1.only_if { snitch_var1 = 1 } resource1.not_if { snitch_var2 = 2 } resource2 = Chef::Resource::Cat.new("coffee", run_context) resource2.notifies :purr, resource1 resource2.action = :purr run_context.resource_collection << resource1 run_context.resource_collection << resource2 runner.converge expect(snitch_var1).to eq(1) expect(snitch_var2).to eq(2) end end describe "building the platform map" do let(:klz) { Class.new(Chef::Resource) } before do Chef::Resource::Klz = klz end after do Chef::Resource.send(:remove_const, :Klz) end it "adds mappings for a single platform" do expect(Chef.resource_handler_map).to receive(:set).with( :dinobot, Chef::Resource::Klz, { platform: ["autobots"] } ) klz.provides :dinobot, platform: ["autobots"] end it "adds mappings for multiple platforms" do expect(Chef.resource_handler_map).to receive(:set).with( :energy, Chef::Resource::Klz, { platform: %w{autobots decepticons} } ) klz.provides :energy, platform: %w{autobots decepticons} end it "adds mappings for all platforms" do expect(Chef.resource_handler_map).to receive(:set).with( :tape_deck, Chef::Resource::Klz, {} ) klz.provides :tape_deck end end describe "resource_for_node" do describe "lookups from the platform map" do let(:klz1) { Class.new(Chef::Resource) } before(:each) do Chef::Resource::Klz1 = klz1 node = Chef::Node.new node.name("bumblebee") node.automatic[:platform] = "autobots" node.automatic[:platform_version] = "6.1" Object.const_set("Soundwave", klz1) klz1.provides :soundwave end after(:each) do Object.send(:remove_const, :Soundwave) Chef::Resource.send(:remove_const, :Klz1) end it "returns a resource by short_name if nothing else matches" do expect(Chef::Resource.resource_for_node(:soundwave, node)).to eql(klz1) end end describe "lookups from the platform map" do let(:klz2) { Class.new(Chef::Resource) } before(:each) do Chef::Resource::Klz2 = klz2 node.name("bumblebee") node.automatic[:platform] = "autobots" node.automatic[:platform_version] = "6.1" klz2.provides :dinobot, :platform => ["autobots"] Object.const_set("Grimlock", klz2) klz2.provides :grimlock end after(:each) do Object.send(:remove_const, :Grimlock) Chef::Resource.send(:remove_const, :Klz2) end it "returns a resource by short_name and node" do expect(Chef::Resource.resource_for_node(:dinobot, node)).to eql(klz2) end end end describe "when creating notifications" do describe "with a string resource spec" do it "creates a delayed notification when timing is not specified" do resource.notifies(:run, "execute[foo]") expect(run_context.delayed_notification_collection.size).to eq(1) end it "creates a delayed notification when :delayed is not specified" do resource.notifies(:run, "execute[foo]", :delayed) expect(run_context.delayed_notification_collection.size).to eq(1) end it "creates an immediate notification when :immediate is specified" do resource.notifies(:run, "execute[foo]", :immediate) expect(run_context.immediate_notification_collection.size).to eq(1) end it "creates an immediate notification when :immediately is specified" do resource.notifies(:run, "execute[foo]", :immediately) expect(run_context.immediate_notification_collection.size).to eq(1) end describe "with a syntax error in the resource spec" do it "raises an exception immmediately" do expect do resource.notifies(:run, "typo[missing-closing-bracket") end.to raise_error(Chef::Exceptions::InvalidResourceSpecification) end end end describe "with a resource reference" do let(:notified_resource) { Chef::Resource.new("punk", run_context) } it "creates a delayed notification when timing is not specified" do resource.notifies(:run, notified_resource) expect(run_context.delayed_notification_collection.size).to eq(1) end it "creates a delayed notification when :delayed is not specified" do resource.notifies(:run, notified_resource, :delayed) expect(run_context.delayed_notification_collection.size).to eq(1) end it "creates an immediate notification when :immediate is specified" do resource.notifies(:run, notified_resource, :immediate) expect(run_context.immediate_notification_collection.size).to eq(1) end it "creates an immediate notification when :immediately is specified" do resource.notifies(:run, notified_resource, :immediately) expect(run_context.immediate_notification_collection.size).to eq(1) end end end describe "resource sensitive attribute" do let(:resource_file) { Chef::Resource::File.new("/nonexistent/CHEF-5098/file", run_context) } let(:action) { :create } def compiled_resource_data(resource, action, err) error_inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(resource, action, err) description = Chef::Formatters::ErrorDescription.new("test") error_inspector.add_explanation(description) Chef::Log.info("descrtiption: #{description.inspect},error_inspector: #{error_inspector}") description.sections[1]["Compiled Resource:"] end it "set to false by default" do expect(resource.sensitive).to be_falsey end it "when set to false should show compiled resource for failed resource" do expect { resource_file.run_action(action) }.to raise_error { |err| expect(compiled_resource_data(resource_file, action, err)).to match 'path "/nonexistent/CHEF-5098/file"' } end it "when set to true should show compiled resource for failed resource" do resource_file.sensitive true expect { resource_file.run_action(action) }.to raise_error { |err| expect(compiled_resource_data(resource_file, action, err)).to eql("suppressed sensitive resource output") } end end describe "#action" do let(:resource_class) do Class.new(described_class) do allowed_actions(%i{one two}) end end let(:resource) { resource_class.new("test", nil) } subject { resource.action } context "with a no action" do it { is_expected.to eq [:nothing] } end context "with a default action" do let(:resource_class) do Class.new(described_class) do default_action(:one) end end it { is_expected.to eq [:one] } end context "with a symbol action" do before { resource.action(:one) } it { is_expected.to eq [:one] } end context "with a string action" do before { resource.action("two") } it { is_expected.to eq [:two] } end context "with an array action" do before { resource.action([:two, :one]) } it { is_expected.to eq [:two, :one] } end context "with an assignment" do before { resource.action = :one } it { is_expected.to eq [:one] } end context "with an array assignment" do before { resource.action = [:two, :one] } it { is_expected.to eq [:two, :one] } end context "with an invalid action" do it { expect { resource.action(:three) }.to raise_error Chef::Exceptions::ValidationFailed } end context "with an invalid assignment action" do it { expect { resource.action = :three }.to raise_error Chef::Exceptions::ValidationFailed } end end describe ".default_action" do let(:default_action) {} let(:resource_class) do actions = default_action Class.new(described_class) do default_action(actions) if actions end end subject { resource_class.default_action } context "with no default actions" do it { is_expected.to eq [:nothing] } end context "with a symbol default action" do let(:default_action) { :one } it { is_expected.to eq [:one] } end context "with a string default action" do let(:default_action) { "one" } it { is_expected.to eq [:one] } end context "with an array default action" do let(:default_action) { [:two, :one] } it { is_expected.to eq [:two, :one] } end end end chef-12.14.60/spec/unit/rest/000077500000000000000000000000001276456504500155565ustar00rootroot00000000000000chef-12.14.60/spec/unit/rest/auth_credentials_spec.rb000066400000000000000000000273101276456504500224360ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Brown () # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Copyright:: Copyright 2010-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 "uri" require "net/https" describe Chef::REST::AuthCredentials do before do @key_file_fixture = CHEF_SPEC_DATA + "/ssl/private_key.pem" @key = OpenSSL::PKey::RSA.new(IO.read(@key_file_fixture).strip) @auth_credentials = Chef::REST::AuthCredentials.new("client-name", @key) end it "has a client name" do expect(@auth_credentials.client_name).to eq("client-name") end it "loads the private key when initialized with the path to the key" do expect(@auth_credentials.key).to respond_to(:private_encrypt) expect(@auth_credentials.key).to eq(@key) end describe "when loading the private key" do it "strips extra whitespace before checking the key" do key_file_fixture = CHEF_SPEC_DATA + "/ssl/private_key_with_whitespace.pem" expect { Chef::REST::AuthCredentials.new("client-name", @key_file_fixture) }.not_to raise_error end end describe "generating signature headers for a request" do before do @request_time = Time.at(1270920860) @request_params = { :http_method => :POST, :path => "/clients", :body => '{"some":"json"}', :host => "localhost" } allow(Chef::Config).to( receive(:[]).with(:authentication_protocol_version).and_return(protocol_version)) end context "when configured for version 1.0 of the authn protocol" do let(:protocol_version) { "1.0" } it "generates signature headers for the request" do allow(Time).to receive(:now).and_return(@request_time) actual = @auth_credentials.signature_headers(@request_params) expect(actual["HOST"]).to eq("localhost") expect(actual["X-OPS-AUTHORIZATION-1"]).to eq("kBssX1ENEwKtNYFrHElN9vYGWS7OeowepN9EsYc9csWfh8oUovryPKDxytQ/") expect(actual["X-OPS-AUTHORIZATION-2"]).to eq("Wc2/nSSyxdWJjjfHzrE+YrqNQTaArOA7JkAf5p75eTUonCWcvNPjFrZVgKGS") expect(actual["X-OPS-AUTHORIZATION-3"]).to eq("yhzHJQh+lcVA9wwARg5Hu9q+ddS8xBOdm3Vp5atl5NGHiP0loiigMYvAvzPO") expect(actual["X-OPS-AUTHORIZATION-4"]).to eq("r9853eIxwYMhn5hLGhAGFQznJbE8+7F/lLU5Zmk2t2MlPY8q3o1Q61YD8QiJ") expect(actual["X-OPS-AUTHORIZATION-5"]).to eq("M8lIt53ckMyUmSU0DDURoiXLVkE9mag/6Yq2tPNzWq2AdFvBqku9h2w+DY5k") expect(actual["X-OPS-AUTHORIZATION-6"]).to eq("qA5Rnzw5rPpp3nrWA9jKkPw4Wq3+4ufO2Xs6w7GCjA==") expect(actual["X-OPS-CONTENT-HASH"]).to eq("1tuzs5XKztM1ANrkGNPah6rW9GY=") expect(actual["X-OPS-SIGN"]).to match(%r{(version=1\.0)|(algorithm=sha1;version=1.0;)}) expect(actual["X-OPS-TIMESTAMP"]).to eq("2010-04-10T17:34:20Z") expect(actual["X-OPS-USERID"]).to eq("client-name") end end context "when configured for version 1.1 of the authn protocol" do let(:protocol_version) { "1.1" } it "generates the correct signature for version 1.1" do allow(Time).to receive(:now).and_return(@request_time) actual = @auth_credentials.signature_headers(@request_params) expect(actual["HOST"]).to eq("localhost") expect(actual["X-OPS-CONTENT-HASH"]).to eq("1tuzs5XKztM1ANrkGNPah6rW9GY=") expect(actual["X-OPS-SIGN"]).to eq("algorithm=sha1;version=1.1;") expect(actual["X-OPS-TIMESTAMP"]).to eq("2010-04-10T17:34:20Z") expect(actual["X-OPS-USERID"]).to eq("client-name") # mixlib-authN will test the actual signature stuff for each version of # the protocol so we won't test it again here. end end end end describe Chef::REST::RESTRequest do let(:url) { URI.parse("http://chef.example.com:4000/?q=chef_is_awesome") } def new_request(method = nil) method ||= :POST Chef::REST::RESTRequest.new(method, url, @req_body, @headers) end before do @auth_credentials = Chef::REST::AuthCredentials.new("client-name", CHEF_SPEC_DATA + "/ssl/private_key.pem") @req_body = '{"json_data":"as_a_string"}' @headers = { "Content-type" => "application/json", "Accept" => "application/json", "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, "Host" => "chef.example.com:4000" } @request = Chef::REST::RESTRequest.new(:POST, url, @req_body, @headers) end it "stores the url it was created with" do expect(@request.url).to eq(url) end it "stores the HTTP method" do expect(@request.method).to eq(:POST) end it "adds the chef version header" do expect(@request.headers).to eq(@headers.merge("X-Chef-Version" => ::Chef::VERSION)) end describe "configuring the HTTP request" do let(:url) do URI.parse("http://homie:theclown@chef.example.com:4000/?q=chef_is_awesome") end it "configures GET requests" do @req_body = nil rest_req = new_request(:GET) expect(rest_req.http_request).to be_a_kind_of(Net::HTTP::Get) expect(rest_req.http_request.path).to eq("/?q=chef_is_awesome") expect(rest_req.http_request.body).to be_nil end it "configures POST requests, including the body" do expect(@request.http_request).to be_a_kind_of(Net::HTTP::Post) expect(@request.http_request.path).to eq("/?q=chef_is_awesome") expect(@request.http_request.body).to eq(@req_body) end it "configures PUT requests, including the body" do rest_req = new_request(:PUT) expect(rest_req.http_request).to be_a_kind_of(Net::HTTP::Put) expect(rest_req.http_request.path).to eq("/?q=chef_is_awesome") expect(rest_req.http_request.body).to eq(@req_body) end it "configures DELETE requests" do rest_req = new_request(:DELETE) expect(rest_req.http_request).to be_a_kind_of(Net::HTTP::Delete) expect(rest_req.http_request.path).to eq("/?q=chef_is_awesome") expect(rest_req.http_request.body).to be_nil end it "configures HTTP basic auth" do rest_req = new_request(:GET) expect(rest_req.http_request.to_hash["authorization"]).to eq(["Basic aG9taWU6dGhlY2xvd24="]) end end describe "configuring the HTTP client" do it "configures the HTTP client for the host and port" do http_client = new_request.http_client expect(http_client.address).to eq("chef.example.com") expect(http_client.port).to eq(4000) end it "configures the HTTP client with the read timeout set in the config file" do Chef::Config[:rest_timeout] = 9001 expect(new_request.http_client.read_timeout).to eq(9001) end describe "for proxy" do before do stub_const("ENV", "http_proxy" => "http://proxy.example.com:3128", "https_proxy" => "http://sproxy.example.com:3129", "http_proxy_user" => nil, "http_proxy_pass" => nil, "https_proxy_user" => nil, "https_proxy_pass" => nil, "no_proxy" => nil ) end describe "with :no_proxy nil" do it "configures the proxy address and port when using http scheme" do http_client = new_request.http_client expect(http_client.proxy?).to eq(true) expect(http_client.proxy_address).to eq("proxy.example.com") expect(http_client.proxy_port).to eq(3128) expect(http_client.proxy_user).to be_nil expect(http_client.proxy_pass).to be_nil end context "when the url has an https scheme" do let(:url) { URI.parse("https://chef.example.com:4000/?q=chef_is_awesome") } it "configures the proxy address and port when using https scheme" do http_client = new_request.http_client expect(http_client.proxy?).to eq(true) expect(http_client.proxy_address).to eq("sproxy.example.com") expect(http_client.proxy_port).to eq(3129) expect(http_client.proxy_user).to be_nil expect(http_client.proxy_pass).to be_nil end end end describe "with :no_proxy set" do before do stub_const("ENV", "no_proxy" => "10.*,*.example.com") end it "does not configure the proxy address and port when using http scheme" do http_client = new_request.http_client expect(http_client.proxy?).to eq(false) expect(http_client.proxy_address).to be_nil expect(http_client.proxy_port).to be_nil expect(http_client.proxy_user).to be_nil expect(http_client.proxy_pass).to be_nil end context "when the url has an https scheme" do let(:url) { URI.parse("https://chef.example.com:4000/?q=chef_is_awesome") } it "does not configure the proxy address and port when using https scheme" do http_client = new_request.http_client expect(http_client.proxy?).to eq(false) expect(http_client.proxy_address).to be_nil expect(http_client.proxy_port).to be_nil expect(http_client.proxy_user).to be_nil expect(http_client.proxy_pass).to be_nil end end end describe "with :http_proxy_user and :http_proxy_pass set" do before do stub_const("ENV", "http_proxy" => "http://homie:theclown@proxy.example.com:3128") end it "configures the proxy user and pass when using http scheme" do http_client = new_request.http_client expect(http_client.proxy?).to eq(true) expect(http_client.proxy_user).to eq("homie") expect(http_client.proxy_pass).to eq("theclown") end context "when the url has an https scheme" do let(:url) { URI.parse("https://chef.example.com:4000/?q=chef_is_awesome") } it "does not configure the proxy user and pass when using https scheme" do http_client = new_request.http_client expect(http_client.proxy?).to eq(false) expect(http_client.proxy_user).to be_nil expect(http_client.proxy_pass).to be_nil end end end describe "with :https_proxy_user and :https_proxy_pass set" do before do stub_const("ENV", "http_proxy" => "http://proxy.example.com:3128", "https_proxy" => "https://homie:theclown@sproxy.example.com:3129" ) end it "does not configure the proxy user and pass when using http scheme" do http_client = new_request.http_client expect(http_client.proxy?).to eq(true) expect(http_client.proxy_user).to be_nil expect(http_client.proxy_pass).to be_nil end context "when the url has an https scheme" do let(:url) { URI.parse("https://chef.example.com:4000/?q=chef_is_awesome") } it "configures the proxy user and pass when using https scheme" do http_client = new_request.http_client expect(http_client.proxy?).to eq(true) expect(http_client.proxy_user).to eq("homie") expect(http_client.proxy_pass).to eq("theclown") end end end end end end chef-12.14.60/spec/unit/rest_spec.rb000066400000000000000000000755221276456504500171300ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Christopher Brown () # Author:: Daniel DeLeo () # Copyright:: Copyright 2008-2016, Chef Software Inc. # Copyright:: Copyright 2010-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 "uri" require "net/https" require "stringio" SIGNING_KEY_DOT_PEM = "-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh 8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD 2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/ /RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4 DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8 OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12 2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ== -----END RSA PRIVATE KEY-----" describe Chef::REST do let(:base_url) { "http://chef.example.com:4000" } let(:monkey_uri) { URI.parse("http://chef.example.com:4000/monkey") } let(:log_stringio) { StringIO.new } let(:request_id) { "1234" } let(:rest) do allow(Chef::REST::CookieJar).to receive(:instance).and_return({}) allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id) rest = Chef::REST.new(base_url, nil, nil) Chef::REST::CookieJar.instance.clear rest end let(:standard_read_headers) { { "Accept" => "application/json", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID" => request_id, "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION } } let(:standard_write_headers) { { "Accept" => "application/json", "Content-Type" => "application/json", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID" => request_id, "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION } } before(:each) do Chef::Log.init(log_stringio) Chef::Config[:treat_deprecation_warnings_as_errors] = false end it "should have content length validation middleware after compressor middleware" do middlewares = rest.instance_variable_get(:@middlewares) content_length = middlewares.find_index { |e| e.is_a? Chef::HTTP::ValidateContentLength } decompressor = middlewares.find_index { |e| e.is_a? Chef::HTTP::Decompressor } expect(content_length).not_to be_nil expect(decompressor).not_to be_nil expect(decompressor < content_length).to be_truthy end it "should allow the options hash to be frozen" do options = {}.freeze # should not raise any exception Chef::REST.new(base_url, nil, nil, options) end it "emits a deprecation warning" do Chef::Config[:treat_deprecation_warnings_as_errors] = true expect { Chef::REST.new(base_url) }.to raise_error Chef::Exceptions::DeprecatedFeatureError, /Chef::REST is deprecated. Please use Chef::ServerAPI, or investigate Ridley or ChefAPI./ end context "when created with a chef zero URL" do let(:url) { "chefzero://localhost:1" } it "does not load the signing key" do expect { Chef::REST.new(url) }.to_not raise_error end end describe "calling an HTTP verb on a path or absolute URL" do it "adds a relative URL to the base url it was initialized with" do expect(rest.create_url("foo/bar/baz")).to eq(URI.parse(base_url + "/foo/bar/baz")) end it "replaces the base URL when given an absolute URL" do expect(rest.create_url("http://chef-rulez.example.com:9000")).to eq(URI.parse("http://chef-rulez.example.com:9000")) end it "makes a :GET request with the composed url object" do expect(rest).to receive(:send_http_request). with(:GET, monkey_uri, standard_read_headers, false). and_return([1, 2, 3]) expect(rest).to receive(:apply_response_middleware).with(1, 2, 3).and_return([1, 2, 3]) expect(rest).to receive("success_response?".to_sym).with(1).and_return(true) rest.get_rest("monkey") end it "makes a :GET reqest for a streaming download with the composed url" do expect(rest).to receive(:streaming_request).with("monkey", {}) rest.get_rest("monkey", true) end it "makes a :DELETE request with the composed url object" do expect(rest).to receive(:send_http_request). with(:DELETE, monkey_uri, standard_read_headers, false). and_return([1, 2, 3]) expect(rest).to receive(:apply_response_middleware).with(1, 2, 3).and_return([1, 2, 3]) expect(rest).to receive("success_response?".to_sym).with(1).and_return(true) rest.delete_rest("monkey") end it "makes a :POST request with the composed url object and data" do expect(rest).to receive(:send_http_request). with(:POST, monkey_uri, standard_write_headers, "\"data\""). and_return([1, 2, 3]) expect(rest).to receive(:apply_response_middleware).with(1, 2, 3).and_return([1, 2, 3]) expect(rest).to receive("success_response?".to_sym).with(1).and_return(true) rest.post_rest("monkey", "data") end it "makes a :PUT request with the composed url object and data" do expect(rest).to receive(:send_http_request). with(:PUT, monkey_uri, standard_write_headers, "\"data\""). and_return([1, 2, 3]) expect(rest).to receive(:apply_response_middleware).with(1, 2, 3).and_return([1, 2, 3]) expect(rest).to receive("success_response?".to_sym).with(1).and_return(true) rest.put_rest("monkey", "data") end end describe "legacy API" do let(:rest) do Chef::REST.new(base_url) end before(:each) do Chef::Config[:node_name] = "webmonkey.example.com" Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem" end it "responds to raw_http_request as a public method" do expect(rest.public_methods.map(&:to_s)).to include("raw_http_request") end it "calls the authn middleware" do data = "\"secure data\"" auth_headers = standard_write_headers.merge({ "auth_done" => "yep" }) expect(rest.authenticator).to receive(:handle_request). with(:POST, monkey_uri, standard_write_headers, data). and_return([:POST, monkey_uri, auth_headers, data]) expect(rest).to receive(:send_http_request). with(:POST, monkey_uri, auth_headers, data). and_return([1, 2, 3]) expect(rest).to receive("success_response?".to_sym).with(1).and_return(true) rest.raw_http_request(:POST, monkey_uri, standard_write_headers, data) end it "sets correct authn headers" do data = "\"secure data\"" method, uri, auth_headers, d = rest.authenticator.handle_request(:POST, monkey_uri, standard_write_headers, data) expect(rest).to receive(:send_http_request). with(:POST, monkey_uri, auth_headers, data). and_return([1, 2, 3]) expect(rest).to receive("success_response?".to_sym).with(1).and_return(true) rest.raw_http_request(:POST, monkey_uri, standard_write_headers, data) end end describe "when configured to authenticate to the Chef server" do let(:base_url) { URI.parse("http://chef.example.com:4000") } let(:rest) do Chef::REST.new(base_url) end before do Chef::Config[:node_name] = "webmonkey.example.com" Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem" end it "configures itself to use the node_name and client_key in the config by default" do expect(rest.client_name).to eq("webmonkey.example.com") expect(rest.signing_key_filename).to eq(CHEF_SPEC_DATA + "/ssl/private_key.pem") end it "provides access to the raw key data" do expect(rest.signing_key).to eq(SIGNING_KEY_DOT_PEM) end it "does not error out when initialized without credentials" do rest = Chef::REST.new(base_url, nil, nil) #should_not raise_error hides the bt from you, so screw it. expect(rest.client_name).to be_nil expect(rest.signing_key).to be_nil end it "indicates that requests should not be signed when it has no credentials" do rest = Chef::REST.new(base_url, nil, nil) expect(rest.sign_requests?).to be_falsey end it "raises PrivateKeyMissing when the key file doesn't exist" do expect { Chef::REST.new(base_url, "client-name", "/dev/null/nothing_here") }.to raise_error(Chef::Exceptions::PrivateKeyMissing) end it "raises InvalidPrivateKey when the key file doesnt' look like a key" do invalid_key_file = CHEF_SPEC_DATA + "/bad-config.rb" expect { Chef::REST.new(base_url, "client-name", invalid_key_file) }.to raise_error(Chef::Exceptions::InvalidPrivateKey) end it "can take private key as a sting :raw_key in options during initializaton" do expect(Chef::REST.new(base_url, "client-name", nil, :raw_key => SIGNING_KEY_DOT_PEM).signing_key).to eq(SIGNING_KEY_DOT_PEM) end it "raises InvalidPrivateKey when the key passed as string :raw_key in options doesnt' look like a key" do expect { Chef::REST.new(base_url, "client-name", nil, :raw_key => "bad key string") }.to raise_error(Chef::Exceptions::InvalidPrivateKey) end end context "when making REST requests" do let(:body) { "ninja" } let(:http_response) do http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req") allow(http_response).to receive(:read_body) allow(http_response).to receive(:body).and_return(body) http_response["Content-Length"] = body.bytesize.to_s http_response end let(:host_header) { "one" } let(:url) { URI.parse("http://one:80/?foo=bar") } let(:base_url) { "http://chef.example.com:4000" } let!(:http_client) do http_client = Net::HTTP.new(url.host, url.port) allow(http_client).to receive(:request).and_yield(http_response).and_return(http_response) http_client end let(:rest) do allow(Net::HTTP).to receive(:new).and_return(http_client) allow(Chef::REST::CookieJar).to receive(:instance).and_return({}) allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id) rest = Chef::REST.new(base_url, nil, nil) Chef::REST::CookieJar.instance.clear rest end before(:each) do Chef::Config[:ssl_client_cert] = nil Chef::Config[:ssl_client_key] = nil end describe "as JSON API requests" do let(:request_mock) { {} } let(:base_headers) do #FIXME: huh? { "Accept" => "application/json", "X-Chef-Version" => Chef::VERSION, "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, "Host" => host_header, "X-REMOTE-REQUEST-ID" => request_id, "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION, } end before do allow(Net::HTTP::Get).to receive(:new).and_return(request_mock) end it "should always include the X-Chef-Version header" do expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) rest.request(:GET, url, {}) end it "should always include the X-Remote-Request-Id header" do expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) rest.request(:GET, url, {}) end it "sets the user agent to chef-client" do # XXX: must reset to default b/c knife changes the UA Chef::REST::RESTRequest.user_agent = Chef::REST::RESTRequest::DEFAULT_UA rest.request(:GET, url, {}) expect(request_mock["User-Agent"]).to match(/^Chef Client\/#{Chef::VERSION}/) end # CHEF-3140 context "when configured to disable compression" do let(:rest) do allow(Net::HTTP).to receive(:new).and_return(http_client) Chef::REST.new(base_url, nil, nil, :disable_gzip => true) end it "does not accept encoding gzip" do expect(rest.send(:build_headers, :GET, url, {})).not_to have_key("Accept-Encoding") end it "does not decompress a response encoded as gzip" do http_response.add_field("content-encoding", "gzip") request = Net::HTTP::Get.new(url.path) expect(Net::HTTP::Get).to receive(:new).and_return(request) # will raise a Zlib error if incorrect expect(rest.request(:GET, url, {})).to eq("ninja") end end context "when configured with custom http headers" do let(:custom_headers) do { "X-Custom-ChefSecret" => "sharpknives", "X-Custom-RequestPriority" => "extremely low", } end before(:each) do Chef::Config[:custom_http_headers] = custom_headers end after(:each) do Chef::Config[:custom_http_headers] = nil end it "should set them on the http request" do url_string = an_instance_of(String) header_hash = hash_including(custom_headers) expect(Net::HTTP::Get).to receive(:new).with(url_string, header_hash) rest.request(:GET, url, {}) end end context "when setting cookies" do let(:rest) do allow(Net::HTTP).to receive(:new).and_return(http_client) Chef::REST::CookieJar.instance["#{url.host}:#{url.port}"] = "cookie monster" allow(Chef::RequestID.instance).to receive(:request_id).and_return(request_id) rest = Chef::REST.new(base_url, nil, nil) rest end it "should set the cookie for this request if one exists for the given host:port" do expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers.merge("Cookie" => "cookie monster")).and_return(request_mock) rest.request(:GET, url, {}) end end it "should build a new HTTP GET request" do expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) rest.request(:GET, url, {}) end it "should build a new HTTP POST request" do request = Net::HTTP::Post.new(url.path) expected_headers = base_headers.merge("Content-Type" => "application/json", "Content-Length" => "13") expect(Net::HTTP::Post).to receive(:new).with("/?foo=bar", expected_headers).and_return(request) rest.request(:POST, url, {}, { :one => :two }) expect(request.body).to eq('{"one":"two"}') end it "should build a new HTTP PUT request" do request = Net::HTTP::Put.new(url.path) expected_headers = base_headers.merge("Content-Type" => "application/json", "Content-Length" => "13") expect(Net::HTTP::Put).to receive(:new).with("/?foo=bar", expected_headers).and_return(request) rest.request(:PUT, url, {}, { :one => :two }) expect(request.body).to eq('{"one":"two"}') end it "should build a new HTTP DELETE request" do expect(Net::HTTP::Delete).to receive(:new).with("/?foo=bar", base_headers).and_return(request_mock) rest.request(:DELETE, url) end it "should raise an error if the method is not GET/PUT/POST/DELETE" do expect { rest.request(:MONKEY, url) }.to raise_error(ArgumentError) end it "returns nil when the response is successful but content-type is not JSON" do expect(rest.request(:GET, url)).to eq("ninja") end it "should fail if the response is truncated" do http_response["Content-Length"] = (body.bytesize + 99).to_s expect { rest.request(:GET, url) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end context "when JSON is returned" do let(:body) { '{"ohai2u":"json_api"}' } it "should inflate the body as to an object" do http_response.add_field("content-type", "application/json") expect(rest.request(:GET, url, {})).to eq({ "ohai2u" => "json_api" }) end it "should fail if the response is truncated" do http_response.add_field("content-type", "application/json") http_response["Content-Length"] = (body.bytesize + 99).to_s expect { rest.request(:GET, url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end end %w{ HTTPFound HTTPMovedPermanently HTTPSeeOther HTTPUseProxy HTTPTemporaryRedirect HTTPMultipleChoice }.each do |resp_name| describe "when encountering a #{resp_name} redirect" do let(:http_response) do resp_cls = Net.const_get(resp_name) resp_code = Net::HTTPResponse::CODE_TO_OBJ.keys.detect { |k| Net::HTTPResponse::CODE_TO_OBJ[k] == resp_cls } http_response = Net::HTTPFound.new("1.1", resp_code, "bob is somewhere else again") http_response.add_field("location", url.path) allow(http_response).to receive(:read_body) http_response end it "should call request again" do expect { rest.request(:GET, url) }.to raise_error(Chef::Exceptions::RedirectLimitExceeded) [:PUT, :POST, :DELETE].each do |method| expect { rest.request(method, url) }.to raise_error(Chef::Exceptions::InvalidRedirect) end end end end context "when the response is 304 NotModified" do let (:http_response) do http_response = Net::HTTPNotModified.new("1.1", "304", "it's the same as when you asked 5 minutes ago") allow(http_response).to receive(:read_body) http_response end it "should return `false`" do expect(rest.request(:GET, url)).to be_falsey end end describe "when the request fails" do before do @original_log_level = Chef::Log.level Chef::Log.level = :info end after do Chef::Log.level = @original_log_level end context "on an unsuccessful response with a JSON error" do let(:http_response) do http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth") http_response.add_field("content-type", "application/json") allow(http_response).to receive(:body).and_return('{ "error":[ "Ears get sore!", "Not even four" ] }') allow(http_response).to receive(:read_body) http_response end it "should show the JSON error message" do allow(rest).to receive(:sleep) expect { rest.request(:GET, url) }.to raise_error(Net::HTTPFatalError) expect(log_stringio.string).to match(Regexp.escape("INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four")) end end context "on an unsuccessful response with a JSON error that is compressed" do let(:http_response) do http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth") http_response.add_field("content-type", "application/json") http_response.add_field("content-encoding", "deflate") unzipped_body = '{ "error":[ "Ears get sore!", "Not even four" ] }' gzipped_body = Zlib::Deflate.deflate(unzipped_body) gzipped_body.force_encoding(Encoding::BINARY) if "strings".respond_to?(:force_encoding) allow(http_response).to receive(:body).and_return gzipped_body allow(http_response).to receive(:read_body) http_response end before do allow(rest).to receive(:sleep) allow(rest).to receive(:http_retry_count).and_return(0) end it "decompresses the JSON error message" do expect { rest.request(:GET, url) }.to raise_error(Net::HTTPFatalError) expect(log_stringio.string).to match(Regexp.escape("INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four")) end it "fails when the compressed body is truncated" do http_response["Content-Length"] = (body.bytesize + 99).to_s expect { rest.request(:GET, url) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end end context "on a generic unsuccessful request" do let(:http_response) do http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth") allow(http_response).to receive(:body) allow(http_response).to receive(:read_body) http_response end it "retries then throws an exception" do allow(rest).to receive(:sleep) expect { rest.request(:GET, url) }.to raise_error(Net::HTTPFatalError) count = Chef::Config[:http_retry_count] expect(log_stringio.string).to match(Regexp.escape("ERROR: Server returned error 500 for #{url}, retrying #{count}/#{count}")) end end end end # as JSON API requests context "when streaming downloads to a tempfile" do let!(:tempfile) { Tempfile.open("chef-rspec-rest_spec-line-@{__LINE__}--") } let(:request_mock) { {} } let(:http_response) do http_response = Net::HTTPSuccess.new("1.1", "200", "it-works") allow(http_response).to receive(:read_body) expect(http_response).not_to receive(:body) http_response["Content-Length"] = "0" # call set_content_length (in test), if otherwise http_response end def set_content_length content_length = 0 http_response.read_body do |chunk| content_length += chunk.bytesize end http_response["Content-Length"] = content_length.to_s end before do allow(Tempfile).to receive(:new).with("chef-rest").and_return(tempfile) allow(Net::HTTP::Get).to receive(:new).and_return(request_mock) end after do tempfile.close! end it " build a new HTTP GET request without the application/json accept header" do expected_headers = { "Accept" => "*/*", "X-Chef-Version" => Chef::VERSION, "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, "Host" => host_header, "X-REMOTE-REQUEST-ID" => request_id, "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION, } expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock) rest.streaming_request(url, {}) end it "build a new HTTP GET request with the X-Remote-Request-Id header" do expected_headers = { "Accept" => "*/*", "X-Chef-Version" => Chef::VERSION, "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE, "Host" => host_header, "X-REMOTE-REQUEST-ID" => request_id, "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION, } expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock) rest.streaming_request(url, {}) end it "returns a tempfile containing the streamed response body" do expect(rest.streaming_request(url, {})).to equal(tempfile) end it "writes the response body to a tempfile" do allow(http_response).to receive(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") set_content_length rest.streaming_request(url, {}) expect(IO.read(tempfile.path).chomp).to eq("realultimatepower") end it "closes the tempfile" do rest.streaming_request(url, {}) expect(tempfile).to be_closed end it "yields the tempfile containing the streamed response body and then unlinks it when given a block" do allow(http_response).to receive(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") set_content_length tempfile_path = nil rest.streaming_request(url, {}) do |tempfile| tempfile_path = tempfile.path expect(File.exist?(tempfile.path)).to be_truthy expect(IO.read(tempfile.path).chomp).to eq("realultimatepower") end expect(File.exist?(tempfile_path)).to be_falsey end it "does not raise a divide by zero exception if the content's actual size is 0" do http_response["Content-Length"] = "5" allow(http_response).to receive(:read_body).and_yield("") expect { rest.streaming_request(url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end it "does not raise a divide by zero exception when the Content-Length is 0" do http_response["Content-Length"] = "0" allow(http_response).to receive(:read_body).and_yield("ninja") expect { rest.streaming_request(url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end it "it raises an exception when the download is truncated" do http_response["Content-Length"] = (body.bytesize + 99).to_s allow(http_response).to receive(:read_body).and_yield("ninja") expect { rest.streaming_request(url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch) end it "fetches a file and yields the tempfile it is streamed to" do allow(http_response).to receive(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") set_content_length tempfile_path = nil rest.fetch("cookbooks/a_cookbook") do |tempfile| tempfile_path = tempfile.path expect(IO.read(tempfile.path).chomp).to eq("realultimatepower") end expect(File.exist?(tempfile_path)).to be_falsey end it "closes and unlinks the tempfile if there is an error while streaming the content to the tempfile" do path = tempfile.path expect(path).not_to be_nil allow(tempfile).to receive(:write).and_raise(IOError) rest.fetch("cookbooks/a_cookbook") { |tmpfile| "shouldn't get here" } expect(File.exists?(path)).to be_falsey end it "closes and unlinks the tempfile when the response is a redirect" do tempfile = double("A tempfile", :path => "/tmp/ragefist", :close => true, :binmode => true) expect(tempfile).to receive(:close!).at_least(1).times allow(Tempfile).to receive(:new).with("chef-rest").and_return(tempfile) redirect = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today") redirect.add_field("location", url.path) allow(redirect).to receive(:read_body) expect(http_client).to receive(:request).and_yield(redirect).and_return(redirect) expect(http_client).to receive(:request).and_yield(http_response).and_return(http_response) rest.fetch("cookbooks/a_cookbook") { |tmpfile| "shouldn't get here" } end it "passes the original block to the redirected request" do http_redirect = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today") http_redirect.add_field("location", "/that-thing-is-here-now") allow(http_redirect).to receive(:read_body) block_called = false allow(http_client).to receive(:request).and_yield(http_response).and_return(http_redirect, http_response) rest.fetch("cookbooks/a_cookbook") do |tmpfile| block_called = true end expect(block_called).to be_truthy end end end # when making REST requests context "when following redirects" do let(:rest) do Chef::REST.new(base_url) end before do Chef::Config[:node_name] = "webmonkey.example.com" Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem" end it "raises a RedirectLimitExceeded when redirected more than 10 times" do redirected = lambda { rest.follow_redirect { redirected.call } } expect { redirected.call }.to raise_error(Chef::Exceptions::RedirectLimitExceeded) end it "does not count redirects from previous calls against the redirect limit" do total_redirects = 0 redirected = lambda do rest.follow_redirect do total_redirects += 1 redirected.call unless total_redirects >= 9 end end expect { redirected.call }.not_to raise_error total_redirects = 0 expect { redirected.call }.not_to raise_error end it "does not sign the redirected request when sign_on_redirect is false" do rest.sign_on_redirect = false rest.follow_redirect { expect(rest.sign_requests?).to be_falsey } end it "resets sign_requests to the original value after following an unsigned redirect" do rest.sign_on_redirect = false expect(rest.sign_requests?).to be_truthy rest.follow_redirect { expect(rest.sign_requests?).to be_falsey } expect(rest.sign_requests?).to be_truthy end it "configures the redirect limit" do total_redirects = 0 redirected = lambda do rest.follow_redirect do total_redirects += 1 redirected.call unless total_redirects >= 9 end end expect { redirected.call }.not_to raise_error total_redirects = 0 rest.redirect_limit = 3 expect { redirected.call }.to raise_error(Chef::Exceptions::RedirectLimitExceeded) end end end chef-12.14.60/spec/unit/role_spec.rb000066400000000000000000000355361276456504500171150ustar00rootroot00000000000000# # Author:: Adam Jacob () # 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" require "chef/role" describe Chef::Role do before(:each) do allow(ChefConfig).to receive(:windows?) { false } @role = Chef::Role.new @role.name("ops_master") end it "has a name" do expect(@role.name("ops_master")).to eq("ops_master") end it "does not accept a name with spaces" do expect { @role.name "ops master" }.to raise_error(ArgumentError) end it "does not accept non-String objects for the name" do expect { @role.name({}) }.to raise_error(ArgumentError) end describe "when a run list is set" do before do @role.run_list(%w{ nginx recipe[ree] role[base]}) end it "returns the run list" do expect(@role.run_list).to eq(%w{ nginx recipe[ree] role[base]}) end describe "and per-environment run lists are set" do before do @role.name("base") @role.run_list(%w{ recipe[nagios::client] recipe[tims-acl::bork]}) @role.env_run_list["prod"] = Chef::RunList.new(*(@role.run_list.to_a << "recipe[prod-base]")) @role.env_run_list["dev"] = Chef::RunList.new end it "uses the default run list as *the* run_list" do expect(@role.run_list).to eq(Chef::RunList.new("recipe[nagios::client]", "recipe[tims-acl::bork]")) end it "gives the default run list as the when getting the _default run list" do expect(@role.run_list_for("_default")).to eq(@role.run_list) end it "gives an environment specific run list" do expect(@role.run_list_for("prod")).to eq(Chef::RunList.new("recipe[nagios::client]", "recipe[tims-acl::bork]", "recipe[prod-base]")) end it "gives the default run list when no run list exists for the given environment" do expect(@role.run_list_for("qa")).to eq(@role.run_list) end it "gives the environment specific run list even if it is empty" do expect(@role.run_list_for("dev")).to eq(Chef::RunList.new) end it "env_run_lists can only be set with _default run list in it" do long_exception_name = Chef::Exceptions::InvalidEnvironmentRunListSpecification expect { @role.env_run_lists({}) }.to raise_error(long_exception_name) end end describe "using the old #recipes API" do it "should let you set the recipe array" do expect(@role.recipes(%w{one two})).to eq(%w{one two}) end it "should let you return the recipe array" do @role.recipes(%w{one two}) expect(@role.recipes).to eq(%w{one two}) end it "should not list roles in the recipe array" do @role.run_list([ "one", "role[two]"]) expect(@role.recipes).to eq([ "recipe[one]", "role[two]" ]) end end end describe "default_attributes" do it "should let you set the default attributes hash explicitly" do expect(@role.default_attributes({ :one => "two" })).to eq({ :one => "two" }) end it "should let you return the default attributes hash" do @role.default_attributes({ :one => "two" }) expect(@role.default_attributes).to eq({ :one => "two" }) end it "should throw an ArgumentError if we aren't a kind of hash" do expect { @role.default_attributes(Array.new) }.to raise_error(ArgumentError) end end describe "override_attributes" do it "should let you set the override attributes hash explicitly" do expect(@role.override_attributes({ :one => "two" })).to eq({ :one => "two" }) end it "should let you return the override attributes hash" do @role.override_attributes({ :one => "two" }) expect(@role.override_attributes).to eq({ :one => "two" }) end it "should throw an ArgumentError if we aren't a kind of hash" do expect { @role.override_attributes(Array.new) }.to raise_error(ArgumentError) end end describe "update_from!" do before(:each) do @role.name("mars_volta") @role.description("Great band!") @role.run_list("one", "two", "role[a]") @role.default_attributes({ :el_groupo => "nuevo" }) @role.override_attributes({ :deloused => "in the comatorium" }) @example = Chef::Role.new @example.name("newname") @example.description("Really Great band!") @example.run_list("alpha", "bravo", "role[alpha]") @example.default_attributes({ :el_groupo => "nuevo dos" }) @example.override_attributes({ :deloused => "in the comatorium XOXO" }) end it "should update all fields except for name" do @role.update_from!(@example) expect(@role.name).to eq("mars_volta") expect(@role.description).to eq(@example.description) expect(@role.run_list).to eq(@example.run_list) expect(@role.default_attributes).to eq(@example.default_attributes) expect(@role.override_attributes).to eq(@example.override_attributes) end end describe "when serialized as JSON", :json => true do before(:each) do @role.name("mars_volta") @role.description("Great band!") @role.run_list("one", "two", "role[a]") @role.default_attributes({ :el_groupo => "nuevo" }) @role.override_attributes({ :deloused => "in the comatorium" }) @serialized_role = Chef::JSONCompat.to_json(@role) end it "should serialize to a json hash" do expect(Chef::JSONCompat.to_json(@role)).to match(/^\{.+\}$/) end it "includes the name in the JSON output" do expect(@serialized_role).to match(/"name":"mars_volta"/) end it "includes its description in the JSON" do expect(@serialized_role).to match(/"description":"Great band!"/) end it "should include 'default_attributes'" do expect(@serialized_role).to match(/"default_attributes":\{"el_groupo":"nuevo"\}/) end it "should include 'override_attributes'" do expect(@serialized_role).to match(/"override_attributes":\{"deloused":"in the comatorium"\}/) end it "should include 'run_list'" do #Activesupport messes with Chef json formatting #This test should pass with and without activesupport expect(@serialized_role).to match(/"run_list":\["recipe\[one\]","recipe\[two\]","role\[a\]"\]/) end describe "and it has per-environment run lists" do before do @role.env_run_lists("_default" => ["one", "two", "role[a]"], "production" => ["role[monitoring]", "role[auditing]", "role[apache]"], "dev" => ["role[nginx]"]) @serialized_role = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(@role), :create_additions => false) end it "includes the per-environment run lists" do #Activesupport messes with Chef json formatting #This test should pass with and without activesupport expect(@serialized_role["env_run_lists"]["production"]).to eq(["role[monitoring]", "role[auditing]", "role[apache]"]) expect(@serialized_role["env_run_lists"]["dev"]).to eq(["role[nginx]"]) end it "does not include the default environment in the per-environment run lists" do expect(@serialized_role["env_run_lists"]).not_to have_key("_default") end end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @role } end end describe "when created from JSON", :json => true do before(:each) do @role.name("mars_volta") @role.description("Great band!") @role.run_list("one", "two", "role[a]") @role.default_attributes({ "el_groupo" => "nuevo" }) @role.override_attributes({ "deloused" => "in the comatorium" }) @deserial = Chef::Role.from_hash(Chef::JSONCompat.parse(Chef::JSONCompat.to_json(@role))) end it "should deserialize to a Chef::Role object" do expect(@deserial).to be_a_kind_of(Chef::Role) end %w{ name description default_attributes override_attributes run_list }.each do |t| it "should preserves the '#{t}' attribute from the JSON object" do expect(@deserial.send(t.to_sym)).to eq(@role.send(t.to_sym)) end end end ROLE_DSL = <<-EOR name "ceiling_cat" description "like Aliens, but furry" EOR describe "when loading from disk" do before do default_cache_path = windows? ? 'C:\chef' : "/var/chef" allow(Chef::Config).to receive(:cache_path).and_return(default_cache_path) end it "should return a Chef::Role object from JSON" do expect(Dir).to receive(:glob).and_return(["#{Chef::Config[:role_path]}/memes", "#{Chef::Config[:role_path]}/memes/lolcat.json"]) file_path = File.join(Chef::Config[:role_path], "memes/lolcat.json") expect(File).to receive(:exists?).with(file_path).exactly(1).times.and_return(true) expect(IO).to receive(:read).with(file_path).and_return('{"name": "ceiling_cat", "json_class": "Chef::Role" }') expect(@role).to be_a_kind_of(Chef::Role) @role.class.from_disk("lolcat") end it "should return a Chef::Role object from a Ruby DSL" do expect(Dir).to receive(:glob).and_return(["#{Chef::Config[:role_path]}/memes", "#{Chef::Config[:role_path]}/memes/lolcat.rb"]) rb_path = File.join(Chef::Config[:role_path], "memes/lolcat.rb") expect(File).to receive(:exists?).with(rb_path).exactly(2).times.and_return(true) expect(File).to receive(:readable?).with(rb_path).exactly(1).times.and_return(true) expect(IO).to receive(:read).with(rb_path).and_return(ROLE_DSL) expect(@role).to be_a_kind_of(Chef::Role) @role.class.from_disk("lolcat") end it "should prefer a Chef::Role Object from JSON over one from a Ruby DSL" do expect(Dir).to receive(:glob).and_return(["#{Chef::Config[:role_path]}/memes", "#{Chef::Config[:role_path]}/memes/lolcat.json", "#{Chef::Config[:role_path]}/memes/lolcat.rb"]) js_path = File.join(Chef::Config[:role_path], "memes/lolcat.json") rb_path = File.join(Chef::Config[:role_path], "memes/lolcat.rb") expect(File).to receive(:exists?).with(js_path).exactly(1).times.and_return(true) expect(File).not_to receive(:exists?).with(rb_path) expect(IO).to receive(:read).with(js_path).and_return('{"name": "ceiling_cat", "json_class": "Chef::Role" }') expect(@role).to be_a_kind_of(Chef::Role) @role.class.from_disk("lolcat") end it "should raise an exception if the file does not exist" do expect(Dir).to receive(:glob).and_return(["#{Chef::Config[:role_path]}/meme.rb"]) expect(File).not_to receive(:exists?) expect { @role.class.from_disk("lolcat") }.to raise_error(Chef::Exceptions::RoleNotFound) end it "should raise an exception if two files exist with the same name" do expect(Dir).to receive(:glob).and_return(["#{Chef::Config[:role_path]}/memes/lolcat.rb", "#{Chef::Config[:role_path]}/lolcat.rb"]) expect(File).not_to receive(:exists?) expect { @role.class.from_disk("lolcat") }.to raise_error(Chef::Exceptions::DuplicateRole) end it "should not raise an exception if two files exist with a similar name" do expect(Dir).to receive(:glob).and_return(["#{Chef::Config[:role_path]}/memes/lolcat.rb", "#{Chef::Config[:role_path]}/super_lolcat.rb"]) expect(File).to receive(:exists?).with("#{Chef::Config[:role_path]}/memes/lolcat.rb").and_return(true) allow_any_instance_of(Chef::Role).to receive(:from_file).with("#{Chef::Config[:role_path]}/memes/lolcat.rb") expect { @role.class.from_disk("lolcat") }.not_to raise_error end end describe "when loading from disk and role_path is an array" do before(:each) do Chef::Config[:role_path] = ["/path1", "/path/path2"] end it "should return a Chef::Role object from JSON" do expect(Dir).to receive(:glob).with(File.join("/path1", "**", "**")).exactly(1).times.and_return(["/path1/lolcat.json"]) expect(File).to receive(:exists?).with("/path1/lolcat.json").exactly(1).times.and_return(true) expect(IO).to receive(:read).with("/path1/lolcat.json").and_return('{"name": "ceiling_cat", "json_class": "Chef::Role" }') expect(@role).to be_a_kind_of(Chef::Role) @role.class.from_disk("lolcat") end it "should return a Chef::Role object from JSON when role is in the second path" do expect(Dir).to receive(:glob).with(File.join("/path1", "**", "**")).exactly(1).times.and_return([]) expect(Dir).to receive(:glob).with(File.join("/path/path2", "**", "**")).exactly(1).times.and_return(["/path/path2/lolcat.json"]) expect(File).to receive(:exists?).with("/path/path2/lolcat.json").exactly(1).times.and_return(true) expect(IO).to receive(:read).with("/path/path2/lolcat.json").and_return('{"name": "ceiling_cat", "json_class": "Chef::Role" }') expect(@role).to be_a_kind_of(Chef::Role) @role.class.from_disk("lolcat") end it "should return a Chef::Role object from a Ruby DSL" do expect(Dir).to receive(:glob).with(File.join("/path1", "**", "**")).exactly(1).times.and_return(["/path1/lolcat.rb"]) expect(File).to receive(:exists?).with("/path1/lolcat.rb").exactly(2).times.and_return(true) expect(File).to receive(:readable?).with("/path1/lolcat.rb").and_return(true) expect(IO).to receive(:read).with("/path1/lolcat.rb").exactly(1).times.and_return(ROLE_DSL) expect(@role).to be_a_kind_of(Chef::Role) @role.class.from_disk("lolcat") end it "should return a Chef::Role object from a Ruby DSL when role is in the second path" do expect(Dir).to receive(:glob).with(File.join("/path1", "**", "**")).exactly(1).times.and_return([]) expect(Dir).to receive(:glob).with(File.join("/path/path2", "**", "**")).exactly(1).times.and_return(["/path/path2/lolcat.rb"]) expect(File).to receive(:exists?).with("/path/path2/lolcat.rb").exactly(2).times.and_return(true) expect(File).to receive(:readable?).with("/path/path2/lolcat.rb").and_return(true) expect(IO).to receive(:read).with("/path/path2/lolcat.rb").exactly(1).times.and_return(ROLE_DSL) expect(@role).to be_a_kind_of(Chef::Role) @role.class.from_disk("lolcat") end it "should raise an exception if the file does not exist" do expect(Dir).to receive(:glob).with(File.join("/path1", "**", "**")).exactly(1).times.and_return([]) expect(Dir).to receive(:glob).with(File.join("/path/path2", "**", "**")).exactly(1).times.and_return([]) expect { @role.class.from_disk("lolcat") }.to raise_error(Chef::Exceptions::RoleNotFound) end end end chef-12.14.60/spec/unit/run_context/000077500000000000000000000000001276456504500171515ustar00rootroot00000000000000chef-12.14.60/spec/unit/run_context/child_run_context_spec.rb000066400000000000000000000132011276456504500242200ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tim Hinderliter () # Author:: Christopher Walters () # 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" require "support/lib/library_load_order" describe Chef::RunContext::ChildRunContext do context "with a run context with stuff in it" do let(:chef_repo_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "cookbooks")) } let(:cookbook_collection) do cl = Chef::CookbookLoader.new(chef_repo_path) cl.load_cookbooks Chef::CookbookCollection.new(cl) end let(:node) do node = Chef::Node.new node.run_list << "test" << "test::one" << "test::two" node end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) } context "and a child run context" do let(:child) { run_context.create_child } it "parent_run_context is set to the parent" do expect(child.parent_run_context).to eq run_context end it "audits is not the same as the parent" do expect(child.audits.object_id).not_to eq run_context.audits.object_id child.audits["hi"] = "lo" expect(child.audits["hi"]).to eq("lo") expect(run_context.audits["hi"]).not_to eq("lo") end it "resource_collection is not the same as the parent" do expect(child.resource_collection.object_id).not_to eq run_context.resource_collection.object_id f = Chef::Resource::File.new("hi", child) child.resource_collection.insert(f) expect(child.resource_collection).to include f expect(run_context.resource_collection).not_to include f end it "immediate_notification_collection is not the same as the parent" do expect(child.immediate_notification_collection.object_id).not_to eq run_context.immediate_notification_collection.object_id src = Chef::Resource::File.new("hi", child) dest = Chef::Resource::File.new("argh", child) notification = Chef::Resource::Notification.new(dest, :create, src) child.notifies_immediately(notification) expect(child.immediate_notification_collection["file[hi]"]).to eq([notification]) expect(run_context.immediate_notification_collection["file[hi]"]).not_to eq([notification]) end it "immediate_notifications is not the same as the parent" do src = Chef::Resource::File.new("hi", child) dest = Chef::Resource::File.new("argh", child) notification = Chef::Resource::Notification.new(dest, :create, src) child.notifies_immediately(notification) expect(child.immediate_notifications(src)).to eq([notification]) expect(run_context.immediate_notifications(src)).not_to eq([notification]) end it "delayed_notification_collection is not the same as the parent" do expect(child.delayed_notification_collection.object_id).not_to eq run_context.delayed_notification_collection.object_id src = Chef::Resource::File.new("hi", child) dest = Chef::Resource::File.new("argh", child) notification = Chef::Resource::Notification.new(dest, :create, src) child.notifies_delayed(notification) expect(child.delayed_notification_collection["file[hi]"]).to eq([notification]) expect(run_context.delayed_notification_collection["file[hi]"]).not_to eq([notification]) end it "delayed_notifications is not the same as the parent" do src = Chef::Resource::File.new("hi", child) dest = Chef::Resource::File.new("argh", child) notification = Chef::Resource::Notification.new(dest, :create, src) child.notifies_delayed(notification) expect(child.delayed_notifications(src)).to eq([notification]) expect(run_context.delayed_notifications(src)).not_to eq([notification]) end it "create_child creates a child-of-child" do c = child.create_child expect(c.parent_run_context).to eq child end context "after load('include::default')" do before do run_list = Chef::RunList.new("include::default").expand("_default") # TODO not sure why we had to do this to get everything to work ... node.automatic_attrs[:recipes] = [] child.load(run_list) end it "load_recipe loads into the child" do expect(child.resource_collection).to be_empty child.load_recipe("include::includee") expect(child.resource_collection).not_to be_empty end it "include_recipe loads into the child" do expect(child.resource_collection).to be_empty child.include_recipe("include::includee") expect(child.resource_collection).not_to be_empty end it "load_recipe_file loads into the child" do expect(child.resource_collection).to be_empty child.load_recipe_file(File.expand_path("include/recipes/includee.rb", chef_repo_path)) expect(child.resource_collection).not_to be_empty end end end end end chef-12.14.60/spec/unit/run_context/cookbook_compiler_spec.rb000066400000000000000000000217531276456504500242200ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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 "support/lib/library_load_order" # These tests rely on fixture data in spec/data/run_context/cookbooks. # # Dependencies (circular or not) are specified by `depends` directives in the # metadata of these cookbooks. # # Side effects used to verify the behavior are implemented as code in the various file types. # describe Chef::RunContext::CookbookCompiler do let(:node) { Chef::Node.new } let(:events) { Chef::EventDispatch::Dispatcher.new } let(:cookbook_loader) do cl = Chef::CookbookLoader.new(chef_repo_path) cl.load_cookbooks cl end let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) } let(:chef_repo_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "cookbooks")) } let(:cookbook_collection) { Chef::CookbookCollection.new(cookbook_loader) } # Lazy evaluation of `expansion` here is used to mutate the run list before expanding it let(:run_list_expansion) { node.run_list.expand("_default") } let(:compiler) do Chef::RunContext::CookbookCompiler.new(run_context, run_list_expansion, events) end describe "loading attribute files" do # Attribute files in the fixture data will append their # "cookbook_name::attribute_file_name" to the node's `:attr_load_order` # attribute when loaded. it "loads default.rb first, then other files in sort order" do node.run_list("dependency1::default") compiler.compile_attributes expect(node[:attr_load_order]).to eq(["dependency1::default", "dependency1::aa_first", "dependency1::zz_last"]) end it "loads dependencies before loading the depending cookbook's attributes" do # Also make sure that attributes aren't loaded twice if we have two # recipes from the same cookbook in the run list node.run_list("test-with-deps::default", "test-with-deps::server") compiler.compile_attributes # dependencies are stored in a hash so therefore unordered, but they should be loaded in sort order expect(node[:attr_load_order]).to eq(["dependency1::default", "dependency1::aa_first", "dependency1::zz_last", "dependency2::default", "test-with-deps::default"]) end it "does not follow infinite dependency loops" do node.run_list("test-with-circular-deps::default") # Circular deps should not cause infinite loops compiler.compile_attributes expect(node[:attr_load_order]).to eq(["circular-dep2::default", "circular-dep1::default", "test-with-circular-deps::default"]) end it "loads attributes from cookbooks that don't have a default.rb attribute file" do node.run_list("no-default-attr::default.rb") compiler.compile_attributes expect(node[:attr_load_order]).to eq(["no-default-attr::server"]) end end describe "loading libraries" do before do LibraryLoadOrder.reset! end # One big test for everything. Individual behaviors are tested by the attribute code above. it "loads libraries in run list order" do node.run_list("test-with-deps::default", "test-with-circular-deps::default") compiler.compile_libraries expect(LibraryLoadOrder.load_order).to eq(["dependency1", "dependency2", "test-with-deps", "circular-dep2", "circular-dep1", "test-with-circular-deps"]) end end describe "loading LWRPs" do before do LibraryLoadOrder.reset! end # One big test for everything. Individual behaviors are tested by the attribute code above. it "loads LWRPs in run list order" do node.run_list("test-with-deps::default", "test-with-circular-deps::default") compiler.compile_lwrps expect(LibraryLoadOrder.load_order).to eq(["dependency1-provider", "dependency1-resource", "dependency2-provider", "dependency2-resource", "test-with-deps-provider", "test-with-deps-resource", "circular-dep2-provider", "circular-dep2-resource", "circular-dep1-provider", "circular-dep1-resource", "test-with-circular-deps-provider", "test-with-circular-deps-resource"]) end end describe "loading resource definitions" do before do LibraryLoadOrder.reset! end # One big test for all load order concerns. Individual behaviors are tested # by the attribute code above. it "loads resource definitions in run list order" do node.run_list("test-with-deps::default", "test-with-circular-deps::default") compiler.compile_resource_definitions expect(LibraryLoadOrder.load_order).to eq(["dependency1-definition", "dependency2-definition", "test-with-deps-definition", "circular-dep2-definition", "circular-dep1-definition", "test-with-circular-deps-definition"]) end end describe "loading recipes" do # Additional tests for this behavior are in RunContext's tests describe "event dispatch" do let(:recipe) { "dependency1::default" } let(:recipe_path) do File.expand_path("../../../data/run_context/cookbooks/dependency1/recipes/default.rb", __FILE__).tap do |path| path.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR end end before do node.run_list(recipe) end subject { compiler.compile_recipes } it "dispatches normally" do allow(run_context).to receive(:load_recipe) expect(events).to receive(:recipe_load_start).with(1) expect(events).to receive(:recipe_file_loaded).with(recipe_path, "dependency1::default") expect(events).to receive(:recipe_load_complete).with(no_args) subject end it "dispatches when a recipe is not found" do exc = Chef::Exceptions::RecipeNotFound.new allow(run_context).to receive(:load_recipe).and_raise(exc) expect(events).to receive(:recipe_load_start).with(1) expect(events).to_not receive(:recipe_file_loaded) expect(events).to receive(:recipe_not_found).with(exc) expect(events).to_not receive(:recipe_load_complete) expect { subject }.to raise_error(exc) end it "dispatches when a recipe has an error" do exc = ArgumentError.new allow(run_context).to receive(:load_recipe).and_raise(exc) expect(events).to receive(:recipe_load_start).with(1) expect(events).to_not receive(:recipe_file_loaded) expect(events).to receive(:recipe_file_load_failed).with(recipe_path, exc, "dependency1::default") expect(events).to_not receive(:recipe_load_complete) expect { subject }.to raise_error(exc) end end end describe "listing cookbook order" do it "should return an array of cookbook names as symbols without duplicates" do node.run_list("test-with-circular-deps::default", "circular-dep1::default", "circular-dep2::default") expect(compiler.cookbook_order).to eq([:"circular-dep2", :"circular-dep1", :"test-with-circular-deps"]) end it "determines if a cookbook is in the list of cookbooks reachable by dependency" do node.run_list("test-with-deps::default", "test-with-deps::server") expect(compiler.cookbook_order).to eq([:dependency1, :dependency2, :"test-with-deps"]) expect(compiler.unreachable_cookbook?(:dependency1)).to be_falsey expect(compiler.unreachable_cookbook?(:dependency2)).to be_falsey expect(compiler.unreachable_cookbook?(:'test-with-deps')).to be_falsey expect(compiler.unreachable_cookbook?(:'circular-dep1')).to be_truthy expect(compiler.unreachable_cookbook?(:'circular-dep2')).to be_truthy end end end chef-12.14.60/spec/unit/run_context_spec.rb000066400000000000000000000203501276456504500205100ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Tim Hinderliter () # Author:: Christopher Walters () # 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" require "support/lib/library_load_order" describe Chef::RunContext do let(:chef_repo_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "cookbooks")) } let(:cookbook_collection) do cl = Chef::CookbookLoader.new(chef_repo_path) cl.load_cookbooks Chef::CookbookCollection.new(cl) end let(:node) do node = Chef::Node.new node.run_list << "test" << "test::one" << "test::two" node end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) } before(:each) do @original_log_level = Chef::Log.level Chef::Log.level = :debug end after(:each) do Chef::Log.level = @original_log_level end it "has a cookbook collection" do expect(run_context.cookbook_collection).to eq(cookbook_collection) end it "has a node" do expect(run_context.node).to eq(node) end it "loads up node[:cookbooks]" do expect(run_context.node[:cookbooks]).to eql( { "circular-dep1" => { "version" => "0.0.0", }, "circular-dep2" => { "version" => "0.0.0", }, "dependency1" => { "version" => "0.0.0", }, "dependency2" => { "version" => "0.0.0", }, "include" => { "version" => "0.0.0", }, "no-default-attr" => { "version" => "0.0.0", }, "test" => { "version" => "0.0.0", }, "test-with-circular-deps" => { "version" => "0.0.0", }, "test-with-deps" => { "version" => "0.0.0", }, } ) end it "has a nil parent_run_context" do expect(run_context.parent_run_context).to be_nil end describe "loading cookbooks for a run list" do before do # Each invocation reloads LWRPs, which triggers constant redefinition # warnings. In real usage this isn't an issue because of fork mode. if Chef::Provider.const_defined?(:TestProvider) Chef::Provider.send(:remove_const, :TestProvider) end node.run_list << "test" << "test::one" << "test::two" expect(node).to receive(:loaded_recipe).with(:test, "default") expect(node).to receive(:loaded_recipe).with(:test, "one") expect(node).to receive(:loaded_recipe).with(:test, "two") run_context.load(node.run_list.expand("_default")) end it "should load all the definitions in the cookbooks for this node" do expect(run_context.definitions).to have_key(:new_cat) expect(run_context.definitions).to have_key(:new_badger) expect(run_context.definitions).to have_key(:new_dog) end it "should load all the recipes specified for this node" do expect(run_context.resource_collection[0].to_s).to eq("cat[einstein]") expect(run_context.resource_collection[1].to_s).to eq("cat[loulou]") expect(run_context.resource_collection[2].to_s).to eq("cat[birthday]") expect(run_context.resource_collection[3].to_s).to eq("cat[peanut]") expect(run_context.resource_collection[4].to_s).to eq("cat[fat peanut]") end it "loads all the attribute files in the cookbook collection" do expect(run_context.loaded_fully_qualified_attribute?("test", "george")).to be_truthy expect(node[:george]).to eq("washington") end it "registers attributes files as loaded so they won't be reloaded" do # This test unfortunately is pretty tightly intertwined with the # implementation of how nodes load attribute files, but is the only # convenient way to test this behavior. expect(node).not_to receive(:from_file) node.include_attribute("test::george") end it "raises an error when attempting to include_recipe from a cookbook not reachable by run list or dependencies" do expect(node).to receive(:loaded_recipe).with(:ancient, "aliens") expect do run_context.include_recipe("ancient::aliens") # In CHEF-5120, this becomes a Chef::Exceptions::MissingCookbookDependency error: end.to raise_error(Chef::Exceptions::CookbookNotFound) end it "raises an error on a recipe with a leading :: with no current_cookbook" do expect do run_context.include_recipe("::aliens") end.to raise_error(RuntimeError) end end describe "querying the contents of cookbooks" do let(:chef_repo_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) } let(:node) do node = Chef::Node.new node.normal[:platform] = "ubuntu" node.normal[:platform_version] = "13.04" node.name("testing") node end it "queries whether a given cookbook has a specific template" do expect(run_context).to have_template_in_cookbook("openldap", "test.erb") expect(run_context).not_to have_template_in_cookbook("openldap", "missing.erb") end it "errors when querying for a template in a not-available cookbook" do expect do run_context.has_template_in_cookbook?("no-such-cookbook", "foo.erb") end.to raise_error(Chef::Exceptions::CookbookNotFound) end it "queries whether a given cookbook has a specific cookbook_file" do expect(run_context).to have_cookbook_file_in_cookbook("java", "java.response") expect(run_context).not_to have_cookbook_file_in_cookbook("java", "missing.txt") end it "errors when querying for a cookbook_file in a not-available cookbook" do expect do run_context.has_cookbook_file_in_cookbook?("no-such-cookbook", "foo.txt") end.to raise_error(Chef::Exceptions::CookbookNotFound) end end describe "handling reboot requests" do let(:expected) do { :reason => "spec tests require a reboot" } end it "stores and deletes the reboot request" do run_context.request_reboot(expected) expect(run_context.reboot_info).to eq(expected) expect(run_context.reboot_requested?).to be_truthy run_context.cancel_reboot expect(run_context.reboot_info).to eq({}) expect(run_context.reboot_requested?).to be_falsey end end describe "notifications" do let(:notification) { Chef::Resource::Notification.new(nil, nil, notifying_resource) } shared_context "notifying resource is a Chef::Resource" do let(:notifying_resource) { Chef::Resource.new("gerbil") } it "should be keyed off the resource name" do run_context.send(setter, notification) expect(run_context.send(getter, notifying_resource)).to eq([notification]) end end shared_context "notifying resource is a subclass of Chef::Resource" do let(:declared_type) { :alpaca } let(:notifying_resource) do r = Class.new(Chef::Resource).new("guinea pig") r.declared_type = declared_type r end it "should be keyed off the resource declared key" do run_context.send(setter, notification) expect(run_context.send(getter, notifying_resource)).to eq([notification]) end end describe "of the immediate kind" do let(:setter) { :notifies_immediately } let(:getter) { :immediate_notifications } include_context "notifying resource is a Chef::Resource" include_context "notifying resource is a subclass of Chef::Resource" end describe "of the delayed kind" do let(:setter) { :notifies_delayed } let(:getter) { :delayed_notifications } include_context "notifying resource is a Chef::Resource" include_context "notifying resource is a subclass of Chef::Resource" end end end chef-12.14.60/spec/unit/run_list/000077500000000000000000000000001276456504500164405ustar00rootroot00000000000000chef-12.14.60/spec/unit/run_list/run_list_expansion_spec.rb000066400000000000000000000114761276456504500237330ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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::RunList::RunListExpansion do before do @run_list = Chef::RunList.new @run_list << "recipe[lobster::mastercookbook@0.1.0]" << "role[rage]" << "recipe[fist@0.1]" @expansion = Chef::RunList::RunListExpansion.new("_default", @run_list.run_list_items) end describe "before expanding the run list" do it "has an array of run list items" do expect(@expansion.run_list_items).to eq(@run_list.run_list_items) end it "has default_attrs" do expect(@expansion.default_attrs).to eq(Mash.new) end it "has override attrs" do expect(@expansion.override_attrs).to eq(Mash.new) end it "it has an empty list of recipes" do expect(@expansion.recipes.size).to eq(0) end it "has not applied its roles" do expect(@expansion.applied_role?("rage")).to be_falsey end end describe "after applying a role with environment-specific run lists" do before do @rage_role = Chef::Role.new.tap do |r| r.name("rage") r.env_run_lists("_default" => [], "prod" => ["recipe[prod-only]"]) end @expansion = Chef::RunList::RunListExpansion.new("prod", @run_list.run_list_items) expect(@expansion).to receive(:fetch_role).and_return(@rage_role) @expansion.expand end it "has the correct list of recipes for the given environment" do expect(@expansion.recipes).to eq(["lobster::mastercookbook", "prod-only", "fist"]) end end describe "after applying a role" do before do allow(@expansion).to receive(:fetch_role).and_return(Chef::Role.new) @expansion.inflate_role("rage", "role[base]") end it "tracks the applied role" do expect(@expansion.applied_role?("rage")).to be_truthy end it "does not inflate the role again" do expect(@expansion.inflate_role("rage", "role[base]")).to be_falsey end end describe "after expanding a run list" do before do @first_role = Chef::Role.new @first_role.name("rage") @first_role.run_list("role[mollusk]") @first_role.default_attributes({ "foo" => "bar" }) @first_role.override_attributes({ "baz" => "qux" }) @second_role = Chef::Role.new @second_role.name("rage") @second_role.run_list("recipe[crabrevenge]") @second_role.default_attributes({ "foo" => "boo" }) @second_role.override_attributes({ "baz" => "bux" }) allow(@expansion).to receive(:fetch_role).and_return(@first_role, @second_role) @expansion.expand @json = '{"id":"_default","run_list":[{"type":"recipe","name":"lobster::mastercookbook","version":"0.1.0",' .concat( '"skipped":false},{"type":"role","name":"rage","children":[{"type":"role","name":"mollusk","children":[],"missing":null,' .concat( '"error":null,"skipped":null},{"type":"recipe","name":"crabrevenge","version":null,"skipped":false}],"missing":null,' .concat( '"error":null,"skipped":null},{"type":"recipe","name":"fist","version":"0.1","skipped":false}]}'))) end it "produces json tree upon tracing expansion" do json_run_list = @expansion.to_json expect(json_run_list).to eq(@json) end it "has the ordered list of recipes" do expect(@expansion.recipes).to eq(["lobster::mastercookbook", "crabrevenge", "fist"]) end it "has the merged attributes from the roles with outer roles overriding inner" do expect(@expansion.default_attrs).to eq({ "foo" => "bar" }) expect(@expansion.override_attrs).to eq({ "baz" => "qux" }) end it "has the list of all roles applied" do # this is the correct order, but 1.8 hash order is not stable expect(@expansion.roles).to match_array(%w{rage mollusk}) end end describe "after expanding a run list with a non existent role" do before do allow(@expansion).to receive(:fetch_role) { @expansion.role_not_found("crabrevenge", "role[base]") } @expansion.expand end it "is invalid" do expect(@expansion).to be_invalid expect(@expansion.errors?).to be_truthy # aliases end it "has a list of invalid role names" do expect(@expansion.errors).to include("crabrevenge") end end end chef-12.14.60/spec/unit/run_list/run_list_item_spec.rb000066400000000000000000000104231276456504500226540ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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::RunList::RunListItem do describe "when creating from a Hash" do it "raises an exception when the hash doesn't have a :type key" do expect { Chef::RunList::RunListItem.new(:name => "tatft") }.to raise_error(ArgumentError) end it "raises an exception when the hash doesn't have an :name key" do expect { Chef::RunList::RunListItem.new(:type => "R") }.to raise_error(ArgumentError) end it "sets the name and type as given in the hash" do item = Chef::RunList::RunListItem.new(:type => "fuuu", :name => "uuuu") expect(item.to_s).to eq("fuuu[uuuu]") end end describe "when creating an item from a string" do it "parses a qualified recipe" do item = Chef::RunList::RunListItem.new("recipe[rage]") expect(item).to be_a_recipe expect(item).not_to be_a_role expect(item.to_s).to eq("recipe[rage]") expect(item.name).to eq("rage") end it "parses a qualified recipe with a version" do item = Chef::RunList::RunListItem.new("recipe[rage@0.1.0]") expect(item).to be_a_recipe expect(item).not_to be_a_role expect(item.to_s).to eq("recipe[rage@0.1.0]") expect(item.name).to eq("rage") expect(item.version).to eq("0.1.0") end it "parses a qualified role" do item = Chef::RunList::RunListItem.new("role[fist]") expect(item).to be_a_role expect(item).not_to be_a_recipe expect(item.to_s).to eq("role[fist]") expect(item.name).to eq("fist") end it "parses an unqualified recipe" do item = Chef::RunList::RunListItem.new("lobster") expect(item).to be_a_recipe expect(item).not_to be_a_role expect(item.to_s).to eq("recipe[lobster]") expect(item.name).to eq("lobster") end it "raises an exception when the string has typo on the type part" do expect { Chef::RunList::RunListItem.new("Recipe[lobster]") }.to raise_error(ArgumentError) end it "raises an exception when the string has extra space between the type and the name" do expect { Chef::RunList::RunListItem.new("recipe [lobster]") }.to raise_error(ArgumentError) end it "raises an exception when the string does not close the bracket" do expect { Chef::RunList::RunListItem.new("recipe[lobster") }.to raise_error(ArgumentError) end end describe "comparing to other run list items" do it "is equal to another run list item that has the same name and type" do item1 = Chef::RunList::RunListItem.new("recipe[lrf]") item2 = Chef::RunList::RunListItem.new("recipe[lrf]") expect(item1).to eq(item2) end it "is not equal to another run list item with the same name and different type" do item1 = Chef::RunList::RunListItem.new("recipe[lrf]") item2 = Chef::RunList::RunListItem.new("role[lrf]") expect(item1).not_to eq(item2) end it "is not equal to another run list item with the same type and different name" do item1 = Chef::RunList::RunListItem.new("recipe[lrf]") item2 = Chef::RunList::RunListItem.new("recipe[lobsterragefist]") expect(item1).not_to eq(item2) end it "is not equal to another run list item with the same name and type but different version" do item1 = Chef::RunList::RunListItem.new("recipe[lrf,0.1.0]") item2 = Chef::RunList::RunListItem.new("recipe[lrf,0.2.0]") expect(item1).not_to eq(item2) end end describe "comparing to strings" do it "is equal to a string if that string matches its to_s representation" do expect(Chef::RunList::RunListItem.new("recipe[lrf]")).to eq("recipe[lrf]") end end end chef-12.14.60/spec/unit/run_list/versioned_recipe_list_spec.rb000066400000000000000000000131021276456504500243540ustar00rootroot00000000000000# # Author:: Stephen Delano () # Copyright:: Copyright 2010-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::RunList::VersionedRecipeList do describe "initialize" do it "should create an empty array" do l = Chef::RunList::VersionedRecipeList.new expect(l).to eq([]) end end let(:list) { described_class.new } let(:versioned_recipes) { [] } let(:recipes) { [] } before do recipes.each { |r| list << r } versioned_recipes.each { |r| list.add_recipe r[:name], r[:version] } end describe "add_recipe" do let(:recipes) { %w{ apt god apache2 } } it "should append the recipe to the end of the list" do list.add_recipe "rails" expect(list).to eq(%w{apt god apache2 rails}) end it "should not duplicate entries" do list.add_recipe "apt" expect(list).to eq(%w{apt god apache2}) end it "should allow you to specify a version" do list.add_recipe "rails", "1.0.0" expect(list).to eq(%w{apt god apache2 rails}) expect(list.with_versions).to include({ :name => "rails", :version => "1.0.0" }) end it "should allow you to specify a version for a recipe that already exists" do list.add_recipe "apt", "1.2.3" expect(list).to eq(%w{apt god apache2}) expect(list.with_versions).to include({ :name => "apt", :version => "1.2.3" }) end it "should allow you to specify the same version of a recipe twice" do list.add_recipe "rails", "1.0.0" list.add_recipe "rails", "1.0.0" expect(list.with_versions).to include({ :name => "rails", :version => "1.0.0" }) end it "should allow you to spcify no version, even when a version already exists" do list.add_recipe "rails", "1.0.0" list.add_recipe "rails" expect(list.with_versions).to include({ :name => "rails", :version => "1.0.0" }) end it "should not allow multiple versions of the same recipe" do list.add_recipe "rails", "1.0.0" expect { list.add_recipe "rails", "0.1.0" }.to raise_error Chef::Exceptions::CookbookVersionConflict end end describe "with_versions" do let(:versioned_recipes) do [ { :name => "apt", :version => "1.0.0" }, { :name => "god", :version => nil }, { :name => "apache2", :version => "0.0.1" }, ] end it "should return an array of hashes with :name and :version" do expect(list.with_versions).to eq(versioned_recipes) end it "should retain the same order as the version-less list" do with_versions = list.with_versions list.each_with_index do |item, index| expect(with_versions[index][:name]).to eq(item) end end end describe "with_version_constraints" do let(:versioned_recipes) do [ { :name => "apt", :version => "~> 1.2.0" }, { :name => "god", :version => nil }, { :name => "apache2", :version => "0.0.1" }, ] end it "should return an array of hashes with :name and :version_constraint" do list.with_version_constraints.each_with_index do |recipe_spec, i| expected_recipe = versioned_recipes[i] expect(recipe_spec[:name]).to eq(expected_recipe[:name]) expect(recipe_spec[:version_constraint]).to eq(Chef::VersionConstraint.new(expected_recipe[:version])) end end end describe "with_fully_qualified_names_and_version_constraints" do let(:fq_names) { list.with_fully_qualified_names_and_version_constraints } context "with bare cookbook names" do let(:recipes) { %w{ apache2 } } it "gives $cookbook_name::default" do expect(fq_names).to eq( %w{ apache2::default } ) end end context "with qualified recipe names but no versions" do let(:recipes) { %w{ mysql::server } } it "returns the qualified recipe names" do expect(fq_names).to eq( %w{ mysql::server } ) end end context "with unqualified names that have version constraints" do let(:versioned_recipes) do [ { :name => "apt", :version => "~> 1.2.0" }, ] end it "gives qualified names with their versions" do expect(fq_names).to eq([ "apt::default@~> 1.2.0" ]) end it "does not mutate the recipe name" do expect(fq_names).to eq([ "apt::default@~> 1.2.0" ]) expect(list).to eq( [ "apt" ] ) end end context "with fully qualified names that have version constraints" do let(:versioned_recipes) do [ { :name => "apt::cacher", :version => "~> 1.2.0" }, ] end it "gives qualified names with their versions" do expect(fq_names).to eq([ "apt::cacher@~> 1.2.0" ]) end it "does not mutate the recipe name" do expect(fq_names).to eq([ "apt::cacher@~> 1.2.0" ]) expect(list).to eq( [ "apt::cacher" ] ) end end end context "with duplicated names", chef: ">= 13" do it "should fail in Chef 13" do expect(list).to_not respond_to(:with_duplicate_names) end end end chef-12.14.60/spec/unit/run_list_spec.rb000066400000000000000000000247011276456504500200030ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Seth Falcon () # Author:: Christopher Walters () # 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" require "chef/version_class" require "chef/version_constraint" describe Chef::RunList do before(:each) do @run_list = Chef::RunList.new end describe "<<" do it "should add a recipe to the run list and recipe list with the fully qualified name" do @run_list << "recipe[needy]" expect(@run_list).to include("recipe[needy]") expect(@run_list.recipes).to include("needy") end it "should add a role to the run list and role list with the fully qualified name" do @run_list << "role[woot]" expect(@run_list).to include("role[woot]") expect(@run_list.roles).to include("woot") end it "should accept recipes that are unqualified" do @run_list << "needy" expect(@run_list).to include("recipe[needy]") expect(@run_list.recipes.include?("needy")).to eq(true) end it "should not allow duplicates" do @run_list << "needy" @run_list << "needy" expect(@run_list.run_list.length).to eq(1) expect(@run_list.recipes.length).to eq(1) end it "should allow two versions of a recipe" do @run_list << "recipe[needy@0.2.0]" @run_list << "recipe[needy@0.1.0]" expect(@run_list.run_list.length).to eq(2) expect(@run_list.recipes.length).to eq(2) expect(@run_list.recipes.include?("needy")).to eq(true) end it "should not allow duplicate versions of a recipe" do @run_list << "recipe[needy@0.2.0]" @run_list << "recipe[needy@0.2.0]" expect(@run_list.run_list.length).to eq(1) expect(@run_list.recipes.length).to eq(1) end end describe "add" do # Testing only the basic functionality here # since full behavior is tested above. it "should add a recipe to the run_list" do @run_list.add "recipe[needy]" expect(@run_list).to include("recipe[needy]") end it "should add a role to the run_list" do @run_list.add "role[needy]" expect(@run_list).to include("role[needy]") end end describe "==" do it "should believe two RunLists are equal if they have the same members" do @run_list << "foo" r = Chef::RunList.new r << "foo" expect(@run_list).to eq(r) end it "should believe a RunList is equal to an array named after it's members" do @run_list << "foo" @run_list << "baz" expect(@run_list).to eq(%w{foo baz}) end end describe "empty?" do it "should be emtpy if the run list has no members" do expect(@run_list.empty?).to eq(true) end it "should not be empty if the run list has members" do @run_list << "chromeo" expect(@run_list.empty?).to eq(false) end end describe "[]" do it "should let you look up a member in the run list by position" do @run_list << "recipe[loulou]" expect(@run_list[0]).to eq("recipe[loulou]") end end describe "[]=" do it "should let you set a member of the run list by position" do @run_list[0] = "recipe[loulou]" expect(@run_list[0]).to eq("recipe[loulou]") end it "should properly expand a member of the run list given by position" do @run_list[0] = "loulou" expect(@run_list[0]).to eq("recipe[loulou]") end end describe "each" do it "should yield each member to your block" do @run_list << "foo" @run_list << "bar" seen = Array.new @run_list.each { |r| seen << r } expect(seen).to be_include("recipe[foo]") expect(seen).to be_include("recipe[bar]") end end describe "each_index" do it "should yield each members index to your block" do to_add = [ "recipe[foo]", "recipe[bar]", "recipe[baz]" ] to_add.each { |i| @run_list << i } @run_list.each_index { |i| expect(@run_list[i]).to eq(to_add[i]) } end end describe "include?" do it "should be true if the run list includes the item" do @run_list << "foo" @run_list.include?("foo") end end describe "reset" do it "should reset the run_list based on the array you pass" do @run_list << "chromeo" list = %w{camp chairs snakes clowns} @run_list.reset!(list) list.each { |i| expect(@run_list).to be_include(i) } expect(@run_list.include?("chromeo")).to eq(false) end end describe "when expanding the run list" do before(:each) do @role = Chef::Role.new @role.name "stubby" @role.run_list "one", "two" @role.default_attributes :one => :two @role.override_attributes :three => :four @role.env_run_list["production"] = Chef::RunList.new( "one", "two", "five") allow(Chef::Role).to receive(:load).and_return(@role) @rest = double("Chef::ServerAPI", { :get => @role.to_hash, :url => "/" }) allow(Chef::ServerAPI).to receive(:new).and_return(@rest) @run_list << "role[stubby]" @run_list << "kitty" end describe "from disk" do it "should load the role from disk" do expect(Chef::Role).to receive(:from_disk).with("stubby") @run_list.expand("_default", "disk") end it "should log a helpful error if the role is not available" do allow(Chef::Role).to receive(:from_disk).and_raise(Chef::Exceptions::RoleNotFound) expect(Chef::Log).to receive(:error).with("Role stubby (included by 'top level') is in the runlist but does not exist. Skipping expand.") @run_list.expand("_default", "disk") end end describe "from the chef server" do it "should load the role from the chef server" do #@rest.should_receive(:get).with("roles/stubby") expansion = @run_list.expand("_default", "server") expect(expansion.recipes).to eq(%w{one two kitty}) end it "should default to expanding from the server" do expect(@rest).to receive(:get).with("roles/stubby") @run_list.expand("_default") end describe "with an environment set" do it "expands the run list using the environment specific run list" do expansion = @run_list.expand("production", "server") expect(expansion.recipes).to eq(%w{one two five kitty}) end describe "and multiply nested roles" do before do @multiple_rest_requests = double("Chef::ServerAPI") @role.env_run_list["production"] << "role[prod-base]" @role_prod_base = Chef::Role.new @role_prod_base.name("prod-base") @role_prod_base.env_run_list["production"] = Chef::RunList.new("role[nested-deeper]") @role_nested_deeper = Chef::Role.new @role_nested_deeper.name("nested-deeper") @role_nested_deeper.env_run_list["production"] = Chef::RunList.new("recipe[prod-secret-sauce]") end it "expands the run list using the specified environment for all nested roles" do allow(Chef::ServerAPI).to receive(:new).and_return(@multiple_rest_requests) expect(@multiple_rest_requests).to receive(:get).with("roles/stubby").and_return(@role.to_hash) expect(@multiple_rest_requests).to receive(:get).with("roles/prod-base").and_return(@role_prod_base.to_hash) expect(@multiple_rest_requests).to receive(:get).with("roles/nested-deeper").and_return(@role_nested_deeper.to_hash) expansion = @run_list.expand("production", "server") expect(expansion.recipes).to eq(%w{one two five prod-secret-sauce kitty}) end end end end it "should return the list of expanded recipes" do expansion = @run_list.expand("_default") expect(expansion.recipes[0]).to eq("one") expect(expansion.recipes[1]).to eq("two") end it "should return the list of default attributes" do expansion = @run_list.expand("_default") expect(expansion.default_attrs[:one]).to eq(:two) end it "should return the list of override attributes" do expansion = @run_list.expand("_default") expect(expansion.override_attrs[:three]).to eq(:four) end it "should recurse into a child role" do dog = Chef::Role.new dog.name "dog" dog.default_attributes :seven => :nine dog.run_list "three" @role.run_list << "role[dog]" allow(Chef::Role).to receive(:from_disk).with("stubby").and_return(@role) allow(Chef::Role).to receive(:from_disk).with("dog").and_return(dog) expansion = @run_list.expand("_default", "disk") expect(expansion.recipes[2]).to eq("three") expect(expansion.default_attrs[:seven]).to eq(:nine) end it "should not recurse infinitely" do dog = Chef::Role.new dog.name "dog" dog.default_attributes :seven => :nine dog.run_list "role[dog]", "three" @role.run_list << "role[dog]" allow(Chef::Role).to receive(:from_disk).with("stubby").and_return(@role) expect(Chef::Role).to receive(:from_disk).with("dog").once.and_return(dog) expansion = @run_list.expand("_default", "disk") expect(expansion.recipes[2]).to eq("three") expect(expansion.recipes[3]).to eq("kitty") expect(expansion.default_attrs[:seven]).to eq(:nine) end end describe "when converting to an alternate representation" do before do @run_list << "recipe[nagios::client]" << "role[production]" << "recipe[apache2]" end it "converts to an array of the string forms of its items" do expect(@run_list.to_a).to eq(["recipe[nagios::client]", "role[production]", "recipe[apache2]"]) end it "converts to json by converting its array form" do expect(Chef::JSONCompat.to_json(@run_list)).to eq(Chef::JSONCompat.to_json(["recipe[nagios::client]", "role[production]", "recipe[apache2]"])) end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @run_list } end end end chef-12.14.60/spec/unit/run_lock_spec.rb000066400000000000000000000113251276456504500177560ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # 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 File.expand_path("../../spec_helper", __FILE__) require "chef/client" describe Chef::RunLock do default_cache_path = windows? ? 'C:\chef' : "/var/chef" default_pid_location = windows? ? 'C:\chef\cache\chef-client-running.pid' : "/var/chef/cache/chef-client-running.pid" describe "when first created" do it "locates the lockfile in the file cache path by default" do allow(Chef::Config).to receive(:cache_path).and_return(default_cache_path) run_lock = Chef::RunLock.new(Chef::Config.lockfile) expect(run_lock.runlock_file).to eq(default_pid_location) end it "locates the lockfile in the user-configured path when set" do Chef::Config.lockfile = "/tmp/chef-client-running.pid" run_lock = Chef::RunLock.new(Chef::Config.lockfile) expect(run_lock.runlock_file).to eq("/tmp/chef-client-running.pid") end end describe "acquire" do let(:lockfile) { "/tmp/chef-client-running.pid" } subject(:runlock) { Chef::RunLock.new(lockfile) } def stub_unblocked_run allow(runlock).to receive(:test).and_return(true) end def stub_blocked_run(duration) allow(runlock).to receive(:test).and_return(false) allow(runlock).to receive(:wait) { sleep(duration) } allow(runlock).to receive(:runpid).and_return(666) # errors read blocking pid end describe "when Chef::Config[:run_lock_timeout] is not set (set to default)" do describe "and the lockfile is not locked by another client run" do it "should not wait" do stub_unblocked_run expect_any_instance_of(Chef::RunLock).not_to receive(:wait) runlock.acquire end end describe "and the lockfile is locked by another client run" do it "should wait for the lock to be released" do stub_blocked_run(0.001) expect(runlock).to receive(:wait) runlock.acquire end end end describe "when Chef::Config[:run_lock_timeout] is set to 0" do before(:each) do @default_timeout = Chef::Config[:run_lock_timeout] Chef::Config[:run_lock_timeout] = 0 end after(:each) do Chef::Config[:run_lock_timeout] = @default_timeout end describe "and the lockfile is not locked by another client run" do it "should acquire the lock" do stub_unblocked_run expect(runlock).not_to receive(:wait) runlock.acquire end end describe "and the lockfile is locked by another client run" do it "should raise Chef::Exceptions::RunLockTimeout" do stub_blocked_run(0.001) expect(runlock).not_to receive(:wait) expect { runlock.acquire }.to raise_error(Chef::Exceptions::RunLockTimeout) end end end describe "when Chef::Config[:run_lock_timeout] is set to >0" do before(:each) do @default_timeout = Chef::Config[:run_lock_timeout] @timeout = 0.1 Chef::Config[:run_lock_timeout] = @timeout end after(:each) do Chef::Config[:run_lock_timeout] = @default_timeout end describe "and the lockfile is not locked by another client run" do it "should acquire the lock" do stub_unblocked_run expect(runlock).not_to receive(:wait) runlock.acquire end end describe "and the lockfile is locked by another client run" do describe "and the lock is released before the timeout expires" do it "should acquire the lock" do stub_blocked_run(@timeout / 2.0) expect(runlock).to receive(:wait) expect { runlock.acquire }.not_to raise_error end end describe "and the lock is not released before the timeout expires" do it "should raise a RunLockTimeout exception" do stub_blocked_run(2.0) expect(runlock).to receive(:wait) expect { runlock.acquire }.to raise_error(Chef::Exceptions::RunLockTimeout) end end end end end # See also: spec/functional/run_lock_spec end chef-12.14.60/spec/unit/run_status_spec.rb000066400000000000000000000106331276456504500203520ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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::RunStatus do before do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @run_status = Chef::RunStatus.new(@node, @events) end describe "before the run context has been set" do it "converts to a hash" do @run_status.to_hash end end describe "when the run context has been set" do before do @run_status.run_context = @run_context end it "has a run context" do expect(@run_status.run_context).to equal(@run_context) end it "provides access to the run context's node" do expect(@run_status.node).to equal(@node) end it "converts to a hash" do expect(@run_status.to_hash[:node]).to equal(@node) expect(@run_status.to_hash[:success]).to be_truthy end describe "after it has recorded timing information" do before do @start_time = Time.new @end_time = @start_time + 23 allow(Time).to receive(:now).and_return(@start_time, @end_time) @run_status.start_clock @run_status.stop_clock end it "records the start time of the run" do expect(@run_status.start_time).to eq(@start_time) end it "records the end time of the run" do expect(@run_status.end_time).to eq(@end_time) end it "gives the elapsed time of the chef run" do expect(@run_status.elapsed_time).to eq(23) end it "includes timing information in its hash form" do expect(@run_status.to_hash[:start_time]).to eq(@start_time) expect(@run_status.to_hash[:end_time]).to eq(@end_time) expect(@run_status.to_hash[:elapsed_time]).to eq(23) end end describe "with resources in the resource_collection" do before do @all_resources = [Chef::Resource::Cat.new("whiskers"), Chef::Resource::ZenMaster.new("dtz")] @run_context.resource_collection.all_resources.replace(@all_resources) end it "lists all resources" do expect(@run_status.all_resources).to eq(@all_resources) end it "has no updated resources" do expect(@run_status.updated_resources).to be_empty end it "includes the list of all resources in its hash form" do expect(@run_status.to_hash[:all_resources]).to eq(@all_resources) expect(@run_status.to_hash[:updated_resources]).to be_empty end describe "and some have been updated" do before do @all_resources.first.updated = true end it "lists the updated resources" do expect(@run_status.updated_resources).to eq([@all_resources.first]) end it "includes the list of updated resources in its hash form" do expect(@run_status.to_hash[:updated_resources]).to eq([@all_resources.first]) end end end describe "when the run failed" do before do @exception = Exception.new("just testing") @backtrace = caller @exception.set_backtrace(@backtrace) @run_status.exception = @exception end it "stores the exception" do expect(@run_status.exception).to equal(@exception) end it "stores the backtrace" do expect(@run_status.backtrace).to eq(@backtrace) end it "says the run was not successful" do expect(@run_status.success?).to be_falsey expect(@run_status.failed?).to be_truthy end it "converts to a hash including the exception information" do expect(@run_status.to_hash[:success]).to be_falsey expect(@run_status.to_hash[:exception]).to eq("Exception: just testing") expect(@run_status.to_hash[:backtrace]).to eq(@backtrace) end end end end chef-12.14.60/spec/unit/runner_spec.rb000066400000000000000000000355601276456504500174620ustar00rootroot00000000000000 # Author:: Adam Jacob () # 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" class SnitchyProvider < Chef::Provider def self.all_actions_called @all_actions_called ||= [] end def self.action_called(action) all_actions_called << action end def self.clear_action_record @all_actions_called = nil end def load_current_resource true end def action_first_action @new_resource.updated_by_last_action(true) self.class.action_called(:first) end def action_second_action @new_resource.updated_by_last_action(true) self.class.action_called(:second) end def action_third_action @new_resource.updated_by_last_action(true) self.class.action_called(:third) end end class FailureResource < Chef::Resource attr_accessor :action def initialize(*args) super @action = :fail end def provider FailureProvider end end class FailureProvider < Chef::Provider class ChefClientFail < StandardError; end def load_current_resource true end def action_fail raise ChefClientFail, "chef had an error of some sort" end end describe Chef::Runner do let(:node) do node = Chef::Node.new node.name "latte" node.automatic[:platform] = "mac_os_x" node.automatic[:platform_version] = "10.5.1" node end let(:events) { Chef::EventDispatch::Dispatcher.new } let(:run_context) { Chef::RunContext.new(node, Chef::CookbookCollection.new({}), events) } let(:first_resource) { Chef::Resource::Cat.new("loulou1", run_context) } let(:runner) { Chef::Runner.new(run_context) } before do run_context.resource_collection << first_resource end context "when we fall through to old Chef::Platform resolution" do let(:provider_resolver) { Chef::ProviderResolver.new(node, first_resource, nil) } before do # set up old Chef::Platform resolution instead of provider_resolver Chef::Platform.set( :resource => :cat, :provider => Chef::Provider::SnakeOil ) allow(Chef::ProviderResolver).to receive(:new).and_return(provider_resolver) allow(provider_resolver).to receive(:maybe_dynamic_provider_resolution).with(first_resource, anything()).and_return(nil) end it "should use the platform provider if it has one" do expect(Chef::Platform).to receive(:find_provider_for_node).with(node, first_resource).and_call_original runner.converge end end context "when we are doing dynamic provider resolution" do it "should pass each resource in the collection to a provider" do expect(run_context.resource_collection).to receive(:execute_each_resource).once runner.converge end it "should use the provider specified by the resource (if it has one)" do provider = Chef::Provider::Easy.new(run_context.resource_collection[0], run_context) # Expect provider to be called twice, because will fall back to old provider lookup expect(run_context.resource_collection[0]).to receive(:provider).twice.and_return(Chef::Provider::Easy) expect(Chef::Provider::Easy).to receive(:new).once.and_return(provider) runner.converge end it "should run the action for each resource" do provider = Chef::Provider::SnakeOil.new(run_context.resource_collection[0], run_context) expect(provider).to receive(:action_sell).once.and_return(true) expect(Chef::Provider::SnakeOil).to receive(:new).once.and_return(provider) runner.converge end it "should raise exceptions as thrown by a provider" do provider = Chef::Provider::SnakeOil.new(run_context.resource_collection[0], run_context) allow(Chef::Provider::SnakeOil).to receive(:new).once.and_return(provider) allow(provider).to receive(:action_sell).once.and_raise(ArgumentError) expect { runner.converge }.to raise_error(ArgumentError) end it "should not raise exceptions thrown by providers if the resource has ignore_failure set to true" do allow(run_context.resource_collection[0]).to receive(:ignore_failure).and_return(true) provider = Chef::Provider::SnakeOil.new(run_context.resource_collection[0], run_context) allow(Chef::Provider::SnakeOil).to receive(:new).once.and_return(provider) allow(provider).to receive(:action_sell).once.and_raise(ArgumentError) expect { runner.converge }.not_to raise_error end it "should retry with the specified delay if retries are specified" do first_resource.retries 3 provider = Chef::Provider::SnakeOil.new(run_context.resource_collection[0], run_context) allow(Chef::Provider::SnakeOil).to receive(:new).once.and_return(provider) allow(provider).to receive(:action_sell).and_raise(ArgumentError) expect(first_resource).to receive(:sleep).with(2).exactly(3).times expect { runner.converge }.to raise_error(ArgumentError) end it "should execute immediate actions on changed resources" do notifying_resource = Chef::Resource::Cat.new("peanut", run_context) notifying_resource.action = :purr # only action that will set updated on the resource run_context.resource_collection << notifying_resource first_resource.action = :nothing # won't be updated unless notified by other resource notifying_resource.notifies(:purr, first_resource, :immediately) runner.converge expect(first_resource).to be_updated end it "should follow a chain of actions" do first_resource.action = :nothing middle_resource = Chef::Resource::Cat.new("peanut", run_context) middle_resource.action = :nothing run_context.resource_collection << middle_resource middle_resource.notifies(:purr, first_resource, :immediately) last_resource = Chef::Resource::Cat.new("snuffles", run_context) last_resource.action = :purr run_context.resource_collection << last_resource last_resource.notifies(:purr, middle_resource, :immediately) runner.converge expect(last_resource).to be_updated # by action(:purr) expect(middle_resource).to be_updated # by notification from last_resource expect(first_resource).to be_updated # by notification from middle_resource end it "should execute delayed actions on changed resources" do first_resource.action = :nothing second_resource = Chef::Resource::Cat.new("peanut", run_context) second_resource.action = :purr run_context.resource_collection << second_resource second_resource.notifies(:purr, first_resource, :delayed) runner.converge expect(first_resource).to be_updated end it "should execute delayed notifications when a failure occurs in the chef client run" do first_resource.action = :nothing second_resource = Chef::Resource::Cat.new("peanut", run_context) second_resource.action = :purr run_context.resource_collection << second_resource second_resource.notifies(:purr, first_resource, :delayed) third_resource = FailureResource.new("explode", run_context) run_context.resource_collection << third_resource expect { runner.converge }.to raise_error(FailureProvider::ChefClientFail) expect(first_resource).to be_updated end it "should execute delayed notifications when a failure occurs in a notification" do first_resource.action = :nothing second_resource = Chef::Resource::Cat.new("peanut", run_context) second_resource.action = :purr run_context.resource_collection << second_resource third_resource = FailureResource.new("explode", run_context) third_resource.action = :nothing run_context.resource_collection << third_resource second_resource.notifies(:fail, third_resource, :delayed) second_resource.notifies(:purr, first_resource, :delayed) expect { runner.converge }.to raise_error(FailureProvider::ChefClientFail) expect(first_resource).to be_updated end it "should execute delayed notifications when a failure occurs in multiple notifications" do first_resource.action = :nothing second_resource = Chef::Resource::Cat.new("peanut", run_context) second_resource.action = :purr run_context.resource_collection << second_resource third_resource = FailureResource.new("explode", run_context) third_resource.action = :nothing run_context.resource_collection << third_resource fourth_resource = FailureResource.new("explode again", run_context) fourth_resource.action = :nothing run_context.resource_collection << fourth_resource second_resource.notifies(:fail, third_resource, :delayed) second_resource.notifies(:fail, fourth_resource, :delayed) second_resource.notifies(:purr, first_resource, :delayed) exception = nil begin runner.converge rescue => e exception = e end expect(exception).to be_a(Chef::Exceptions::MultipleFailures) expected_message = <<-E Multiple failures occurred: * FailureProvider::ChefClientFail occurred in delayed notification: [explode] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort * FailureProvider::ChefClientFail occurred in delayed notification: [explode again] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort E expect(exception.message).to eq(expected_message) expect(first_resource).to be_updated end it "does not duplicate delayed notifications" do SnitchyProvider.clear_action_record first_resource.action = :nothing first_resource.provider = SnitchyProvider second_resource = Chef::Resource::Cat.new("peanut", run_context) second_resource.action = :first_action second_resource.provider = SnitchyProvider run_context.resource_collection << second_resource third_resource = Chef::Resource::Cat.new("snickers", run_context) third_resource.action = :first_action third_resource.provider = SnitchyProvider run_context.resource_collection << third_resource second_resource.notifies(:second_action, first_resource, :delayed) second_resource.notifies(:third_action, first_resource, :delayed) third_resource.notifies(:second_action, first_resource, :delayed) third_resource.notifies(:third_action, first_resource, :delayed) runner.converge # resources 2 and 3 call :first_action in the course of normal resource # execution, and schedule delayed actions :second and :third on the first # resource. The duplicate actions should "collapse" to a single notification # and order should be preserved. expect(SnitchyProvider.all_actions_called).to eq([:first, :first, :second, :third]) end it "executes delayed notifications in the order they were declared" do SnitchyProvider.clear_action_record first_resource.action = :nothing first_resource.provider = SnitchyProvider second_resource = Chef::Resource::Cat.new("peanut", run_context) second_resource.action = :first_action second_resource.provider = SnitchyProvider run_context.resource_collection << second_resource third_resource = Chef::Resource::Cat.new("snickers", run_context) third_resource.action = :first_action third_resource.provider = SnitchyProvider run_context.resource_collection << third_resource second_resource.notifies(:second_action, first_resource, :delayed) second_resource.notifies(:second_action, first_resource, :delayed) third_resource.notifies(:third_action, first_resource, :delayed) third_resource.notifies(:third_action, first_resource, :delayed) runner.converge expect(SnitchyProvider.all_actions_called).to eq([:first, :first, :second, :third]) end it "does not fire notifications if the resource was not updated by the last action executed" do # REGRESSION TEST FOR CHEF-1452 SnitchyProvider.clear_action_record first_resource.action = :first_action first_resource.provider = SnitchyProvider second_resource = Chef::Resource::Cat.new("peanut", run_context) second_resource.action = :nothing second_resource.provider = SnitchyProvider run_context.resource_collection << second_resource third_resource = Chef::Resource::Cat.new("snickers", run_context) third_resource.action = :nothing third_resource.provider = SnitchyProvider run_context.resource_collection << third_resource first_resource.notifies(:second_action, second_resource, :immediately) second_resource.notifies(:third_action, third_resource, :immediately) runner.converge # All of the resources should only fire once: expect(SnitchyProvider.all_actions_called).to eq([:first, :second, :third]) # all of the resources should be marked as updated for reporting purposes expect(first_resource).to be_updated expect(second_resource).to be_updated expect(third_resource).to be_updated end it "should check a resource's only_if and not_if if notified by another resource" do first_resource.action = :buy only_if_called_times = 0 first_resource.only_if { only_if_called_times += 1; true } not_if_called_times = 0 first_resource.not_if { not_if_called_times += 1; false } second_resource = Chef::Resource::Cat.new("carmel", run_context) run_context.resource_collection << second_resource second_resource.notifies(:purr, first_resource, :delayed) second_resource.action = :purr # hits only_if first time when the resource is run in order, second on notify runner.converge expect(only_if_called_times).to eq(2) expect(not_if_called_times).to eq(2) end it "should resolve resource references in notifications when resources are defined lazily" do first_resource.action = :nothing lazy_resources = lambda do last_resource = Chef::Resource::Cat.new("peanut", run_context) run_context.resource_collection << last_resource last_resource.notifies(:purr, first_resource.to_s, :delayed) last_resource.action = :purr end second_resource = Chef::Resource::RubyBlock.new("myblock", run_context) run_context.resource_collection << second_resource second_resource.block { lazy_resources.call } runner.converge expect(first_resource).to be_updated end end end chef-12.14.60/spec/unit/scan_access_control_spec.rb000066400000000000000000000132761276456504500221560ustar00rootroot00000000000000# Author:: Daniel DeLeo () # 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 File.expand_path("../../spec_helper", __FILE__) require "chef/scan_access_control" describe Chef::ScanAccessControl do before do @new_resource = Chef::Resource::File.new("/tmp/foo/bar/baz/link") @real_file = "/tmp/foo/bar/real/file" @current_resource = Chef::Resource::File.new(@new_resource.path) @scanner = Chef::ScanAccessControl.new(@new_resource, @current_resource) end describe "when the fs entity does not exist" do before do @new_resource.tap do |f| f.owner("root") f.group("root") f.mode("0755") end @scanner.set_all! end it "does not set any fields on the current resource" do expect(@current_resource.owner).to be_nil expect(@current_resource.group).to be_nil expect(@current_resource.mode).to be_nil end end describe "when the fs entity exists" do before do @stat = double("File::Stat for #{@new_resource.path}", :uid => 0, :gid => 0, :mode => 00100644) expect(File).to receive(:realpath).with(@new_resource.path).and_return(@real_file) expect(File).to receive(:stat).with(@real_file).and_return(@stat) expect(File).to receive(:exist?).with(@new_resource.path).and_return(true) end describe "when new_resource does not specify mode, user or group" do # these tests are necessary for minitest-chef-handler to use as an API, see CHEF-3235 before do @scanner.set_all! end it "sets the mode of the current resource to the current mode as a String" do expect(@current_resource.mode).to eq("0644") end context "on unix", :unix_only do it "sets the group of the current resource to the current group as a String" do expect(@current_resource.group).to eq(Etc.getgrgid(0).name) end it "sets the owner of the current resource to the current owner as a String" do expect(@current_resource.user).to eq("root") end end context "on windows", :windows_only do it "sets the group of the current resource to the current group as a String" do expect(@current_resource.group).to eq(0) end it "sets the owner of the current resource to the current owner as a String" do expect(@current_resource.user).to eq(0) end end end describe "when new_resource specifies the mode with a string" do before do @new_resource.mode("0755") @scanner.set_all! end it "sets the mode of the current resource to the file's current mode as a string" do expect(@current_resource.mode).to eq("0644") end end describe "when new_resource specified the mode with an integer" do before do @new_resource.mode(00755) @scanner.set_all! end it "sets the mode of the current resource to the current mode as a String" do expect(@current_resource.mode).to eq("0644") end end describe "when new_resource specifies the user with a UID" do before do @new_resource.user(0) @scanner.set_all! end it "sets the owner of current_resource to the UID of the current owner" do expect(@current_resource.user).to eq(0) end end describe "when new_resource specifies the user with a username" do before do @new_resource.user("root") end it "sets the owner of current_resource to the username of the current owner" do @root_passwd = double("Struct::Passwd for uid 0", :name => "root") expect(Etc).to receive(:getpwuid).with(0).and_return(@root_passwd) @scanner.set_all! expect(@current_resource.user).to eq("root") end describe "and there is no passwd entry for the user" do it "sets the owner of the current_resource to the UID" do expect(Etc).to receive(:getpwuid).with(0).and_raise(ArgumentError) @scanner.set_all! expect(@current_resource.user).to eq(0) end end end describe "when new_resource specifies the group with a GID" do before do @new_resource.group(0) @scanner.set_all! end it "sets the group of the current_resource to the gid of the current owner" do expect(@current_resource.group).to eq(0) end end describe "when new_resource specifies the group with a group name" do before do @new_resource.group("wheel") end it "sets the group of the current resource to the group name" do @group_entry = double("Struct::Group for wheel", :name => "wheel") expect(Etc).to receive(:getgrgid).with(0).and_return(@group_entry) @scanner.set_all! expect(@current_resource.group).to eq("wheel") end describe "and there is no group entry for the group" do it "sets the current_resource's group to the GID" do expect(Etc).to receive(:getgrgid).with(0).and_raise(ArgumentError) @scanner.set_all! expect(@current_resource.group).to eq(0) end end end end end chef-12.14.60/spec/unit/search/000077500000000000000000000000001276456504500160465ustar00rootroot00000000000000chef-12.14.60/spec/unit/search/query_spec.rb000066400000000000000000000227441276456504500205630ustar00rootroot00000000000000# # Author:: Adam Jacob () # Copyright:: Copyright 2009-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/search/query" describe Chef::Search::Query do let(:rest) { double("Chef::ServerAPI") } let(:query) { Chef::Search::Query.new } shared_context "filtered search" do let(:query_string) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0" } let(:server_url) { "https://api.opscode.com/organizations/opscode/nodes" } let(:args) { { filter_key => filter_hash } } let(:filter_hash) do { "env" => [ "chef_environment" ], "ruby_plat" => %w{languages ruby platform}, } end let(:response) do { "rows" => [ { "url" => "#{server_url}/my-name-is-node", "data" => { "env" => "elysium", "ruby_plat" => "nudibranch", }, }, { "url" => "#{server_url}/my-name-is-jonas", "data" => { "env" => "hades", "ruby_plat" => "i386-mingw32", }, }, { "url" => "#{server_url}/my-name-is-flipper", "data" => { "env" => "elysium", "ruby_plat" => "centos", }, }, { "url" => "#{server_url}/my-name-is-butters", "data" => { "env" => "moon", "ruby_plat" => "solaris2", }, }, ], "start" => 0, "total" => 4, } end let(:response_rows) do [ { "env" => "elysium", "ruby_plat" => "nudibranch" }, { "env" => "hades", "ruby_plat" => "i386-mingw32" }, { "env" => "elysium", "ruby_plat" => "centos" }, { "env" => "moon", "ruby_plat" => "solaris2" }, ] end end before(:each) do allow(Chef::ServerAPI).to receive(:new).and_return(rest) allow(rest).to receive(:get).and_return(response) end describe "search" do let(:query_string) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0" } let(:query_string_continue) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=4" } let(:query_string_with_rows) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=4" } let(:query_string_continue_with_rows) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=4&rows=4" } let(:response) do { "rows" => [ { "name" => "my-name-is-node", "chef_environment" => "elysium", "platform" => "rhel", "run_list" => [], "automatic" => { "languages" => { "ruby" => { "platform" => "nudibranch", "version" => "1.9.3", "target" => "ming-the-merciless", }, }, }, }, { "name" => "my-name-is-jonas", "chef_environment" => "hades", "platform" => "rhel", "run_list" => [], "automatic" => { "languages" => { "ruby" => { "platform" => "i386-mingw32", "version" => "1.9.3", "target" => "bilbo", }, }, }, }, { "name" => "my-name-is-flipper", "chef_environment" => "elysium", "platform" => "rhel", "run_list" => [], "automatic" => { "languages" => { "ruby" => { "platform" => "centos", "version" => "2.0.0", "target" => "uno", }, }, }, }, { "name" => "my-name-is-butters", "chef_environment" => "moon", "platform" => "rhel", "run_list" => [], "automatic" => { "languages" => { "ruby" => { "platform" => "solaris2", "version" => "2.1.2", "target" => "random", }, }, }, }, ], "start" => 0, "total" => 4, } end let(:big_response) do r = response.dup r["total"] = 8 r end let(:big_response_empty) do { "start" => 0, "total" => 8, "rows" => [], } end let(:big_response_end) do r = response.dup r["start"] = 4 r["total"] = 8 r end it "accepts a type as the first argument" do expect { query.search("node") }.not_to raise_error expect { query.search(:node) }.not_to raise_error expect { query.search(Hash.new) }.to raise_error(Chef::Exceptions::InvalidSearchQuery, /(Hash)/) end it "queries for every object of a type by default" do expect(rest).to receive(:get).with("search/node?q=*:*&sort=X_CHEF_id_CHEF_X%20asc&start=0").and_return(response) query.search(:node) end it "allows a custom query" do expect(rest).to receive(:get).with("search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0").and_return(response) query.search(:node, "platform:rhel") end it "lets you set a sort order" do expect(rest).to receive(:get).with("search/node?q=platform:rhel&sort=id%20desc&start=0").and_return(response) query.search(:node, "platform:rhel", sort: "id desc") end it "lets you set a starting object" do expect(rest).to receive(:get).with("search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=2").and_return(response) query.search(:node, "platform:rhel", start: 2) end it "lets you set how many rows to return" do expect(rest).to receive(:get).with("search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=40").and_return(response) query.search(:node, "platform:rhel", rows: 40) end it "throws an exception if you pass an incorrect option" do expect { query.search(:node, "platform:rhel", total: 10) } .to raise_error(ArgumentError, /unknown keyword: total/) end it "returns the raw rows, start, and total if no block is passed" do rows, start, total = query.search(:node) expect(rows).to equal(response["rows"]) expect(start).to equal(response["start"]) expect(total).to equal(response["total"]) end it "calls a block for each object in the response" do @call_me = double("blocky") response["rows"].each { |r| expect(@call_me).to receive(:do).with(Chef::Node.from_hash(r)) } query.search(:node) { |r| @call_me.do(r) } end it "pages through the responses" do @call_me = double("blocky") response["rows"].each { |r| expect(@call_me).to receive(:do).with(Chef::Node.from_hash(r)) } query.search(:node, "*:*", sort: nil, start: 0, rows: 4) { |r| @call_me.do(r) } end it "sends multiple API requests when the server indicates there is more data" do expect(rest).to receive(:get).with(query_string).and_return(big_response) expect(rest).to receive(:get).with(query_string_continue).and_return(big_response_end) query.search(:node, "platform:rhel") do |r| nil end end it "paginates correctly in the face of filtered nodes" do expect(rest).to receive(:get).with(query_string_with_rows).and_return(big_response_empty) expect(rest).to receive(:get).with(query_string_continue_with_rows).and_return(big_response_end) query.search(:node, "platform:rhel", rows: 4) do |r| nil end end context "when :filter_result is provided as a result" do include_context "filtered search" do let(:filter_key) { :filter_result } before(:each) do expect(rest).to receive(:post).with(query_string, args[filter_key]).and_return(response) end it "returns start" do start = query.search(:node, "platform:rhel", args)[1] expect(start).to eq(response["start"]) end it "returns total" do total = query.search(:node, "platform:rhel", args)[2] expect(total).to eq(response["total"]) end it "returns rows with the filter applied" do filtered_rows = query.search(:node, "platform:rhel", args)[0] expect(filtered_rows).to match_array(response_rows) end end end end describe "#partial_search" do include_context "filtered search" do let(:filter_key) { :keys } it "emits a deprecation warning" do # partial_search calls search, so we'll stub search to return empty allow(query).to receive(:search).and_return( [ [], 0, 0 ] ) expect(Chef::Log).to receive(:warn).with(/DEPRECATED: The 'partial_search' API is deprecated/) query.partial_search(:node, "platform:rhel", args) end it "returns an array of filtered hashes" do expect(rest).to receive(:post).with(query_string, args[filter_key]).and_return(response) results = query.partial_search(:node, "platform:rhel", args) expect(results[0]).to match_array(response_rows) end end end end chef-12.14.60/spec/unit/shell/000077500000000000000000000000001276456504500157105ustar00rootroot00000000000000chef-12.14.60/spec/unit/shell/model_wrapper_spec.rb000066400000000000000000000063401276456504500221120ustar00rootroot00000000000000# # Author:: Daniel DeLeo () # Copyright:: Copyright 2010-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 "ostruct" describe Shell::ModelWrapper do before do @model = OpenStruct.new(:name => "Chef::Node") @wrapper = Shell::ModelWrapper.new(@model) end describe "when created with an explicit model_symbol" do before do @model = OpenStruct.new(:name => "Chef::ApiClient") @wrapper = Shell::ModelWrapper.new(@model, :client) end it "uses the explicit model symbol" do expect(@wrapper.model_symbol).to eq(:client) end end it "determines the model symbol from the class name" do expect(@wrapper.model_symbol).to eq(:node) end describe "when listing objects" do before do @node_1 = Chef::Node.new @node_1.name("sammich") @node_2 = Chef::Node.new @node_2.name("yummy") @server_response = { :node_1 => @node_1, :node_2 => @node_2 } @wrapper = Shell::ModelWrapper.new(Chef::Node) allow(Chef::Node).to receive(:list).and_return(@server_response) end it "lists fully inflated objects without the resource IDs" do expect(@wrapper.all.size).to eq(2) expect(@wrapper.all).to include(@node_1, @node_2) end it "maps the listed nodes when given a block" do expect(@wrapper.all { |n| n.name }.sort.reverse).to eq(%w{yummy sammich}) end end describe "when searching for objects" do before do @node_1 = Chef::Node.new @node_1.name("sammich") @node_2 = Chef::Node.new @node_2.name("yummy") @server_response = { :node_1 => @node_1, :node_2 => @node_2 } @wrapper = Shell::ModelWrapper.new(Chef::Node) # Creating a Chef::Search::Query object tries to read the private key... @searcher = double("Chef::Search::Query #{__FILE__}:#{__LINE__}") allow(Chef::Search::Query).to receive(:new).and_return(@searcher) end it "falls back to listing the objects when the 'query' is :all" do allow(Chef::Node).to receive(:list).and_return(@server_response) expect(@wrapper.find(:all)).to include(@node_1, @node_2) end it "searches for objects using the given query string" do expect(@searcher).to receive(:search).with(:node, "name:app*").and_yield(@node_1).and_yield(@node_2) expect(@wrapper.find("name:app*")).to include(@node_1, @node_2) end it "creates a 'AND'-joined query string from a HASH" do # Hash order woes expect(@searcher).to receive(:search).with(:node, "name:app* AND name:app*").and_yield(@node_1).and_yield(@node_2) expect(@wrapper.find(:name => "app*", "name" => "app*")).to include(@node_1, @node_2) end end end chef-12.14.60/spec/unit/shell/shell_ext_spec.rb000066400000000000000000000131461276456504500212430ustar00rootroot00000000000000# Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # Copyright:: Copyright 2010-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 Shell::Extensions do describe "extending object for top level methods" do before do @shell_client = TestableShellSession.instance allow(Shell).to receive(:session).and_return(@shell_client) @job_manager = TestJobManager.new @root_context = Object.new @root_context.instance_eval(&ObjectTestHarness) Shell::Extensions.extend_context_object(@root_context) @root_context.conf = double("irbconf") end it "finds a subsession in irb for an object" do target_context_obj = Chef::Node.new irb_context = double("context", :main => target_context_obj) irb_session = double("irb session", :context => irb_context) @job_manager.jobs = [[:thread, irb_session]] allow(@root_context).to receive(:jobs).and_return(@job_manager) @root_context.ensure_session_select_defined expect(@root_context.jobs.select_shell_session(target_context_obj)).to eq(irb_session) expect(@root_context.jobs.select_shell_session(:idontexist)).to be_nil end it "finds, then switches to a session" do @job_manager.jobs = [] allow(@root_context).to receive(:ensure_session_select_defined) allow(@root_context).to receive(:jobs).and_return(@job_manager) expect(@job_manager).to receive(:select_shell_session).and_return(:the_shell_session) expect(@job_manager).to receive(:switch).with(:the_shell_session) @root_context.find_or_create_session_for(:foo) end it "creates a new session if an existing one isn't found" do @job_manager.jobs = [] allow(@root_context).to receive(:jobs).and_return(@job_manager) allow(@job_manager).to receive(:select_shell_session).and_return(nil) expect(@root_context).to receive(:irb).with(:foo) @root_context.find_or_create_session_for(:foo) end it "switches to recipe context" do expect(@root_context).to respond_to(:recipe_mode) @shell_client.recipe = :monkeyTime expect(@root_context).to receive(:find_or_create_session_for).with(:monkeyTime) @root_context.recipe_mode end it "switches to attribute context" do expect(@root_context).to respond_to(:attributes_mode) @shell_client.node = "monkeyNodeTime" expect(@root_context).to receive(:find_or_create_session_for).with("monkeyNodeTime") @root_context.attributes_mode end it "has a help command" do expect(@root_context).to respond_to(:help) end it "turns irb tracing on and off" do expect(@root_context).to respond_to(:trace) expect(@root_context.conf).to receive(:use_tracer=).with(true) allow(@root_context).to receive(:tracing?) @root_context.tracing :on end it "says if tracing is on or off" do allow(@root_context.conf).to receive(:use_tracer).and_return(true) expect(@root_context).to receive(:puts).with("tracing is on") @root_context.tracing? end it "prints node attributes" do node = double("node", :attribute => { :foo => :bar }) @shell_client.node = node expect(@root_context).to receive(:pp).with({ :foo => :bar }) @root_context.ohai expect(@root_context).to receive(:pp).with(:bar) @root_context.ohai(:foo) end it "resets the recipe and reloads ohai data" do expect(@shell_client).to receive(:reset!) @root_context.reset end it "turns irb echo on and off" do expect(@root_context.conf).to receive(:echo=).with(true) @root_context.echo :on end it "says if echo is on or off" do allow(@root_context.conf).to receive(:echo).and_return(true) expect(@root_context).to receive(:puts).with("echo is on") @root_context.echo? end it "gives access to the stepable iterator" do allow(Shell::StandAloneSession.instance).to receive(:reset!) allow(Shell.session).to receive(:rebuild_context) events = Chef::EventDispatch::Dispatcher.new run_context = Chef::RunContext.new(Chef::Node.new, {}, events) run_context.resource_collection.instance_variable_get(:@resource_list).instance_variable_set(:@iterator, :the_iterator) Shell.session.run_context = run_context expect(@root_context.chef_run).to eq(:the_iterator) end it "lists directory contents" do entries = %w{. .. someFile} expect(Dir).to receive(:entries).with("/tmp").and_return(entries) @root_context.ls "/tmp" end end describe "extending the recipe object" do before do @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(Chef::Node.new, {}, @events) @recipe_object = Chef::Recipe.new(nil, nil, @run_context) Shell::Extensions.extend_context_recipe(@recipe_object) end it "gives a list of the resources" do resource = @recipe_object.file("foo") expect(@recipe_object).to receive(:pp).with(["file[foo]"]) @recipe_object.resources end end end chef-12.14.60/spec/unit/shell/shell_session_spec.rb000066400000000000000000000153171276456504500221300ustar00rootroot00000000000000# Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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 "ostruct" class TestableShellSession < Shell::ShellSession def rebuild_node nil end def rebuild_collection nil end def loading nil end def loading_complete nil end end describe Shell::ShellSession do it "is a singleton object" do expect(Shell::ShellSession).to include(Singleton) end end describe Shell::ClientSession do before do Chef::Config[:shell_config] = { :override_runlist => [Chef::RunList::RunListItem.new("shell::override")] } @chef_rest = double("Chef::ServerAPI") @session = Shell::ClientSession.instance @node = Chef::Node.build("foo") @session.node = @node @client = double("Chef::Client.new", :run_ohai => true, :load_node => true, :build_node => true, :register => true, :sync_cookbooks => {}) end it "builds the node's run_context with the proper environment" do @session.instance_variable_set(:@client, @client) @expansion = Chef::RunList::RunListExpansion.new(@node.chef_environment, []) expect(@node.run_list).to receive(:expand).with(@node.chef_environment).and_return(@expansion) expect(Chef::ServerAPI).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(@chef_rest) @session.rebuild_context end it "passes the shell CLI args to the client" do expect(Chef::Client).to receive(:new).with(nil, Chef::Config[:shell_config]).and_return(@client) @session.send(:rebuild_node) end end describe Shell::StandAloneSession do before do Chef::Config[:shell_config] = { :override_runlist => [Chef::RunList::RunListItem.new("shell::override")] } @session = Shell::StandAloneSession.instance @node = @session.node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = @session.run_context = Chef::RunContext.new(@node, {}, @events) @recipe = @session.recipe = Chef::Recipe.new(nil, nil, @run_context) Shell::Extensions.extend_context_recipe(@recipe) end it "has a run_context" do expect(@session.run_context).to equal(@run_context) end it "returns a collection based on it's standalone recipe file" do expect(@session.resource_collection).to eq(@recipe.run_context.resource_collection) end it "gives nil for the definitions (for now)" do expect(@session.definitions).to be_nil end it "gives nil for the cookbook_loader" do expect(@session.cookbook_loader).to be_nil end it "runs chef with the standalone recipe" do allow(@session).to receive(:node_built?).and_return(true) allow(Chef::Log).to receive(:level) chef_runner = double("Chef::Runner.new", :converge => :converged) # pre-heat resource collection cache @session.resource_collection expect(Chef::Runner).to receive(:new).with(@session.recipe.run_context).and_return(chef_runner) expect(@recipe.run_chef).to eq(:converged) end it "passes the shell CLI args to the client" do @client = double("Chef::Client.new", :run_ohai => true, :load_node => true, :build_node => true, :register => true, :sync_cookbooks => {}) expect(Chef::Client).to receive(:new).with(nil, Chef::Config[:shell_config]).and_return(@client) @session.send(:rebuild_node) end end describe Shell::SoloSession do before do Chef::Config[:shell_config] = { :override_runlist => [Chef::RunList::RunListItem.new("shell::override")] } Chef::Config[:shell_solo] = true @session = Shell::SoloSession.instance @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = @session.run_context = Chef::RunContext.new(@node, {}, @events) @session.node = @node @recipe = @session.recipe = Chef::Recipe.new(nil, nil, @run_context) Shell::Extensions.extend_context_recipe(@recipe) end after do Chef::Config[:shell_solo] = nil end it "returns a collection based on it's compilation object and the extra recipe provided by chef-shell" do allow(@session).to receive(:node_built?).and_return(true) kitteh = Chef::Resource::Cat.new("keyboard") @recipe.run_context.resource_collection << kitteh expect(@session.resource_collection).to include(kitteh) end it "returns definitions from its compilation object" do expect(@session.definitions).to eq(@run_context.definitions) end it "keeps json attribs and passes them to the node for consumption" do @session.node_attributes = { "besnard_lakes" => "are_the_dark_horse" } expect(@session.node["besnard_lakes"]).to eq("are_the_dark_horse") #pending "1) keep attribs in an ivar 2) pass them to the node 3) feed them to the node on reset" end it "generates its resource collection from the compiled cookbooks and the ad hoc recipe" do allow(@session).to receive(:node_built?).and_return(true) kitteh_cat = Chef::Resource::Cat.new("kitteh") @run_context.resource_collection << kitteh_cat keyboard_cat = Chef::Resource::Cat.new("keyboard_cat") @recipe.run_context.resource_collection << keyboard_cat #@session.rebuild_collection expect(@session.resource_collection).to include(kitteh_cat, keyboard_cat) end it "runs chef with a resource collection from the compiled cookbooks" do allow(@session).to receive(:node_built?).and_return(true) allow(Chef::Log).to receive(:level) chef_runner = double("Chef::Runner.new", :converge => :converged) expect(Chef::Runner).to receive(:new).with(an_instance_of(Chef::RunContext)).and_return(chef_runner) expect(@recipe.run_chef).to eq(:converged) end it "passes the shell CLI args to the client" do @client = double("Chef::Client.new", :run_ohai => true, :load_node => true, :build_node => true, :register => true, :sync_cookbooks => {}) expect(Chef::Client).to receive(:new).with(nil, Chef::Config[:shell_config]).and_return(@client) @session.send(:rebuild_node) end end chef-12.14.60/spec/unit/shell_out_spec.rb000066400000000000000000000014441276456504500201410ustar00rootroot00000000000000require File.expand_path("../../spec_helper", __FILE__) describe "Chef::ShellOut deprecation notices" do it "logs a warning when initializing a new Chef::ShellOut object" do expect(Chef::Log).to receive(:warn).with("Chef::ShellOut is deprecated, please use Mixlib::ShellOut") expect(Chef::Log).to receive(:warn).with(/Called from\:/) Chef::ShellOut.new("pwd") end end describe "Chef::Exceptions::ShellCommandFailed deprecation notices" do it "logs a warning when referencing the constant Chef::Exceptions::ShellCommandFailed" do expect(Chef::Log).to receive(:warn).with("Chef::Exceptions::ShellCommandFailed is deprecated, use Mixlib::ShellOut::ShellCommandFailed") expect(Chef::Log).to receive(:warn).with(/Called from\:/) Chef::Exceptions::ShellCommandFailed end end chef-12.14.60/spec/unit/shell_spec.rb000066400000000000000000000123321276456504500172500ustar00rootroot00000000000000# Author:: Daniel DeLeo () # Copyright:: Copyright 2009-2016, Daniel DeLeo # 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 "ostruct" ObjectTestHarness = Proc.new do extend Shell::Extensions::ObjectCoreExtensions def conf=(new_conf) @conf = new_conf end def conf @conf end desc "rspecin'" def rspec_method end end class TestJobManager attr_accessor :jobs end describe Shell do before do Shell.irb_conf = {} allow(Shell::ShellSession.instance).to receive(:reset!) allow(ChefConfig).to receive(:windows?).and_return(false) allow(Chef::Util::PathHelper).to receive(:home).and_return("/home/foo") end describe "reporting its status" do it "always says it is running" do expect(Shell).to be_running end end describe "configuring IRB" do it "configures irb history" do Shell.configure_irb expect(Shell.irb_conf[:HISTORY_FILE]).to eq(Chef::Util::PathHelper.home(".chef", "chef_shell_history")) expect(Shell.irb_conf[:SAVE_HISTORY]).to eq(1000) end it "has a prompt like ``chef > '' in the default context" do Shell.configure_irb conf = OpenStruct.new conf.main = Object.new conf.main.instance_eval(&ObjectTestHarness) Shell.irb_conf[:IRB_RC].call(conf) expect(conf.prompt_c).to eq("chef > ") expect(conf.return_format).to eq(" => %s \n") expect(conf.prompt_i).to eq("chef (#{Chef::VERSION})> ") expect(conf.prompt_n).to eq("chef ?> ") expect(conf.prompt_s).to eq("chef%l> ") expect(conf.use_tracer).to eq(false) end it "has a prompt like ``chef:recipe > '' in recipe context" do Shell.configure_irb conf = OpenStruct.new events = Chef::EventDispatch::Dispatcher.new conf.main = Chef::Recipe.new(nil, nil, Chef::RunContext.new(Chef::Node.new, {}, events)) Shell.irb_conf[:IRB_RC].call(conf) expect(conf.prompt_c).to eq("chef:recipe > ") expect(conf.prompt_i).to eq("chef:recipe (#{Chef::VERSION})> ") expect(conf.prompt_n).to eq("chef:recipe ?> ") expect(conf.prompt_s).to eq("chef:recipe%l> ") end it "has a prompt like ``chef:attributes > '' in attributes/node context" do Shell.configure_irb conf = OpenStruct.new conf.main = Chef::Node.new Shell.irb_conf[:IRB_RC].call(conf) expect(conf.prompt_c).to eq("chef:attributes > ") expect(conf.prompt_i).to eq("chef:attributes (#{Chef::VERSION})> ") expect(conf.prompt_n).to eq("chef:attributes ?> ") expect(conf.prompt_s).to eq("chef:attributes%l> ") end end describe "convenience macros for creating the chef object" do before do @chef_object = Object.new @chef_object.instance_eval(&ObjectTestHarness) end it "creates help text for methods with descriptions" do expect(@chef_object.help_descriptions).to eq([Shell::Extensions::Help.new("rspec_method", "rspecin'", nil)]) end it "adds help text when a new method is described then defined" do describe_define = <<-EVAL desc "foo2the Bar" def baz end EVAL @chef_object.instance_eval describe_define expect(@chef_object.help_descriptions).to eq([Shell::Extensions::Help.new("rspec_method", "rspecin'"), Shell::Extensions::Help.new("baz", "foo2the Bar")]) end it "adds help text for subcommands" do describe_define = <<-EVAL subcommands :baz_obj_command => "something you can do with baz.baz_obj_command" def baz end EVAL @chef_object.instance_eval describe_define expected_help_text_fragments = [Shell::Extensions::Help.new("rspec_method", "rspecin'")] expected_help_text_fragments << Shell::Extensions::Help.new("baz.baz_obj_command", "something you can do with baz.baz_obj_command") expect(@chef_object.help_descriptions).to eq(expected_help_text_fragments) end it "doesn't add previous subcommand help to commands defined afterward" do describe_define = <<-EVAL desc "swingFromTree" def monkey_time end def super_monkey_time end EVAL @chef_object.instance_eval describe_define expect(@chef_object.help_descriptions.size).to eq(2) expect(@chef_object.help_descriptions.select { |h| h.cmd == "super_monkey_time" }).to be_empty end it "creates a help banner with the command descriptions" do expect(@chef_object.help_banner).to match(/^\|\ Command[\s]+\|\ Description[\s]*$/) expect(@chef_object.help_banner).to match(/^\|\ rspec_method[\s]+\|\ rspecin\'[\s]*$/) end end end chef-12.14.60/spec/unit/user_spec.rb000066400000000000000000000205041276456504500171170ustar00rootroot00000000000000# # Author:: Steven Danna (steve@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. # # DEPRECATION NOTE # This code only remains to support users still operating with # Open Source Chef Server 11 and should be removed once support # for OSC 11 ends. New development should occur in user_spec.rb. require "spec_helper" require "chef/user" require "tempfile" describe Chef::User do before(:each) do @user = Chef::User.new end describe "initialize" do it "should be a Chef::User" do expect(@user).to be_a_kind_of(Chef::User) end end describe "name" do it "should let you set the name to a string" do expect(@user.name("ops_master")).to eq("ops_master") end it "should return the current name" do @user.name "ops_master" expect(@user.name).to eq("ops_master") end # It is not feasible to check all invalid characters. Here are a few # that we probably care about. it "should not accept invalid characters" do # capital letters expect { @user.name "Bar" }.to raise_error(ArgumentError) # slashes expect { @user.name "foo/bar" }.to raise_error(ArgumentError) # ? expect { @user.name "foo?" }.to raise_error(ArgumentError) # & expect { @user.name "foo&" }.to raise_error(ArgumentError) end it "should not accept spaces" do expect { @user.name "ops master" }.to raise_error(ArgumentError) end it "should throw an ArgumentError if you feed it anything but a string" do expect { @user.name Hash.new }.to raise_error(ArgumentError) end end describe "admin" do it "should let you set the admin bit" do expect(@user.admin(true)).to eq(true) end it "should return the current admin value" do @user.admin true expect(@user.admin).to eq(true) end it "should default to false" do expect(@user.admin).to eq(false) end it "should throw an ArgumentError if you feed it anything but true or false" do expect { @user.name Hash.new }.to raise_error(ArgumentError) end end describe "public_key" do it "should let you set the public key" do expect(@user.public_key("super public")).to eq("super public") end it "should return the current public key" do @user.public_key("super public") expect(@user.public_key).to eq("super public") end it "should throw an ArgumentError if you feed it something lame" do expect { @user.public_key Hash.new }.to raise_error(ArgumentError) end end describe "private_key" do it "should let you set the private key" do expect(@user.private_key("super private")).to eq("super private") end it "should return the private key" do @user.private_key("super private") expect(@user.private_key).to eq("super private") end it "should throw an ArgumentError if you feed it something lame" do expect { @user.private_key Hash.new }.to raise_error(ArgumentError) end end describe "when serializing to JSON" do before(:each) do @user.name("black") @user.public_key("crowes") @json = @user.to_json end it "serializes as a JSON object" do expect(@json).to match(/^\{.+\}$/) end it "includes the name value" do expect(@json).to include(%q{"name":"black"}) end it "includes the public key value" do expect(@json).to include(%{"public_key":"crowes"}) end it "includes the 'admin' flag" do expect(@json).to include(%q{"admin":false}) end it "includes the private key when present" do @user.private_key("monkeypants") expect(@user.to_json).to include(%q{"private_key":"monkeypants"}) end it "does not include the private key if not present" do expect(@json).not_to include("private_key") end it "includes the password if present" do @user.password "password" expect(@user.to_json).to include(%q{"password":"password"}) end it "does not include the password if not present" do expect(@json).not_to include("password") end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @user } end end describe "when deserializing from JSON" do before(:each) do user = { "name" => "mr_spinks", "public_key" => "turtles", "private_key" => "pandas", "password" => "password", "admin" => true } @user = Chef::User.from_json(Chef::JSONCompat.to_json(user)) end it "should deserialize to a Chef::User object" do expect(@user).to be_a_kind_of(Chef::User) end it "preserves the name" do expect(@user.name).to eq("mr_spinks") end it "preserves the public key" do expect(@user.public_key).to eq("turtles") end it "preserves the admin status" do expect(@user.admin).to be_truthy end it "includes the private key if present" do expect(@user.private_key).to eq("pandas") end it "includes the password if present" do expect(@user.password).to eq("password") end end describe "API Interactions" do before (:each) do @user = Chef::User.new @user.name "foobar" @http_client = double("Chef::ServerAPI mock") allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) end describe "list" do before(:each) do Chef::Config[:chef_server_url] = "http://www.example.com" @osc_response = { "admin" => "http://www.example.com/users/admin" } @ohc_response = [ { "user" => { "username" => "admin" } } ] allow(Chef::User).to receive(:load).with("admin").and_return(@user) @osc_inflated_response = { "admin" => @user } end it "lists all clients on an OSC server" do allow(@http_client).to receive(:get).with("users").and_return(@osc_response) expect(Chef::User.list).to eq(@osc_response) end it "inflate all clients on an OSC server" do allow(@http_client).to receive(:get).with("users").and_return(@osc_response) expect(Chef::User.list(true)).to eq(@osc_inflated_response) end it "lists all clients on an OHC/OPC server" do allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) # We expect that Chef::User.list will give a consistent response # so OHC API responses should be transformed to OSC-style output. expect(Chef::User.list).to eq(@osc_response) end it "inflate all clients on an OHC/OPC server" do allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) expect(Chef::User.list(true)).to eq(@osc_inflated_response) end end describe "create" do it "creates a new user via the API" do @user.password "password" expect(@http_client).to receive(:post).with("users", { :name => "foobar", :admin => false, :password => "password" }).and_return({}) @user.create end end describe "read" do it "loads a named user from the API" do expect(@http_client).to receive(:get).with("users/foobar").and_return({ "name" => "foobar", "admin" => true, "public_key" => "pubkey" }) user = Chef::User.load("foobar") expect(user.name).to eq("foobar") expect(user.admin).to eq(true) expect(user.public_key).to eq("pubkey") end end describe "update" do it "updates an existing user on via the API" do expect(@http_client).to receive(:put).with("users/foobar", { :name => "foobar", :admin => false }).and_return({}) @user.update end end describe "destroy" do it "deletes the specified user via the API" do expect(@http_client).to receive(:delete).with("users/foobar") @user.destroy end end end end chef-12.14.60/spec/unit/user_v1_spec.rb000066400000000000000000000462121276456504500175310ustar00rootroot00000000000000# # Author:: Steven Danna (steve@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/user_v1" require "tempfile" describe Chef::UserV1 do before(:each) do @user = Chef::UserV1.new end shared_examples_for "string fields with no contraints" do it "should let you set the public key" do expect(@user.send(method, "some_string")).to eq("some_string") end it "should return the current public key" do @user.send(method, "some_string") expect(@user.send(method)).to eq("some_string") end it "should throw an ArgumentError if you feed it something lame" do expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError) end end shared_examples_for "boolean fields with no constraints" do it "should let you set the field" do expect(@user.send(method, true)).to eq(true) end it "should return the current field value" do @user.send(method, true) expect(@user.send(method)).to eq(true) end it "should return the false value when false" do @user.send(method, false) expect(@user.send(method)).to eq(false) end it "should throw an ArgumentError if you feed it anything but true or false" do expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError) end end describe "initialize" do it "should be a Chef::UserV1" do expect(@user).to be_a_kind_of(Chef::UserV1) end end describe "username" do it "should let you set the username to a string" do expect(@user.username("ops_master")).to eq("ops_master") end it "should return the current username" do @user.username "ops_master" expect(@user.username).to eq("ops_master") end # It is not feasible to check all invalid characters. Here are a few # that we probably care about. it "should not accept invalid characters" do # capital letters expect { @user.username "Bar" }.to raise_error(ArgumentError) # slashes expect { @user.username "foo/bar" }.to raise_error(ArgumentError) # ? expect { @user.username "foo?" }.to raise_error(ArgumentError) # & expect { @user.username "foo&" }.to raise_error(ArgumentError) end it "should not accept spaces" do expect { @user.username "ops master" }.to raise_error(ArgumentError) end it "should throw an ArgumentError if you feed it anything but a string" do expect { @user.username Hash.new }.to raise_error(ArgumentError) end end describe "boolean fields" do describe "create_key" do it_should_behave_like "boolean fields with no constraints" do let(:method) { :create_key } end end end describe "string fields" do describe "public_key" do it_should_behave_like "string fields with no contraints" do let(:method) { :public_key } end end describe "private_key" do it_should_behave_like "string fields with no contraints" do let(:method) { :private_key } end end describe "display_name" do it_should_behave_like "string fields with no contraints" do let(:method) { :display_name } end end describe "first_name" do it_should_behave_like "string fields with no contraints" do let(:method) { :first_name } end end describe "middle_name" do it_should_behave_like "string fields with no contraints" do let(:method) { :middle_name } end end describe "last_name" do it_should_behave_like "string fields with no contraints" do let(:method) { :last_name } end end describe "email" do it_should_behave_like "string fields with no contraints" do let(:method) { :email } end end describe "password" do it_should_behave_like "string fields with no contraints" do let(:method) { :password } end end end describe "when serializing to JSON" do before(:each) do @user.username("black") @json = @user.to_json end it "serializes as a JSON object" do expect(@json).to match(/^\{.+\}$/) end it "includes the username value" do expect(@json).to include(%q{"username":"black"}) end it "includes the display name when present" do @user.display_name("get_displayed") expect(@user.to_json).to include(%{"display_name":"get_displayed"}) end it "does not include the display name if not present" do expect(@json).not_to include("display_name") end it "includes the first name when present" do @user.first_name("char") expect(@user.to_json).to include(%{"first_name":"char"}) end it "does not include the first name if not present" do expect(@json).not_to include("first_name") end it "includes the middle name when present" do @user.middle_name("man") expect(@user.to_json).to include(%{"middle_name":"man"}) end it "does not include the middle name if not present" do expect(@json).not_to include("middle_name") end it "includes the last name when present" do @user.last_name("der") expect(@user.to_json).to include(%{"last_name":"der"}) end it "does not include the last name if not present" do expect(@json).not_to include("last_name") end it "includes the email when present" do @user.email("charmander@pokemon.poke") expect(@user.to_json).to include(%{"email":"charmander@pokemon.poke"}) end it "does not include the email if not present" do expect(@json).not_to include("email") end it "includes the public key when present" do @user.public_key("crowes") expect(@user.to_json).to include(%{"public_key":"crowes"}) end it "does not include the public key if not present" do expect(@json).not_to include("public_key") end it "includes the private key when present" do @user.private_key("monkeypants") expect(@user.to_json).to include(%q{"private_key":"monkeypants"}) end it "does not include the private key if not present" do expect(@json).not_to include("private_key") end it "includes the password if present" do @user.password "password" expect(@user.to_json).to include(%q{"password":"password"}) end it "does not include the password if not present" do expect(@json).not_to include("password") end include_examples "to_json equivalent to Chef::JSONCompat.to_json" do let(:jsonable) { @user } end end describe "when deserializing from JSON" do before(:each) do user = { "username" => "mr_spinks", "display_name" => "displayed", "first_name" => "char", "middle_name" => "man", "last_name" => "der", "email" => "charmander@pokemon.poke", "password" => "password", "public_key" => "turtles", "private_key" => "pandas", "create_key" => false, } @user = Chef::UserV1.from_json(Chef::JSONCompat.to_json(user)) end it "should deserialize to a Chef::UserV1 object" do expect(@user).to be_a_kind_of(Chef::UserV1) end it "preserves the username" do expect(@user.username).to eq("mr_spinks") end it "preserves the display name if present" do expect(@user.display_name).to eq("displayed") end it "preserves the first name if present" do expect(@user.first_name).to eq("char") end it "preserves the middle name if present" do expect(@user.middle_name).to eq("man") end it "preserves the last name if present" do expect(@user.last_name).to eq("der") end it "preserves the email if present" do expect(@user.email).to eq("charmander@pokemon.poke") end it "includes the password if present" do expect(@user.password).to eq("password") end it "preserves the public key if present" do expect(@user.public_key).to eq("turtles") end it "includes the private key if present" do expect(@user.private_key).to eq("pandas") end it "includes the create key status if not nil" do expect(@user.create_key).to be_falsey end end describe "Versioned API Interactions" do let(:response_406) { OpenStruct.new(:code => "406") } let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) } before (:each) do @user = Chef::UserV1.new allow(@user).to receive(:chef_root_rest_v0).and_return(double("chef rest root v0 object")) allow(@user).to receive(:chef_root_rest_v1).and_return(double("chef rest root v1 object")) end describe "update" do before do # populate all fields that are valid between V0 and V1 @user.username "some_username" @user.display_name "some_display_name" @user.first_name "some_first_name" @user.middle_name "some_middle_name" @user.last_name "some_last_name" @user.email "some_email" @user.password "some_password" end let(:payload) do { :username => "some_username", :display_name => "some_display_name", :first_name => "some_first_name", :middle_name => "some_middle_name", :last_name => "some_last_name", :email => "some_email", :password => "some_password", } end context "when server API V1 is valid on the Chef Server receiving the request" do context "when the user submits valid data" do it "properly updates the user" do expect(@user.chef_root_rest_v1).to receive(:put).with("users/some_username", payload).and_return({}) @user.update end end end context "when server API V1 is not valid on the Chef Server receiving the request" do let(:payload) do { :username => "some_username", :display_name => "some_display_name", :first_name => "some_first_name", :middle_name => "some_middle_name", :last_name => "some_last_name", :email => "some_email", :password => "some_password", :public_key => "some_public_key", } end before do @user.public_key "some_public_key" allow(@user.chef_root_rest_v1).to receive(:put) end context "when the server returns a 400" do let(:response_400) { OpenStruct.new(:code => "400") } let(:exception_400) { Net::HTTPServerException.new("400 Bad Request", response_400) } context "when the 400 was due to public / private key fields no longer being supported" do let(:response_body_400) { '{"error":["Since Server API v1, all keys must be updated via the keys endpoint. "]}' } before do allow(response_400).to receive(:body).and_return(response_body_400) allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400) end it "proceeds with the V0 PUT since it can handle public / private key fields" do expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({}) @user.update end it "does not call server_client_api_version_intersection, since we know to proceed with V0 in this case" do expect(@user).to_not receive(:server_client_api_version_intersection) allow(@user.chef_root_rest_v0).to receive(:put).and_return({}) @user.update end end # when the 400 was due to public / private key fields context "when the 400 was NOT due to public / private key fields no longer being supported" do let(:response_body_400) { '{"error":["Some other error. "]}' } before do allow(response_400).to receive(:body).and_return(response_body_400) allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400) end it "will not proceed with the V0 PUT since the original bad request was not key related" do expect(@user.chef_root_rest_v0).to_not receive(:put).with("users/some_username", payload) expect { @user.update }.to raise_error(exception_400) end it "raises the original error" do expect { @user.update }.to raise_error(exception_400) end end end # when the server returns a 400 context "when the server returns a 406" do # from spec/support/shared/unit/api_versioning.rb it_should_behave_like "version handling" do let(:object) { @user } let(:method) { :update } let(:http_verb) { :put } let(:rest_v1) { @user.chef_root_rest_v1 } end context "when the server supports API V0" do before do allow(@user).to receive(:server_client_api_version_intersection).and_return([0]) allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_406) end it "properly updates the user" do expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({}) @user.update end end # when the server supports API V0 end # when the server returns a 406 end # when server API V1 is not valid on the Chef Server receiving the request end # update describe "create" do let(:payload) do { :username => "some_username", :display_name => "some_display_name", :first_name => "some_first_name", :last_name => "some_last_name", :email => "some_email", :password => "some_password", } end before do @user.username "some_username" @user.display_name "some_display_name" @user.first_name "some_first_name" @user.last_name "some_last_name" @user.email "some_email" @user.password "some_password" end # from spec/support/shared/unit/user_and_client_shared.rb it_should_behave_like "user or client create" do let(:object) { @user } let(:error) { Chef::Exceptions::InvalidUserAttribute } let(:rest_v0) { @user.chef_root_rest_v0 } let(:rest_v1) { @user.chef_root_rest_v1 } let(:url) { "users" } end context "when handling API V1" do it "creates a new user via the API with a middle_name when it exists" do @user.middle_name "some_middle_name" expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload.merge({ :middle_name => "some_middle_name" })).and_return({}) @user.create end end # when server API V1 is valid on the Chef Server receiving the request context "when API V1 is not supported by the server" do # from spec/support/shared/unit/api_versioning.rb it_should_behave_like "version handling" do let(:object) { @user } let(:method) { :create } let(:http_verb) { :post } let(:rest_v1) { @user.chef_root_rest_v1 } end end context "when handling API V0" do before do allow(@user).to receive(:server_client_api_version_intersection).and_return([0]) allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406) end it "creates a new user via the API with a middle_name when it exists" do @user.middle_name "some_middle_name" expect(@user.chef_root_rest_v0).to receive(:post).with("users", payload.merge({ :middle_name => "some_middle_name" })).and_return({}) @user.create end end # when server API V1 is not valid on the Chef Server receiving the request end # create # DEPRECATION # This can be removed after API V0 support is gone describe "reregister" do let(:payload) do { "username" => "some_username", } end before do @user.username "some_username" end context "when server API V0 is valid on the Chef Server receiving the request" do it "creates a new object via the API" do expect(@user.chef_root_rest_v0).to receive(:put).with("users/#{@user.username}", payload.merge({ "private_key" => true })).and_return({}) @user.reregister end end # when server API V0 is valid on the Chef Server receiving the request context "when server API V0 is not supported by the Chef Server" do # from spec/support/shared/unit/api_versioning.rb it_should_behave_like "user and client reregister" do let(:object) { @user } let(:rest_v0) { @user.chef_root_rest_v0 } end end # when server API V0 is not supported by the Chef Server end # reregister end # Versioned API Interactions describe "API Interactions" do before (:each) do @user = Chef::UserV1.new @user.username "foobar" @http_client = double("Chef::ServerAPI mock") allow(Chef::ServerAPI).to receive(:new).and_return(@http_client) end describe "list" do before(:each) do Chef::Config[:chef_server_url] = "http://www.example.com" @osc_response = { "admin" => "http://www.example.com/users/admin" } @ohc_response = [ { "user" => { "username" => "admin" } } ] allow(Chef::UserV1).to receive(:load).with("admin").and_return(@user) @osc_inflated_response = { "admin" => @user } end it "lists all clients on an OHC/OPC server" do allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) # We expect that Chef::UserV1.list will give a consistent response # so OHC API responses should be transformed to OSC-style output. expect(Chef::UserV1.list).to eq(@osc_response) end it "inflate all clients on an OHC/OPC server" do allow(@http_client).to receive(:get).with("users").and_return(@ohc_response) expect(Chef::UserV1.list(true)).to eq(@osc_inflated_response) end end describe "read" do it "loads a named user from the API" do expect(@http_client).to receive(:get).with("users/foobar").and_return({ "username" => "foobar", "admin" => true, "public_key" => "pubkey" }) user = Chef::UserV1.load("foobar") expect(user.username).to eq("foobar") expect(user.public_key).to eq("pubkey") end end describe "destroy" do it "deletes the specified user via the API" do expect(@http_client).to receive(:delete).with("users/foobar") @user.destroy end end end end chef-12.14.60/spec/unit/util/000077500000000000000000000000001276456504500155565ustar00rootroot00000000000000chef-12.14.60/spec/unit/util/backup_spec.rb000066400000000000000000000120611276456504500203620ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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 "tmpdir" describe Chef::Util::Backup do let (:tempfile) do Tempfile.new("chef-util-backup-spec-test") end before(:each) do @new_resource = double("new_resource") expect(@new_resource).to receive(:path).at_least(:once).and_return(tempfile.path) @backup = Chef::Util::Backup.new(@new_resource) end it "should store the resource passed to new as new_resource" do expect(@backup.new_resource).to eql(@new_resource) end describe "for cases when we don't want to back anything up" do before(:each) do expect(@backup).not_to receive(:do_backup) end it "should not attempt to backup a file if :backup is false" do expect(@new_resource).to receive(:backup).at_least(:once).and_return(false) @backup.backup! end it "should not attempt to backup a file if :backup == 0" do expect(@new_resource).to receive(:backup).at_least(:once).and_return(0) @backup.backup! end it "should not attempt to backup a file if it does not exist" do expect(@new_resource).to receive(:backup).at_least(:once).and_return(1) expect(File).to receive(:exist?).with(tempfile.path).at_least(:once).and_return(false) @backup.backup! end end describe "for cases when we want to back things up" do before(:each) do expect(@backup).to receive(:do_backup) end describe "when the number of backups is specified as 1" do before(:each) do expect(@new_resource).to receive(:backup).at_least(:once).and_return(1) end it "should not delete anything if this is the only backup" do expect(@backup).to receive(:sorted_backup_files).and_return(["a"]) expect(@backup).not_to receive(:delete_backup) @backup.backup! end it "should keep only 1 backup copy" do expect(@backup).to receive(:sorted_backup_files).and_return(%w{a b c}) expect(@backup).to receive(:delete_backup).with("b") expect(@backup).to receive(:delete_backup).with("c") @backup.backup! end end describe "when the number of backups is specified as 2" do before(:each) do expect(@new_resource).to receive(:backup).at_least(:once).and_return(2) end it "should not delete anything if we only have one other backup" do expect(@backup).to receive(:sorted_backup_files).and_return(%w{a b}) expect(@backup).not_to receive(:delete_backup) @backup.backup! end it "should keep only 2 backup copies" do expect(@backup).to receive(:sorted_backup_files).and_return(%w{a b c d}) expect(@backup).to receive(:delete_backup).with("c") expect(@backup).to receive(:delete_backup).with("d") @backup.backup! end end end describe "backup_filename" do it "should return a timestamped path" do expect(@backup).to receive(:path).and_return("/a/b/c.txt") expect(@backup.send(:backup_filename)).to match(%r|^/a/b/c.txt.chef-\d{14}.\d{6}$|) end it "should strip the drive letter off for windows" do expect(@backup).to receive(:path).and_return('c:\a\b\c.txt') expect(@backup.send(:backup_filename)).to match(%r|^\\a\\b\\c.txt.chef-\d{14}.\d{6}$|) end it "should strip the drive letter off for windows (with forwardslashes)" do expect(@backup).to receive(:path).and_return("c:/a/b/c.txt") expect(@backup.send(:backup_filename)).to match(%r|^/a/b/c.txt.chef-\d{14}.\d{6}$|) end end describe "backup_path" do it "uses the file's directory when Chef::Config[:file_backup_path] is nil" do expect(@backup).to receive(:path).and_return("/a/b/c.txt") Chef::Config[:file_backup_path] = nil expect(@backup.send(:backup_path)).to match(%r|^/a/b/c.txt.chef-\d{14}.\d{6}$|) end it "uses the configured Chef::Config[:file_backup_path]" do expect(@backup).to receive(:path).and_return("/a/b/c.txt") Chef::Config[:file_backup_path] = "/backupdir" expect(@backup.send(:backup_path)).to match(%r|^/backupdir[\\/]+a/b/c.txt.chef-\d{14}.\d{6}$|) end it "uses the configured Chef::Config[:file_backup_path] and strips the drive on windows" do expect(@backup).to receive(:path).and_return('c:\\a\\b\\c.txt') Chef::Config[:file_backup_path] = 'c:\backupdir' expect(@backup.send(:backup_path)).to match(%r|^c:\\backupdir[\\/]+a\\b\\c.txt.chef-\d{14}.\d{6}$|) end end end chef-12.14.60/spec/unit/util/diff_spec.rb000066400000000000000000000431621276456504500200330ustar00rootroot00000000000000# # Author:: Lamont Granquist () # Copyright:: Copyright 2013-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 "tmpdir" shared_context "using file paths with spaces" do let!(:old_tempfile) { Tempfile.new("chef-util diff-spec") } let!(:new_tempfile) { Tempfile.new("chef-util diff-spec") } end shared_context "using file paths without spaces" do let!(:old_tempfile) { Tempfile.new("chef-util-diff-spec") } let!(:new_tempfile) { Tempfile.new("chef-util-diff-spec") } end shared_examples_for "a diff util" do it "should return a Chef::Util::Diff" do expect(differ).to be_a_kind_of(Chef::Util::Diff) end it "produces a diff even if the old_file does not exist" do old_tempfile.close old_tempfile.unlink expect(differ.for_output).to eql(["(no diff)"]) end it "produces a diff even if the new_file does not exist" do new_tempfile.close new_tempfile.unlink expect(differ.for_output).to eql(["(no diff)"]) end describe "when the two files exist with no content" do it "calling for_output should return the error message" do expect(differ.for_output).to eql(["(no diff)"]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end describe "when diffs are disabled" do before do Chef::Config[:diff_disabled] = true end after do Chef::Config[:diff_disabled] = false end it "calling for_output should return the error message" do expect(differ.for_output).to eql( [ "(diff output suppressed by config)" ] ) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end describe "when the old_file has binary content" do before do old_tempfile.write("#{0x01.chr}#{0xFF.chr}") old_tempfile.close end it "calling for_output should return the error message" do expect(differ.for_output).to eql( [ "(current file is binary, diff output suppressed)" ] ) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end describe "when the new_file has binary content" do before do new_tempfile.write("#{0x01.chr}#{0xFF.chr}") new_tempfile.close end it "calling for_output should return the error message" do expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end describe "when the default external encoding is UTF-8" do before do @saved_default_external = Encoding.default_external Encoding.default_external = Encoding::UTF_8 end after do Encoding.default_external = @saved_default_external end describe "when a file has ASCII text" do before do new_tempfile.write(plain_ascii) new_tempfile.close end it "calling for_output should return a valid diff" do expect(differ.for_output.join("\\n")).to match(/\A--- .*\\n\+\+\+ .*\\n@@/m) end it "calling for_reporting should return a utf-8 string" do expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8) end end describe "when a file has UTF-8 text" do before do new_tempfile.write(utf_8) new_tempfile.close end it "calling for_output should return a valid diff" do expect(differ.for_output.join("\\n")).to match(/\A--- .*\\n\+\+\+ .*\\n@@/m) end it "calling for_reporting should return a utf-8 string" do expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8) end end describe "when a file has Latin-1 text" do before do new_tempfile.write(latin_1) new_tempfile.close end it "calling for_output should complain that the content is binary" do expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end describe "when a file has Shift-JIS text" do before do new_tempfile.write(shift_jis) new_tempfile.close end it "calling for_output should complain that the content is binary" do expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end end describe "when the default external encoding is Latin-1" do before do @saved_default_external = Encoding.default_external Encoding.default_external = Encoding::ISO_8859_1 end after do Encoding.default_external = @saved_default_external end describe "when a file has ASCII text" do before do new_tempfile.write(plain_ascii) new_tempfile.close end it "calling for_output should return a valid diff" do expect(differ.for_output.join("\\n")).to match(/\A--- .*\\n\+\+\+ .*\\n@@/m) end it "calling for_reporting should return a utf-8 string" do expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8) end end describe "when a file has UTF-8 text" do before do new_tempfile.write(utf_8) new_tempfile.close end it "calling for_output should complain that the content is binary" do expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end describe "when a file has Latin-1 text" do before do new_tempfile.write(latin_1) new_tempfile.close end it "calling for_output should return a valid diff" do expect(differ.for_output.join("\\n")).to match(/\A--- .*\\n\+\+\+ .*\\n@@/m) end it "calling for_reporting should return a utf-8 string" do expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8) end end describe "when a file has Shift-JIS text" do before do new_tempfile.write(shift_jis) new_tempfile.close end it "calling for_output should complain that the content is binary" do expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end end describe "when the default external encoding is Shift_JIS" do before do @saved_default_external = Encoding.default_external Encoding.default_external = Encoding::Shift_JIS end after do Encoding.default_external = @saved_default_external end describe "when a file has ASCII text" do before do new_tempfile.write(plain_ascii) new_tempfile.close end it "calling for_output should return a valid diff" do expect(differ.for_output.join("\\n")).to match(/\A--- .*\\n\+\+\+ .*\\n@@/m) end it "calling for_reporting should return a utf-8 string" do expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8) end end describe "when a file has UTF-8 text" do before do new_tempfile.write(utf_8) new_tempfile.close end it "calling for_output should complain that the content is binary" do expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end describe "when a file has Latin-1 text" do before do new_tempfile.write(latin_1) new_tempfile.close end it "calling for_output should complain that the content is binary" do expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end describe "when a file has Shift-JIS text" do before do new_tempfile.write(shift_jis) new_tempfile.close end it "calling for_output should return a valid diff" do expect(differ.for_output.join("\\n")).to match(/\A--- .*\\n\+\+\+ .*\\n@@/m) end it "calling for_reporting should return a utf-8 string" do expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8) end end end describe "when testing the diff_filesize_threshold" do before do @diff_filesize_threshold_saved = Chef::Config[:diff_filesize_threshold] Chef::Config[:diff_filesize_threshold] = 10 end after do Chef::Config[:diff_filesize_threshold] = @diff_filesize_threshold_saved end describe "when the old_file goes over the threshold" do before do old_tempfile.write("But thats what you get when Wu-Tang raised you") old_tempfile.close end it "calling for_output should return the error message" do expect(differ.for_output).to eql( [ "(file sizes exceed 10 bytes, diff output suppressed)" ]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end describe "when the new_file goes over the threshold" do before do new_tempfile.write("But thats what you get when Wu-Tang raised you") new_tempfile.close end it "calling for_output should return the error message" do expect(differ.for_output).to eql( [ "(file sizes exceed 10 bytes, diff output suppressed)" ]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end end describe "when generating a valid diff" do before do old_tempfile.write("foo") old_tempfile.close new_tempfile.write("bar") new_tempfile.close end it "calling for_output should return a unified diff" do expect(differ.for_output.size).to eql(5) expect(differ.for_output.join("\\n")).to match(/\A--- .*\\n\+\+\+ .*\\n@@/m) end it "calling for_reporting should return a unified diff" do expect(differ.for_reporting).to match(/\A--- .*\\n\+\+\+ .*\\n@@/m) end describe "when the diff output is too long" do before do @diff_output_threshold_saved = Chef::Config[:diff_output_threshold] Chef::Config[:diff_output_threshold] = 10 end after do Chef::Config[:diff_output_threshold] = @diff_output_threshold_saved end it "calling for_output should return the error message" do expect(differ.for_output).to eql(["(long diff of over 10 characters, diff output suppressed)"]) end it "calling for_reporting should be nil" do expect(differ.for_reporting).to be_nil end end end describe "when checking if files are binary or text" do it "should identify zero-length files as text" do Tempfile.open("chef-util-diff-spec") do |file| file.close expect(differ.send(:is_binary?, file.path)).to be_falsey end end it "should identify text files as text" do Tempfile.open("chef-util-diff-spec") do |file| file.write(plain_ascii) file.close expect(differ.send(:is_binary?, file.path)).to be_falsey end end it "should identify a null-terminated string files as binary" do Tempfile.open("chef-util-diff-spec") do |file| file.write("This is a binary file.\0") file.close expect(differ.send(:is_binary?, file.path)).to be_truthy end end it "should identify null-teriminated multi-line string files as binary" do Tempfile.open("chef-util-diff-spec") do |file| file.write("This is a binary file.\nNo Really\nit is\0") file.close expect(differ.send(:is_binary?, file.path)).to be_truthy end end describe "when the default external encoding is UTF-8" do before do @saved_default_external = Encoding.default_external Encoding.default_external = Encoding::UTF_8 end after do Encoding.default_external = @saved_default_external end it "should identify normal ASCII as text" do Tempfile.open("chef-util-diff-spec") do |file| file.write(plain_ascii) file.close expect(differ.send(:is_binary?, file.path)).to be_falsey end end it "should identify UTF-8 as text" do Tempfile.open("chef-util-diff-spec") do |file| file.write(utf_8) file.close expect(differ.send(:is_binary?, file.path)).to be_falsey end end it "should identify Latin-1 that is invalid UTF-8 as binary" do Tempfile.open("chef-util-diff-spec") do |file| file.write(latin_1) file.close expect(differ.send(:is_binary?, file.path)).to be_truthy end end it "should identify Shift-JIS that is invalid UTF-8 as binary" do Tempfile.open("chef-util-diff-spec") do |file| file.write(shift_jis) file.close expect(differ.send(:is_binary?, file.path)).to be_truthy end end end describe "when the default external encoding is Latin-1" do before do @saved_default_external = Encoding.default_external Encoding.default_external = Encoding::ISO_8859_1 end after do Encoding.default_external = @saved_default_external end it "should identify normal ASCII as text" do Tempfile.open("chef-util-diff-spec") do |file| file.write(plain_ascii) file.close expect(differ.send(:is_binary?, file.path)).to be_falsey end end it "should identify UTF-8 that is invalid Latin-1 as binary" do Tempfile.open("chef-util-diff-spec") do |file| file.write(utf_8) file.close expect(differ.send(:is_binary?, file.path)).to be_truthy end end it "should identify Latin-1 as text" do Tempfile.open("chef-util-diff-spec") do |file| file.write(latin_1) file.close expect(differ.send(:is_binary?, file.path)).to be_falsey end end it "should identify Shift-JIS that is invalid Latin-1 as binary" do Tempfile.open("chef-util-diff-spec") do |file| file.write(shift_jis) file.close expect(differ.send(:is_binary?, file.path)).to be_truthy end end end describe "when the default external encoding is Shift-JIS" do before do @saved_default_external = Encoding.default_external Encoding.default_external = Encoding::Shift_JIS end after do Encoding.default_external = @saved_default_external end it "should identify normal ASCII as text" do Tempfile.open("chef-util-diff-spec") do |file| file.write(plain_ascii) file.close expect(differ.send(:is_binary?, file.path)).to be_falsey end end it "should identify UTF-8 that is invalid Shift-JIS as binary" do Tempfile.open("chef-util-diff-spec") do |file| file.write(utf_8) file.close expect(differ.send(:is_binary?, file.path)).to be_truthy end end it "should identify Latin-1 that is invalid Shift-JIS as binary" do Tempfile.open("chef-util-diff-spec") do |file| file.write(latin_1) file.close expect(differ.send(:is_binary?, file.path)).to be_truthy end end it "should identify Shift-JIS as text" do Tempfile.open("chef-util-diff-spec") do |file| file.write(shift_jis) file.close expect(differ.send(:is_binary?, file.path)).to be_falsey end end end end end describe Chef::Util::Diff, :uses_diff => true do let!(:old_file) { old_tempfile.path } let!(:new_file) { new_tempfile.path } let(:plain_ascii) { "This is a text file.\nWith more than one line.\nAnd a \tTab.\nAnd lets make sure that other printable chars work too: ~!@\#$%^&*()`:\"<>?{}|_+,./;'[]\\-=\n" } # these are all byte sequences that are illegal in the other encodings... (but they may legally transcode) let(:utf_8) { "testing utf-8 unicode...\n\n\non a new line: \xE2\x80\x93\n" } # unicode em-dash let(:latin_1) { "It is more metal.\nif you have an #{0xFD.chr}mlaut.\n" } # NB: changed to y-with-diaresis, but i'm American so I don't know the difference let(:shift_jis) { "I have no idea what this character is:\n #{0x83.chr}#{0x80.chr}.\n" } # seriously, no clue, but \x80 is nice and illegal in other encodings let(:differ) do # subject differ = Chef::Util::Diff.new differ.diff(old_file, new_file) differ end describe "when file path has spaces" do include_context "using file paths with spaces" it_behaves_like "a diff util" end describe "when file path doesn't have spaces" do include_context "using file paths without spaces" it_behaves_like "a diff util" end end chef-12.14.60/spec/unit/util/dsc/000077500000000000000000000000001276456504500163275ustar00rootroot00000000000000chef-12.14.60/spec/unit/util/dsc/configuration_generator_spec.rb000066400000000000000000000170361276456504500246120ustar00rootroot00000000000000# # Author:: Jay Mundrawala # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef" require "chef/util/dsc/configuration_generator" describe Chef::Util::DSC::ConfigurationGenerator do let(:conf_man) do node = Chef::Node.new Chef::Util::DSC::ConfigurationGenerator.new(node, "tmp") end describe "#validate_configuration_name!" do it "should not raise an error if a name contains all upper case letters" do conf_man.send(:validate_configuration_name!, "HELLO") end it "should not raise an error if the name contains all lower case letters" do conf_man.send(:validate_configuration_name!, "hello") end it "should not raise an error if no special characters are used except _" do conf_man.send(:validate_configuration_name!, "hello_world") end %w{! @ # $ % ^ & * & * ( ) - = + \{ \} . ? < > \\ /}.each do |sym| it "raises an Argument error if it configuration name contains #{sym}" do expect do conf_man.send(:validate_configuration_name!, "Hello#{sym}") end.to raise_error(ArgumentError) end end end describe "#get_merged_configuration_flags" do context "when strings are used as switches" do it "should merge the hash if there are no restricted switches" do merged = conf_man.send(:get_merged_configuration_flags!, { "flag" => "a" }, "hello") expect(merged).to include(:flag) expect(merged[:flag]).to eql("a") expect(merged).to include(:outputpath) end it "should raise an ArgumentError if you try to override outputpath" do expect do conf_man.send(:get_merged_configuration_flags!, { "outputpath" => "a" }, "hello") end.to raise_error(ArgumentError) end it "should be case insensitive for switches that are not allowed" do expect do conf_man.send(:get_merged_configuration_flags!, { "OutputPath" => "a" }, "hello") end.to raise_error(ArgumentError) end it "should be case insensitive to switches that are allowed" do merged = conf_man.send(:get_merged_configuration_flags!, { "FLAG" => "a" }, "hello") expect(merged).to include(:flag) end end context "when symbols are used as switches" do it "should merge the hash if there are no restricted switches" do merged = conf_man.send(:get_merged_configuration_flags!, { :flag => "a" }, "hello") expect(merged).to include(:flag) expect(merged[:flag]).to eql("a") expect(merged).to include(:outputpath) end it "should raise an ArgumentError if you try to override outputpath" do expect do conf_man.send(:get_merged_configuration_flags!, { :outputpath => "a" }, "hello") end.to raise_error(ArgumentError) end it "should be case insensitive for switches that are not allowed" do expect do conf_man.send(:get_merged_configuration_flags!, { :OutputPath => "a" }, "hello") end.to raise_error(ArgumentError) end it "should be case insensitive to switches that are allowed" do merged = conf_man.send(:get_merged_configuration_flags!, { :FLAG => "a" }, "hello") expect(merged).to include(:flag) end end context "when there are no flags" do it "should supply an output path if configuration_flags is an empty hash" do merged = conf_man.send(:get_merged_configuration_flags!, {}, "hello") expect(merged).to include(:outputpath) expect(merged.length).to eql(1) end it "should supply an output path if configuration_flags is an empty hash" do merged = conf_man.send(:get_merged_configuration_flags!, nil, "hello") expect(merged).to include(:outputpath) expect(merged.length).to eql(1) end end # What should happen if configuration flags contains duplicates? # flagA => 'a', flaga => 'a' # or # flagA => 'a', flaga => 'b' # end describe "#write_document_generation_script" do let(:file_like_object) { double("file like object") } it "should write the input to a file" do allow(File).to receive(:open).and_yield(file_like_object) allow(File).to receive(:join) do |a, b| [a, b].join("++") end allow(file_like_object).to receive(:write) conf_man.send(:write_document_generation_script, "file", "hello", {}) expect(file_like_object).to have_received(:write) end end describe "#find_configuration_document" do it "should find the mof file" do # These tests seem way too implementation specific. Unfortunately, File and Dir # need to be mocked because they are OS specific allow(File).to receive(:join) do |a, b| [a, b].join("++") end allow(Dir).to receive(:entries).with("tmp++hello") { ["f1", "f2", "hello.mof", "f3"] } expect(conf_man.send(:find_configuration_document, "hello")).to eql("tmp++hello++hello.mof") end it "should return nil if the mof file is not found" do allow(File).to receive(:join) do |a, b| [a, b].join("++") end allow(Dir).to receive(:entries).with("tmp++hello") { %w{f1 f2 f3} } expect(conf_man.send(:find_configuration_document, "hello")).to be_nil end end describe "#configuration_code" do it "should build dsc" do dsc = conf_man.send(:configuration_code, "archive{}", "hello", {}) found_configuration = false dsc.split(";").each do |command| if command.downcase =~ /\s*configuration\s+'hello'\s*\{\s*node\s+'localhost'\s*\{\s*archive\s*\{\s*\}\s*\}\s*\}\s*/ found_configuration = true end end expect(found_configuration).to be_truthy end context "with imports" do it "should import all resources when a module has an empty list" do dsc = conf_man.send(:configuration_code, "archive{}", "hello", { "FooModule" => [] }) expect(dsc).to match(/Import-DscResource -ModuleName FooModule\s*\n/) end it "should import all resources when a module has a list with *" do dsc = conf_man.send(:configuration_code, "archive{}", "hello", { "FooModule" => ["FooResource", "*", "BarResource"] }) expect(dsc).to match(/Import-DscResource -ModuleName FooModule\s*\n/) end it "should import specific resources when a module has list without * that is not empty" do dsc = conf_man.send(:configuration_code, "archive{}", "hello", { "FooModule" => %w{FooResource BarResource} }) expect(dsc).to match(/Import-DscResource -ModuleName FooModule -Name FooResource,BarResource/) end it "should import multiple modules with multiple import statements" do dsc = conf_man.send(:configuration_code, "archive{}", "hello", { "FooModule" => %w{FooResource BarResource}, "BazModule" => [] }) expect(dsc).to match(/Import-DscResource -ModuleName FooModule -Name FooResource,BarResource/) expect(dsc).to match(/Import-DscResource -ModuleName BazModule\s*\n/) end end end end chef-12.14.60/spec/unit/util/dsc/lcm_output_parser_spec.rb000066400000000000000000000154671276456504500234520ustar00rootroot00000000000000# # Author:: Bryan McLellan # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/util/dsc/lcm_output_parser" describe Chef::Util::DSC::LocalConfigurationManager::Parser do context "empty input parameter" do it "raises an exception when there are no valid lines" do str = <<-EOF EOF expect { Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str) }.to raise_error(Chef::Exceptions::LCMParser) end it "raises an exception for a nil input" do expect { Chef::Util::DSC::LocalConfigurationManager::Parser.parse(nil) }.to raise_error(Chef::Exceptions::LCMParser) end end context "correctly formatted output from lcm" do it "returns a single resource when only 1 logged with the correct name" do str = < # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef" require "chef/util/dsc/local_configuration_manager" describe Chef::Util::DSC::LocalConfigurationManager do let(:lcm) { Chef::Util::DSC::LocalConfigurationManager.new(nil, "tmp") } let(:normal_lcm_output) do <<-EOH logtype: [machinename]: LCM: [ Start Set ] logtype: [machinename]: LCM: [ Start Resource ] [name] logtype: [machinename]: LCM: [ End Resource ] [name] logtype: [machinename]: LCM: [ End Set ] EOH end let(:no_whatif_lcm_output) do <<-EOH Start-DscConfiguration : A parameter cannot be found\r\n that matches parameter name 'whatif'. At line:1 char:123 + run-somecommand -whatif + ~~~~~~~~ + CategoryInfo : InvalidArgument: (:) [Start-DscConfiguration], ParameterBindingException + FullyQualifiedErrorId : NamedParameterNotFound,SomeCompany.SomeAssembly.Commands.RunSomeCommand EOH end let(:dsc_resource_import_failure_output) do <<-EOH PowerShell provider MSFT_xWebsite failed to execute Test-TargetResource functionality with error message: Please ensure that WebAdministration module is installed. + CategoryInfo : InvalidOperation: (:) [], CimException + FullyQualifiedErrorId : ProviderOperationExecutionFailure + PSComputerName : . PowerShell provider MSFT_xWebsite failed to execute Test-TargetResource functionality with error message: Please ensure that WebAdministration module is installed. + CategoryInfo : InvalidOperation: (:) [], CimException + FullyQualifiedErrorId : ProviderOperationExecutionFailure + PSComputerName : . The SendConfigurationApply function did not succeed. + CategoryInfo : NotSpecified: (root/Microsoft/...gurationManager:String) [], CimException + FullyQualifiedErrorId : MI RESULT 1 + PSComputerName : . EOH end let(:lcm_status) do double("LCM cmdlet status", :stderr => lcm_standard_error, :return_value => lcm_standard_output, :succeeded? => lcm_cmdlet_success) end describe "test_configuration method invocation" do context "when interacting with the LCM using a PowerShell cmdlet" do before(:each) do allow(lcm).to receive(:run_configuration_cmdlet).and_return(lcm_status) end context "that returns successfully" do before(:each) do allow(lcm).to receive(:run_configuration_cmdlet).and_return(lcm_status) end let(:lcm_standard_output) { normal_lcm_output } let(:lcm_standard_error) { nil } let(:lcm_cmdlet_success) { true } it "should successfully return resource information for normally formatted output when cmdlet the cmdlet succeeds" do test_configuration_result = lcm.test_configuration("config", {}) expect(test_configuration_result.class).to be(Array) expect(test_configuration_result.length).to be > 0 expect(Chef::Log).not_to receive(:warn) end end context "that fails due to missing what-if switch in DSC resource cmdlet implementation" do let(:lcm_standard_output) { "" } let(:lcm_standard_error) { no_whatif_lcm_output } let(:lcm_cmdlet_success) { false } it "returns true when passed to #whatif_not_supported?" do expect(lcm.send(:whatif_not_supported?, no_whatif_lcm_output)).to be_truthy end it "should should return a (possibly empty) array of ResourceInfo instances" do expect(Chef::Log).to receive(:warn).at_least(:once) expect(lcm).to receive(:whatif_not_supported?).and_call_original test_configuration_result = nil expect { test_configuration_result = lcm.test_configuration("config", {}) }.not_to raise_error expect(test_configuration_result.class).to be(Array) end end context "that fails due to a DSC resource not being imported before StartDSCConfiguration -whatif is executed" do let(:lcm_standard_output) { "" } let(:lcm_standard_error) { dsc_resource_import_failure_output } let(:lcm_cmdlet_success) { false } it "should log a warning if the message is formatted as expected when a resource import failure occurs" do expect(Chef::Log).to receive(:warn).at_least(:once) expect(lcm).to receive(:dsc_module_import_failure?).and_call_original test_configuration_result = nil expect { test_configuration_result = lcm.test_configuration("config", {}) }.not_to raise_error end it "should return a (possibly empty) array of ResourceInfo instances" do expect(Chef::Log).to receive(:warn).at_least(:once) test_configuration_result = nil expect { test_configuration_result = lcm.test_configuration("config", {}) }.not_to raise_error expect(test_configuration_result.class).to be(Array) end end context "that fails due to an unknown PowerShell cmdlet error" do let(:lcm_standard_output) { "some output" } let(:lcm_standard_error) { "Abort, Retry, Fail?" } let(:lcm_cmdlet_success) { false } it "should log a warning" do expect(Chef::Log).to receive(:warn).at_least(:once) expect(lcm).to receive(:dsc_module_import_failure?).and_call_original expect { lcm.test_configuration("config", {}) }.not_to raise_error end end end it "should identify a correctly formatted error message as a resource import failure" do expect(lcm.send(:dsc_module_import_failure?, dsc_resource_import_failure_output)).to be(true) end it "should not identify an incorrectly formatted error message as a resource import failure" do expect(lcm.send(:dsc_module_import_failure?, dsc_resource_import_failure_output.gsub("module", "gibberish"))).to be(false) end it "should not identify a message without a CimException reference as a resource import failure" do expect(lcm.send(:dsc_module_import_failure?, dsc_resource_import_failure_output.gsub("CimException", "ArgumentException"))).to be(false) end end end chef-12.14.60/spec/unit/util/dsc/resource_store.rb000066400000000000000000000050511276456504500217200ustar00rootroot00000000000000# # Author:: Jay Mundrawala # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef" require "chef/util/dsc/resource_store" describe Chef::Util::DSC::ResourceStore do let(:resource_store) { Chef::Util::DSC::ResourceStore.new } let(:resource_a) do { "ResourceType" => "AFoo", "Name" => "Foo", "Module" => { "Name" => "ModuleA" }, } end let(:resource_b) do { "ResourceType" => "BFoo", "Name" => "Foo", "Module" => { "Name" => "ModuleB" }, } end context "when resources are not cached" do context "when calling #resources" do it "returns an empty array" do expect(resource_store.resources).to eql([]) end end context "when calling #find" do it "returns an empty list if it cannot find any matching resources" do expect(resource_store).to receive(:query_resource).and_return([]) expect(resource_store.find("foo")).to eql([]) end it "returns the resource if it is found (comparisons are case insensitive)" do expect(resource_store).to receive(:query_resource).and_return([resource_a]) expect(resource_store.find("foo")).to eql([resource_a]) end it "returns multiple resoures if they are found" do expect(resource_store).to receive(:query_resource).and_return([resource_a, resource_b]) expect(resource_store.find("foo")).to include(resource_a, resource_b) end it "deduplicates resources by ResourceName" do expect(resource_store).to receive(:query_resource).and_return([resource_a, resource_a]) resource_store.find("foo") expect(resource_store.resources).to eq([resource_a]) end end end context "when resources are cached" do it "recalls resources from the cache if present" do expect(resource_store).not_to receive(:query_resource) expect(resource_store).to receive(:resources).and_return([resource_a]) resource_store.find("foo") end end end chef-12.14.60/spec/unit/util/editor_spec.rb000066400000000000000000000111051276456504500204010ustar00rootroot00000000000000require "spec_helper" require "chef/util/editor" describe Chef::Util::Editor do describe "#initialize" do it "takes an Enumerable of lines" do editor = described_class.new(File.open(__FILE__)) expect(editor.lines).to be == IO.readlines(__FILE__) end it "makes a copy of an Array" do array = Array.new editor = described_class.new(array) expect(editor.lines).to_not be(array) end end subject(:editor) { described_class.new(input_lines) } let(:input_lines) { %w{one two two three} } describe "#append_line_after" do context "when there is no match" do subject(:execute) { editor.append_line_after("missing", "new") } it("returns the number of added lines") { is_expected.to eq(0) } it "does not add any lines" do expect { execute }.to_not change { editor.lines } end end context "when there is a match" do subject(:execute) { editor.append_line_after("two", "new") } it("returns the number of added lines") { is_expected.to eq(2) } it "adds a line after each match" do execute expect(editor.lines).to be == %w{one two new two new three} end end it "matches a Regexp" do expect(editor.append_line_after(/^ee/, "new")).to be == 0 expect(editor.append_line_after(/ee$/, "new")).to be == 1 end end describe "#append_line_if_missing" do context "when there is no match" do subject(:execute) { editor.append_line_if_missing("missing", "new") } it("returns the number of added lines") { is_expected.to eq(1) } it "adds a line to the end" do execute expect(editor.lines).to be == %w{one two two three new} end end context "when there is a match" do subject(:execute) { editor.append_line_if_missing("one", "new") } it("returns the number of added lines") { is_expected.to eq(0) } it "does not add any lines" do expect { execute }.to_not change { editor.lines } end end it "matches a Regexp" do expect(editor.append_line_if_missing(/ee$/, "new")).to be == 0 expect(editor.append_line_if_missing(/^ee/, "new")).to be == 1 end end describe "#remove_lines" do context "when there is no match" do subject(:execute) { editor.remove_lines("missing") } it("returns the number of removed lines") { is_expected.to eq(0) } it "does not remove any lines" do expect { execute }.to_not change { editor.lines } end end context "when there is a match" do subject(:execute) { editor.remove_lines("two") } it("returns the number of removed lines") { is_expected.to eq(2) } it "removes the matching lines" do execute expect(editor.lines).to be == %w{one three} end end it "matches a Regexp" do expect(editor.remove_lines(/^ee/)).to be == 0 expect(editor.remove_lines(/ee$/)).to be == 1 end end describe "#replace" do context "when there is no match" do subject(:execute) { editor.replace("missing", "new") } it("returns the number of changed lines") { is_expected.to eq(0) } it "does not change any lines" do expect { execute }.to_not change { editor.lines } end end context "when there is a match" do subject(:execute) { editor.replace("two", "new") } it("returns the number of changed lines") { is_expected.to eq(2) } it "replaces the matching portions" do execute expect(editor.lines).to be == %w{one new new three} end end it "matches a Regexp" do expect(editor.replace(/^ee/, "new")).to be == 0 expect(editor.replace(/ee$/, "new")).to be == 1 expect(editor.lines).to be == %w{one two two thrnew} end end describe "#replace_lines" do context "when there is no match" do subject(:execute) { editor.replace_lines("missing", "new") } it("returns the number of changed lines") { is_expected.to eq(0) } it "does not change any lines" do expect { execute }.to_not change { editor.lines } end end context "when there is a match" do subject(:execute) { editor.replace_lines("two", "new") } it("returns the number of replaced lines") { is_expected.to eq(2) } it "replaces the matching line" do execute expect(editor.lines).to be == %w{one new new three} end end it "matches a Regexp" do expect(editor.replace_lines(/^ee/, "new")).to be == 0 expect(editor.replace_lines(/ee$/, "new")).to be == 1 expect(editor.lines).to be == %w{one two two new} end end end chef-12.14.60/spec/unit/util/file_edit_spec.rb000066400000000000000000000143241276456504500210450ustar00rootroot00000000000000# # Author:: Nuo Yan () # 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" require "tempfile" describe Chef::Util::FileEdit do let(:starting_content) do <<-EOF 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost EOF end let(:localhost_replaced) do <<-EOF 127.0.0.1 replacement 255.255.255.255 broadcasthost ::1 replacement fe80::1%lo0 replacement EOF end let(:localhost_line_replaced) do <<-EOF replacement line 255.255.255.255 broadcasthost replacement line replacement line EOF end let(:localhost_deleted) do # sensitive to deliberate trailing whitespace "127.0.0.1 \n255.255.255.255 broadcasthost\n::1 \nfe80::1%lo0 \n" end let(:localhost_line_deleted) do <<-EOF 255.255.255.255 broadcasthost EOF end let(:append_after_all_localhost) do <<-EOF 127.0.0.1 localhost new line inserted 255.255.255.255 broadcasthost ::1 localhost new line inserted fe80::1%lo0 localhost new line inserted EOF end let(:append_after_content) do <<-EOF 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost new line inserted EOF end let(:append_twice) do <<-EOF 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost once twice EOF end let(:target_file) do f = Tempfile.open("file_edit_spec") f.write(starting_content) f.close f end let(:fedit) { Chef::Util::FileEdit.new(target_file.path) } after(:each) do target_file.close! end describe "initialiize" do it "should create a new Chef::Util::FileEdit object" do expect(fedit).to be_instance_of(Chef::Util::FileEdit) end it "should throw an exception if the input file does not exist" do expect { Chef::Util::FileEdit.new("nonexistfile") }.to raise_error(ArgumentError) end # CHEF-5018: people have monkey patched this and it has accidentally been broken it "should read the contents into memory as an array" do expect(fedit.send(:editor).lines).to be_instance_of(Array) end end describe "when the file is blank" do let(:hosts_content) { "" } it "should not throw an exception" do expect { fedit }.not_to raise_error end end def edited_file_contents IO.read(target_file.path) end describe "search_file_replace" do it "should accept regex passed in as a string (not Regexp object) and replace the match if there is one" do fedit.search_file_replace("localhost", "replacement") expect(fedit.unwritten_changes?).to be_truthy fedit.write_file expect(edited_file_contents).to eq(localhost_replaced) end it "should accept regex passed in as a Regexp object and replace the match if there is one" do fedit.search_file_replace(/localhost/, "replacement") expect(fedit.unwritten_changes?).to be_truthy fedit.write_file expect(edited_file_contents).to eq(localhost_replaced) end it "should do nothing if there isn't a match" do fedit.search_file_replace(/pattern/, "replacement") expect(fedit.unwritten_changes?).to be_falsey fedit.write_file expect(edited_file_contents).to eq(starting_content) end end describe "search_file_replace_line" do it "should search for match and replace the whole line" do fedit.search_file_replace_line(/localhost/, "replacement line") expect(fedit.unwritten_changes?).to be_truthy fedit.write_file expect(edited_file_contents).to eq(localhost_line_replaced) end end describe "search_file_delete" do it "should search for match and delete the match" do fedit.search_file_delete(/localhost/) expect(fedit.unwritten_changes?).to be_truthy fedit.write_file expect(edited_file_contents).to eq(localhost_deleted) end end describe "search_file_delete_line" do it "should search for match and delete the matching line" do fedit.search_file_delete_line(/localhost/) expect(fedit.unwritten_changes?).to be_truthy fedit.write_file expect(edited_file_contents).to eq(localhost_line_deleted) end end describe "insert_line_after_match" do it "should search for match and insert the given line after the matching line" do fedit.insert_line_after_match(/localhost/, "new line inserted") expect(fedit.unwritten_changes?).to be_truthy fedit.write_file expect(edited_file_contents).to eq(append_after_all_localhost) end end describe "insert_line_if_no_match" do it "should search for match and insert the given line if no line match" do fedit.insert_line_if_no_match(/pattern/, "new line inserted") expect(fedit.unwritten_changes?).to be_truthy fedit.write_file expect(edited_file_contents).to eq(append_after_content) end it "should do nothing if there is a match" do fedit.insert_line_if_no_match(/localhost/, "replacement") expect(fedit.unwritten_changes?).to be_falsey fedit.write_file expect(edited_file_contents).to eq(starting_content) end it "should work more than once" do fedit.insert_line_if_no_match(/missing/, "once") fedit.insert_line_if_no_match(/missing/, "twice") fedit.write_file expect(edited_file_contents).to eq(append_twice) end end describe "file_edited" do it "should return true if a file got edited" do fedit.insert_line_if_no_match(/pattern/, "new line inserted") fedit.write_file expect(fedit.file_edited?).to be_truthy end end end chef-12.14.60/spec/unit/util/powershell/000077500000000000000000000000001276456504500177425ustar00rootroot00000000000000chef-12.14.60/spec/unit/util/powershell/cmdlet_spec.rb000066400000000000000000000067671276456504500225710ustar00rootroot00000000000000# # Author:: Jay Mundrawala # Copyright:: Copyright 2014-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef" require "chef/util/powershell/cmdlet" describe Chef::Util::Powershell::Cmdlet do before (:all) do @node = Chef::Node.new @cmdlet = Chef::Util::Powershell::Cmdlet.new(@node, "Some-Commandlet") end describe "#validate_switch_name!" do it "should not raise an error if a name contains all upper case letters" do @cmdlet.send(:validate_switch_name!, "HELLO") end it "should not raise an error if the name contains all lower case letters" do @cmdlet.send(:validate_switch_name!, "hello") end it "should not raise an error if no special characters are used except _" do @cmdlet.send(:validate_switch_name!, "hello_world") end %w{! @ # $ % ^ & * & * ( ) - = + \{ \} . ? < > \\ /}.each do |sym| it "raises an Argument error if it configuration name contains #{sym}" do expect do @cmdlet.send(:validate_switch_name!, "Hello#{sym}") end.to raise_error(ArgumentError) end end end describe "#escape_parameter_value" do # Is this list really complete? %w{` " # '}.each do |c| it "escapse #{c}" do expect(@cmdlet.send(:escape_parameter_value, "stuff #{c}")).to eql("stuff `#{c}") end end it "does not do anything to a string without special characters" do expect(@cmdlet.send(:escape_parameter_value, "stuff")).to eql("stuff") end end describe "#escape_string_parameter_value" do it "surrounds a string with ''" do expect(@cmdlet.send(:escape_string_parameter_value, "stuff")).to eql("'stuff'") end end describe "#command_switches_string" do it "raises an ArgumentError if the key is not a symbol" do expect do @cmdlet.send(:command_switches_string, { "foo" => "bar" }) end.to raise_error(ArgumentError) end it "does not allow invalid switch names" do expect do @cmdlet.send(:command_switches_string, { :foo! => "bar" }) end.to raise_error(ArgumentError) end it "ignores switches with a false value" do expect(@cmdlet.send(:command_switches_string, { foo: false })).to eql("") end it "should correctly handle a value type of string" do expect(@cmdlet.send(:command_switches_string, { foo: "bar" })).to eql("-foo 'bar'") end it "should correctly handle a value type of string even when it is 0 length" do expect(@cmdlet.send(:command_switches_string, { foo: "" })).to eql("-foo ''") end it "should not quote integers" do expect(@cmdlet.send(:command_switches_string, { foo: 1 })).to eql("-foo 1") end it "should not quote floats" do expect(@cmdlet.send(:command_switches_string, { foo: 1.0 })).to eql("-foo 1.0") end it "has just the switch when the value is true" do expect(@cmdlet.send(:command_switches_string, { foo: true })).to eql("-foo") end end end chef-12.14.60/spec/unit/util/powershell/ps_credential_spec.rb000066400000000000000000000032321276456504500241150ustar00rootroot00000000000000# # Author:: Jay Mundrawala # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef" require "chef/util/powershell/ps_credential" describe Chef::Util::Powershell::PSCredential do let (:username) { "foo" } let (:password) { "ThIsIsThEpAsSwOrD" } context "when username and password are provided" do let(:ps_credential) { Chef::Util::Powershell::PSCredential.new(username, password) } context "when calling to_psobject" do it "should create the script to create a PSCredential when calling" do allow(ps_credential).to receive(:encrypt).with(password).and_return("encrypted") expect(ps_credential.to_psobject).to eq( "New-Object System.Management.Automation.PSCredential("\ "'#{username}',('encrypted' | ConvertTo-SecureString))") end end context "when to_text is called" do it "should not contain the password" do allow(ps_credential).to receive(:encrypt).with(password).and_return("encrypted") expect(ps_credential.to_text).not_to match(/#{password}/) end end end end chef-12.14.60/spec/unit/util/selinux_spec.rb000066400000000000000000000134641276456504500206140ustar00rootroot00000000000000# # Author:: Serdar Sutay () # Copyright:: Copyright 2013-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::Util::Selinux do class TestClass include Chef::Util::Selinux def self.reset_state @@selinux_enabled = nil @@restorecon_path = nil @@selinuxenabled_path = nil end end before do TestClass.reset_state @test_instance = TestClass.new end after(:each) do TestClass.reset_state end it "each part of ENV['PATH'] should be checked" do expected_paths = ENV["PATH"].split(File::PATH_SEPARATOR) + [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ] expected_paths.each do |bin_path| selinux_path = File.join(bin_path, "selinuxenabled") expect(File).to receive(:executable?).with(selinux_path).and_return(false) end expect(@test_instance.selinux_enabled?).to be_falsey end describe "when selinuxenabled binary exists" do before do @selinux_enabled_path = File.join("/sbin", "selinuxenabled") allow(File).to receive(:executable?) do |file_path| expect(file_path.end_with?("selinuxenabled")).to be_truthy file_path == @selinux_enabled_path end end describe "when selinux is enabled" do before do cmd_result = double("Cmd Result", :exitstatus => 0) expect(@test_instance).to receive(:shell_out!).once.with(@selinux_enabled_path, { :returns => [0, 1] }).and_return(cmd_result) end it "should report selinux is enabled" do expect(@test_instance.selinux_enabled?).to be_truthy # should check the file system only once for multiple calls expect(@test_instance.selinux_enabled?).to be_truthy end end describe "when selinux is disabled" do before do cmd_result = double("Cmd Result", :exitstatus => 1) expect(@test_instance).to receive(:shell_out!).once.with(@selinux_enabled_path, { :returns => [0, 1] }).and_return(cmd_result) end it "should report selinux is disabled" do expect(@test_instance.selinux_enabled?).to be_falsey # should check the file system only once for multiple calls expect(@test_instance.selinux_enabled?).to be_falsey end end describe "when selinux gives an unexpected status" do before do cmd_result = double("Cmd Result", :exitstatus => 101) expect(@test_instance).to receive(:shell_out!).once.with(@selinux_enabled_path, { :returns => [0, 1] }).and_return(cmd_result) end it "should throw an error" do expect { @test_instance.selinux_enabled? }.to raise_error(RuntimeError) end end end describe "when selinuxenabled binary doesn't exist" do before do allow(File).to receive(:executable?) do |file_path| expect(file_path.end_with?("selinuxenabled")).to be_truthy false end end it "should report selinux is disabled" do expect(@test_instance.selinux_enabled?).to be_falsey # should check the file system only once for multiple calls expect(File).not_to receive(:executable?) expect(@test_instance.selinux_enabled?).to be_falsey end end describe "when restorecon binary exists on the system" do let (:path) { "/path/to/awesome directory" } before do @restorecon_enabled_path = File.join("/sbin", "restorecon") allow(File).to receive(:executable?) do |file_path| expect(file_path.end_with?("restorecon")).to be_truthy file_path == @restorecon_enabled_path end end it "should call restorecon non-recursive by default" do restorecon_command = "#{@restorecon_enabled_path} -R \"#{path}\"" expect(@test_instance).to receive(:shell_out!).twice.with(restorecon_command) @test_instance.restore_security_context(path) expect(File).not_to receive(:executable?) @test_instance.restore_security_context(path) end it "should call restorecon recursive when recursive is set" do restorecon_command = "#{@restorecon_enabled_path} -R -r \"#{path}\"" expect(@test_instance).to receive(:shell_out!).twice.with(restorecon_command) @test_instance.restore_security_context(path, true) expect(File).not_to receive(:executable?) @test_instance.restore_security_context(path, true) end it "should call restorecon non-recursive when recursive is not set" do restorecon_command = "#{@restorecon_enabled_path} -R \"#{path}\"" expect(@test_instance).to receive(:shell_out!).twice.with(restorecon_command) @test_instance.restore_security_context(path) expect(File).not_to receive(:executable?) @test_instance.restore_security_context(path) end describe "when restorecon doesn't exist on the system" do before do allow(File).to receive(:executable?) do |file_path| expect(file_path.end_with?("restorecon")).to be_truthy false end end it "should log a warning message" do log = [ ] allow(Chef::Log).to receive(:warn) do |message| log << message end @test_instance.restore_security_context(path) expect(log).not_to be_empty expect(File).not_to receive(:executable?) @test_instance.restore_security_context(path) end end end end chef-12.14.60/spec/unit/util/threaded_job_queue_spec.rb000066400000000000000000000031761276456504500227420ustar00rootroot00000000000000# Copyright:: Copyright 2014-2016, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" class WorkerThreadError < StandardError end describe Chef::Util::ThreadedJobQueue do let(:queue) { Chef::Util::ThreadedJobQueue.new } it "should pass mutex to jobs with an arity of 1" do job = double() expect(job).to receive(:arity).at_least(:once).and_return(1) expect(job).to receive(:call).exactly(5).times.with(an_instance_of(Mutex)) 5.times { queue << job } queue.process end it "should pass nothing to jobs with an arity of 0" do job = double() expect(job).to receive(:arity).at_least(:once).and_return(0) expect(job).to receive(:call).exactly(5).times.with(no_args) 5.times { queue << job } queue.process end it "should use specified number of threads" do expect(Thread).to receive(:new).exactly(7).times.and_call_original queue.process(7) end it "should propagate exceptions to the main thread" do queue << lambda { raise WorkerThreadError } expect { queue.process }.to raise_error(WorkerThreadError) end end chef-12.14.60/spec/unit/version/000077500000000000000000000000001276456504500162665ustar00rootroot00000000000000chef-12.14.60/spec/unit/version/platform_spec.rb000066400000000000000000000036451276456504500214610ustar00rootroot00000000000000# Author:: Xabier de Zuazo () # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "chef/version/platform" describe Chef::Version::Platform do it "is a subclass of Chef::Version" do v = Chef::Version::Platform.new("1.1") expect(v).to be_an_instance_of(Chef::Version::Platform) expect(v).to be_a_kind_of(Chef::Version) end it "should transform 1 to 1.0.0" do expect(Chef::Version::Platform.new("1").to_s).to eq("1.0.0") end describe "when creating valid Versions" do good_versions = %w{1 1.2 1.2.3 1000.80.50000 0.300.25 001.02.00003 1.2-STABLE 10.0-BETA3 9.1-RELEASE-p3} good_versions.each do |v| it "should accept '#{v}'" do Chef::Version::Platform.new v end end end describe "when given bogus input" do bad_versions = ["1.2.3.4", "1.2.a4", "a", "1.2 3", "1.2 a", "1 2 3", "1-2-3", "1_2_3", "1.2_3", "1.2-3"] the_error = Chef::Exceptions::InvalidPlatformVersion bad_versions.each do |v| it "should raise #{the_error} when given '#{v}'" do expect { Chef::Version::Platform.new v }.to raise_error(the_error) end end end describe "<=>" do it "should equate versions 1 and 1.0.0" do expect(Chef::Version::Platform.new("1")).to eq(Chef::Version::Platform.new("1.0.0")) end end end chef-12.14.60/spec/unit/version_class_spec.rb000066400000000000000000000130551276456504500210160ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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/version_class" describe Chef::Version do before do @v0 = Chef::Version.new "0.0.0" @v123 = Chef::Version.new "1.2.3" end it "should turn itself into a string" do expect(@v0.to_s).to eq("0.0.0") expect(@v123.to_s).to eq("1.2.3") end it "should make a round trip with its string representation" do a = Chef::Version.new(@v123.to_s) expect(a).to eq(@v123) end it "should transform 1.2 to 1.2.0" do expect(Chef::Version.new("1.2").to_s).to eq("1.2.0") end it "should transform 01.002.0003 to 1.2.3" do a = Chef::Version.new "01.002.0003" expect(a).to eq(@v123) end describe "when creating valid Versions" do good_versions = %w{1.2 1.2.3 1000.80.50000 0.300.25 001.02.00003} good_versions.each do |v| it "should accept '#{v}'" do Chef::Version.new v end end end describe "when given bogus input" do bad_versions = ["1.2.3.4", "1.2.a4", "1", "a", "1.2 3", "1.2 a", "1 2 3", "1-2-3", "1_2_3", "1.2_3", "1.2-3"] the_error = Chef::Exceptions::InvalidCookbookVersion bad_versions.each do |v| it "should raise #{the_error} when given '#{v}'" do expect { Chef::Version.new v }.to raise_error(the_error) end end end describe "<=>" do it "should equate versions 1.2 and 1.2.0" do expect(Chef::Version.new("1.2")).to eq(Chef::Version.new("1.2.0")) end it "should equate version 1.04 and 1.4" do expect(Chef::Version.new("1.04")).to eq(Chef::Version.new("1.4")) end it "should treat versions as numbers in the right way" do expect(Chef::Version.new("2.0")).to be < Chef::Version.new("11.0") end it "should sort based on the version number" do examples = [ # smaller, larger ["1.0", "2.0"], ["1.2.3", "1.2.4"], ["1.2.3", "1.3.0"], ["1.2.3", "1.3"], ["1.2.3", "2.1.1"], ["1.2.3", "2.1"], ["1.2", "1.2.4"], ["1.2", "1.3.0"], ["1.2", "1.3"], ["1.2", "2.1.1"], ["1.2", "2.1"], ] examples.each do |smaller, larger| sm = Chef::Version.new(smaller) lg = Chef::Version.new(larger) expect(sm).to be < lg expect(lg).to be > sm expect(sm).not_to eq(lg) end end it "should sort an array of versions" do a = %w{0.0.0 0.0.1 0.1.0 0.1.1 1.0.0 1.1.0 1.1.1}.map do |s| Chef::Version.new(s) end got = a.sort.map { |v| v.to_s } expect(got).to eq(%w{0.0.0 0.0.1 0.1.0 0.1.1 1.0.0 1.1.0 1.1.1}) end it "should sort an array of versions, part 2" do a = %w{9.8.7 1.0.0 1.2.3 4.4.6 4.5.6 0.8.6 4.5.5 5.9.8 3.5.7}.map do |s| Chef::Version.new(s) end got = a.sort.map { |v| v.to_s } expect(got).to eq(%w{0.8.6 1.0.0 1.2.3 3.5.7 4.4.6 4.5.5 4.5.6 5.9.8 9.8.7}) end describe "comparison examples" do [ [ "0.0.0", :>, "0.0.0", false ], [ "0.0.0", :>=, "0.0.0", true ], [ "0.0.0", :==, "0.0.0", true ], [ "0.0.0", :<=, "0.0.0", true ], [ "0.0.0", :<, "0.0.0", false ], [ "0.0.0", :>, "0.0.1", false ], [ "0.0.0", :>=, "0.0.1", false ], [ "0.0.0", :==, "0.0.1", false ], [ "0.0.0", :<=, "0.0.1", true ], [ "0.0.0", :<, "0.0.1", true ], [ "0.0.1", :>, "0.0.1", false ], [ "0.0.1", :>=, "0.0.1", true ], [ "0.0.1", :==, "0.0.1", true ], [ "0.0.1", :<=, "0.0.1", true ], [ "0.0.1", :<, "0.0.1", false ], [ "0.1.0", :>, "0.1.0", false ], [ "0.1.0", :>=, "0.1.0", true ], [ "0.1.0", :==, "0.1.0", true ], [ "0.1.0", :<=, "0.1.0", true ], [ "0.1.0", :<, "0.1.0", false ], [ "0.1.1", :>, "0.1.1", false ], [ "0.1.1", :>=, "0.1.1", true ], [ "0.1.1", :==, "0.1.1", true ], [ "0.1.1", :<=, "0.1.1", true ], [ "0.1.1", :<, "0.1.1", false ], [ "1.0.0", :>, "1.0.0", false ], [ "1.0.0", :>=, "1.0.0", true ], [ "1.0.0", :==, "1.0.0", true ], [ "1.0.0", :<=, "1.0.0", true ], [ "1.0.0", :<, "1.0.0", false ], [ "1.0.0", :>, "0.0.1", true ], [ "1.0.0", :>=, "1.9.2", false ], [ "1.0.0", :==, "9.7.2", false ], [ "1.0.0", :<=, "1.9.1", true ], [ "1.0.0", :<, "1.9.0", true ], [ "1.2.2", :>, "1.2.1", true ], [ "1.2.2", :>=, "1.2.1", true ], [ "1.2.2", :==, "1.2.1", false ], [ "1.2.2", :<=, "1.2.1", false ], [ "1.2.2", :<, "1.2.1", false ], ].each do |spec| it "(#{spec.first(3).join(' ')}) should be #{spec[3]}" do got = Chef::Version.new(spec[0]).send(spec[1], Chef::Version.new(spec[2])) expect(got).to eq(spec[3]) end end end end end chef-12.14.60/spec/unit/version_constraint/000077500000000000000000000000001276456504500205325ustar00rootroot00000000000000chef-12.14.60/spec/unit/version_constraint/platform_spec.rb000066400000000000000000000027301276456504500237170ustar00rootroot00000000000000# Author:: Xabier de Zuazo () # Copyright:: Copyright 2013-2016, Onddo Labs, SL. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "chef/version_constraint/platform" describe Chef::VersionConstraint::Platform do it "is a subclass of Chef::VersionConstraint" do v = Chef::VersionConstraint::Platform.new expect(v).to be_an_instance_of(Chef::VersionConstraint::Platform) expect(v).to be_a_kind_of(Chef::VersionConstraint) end it "should work with Chef::Version::Platform classes" do vc = Chef::VersionConstraint::Platform.new("1.0") expect(vc.version).to be_an_instance_of(Chef::Version::Platform) end describe "include?" do it "pessimistic ~> x" do vc = Chef::VersionConstraint::Platform.new "~> 1" expect(vc).to include "1.3.3" expect(vc).to include "1.4" expect(vc).not_to include "2.2" expect(vc).not_to include "0.3.0" end end end chef-12.14.60/spec/unit/version_constraint_spec.rb000066400000000000000000000130601276456504500220710ustar00rootroot00000000000000# # Author:: Seth Falcon () # Copyright:: Copyright 2010-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/version_constraint" describe Chef::VersionConstraint do describe "validation" do bad_version = [">= 1.2.z", "> 1.2.3 < 5.0", "> 1.2.3, < 5.0"] bad_op = ["> >", ">$ 1.2.3", "! 3.4"] o_error = Chef::Exceptions::InvalidVersionConstraint v_error = Chef::Exceptions::InvalidCookbookVersion bad_version.each do |s| it "should raise #{v_error} when given #{s}" do expect { Chef::VersionConstraint.new s }.to raise_error(v_error) end end bad_op.each do |s| it "should raise #{o_error} when given #{s}" do expect { Chef::VersionConstraint.new s }.to raise_error(o_error) end end it "should interpret a lone version number as implicit = OP" do vc = Chef::VersionConstraint.new("1.2.3") expect(vc.to_s).to eq("= 1.2.3") end it "should allow initialization with [] for back compatibility" do Chef::VersionConstraint.new([]) == Chef::VersionConstraint.new end it "should allow initialization with ['1.2.3'] for back compatibility" do Chef::VersionConstraint.new(["1.2"]) == Chef::VersionConstraint.new("1.2") end end it "should default to >= 0.0.0" do vc = Chef::VersionConstraint.new expect(vc.to_s).to eq(">= 0.0.0") end it "should default to >= 0.0.0 when initialized with nil" do expect(Chef::VersionConstraint.new(nil).to_s).to eq(">= 0.0.0") end it "should work with Chef::Version classes" do vc = Chef::VersionConstraint.new("1.0") expect(vc.version).to be_an_instance_of(Chef::Version) end it "should allow ops without space separator" do expect(Chef::VersionConstraint.new("=1.2.3")).to eql(Chef::VersionConstraint.new("= 1.2.3")) expect(Chef::VersionConstraint.new(">1.2.3")).to eql(Chef::VersionConstraint.new("> 1.2.3")) expect(Chef::VersionConstraint.new("<1.2.3")).to eql(Chef::VersionConstraint.new("< 1.2.3")) expect(Chef::VersionConstraint.new(">=1.2.3")).to eql(Chef::VersionConstraint.new(">= 1.2.3")) expect(Chef::VersionConstraint.new("<=1.2.3")).to eql(Chef::VersionConstraint.new("<= 1.2.3")) end it "should allow ops with multiple spaces" do expect(Chef::VersionConstraint.new("= 1.2.3")).to eql(Chef::VersionConstraint.new("= 1.2.3")) end describe "include?" do describe "handles various input data types" do before do @vc = Chef::VersionConstraint.new "> 1.2.3" end it "String" do expect(@vc).to include "1.4" end it "Chef::Version" do expect(@vc).to include Chef::Version.new("1.4") end it "Chef::CookbookVersion" do cv = Chef::CookbookVersion.new("alice", "/tmp/blah.txt") cv.version = "1.4" expect(@vc).to include cv end end it "strictly less than" do vc = Chef::VersionConstraint.new "< 1.2.3" expect(vc).not_to include "1.3.0" expect(vc).not_to include "1.2.3" expect(vc).to include "1.2.2" end it "strictly greater than" do vc = Chef::VersionConstraint.new "> 1.2.3" expect(vc).to include "1.3.0" expect(vc).not_to include "1.2.3" expect(vc).not_to include "1.2.2" end it "less than or equal to" do vc = Chef::VersionConstraint.new "<= 1.2.3" expect(vc).not_to include "1.3.0" expect(vc).to include "1.2.3" expect(vc).to include "1.2.2" end it "greater than or equal to" do vc = Chef::VersionConstraint.new ">= 1.2.3" expect(vc).to include "1.3.0" expect(vc).to include "1.2.3" expect(vc).not_to include "1.2.2" end it "equal to" do vc = Chef::VersionConstraint.new "= 1.2.3" expect(vc).not_to include "1.3.0" expect(vc).to include "1.2.3" expect(vc).not_to include "0.3.0" end it "pessimistic ~> x.y.z" do vc = Chef::VersionConstraint.new "~> 1.2.3" expect(vc).to include "1.2.3" expect(vc).to include "1.2.4" expect(vc).not_to include "1.2.2" expect(vc).not_to include "1.3.0" expect(vc).not_to include "2.0.0" end it "pessimistic ~> x.y" do vc = Chef::VersionConstraint.new "~> 1.2" expect(vc).to include "1.3.3" expect(vc).to include "1.4" expect(vc).not_to include "2.2" expect(vc).not_to include "0.3.0" end end describe "to_s" do it "shows a patch-level if one is given" do vc = Chef::VersionConstraint.new "~> 1.2.0" expect(vc.to_s).to eq("~> 1.2.0") end it "shows no patch-level if one is not given" do vc = Chef::VersionConstraint.new "~> 1.2" expect(vc.to_s).to eq("~> 1.2") end end describe "inspect" do it "shows a patch-level if one is given" do vc = Chef::VersionConstraint.new "~> 1.2.0" expect(vc.inspect).to eq("(~> 1.2.0)") end it "shows no patch-level if one is not given" do vc = Chef::VersionConstraint.new "~> 1.2" expect(vc.inspect).to eq("(~> 1.2)") end end end chef-12.14.60/spec/unit/win32/000077500000000000000000000000001276456504500155435ustar00rootroot00000000000000chef-12.14.60/spec/unit/win32/registry_spec.rb000066400000000000000000000511071276456504500207560ustar00rootroot00000000000000# # Author:: Prajakta Purohit (prajakta@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" describe Chef::Win32::Registry do include_context "Win32" let(:value1) { { :name => "one", :type => :string, :data => "1" } } let(:value1_upcase_name) { { :name => "ONE", :type => :string, :data => "1" } } let(:key_path) { 'HKCU\Software\OpscodeNumbers' } let(:key) { 'Software\OpscodeNumbers' } let(:key_parent) { "Software" } let(:key_to_delete) { "OpscodeNumbers" } let(:sub_key) { "OpscodePrimes" } let(:missing_key_path) { 'HKCU\Software' } let(:registry) { Chef::Win32::Registry.new() } let(:hive_mock) { double("::Win32::Registry::KHKEY_CURRENT_USER") } let(:reg_mock) { double("reg") } before(:all) do Win32::Registry = Class.new Win32::Registry::Error = Class.new(RuntimeError) end before(:each) do allow_any_instance_of(Chef::Win32::Registry).to receive(:machine_architecture).and_return(:x86_64) #Making the values for registry constants available on unix Win32::Registry::KEY_SET_VALUE = 0x0002 Win32::Registry::KEY_QUERY_VALUE = 0x0001 Win32::Registry::KEY_WRITE = 0x00020000 | 0x0002 | 0x0004 Win32::Registry::KEY_READ = 0x00020000 | 0x0001 | 0x0008 | 0x0010 end after(:each) do Win32::Registry.send(:remove_const, "KEY_SET_VALUE") if defined?(Win32::Registry::KEY_SET_VALUE) Win32::Registry.send(:remove_const, "KEY_QUERY_VALUE") if defined?(Win32::Registry::KEY_QUERY_VALUE) Win32::Registry.send(:remove_const, "KEY_READ") if defined?(Win32::Registry::KEY_READ) Win32::Registry.send(:remove_const, "KEY_WRITE") if defined?(Win32::Registry::KEY_WRITE) end describe "get_values" do it "gets all values for a key if the key exists" do expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(reg_mock).to receive(:map) registry.get_values(key_path) end it "throws an exception if key does not exist" do expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) expect { registry.get_values(key_path) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end end describe "set_value" do it "does nothing if key and hive and value exist" do expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true) expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(true) registry.set_value(key_path, value1) end it "does nothing if case insensitive key and hive and value exist" do expect(registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([hive_mock, key]) expect(registry).to receive(:value_exists?).with(key_path.downcase, value1).and_return(true) expect(registry).to receive(:data_exists?).with(key_path.downcase, value1).and_return(true) registry.set_value(key_path.downcase, value1) end it "does nothing if key and hive and value with a case insensitive name exist" do expect(registry).to receive(:key_exists!).with(key_path.downcase).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([hive_mock, key]) expect(registry).to receive(:value_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) expect(registry).to receive(:data_exists?).with(key_path.downcase, value1_upcase_name).and_return(true) registry.set_value(key_path.downcase, value1_upcase_name) end it "updates value if key and hive and value exist, but data is different" do expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true) expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(false) expect(hive_mock).to receive(:open).with(key, Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry.registry_system_architecture).and_yield(reg_mock) expect(registry).to receive(:get_type_from_name).with(:string).and_return(1) expect(reg_mock).to receive(:write).with("one", 1, "1") registry.set_value(key_path, value1) end it "creates value if the key exists and the value does not exist" do expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry.registry_system_architecture).and_yield(reg_mock) expect(registry).to receive(:get_type_from_name).with(:string).and_return(1) expect(reg_mock).to receive(:write).with("one", 1, "1") registry.set_value(key_path, value1) end it "should raise an exception if the key does not exist" do expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) expect { registry.set_value(key_path, value1) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end end describe "delete_value" do it "deletes value if value exists" do expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | registry.registry_system_architecture).and_yield(reg_mock) expect(reg_mock).to receive(:delete_value).with("one").and_return(true) registry.delete_value(key_path, value1) end it "raises an exception if the key does not exist" do expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) registry.delete_value(key_path, value1) end it "does nothing if the value does not exist" do expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false) registry.delete_value(key_path, value1) end end describe "create_key" do it "creates key if intermediate keys are missing and recursive is set to true" do expect(registry).to receive(:keys_missing?).with(key_path).and_return(true) expect(registry).to receive(:create_missing).with(key_path) expect(registry).to receive(:key_exists?).with(key_path).and_return(false) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture) registry.create_key(key_path, true) end it "raises an exception if intermediate keys are missing and recursive is set to false" do expect(registry).to receive(:keys_missing?).with(key_path).and_return(true) expect { registry.create_key(key_path, false) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) end it "does nothing if the key exists" do expect(registry).to receive(:keys_missing?).with(key_path).and_return(true) expect(registry).to receive(:create_missing).with(key_path) expect(registry).to receive(:key_exists?).with(key_path).and_return(true) registry.create_key(key_path, true) end it "create key if intermediate keys not missing and recursive is set to false" do expect(registry).to receive(:keys_missing?).with(key_path).and_return(false) expect(registry).to receive(:key_exists?).with(key_path).and_return(false) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture) registry.create_key(key_path, false) end it "create key if intermediate keys not missing and recursive is set to true" do expect(registry).to receive(:keys_missing?).with(key_path).and_return(false) expect(registry).to receive(:key_exists?).with(key_path).and_return(false) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:create).with(key, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture) registry.create_key(key_path, true) end end describe "delete_key", :windows_only do it "deletes key if it has subkeys and recursive is set to true" do expect(registry).to receive(:key_exists?).with(key_path).and_return(true) expect(registry).to receive(:has_subkeys?).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture).and_yield(reg_mock) expect(reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) registry.delete_key(key_path, true) end it "raises an exception if it has subkeys but recursive is set to false" do expect(registry).to receive(:key_exists?).with(key_path).and_return(true) expect(registry).to receive(:has_subkeys?).with(key_path).and_return(true) expect { registry.delete_key(key_path, false) }.to raise_error(Chef::Exceptions::Win32RegNoRecursive) end it "deletes key if the key exists and has no subkeys" do expect(registry).to receive(:key_exists?).with(key_path).and_return(true) expect(registry).to receive(:has_subkeys?).with(key_path).and_return(false) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key_parent, ::Win32::Registry::KEY_WRITE | registry.registry_system_architecture).and_yield(reg_mock) expect(reg_mock).to receive(:delete_key).with(key_to_delete, true).and_return(true) registry.delete_key(key_path, true) end end describe "key_exists?" do it "returns true if key_exists" do expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(registry.key_exists?(key_path)).to eq(true) end it "returns false if key does not exist" do expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_raise(::Win32::Registry::Error) expect(registry.key_exists?(key_path)).to eq(false) end end describe "key_exists!" do it "throws an exception if the key_parent does not exist" do expect(registry).to receive(:key_exists?).with(key_path).and_return(false) expect { registry.key_exists!(key_path) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end end describe "hive_exists?" do it "returns true if the hive exists" do expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) registry.hive_exists?(key_path) == true end it "returns false if the hive does not exist" do expect(registry).to receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegHiveMissing) registry.hive_exists?(key_path) == false end end describe "has_subkeys?" do it "returns true if the key has subkeys" do expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(reg_mock).to receive(:each_key).and_yield(key) registry.has_subkeys?(key_path) == true end it "returns false if the key does not have subkeys" do expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(reg_mock).to receive(:each_key).and_return(no_args()) expect(registry.has_subkeys?(key_path)).to eq(false) end it "throws an exception if the key does not exist" do expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) expect { registry.set_value(key_path, value1) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end end describe "get_subkeys" do it "returns the subkeys if they exist" do expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(reg_mock).to receive(:each_key).and_yield(sub_key) registry.get_subkeys(key_path) end end describe "value_exists?" do it "throws an exception if the key does not exist" do expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) expect { registry.value_exists?(key_path, value1) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "returns true if the value exists" do expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(reg_mock).to receive(:any?).and_yield("one") registry.value_exists?(key_path, value1) == true end it "returns false if the value does not exist" do expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(reg_mock).to receive(:any?).and_yield(no_args()) registry.value_exists?(key_path, value1) == false end end describe "data_exists?" do it "throws an exception if the key does not exist" do expect(registry).to receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing) expect { registry.data_exists?(key_path, value1) }.to raise_error(Chef::Exceptions::Win32RegKeyMissing) end it "returns true if the data exists" do expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(registry).to receive(:get_type_from_name).with(:string).and_return(1) expect(reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "1") expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(registry.data_exists?(key_path, value1)).to eq(true) end it "returns false if the data does not exist" do expect(registry).to receive(:key_exists!).with(key_path).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(registry).to receive(:get_type_from_name).with(:string).and_return(1) expect(reg_mock).to receive(:each).with(no_args()).and_yield("one", 1, "2") expect(registry.data_exists?(key_path, value1)).to eq(false) end end describe "value_exists!" do it "does nothing if the value exists" do expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(true) registry.value_exists!(key_path, value1) end it "throws an exception if the value does not exist" do expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false) expect { registry.value_exists!(key_path, value1) }.to raise_error(Chef::Exceptions::Win32RegValueMissing) end end describe "data_exists!" do it "does nothing if the data exists" do expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(true) registry.data_exists!(key_path, value1) end it "throws an exception if the data does not exist" do expect(registry).to receive(:data_exists?).with(key_path, value1).and_return(false) expect { registry.data_exists!(key_path, value1) }.to raise_error(Chef::Exceptions::Win32RegDataMissing) end end describe "type_matches?" do it "returns true if type matches" do expect(registry).to receive(:value_exists!).with(key_path, value1).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(registry).to receive(:get_type_from_name).with(:string).and_return(1) expect(reg_mock).to receive(:each).and_yield("one", 1) expect(registry.type_matches?(key_path, value1)).to eq(true) end it "returns false if type does not match" do expect(registry).to receive(:value_exists!).with(key_path, value1).and_return(true) expect(registry).to receive(:get_hive_and_key).with(key_path).and_return([hive_mock, key]) expect(hive_mock).to receive(:open).with(key, ::Win32::Registry::KEY_READ | registry.registry_system_architecture).and_yield(reg_mock) expect(reg_mock).to receive(:each).and_yield("two", 2) expect(registry.type_matches?(key_path, value1)).to eq(false) end it "throws an exception if value does not exist" do expect(registry).to receive(:value_exists?).with(key_path, value1).and_return(false) expect { registry.type_matches?(key_path, value1) }.to raise_error(Chef::Exceptions::Win32RegValueMissing) end end describe "type_matches!" do it "does nothing if the type_matches" do expect(registry).to receive(:type_matches?).with(key_path, value1).and_return(true) registry.type_matches!(key_path, value1) end it "throws an exception if the type does not match" do expect(registry).to receive(:type_matches?).with(key_path, value1).and_return(false) expect { registry.type_matches!(key_path, value1) }.to raise_error(Chef::Exceptions::Win32RegTypesMismatch) end end describe "keys_missing?" do it "returns true if the keys are missing" do expect(registry).to receive(:key_exists?).with(missing_key_path).and_return(false) expect(registry.keys_missing?(key_path)).to eq(true) end it "returns false if no keys in the path are missing" do expect(registry).to receive(:key_exists?).with(missing_key_path).and_return(true) expect(registry.keys_missing?(key_path)).to eq(false) end end end chef-12.14.60/spec/unit/windows_service_spec.rb000066400000000000000000000066201276456504500213560ustar00rootroot00000000000000# # Author:: Mukta Aphale () # Copyright:: Copyright 2013-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" if Chef::Platform.windows? require "chef/application/windows_service" end describe "Chef::Application::WindowsService", :windows_only do let(:shell_out_result) { double("shellout", stdout: nil, stderr: nil) } let(:config_options) do { log_location: STDOUT, config_file: "test_config_file", log_level: :info, } end let(:timeout) { 7200 } let(:shellout_options) do { :timeout => timeout, :logger => Chef::Log, } end before do Chef::Config.merge!(config_options) allow(subject).to receive(:configure_chef) allow(subject).to receive(:parse_options) allow(MonoLogger).to receive(:new) allow(subject).to receive(:running?).and_return(true, false) allow(subject).to receive(:state).and_return(4) subject.service_init end subject { Chef::Application::WindowsService.new } it "passes DEFAULT_LOG_LOCATION to chef-client instead of STDOUT" do expect(subject).to receive(:shell_out).with( "chef-client.bat --no-fork -c test_config_file -L #{Chef::Application::WindowsService::DEFAULT_LOG_LOCATION}", shellout_options ).and_return(shell_out_result) subject.service_main end context "has a log location configured" do let(:tempfile) { Tempfile.new "log_file" } let(:config_options) do { log_location: tempfile.path, config_file: "test_config_file", log_level: :info, } end after do tempfile.unlink end it "uses the configured log location" do expect(subject).to receive(:shell_out).with( "chef-client.bat --no-fork -c test_config_file -L #{tempfile.path}", shellout_options ).and_return(shell_out_result) subject.service_main end context "configured to Event Logger" do let(:config_options) do { log_location: Chef::Log::WinEvt.new, config_file: "test_config_file", log_level: :info, } end it "does not pass log location to new process" do expect(subject).to receive(:shell_out).with( "chef-client.bat --no-fork -c test_config_file", shellout_options ).and_return(shell_out_result) subject.service_main end end end context "configueres a watchdog timeout" do let(:timeout) { 10 } before do Chef::Config[:windows_service][:watchdog_timeout] = 10 end it "passes watchdog timeout to new process" do expect(subject).to receive(:shell_out).with( "chef-client.bat --no-fork -c test_config_file -L #{Chef::Application::WindowsService::DEFAULT_LOG_LOCATION}", shellout_options ).and_return(shell_out_result) subject.service_main end end end chef-12.14.60/tasks/000077500000000000000000000000001276456504500140155ustar00rootroot00000000000000chef-12.14.60/tasks/bin/000077500000000000000000000000001276456504500145655ustar00rootroot00000000000000chef-12.14.60/tasks/bin/bundle-platform000077500000000000000000000012261276456504500176070ustar00rootroot00000000000000#!/usr/bin/env ruby require_relative "bundler_patch" platforms = ARGV.shift platforms = platforms.split(" ").map { |p| Gem::Platform.new(p) } Gem::Platform.instance_eval { @local = platforms.last } old_platforms = Gem.platforms Gem.platforms = platforms puts "bundle-platform set Gem.platforms to #{Gem.platforms.map { |p| p.to_s }} (was #{old_platforms.map { |p| p.to_s } })" desired_version = ARGV.shift.delete("_") # The rest of this is a normal bundler binstub require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", Pathname.new(__FILE__).realpath) require "rubygems" load Gem.bin_path("bundler", "bundle", desired_version) chef-12.14.60/tasks/bin/bundle-platform.bat000066400000000000000000000000331276456504500203440ustar00rootroot00000000000000@ECHO OFF ruby "%~dpn0" %* chef-12.14.60/tasks/bin/bundler_patch.rb000066400000000000000000000022141276456504500177230ustar00rootroot00000000000000# This is a temporary monkey patch to address https://github.com/bundler/bundler/issues/4896 # the heart of the fix is line #18 with the addition of: # && (possibility.activated - existing_node.payload.activated).empty? # This ensures we do not mis linux platform gems in some scenarios like ffi in kitchen-test. # There is a permanent fix to bundler (See https://github.com/bundler/bundler/pull/4836) which # is due to ship in v1.14. Once we adopt that version, we can remove this file require "bundler" require "bundler/vendor/molinillo/lib/molinillo/resolution" module Bundler::Molinillo class Resolver # A specific resolution from a given {Resolver} class Resolution def attempt_to_activate debug(depth) { "Attempting to activate " + possibility.to_s } existing_node = activated.vertex_named(name) if existing_node.payload && (possibility.activated - existing_node.payload.activated).empty? debug(depth) { "Found existing spec (#{existing_node.payload})" } attempt_to_activate_existing_spec(existing_node) else attempt_to_activate_new_spec end end end end end chef-12.14.60/tasks/bin/create-override-gemfile000077500000000000000000000103171276456504500212030ustar00rootroot00000000000000#!/usr/bin/env ruby require "rubygems" require "bundler" Bundler.with_clean_env do require_relative "../gemfile_util" options = {} opts = OptionParser.new do |opts| opts.banner = "Usage: create-override-gemfile [OPTIONS]" opts.on("--gemfile GEMFILE", "The Gemfile to read (default: Gemfile).") { |path| options[:gemfile_path] = path } opts.on("--lockfile GEMFILE", "The lockfile to read (default: .lock).") { |path| options[:lockfile_path] = path } opts.on("--group GROUP", "Groups to include (whitelist).") do |group| options[:groups] ||= [] options[:groups] << group.to_sym end opts.on("--without GROUP", "Groups to exclude.") do |group| options[:without_groups] ||= [] options[:without_groups] << group.to_sym end opts.on("--gem GEM", "Gems to include regardless of groups.") do |name| options[:gems] ||= [] options[:gems] << name end opts.on("--relative-to PATH", "A path to prepend to any relative paths in the Gemfile.") do |path| unless Pathname.new(path).absolute? puts opts raise "--relative-to #{path} was not an absolute path!" end options[:relative_to] = path end opts.on("--[no-]copy-groups", "Whether to copy groups over from the original Gemfile or not (default: false).") { |val| options[:copy_groups] = val } opts.on("--[no-]override", "Whether to emit override: true on each gem line (default: false).") { |val| options[:override] = val } opts.on("-h", "--help", "Print this message.") do puts opts exit(0) end end args = opts.parse(ARGV) if args.size > 0 puts opts raise "Invalid arguments #{args}" end def create_override_gemfile(gemfile_path: "Gemfile", lockfile_path: "#{gemfile_path}.lock", groups: nil, without_groups: nil, gems: [], copy_groups: false, relative_to: ".", override: false) relative_to = Pathname.new(relative_to).realpath # Select the gems we want bundle = GemfileUtil::Bundle.parse(gemfile_path, lockfile_path) gems_to_include = bundle.select_gems(groups: groups, without_groups: without_groups) gems.each do |name| raise "Requested gem #{name} is not in #{gemfile_path}.lock!" if !bundle.gems[name] gems_to_include[name] ||= bundle.gems[name] gems_to_include[name][:dependencies].each do |dep| gems_to_include[name] ||= bundle.gems[dep] end end # Add the gems to the Gemfile gem_root = Pathname.new(gemfile_path).dirname.realpath gems_to_include.sort_by { |name, options| options[:declared_groups].empty? ? 1 : 0 }.each do |name, options| comment = nil options = options.dup version = options.delete(:version) if copy_groups # Some dependencies have no groups (are not in the Gemfile--just runtime # dependencies). If we actually record that they have no groups, they # will *always* be installed (or perhaps never). We only want them to # install if their other deps do, so we mark them with the groups of the # things that brought them in (the gems that depended on them). To do # this, we just leave :groups intact. if options[:declared_groups].empty? options.delete(:declared_groups) comment = " # Transitive dependency, not actually in original Gemfile" else # For other things, we want to copy the actual declared_groups--the # ones that were in the Gemfile. We want the same --with and --without # options to include and exclude them as worked with the original # Gemfile. options[:groups] = options.delete(:declared_groups) end else options.delete(:groups) options.delete(:declared_groups) end options.delete(:dependencies) options.delete(:development_dependencies) options[:override] = true if override options[:path] = Pathname.new(options[:path]).expand_path(gem_root).relative_path_from(relative_to).to_s if options[:path] line = "gem #{name.inspect}, #{version.inspect}" options.each do |name, value| line << ", #{name}: #{value.inspect}" end line << comment if comment puts line end end create_override_gemfile(options) end chef-12.14.60/tasks/bin/gem-version-diff000077500000000000000000000023711276456504500176570ustar00rootroot00000000000000#!/usr/bin/env ruby require_relative "../../version_policy" old_version, new_version = ARGV[0..1] require "set" ENV["BUNDLE_WITHOUT"] = INSTALL_WITHOUT_GROUPS.join(":") relevant_gems = Set.new `bundle list`.each_line do |line| next unless line =~ /^ \* (\S+)/ relevant_gems.add($1) end old_gems = {} old_file = `git show #{old_version}:Gemfile.lock` old_file.each_line do |line| next unless line =~ /^ (\S+) \(([^\)]+)\)$/ next unless relevant_gems.include?($1) old_gems[$1] = $2 end new_gems = {} new_file = `git show #{new_version}:Gemfile.lock` new_file.each_line do |line| next unless line =~ /^ (\S+) \(([^\)]+)\)$/ next unless relevant_gems.include?($1) new_gems[$1] = $2 end modified_gems = (old_gems.keys & new_gems.keys).sort.select { |name| new_gems[name] != old_gems[name] }.map { |name| "#{name} - #{new_gems[name]} (was #{old_gems[name]})" } removed_gems = (old_gems.keys - new_gems.keys).sort.map { |name| "#{name} - #{old_gems[name]}" } added_gems = (new_gems.keys - old_gems.keys).sort.map { |name| "#{name} - #{new_gems[name]}" } puts "MODIFIED:\n#{modified_gems.join("\n")}" if modified_gems.any? puts "\nADDED:\n#{added_gems.join("\n")}" if added_gems.any? puts "\nREMOVED:\n#{removed_gems.join("\n")}" if removed_gems.any? chef-12.14.60/tasks/bin/run_external_test000077500000000000000000000025661276456504500202710ustar00rootroot00000000000000#!/bin/bash # Fail fast (e) and echo commands (vx) set -evx # Arguments TEST_GEM=$1 shift PROJECT_ROOT=$(pwd) PROJECT_BUNDLE_PATH=${BUNDLE_PATH:-$(grep BUNDLE_PATH: $PROJECT_ROOT/.bundle/config | cut -d' ' -f2-)} if [ -n "$PROJECT_BUNDLE_PATH" ]; then PROJECT_BUNDLE_PATH=$PROJECT_ROOT/$PROJECT_BUNDLE_PATH fi TEST_GEM_ROOT=$(bundle show $TEST_GEM) # Make a copy of the original Gemfile and stitch in our Gemfile.lock TEST_GEMFILE=$TEST_GEM_ROOT/Gemfile MODIFIED_TEST_GEMFILE=$TEST_GEMFILE.externaltest cat < $MODIFIED_TEST_GEMFILE require_relative "$PROJECT_ROOT/tasks/gemfile_util" GemfileUtil.include_locked_gemfile(self, "$PROJECT_ROOT/Gemfile", gems: ["$TEST_GEM"] + "$TEST_WITH_GEMS".split(/\s+/)) $TEST_GEM_OVERRIDES EOM cat $TEST_GEMFILE >> $MODIFIED_TEST_GEMFILE if [ -f $TEST_GEMFILE.lock ]; then cp $TEST_GEMFILE.lock $MODIFIED_TEST_GEMFILE.lock elif [ -f $MODIFIED_TEST_GEMFILE.lock ]; then rm -f $MODIFIED_TEST_GEMFILE.lock fi # Run the bundle install cd $TEST_GEM_ROOT export BUNDLE_GEMFILE=$MODIFIED_TEST_GEMFILE # Don't read from the project .bundle/config, just our env vars export BUNDLE_IGNORE_CONFIG=true # Use the top level bundle cache so we don't have to reinstall their packages if [ -n "$PROJECT_BUNDLE_PATH" ]; then export BUNDLE_PATH=$PROJECT_BUNDLE_PATH fi export BUNDLE_FROZEN= bundle install export BUNDLE_FROZEN=true bundle config bundle exec $@ chef-12.14.60/tasks/bundle.rb000066400000000000000000000072161276456504500156210ustar00rootroot00000000000000# # Copyright:: Copyright (c) 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_relative "bundle_util" require_relative "../version_policy" require "fileutils" desc "Tasks to work with the main Gemfile and Gemfile." namespace :bundle do desc "Update Gemfile.lock and all Gemfile..locks (or one or more gems via bundle:update gem1 gem2 ...)." task :update, [:args] do |t, rake_args| extend BundleUtil args = rake_args[:args] || "" with_bundle_unfrozen do puts "" puts "-------------------------------------------------------------------" puts "Updating Gemfile.lock ..." puts "-------------------------------------------------------------------" bundle "update #{args}" platforms.each do |platform| bundle "update #{args}", platform: platform end end end desc "Conservatively update Gemfile.lock and all Gemfile..locks" task :install, [:args] do |t, rake_args| extend BundleUtil args = rake_args[:args] || "" with_bundle_unfrozen do puts "" puts "-------------------------------------------------------------------" puts "Updating Gemfile.lock ..." puts "-------------------------------------------------------------------" bundle "install #{args}" platforms.each do |platform| bundle "lock", platform: platform end end end # Find out if we're using the latest gems we can (so we don't regress versions) desc "Check for gems that are not at the latest released version, and report if anything not in ACCEPTABLE_OUTDATED_GEMS (version_policy.rb) is out of date." task :outdated do extend BundleUtil puts "" puts "-------------------------------------------------------------------" puts "Checking for outdated gems ..." puts "-------------------------------------------------------------------" # TODO check for outdated windows gems too with_bundle_unfrozen do bundle_outdated = bundle("outdated", extract_output: true) puts bundle_outdated outdated_gems = parse_bundle_outdated(bundle_outdated).map { |line, gem_name| gem_name } # Weed out the acceptable ones outdated_gems = outdated_gems.reject { |gem_name| ACCEPTABLE_OUTDATED_GEMS.include?(gem_name) } if outdated_gems.empty? puts "" puts "SUCCESS!" else raise "ERROR: outdated gems: #{outdated_gems.join(", ")}. Either fix them or add them to ACCEPTABLE_OUTDATED_GEMS in #{__FILE__}." end end end end desc "Run bundle with arbitrary args against the given platform; e.g. rake bundle[show]. No platform to run against the main bundle; bundle[show,windows] to run the windows one; bundle[show,*] to run against all non-default platforms." task :bundle, [:args, :platform] do |t, rake_args| extend BundleUtil args = rake_args[:args] || "" platform = rake_args[:platform] with_bundle_unfrozen do if platform == "*" platforms.each do |platform| bundle args, platform: platform end elsif platform bundle args, platform: platform else bundle args end end end chef-12.14.60/tasks/bundle_util.rb000066400000000000000000000060501276456504500166510ustar00rootroot00000000000000require "bundler" require "shellwords" module BundleUtil PLATFORMS = { "windows" => %w{ruby x86-mingw32} } def project_root File.expand_path("../..", __FILE__) end def bundle_platform File.join(project_root, "tasks", "bin", "bundle-platform") end # Parse the output of "bundle outdated" and get the list of gems that # were outdated def parse_bundle_outdated(bundle_outdated_output) result = [] bundle_outdated_output.each_line do |line| if line =~ /^\s*\* (.+) \(newest ([^,]+), installed ([^,)])*/ gem_name, newest_version, installed_version = $1, $2, $3 result << [ line, gem_name ] end end result end def with_bundle_unfrozen(cwd: nil, leave_frozen: false) bundle "config --delete frozen", cwd: cwd begin yield ensure bundle "config --local frozen 1", cwd: cwd unless leave_frozen end end # Run bundle-platform with the given ruby platform(s) def bundle(args, gemfile: nil, platform: nil, cwd: nil, extract_output: false, delete_gemfile_lock: false) args = args.split(/\s+/) if cwd prefix = "[#{cwd}] " end cwd = File.expand_path(cwd || ".", project_root) Bundler.with_clean_env do Dir.chdir(cwd) do gemfile ||= "Gemfile" gemfile = File.expand_path(gemfile, cwd) raise "No platform #{platform} (supported: #{PLATFORMS.keys.join(", ")})" if platform && !PLATFORMS[platform] # First delete the gemfile.lock if delete_gemfile_lock if File.exist?("#{gemfile}.lock") puts "Deleting #{gemfile}.lock ..." File.delete("#{gemfile}.lock") end end # Run the bundle command ruby_platforms = platform ? PLATFORMS[platform].join(" ") : "ruby" cmd = Shellwords.join([ Gem.ruby, "-S", bundle_platform, ruby_platforms, "_#{desired_bundler_version}_", *args, ]) puts "#{prefix}#{Shellwords.join(["bundle", *args])}#{platform ? " for #{platform} platform" : ""}:" with_gemfile(gemfile) do puts "#{prefix}BUNDLE_GEMFILE=#{gemfile}" puts "#{prefix}> #{cmd}" if extract_output `#{cmd}` else unless system(bundle_platform, ruby_platforms, "_#{desired_bundler_version}_", *args) raise "#{bundle_platform} failed: exit code #{$?}" end end end end end end def with_gemfile(gemfile) old_gemfile = ENV["BUNDLE_GEMFILE"] ENV["BUNDLE_GEMFILE"] = gemfile begin yield ensure if old_gemfile ENV["BUNDLE_GEMFILE"] = old_gemfile else ENV.delete("BUNDLE_GEMFILE") end end end def platforms PLATFORMS.keys end def desired_bundler_version @desired_bundler_version ||= begin omnibus_overrides = File.join(project_root, "omnibus_overrides.rb") File.readlines(omnibus_overrides).each do |line| return $1 if line =~ /^override :bundler, version: "(.+)"$/ end end end end chef-12.14.60/tasks/cbgb.rb000066400000000000000000000053071276456504500152440ustar00rootroot00000000000000# # Author:: Thom May (tmay@chef.io) # Author:: Nathen Harvey (nharvey@chef.io) # Copyright:: Copyright 2015-2016, Chef Software, Inc # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "rake" CBGB_SOURCE = File.join(File.dirname(__FILE__), "..", "CBGB.toml") CBGB_TARGET = File.join(File.dirname(__FILE__), "..", "CBGB.md") begin require "tomlrb" task :default => :generate namespace :cbgb do desc "Generate MarkDown version of CBGB file" task :generate do cbgb = Tomlrb.load_file CBGB_SOURCE out = "\n" out << "\n\n" out << "# " + cbgb["Preamble"]["title"] + "\n\n" out << cbgb["Preamble"]["text"] + "\n" out << "# Board of Governors\n\n" out << "## " + cbgb["Org"]["Lead"]["title"] + "\n\n" out << person(cbgb["people"], cbgb["Org"]["Lead"]["person"]) + "\n\n" out << "### " + cbgb["Org"]["Contributors"]["title"] + "\n\n" out << cbgb(cbgb["people"], cbgb["Org"]["Contributors"]["governers"]) + "\n\n" out << "### " + cbgb["Org"]["Corporate-Contributors"]["title"] + "\n\n" out << cbgb(cbgb["corporations"], cbgb["Org"]["Corporate-Contributors"]["governers"]) + "\n\n" out << "### " + cbgb["Org"]["Lieutenants"]["title"] + "\n\n" out << cbgb(cbgb["people"], cbgb["Org"]["Lieutenants"]["governers"]) + "\n\n" File.open(CBGB_TARGET, "w") do |fn| fn.write out end end end def components(list, cmp) out = "" cmp.each do |k, v| out << "\n#### #{v['title'].gsub('#', '\\#')}\n" out << cbgb(list, v["cbgb"]) end out end def cbgb(list, people) o = "" people.each do |p| o << person(list, p) + "\n" end o end def person(list, person) if list[person].has_key?("GitHub") out = "* [#{list[person]["Name"]}](https://github.com/#{list[person]["GitHub"]})" else out = "* #{list[person]["Name"]}" end if list[person].has_key?("Person") out << " - #{list[person]["Person"]}" end out end rescue LoadError STDERR.puts "\n*** TomlRb not available.\n\n" end chef-12.14.60/tasks/changelog.rb000066400000000000000000000011511276456504500162670ustar00rootroot00000000000000begin require "github_changelog_generator/task" GitHubChangelogGenerator::RakeTask.new :changelog do |config| config.issues = false config.future_release = Chef::VERSION config.enhancement_labels = "enhancement,Enhancement,New Feature,Feature".split(",") config.bug_labels = "bug,Bug,Improvement,Upstream Bug".split(",") config.exclude_labels = "duplicate,question,invalid,wontfix,no_changelog,Exclude From Changelog,Question,Discussion".split(",") end rescue LoadError puts "github_changelog_generator is not available. gem install github_changelog_generator to generate changelogs" end chef-12.14.60/tasks/dependencies.rb000066400000000000000000000124211276456504500167700ustar00rootroot00000000000000# # Copyright:: Copyright (c) 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_relative "bundle_util" require_relative "bundle" require_relative "../version_policy" desc "Tasks to update and check dependencies" namespace :dependencies do # Update all dependencies to the latest constraint-matching version desc "Update all dependencies. dependencies:update to update as little as possible." task :update => %w{ dependencies:update_gemfile_lock dependencies:update_omnibus_overrides dependencies:update_omnibus_gemfile_lock dependencies:update_acceptance_gemfile_lock dependencies:update_kitchen_tests_gemfile_lock dependencies:update_kitchen_tests_berksfile_lock } desc "Update Gemfile.lock and all Gemfile..locks." task :update_gemfile_lock do |t, rake_args| Rake::Task["bundle:update"].invoke end def gemfile_lock_task(task_name, dirs: [], other_platforms: true, leave_frozen: true) dirs.each do |dir| desc "Update #{dir}/Gemfile.lock." task task_name do |t, rake_args| extend BundleUtil puts "" puts "-------------------------------------------------------------------" puts "Updating #{dir}/Gemfile.lock ..." puts "-------------------------------------------------------------------" with_bundle_unfrozen(cwd: dir, leave_frozen: leave_frozen) do bundle "install", cwd: dir, delete_gemfile_lock: true if other_platforms # Include all other supported platforms into the lockfile as well platforms.each do |platform| bundle "lock", cwd: dir, platform: platform end end end end end end def berksfile_lock_task(task_name, dirs: []) dirs.each do |dir| desc "Update #{dir}/Berksfile.lock." task task_name do |t, rake_args| extend BundleUtil puts "" puts "-------------------------------------------------------------------" puts "Updating #{dir}/Berksfile.lock ..." puts "-------------------------------------------------------------------" if File.exist?("#{project_root}/#{dir}/Berksfile.lock") File.delete("#{project_root}/#{dir}/Berksfile.lock") end Dir.chdir("#{project_root}/#{dir}") do Bundler.with_clean_env do sh "bundle exec berks install" end end end end end gemfile_lock_task :update_omnibus_gemfile_lock, dirs: %w{omnibus} gemfile_lock_task :update_acceptance_gemfile_lock, dirs: %w{acceptance}, other_platforms: false, leave_frozen: false gemfile_lock_task :update_kitchen_tests_gemfile_lock, dirs: %w{ kitchen-tests } berksfile_lock_task :update_kitchen_tests_berksfile_lock, dirs: %w{ kitchen-tests kitchen-tests/cookbooks/audit_test } desc "Update omnibus overrides, including versions in version_policy.rb and latest version of gems: #{OMNIBUS_RUBYGEMS_AT_LATEST_VERSION.keys}." task :update_omnibus_overrides do |t, rake_args| puts "" puts "-------------------------------------------------------------------" puts "Updating omnibus_overrides.rb ..." puts "-------------------------------------------------------------------" # Generate the new overrides file overrides = "# DO NOT EDIT. Generated by \"rake dependencies\". Edit version_policy.rb instead.\n" # Replace the bundler and rubygems versions OMNIBUS_RUBYGEMS_AT_LATEST_VERSION.each do |override_name, gem_name| # Get the latest bundler version puts "Running gem list -r #{gem_name} ..." gem_list = `gem list -r #{gem_name}` unless gem_list =~ /^#{gem_name}\s*\(([^)]*)\)$/ raise "gem list -r #{gem_name} failed with output:\n#{gem_list}" end # Emit it puts "Latest version of #{gem_name} is #{$1}" overrides << "override #{override_name.inspect}, version: #{$1.inspect}\n" end # Add explicit overrides OMNIBUS_OVERRIDES.each do |override_name, version| overrides << "override #{override_name.inspect}, version: #{version.inspect}\n" end # Write the file out (if changed) overrides_path = File.expand_path("../../omnibus_overrides.rb", __FILE__) if overrides != IO.read(overrides_path) puts "Overrides changed!" puts `git diff #{overrides_path}` puts "Writing modified #{overrides_path} ..." IO.write(overrides_path, overrides) end end end desc "Update all dependencies and check for outdated gems." task :dependencies => [ "dependencies:update", "bundle:outdated" ] task :update => [ "dependencies:update", "bundle:outdated"] chef-12.14.60/tasks/gemfile_util.rb000066400000000000000000000363011276456504500170120ustar00rootroot00000000000000require "rubygems" require "bundler" require "shellwords" require "set" module GemfileUtil # # Adds `override: true`, which allows your statement to override any other # gem statement about the same gem in the Gemfile. # def gem(name, *args) options = args[-1].is_a?(Hash) ? args[-1] : {} # Unless we're finished with everything, ignore gems that are being overridden unless overridden_gems == :finished # If it's a path or override gem, it overrides whatever else is there. if options[:path] || options[:override] options.delete(:override) warn_if_replacing(name, overridden_gems[name], args) overridden_gems[name] = args return # If there's an override gem, and we're *not* an override gem, don't do anything elsif overridden_gems[name] warn_if_replacing(name, args, overridden_gems[name]) return end end # Otherwise, add the gem normally super rescue puts $!.backtrace raise end def overridden_gems @overridden_gems ||= {} end # # Just before we finish the Gemfile, finish up the override gems # def to_definition(*args) complete_overrides super end def complete_overrides to_override = overridden_gems unless to_override == :finished @overridden_gems = :finished to_override.each do |name, args| gem name, *args end end end # # Include all gems in the locked gemfile. # # @param gemfile_path Path to the Gemfile to load (relative to your Gemfile) # @param lockfile_path Path to the Gemfile to load (relative to your Gemfile). # Defaults to .lock. # @param groups A list of groups to include (whitelist). If not passed (or set # to nil), all gems will be selected. # @param without_groups A list of groups to ignore. Gems will be excluded from # the results if all groups they belong to are ignored. This matches # bundler's `without` behavior. # @param gems A list of gems to include above and beyond the given groups. # Gems in this list must be explicitly included in the Gemfile # with a `gem "gem_name", ...` line or they will be silently # ignored. # @param copy_groups Whether to copy the groups over from the old lockfile to # the new. Use this when the new lockfile has the same convention for # groups as the old. Defaults to `false`. # def include_locked_gemfile(gemfile_path, lockfile_path = "#{gemfile_path}.lock", groups: nil, without_groups: nil, gems: [], copy_groups: false) # Parse the desired lockfile gemfile_path = Pathname.new(gemfile_path).expand_path(Bundler.default_gemfile.dirname).realpath lockfile_path = Pathname.new(lockfile_path).expand_path(Bundler.default_gemfile.dirname).realpath # Calculate relative_to relative_to = Bundler.default_gemfile.dirname.realpath # Call out to create-override-gemfile to read the Gemfile+Gemfile.lock (bundler does not work well if you do two things in one process) create_override_gemfile_bin = File.expand_path("../bin/create-override-gemfile", __FILE__) arguments = [ "--gemfile", gemfile_path, "--lockfile", lockfile_path, "--override" ] arguments += [ "--relative-to", relative_to ] if relative_to != "." arguments += Array(groups).flat_map { |group| [ "--group", group ] } arguments += Array(without_groups).flat_map { |without| [ "--without", without ] } arguments += Array(gems).flat_map { |name| [ "--gem", name ] } arguments << "--copy-groups" if copy_groups cmd = Shellwords.join([ Gem.ruby, "-S", create_override_gemfile_bin, *arguments ]) output = nil Bundler.ui.info("> #{cmd}") Bundler.with_clean_env do output = `#{cmd}` end instance_eval(output, cmd, 1) end # # Include all gems in the locked gemfile. # # @param current_gemfile The Gemfile you are currently loading (`self`). # @param gemfile_path Path to the Gemfile to load (relative to your Gemfile) # @param lockfile_path Path to the Gemfile to load (relative to your Gemfile). # Defaults to .lock. # @param groups A list of groups to include (whitelist). If not passed (or set # to nil), all gems will be selected. # @param without_groups A list of groups to ignore. Gems will be excluded from # the results if all groups they belong to are ignored. This matches # bundler's `without` behavior. # @param gems A list of gems to include above and beyond the given groups. # Gems in this list must be explicitly included in the Gemfile # with a `gem "gem_name", ...` line or they will be silently # ignored. # @param copy_groups Whether to copy the groups over from the old lockfile to # the new. Use this when the new lockfile has the same convention for # groups as the old. Defaults to `false`. # def self.include_locked_gemfile(current_gemfile, gemfile_path, lockfile_path = "#{gemfile_path}.lock", groups: nil, without_groups: nil, gems: [], copy_groups: false) current_gemfile.instance_eval do extend GemfileUtil include_locked_gemfile(gemfile_path, lockfile_path, groups: groups, without_groups: without_groups, gems: gems, copy_groups: copy_groups) end end def warn_if_replacing(name, old_args, new_args) return if !old_args || !new_args if args_to_dep(name, *old_args) =~ args_to_dep(name, *new_args) Bundler.ui.debug "Replaced Gemfile dependency #{name} (#{old_args}) with (#{new_args})" else Bundler.ui.warn "Replaced Gemfile dependency #{name} (#{old_args}) with (#{new_args})" end end def args_to_dep(name, *version, **options) version = [">= 0"] if version.empty? Bundler::Dependency.new(name, version, options) end # # Reads a bundle, including a gemfile and lockfile. # # Does no validation, does not update the lockfile or its gems in any way. # class Bundle # # Parse the given gemfile/lockfile pair. # # @return [Bundle] The parsed bundle. # def self.parse(gemfile_path, lockfile_path = "#{gemfile_path}.lock") result = new(gemfile_path, lockfile_path) result.gems result end # # Create a new Bundle to parse the given gemfile/lockfile pair. # def initialize(gemfile_path, lockfile_path = "#{gemfile_path}.lock") @gemfile_path = gemfile_path @lockfile_path = lockfile_path end # # The path to the Gemfile # attr_reader :gemfile_path # # The path to the Lockfile # attr_reader :lockfile_path # # The list of gems. # # @return [Hash] The resulting gems, where key = gem_name, and the # hash has: # - version: version of the gem. # - source info (:source/:git/:ref/:path) from the lockfile # - dependencies: A list of gem names this gem has a runtime # dependency on. Dependencies are transitive: if A depends on B, # and B depends on C, then A has C in its :dependencies list. # - development_dependencies: - A list of gem names this gem has a # development dependency on. Dependencies are transitive: if A # depends on B, and B depends on C, then A has C in its # :development_dependencies list. development dependencies *include* # runtime dependencies. # - groups: The list of groups (symbols) this gem is in. Groups # are transitive: if A has a runtime dependency on B, and A is # in group X, then B is also in group X. # - declared_groups: The list of groups (symbols) this gem was # declared in the Gemfile. # def gems @gems ||= begin gems = locks.dup gems.each do |name, g| if gem_declarations.has_key?(name) g[:declared_groups] = gem_declarations[name][:groups] else g[:declared_groups] = [] end g[:groups] = g[:declared_groups].dup end # Transitivize groups (since dependencies are already transitive, this is easy) gems.each do |name, g| g[:dependencies].each do |dep| gems[dep][:groups] |= gems[name][:declared_groups].dup end end gems end end # # Get the gems (and their deps) in the given group. # # @param groups A list of groups to include (whitelist). If not passed (or set # to nil), all gems will be selected. # @param without_groups A list of groups to ignore. Gems will be excluded from # the results if all groups they belong to are ignored. # This matches bundler's `without` behavior. # @param gems A list of gems to include regardless of what groups are included. # # @return Hash[String, Hash] The resulting gems, where key = gem_name, and the # hash has: # - version: version of the gem. # - source info (:source/:git/:ref/:path) from the lockfile # - dependencies: A list of gem names this gem has a runtime # dependency on. Dependencies are transitive: if A depends on B, # and B depends on C, then A has C in its :dependencies list. # - development_dependencies: - A list of gem names this gem has a # development dependency on. Dependencies are transitive: if A # depends on B, and B depends on C, then A has C in its # :development_dependencies list. development dependencies # *include* runtime dependencies. # - groups: The list of groups (symbols) this gem is in. Groups # are transitive: if A has a runtime dependency on B, and A is # in group X, then B is also in group X. # - declared_groups: The list of groups (symbols) this gem was # declared in the Gemfile. # def select_gems(groups: nil, without_groups: nil) # First, select the gems that match result = {} gems.each do |name, g| dep_groups = g[:declared_groups] - [ :only_a_runtime_dependency_of_other_gems ] dep_groups = dep_groups & groups if groups dep_groups = dep_groups - without_groups if without_groups if dep_groups.any? result[name] ||= g g[:dependencies].each do |dep| result[dep] ||= gems[dep] end end end result end # # Get all locks from the given lockfile. # # @return Hash[String, Hash] The resulting gems, where key = gem_name, and the # hash has: # - version: version of the gem. # - source info (:source/:git/:ref/:path) # - dependencies: A list of gem names this gem has a runtime # dependency on. Dependencies are transitive: if A depends on B, # and B depends on C, then A has C in its :dependencies list. # - development_dependencies: - A list of gem names this gem has a # development dependency on. Dependencies are transitive: if A # depends on B, and B depends on C, then A has C in its # :development_dependencies list. development dependencies *include* # runtime dependencies. # def locks @locks ||= begin # Grab all the specs from the lockfile locks = {} parsed_lockfile = Bundler::LockfileParser.new(IO.read(lockfile_path)) parsed_lockfile.specs.each do |spec| # Never include bundler, it can't be bundled and doesn't put itself in # the lockfile correctly anyway next if spec.name == "bundler" # Only the platform-specific locks for now (TODO make it possible to emit all locks) next if spec.platform && spec.platform != Gem::Platform::RUBY lock = lock_source_metadata(spec) lock[:version] = spec.version.to_s runtime = spec.dependencies.select { |dep| dep.type == :runtime } lock[:dependencies] = Set.new(runtime.map { |dep| dep.name }) lock[:development_dependencies] = Set.new(spec.dependencies.map { |dep| dep.name }) lock[:dependencies].delete("bundler") lock[:development_dependencies].delete("bundler") locks[spec.name] = lock end # Transitivize the deps. locks.each do |name, lock| # Not all deps were brought over (platform-specific ones) so weed them out lock[:dependencies] &= locks.keys lock[:development_dependencies] &= locks.keys lock[:dependencies] = transitive_dependencies(locks, name, :dependencies) lock[:development_dependencies] = transitive_dependencies(locks, name, :development_dependencies) end locks end end # # Get all desired gems, sans dependencies, from the gemfile. # # @param gemfile Path to the Gemfile to load # # @return Hash An array of hashes where key = gem name and value # has :groups (an array of symbols representing the groups the gem # is in). :groups are not transitive, since we don't know the # dependency tree yet. # def gem_declarations @gem_declarations ||= begin Bundler.with_clean_env do # Set BUNDLE_GEMFILE to the new gemfile temporarily so all bundler's things work # This works around some issues in bundler 1.11.2. ENV["BUNDLE_GEMFILE"] = gemfile_path parsed_gemfile = Bundler::Dsl.new parsed_gemfile.eval_gemfile(gemfile_path) parsed_gemfile.complete_overrides if parsed_gemfile.respond_to?(:complete_overrides) result = {} parsed_gemfile.dependencies.each do |dep| groups = dep.groups.empty? ? [:default] : dep.groups result[dep.name] = { groups: groups, platforms: dep.platforms } end result end end end private # # Given a bunch of locks (name -> { dependencies: [name,name] }) and a # dependency name, add its dependencies to the result transitively. # def transitive_dependencies(locks, name, dep_key, result = Set.new) locks[name][dep_key].each do |dep| # Only ever add a dep once, so we don't infinitely recurse if result.add?(dep) transitive_dependencies(locks, dep, dep_key, result) end end result end # # Get source and version metadata for the given Bundler spec (coming from a lockfile). # # @return Hash { version: , git: , path: , source: , ref: } # def lock_source_metadata(spec) # Copy source information from included Gemfile result = {} case spec.source when Bundler::Source::Rubygems result[:source] = spec.source.remotes.first.to_s when Bundler::Source::Git result[:git] = spec.source.uri.to_s result[:ref] = spec.source.revision when Bundler::Source::Path result[:path] = spec.source.path.to_s else raise "Unknown source #{spec.source} for gem #{spec.name}" end result end end end chef-12.14.60/tasks/maintainers.rb000066400000000000000000000153211276456504500166560ustar00rootroot00000000000000# # Copyright:: Copyright 2015-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "rake" SOURCE = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.toml") TARGET = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.md") # The list of repositories that teams should own REPOSITORIES = ["chef/chef", "chef/chef-census", "chef/chef-repo", "chef/client-docs", "chef/ffi-yajl", "chef/libyajl2-gem", "chef/mixlib-authentication", "chef/mixlib-cli", "chef/mixlib-config", "chef/mixlib-install", "chef/mixlib-log", "chef/mixlib-shellout", "chef/ohai", "chef/omnibus-chef"] begin require "tomlrb" require "octokit" require "pp" namespace :maintainers do task :default => :generate desc "Generate MarkDown version of MAINTAINERS file" task :generate do out = "\n\n" out << "# " + source["Preamble"]["title"] + "\n\n" out << source["Preamble"]["text"] + "\n" # The project lead is a special case out << "# " + source["Org"]["Lead"]["title"] + "\n\n" out << format_person(source["Org"]["Lead"]["person"]) + "\n\n" out << format_components(source["Org"]["Components"]) File.open(TARGET, "w") do |fn| fn.write out end end desc "Synchronize GitHub teams" # there's a special @chef/client-maintainers team that's everyone # and then there's a team per component task :synchronize do Octokit.auto_paginate = true get_github_teams prepare_teams(source["Org"]["Components"].dup) sync_teams! end end def github @github ||= Octokit::Client.new(:netrc => true) end def source @source ||= Tomlrb.load_file SOURCE end def teams @teams ||= { "client-maintainers" => { "title" => "Client Maintainers" } } end def add_members(team, name) teams["client-maintainers"]["members"] ||= [] teams["client-maintainers"]["members"] << name teams[team] ||= {} teams[team]["members"] ||= [] teams[team]["members"] << name end def set_team_title(team, title) teams[team] ||= {} teams[team]["title"] = title end def gh_teams @gh_teams ||= {} end # we have to resolve team names to ids. While we're at it, we can get the privacy # setting, so we know whether we need to update it def get_github_teams github.org_teams("chef").each do |team| gh_teams[team[:slug]] = { "id" => team[:id], "privacy" => team[:privacy] } end end def get_github_team(team) github.team_members(gh_teams[team]["id"]).map do |member| member[:login] end.sort.uniq.map(&:downcase) rescue [] end def create_team(team) puts "creating new github team: #{team} with title: #{teams[team]["title"]} " t = github.create_team("chef", name: team, description: teams[team]["title"], privacy: "closed", repo_names: REPOSITORIES, accept: "application/vnd.github.ironman-preview+json") gh_teams[team] = { "id" => t[:id], "privacy" => t[:privacy] } end def compare_teams(current, desired) # additions are the subtraction of the current state from the desired state # deletions are the subtraction of the desired state from the current state [desired - current, current - desired] end def prepare_teams(cmp) %w{text paths}.each { |k| cmp.delete(k) } if cmp.key?("team") team = cmp.delete("team") add_members(team, cmp.delete("lieutenant")) if cmp.key?("lieutenant") add_members(team, cmp.delete("maintainers")) if cmp.key?("maintainers") set_team_title(team, cmp.delete("title")) else %w{maintainers lieutenant title}.each { |k| cmp.delete(k) } end cmp.each { |_k, v| prepare_teams(v) } end def update_team(team, additions, deletions) create_team(team) unless gh_teams.key?(team) update_team_privacy(team) add_team_members(team, additions) remove_team_members(team, deletions) rescue puts "failed for #{team}" end def update_team_privacy(team) return if gh_teams[team]["privacy"] == "closed" puts "Setting #{team} privacy to closed from #{gh_teams[team]["privacy"]}" github.update_team(gh_teams[team]["id"], privacy: "closed", accept: "application/vnd.github.ironman-preview+json") end def add_team_members(team, additions) additions.each do |member| puts "Adding #{member} to #{team}" github.add_team_membership(gh_teams[team]["id"], member, role: "member", accept: "application/vnd.github.ironman-preview+json") end end def remove_team_members(team, deletions) deletions.each do |member| puts "Removing #{member} from #{team}" github.remove_team_membership(gh_teams[team]["id"], member, accept: "application/vnd.github.ironman-preview+json") end end def sync_teams! teams.each do |name, details| current = get_github_team(name) desired = details["members"].flatten.sort.uniq.map(&:downcase) additions, deletions = compare_teams(current, desired) update_team(name, additions, deletions) end end def get_person(person) source["people"][person] end def format_components(cmp) out = "## " + cmp.delete("title") + "\n\n" out << cmp.delete("text") + "\n" if cmp.has_key?("text") out << "To mention the team, use @chef/#{cmp.delete("team")}\n\n" if cmp.has_key?("team") if cmp.has_key?("lieutenant") out << "### Lieutenant\n\n" out << format_person(cmp.delete("lieutenant")) + "\n\n" end out << format_maintainers(cmp.delete("maintainers")) + "\n" if cmp.has_key?("maintainers") cmp.delete("paths") cmp.each { |k, v| out << format_components(v) } out end def format_maintainers(people) o = "### Maintainers\n\n" people.each do |p| o << format_person(p) + "\n" end o end def format_person(person) mnt = get_person(person) "* [#{mnt["Name"]}](https://github.com/#{mnt["GitHub"]})" end rescue LoadError STDERR.puts "\n*** TomlRb not available.\n\n" end chef-12.14.60/tasks/rspec.rb000066400000000000000000000055501276456504500154630ustar00rootroot00000000000000# # Author:: Adam Jacob () # Author:: Daniel DeLeo () # 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 "rubygems" require "rake" CHEF_ROOT = File.join(File.dirname(__FILE__), "..") begin require "rspec/core/rake_task" desc "Run specs for Chef's Components" task :component_specs do Dir.chdir("chef-config") do Bundler.with_clean_env do sh("bundle install") sh("bundle exec rake spec") end end end task :default => :spec task :spec => :component_specs desc "Run standard specs (minus long running specs)" RSpec::Core::RakeTask.new(:spec) do |t| t.rspec_opts = %w{--profile} # right now this just limits to functional + unit, but could also remove # individual tests marked long-running t.pattern = FileList["spec/**/*_spec.rb"] end namespace :spec do desc "Run all specs in spec directory with RCov" RSpec::Core::RakeTask.new(:rcov) do |t| t.rspec_opts = %w{--profile} t.pattern = FileList["spec/**/*_spec.rb"] t.rcov = true t.rcov_opts = lambda do IO.readlines("#{CHEF_ROOT}/spec/rcov.opts").map { |l| l.chomp.split " " }.flatten end end desc "Run all specs in spec directory" RSpec::Core::RakeTask.new(:all) do |t| t.rspec_opts = %w{--profile} t.pattern = FileList["spec/**/*_spec.rb"] end desc "Print Specdoc for all specs" RSpec::Core::RakeTask.new(:doc) do |t| t.rspec_opts = %w{--format specdoc --dry-run --profile} t.pattern = FileList["spec/**/*_spec.rb"] end desc "Run the specs under spec/unit with activesupport loaded" RSpec::Core::RakeTask.new(:activesupport) do |t| t.rspec_opts = %w{--require active_support/core_ext --profile} # Only node_spec and role_spec specifically have issues, target those tests t.pattern = FileList["spec/unit/node_spec.rb", "spec/unit/role_spec.rb"] end [:unit, :functional, :integration, :stress].each do |sub| desc "Run the specs under spec/#{sub}" RSpec::Core::RakeTask.new(sub) do |t| t.rspec_opts = %w{--profile} t.pattern = FileList["spec/#{sub}/**/*_spec.rb"] end end end rescue LoadError STDERR.puts "\n*** RSpec not available. (sudo) gem install rspec to run unit tests. ***\n\n" end chef-12.14.60/vendor/000077500000000000000000000000001276456504500141655ustar00rootroot00000000000000chef-12.14.60/vendor/bundle/000077500000000000000000000000001276456504500154365ustar00rootroot00000000000000chef-12.14.60/vendor/bundle/bundler/000077500000000000000000000000001276456504500170715ustar00rootroot00000000000000chef-12.14.60/vendor/bundle/bundler/gems/000077500000000000000000000000001276456504500200245ustar00rootroot00000000000000chef-12.14.60/vendor/bundle/bundler/gems/bundler-audit-4e32fca89d75/000077500000000000000000000000001276456504500244115ustar00rootroot00000000000000chef-12.14.60/vendor/bundle/bundler/gems/chefstyle-52a0d55a9e8f/000077500000000000000000000000001276456504500236325ustar00rootroot00000000000000chef-12.14.60/version_policy.rb000066400000000000000000000077331276456504500162730ustar00rootroot00000000000000# # Copyright:: Copyright (c) 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. # # Explicit omnibus overrides. OMNIBUS_OVERRIDES = { # Lower level library pins ## according to comment in omnibus-sw, latest versions don't work on solaris # https://github.com/chef/omnibus-software/blob/aefb7e79d29ca746c3f843673ef5e317fa3cba54/config/software/libtool.rb#L23 "libffi" => "3.2.1", "libiconv" => "1.14", "liblzma" => "5.2.2", ## according to comment in omnibus-sw, the very latest versions don't work on solaris # https://github.com/chef/omnibus-software/blob/aefb7e79d29ca746c3f843673ef5e317fa3cba54/config/software/libtool.rb#L23 "libtool" => "2.4.2", "libxml2" => "2.9.4", "libxslt" => "1.1.29", "libyaml" => "0.1.6", "makedepend" => "1.0.5", "ncurses" => "5.9", "pkg-config-lite" => "0.28-1", "ruby" => "2.3.1", # Leave dev-kit pinned to 4.5 on 32-bit, because 4.7 is 20MB larger and we don't want # to unnecessarily make the client any fatter. (Since it's different between # 32 and 64, we have to do it in the project file still.) # "ruby-windows-devkit" => "4.5.2-20111229-1559", "ruby-windows-devkit-bash" => "3.1.23-4-msys-1.0.18", "util-macros" => "1.19.0", "xproto" => "7.0.28", "zlib" => "1.2.8", ## These can float as they are frequently updated in a way that works for us #override "cacerts" =>"???", "openssl" => "1.0.2h", } # # rake dependencies:update_omnibus_overrides (tasks/dependencies.rb) reads this # and modifies omnibus_overrides.rb # # The left side is the software definition name, and the right side is the # name of the rubygem (gem list -re gets us the latest version). # OMNIBUS_RUBYGEMS_AT_LATEST_VERSION = { rubygems: "rubygems-update", bundler: "bundler", } # # rake dependencies:check (tasks/dependencies.rb) uses this as a list of gems # that are allowed to be outdated according to `bundle updated` # # Once you decide that the list of outdated gems is OK, you can just # add gems to the output of bundle outdated here and we'll parse it to get the # list of outdated gems. # # gherkin - expected to update with new cucumber (and foodcritic?) release # jwt - expected to update with new oauth2 release # mini_portile2 - should go away *entirely* with new nokogiri release (not a dep anymore) # slop - expected to disappear with new pry release # stove - halite pins to ~> 3.2 in 1.2.1 # rubocop - chef-style pins to 0.39.0 in 0.3.1 # ACCEPTABLE_OUTDATED_GEMS = [ "json", # aws-sdk-v1 pins this because Ruby 2.0; chef-provisioning fix to abandon v1 TBD "rubocop", # chefstyle pins this, will often be somewhat behind "slop", # expected to disappear with pry 0.11 "typhoeus", # Until the travis gem updates to 1.0. ] # # Some gems are part of our bundle (must be installed) but not important # enough to lock. We allow `bundle install` in test-kitchen, berks, etc. # to use their own versions of these. # # This mainly tells you which gems `chef verify` allows you to install and # run. # GEMS_ALLOWED_TO_FLOAT = [ ] # # The list of groups we install without: this drives both the `bundle install` # we do in chef-dk, and the `bundle check` we do to ensure installed gems don't # have extra deps hiding in their Gemfiles. # # NOTE: we DO install test, because there aren't many gems there, and it makes # our test phase a lot easier. # INSTALL_WITHOUT_GROUPS = %w{ changelog development docgen guard integration maintenance tools travis style }